diff --git a/crates/hir/src/container.rs b/crates/hir/src/container.rs index 6b9a9485..eadf5004 100644 --- a/crates/hir/src/container.rs +++ b/crates/hir/src/container.rs @@ -27,6 +27,7 @@ use crate::{ typedef::{Typedef, TypedefId, TypedefSrc}, }, region_tree::RegionTree, + source_map::SourcePresentation, }; define_enum_deriving_from! { @@ -277,6 +278,66 @@ impl ContainerSrcMap { ContainerSrcMap::SubroutineSourceMap(subroutine) => Some(&subroutine.region_tree), } } + + pub fn decl_presentation(&self, decl_id: DeclId) -> Option<&SourcePresentation> { + match self { + ContainerSrcMap::FileSourceMap(src_map) => { + src_map.decl_srcs.hir_to_presentation(decl_id) + } + ContainerSrcMap::ModuleSourceMap(src_map) => { + src_map.decl_srcs.hir_to_presentation(decl_id) + } + ContainerSrcMap::GenerateBlockSourceMap(src_map) => { + src_map.decl_srcs.hir_to_presentation(decl_id) + } + ContainerSrcMap::BlockSourceMap(src_map) => { + src_map.decl_srcs.hir_to_presentation(decl_id) + } + ContainerSrcMap::SubroutineSourceMap(src_map) => { + src_map.decl_srcs.hir_to_presentation(decl_id) + } + } + } + + pub fn typedef_presentation(&self, typedef_id: TypedefId) -> Option<&SourcePresentation> { + match self { + ContainerSrcMap::FileSourceMap(src_map) => { + src_map.typedef_srcs.hir_to_presentation(typedef_id) + } + ContainerSrcMap::ModuleSourceMap(src_map) => { + src_map.typedef_srcs.hir_to_presentation(typedef_id) + } + ContainerSrcMap::GenerateBlockSourceMap(src_map) => { + src_map.typedef_srcs.hir_to_presentation(typedef_id) + } + ContainerSrcMap::BlockSourceMap(src_map) => { + src_map.typedef_srcs.hir_to_presentation(typedef_id) + } + ContainerSrcMap::SubroutineSourceMap(src_map) => { + src_map.typedef_srcs.hir_to_presentation(typedef_id) + } + } + } + + pub fn stmt_presentation(&self, stmt_id: StmtId) -> Option<&SourcePresentation> { + match self { + ContainerSrcMap::FileSourceMap(src_map) => { + src_map.stmt_srcs.hir_to_presentation(stmt_id) + } + ContainerSrcMap::ModuleSourceMap(src_map) => { + src_map.stmt_srcs.hir_to_presentation(stmt_id) + } + ContainerSrcMap::GenerateBlockSourceMap(src_map) => { + src_map.stmt_srcs.hir_to_presentation(stmt_id) + } + ContainerSrcMap::BlockSourceMap(src_map) => { + src_map.stmt_srcs.hir_to_presentation(stmt_id) + } + ContainerSrcMap::SubroutineSourceMap(src_map) => { + src_map.stmt_srcs.hir_to_presentation(stmt_id) + } + } + } } impl AsRef for ContainerSrcMap { diff --git a/crates/hir/src/hir_def.rs b/crates/hir/src/hir_def.rs index 045dd283..84f7b877 100644 --- a/crates/hir/src/hir_def.rs +++ b/crates/hir/src/hir_def.rs @@ -42,8 +42,9 @@ macro alloc_idx_and_src($hir:expr => $arena:expr, $ast:expr => $src_map:expr $(, // HIR lowering can consume include-expanded AST nodes, but source maps only // store locations that can be navigated in the parsed root file. if let Some(ast) = $crate::source_map::SourceAst::new($ast) { - let src = $crate::source_map::FromSourceAst::from_source_ast(ast); - $src_map.insert(src, idx); + let (src, presentation) = + $crate::source_map::FromSourceAst::from_source_ast_with_presentation(ast); + $src_map.insert_with_presentation(src, idx, presentation); } idx }} diff --git a/crates/hir/src/hir_def/aggregate.rs b/crates/hir/src/hir_def/aggregate.rs index f12acfec..8d9edd29 100644 --- a/crates/hir/src/hir_def/aggregate.rs +++ b/crates/hir/src/hir_def/aggregate.rs @@ -11,7 +11,9 @@ use utils::text_edit::TextRange; use super::{Ident, expr::data_ty::DataTy, lower_ident_opt}; use crate::{ container::{ContainerId, InContainer}, - source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, ToAstNode, root_token_in}, + source_map::{ + FromSourceAst, IsNamedSrc, IsSrc, SourceAst, SourcePresentation, ToAstNode, root_token_in, + }, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -85,7 +87,7 @@ impl IsSrc for StructSrc { } #[inline] - fn range(&self) -> TextRange { + fn expanded_range(&self) -> TextRange { self.node.range() } } @@ -97,7 +99,7 @@ impl IsNamedSrc for StructSrc { } #[inline] - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name.map(|name| name.range()) } } @@ -128,6 +130,10 @@ impl<'a> FromSourceAst<'a, ast::StructUnionType<'a>> for StructSrc { .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); StructSrc { node: AstNodeExt::to_ptr(&node), name } } + + fn source_presentation(node: &SourceAst>) -> SourcePresentation { + SourcePresentation::from_node_and_name(node.ast.syntax(), struct_name_token(node.ast)) + } } fn struct_name_token(node: ast::StructUnionType<'_>) -> Option> { @@ -173,7 +179,7 @@ impl IsSrc for ClassSrc { } #[inline] - fn range(&self) -> TextRange { + fn expanded_range(&self) -> TextRange { self.node.range() } } @@ -185,7 +191,7 @@ impl IsNamedSrc for ClassSrc { } #[inline] - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name.map(|name| name.range()) } } @@ -217,4 +223,8 @@ impl<'a> FromSourceAst<'a, ast::ClassDeclaration<'a>> for ClassSrc { .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); ClassSrc { node: AstNodeExt::to_ptr(&node), name } } + + fn source_presentation(node: &SourceAst>) -> SourcePresentation { + SourcePresentation::from_node_and_name(node.ast.syntax(), node.ast.name()) + } } diff --git a/crates/hir/src/hir_def/block.rs b/crates/hir/src/hir_def/block.rs index 6ebf8cf3..8c89e9dd 100644 --- a/crates/hir/src/hir_def/block.rs +++ b/crates/hir/src/hir_def/block.rs @@ -152,18 +152,18 @@ pub(crate) fn find_local_block_id( } let block_kind = block_src.kind(); - let block_range = block_src.range(); - let block_name_range = block_src.name_range(); + let block_range = block_src.expanded_range(); + let block_name_range = block_src.expanded_name_range(); let (stmt_id, _) = stmt_srcs .iter() .find(|(_, stmt_src)| { stmt_src.kind() == block_kind - && stmt_src.range() == block_range - && stmt_src.name_range() == block_name_range + && stmt_src.expanded_range() == block_range + && stmt_src.expanded_name_range() == block_name_range }) .or_else(|| { stmt_srcs.iter().find(|(_, stmt_src)| { - stmt_src.kind() == block_kind && stmt_src.range() == block_range + stmt_src.kind() == block_kind && stmt_src.expanded_range() == block_range }) })?; Some(LocalBlockId(stmt_id)) diff --git a/crates/hir/src/hir_def/module/generate.rs b/crates/hir/src/hir_def/module/generate.rs index 05b6398d..f8d2fcc0 100644 --- a/crates/hir/src/hir_def/module/generate.rs +++ b/crates/hir/src/hir_def/module/generate.rs @@ -92,7 +92,7 @@ impl IsSrc for GenerateRegionSrc { self.ptr().kind() } - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { self.ptr().range() } } @@ -102,7 +102,7 @@ impl IsNamedSrc for GenerateRegionSrc { None } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { None } } @@ -184,7 +184,7 @@ impl IsSrc for GenerateBlockSrc { self.node().kind() } - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { self.node().range() } } @@ -194,7 +194,7 @@ impl IsNamedSrc for GenerateBlockSrc { self.name().map(|name| name.kind()) } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name().map(|name| name.range()) } } diff --git a/crates/hir/src/hir_def/module/port.rs b/crates/hir/src/hir_def/module/port.rs index 33f24604..bd846ab4 100644 --- a/crates/hir/src/hir_def/module/port.rs +++ b/crates/hir/src/hir_def/module/port.rs @@ -20,7 +20,7 @@ use crate::{ module::LowerModuleCtx, ty::{NetType, lower_net_kind}, }, - source_map::SourceMap, + source_map::{SourceMap, SourcePresentation}, }; // structure: @@ -189,6 +189,20 @@ impl PortSrcs { } } } + + pub fn ref_presentation(&self, port_ref_id: PortRefId) -> Option<&SourcePresentation> { + match self { + PortSrcs::NonAnsi { refs, .. } => refs.hir_to_presentation(port_ref_id), + PortSrcs::Ansi { .. } => None, + } + } + + pub fn port_presentation(&self, port_id: NonAnsiPortId) -> Option<&SourcePresentation> { + match self { + PortSrcs::NonAnsi { ports, .. } => ports.hir_to_presentation(port_id), + PortSrcs::Ansi { .. } => None, + } + } } define_src!(PortListSrc(ast::NonAnsiPortList, ast::AnsiPortList, ast::WildcardPortList)); diff --git a/crates/hir/src/hir_def/module/specify.rs b/crates/hir/src/hir_def/module/specify.rs index 0d6b3067..c77e1816 100644 --- a/crates/hir/src/hir_def/module/specify.rs +++ b/crates/hir/src/hir_def/module/specify.rs @@ -28,7 +28,7 @@ impl IsNamedSrc for SpecifyBlockSrc { None } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { None } } diff --git a/crates/hir/src/hir_def/typedef.rs b/crates/hir/src/hir_def/typedef.rs index 4e3088f9..b58d1489 100644 --- a/crates/hir/src/hir_def/typedef.rs +++ b/crates/hir/src/hir_def/typedef.rs @@ -10,7 +10,9 @@ use utils::text_edit::TextRange; use super::{Ident, aggregate::StructId, expr::data_ty::DataTy}; use crate::{ container::{ContainerId, InContainer}, - source_map::{FromSourceAst, IsNamedSrc, IsSrc, SourceAst, ToAstNode, root_token_in}, + source_map::{ + FromSourceAst, IsNamedSrc, IsSrc, SourceAst, SourcePresentation, ToAstNode, root_token_in, + }, }; #[derive(Debug, PartialEq, Eq, Clone)] @@ -34,7 +36,7 @@ impl IsSrc for TypedefSrc { } #[inline] - fn range(&self) -> TextRange { + fn expanded_range(&self) -> TextRange { self.node.range() } } @@ -46,7 +48,7 @@ impl IsNamedSrc for TypedefSrc { } #[inline] - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name.map(|name| name.range()) } } @@ -81,6 +83,10 @@ impl<'a> FromSourceAst<'a, ast::TypedefDeclaration<'a>> for TypedefSrc { .and_then(|name| root_token_in(syntax, name).map(SyntaxTokenPtr::from_token)); TypedefSrc { node: AstNodeExt::to_ptr(&node), name } } + + fn source_presentation(node: &SourceAst>) -> SourcePresentation { + SourcePresentation::from_node_and_name(node.ast.syntax(), node.ast.name()) + } } impl TypedefSrc { diff --git a/crates/hir/src/preproc.rs b/crates/hir/src/preproc.rs index f74e8153..33cf7b9a 100644 --- a/crates/hir/src/preproc.rs +++ b/crates/hir/src/preproc.rs @@ -35,14 +35,15 @@ mod expansion; mod helpers; mod includes; mod predefines; +mod presentation; mod reference_index; mod reference_queries; mod types; use self::helpers::*; pub use self::{ - conditionals::*, definitions::*, expansion::*, includes::*, reference_index::*, - reference_queries::*, types::*, + conditionals::*, definitions::*, expansion::*, includes::*, presentation::*, + reference_index::*, reference_queries::*, types::*, }; #[cfg(test)] diff --git a/crates/hir/src/preproc/presentation.rs b/crates/hir/src/preproc/presentation.rs new file mode 100644 index 00000000..1229e212 --- /dev/null +++ b/crates/hir/src/preproc/presentation.rs @@ -0,0 +1,260 @@ +use super::*; +use crate::source_map::SourcePresentationAnchor; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SourcePresentationFileRange { + pub file_id: FileId, + pub range: TextRange, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SourcePresentationResolution { + Available(SourcePresentationFileRange), + Unavailable(PreprocUnavailable), + Ambiguous(Vec), +} + +impl SourcePresentationResolution { + pub fn available(self) -> Option { + match self { + Self::Available(target) => Some(target), + Self::Unavailable(_) | Self::Ambiguous(_) => None, + } + } +} + +pub fn resolve_source_presentation_anchor( + db: &dyn SourceRootDb, + file_id: FileId, + anchor: SourcePresentationAnchor, +) -> PreprocResult { + match anchor { + SourcePresentationAnchor::Direct(range) => { + Ok(SourcePresentationResolution::Available(SourcePresentationFileRange { + file_id, + range, + })) + } + SourcePresentationAnchor::Source(source_range) => { + resolve_source_range_anchor(db, file_id, source_range) + } + SourcePresentationAnchor::MacroBody(_) + | SourcePresentationAnchor::MacroArgument(_) + | SourcePresentationAnchor::MacroOperation(_) => { + resolve_macro_token_anchor(db, file_id, anchor) + } + SourcePresentationAnchor::Unavailable => { + Ok(SourcePresentationResolution::Unavailable(unavailable_presentation_anchor())) + } + } +} + +fn resolve_source_range_anchor( + db: &dyn SourceRootDb, + file_id: FileId, + source_range: SourceRange, +) -> PreprocResult { + let mut targets = Vec::new(); + let mut first_error = None; + let mut first_unavailable = None; + let contexts = source_preproc_single_query_contexts(db, file_id); + + for model_file_id in contexts.model_file_ids.iter().copied() { + let mapped = db.source_preproc_model(model_file_id); + let mapped = match mapped_result(mapped.as_ref()) { + Ok(mapped) => mapped, + Err(error) => { + record_first_error(&mut first_error, error); + continue; + } + }; + match map_mapped_source_range(mapped, source_range) { + Ok((source, range)) => { + push_original_target_or_unavailable( + &mut targets, + &mut first_unavailable, + source, + range, + ); + } + Err(error) => record_first_error(&mut first_error, error), + } + } + + finish_presentation_targets(targets, &contexts, first_error, first_unavailable) +} + +fn resolve_macro_token_anchor( + db: &dyn SourceRootDb, + file_id: FileId, + anchor: SourcePresentationAnchor, +) -> PreprocResult { + let mut targets = Vec::new(); + let mut first_error = None; + let mut first_unavailable = None; + let contexts = source_preproc_single_query_contexts(db, file_id); + + for model_file_id in contexts.model_file_ids.iter().copied() { + let mapped = db.source_preproc_model(model_file_id); + let mapped = match mapped_result(mapped.as_ref()) { + Ok(mapped) => mapped, + Err(error) => { + record_first_error(&mut first_error, error); + continue; + } + }; + collect_macro_token_anchor_targets( + mapped, + anchor, + &mut targets, + &mut first_error, + &mut first_unavailable, + ); + } + + finish_presentation_targets(targets, &contexts, first_error, first_unavailable) +} + +fn collect_macro_token_anchor_targets( + mapped: &MappedSourcePreprocModel, + anchor: SourcePresentationAnchor, + targets: &mut Vec, + first_error: &mut Option, + first_unavailable: &mut Option, +) { + for provenance in mapped.model.token_provenance().iter() { + if !token_provenance_matches_anchor(provenance, anchor) { + continue; + } + let provenance = match map_token_provenance(mapped, provenance) { + Ok(provenance) => provenance, + Err(error) => { + record_first_error(first_error, error); + continue; + } + }; + let Some((source, range)) = presentation_source_for_token_provenance(&provenance) else { + record_first_unavailable(first_unavailable, unavailable_presentation_anchor()); + continue; + }; + push_original_target_or_unavailable(targets, first_unavailable, source, range); + } +} + +fn token_provenance_matches_anchor( + provenance: &SourceTokenProvenanceFact, + anchor: SourcePresentationAnchor, +) -> bool { + match (anchor, provenance) { + ( + SourcePresentationAnchor::MacroBody(anchor), + SourceTokenProvenanceFact::MacroBody { identity, .. }, + ) => MacroBodyTokenIdentity::from(*identity) == anchor, + ( + SourcePresentationAnchor::MacroArgument(anchor), + SourceTokenProvenanceFact::MacroArgument { identity, .. }, + ) => MacroArgumentTokenIdentity::from(*identity) == anchor, + ( + SourcePresentationAnchor::MacroOperation(anchor), + SourceTokenProvenanceFact::TokenPaste { identity, .. } + | SourceTokenProvenanceFact::Stringification { identity, .. }, + ) => MacroOperationTokenIdentity::from(*identity) == anchor, + _ => false, + } +} + +fn presentation_source_for_token_provenance( + provenance: &TokenProvenance, +) -> Option<(MappedPreprocSource, TextRange)> { + match provenance { + TokenProvenance::SourceToken { source, range } + | TokenProvenance::MacroBody { source, range, .. } + | TokenProvenance::MacroArgument { source, range, .. } => Some((source.clone(), *range)), + TokenProvenance::TokenPaste { identity, call } + | TokenProvenance::Stringification { identity, call } => { + macro_operation_source_token(call, *identity) + } + TokenProvenance::Predefine { .. } + | TokenProvenance::Builtin { .. } + | TokenProvenance::Unavailable(_) => None, + } +} + +fn macro_operation_source_token( + call: &MacroCall, + identity: MacroOperationTokenIdentity, +) -> Option<(MappedPreprocSource, TextRange)> { + let argument_index = identity.argument_index?; + let argument_token_index = identity.argument_token_index?; + let argument = call.arguments.get(argument_index)?; + if argument.argument_index != argument_index { + return None; + } + let token = argument.tokens.get(argument_token_index)?; + Some((token.source.as_ref()?.clone(), token.range?)) +} + +fn push_original_target_or_unavailable( + targets: &mut Vec, + first_unavailable: &mut Option, + source: MappedPreprocSource, + range: TextRange, +) { + match source { + MappedPreprocSource::RealFile { file_id } => { + push_unique_target(targets, SourcePresentationFileRange { file_id, range }); + } + MappedPreprocSource::VirtualFile { path, origin, .. } + | MappedPreprocSource::VirtualDisplay { path, origin } => { + record_first_unavailable( + first_unavailable, + PreprocUnavailable::DisplayOnlyVirtualExpansion { path, origin }, + ); + } + } +} + +fn push_unique_target( + targets: &mut Vec, + target: SourcePresentationFileRange, +) { + if !targets.contains(&target) { + targets.push(target); + } +} + +fn finish_presentation_targets( + targets: Vec, + contexts: &SourcePreprocQueryContexts, + first_error: Option, + first_unavailable: Option, +) -> PreprocResult { + match targets.len() { + 0 => {} + 1 => { + return Ok(SourcePresentationResolution::Available( + targets.into_iter().next().unwrap(), + )); + } + _ => return Ok(SourcePresentationResolution::Ambiguous(targets)), + } + + if let Some(reason) = first_unavailable { + return Ok(SourcePresentationResolution::Unavailable(reason)); + } + finish_empty_single_query(contexts, first_error)?; + Ok(SourcePresentationResolution::Unavailable(unavailable_presentation_anchor())) +} + +fn record_first_unavailable( + first_unavailable: &mut Option, + reason: PreprocUnavailable, +) { + if first_unavailable.is_none() { + *first_unavailable = Some(reason); + } +} + +fn unavailable_presentation_anchor() -> PreprocUnavailable { + PreprocUnavailable::Source(SourcePreprocUnavailable::TokenProvenanceAuthorityUnavailable) +} diff --git a/crates/hir/src/source_map.rs b/crates/hir/src/source_map.rs index 31d4448a..b51b8f9e 100644 --- a/crates/hir/src/source_map.rs +++ b/crates/hir/src/source_map.rs @@ -1,13 +1,22 @@ use std::{fmt::Debug, hash::Hash}; +use ::preproc::source::{PreprocSourceId, SourceRange as PreprocSourceRange}; pub(crate) use la_arena::{ArenaMap, Idx}; use rustc_hash::FxHashMap; use syntax::{ - SyntaxKind, SyntaxNode, SyntaxToken, SyntaxTokenWithParent, TokenKind, ast::AstNode, + PreprocessorTraceTokenProvenance, SourceBufferRange, SyntaxElement, SyntaxKind, SyntaxNode, + SyntaxToken, SyntaxTokenWithParent, TokenKind, WalkEvent, ast::AstNode, has_text_range::HasTextRange, }; pub(crate) use utils::get::Get; -use utils::{get::GetRef, text_edit::TextRange}; +use utils::{ + get::GetRef, + text_edit::{TextRange, TextSize}, +}; + +use crate::preproc::{ + MacroArgumentTokenIdentity, MacroBodyTokenIdentity, MacroOperationTokenIdentity, +}; pub trait IsSrc: PartialEq + Eq + Hash + Copy + Clone + Debug { #[inline] @@ -26,25 +35,31 @@ pub trait IsSrc: PartialEq + Eq + Hash + Copy + Clone + Debug { fn kind(&self) -> SyntaxKind; - /// Returns the full syntactic extent of the mapped AST node. + /// Returns the full syntactic extent of the mapped expanded AST node. /// /// Use this for containment, folding, diagnostics, and operations that act /// on the whole construct rather than just its defining identifier. - fn range(&self) -> TextRange; + /// + /// This is a HIR/source-map coordinate. Editor-facing ranges must resolve a + /// presentation anchor instead of treating this range as original source. + fn expanded_range(&self) -> TextRange; } pub trait IsNamedSrc: IsSrc { fn name_kind(&self) -> Option; - /// Returns the token range that names this source node, when it has one. + /// Returns the expanded token range that names this source node, when it + /// has one. /// - /// Use this for symbol focus ranges such as navigation targets, document - /// symbol selections, rename/reference origins, and semantic tokens. - fn name_range(&self) -> Option; - - /// Returns the symbol focus range when present, otherwise the full range. - fn name_or_full_range(&self) -> TextRange { - self.name_range().unwrap_or_else(|| self.range()) + /// This is a HIR/source-map coordinate. Editor-facing focus ranges must + /// resolve a presentation anchor instead of treating this range as original + /// source. + fn expanded_name_range(&self) -> Option; + + /// Returns the expanded symbol focus range when present, otherwise the full + /// expanded range. + fn expanded_name_or_full_range(&self) -> TextRange { + self.expanded_name_range().unwrap_or_else(|| self.expanded_range()) } } @@ -52,17 +67,29 @@ pub trait IsNamedSrc: IsSrc { pub struct SourceMap { src2hir: FxHashMap>, hir2src: ArenaMap, Src>, + hir2presentation: ArenaMap, SourcePresentation>, } impl SourceMap { pub fn insert(&mut self, src: Src, idx: Idx) { + self.insert_with_presentation(src, idx, SourcePresentation::direct(src.expanded_range())); + } + + pub fn insert_with_presentation( + &mut self, + src: Src, + idx: Idx, + presentation: SourcePresentation, + ) { self.src2hir.insert(src, idx); self.hir2src.insert(idx, src); + self.hir2presentation.insert(idx, presentation); } pub fn shrink_to_fit(&mut self) { self.src2hir.shrink_to_fit(); self.hir2src.shrink_to_fit(); + self.hir2presentation.shrink_to_fit(); } pub fn iter(&self) -> impl Iterator, &Src)> { @@ -78,6 +105,11 @@ impl SourceMap { pub fn hir_to_src(&self, idx: Idx) -> Option { self.hir2src.get(idx).copied() } + + #[inline] + pub fn hir_to_presentation(&self, idx: Idx) -> Option<&SourcePresentation> { + self.hir2presentation.get(idx) + } } impl Get for SourceMap { @@ -98,10 +130,143 @@ impl Get> for SourceMap { impl Default for SourceMap { fn default() -> Self { - SourceMap { src2hir: FxHashMap::default(), hir2src: ArenaMap::default() } + SourceMap { + src2hir: FxHashMap::default(), + hir2src: ArenaMap::default(), + hir2presentation: ArenaMap::default(), + } } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SourcePresentation { + pub full: SourcePresentationAnchor, + pub name: Option, +} + +impl SourcePresentation { + pub fn direct(range: TextRange) -> Self { + Self { full: SourcePresentationAnchor::Direct(range), name: None } + } + + pub(crate) fn from_node_and_name(node: SyntaxNode<'_>, name: Option>) -> Self { + let full = SourcePresentationAnchor::from_node(node); + let name = name + .and_then(|name| root_token_in(node, name).map(SourcePresentationAnchor::from_token)); + Self { full, name } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SourcePresentationAnchor { + Direct(TextRange), + Source(PreprocSourceRange), + MacroBody(MacroBodyTokenIdentity), + MacroArgument(MacroArgumentTokenIdentity), + MacroOperation(MacroOperationTokenIdentity), + Unavailable, +} + +impl SourcePresentationAnchor { + fn from_node(node: SyntaxNode<'_>) -> Self { + let Some(direct_range) = node.text_range() else { + return Self::Unavailable; + }; + let mut saw_token = false; + let mut direct = true; + let mut source_range = None; + let mut macro_anchor = None; + + for event in node.elem_preorder() { + let WalkEvent::Enter(SyntaxElement::Token(token)) = event else { + continue; + }; + saw_token = true; + let anchor = Self::from_token(token); + match anchor { + Self::Direct(_) => { + if source_range.is_some() || macro_anchor.is_some() { + return Self::Unavailable; + } + } + Self::Source(range) => { + direct = false; + if macro_anchor.is_some() { + return Self::Unavailable; + } + source_range = match source_range { + Some(existing) => merge_source_ranges(existing, range), + None => Some(range), + }; + if source_range.is_none() { + return Self::Unavailable; + } + } + Self::MacroBody(_) | Self::MacroArgument(_) | Self::MacroOperation(_) => { + direct = false; + if source_range.is_some() { + return Self::Unavailable; + } + macro_anchor = match macro_anchor { + Some(existing) if existing == anchor => Some(existing), + Some(_) => return Self::Unavailable, + None => Some(anchor), + }; + } + Self::Unavailable => return Self::Unavailable, + } + } + + if !saw_token || direct { + return Self::Direct(direct_range); + } + source_range.map(Self::Source).or(macro_anchor).unwrap_or(Self::Unavailable) + } + + fn from_token(token: SyntaxTokenWithParent<'_>) -> Self { + match token.preprocessor_trace_provenance() { + PreprocessorTraceTokenProvenance::Source { token_range } => { + source_range_from_trace(token_range).map(Self::Source).unwrap_or(Self::Unavailable) + } + PreprocessorTraceTokenProvenance::MacroBody { identity, .. } => { + Self::MacroBody(identity.into()) + } + PreprocessorTraceTokenProvenance::MacroArgument { identity, .. } => { + Self::MacroArgument(identity.into()) + } + PreprocessorTraceTokenProvenance::TokenPaste { identity } + | PreprocessorTraceTokenProvenance::Stringification { identity } => { + Self::MacroOperation(identity.into()) + } + PreprocessorTraceTokenProvenance::Builtin { .. } + | PreprocessorTraceTokenProvenance::Unavailable => Self::Unavailable, + } + } +} + +fn source_range_from_trace(range: SourceBufferRange) -> Option { + Some(PreprocSourceRange { + source: PreprocSourceId::from(range.buffer_id), + range: TextRange::new( + TextSize::from(u32::try_from(range.range.start).ok()?), + TextSize::from(u32::try_from(range.range.end).ok()?), + ), + }) +} + +fn merge_source_ranges( + left: PreprocSourceRange, + right: PreprocSourceRange, +) -> Option { + (left.source == right.source).then(|| PreprocSourceRange { + source: left.source, + range: TextRange::new( + left.range.start().min(right.range.start()), + left.range.end().max(right.range.end()), + ), + }) +} + pub trait ToAstNode<'a, Output: AstNode<'a>> { fn to_node(&self, tree: &'a syntax::SyntaxTree) -> Option; } @@ -117,7 +282,7 @@ pub trait ToAstNode<'a, Output: AstNode<'a>> { /// absent. #[derive(Debug, Clone, Copy)] pub(crate) struct SourceAst { - ast: Ast, + pub(crate) ast: Ast, } impl<'a, Ast> SourceAst @@ -147,6 +312,18 @@ where /// file. pub(crate) trait FromSourceAst<'a, Ast: AstNode<'a>> { fn from_source_ast(ast: SourceAst) -> Self; + + fn source_presentation(ast: &SourceAst) -> SourcePresentation { + SourcePresentation::from_node_and_name(ast.ast.syntax(), None) + } + + fn from_source_ast_with_presentation(ast: SourceAst) -> (Self, SourcePresentation) + where + Self: Sized, + { + let presentation = Self::source_presentation(&ast); + (Self::from_source_ast(ast), presentation) + } } /// Attach a bare token returned by generated AST accessors to a root-buffer @@ -178,7 +355,7 @@ macro_rules! define_src { } #[inline] - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { self.0.range() } } @@ -232,7 +409,7 @@ macro_rules! define_src { } #[inline] - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { match self { $( $name::$ty(ptr) => ptr.range(), @@ -283,7 +460,7 @@ macro_rules! define_src_with_name { self.node.kind() } - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { self.node.range() } } @@ -293,7 +470,7 @@ macro_rules! define_src_with_name { self.name.map(|name| name.kind()) } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name.map(|name| name.range()) } } @@ -332,6 +509,15 @@ macro_rules! define_src_with_name { }), } } + + fn source_presentation( + node: &$crate::source_map::SourceAst>, + ) -> $crate::source_map::SourcePresentation { + $crate::source_map::SourcePresentation::from_node_and_name( + syntax::ast::AstNode::syntax(&node.ast), + as syntax::has_name::HasName<'a>>::name(&node.ast), + ) + } } impl From<$name> for syntax::ptr::SyntaxNodePtr { @@ -367,7 +553,7 @@ macro_rules! define_src_with_name { } } - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { match self { $( $name::$ty { node, .. } => node.range(), @@ -385,7 +571,7 @@ macro_rules! define_src_with_name { } } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { match self { $( $name::$ty { name, .. } => name.map(|name| name.range()), @@ -436,6 +622,15 @@ macro_rules! define_src_with_name { }), } } + + fn source_presentation( + node: &$crate::source_map::SourceAst>, + ) -> $crate::source_map::SourcePresentation { + $crate::source_map::SourcePresentation::from_node_and_name( + syntax::ast::AstNode::syntax(&node.ast), + as syntax::has_name::HasName<'a>>::name(&node.ast), + ) + } } )* @@ -482,7 +677,7 @@ macro_rules! define_src_with_name_and_token { self.node.kind() } - fn range(&self) -> utils::text_edit::TextRange { + fn expanded_range(&self) -> utils::text_edit::TextRange { self.node.range() } } @@ -492,7 +687,7 @@ macro_rules! define_src_with_name_and_token { self.name.map(|name| name.kind()) } - fn name_range(&self) -> Option { + fn expanded_name_range(&self) -> Option { self.name.map(|name| name.range()) } } @@ -539,6 +734,15 @@ macro_rules! define_src_with_name_and_token { }), } } + + fn source_presentation( + node: &$crate::source_map::SourceAst>, + ) -> $crate::source_map::SourcePresentation { + $crate::source_map::SourcePresentation::from_node_and_name( + syntax::ast::AstNode::syntax(&node.ast), + as syntax::has_name::HasName<'a>>::name(&node.ast), + ) + } } impl From<$name> for syntax::ptr::SyntaxNodePtr { diff --git a/crates/ide/src/code_action/handlers/add_missing_connections.rs b/crates/ide/src/code_action/handlers/add_missing_connections.rs index 607db3f6..bdb36397 100644 --- a/crates/ide/src/code_action/handlers/add_missing_connections.rs +++ b/crates/ide/src/code_action/handlers/add_missing_connections.rs @@ -95,7 +95,7 @@ pub(super) fn add_missing_connections( let text = sema.db.file_text(ctx.file_id()); let item_ranges = instance.connections.iter().filter_map(|conn_id| { - let range = module_src_map.get(*conn_id)?.range(); + let range = module_src_map.get(*conn_id)?.expanded_range(); (!range.is_empty()).then_some(range) }); apply_missing_list_edit(builder, &text, open_paren, close_paren, item_ranges, entries); diff --git a/crates/ide/src/code_action/handlers/add_missing_parameters.rs b/crates/ide/src/code_action/handlers/add_missing_parameters.rs index b9f466d7..6b879001 100644 --- a/crates/ide/src/code_action/handlers/add_missing_parameters.rs +++ b/crates/ide/src/code_action/handlers/add_missing_parameters.rs @@ -99,7 +99,7 @@ pub(super) fn add_missing_parameters( let text = sema.db.file_text(ctx.file_id()); let item_ranges = instantiation.param_assigns.iter().filter_map(|assign_id| { - let range = module_src_map.get(*assign_id)?.range(); + let range = module_src_map.get(*assign_id)?.expanded_range(); (!range.is_empty()).then_some(range) }); apply_missing_list_edit(builder, &text, open_paren, close_paren, item_ranges, entries); diff --git a/crates/ide/src/code_action/handlers/convert_ordered_connections.rs b/crates/ide/src/code_action/handlers/convert_ordered_connections.rs index 52eb0ae3..d5c3d389 100644 --- a/crates/ide/src/code_action/handlers/convert_ordered_connections.rs +++ b/crates/ide/src/code_action/handlers/convert_ordered_connections.rs @@ -70,8 +70,8 @@ pub(super) fn convert_ordered_ports( return None; }; let name = port_names.get(idx)?; - let expr = module_src_map.get(*expr_id)?.range(); - let range = module_src_map.get(*conn_id)?.range(); + let expr = module_src_map.get(*expr_id)?.expanded_range(); + let range = module_src_map.get(*conn_id)?.expanded_range(); Some((range, format!(".{name}({})", text.get(Range::from(expr))?))) }) .collect_vec(); @@ -126,8 +126,8 @@ pub(super) fn convert_ordered_params( return None; }; let name = param_names.get(idx)?; - let expr = module_src_map.get(*expr_id)?.range(); - let range = module_src_map.get(*assign_id)?.range(); + let expr = module_src_map.get(*expr_id)?.expanded_range(); + let range = module_src_map.get(*assign_id)?.expanded_range(); Some((range, format!(".{name}({})", text.get(Range::from(expr))?))) }) .collect_vec(); diff --git a/crates/ide/src/code_action/handlers/convert_port_declarations.rs b/crates/ide/src/code_action/handlers/convert_port_declarations.rs index a159330d..47e8456f 100644 --- a/crates/ide/src/code_action/handlers/convert_port_declarations.rs +++ b/crates/ide/src/code_action/handlers/convert_port_declarations.rs @@ -240,7 +240,7 @@ fn non_ansi_port_replacement( let PortDeclSrc::PortDeclaration(_) = port_src else { return None; }; - let port_range = port_src.range(); + let port_range = port_src.expanded_range(); if let Some(data_decl) = data_decl { let data_range = data_decl_range_for_name(module, module_src_map, data_decl, name)?; @@ -281,7 +281,9 @@ fn data_decl_range_for_name( let src = module_src_map.declaration_srcs.get(declaration_id)?; match src { - DeclarationSrc::DataDeclaration(_) | DeclarationSrc::NetDeclaration(_) => Some(src.range()), + DeclarationSrc::DataDeclaration(_) | DeclarationSrc::NetDeclaration(_) => { + Some(src.expanded_range()) + } _ => None, } } @@ -293,7 +295,7 @@ fn render_ansi_port_declaration( src: PortDeclSrc, text: &str, ) -> Option { - let source = text.get(Range::from(src.range()))?; + let source = text.get(Range::from(src.expanded_range()))?; if source .split_ascii_whitespace() .next() diff --git a/crates/ide/src/code_action/handlers/sort_named_instantiation_items.rs b/crates/ide/src/code_action/handlers/sort_named_instantiation_items.rs index 9ee0c222..5511f814 100644 --- a/crates/ide/src/code_action/handlers/sort_named_instantiation_items.rs +++ b/crates/ide/src/code_action/handlers/sort_named_instantiation_items.rs @@ -72,7 +72,7 @@ pub(super) fn sort_named_parameter_assignments( return None; }; let order = *parameter_order_map.get(name.as_str())?; - let range = module_src_map.get(*assign_id)?.range(); + let range = module_src_map.get(*assign_id)?.expanded_range(); items.push((order, text.get(Range::from(range))?, range)); } @@ -133,7 +133,7 @@ pub(super) fn sort_named_port_connections( return None; }; let order = *port_order_map.get(name.as_str())?; - let range = module_src_map.get(*conn_id)?.range(); + let range = module_src_map.get(*conn_id)?.expanded_range(); items.push((order, text.get(Range::from(range))?, range)); } diff --git a/crates/ide/src/code_lens.rs b/crates/ide/src/code_lens.rs index 2308fc66..259ba857 100644 --- a/crates/ide/src/code_lens.rs +++ b/crates/ide/src/code_lens.rs @@ -63,7 +63,7 @@ fn process_instantiations( continue; }; - let Some(range) = src_map.get(local_module_id).map(|src| src.range()) else { + let Some(range) = src_map.get(local_module_id).map(|src| src.expanded_range()) else { continue; }; let pos = FilePosition { file_id: file_id.file_id(), offset: range.start() }; @@ -80,7 +80,7 @@ pub(crate) fn code_lens_resolve(db: &RootDb, mut kind: CodeLensKind) -> CodeLens let hir_file_id = HirFileId(file_id); let (_, src_map) = sema.db.hir_file_with_source_map(hir_file_id); let Some((local_module_id, _)) = - src_map.module_srcs.iter().find(|(_, src)| src.range().start() == offset) + src_map.module_srcs.iter().find(|(_, src)| src.expanded_range().start() == offset) else { *data = Some(Vec::new()); return kind; diff --git a/crates/ide/src/completion/engine/sensitivity_list.rs b/crates/ide/src/completion/engine/sensitivity_list.rs index 1f90989f..d8458426 100644 --- a/crates/ide/src/completion/engine/sensitivity_list.rs +++ b/crates/ide/src/completion/engine/sensitivity_list.rs @@ -33,7 +33,7 @@ fn module_id_at_offset(db: &RootDb, position: FilePosition) -> Option let mut best: Option<(TextSize, ModuleId)> = None; for (local_module_id, _) in hir_file.modules.iter() { - let Some(range) = file_src_map.get(local_module_id).map(|src| src.range()) else { + let Some(range) = file_src_map.get(local_module_id).map(|src| src.expanded_range()) else { continue; }; if !range.contains(position.offset) && range.end() != position.offset { diff --git a/crates/ide/src/definitions.rs b/crates/ide/src/definitions.rs index 7c4b6458..a3cb6465 100644 --- a/crates/ide/src/definitions.rs +++ b/crates/ide/src/definitions.rs @@ -30,11 +30,7 @@ use syntax::{ match_ast, token::TokenKindExt, }; -use utils::{ - get::{Get, GetRef}, - impl_from, - line_index::TextRange, -}; +use utils::{get::GetRef, impl_from, line_index::TextRange}; use crate::{ db::root_db::RootDb, @@ -42,6 +38,10 @@ use crate::{ ModuleResolution, resolve_instantiation_target, resolve_named_param_assignment, resolve_named_port_connection, }, + presentation::{ + presentation_full_file_range, presentation_name_file_range, + presentation_name_or_full_file_range, + }, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -153,35 +153,58 @@ impl DefinitionOrigin { pub fn name_range(&self, db: &dyn HirDb) -> Option> { match *self { DefinitionOrigin::ModuleId(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) + let src_map = file_id.to_container_src_map(db); + presentation_name_file_range( + db, + file_id, + src_map.module_srcs.hir_to_presentation(value)?, + ) } DefinitionOrigin::Config(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) + let src_map = file_id.to_container_src_map(db); + presentation_name_file_range( + db, + file_id, + src_map.config_decl_srcs.hir_to_presentation(value)?, + ) } DefinitionOrigin::Library(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) + let src_map = file_id.to_container_src_map(db); + presentation_name_file_range( + db, + file_id, + src_map.library_decl_srcs.hir_to_presentation(value)?, + ) } DefinitionOrigin::Udp(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(file_id, range)) + let src_map = file_id.to_container_src_map(db); + presentation_name_file_range( + db, + file_id, + src_map.udp_decl_srcs.hir_to_presentation(value)?, + ) } DefinitionOrigin::BlockId(block_id) => { let BlockLoc { src: InFile { value, file_id }, .. } = block_id.lookup(db); - let range = value.name_range()?; + let range = value.expanded_name_range()?; Some(InFile::new(file_id, range)) } DefinitionOrigin::GenerateBlockId(generate_block_id) => { let GenerateBlockLoc { src: InFile { value, file_id }, .. } = generate_block_id.lookup(db); - let range = value.name_range()?; + let range = value.expanded_name_range()?; Some(InFile::new(file_id, range)) } DefinitionOrigin::SubroutineId(subroutine_id) => { let src = subroutine_id.lookup(db).src; - Some(InFile::new(src.file_id, src.value.name_or_full_range())) + let src_map = src.file_id.to_container_src_map(db); + presentation_name_or_full_file_range( + db, + src.file_id, + src_map + .subroutine_srcs + .hir_to_presentation(subroutine_id.lookup(db).local_id)?, + ) } DefinitionOrigin::SubroutinePort(InSubroutine { subroutine, value }) => { let src = subroutine.lookup(db).src; @@ -201,24 +224,35 @@ impl DefinitionOrigin { Some(InFile::new(src.file_id, range)) } DefinitionOrigin::NonAnsiPort(InModule { value, module_id }) => { - let range = module_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(module_id.file_id, range)) + let src_map = module_id.to_container_src_map(db); + presentation_name_file_range( + db, + module_id.file_id, + src_map.port_srcs.port_presentation(value)?, + ) } DefinitionOrigin::Decl(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(cont_id.file_id(db).into(), range)) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_name_file_range(db, file_id, src_map.decl_presentation(value)?) } DefinitionOrigin::Typedef(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(cont_id.file_id(db).into(), range)) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_name_file_range(db, file_id, src_map.typedef_presentation(value)?) } DefinitionOrigin::Instance(InModule { value, module_id }) => { - let range = module_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(module_id.file_id, range)) + let src_map = module_id.to_container_src_map(db); + presentation_name_file_range( + db, + module_id.file_id, + src_map.instance_srcs.hir_to_presentation(value)?, + ) } DefinitionOrigin::Stmt(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.name_range()?; - Some(InFile::new(cont_id.file_id(db).into(), range)) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_name_file_range(db, file_id, src_map.stmt_presentation(value)?) } } } @@ -226,36 +260,58 @@ impl DefinitionOrigin { pub fn range(&self, db: &dyn HirDb) -> Option> { Some(match *self { DefinitionOrigin::ModuleId(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) + let src_map = file_id.to_container_src_map(db); + presentation_full_file_range( + db, + file_id, + src_map.module_srcs.hir_to_presentation(value)?, + )? } DefinitionOrigin::Config(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) + let src_map = file_id.to_container_src_map(db); + presentation_full_file_range( + db, + file_id, + src_map.config_decl_srcs.hir_to_presentation(value)?, + )? } DefinitionOrigin::Library(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) + let src_map = file_id.to_container_src_map(db); + presentation_full_file_range( + db, + file_id, + src_map.library_decl_srcs.hir_to_presentation(value)?, + )? } DefinitionOrigin::Udp(InFile { value, file_id }) => { - let range = file_id.to_container_src_map(db).get(value)?.range(); - InFile::new(file_id, range) + let src_map = file_id.to_container_src_map(db); + presentation_full_file_range( + db, + file_id, + src_map.udp_decl_srcs.hir_to_presentation(value)?, + )? } DefinitionOrigin::BlockId(block_id) => { let BlockLoc { src: InFile { value, file_id }, .. } = block_id.lookup(db); - let range = value.range(); + let range = value.expanded_range(); InFile::new(file_id, range) } DefinitionOrigin::GenerateBlockId(generate_block_id) => { let GenerateBlockLoc { src: InFile { value, file_id }, .. } = generate_block_id.lookup(db); - let range = value.range(); + let range = value.expanded_range(); InFile::new(file_id, range) } DefinitionOrigin::SubroutineId(subroutine_id) => { let src = subroutine_id.lookup(db).src; - let range = src.value.range(); - InFile::new(src.file_id, range) + let src_map = src.file_id.to_container_src_map(db); + presentation_full_file_range( + db, + src.file_id, + src_map + .subroutine_srcs + .hir_to_presentation(subroutine_id.lookup(db).local_id)?, + )? } DefinitionOrigin::SubroutinePort(InSubroutine { subroutine, value }) => { let src = subroutine.lookup(db).src; @@ -271,24 +327,35 @@ impl DefinitionOrigin { InFile::new(src.file_id, range) } DefinitionOrigin::NonAnsiPort(InModule { value, module_id }) => { - let range = module_id.to_container_src_map(db).get(value)?.range(); - InFile::new(module_id.file_id, range) + let src_map = module_id.to_container_src_map(db); + presentation_full_file_range( + db, + module_id.file_id, + src_map.port_srcs.port_presentation(value)?, + )? } DefinitionOrigin::Decl(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.range(); - InFile::new(cont_id.file_id(db).into(), range) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_full_file_range(db, file_id, src_map.decl_presentation(value)?)? } DefinitionOrigin::Typedef(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.range(); - InFile::new(cont_id.file_id(db).into(), range) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_full_file_range(db, file_id, src_map.typedef_presentation(value)?)? } DefinitionOrigin::Instance(InModule { value, module_id }) => { - let range = module_id.to_container_src_map(db).get(value)?.range(); - InFile::new(module_id.file_id, range) + let src_map = module_id.to_container_src_map(db); + presentation_full_file_range( + db, + module_id.file_id, + src_map.instance_srcs.hir_to_presentation(value)?, + )? } DefinitionOrigin::Stmt(InContainer { value, cont_id }) => { - let range = cont_id.to_container_src_map(db).get(value)?.range(); - InFile::new(cont_id.file_id(db).into(), range) + let file_id = HirFileId(cont_id.file_id(db)); + let src_map = cont_id.to_container_src_map(db); + presentation_full_file_range(db, file_id, src_map.stmt_presentation(value)?)? } }) } diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 2d5ce57e..1bc4304c 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs @@ -338,7 +338,7 @@ fn module_instantiation_resolution_diagnostics(db: &RootDb, file_id: FileId) -> continue; }; let mut diag_file_id = file_id; - let mut range = src.range(); + let mut range = src.expanded_range(); match hir::preproc::diagnostic_provenance_for_range(db, file_id, range) { Ok(Some(provenance)) => { let Some((target_file_id, target_range)) = diff --git a/crates/ide/src/document_symbols.rs b/crates/ide/src/document_symbols.rs index 92dfb220..7e6fbb59 100644 --- a/crates/ide/src/document_symbols.rs +++ b/crates/ide/src/document_symbols.rs @@ -72,8 +72,8 @@ impl SymbolCollecter { let container_name = self.stack.last().map(|sym| sym.name.to_owned()); let sym = DocumentSymbol { name: name.as_ref().unwrap_or(&DEFAULT_NAME).to_string(), - focus_range: src.name_or_full_range(), - full_range: src.range(), + focus_range: src.expanded_name_or_full_range(), + full_range: src.expanded_range(), kind: SymbolKind::from_syntax_kind(src.kind()), detail: None, container_name, @@ -260,7 +260,7 @@ fn collect_module_items( if let Some(params) = &module.param_ports { for decl_id in params.clone() { if let Some(src) = src_map.get(decl_id) { - regions.add_region_symbol(src.range(), collector); + regions.add_region_symbol(src.expanded_range(), collector); } build_decl(collector, decl_id, SymbolKind::ParamDecl, module, src_map); } @@ -270,7 +270,7 @@ fn collect_module_items( Ports::NonAnsi { ports, .. } => { for (port_id, port) in ports.iter() { if let Some(src) = src_map.get(port_id) { - regions.add_region_symbol(src.range(), collector); + regions.add_region_symbol(src.expanded_range(), collector); collector.push_symbol(&port.label, src); collector.pop(); } @@ -279,7 +279,7 @@ fn collect_module_items( Ports::Ansi(port_decls) => { for (port_id, port_decl) in port_decls.iter() { if let Some(src) = src_map.get(port_id) { - regions.add_region_symbol(src.range(), collector); + regions.add_region_symbol(src.expanded_range(), collector); } build_decls(collector, &port_decl.decls, SymbolKind::PortDecl, module, src_map); } diff --git a/crates/ide/src/folding_ranges.rs b/crates/ide/src/folding_ranges.rs index 31982fa9..3d2d27e6 100644 --- a/crates/ide/src/folding_ranges.rs +++ b/crates/ide/src/folding_ranges.rs @@ -96,13 +96,14 @@ impl FoldCollector for Vec { line_index: &LineIndex, ) { self.extend( - srcs.iter().filter_map(|(_, src)| Fold::try_build(src.range(), kind, line_index)), + srcs.iter() + .filter_map(|(_, src)| Fold::try_build(src.expanded_range(), kind, line_index)), ); } #[inline] fn collect_fold(&mut self, src: impl IsSrc, kind: FoldKind, line_index: &LineIndex) { - if let Some(fold) = Fold::try_build(src.range(), kind, line_index) { + if let Some(fold) = Fold::try_build(src.expanded_range(), kind, line_index) { self.push(fold); } } @@ -285,18 +286,20 @@ fn collect_module( folds.collect_docs(&src_map.region_tree, line_index); if let Some(port_list_src) = src_map.port_srcs.port_list_src() { - let port_list_fold = Fold::try_build(port_list_src.range(), FoldKind::PortList, line_index); + let port_list_fold = + Fold::try_build(port_list_src.expanded_range(), FoldKind::PortList, line_index); let module_body_start = port_list_fold .as_ref() .and_then(|port_list| { let line = line_index.try_line_col(port_list.range.end())?.line + 1; line_index.range_for_line(line.min(line_index.lines_len().saturating_sub(1))) }) - .unwrap_or(module_src.range()); + .unwrap_or(module_src.expanded_range()); folds.extend(port_list_fold); - let module_range = TextRange::new(module_body_start.start(), module_src.range().end()); + let module_range = + TextRange::new(module_body_start.start(), module_src.expanded_range().end()); folds.extend(Fold::try_build(module_range, FoldKind::Module, line_index)); } else { folds.collect_fold(module_src, FoldKind::Module, line_index); @@ -313,12 +316,12 @@ fn collect_module( let instantiation_id = module.get(instance_id).parent; if module.get(instantiation_id).instances.len() > 1 { - let range = src.range(); - let start = src.name_range().map_or(range.start(), |r| r.end()); + let range = src.expanded_range(); + let start = src.expanded_name_range().map_or(range.start(), |r| r.end()); Fold::try_build(TextRange::new(start, range.end()), FoldKind::Instance, line_index) } else { let instantiation_src = src_map.get(instantiation_id)?; - Fold::try_build(instantiation_src.range(), FoldKind::Instance, line_index) + Fold::try_build(instantiation_src.expanded_range(), FoldKind::Instance, line_index) } })); diff --git a/crates/ide/src/inlay_hint.rs b/crates/ide/src/inlay_hint.rs index 9a57edf1..f86d0bf2 100644 --- a/crates/ide/src/inlay_hint.rs +++ b/crates/ide/src/inlay_hint.rs @@ -75,7 +75,7 @@ struct HintAnchor { impl HintAnchor { fn from_src(src: impl IsSrc, position: Option) -> Option { - let range = src.range(); + let range = src.expanded_range(); let kind = match_ast_kind! { src.kind(), ast::ParamAssignment => InlayKind::ParamAssign, ast::OrderedPortConnection | ast::EmptyPortConnection | ast::NamedPortConnection => InlayKind::Port, @@ -141,7 +141,7 @@ impl InlayHintCollector { } let (tooltip, target_location) = if let Some(InFile { value: src, file_id }) = target_src { - let location = InFile::new(file_id, src.range()); + let location = InFile::new(file_id, src.expanded_range()); (Some(Markup::new()), Some(location)) } else { (None, None) @@ -240,7 +240,7 @@ pub(crate) fn inlay_hint( continue; }; - if collector.intersect(module_src.range()) { + if collector.intersect(module_src.expanded_range()) { collect_module_items(db, module_id, module_src, &mut collector); } } @@ -308,7 +308,7 @@ fn collect_module_items( let Some(instantiation_src) = src_map.get(instantiation_id) else { continue; }; - if collector.intersect(instantiation_src.range()) { + if collector.intersect(instantiation_src.expanded_range()) { process_instantiation(db, module_id, module, src_map, instantiation, collector); } } @@ -347,7 +347,7 @@ fn process_instantiation( continue; }; let assign_src = src_map.get(assign_id)?; - check_or_throw!(collector.intersect(assign_src.range())); + check_or_throw!(collector.intersect(assign_src.expanded_range())); let param_id = target_module.param_port_id_by_idx(id)?; let param_name = target_module.get(param_id).name.as_ref()?; @@ -371,7 +371,7 @@ fn process_instantiation( let Some(instance_src) = src_map.get(*instance_id) else { continue; }; - if !collector.intersect(instance_src.range()) { + if !collector.intersect(instance_src.expanded_range()) { continue; } @@ -379,7 +379,7 @@ fn process_instantiation( try { let conn = module.get(conn_id); let conn_src = src_map.get(conn_id)?; - check_or_throw!(collector.intersect(conn_src.range())); + check_or_throw!(collector.intersect(conn_src.expanded_range())); match &target_module.ports { Ports::NonAnsi { .. } => { @@ -428,7 +428,7 @@ fn collect_connection_hint( PortDirection::Ref => "&", }; - let conn_start = conn_src.range().start(); + let conn_start = conn_src.expanded_range().start(); match conn { PortConn::Empty => { let label = format!("{name} {arrow}"); @@ -440,7 +440,8 @@ fn collect_connection_hint( let label = if same_name { arrow.to_string() } else { format!("{name} {arrow}") }; let target_src = if same_name { None } else { Some(target_src) }; let edit = if same_name { None } else { edits_for_conn(name, conn_src) }; - let position = src_map.get(*expr).map_or_else(|| conn_start, |src| src.range().start()); + let position = + src_map.get(*expr).map_or_else(|| conn_start, |src| src.expanded_range().start()); collector.collect_src_hint(conn_src, target_src, Some(position), label, edit); } PortConn::Named(port_name, expr) => { @@ -451,8 +452,8 @@ fn collect_connection_hint( (arrow.to_string(), None) }; let position = expr - .and_then(|expr| src_map.get(expr).map(|src| src.range().start())) - .or_else(|| conn_src.name_range().map(|range| range.start())) + .and_then(|expr| src_map.get(expr).map(|src| src.expanded_range().start())) + .or_else(|| conn_src.expanded_name_range().map(|range| range.start())) .unwrap_or(conn_start); collector.collect_src_hint(conn_src, target_src, Some(position), label, None); } @@ -542,8 +543,8 @@ fn ansi_port_decl_id_for_conn( fn edits_for_conn(param: &str, conn_src: impl IsSrc) -> Option { let mut builder = TextEdit::builder(); - builder.insert(conn_src.range().start(), format!(".{}(", param)); - builder.insert(conn_src.range().end(), String::from(")")); + builder.insert(conn_src.expanded_range().start(), format!(".{}(", param)); + builder.insert(conn_src.expanded_range().end(), String::from(")")); Some(builder.finish()) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 29313177..2b1694c7 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -18,6 +18,7 @@ pub mod definitions; pub mod markup; pub(crate) mod module_resolution; pub mod navigation_target; +pub(crate) mod presentation; pub mod render; pub mod source_change; diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 7dda6ab8..8929d8ba 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -2,6 +2,7 @@ use hir::{ base_db::intern::Lookup, container::{ContainerId, InContainer, InFile, InModule, InSubroutine}, db::HirDb, + file::HirFileId, hir_def::{ block::{BlockId, BlockLoc}, declaration::Declaration, @@ -25,13 +26,13 @@ use syntax::{ ast::AstNode, has_text_range::{HasTextRange, HasTextRangeIn}, }; -use utils::{ - get::{Get, GetRef}, - line_index::TextRange, -}; +use utils::{get::GetRef, line_index::TextRange}; use vfs::FileId; -use crate::{SymbolKind, db::root_db::RootDb, definitions::DefinitionOrigin}; +use crate::{ + SymbolKind, db::root_db::RootDb, definitions::DefinitionOrigin, + presentation::presentation_nav_range, +}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NavTarget { @@ -79,24 +80,40 @@ impl ToNav for DefinitionOrigin { impl ToNav for ModuleId { fn to_nav(&self, db: &RootDb) -> Option { let InFile { value: local_module_id, file_id } = *self; - let src = file_id.to_container_src_map(db).get(local_module_id)?; + let src_map = file_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + file_id, + src_map.module_srcs.hir_to_presentation(local_module_id)?, + )?; let name = self.to_container(db).name.clone(); - let file_id = file_id.file_id(); - Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Module, None)) + Some(build( + ranges.file_id, + ranges.focus_range, + ranges.full_range, + name, + SymbolKind::Module, + None, + )) } } impl ToNav for InFile { fn to_nav(&self, db: &RootDb) -> Option { let InFile { value: config_id, file_id } = *self; - let src = file_id.to_container_src_map(db).get(config_id)?; + let src_map = file_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + file_id, + src_map.config_decl_srcs.hir_to_presentation(config_id)?, + )?; let name = file_id.to_container(db).get(config_id).name.clone(); Some(build( - file_id.file_id(), - src.name_range(), - src.range(), + ranges.file_id, + ranges.focus_range, + ranges.full_range, name, SymbolKind::Config, None, @@ -107,13 +124,18 @@ impl ToNav for InFile { impl ToNav for InFile { fn to_nav(&self, db: &RootDb) -> Option { let InFile { value: library_id, file_id } = *self; - let src = file_id.to_container_src_map(db).get(library_id)?; + let src_map = file_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + file_id, + src_map.library_decl_srcs.hir_to_presentation(library_id)?, + )?; let name = file_id.to_container(db).get(library_id).name.clone(); Some(build( - file_id.file_id(), - src.name_range(), - src.range(), + ranges.file_id, + ranges.focus_range, + ranges.full_range, name, SymbolKind::Library, None, @@ -124,13 +146,18 @@ impl ToNav for InFile { impl ToNav for InFile { fn to_nav(&self, db: &RootDb) -> Option { let InFile { value: udp_id, file_id } = *self; - let src = file_id.to_container_src_map(db).get(udp_id)?; + let src_map = file_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + file_id, + src_map.udp_decl_srcs.hir_to_presentation(udp_id)?, + )?; let name = file_id.to_container(db).get(udp_id).name.clone(); Some(build( - file_id.file_id(), - src.name_range(), - src.range(), + ranges.file_id, + ranges.focus_range, + ranges.full_range, name, SymbolKind::Primitive, None, @@ -145,7 +172,14 @@ impl ToNav for BlockId { let cont_name = cont_id.to_container(db).name().cloned(); let file_id = file_id.file_id(); - Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Block, cont_name)) + Some(build( + file_id, + src.expanded_name_range(), + src.expanded_range(), + name, + SymbolKind::Block, + cont_name, + )) } } @@ -157,8 +191,8 @@ impl ToNav for GenerateBlockId { Some(build( file_id.file_id(), - src.name_range(), - src.range(), + src.expanded_name_range(), + src.expanded_range(), name, SymbolKind::Generate, cont_name, @@ -172,10 +206,21 @@ impl ToNav for SubroutineId { let cont_id: ContainerId = loc.cont_id.into(); let cont_name = cont_id.to_container(db).name().cloned(); let name = db.subroutine(*self).name.clone(); - let focus_range = loc.src.value.name_range(); + let src_map = loc.src.file_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + loc.src.file_id, + src_map.subroutine_srcs.hir_to_presentation(loc.local_id)?, + )?; - let file_id = loc.src.file_id.file_id(); - Some(build(file_id, focus_range, loc.src.value.range(), name, SymbolKind::Fn, cont_name)) + Some(build( + ranges.file_id, + ranges.focus_range, + ranges.full_range, + name, + SymbolKind::Fn, + cont_name, + )) } } @@ -218,18 +263,21 @@ impl ToNav for InModule { fn to_nav(&self, db: &RootDb) -> Option { let InModule { value: port_id, module_id } = *self; - let file_id = module_id.file_id; - let src = module_id.to_container_src_map(db).get(port_id)?; + let src_map = module_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + module_id.file_id, + src_map.port_srcs.port_presentation(port_id)?, + )?; let module = db.module(module_id); let name = module.get(port_id).label.clone(); let cont_name = module.name.clone(); - let file_id = file_id.file_id(); Some(build( - file_id, - src.name_range(), - src.range(), + ranges.file_id, + ranges.focus_range, + ranges.full_range, name, SymbolKind::NonAnsiPortLabel, cont_name, @@ -242,7 +290,9 @@ impl ToNav for InContainer { let InContainer { value: decl_id, cont_id } = *self; let file_id = cont_id.file_id(db); - let src = cont_id.to_container_src_map(db).get(decl_id)?; + let src_map = cont_id.to_container_src_map(db); + let ranges = + presentation_nav_range(db, HirFileId(file_id), src_map.decl_presentation(decl_id)?)?; let cont = cont_id.to_container(db); let decl = cont.get(decl_id); @@ -262,7 +312,7 @@ impl ToNav for InContainer { let name = decl.name.clone(); let cont_name = cont.name().cloned(); - Some(build(file_id, src.name_range(), src.range(), name, kind, cont_name)) + Some(build(ranges.file_id, ranges.focus_range, ranges.full_range, name, kind, cont_name)) } } @@ -271,16 +321,21 @@ impl ToNav for InContainer { let InContainer { value: typedef_id, cont_id } = *self; let file_id = cont_id.file_id(db); - let src = cont_id.to_container_src_map(db).get(typedef_id)?; + let src_map = cont_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + HirFileId(file_id), + src_map.typedef_presentation(typedef_id)?, + )?; let cont = cont_id.to_container(db); let typedef = cont.get(typedef_id); let cont_name = cont.name().cloned(); Some(build( - file_id, - src.name_range(), - src.range(), + ranges.file_id, + ranges.focus_range, + ranges.full_range, typedef.name.clone(), SymbolKind::Typedef, cont_name, @@ -292,14 +347,25 @@ impl ToNav for InModule { fn to_nav(&self, db: &RootDb) -> Option { let InModule { value: instance_id, module_id } = *self; - let file_id = module_id.file_id(); - let src = module_id.to_container_src_map(db).get(instance_id)?; + let src_map = module_id.to_container_src_map(db); + let ranges = presentation_nav_range( + db, + module_id.file_id, + src_map.instance_srcs.hir_to_presentation(instance_id)?, + )?; let module = module_id.to_container(db); let name = module.get(instance_id).name.clone(); let cont_name = module.name.clone(); - Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Instance, cont_name)) + Some(build( + ranges.file_id, + ranges.focus_range, + ranges.full_range, + name, + SymbolKind::Instance, + cont_name, + )) } } @@ -308,13 +374,22 @@ impl ToNav for InContainer { let InContainer { value: stmt_id, cont_id } = *self; let file_id = cont_id.file_id(db); - let src = cont_id.to_container_src_map(db).get(stmt_id)?; + let src_map = cont_id.to_container_src_map(db); + let ranges = + presentation_nav_range(db, HirFileId(file_id), src_map.stmt_presentation(stmt_id)?)?; let cont = cont_id.to_container(db); let name = cont.get(stmt_id).label.clone(); let cont_name = cont.name().cloned(); - Some(build(file_id, src.name_range(), src.range(), name, SymbolKind::Stmt, cont_name)) + Some(build( + ranges.file_id, + ranges.focus_range, + ranges.full_range, + name, + SymbolKind::Stmt, + cont_name, + )) } } diff --git a/crates/ide/src/presentation.rs b/crates/ide/src/presentation.rs new file mode 100644 index 00000000..1b7ef062 --- /dev/null +++ b/crates/ide/src/presentation.rs @@ -0,0 +1,98 @@ +use hir::{ + base_db::source_db::SourceRootDb, + container::InFile, + file::HirFileId, + preproc::resolve_source_presentation_anchor, + source_map::{SourcePresentation, SourcePresentationAnchor}, +}; +use utils::line_index::TextRange; +use vfs::FileId; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct PresentationNavRange { + pub file_id: FileId, + pub full_range: TextRange, + pub focus_range: Option, +} + +pub(crate) fn presentation_full_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option { + same_file_range(file_id, presentation_file_range(db, file_id, presentation.full)?) +} + +pub(crate) fn presentation_name_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option { + same_file_range(file_id, presentation_file_range(db, file_id, presentation.name?)?) +} + +pub(crate) fn presentation_full_file_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option> { + presentation_file_range(db, file_id, presentation.full) +} + +pub(crate) fn presentation_name_file_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option> { + presentation_file_range(db, file_id, presentation.name?) +} + +pub(crate) fn presentation_name_or_full_file_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option> { + presentation_name_file_range(db, file_id, presentation) + .or_else(|| presentation_full_file_range(db, file_id, presentation)) +} + +pub(crate) fn presentation_nav_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + presentation: &SourcePresentation, +) -> Option { + let full = presentation_full_file_range(db, file_id, presentation); + let focus = presentation_name_file_range(db, file_id, presentation); + match (full, focus) { + (Some(full), Some(focus)) if full.file_id == focus.file_id => Some(PresentationNavRange { + file_id: full.file_id.file_id(), + full_range: full.value, + focus_range: Some(focus.value), + }), + (Some(full), None) => Some(PresentationNavRange { + file_id: full.file_id.file_id(), + full_range: full.value, + focus_range: None, + }), + (None, Some(focus)) => Some(PresentationNavRange { + file_id: focus.file_id.file_id(), + full_range: focus.value, + focus_range: Some(focus.value), + }), + _ => None, + } +} + +fn presentation_file_range( + db: &dyn SourceRootDb, + file_id: HirFileId, + anchor: SourcePresentationAnchor, +) -> Option> { + let target = + resolve_source_presentation_anchor(db, file_id.file_id(), anchor).ok()?.available()?; + Some(InFile::new(HirFileId(target.file_id), target.range)) +} + +fn same_file_range(file_id: HirFileId, range: InFile) -> Option { + (range.file_id == file_id).then_some(range.value) +} diff --git a/crates/ide/src/references/search.rs b/crates/ide/src/references/search.rs index ad482c7a..a2495e54 100644 --- a/crates/ide/src/references/search.rs +++ b/crates/ide/src/references/search.rs @@ -84,8 +84,10 @@ impl SearchScope { match cont { ContainerId::HirFileId(_) => Self::all(db), ContainerId::ModuleId(InFile { value: local_module_id, file_id }) => { - if let Some(range) = - file_id.to_container_src_map(db).get(local_module_id).map(|src| src.range()) + if let Some(range) = file_id + .to_container_src_map(db) + .get(local_module_id) + .map(|src| src.expanded_range()) { Self::single_range(file_id.file_id(), range) } else { @@ -93,16 +95,16 @@ impl SearchScope { } } ContainerId::BlockId(block_id) => { - let range = block_id.lookup(db).src.value.range(); + let range = block_id.lookup(db).src.value.expanded_range(); Self::single_range(block_id.file_id(db), range) } ContainerId::GenerateBlockId(generate_block_id) => { let src = generate_block_id.lookup(db).src; - Self::single_range(src.file_id.file_id(), src.value.range()) + Self::single_range(src.file_id.file_id(), src.value.expanded_range()) } ContainerId::SubroutineId(subroutine_id) => { let src = subroutine_id.lookup(db).src; - Self::single_range(src.file_id.file_id(), src.value.range()) + Self::single_range(src.file_id.file_id(), src.value.expanded_range()) } } } diff --git a/crates/ide/src/semantic_tokens.rs b/crates/ide/src/semantic_tokens.rs index 6abb2cab..65449a04 100644 --- a/crates/ide/src/semantic_tokens.rs +++ b/crates/ide/src/semantic_tokens.rs @@ -30,6 +30,7 @@ use vfs::FileId; use crate::{ db::root_db::RootDb, module_resolution::{resolve_named_param_assignment, resolve_named_port_connection}, + presentation::{presentation_full_range, presentation_name_range}, }; mod collector; @@ -184,7 +185,7 @@ fn collect_file( let (hir_file, file_src_map) = sema.db.hir_file_with_source_map(file_id); for (local_module_id, _) in hir_file.modules.iter() { - let Some(range) = file_src_map.get(local_module_id).map(|src| src.range()) else { + let Some(range) = file_src_map.get(local_module_id).map(|src| src.expanded_range()) else { continue; }; check_range!(collector, range); @@ -201,10 +202,19 @@ fn collect_file( match expr { Expr::Field { .. } => {} Expr::Ident(name) => { - let Some(range) = file_src_map.get(expr_id).map(|src| src.range()) else { + let Some(expanded_range) = + file_src_map.get(expr_id).map(|src| src.expanded_range()) + else { + continue; + }; + check_range!(collector, expanded_range); + let Some(range) = + file_src_map.expr_srcs.hir_to_presentation(expr_id).and_then(|presentation| { + presentation_full_range(sema.db, file_id, presentation) + }) + else { continue; }; - check_range!(collector, range); collect_ident_like(name, range, collector); } _ => {} @@ -214,8 +224,12 @@ fn collect_file( for (decl_id, decl) in hir_file.decls.iter() { let _: Option<()> = try { let name = decl.name.as_ref()?; - let range = file_src_map.get(decl_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = file_src_map.get(decl_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = file_src_map + .decl_srcs + .hir_to_presentation(decl_id) + .and_then(|presentation| presentation_name_range(sema.db, file_id, presentation))?; collect_ident_like(name, range, collector); }; } @@ -223,8 +237,12 @@ fn collect_file( for (typedef_id, typedef) in hir_file.typedefs.iter() { let _: Option<()> = try { let _name = typedef.name.as_ref()?; - let range = file_src_map.get(typedef_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = file_src_map.get(typedef_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = file_src_map + .typedef_srcs + .hir_to_presentation(typedef_id) + .and_then(|presentation| presentation_name_range(sema.db, file_id, presentation))?; collector.tokens.add(SemaToken { range, tag: SemaTokenTag::Type, @@ -235,7 +253,7 @@ fn collect_file( for (stmt_id, stmt) in hir_file.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { - let Some(range) = file_src_map.get(stmt_id).map(|src| src.range()) else { + let Some(range) = file_src_map.get(stmt_id).map(|src| src.expanded_range()) else { continue; }; check_range!(collector, range); @@ -261,8 +279,17 @@ fn collect_module( }; for (instance_id, _) in module.instances.iter() { - if let Some(range) = module_src_map.get(instance_id).and_then(|src| src.name_range()) { - check_range!(collector, range); + if let Some(expanded_range) = + module_src_map.get(instance_id).and_then(|src| src.expanded_name_range()) + { + check_range!(collector, expanded_range); + let Some(range) = + module_src_map.instance_srcs.hir_to_presentation(instance_id).and_then( + |presentation| presentation_name_range(db, module_id.file_id, presentation), + ) + else { + continue; + }; let sema_token = SemaToken { range, tag: SemaTokenTag::Instance, mods: SemaTokenModifier::empty() }; collector.tokens.add(sema_token); @@ -276,10 +303,17 @@ fn collect_module( match expr { Expr::Field { .. } => {} Expr::Ident(name) => { - let Some(range) = module_src_map.get(expr_id).map(|src| src.range()) else { + let Some(expanded_range) = + module_src_map.get(expr_id).map(|src| src.expanded_range()) + else { + continue; + }; + check_range!(collector, expanded_range); + let Some(range) = module_src_map.expr_srcs.hir_to_presentation(expr_id).and_then( + |presentation| presentation_full_range(db, module_id.file_id, presentation), + ) else { continue; }; - check_range!(collector, range); collect_ident_like(name, range, collector); } _ => {} @@ -289,8 +323,12 @@ fn collect_module( for (decl_id, decl) in module.decls.iter() { let _: Option<()> = try { let name = decl.name.as_ref()?; - let range = module_src_map.get(decl_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = module_src_map.get(decl_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = + module_src_map.decl_srcs.hir_to_presentation(decl_id).and_then(|presentation| { + presentation_name_range(db, module_id.file_id, presentation) + })?; collect_ident_like(name, range, collector); }; } @@ -298,8 +336,11 @@ fn collect_module( for (typedef_id, typedef) in module.typedefs.iter() { let _: Option<()> = try { let _name = typedef.name.as_ref()?; - let range = module_src_map.get(typedef_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = module_src_map.get(typedef_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = module_src_map.typedef_srcs.hir_to_presentation(typedef_id).and_then( + |presentation| presentation_name_range(db, module_id.file_id, presentation), + )?; collector.tokens.add(SemaToken { range, tag: SemaTokenTag::Type, @@ -310,7 +351,7 @@ fn collect_module( for (stmt_id, stmt) in module.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { - let Some(range) = module_src_map.get(stmt_id).map(|src| src.range()) else { + let Some(range) = module_src_map.get(stmt_id).map(|src| src.expanded_range()) else { continue; }; check_range!(collector, range); @@ -338,10 +379,19 @@ fn collect_block( match expr { Expr::Field { .. } => {} Expr::Ident(name) => { - let Some(range) = block_src_map.get(expr_id).map(|src| src.range()) else { + let Some(expanded_range) = + block_src_map.get(expr_id).map(|src| src.expanded_range()) + else { + continue; + }; + check_range!(collector, expanded_range); + let Some(range) = + block_src_map.expr_srcs.hir_to_presentation(expr_id).and_then(|presentation| { + presentation_full_range(db, HirFileId(block_id.file_id(db)), presentation) + }) + else { continue; }; - check_range!(collector, range); collect_ident_like(name, range, collector); } _ => {} @@ -351,8 +401,12 @@ fn collect_block( for (decl_id, decl) in block.decls.iter() { let _: Option<()> = try { let name = decl.name.as_ref()?; - let range = block_src_map.get(decl_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = block_src_map.get(decl_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = + block_src_map.decl_srcs.hir_to_presentation(decl_id).and_then(|presentation| { + presentation_name_range(db, HirFileId(block_id.file_id(db)), presentation) + })?; collect_ident_like(name, range, collector); }; } @@ -360,8 +414,13 @@ fn collect_block( for (typedef_id, typedef) in block.typedefs.iter() { let _: Option<()> = try { let _name = typedef.name.as_ref()?; - let range = block_src_map.get(typedef_id)?.name_range()?; - check_range!(collector, range); + let expanded_range = block_src_map.get(typedef_id)?.expanded_name_range()?; + check_range!(collector, expanded_range); + let range = block_src_map.typedef_srcs.hir_to_presentation(typedef_id).and_then( + |presentation| { + presentation_name_range(db, HirFileId(block_id.file_id(db)), presentation) + }, + )?; collector.tokens.add(SemaToken { range, tag: SemaTokenTag::Type, @@ -372,7 +431,7 @@ fn collect_block( for (stmt_id, stmt) in block.stmts.iter() { if let StmtKind::Block(BlockInfo { block_id, .. }) = stmt.kind { - let Some(range) = block_src_map.get(stmt_id).map(|src| src.range()) else { + let Some(range) = block_src_map.get(stmt_id).map(|src| src.expanded_range()) else { continue; }; check_range!(collector, range); @@ -398,10 +457,17 @@ fn collect_named_port_connections( let Some(src) = module_src_map.get(conn_id) else { continue; }; - let Some(range) = src.name_range() else { + let Some(expanded_range) = src.expanded_name_range() else { + continue; + }; + check_range!(collector, expanded_range); + let Some(range) = module_src_map + .inst_port_conn_srcs + .hir_to_presentation(conn_id) + .and_then(|presentation| presentation_name_range(db, module_id.file_id, presentation)) + else { continue; }; - check_range!(collector, range); let Some(named) = src.to_node(&tree).and_then(ast::PortConnection::as_named_port_connection) @@ -431,10 +497,17 @@ fn collect_named_param_assignments( let Some(src) = module_src_map.get(assign_id) else { continue; }; - let Some(range) = src.name_range() else { + let Some(expanded_range) = src.expanded_name_range() else { + continue; + }; + check_range!(collector, expanded_range); + let Some(range) = module_src_map + .inst_param_assign_srcs + .hir_to_presentation(assign_id) + .and_then(|presentation| presentation_name_range(db, module_id.file_id, presentation)) + else { continue; }; - check_range!(collector, range); let Some(named) = src.to_node(&tree).and_then(ast::ParamAssignment::as_named_param_assignment) @@ -752,4 +825,69 @@ endmodule "named port connection must not produce a token spanning the whole connection" ); } + + #[test] + fn macro_argument_semantic_tokens_use_original_token_ranges() { + let text = "\ +`define PIPE_ASSIGN(name, expr) assign name``_q = expr + +module top( + input logic sample_i, + output logic sample_q, + output logic trace_q +); +`PIPE_ASSIGN(sample, sample_i) +`PIPE_ASSIGN(trace, sample_q ^ 1'b1) +endmodule +"; + let (host, file_id) = setup(text); + let tokens = host + .make_analysis() + .semantic_tokens( + file_id, + SemaTokenConfig { port: SemaTokenPortConfig { clk_rst: false, io: true } }, + Some(TextRange::up_to(TextSize::of(text))), + ) + .unwrap(); + + let range_for = |needle: &str| { + let start = text.find(needle).unwrap_or_else(|| panic!("missing {needle}")); + TextRange::new( + TextSize::from(start as u32), + TextSize::from((start + needle.len()) as u32), + ) + }; + let arg_sample = range_for("sample, sample_i"); + let arg_trace = range_for("trace, sample_q ^ 1'b1"); + let sample = + TextRange::new(arg_sample.start(), arg_sample.start() + TextSize::of("sample")); + let sample_i = + TextRange::new(arg_sample.end() - TextSize::of("sample_i"), arg_sample.end()); + let trace = TextRange::new(arg_trace.start(), arg_trace.start() + TextSize::of("trace")); + + let token_at = |range: TextRange| { + tokens + .iter() + .find(|token| !token.is_empty() && token.range == range) + .copied() + .unwrap_or_else(|| panic!("expected semantic token at {range:?}: {tokens:?}")) + }; + + assert_eq!( + (token_at(sample).tag, token_at(sample).mods), + (SemaTokenTag::Port(SemaTokenPort::Others), SemaTokenModifier::WRITE) + ); + assert_eq!( + (token_at(sample_i).tag, token_at(sample_i).mods), + (SemaTokenTag::Port(SemaTokenPort::Others), SemaTokenModifier::READ) + ); + assert_eq!( + (token_at(trace).tag, token_at(trace).mods), + (SemaTokenTag::Port(SemaTokenPort::Others), SemaTokenModifier::WRITE) + ); + assert!( + tokens.iter().all(|token| token.range != arg_sample && token.range != arg_trace), + "macro argument semantic tokens must not cover full arguments: {tokens:?}" + ); + } } diff --git a/crates/ide/src/semantic_tokens/port.rs b/crates/ide/src/semantic_tokens/port.rs index 5ffa32ac..bd0111e4 100644 --- a/crates/ide/src/semantic_tokens/port.rs +++ b/crates/ide/src/semantic_tokens/port.rs @@ -27,6 +27,7 @@ use utils::{ use super::{SemaTokenCollector, SemaTokenTag}; use crate::{ db::root_db::RootDb, + presentation::presentation_name_range, semantic_tokens::{SemaToken, SemaTokenModifier, SemaTokenPort, check_range}, }; @@ -50,15 +51,22 @@ pub(super) fn collect_port( let Some(port_src) = module_src_map.get(port_id) else { continue; }; - check_range!(collector, port_src.range()); + check_range!(collector, port_src.expanded_range()); let Some(refs) = refs.clone() else { continue; }; for ref_id in refs { let _: Option<()> = try { - let name_range = module_src_map.get(ref_id)?.name_range()?; - check_range!(collector, name_range); + let expanded_name_range = + module_src_map.get(ref_id)?.expanded_name_range()?; + check_range!(collector, expanded_name_range); + let name_range = module_src_map + .port_srcs + .ref_presentation(ref_id) + .and_then(|presentation| { + presentation_name_range(db, module_id.file_id, presentation) + })?; let name = module.get(ref_id).ident.as_ref()?; let entry = module_scope.get(name)?; @@ -71,13 +79,20 @@ pub(super) fn collect_port( let Some(port_decl_src) = module_src_map.get(port_decl_id) else { continue; }; - check_range!(collector, port_decl_src.range()); + check_range!(collector, port_decl_src.expanded_range()); for decl_id in port_decl.decls.clone() { let _: Option<()> = try { let decl = module.get(decl_id); - let name_range = module_src_map.get(decl_id)?.name_range()?; - check_range!(collector, name_range); + let expanded_name_range = + module_src_map.get(decl_id)?.expanded_name_range()?; + check_range!(collector, expanded_name_range); + let name_range = module_src_map + .decl_srcs + .hir_to_presentation(decl_id) + .and_then(|presentation| { + presentation_name_range(db, module_id.file_id, presentation) + })?; let name = decl.name.as_ref()?; let entry = module_scope.get(name)?; @@ -93,13 +108,20 @@ pub(super) fn collect_port( let Some(port_decl_src) = module_src_map.get(port_decl_id) else { continue; }; - check_range!(collector, port_decl_src.range()); + check_range!(collector, port_decl_src.expanded_range()); for decl_id in port_decl.decls.clone() { let _: Option<()> = try { let decl = module.get(decl_id); - let name_range = module_src_map.get(decl_id)?.name_range()?; - check_range!(collector, name_range); + let expanded_name_range = + module_src_map.get(decl_id)?.expanded_name_range()?; + check_range!(collector, expanded_name_range); + let name_range = module_src_map + .decl_srcs + .hir_to_presentation(decl_id) + .and_then(|presentation| { + presentation_name_range(db, module_id.file_id, presentation) + })?; let name = decl.name.as_ref()?; let header = &port_decl.header;