internal: Record unresolved derive invocations in hir

This commit is contained in:
Lukas Wirth 2022-01-01 20:31:04 +01:00
parent 989c06b25d
commit 6b7b09d329
8 changed files with 87 additions and 52 deletions

View File

@ -18,13 +18,14 @@ use smallvec::{smallvec, SmallVec};
use syntax::{
algo::skip_trivia_token,
ast::{self, HasAttrs, HasGenericParams, HasLoopBody},
match_ast, AstNode, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize,
match_ast, AstNode, AstToken, Direction, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken,
TextSize, T,
};
use crate::{
db::HirDatabase,
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, resolve_hir_path_as_macro, SourceAnalyzer},
source_analyzer::{resolve_hir_path, SourceAnalyzer},
Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasSource,
HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, Name, Path,
ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef,
@ -354,6 +355,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.resolve_bind_pat_to_const(pat)
}
pub fn resolve_derive_ident(&self, ident: &ast::Ident) -> Option<PathResolution> {
self.imp.resolve_derive_ident(ident)
}
// FIXME: use this instead?
// pub fn resolve_name_ref(&self, name_ref: &ast::NameRef) -> Option<???>;
@ -894,6 +899,64 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat)
}
fn resolve_derive_ident(&self, ident: &ast::Ident) -> Option<PathResolution> {
// derive macros are always at depth 2, tokentree -> meta -> attribute
let syntax = ident.syntax();
let attr = syntax.ancestors().nth(2).and_then(ast::Attr::cast)?;
let tt = attr.token_tree()?;
if !tt.syntax().text_range().contains_range(ident.syntax().text_range()) {
return None;
}
// Fetch hir::Attr definition
// FIXME: Move this to ToDef impl?
let adt = attr.syntax().parent().and_then(ast::Adt::cast)?;
let attr_pos = adt.attrs().position(|it| it == attr)?;
let attrs = {
let file_id = self.find_file(adt.syntax()).file_id;
let adt = InFile::new(file_id, adt);
let def = self.with_ctx(|ctx| ctx.adt_to_def(adt))?;
self.db.attrs(def.into())
};
let attr_def = attrs.get(attr_pos)?;
let mut derive_paths = attr_def.parse_path_comma_token_tree()?;
let derives = self.resolve_derive_macro(&attr)?;
let derive_idx = tt
.syntax()
.children_with_tokens()
.filter_map(SyntaxElement::into_token)
.take_while(|tok| tok != syntax)
.filter(|t| t.kind() == T![,])
.count();
let path_segment_idx = syntax
.siblings_with_tokens(Direction::Prev)
.filter_map(SyntaxElement::into_token)
.take_while(|tok| matches!(tok.kind(), T![:] | T![ident]))
.filter(|tok| tok.kind() == T![ident])
.count();
let mut mod_path = derive_paths.nth(derive_idx)?;
if path_segment_idx < mod_path.len() {
// the path for the given ident is a qualifier, resolve to module if possible
while path_segment_idx < mod_path.len() {
mod_path.pop_segment();
}
resolve_hir_path(
self.db,
&self.scope(attr.syntax()).resolver,
&Path::from_known_path(mod_path, []),
)
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_))))
} else {
// otherwise fetch the derive
derives.get(derive_idx)?.map(PathResolution::Macro)
}
}
fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> {
self.analyze(literal.syntax())
.record_literal_missing_fields(self.db, literal)
@ -1230,14 +1293,4 @@ impl<'a> SemanticsScope<'a> {
let path = Path::from_src(path.clone(), &ctx)?;
resolve_hir_path(self.db, &self.resolver, &path)
}
/// Resolve a path as-if it was written at the given scope. This is
/// necessary a heuristic, as it doesn't take hygiene into account.
// FIXME: This special casing solely exists for attributes for now
// ideally we should have a path resolution infra that properly knows about overlapping namespaces
pub fn speculative_resolve_as_mac(&self, path: &ast::Path) -> Option<MacroDef> {
let ctx = body::LowerCtx::new(self.db.upcast(), self.file_id);
let path = Path::from_src(path.clone(), &ctx)?;
resolve_hir_path_as_macro(self.db, &self.resolver, &path)
}
}

View File

@ -720,11 +720,8 @@ impl Attr {
Self::from_src(db, ast, hygiene, id)
}
/// Parses this attribute as a `#[derive]`, returns an iterator that yields all contained paths
/// to derive macros.
///
/// Returns `None` when the attribute does not have a well-formed `#[derive]` attribute input.
pub(crate) fn parse_derive(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
/// Parses this attribute as a token tree consisting of comma separated paths.
pub fn parse_path_comma_token_tree(&self) -> Option<impl Iterator<Item = ModPath> + '_> {
let args = match self.input.as_deref() {
Some(AttrInput::TokenTree(args, _)) => args,
_ => return None,

View File

@ -1145,7 +1145,7 @@ impl DefCollector<'_> {
}
}
match attr.parse_derive() {
match attr.parse_path_comma_token_tree() {
Some(derive_macros) => {
let mut len = 0;
for (idx, path) in derive_macros.enumerate() {

View File

@ -1381,6 +1381,18 @@ mod foo {
// ^^^^
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
);
check(
r#"
//- minicore:derive
mod foo {
// ^^^
#[rustc_builtin_macro]
pub macro Copy {}
}
#[derive(foo$0::Copy)]
struct Foo;
"#,
);

View File

@ -3,7 +3,7 @@
use hir::{AsAssocItem, HasVisibility, Semantics};
use ide_db::{
defs::{Definition, NameClass, NameRefClass},
helpers::{try_resolve_derive_input, FamousDefs},
helpers::FamousDefs,
RootDatabase, SymbolKind,
};
use rustc_hash::FxHashMap;
@ -40,13 +40,8 @@ pub(super) fn token(
BYTE => HlTag::ByteLiteral.into(),
CHAR => HlTag::CharLiteral.into(),
IDENT if parent_matches::<ast::TokenTree>(&token) => {
match token.ancestors().nth(2).and_then(ast::Attr::cast) {
Some(attr) => {
match try_resolve_derive_input(sema, &attr, &ast::Ident::cast(token).unwrap()) {
Some(res) => highlight_def(sema, krate, Definition::from(res)),
None => HlTag::None.into(),
}
}
match sema.resolve_derive_ident(&ast::Ident::cast(token).unwrap()) {
Some(res) => highlight_def(sema, krate, Definition::from(res)),
None => HlTag::None.into(),
}
}

View File

@ -17,7 +17,7 @@ use syntax::{
match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken,
};
use crate::{helpers::try_resolve_derive_input, RootDatabase};
use crate::RootDatabase;
// FIXME: a more precise name would probably be `Symbol`?
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
@ -55,11 +55,8 @@ impl Definition {
let attr = ast::TokenTree::cast(parent.clone())
.and_then(|tt| tt.parent_meta())
.and_then(|meta| meta.parent_attr());
if let Some(attr) = attr {
return try_resolve_derive_input(&sema, &attr, &ident)
.map(Into::into)
.into_iter()
.collect();
if let Some(_) = attr {
return sema.resolve_derive_ident(&ident).map(Into::into).into_iter().collect();
}
}
Self::from_node(sema, &parent)

View File

@ -74,26 +74,6 @@ pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option<ast::Path> {
})
}
/// Parses and resolves the path at the cursor position in the given attribute, if it is a derive.
/// This special case is required because the derive macro is a compiler builtin that discards the input derives.
pub fn try_resolve_derive_input(
sema: &hir::Semantics<RootDatabase>,
attr: &ast::Attr,
cursor: &ast::Ident,
) -> Option<PathResolution> {
let path = get_path_in_derive_attr(sema, attr, cursor)?;
let scope = sema.scope(attr.syntax());
// FIXME: This double resolve shouldn't be necessary
// It's only here so we prefer macros over other namespaces
match scope.speculative_resolve_as_mac(&path) {
Some(mac) if mac.kind() == hir::MacroKind::Derive => Some(PathResolution::Macro(mac)),
Some(_) => return None,
None => scope
.speculative_resolve(&path)
.filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))),
}
}
/// Picks the token with the highest rank returned by the passed in function.
pub fn pick_best_token(
tokens: TokenAtOffset<SyntaxToken>,

View File

@ -146,6 +146,7 @@ impl ImportAssets {
if let Some(_) = path.qualifier() {
return None;
}
let name = NameToImport::exact_case_sensitive(path.segment()?.name_ref()?.to_string());
let candidate_node = attr.syntax().clone();
Some(Self {