diff --git a/crates/ide/src/document_highlight.rs b/crates/ide/src/document_highlight.rs index 9ec7dd31..af5b92e6 100644 --- a/crates/ide/src/document_highlight.rs +++ b/crates/ide/src/document_highlight.rs @@ -33,14 +33,15 @@ pub(crate) fn document_highlight( let hir_file_id = file_id.into(); let parsed_file = sema.parse_file(file_id); let root = parsed_file.root()?; - let selection = crate::source_tokens::token_candidates_at_offset( + let tokens = crate::source_tokens::source_token_resolution_at_offset( db, file_id, root, offset, token_precedence, - )?; - let tokens = selection.tokens()?; + )? + .resolved()? + .into_tokens(); let highlights = tokens .into_iter() .filter_map(|token| highlight_for_token(&sema, file_id, hir_file_id, token, config.clone())) diff --git a/crates/ide/src/goto_declaration.rs b/crates/ide/src/goto_declaration.rs index 8d7dd429..ec696e1f 100644 --- a/crates/ide/src/goto_declaration.rs +++ b/crates/ide/src/goto_declaration.rs @@ -17,14 +17,15 @@ pub(crate) fn goto_declaration( let hir_file_id = file_id.into(); let parsed_file = sema.parse_file(file_id); let root = parsed_file.root()?; - let selection = crate::source_tokens::token_candidates_at_offset( + let selection = crate::source_tokens::source_token_resolution_at_offset( db, file_id, root, offset, goto_definition::token_precedence, - )?; - let (range, tokens) = selection.range_and_tokens()?; + )? + .resolved()?; + let (range, tokens) = selection.into_parts(); let origins = tokens .into_iter() diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 1c317966..b7cdcfbe 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -40,14 +40,15 @@ pub(crate) fn goto_definition( let hir_file_id = file_id.into(); let parsed_file = sema.parse_file(file_id); let root = parsed_file.root()?; - let selection = crate::source_tokens::token_candidates_at_offset( + let selection = crate::source_tokens::source_token_resolution_at_offset( db, file_id, root, offset, token_precedence, - )?; - let (range, tokens) = selection.range_and_tokens()?; + )? + .resolved()?; + let (range, tokens) = selection.into_parts(); let navs = tokens .into_iter() .filter_map(|token| nav_targets_for_token(db, &sema, hir_file_id, token)) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 16ae216d..32997ba4 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -27,12 +27,8 @@ use utils::{ use vfs::FileId; use crate::{ - FilePosition, RangeInfo, - db::root_db::RootDb, - definitions::DefinitionClass, - markup::Markup, - render, - source_tokens::{PreprocTokenSelection, SourceTokenSelection}, + FilePosition, RangeInfo, db::root_db::RootDb, definitions::DefinitionClass, markup::Markup, + render, source_tokens::SourceTokenSelection, }; const MACRO_EXPANSION_SEPARATOR: &str = "--------------------"; @@ -78,31 +74,24 @@ pub(crate) fn hover( let hir_file_id = file_id.into(); let parsed_file = sema.parse_file(file_id); let root = parsed_file.root()?; - let selection = crate::source_tokens::token_candidates_at_offset( + let selection = crate::source_tokens::source_token_resolution_at_offset( db, file_id, root, offset, token_precedence, - )?; - let hover = match selection { - SourceTokenSelection::NormalSyntax(selection) => { - hover_for_token_selection(&sema, hir_file_id, selection.range, selection.tokens) - } - SourceTokenSelection::Preproc(selection) => { - hover_for_preproc_selection(&sema, hir_file_id, selection) - } - SourceTokenSelection::Unavailable(_) | SourceTokenSelection::Ambiguous(_) => None, - }?; + )? + .resolved()?; + let hover = hover_for_source_token_selection(&sema, hir_file_id, selection)?; Some(with_expanded_macro_hover(db, file_id, offset, hover)) } -fn hover_for_preproc_selection( +fn hover_for_source_token_selection( sema: &Semantics, hir_file_id: HirFileId, - selection: PreprocTokenSelection<'_>, + selection: SourceTokenSelection<'_>, ) -> Option> { - let (range, tokens) = selection.range_and_tokens(); + let (range, tokens) = selection.into_parts(); hover_for_token_selection(sema, hir_file_id, range, tokens) } diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index bb97e8f6..521f86d2 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -102,14 +102,15 @@ pub(crate) fn references( let hir_file_id = file_id.into(); let parsed_file = sema.parse_file(file_id); let root = parsed_file.root()?; - let selection = crate::source_tokens::token_candidates_at_offset( + let tokens = crate::source_tokens::source_token_resolution_at_offset( db, file_id, root, offset, token_precedence, - )?; - let tokens = selection.tokens()?; + )? + .resolved()? + .into_tokens(); let references = tokens .into_iter() .filter_map(|token| references_for_token(&sema, hir_file_id, token, config.clone())) diff --git a/crates/ide/src/references/search.rs b/crates/ide/src/references/search.rs index b0c90477..3c63a37b 100644 --- a/crates/ide/src/references/search.rs +++ b/crates/ide/src/references/search.rs @@ -276,22 +276,20 @@ impl<'a, 'b> ReferencesCtx<'a, 'b> { offset: TextSize, source_token_cache: &mut SourceTokenRequestCache, ) -> Vec> { - let Some(selection) = crate::source_tokens::token_candidates_at_offset_with_cache( + let Some(selection) = crate::source_tokens::source_token_resolution_at_offset_with_cache( db, file_id, node, offset, super::token_precedence, source_token_cache, - ) else { + ) + .and_then(|resolution| resolution.resolved()) else { return Vec::new(); }; - let Some(tokens) = selection.tokens() else { - return Vec::new(); - }; - - tokens + selection + .into_tokens() .into_iter() .filter(|tok| tok.kind().name_like()) .filter(|tok| { diff --git a/crates/ide/src/source_tokens.rs b/crates/ide/src/source_tokens.rs index 67cd4517..15e2ea1c 100644 --- a/crates/ide/src/source_tokens.rs +++ b/crates/ide/src/source_tokens.rs @@ -16,58 +16,96 @@ use vfs::FileId; use crate::db::root_db::RootDb; #[derive(Debug, Clone)] -pub(crate) enum SourceTokenSelection<'tree> { - NormalSyntax(NormalSyntaxSelection<'tree>), - Preproc(PreprocTokenSelection<'tree>), - Unavailable(PreprocTokenUnavailable), - Ambiguous(PreprocTokenAmbiguity), +pub(crate) enum SourceTokenResolution<'tree> { + Resolved(SourceTokenSelection<'tree>), + Blocked(SourceTokenBlock), } -impl<'tree> SourceTokenSelection<'tree> { - pub(crate) fn range_and_tokens(self) -> Option<(TextRange, Vec>)> { +impl<'tree> SourceTokenResolution<'tree> { + pub(crate) fn resolved(self) -> Option> { match self { - Self::NormalSyntax(selection) => Some((selection.range, selection.tokens)), - Self::Preproc(selection) => Some(selection.range_and_tokens()), - Self::Unavailable(PreprocTokenUnavailable { range: _ }) => None, - Self::Ambiguous(PreprocTokenAmbiguity { range: _, hits: _ }) => None, + Self::Resolved(selection) => Some(selection), + Self::Blocked(SourceTokenBlock { .. }) => None, } } - - pub(crate) fn tokens(self) -> Option>> { - self.range_and_tokens().map(|(_, tokens)| tokens) - } } #[derive(Debug, Clone)] -pub(crate) struct NormalSyntaxSelection<'tree> { +pub(crate) struct SourceTokenSelection<'tree> { + pub origin: SourceTokenOrigin, pub range: TextRange, pub tokens: Vec>, } -#[derive(Debug, Clone)] -pub(crate) struct PreprocTokenSelection<'tree> { - pub range: TextRange, - pub hits: Vec, - pub tokens: Vec>, -} +impl<'tree> SourceTokenSelection<'tree> { + fn normal_syntax(range: TextRange, tokens: Vec>) -> Self { + Self { origin: SourceTokenOrigin::NormalSyntax, range, tokens } + } + + fn preproc( + range: TextRange, + hits: Vec, + tokens: Vec>, + ) -> Self { + Self { origin: SourceTokenOrigin::Preproc { hits }, range, tokens } + } + + pub(crate) fn into_parts(self) -> (TextRange, Vec>) { + let Self { origin, range, tokens } = self; + match origin { + SourceTokenOrigin::NormalSyntax => (range, tokens), + SourceTokenOrigin::Preproc { hits } => { + let _hit_count = hits.len(); + (range, tokens) + } + } + } -impl<'tree> PreprocTokenSelection<'tree> { - pub(crate) fn range_and_tokens(self) -> (TextRange, Vec>) { - let Self { range, hits, tokens } = self; - let _hit_count = hits.len(); - (range, tokens) + pub(crate) fn into_tokens(self) -> Vec> { + self.into_parts().1 } } #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct PreprocTokenUnavailable { - pub range: TextRange, +pub(crate) enum SourceTokenOrigin { + NormalSyntax, + Preproc { hits: Vec }, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SourceTokenDomain { + Preproc, } #[derive(Debug, Clone, PartialEq, Eq)] -pub(crate) struct PreprocTokenAmbiguity { +pub(crate) struct SourceTokenBlock { + pub domain: SourceTokenDomain, pub range: TextRange, - pub hits: Vec, + pub reason: SourceTokenBlockReason, +} + +impl SourceTokenBlock { + fn preproc_unavailable(range: TextRange) -> Self { + Self { + domain: SourceTokenDomain::Preproc, + range, + reason: SourceTokenBlockReason::Unavailable, + } + } + + fn preproc_ambiguous(range: TextRange, hits: Vec) -> Self { + Self { + domain: SourceTokenDomain::Preproc, + range, + reason: SourceTokenBlockReason::Ambiguous { hits }, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum SourceTokenBlockReason { + Unavailable, + Ambiguous { hits: Vec }, } #[derive(Debug, Default)] @@ -137,104 +175,111 @@ enum PreprocSemanticTarget { MacroBody { definition_id: MacroDefinitionId, source: MappedPreprocSource, range: TextRange }, } -pub(crate) fn token_candidates_at_offset<'tree, F>( +pub(crate) fn source_token_resolution_at_offset<'tree, F>( db: &RootDb, file_id: FileId, root: SyntaxNode<'tree>, offset: TextSize, precedence: F, -) -> Option> +) -> Option> where F: Fn(TokenKind) -> usize, { let mut cache = SourceTokenRequestCache::default(); - token_candidates_at_offset_with_cache(db, file_id, root, offset, precedence, &mut cache) + source_token_resolution_at_offset_with_cache(db, file_id, root, offset, precedence, &mut cache) } -pub(crate) fn token_candidates_at_offset_with_cache<'tree, F>( +pub(crate) fn source_token_resolution_at_offset_with_cache<'tree, F>( db: &RootDb, file_id: FileId, root: SyntaxNode<'tree>, offset: TextSize, precedence: F, cache: &mut SourceTokenRequestCache, -) -> Option> +) -> Option> where F: Fn(TokenKind) -> usize, { - match provenance_token_candidates_at_offset(db, file_id, root, offset, &precedence, cache) { - ProvenanceTokenLookup::Available(selection) => { - return Some(SourceTokenSelection::Preproc(selection)); - } - ProvenanceTokenLookup::Unavailable(unavailable) => { - return Some(SourceTokenSelection::Unavailable(unavailable)); - } - ProvenanceTokenLookup::Ambiguous(ambiguous) => { - return Some(SourceTokenSelection::Ambiguous(ambiguous)); + match preproc_source_token_at_offset(db, file_id, root, offset, &precedence, cache) { + SourceTokenProviderResult::NotApplicable => { + normal_syntax_source_token_at_offset(root, offset, &precedence).into_resolution() } - ProvenanceTokenLookup::NotApplicable => {} + result => result.into_resolution(), } - - normal_syntax_selection_at_offset(root, offset, &precedence) - .map(SourceTokenSelection::NormalSyntax) } -fn normal_syntax_selection_at_offset<'tree>( +fn normal_syntax_source_token_at_offset<'tree>( root: SyntaxNode<'tree>, offset: TextSize, precedence: &impl Fn(TokenKind) -> usize, -) -> Option> { - let token = root.token_at_offset(offset).pick_bext_token(precedence)?; - Some(NormalSyntaxSelection { range: token.text_range()?, tokens: vec![token] }) +) -> SourceTokenProviderResult<'tree> { + let Some(token) = root.token_at_offset(offset).pick_bext_token(precedence) else { + return SourceTokenProviderResult::NotApplicable; + }; + let Some(range) = token.text_range() else { + return SourceTokenProviderResult::NotApplicable; + }; + SourceTokenProviderResult::Resolved(SourceTokenSelection::normal_syntax(range, vec![token])) } -enum ProvenanceTokenLookup<'tree> { - Available(PreprocTokenSelection<'tree>), - Unavailable(PreprocTokenUnavailable), - Ambiguous(PreprocTokenAmbiguity), +enum SourceTokenProviderResult<'tree> { + Resolved(SourceTokenSelection<'tree>), + Blocked(SourceTokenBlock), NotApplicable, } -fn provenance_token_candidates_at_offset<'tree>( +impl<'tree> SourceTokenProviderResult<'tree> { + fn into_resolution(self) -> Option> { + match self { + Self::Resolved(selection) => Some(SourceTokenResolution::Resolved(selection)), + Self::Blocked(block) => Some(SourceTokenResolution::Blocked(block)), + Self::NotApplicable => None, + } + } +} + +fn preproc_source_token_at_offset<'tree>( db: &RootDb, file_id: FileId, root: SyntaxNode<'tree>, offset: TextSize, precedence: &impl Fn(TokenKind) -> usize, cache: &mut SourceTokenRequestCache, -) -> ProvenanceTokenLookup<'tree> { +) -> SourceTokenProviderResult<'tree> { if !source_macro_invocation_may_cover_offset(db.file_text(file_id).as_ref(), offset) { - return ProvenanceTokenLookup::NotApplicable; + return SourceTokenProviderResult::NotApplicable; } let provenances = match cache.macro_expansion_provenances_at(db, file_id, offset) { Ok(provenances) => provenances, Err(PreprocError::SourceQuery(SourcePreprocQueryError::UnsupportedFileKind(_))) => { - return ProvenanceTokenLookup::NotApplicable; + return SourceTokenProviderResult::NotApplicable; } Err(_) => { - return ProvenanceTokenLookup::Unavailable(PreprocTokenUnavailable { - range: TextRange::empty(offset), - }); + return SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_unavailable( + TextRange::empty(offset), + )); } }; if provenances.is_empty() { - return ProvenanceTokenLookup::NotApplicable; + return SourceTokenProviderResult::NotApplicable; } match preproc_hits_at_offset(&provenances, file_id, offset) { PreprocHitLookup::Available { range, hits } => { let Some(tokens) = syntax_tokens_for_preproc_hit(root, offset, precedence, &hits) else { - return ProvenanceTokenLookup::Unavailable(PreprocTokenUnavailable { range }); + return SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_unavailable( + range, + )); }; - ProvenanceTokenLookup::Available(PreprocTokenSelection { range, hits, tokens }) + SourceTokenProviderResult::Resolved(SourceTokenSelection::preproc(range, hits, tokens)) } PreprocHitLookup::Unavailable { range } => { - ProvenanceTokenLookup::Unavailable(PreprocTokenUnavailable { range }) + SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_unavailable(range)) } PreprocHitLookup::Ambiguous { range, hits } => { - ProvenanceTokenLookup::Ambiguous(PreprocTokenAmbiguity { range, hits }) + SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_ambiguous(range, hits)) } } } @@ -530,7 +575,7 @@ mod tests { ); let hit = test_source_hit(file_id, provenance_range, 0); - let ProvenanceTokenLookup::Available(selection) = preproc_selection_from_hits( + let SourceTokenProviderResult::Resolved(selection) = preproc_provider_result_from_hits( root, offset, &test_precedence, @@ -541,7 +586,10 @@ mod tests { }; assert_eq!(selection.range, provenance_range); - assert_eq!(selection.hits.len(), 1); + let SourceTokenOrigin::Preproc { hits } = &selection.origin else { + panic!("preproc provider should produce a preproc-origin selection"); + }; + assert_eq!(hits.len(), 1); assert_eq!(selection.tokens.len(), 1); assert_eq!(selection.tokens[0].text_range(), Some(parser_range)); assert_ne!(selection.tokens[0].text_range(), Some(provenance_range)); @@ -552,22 +600,41 @@ mod tests { let (root, offset, parser_range) = root_and_offset("module m; wire payload_i; endmodule\n", "payload_i", 0); assert!( - normal_syntax_selection_at_offset(root, offset, &test_precedence).is_some(), + matches!( + normal_syntax_source_token_at_offset(root, offset, &test_precedence), + SourceTokenProviderResult::Resolved(_) + ), "test setup must have an ordinary syntax token that fallback could have selected" ); - let lookup = - preproc_selection_from_hits(root, offset, &test_precedence, Vec::new(), parser_range); - assert!(matches!(lookup, ProvenanceTokenLookup::Unavailable(_))); + let lookup = preproc_provider_result_from_hits( + root, + offset, + &test_precedence, + Vec::new(), + parser_range, + ); + assert!(matches!( + lookup, + SourceTokenProviderResult::Blocked(SourceTokenBlock { + domain: SourceTokenDomain::Preproc, + reason: SourceTokenBlockReason::Unavailable, + .. + }) + )); } #[test] fn source_tokens_normal_syntax_path_still_selects_non_preproc_offsets() { let (root, offset, parser_range) = root_and_offset("module m; wire payload_i; endmodule\n", "payload_i", 0); - let selection = normal_syntax_selection_at_offset(root, offset, &test_precedence) - .expect("normal syntax token expected"); + let SourceTokenProviderResult::Resolved(selection) = + normal_syntax_source_token_at_offset(root, offset, &test_precedence) + else { + panic!("normal syntax token expected"); + }; + assert!(matches!(selection.origin, SourceTokenOrigin::NormalSyntax)); assert_eq!(selection.range, parser_range); assert_eq!(selection.tokens.len(), 1); } @@ -604,13 +671,16 @@ mod tests { test_source_hit(file_id, parser_range, 1), ]; - let ProvenanceTokenLookup::Available(selection) = - preproc_selection_from_hits(root, offset, &test_precedence, hits, parser_range) + let SourceTokenProviderResult::Resolved(selection) = + preproc_provider_result_from_hits(root, offset, &test_precedence, hits, parser_range) else { panic!("same semantic target should dedup to one available preproc hit"); }; - assert_eq!(selection.hits.len(), 1); + let SourceTokenOrigin::Preproc { hits } = selection.origin else { + panic!("preproc provider should produce a preproc-origin selection"); + }; + assert_eq!(hits.len(), 1); } #[test] @@ -622,13 +692,15 @@ mod tests { let second = TextRange::new(parser_range.start() + TextSize::from(1), parser_range.end()); let hits = vec![test_source_hit(file_id, first, 0), test_source_hit(file_id, second, 1)]; - let ProvenanceTokenLookup::Ambiguous(ambiguous) = - preproc_selection_from_hits(root, offset, &test_precedence, hits, parser_range) + let SourceTokenProviderResult::Blocked(SourceTokenBlock { + reason: SourceTokenBlockReason::Ambiguous { hits }, + .. + }) = preproc_provider_result_from_hits(root, offset, &test_precedence, hits, parser_range) else { panic!("conflicting preproc targets should be ambiguous"); }; - assert_eq!(ambiguous.hits.len(), 2); + assert_eq!(hits.len(), 2); } #[test] @@ -691,13 +763,13 @@ mod tests { } } - fn preproc_selection_from_hits<'tree>( + fn preproc_provider_result_from_hits<'tree>( root: SyntaxNode<'tree>, offset: TextSize, precedence: &impl Fn(TokenKind) -> usize, hits: Vec, fallback_range: TextRange, - ) -> ProvenanceTokenLookup<'tree> { + ) -> SourceTokenProviderResult<'tree> { let mut unique_hits = Vec::new(); for hit in hits { if hit.source_range.contains(offset) { @@ -705,24 +777,30 @@ mod tests { } } if unique_hits.is_empty() { - return ProvenanceTokenLookup::Unavailable(PreprocTokenUnavailable { - range: fallback_range, - }); + return SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_unavailable( + fallback_range, + )); } let range = covering_range(&unique_hits.iter().map(|hit| hit.source_range).collect::>()) .unwrap_or(fallback_range); if unique_hits.len() > 1 { - return ProvenanceTokenLookup::Ambiguous(PreprocTokenAmbiguity { + return SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_ambiguous( range, - hits: unique_hits, - }); + unique_hits, + )); } let Some(tokens) = syntax_tokens_for_preproc_hit(root, offset, precedence, &unique_hits) else { - return ProvenanceTokenLookup::Unavailable(PreprocTokenUnavailable { range }); + return SourceTokenProviderResult::Blocked(SourceTokenBlock::preproc_unavailable( + range, + )); }; - ProvenanceTokenLookup::Available(PreprocTokenSelection { range, hits: unique_hits, tokens }) + SourceTokenProviderResult::Resolved(SourceTokenSelection::preproc( + range, + unique_hits, + tokens, + )) } fn preproc_hit_for_raw_provenance(