Skip to content

Commit 6b7b09d

Browse files
committed
internal: Record unresolved derive invocations in hir
1 parent 989c06b commit 6b7b09d

File tree

8 files changed

+87
-52
lines changed

8 files changed

+87
-52
lines changed

crates/hir/src/semantics.rs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@ use smallvec::{smallvec, SmallVec};
1818
use syntax::{
1919
algo::skip_trivia_token,
2020
ast::{self, HasAttrs, HasGenericParams, HasLoopBody},
21-
match_ast, AstNode, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
21+
match_ast, AstNode, AstToken, Direction, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken,
22+
TextSize, T,
2223
};
2324

2425
use crate::{
2526
db::HirDatabase,
2627
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
27-
source_analyzer::{resolve_hir_path, resolve_hir_path_as_macro, SourceAnalyzer},
28+
source_analyzer::{resolve_hir_path, SourceAnalyzer},
2829
Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource,
2930
HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, Name, Path,
3031
ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
@@ -354,6 +355,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
354355
self.imp.resolve_bind_pat_to_const(pat)
355356
}
356357

358+
pub fn resolve_derive_ident(&self, ident: &ast::Ident) -> Option<PathResolution> {
359+
self.imp.resolve_derive_ident(ident)
360+
}
361+
357362
// FIXME: use this instead?
358363
// pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
359364

@@ -894,6 +899,64 @@ impl<'db> SemanticsImpl<'db> {
894899
self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat)
895900
}
896901

902+
fn resolve_derive_ident(&self, ident: &ast::Ident) -> Option<PathResolution> {
903+
// derive macros are always at depth 2, tokentree -> meta -> attribute
904+
let syntax = ident.syntax();
905+
let attr = syntax.ancestors().nth(2).and_then(ast::Attr::cast)?;
906+
907+
let tt = attr.token_tree()?;
908+
if !tt.syntax().text_range().contains_range(ident.syntax().text_range()) {
909+
return None;
910+
}
911+
912+
// Fetch hir::Attr definition
913+
// FIXME: Move this to ToDef impl?
914+
let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
915+
let attr_pos = adt.attrs().position(|it| it == attr)?;
916+
let attrs = {
917+
let file_id = self.find_file(adt.syntax()).file_id;
918+
let adt = InFile::new(file_id, adt);
919+
let def = self.with_ctx(|ctx| ctx.adt_to_def(adt))?;
920+
self.db.attrs(def.into())
921+
};
922+
let attr_def = attrs.get(attr_pos)?;
923+
924+
let mut derive_paths = attr_def.parse_path_comma_token_tree()?;
925+
let derives = self.resolve_derive_macro(&attr)?;
926+
927+
let derive_idx = tt
928+
.syntax()
929+
.children_with_tokens()
930+
.filter_map(SyntaxElement::into_token)
931+
.take_while(|tok| tok != syntax)
932+
.filter(|t| t.kind() == T![,])
933+
.count();
934+
let path_segment_idx = syntax
935+
.siblings_with_tokens(Direction::Prev)
936+
.filter_map(SyntaxElement::into_token)
937+
.take_while(|tok| matches!(tok.kind(), T![:] | T![ident]))
938+
.filter(|tok| tok.kind() == T![ident])
939+
.count();
940+
941+
let mut mod_path = derive_paths.nth(derive_idx)?;
942+
943+
if path_segment_idx < mod_path.len() {
944+
// the path for the given ident is a qualifier, resolve to module if possible
945+
while path_segment_idx < mod_path.len() {
946+
mod_path.pop_segment();
947+
}
948+
resolve_hir_path(
949+
self.db,
950+
&self.scope(attr.syntax()).resolver,
951+
&Path::from_known_path(mod_path, []),
952+
)
953+
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_))))
954+
} else {
955+
// otherwise fetch the derive
956+
derives.get(derive_idx)?.map(PathResolution::Macro)
957+
}
958+
}
959+
897960
fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
898961
self.analyze(literal.syntax())
899962
.record_literal_missing_fields(self.db, literal)
@@ -1230,14 +1293,4 @@ impl<'a> SemanticsScope<'a> {
12301293
let path = Path::from_src(path.clone(), &ctx)?;
12311294
resolve_hir_path(self.db, &self.resolver, &path)
12321295
}
1233-
1234-
/// Resolve a path as-if it was written at the given scope. This is
1235-
/// necessary a heuristic, as it doesn't take hygiene into account.
1236-
// FIXME: This special casing solely exists for attributes for now
1237-
// ideally we should have a path resolution infra that properly knows about overlapping namespaces
1238-
pub fn speculative_resolve_as_mac(&self, path: &ast::Path) -> Option<MacroDef> {
1239-
let ctx = body::LowerCtx::new(self.db.upcast(), self.file_id);
1240-
let path = Path::from_src(path.clone(), &ctx)?;
1241-
resolve_hir_path_as_macro(self.db, &self.resolver, &path)
1242-
}
12431296
}

crates/hir_def/src/attr.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -720,11 +720,8 @@ impl Attr {
720720
Self::from_src(db, ast, hygiene, id)
721721
}
722722

723-
/// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
724-
/// to derive macros.
725-
///
726-
/// Returns `None` when the attribute does not have a well-formed `#[derive]` attribute input.
727-
pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
723+
/// Parses this attribute as a token tree consisting of comma separated paths.
724+
pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
728725
let args = match self.input.as_deref() {
729726
Some(AttrInput::TokenTree(args, _)) => args,
730727
_ => return None,

crates/hir_def/src/nameres/collector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,7 +1145,7 @@ impl DefCollector<'_> {
11451145
}
11461146
}
11471147

1148-
match attr.parse_derive() {
1148+
match attr.parse_path_comma_token_tree() {
11491149
Some(derive_macros) => {
11501150
let mut len = 0;
11511151
for (idx, path) in derive_macros.enumerate() {

crates/ide/src/goto_definition.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,18 @@ mod foo {
13811381
// ^^^^
13821382
}
13831383
#[derive(foo::Copy$0)]
1384+
struct Foo;
1385+
"#,
1386+
);
1387+
check(
1388+
r#"
1389+
//- minicore:derive
1390+
mod foo {
1391+
// ^^^
1392+
#[rustc_builtin_macro]
1393+
pub macro Copy {}
1394+
}
1395+
#[derive(foo$0::Copy)]
13841396
struct Foo;
13851397
"#,
13861398
);

crates/ide/src/syntax_highlighting/highlight.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use hir::{AsAssocItem, HasVisibility, Semantics};
44
use ide_db::{
55
defs::{Definition, NameClass, NameRefClass},
6-
helpers::{try_resolve_derive_input, FamousDefs},
6+
helpers::FamousDefs,
77
RootDatabase, SymbolKind,
88
};
99
use rustc_hash::FxHashMap;
@@ -40,13 +40,8 @@ pub(super) fn token(
4040
BYTE => HlTag::ByteLiteral.into(),
4141
CHAR => HlTag::CharLiteral.into(),
4242
IDENT if parent_matches::<ast::TokenTree>(&token) => {
43-
match token.ancestors().nth(2).and_then(ast::Attr::cast) {
44-
Some(attr) => {
45-
match try_resolve_derive_input(sema, &attr, &ast::Ident::cast(token).unwrap()) {
46-
Some(res) => highlight_def(sema, krate, Definition::from(res)),
47-
None => HlTag::None.into(),
48-
}
49-
}
43+
match sema.resolve_derive_ident(&ast::Ident::cast(token).unwrap()) {
44+
Some(res) => highlight_def(sema, krate, Definition::from(res)),
5045
None => HlTag::None.into(),
5146
}
5247
}

crates/ide_db/src/defs.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use syntax::{
1717
match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken,
1818
};
1919

20-
use crate::{helpers::try_resolve_derive_input, RootDatabase};
20+
use crate::RootDatabase;
2121

2222
// FIXME: a more precise name would probably be `Symbol`?
2323
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
@@ -55,11 +55,8 @@ impl Definition {
5555
let attr = ast::TokenTree::cast(parent.clone())
5656
.and_then(|tt| tt.parent_meta())
5757
.and_then(|meta| meta.parent_attr());
58-
if let Some(attr) = attr {
59-
return try_resolve_derive_input(&sema, &attr, &ident)
60-
.map(Into::into)
61-
.into_iter()
62-
.collect();
58+
if let Some(_) = attr {
59+
return sema.resolve_derive_ident(&ident).map(Into::into).into_iter().collect();
6360
}
6461
}
6562
Self::from_node(sema, &parent)

crates/ide_db/src/helpers.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,6 @@ pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option<ast::Path> {
7474
})
7575
}
7676

77-
/// Parses and resolves the path at the cursor position in the given attribute, if it is a derive.
78-
/// This special case is required because the derive macro is a compiler builtin that discards the input derives.
79-
pub fn try_resolve_derive_input(
80-
sema: &hir::Semantics<RootDatabase>,
81-
attr: &ast::Attr,
82-
cursor: &ast::Ident,
83-
) -> Option<PathResolution> {
84-
let path = get_path_in_derive_attr(sema, attr, cursor)?;
85-
let scope = sema.scope(attr.syntax());
86-
// FIXME: This double resolve shouldn't be necessary
87-
// It's only here so we prefer macros over other namespaces
88-
match scope.speculative_resolve_as_mac(&path) {
89-
Some(mac) if mac.kind() == hir::MacroKind::Derive => Some(PathResolution::Macro(mac)),
90-
Some(_) => return None,
91-
None => scope
92-
.speculative_resolve(&path)
93-
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))),
94-
}
95-
}
96-
9777
/// Picks the token with the highest rank returned by the passed in function.
9878
pub fn pick_best_token(
9979
tokens: TokenAtOffset<SyntaxToken>,

crates/ide_db/src/helpers/import_assets.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ impl ImportAssets {
146146
if let Some(_) = path.qualifier() {
147147
return None;
148148
}
149+
149150
let name = NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string());
150151
let candidate_node = attr.syntax().clone();
151152
Some(Self {

0 commit comments

Comments
 (0)