diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-02-22 10:12:30 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-02-22 10:12:30 +0000 |
| commit | bd2b6c40bca71687c5e0d4d15224acd43f647cc6 (patch) | |
| tree | 21538ad7d3af0e7f04fcf2117cf5639dffee32cc | |
| parent | 1fe3b2edd68889489d2702507ac0ba22a5126adf (diff) | |
| parent | b494795a423d91c8db06d09d6c6dc62840796e8d (diff) | |
| download | rust-bd2b6c40bca71687c5e0d4d15224acd43f647cc6.tar.gz rust-bd2b6c40bca71687c5e0d4d15224acd43f647cc6.zip | |
Merge #11513
11513: internal: Expand the derive attribute into a pseudo expansion r=Veykril a=Veykril Quoting my comment: > We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute > itself in name res, but we do want to expand it to something for the IDE layer, so that the input > derive attributes can be downmapped, and resolved as proper paths. > This is basically a hack, that simplifies the hacks we need in a lot of ide layer places to > somewhat inconsistently resolve derive attributes. > > As such, we expand `#[derive(Foo, bar::Bar)]` into > ``` > #[Foo] > #[bar::Bar] > (); > ``` > which allows fallback path resolution in hir::Semantics to properly identify our derives. > Since we do not expand the attribute in nameres though, we keep the original item. > > The ideal expansion here would be for the `#[derive]` to re-emit the annotated item and somehow > use the input paths in its output as well. > But that would bring two problems with it, for one every derive would duplicate the item token tree > wasting a lot of memory, and it would also require some way to use a path in a way that makes it > always resolve as a derive without nameres recollecting them. > So this hacky approach is a lot more friendly for us, though it does require a bit of support in > [`hir::Semantics`] to make this work. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
29 files changed, 419 insertions, 328 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index f047971a116..9f89bcf9c3d 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1792,6 +1792,10 @@ impl MacroDef { } } + pub fn is_builtin_derive(&self) -> bool { + matches!(self.id.kind, MacroDefKind::BuiltInAttr(exp, _) if exp.is_derive()) + } + pub fn is_attr(&self) -> bool { matches!(self.kind(), MacroKind::Attr) } @@ -2426,24 +2430,7 @@ impl Impl { pub fn is_builtin_derive(self, db: &dyn HirDatabase) -> Option<InFile<ast::Attr>> { let src = self.source(db)?; - let item = src.file_id.is_builtin_derive(db.upcast())?; - let hygenic = hir_expand::hygiene::Hygiene::new(db.upcast(), item.file_id); - - // FIXME: handle `cfg_attr` - let attr = item - .value - .attrs() - .filter_map(|it| { - let path = ModPath::from_src(db.upcast(), it.path()?, &hygenic)?; - if path.as_ident()?.to_smol_str() == "derive" { - Some(it) - } else { - None - } - }) - .last()?; - - Some(item.with_value(attr)) + src.file_id.is_builtin_derive(db.upcast()) } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 243ba63b8a0..20c360e302e 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -5,7 +5,6 @@ mod source_to_def; use std::{cell::RefCell, fmt, iter}; use base_db::{FileId, FileRange}; -use either::Either; use hir_def::{ body, resolver::{self, HasResolver, Resolver, TypeNs}, @@ -19,17 +18,16 @@ use smallvec::{smallvec, SmallVec}; use syntax::{ algo::skip_trivia_token, ast::{self, HasAttrs as _, HasGenericParams, HasLoopBody}, - match_ast, AstNode, AstToken, Direction, SyntaxElement, SyntaxNode, SyntaxNodePtr, SyntaxToken, - TextSize, T, + match_ast, AstNode, Direction, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextSize, }; use crate::{ db::HirDatabase, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{resolve_hir_path, SourceAnalyzer}, - Access, AssocItem, BuiltinAttr, Callable, ConstParam, Crate, Field, Function, HasAttrs as _, - HasSource, HirFileId, Impl, InFile, Label, LifetimeParam, Local, MacroDef, Module, ModuleDef, - Name, Path, ScopeDef, ToolModule, Trait, Type, TypeAlias, TypeParam, VariantDef, + 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, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -162,6 +160,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.is_attr_macro_call(item) } + pub fn is_derive_annotated(&self, item: &ast::Adt) -> bool { + self.imp.is_derive_annotated(item) + } + pub fn speculative_expand( &self, actual_macro_call: &ast::MacroCall, @@ -350,14 +352,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.resolve_bind_pat_to_const(pat) } - pub fn resolve_derive_ident( - &self, - derive: &ast::Attr, - ident: &ast::Ident, - ) -> Option<PathResolution> { - self.imp.resolve_derive_ident(derive, ident) - } - pub fn record_literal_missing_fields(&self, literal: &ast::RecordExpr) -> Vec<(Field, Type)> { self.imp.record_literal_missing_fields(literal) } @@ -475,11 +469,17 @@ impl<'db> SemanticsImpl<'db> { let adt = InFile::new(file_id, &adt); let src = InFile::new(file_id, attr.clone()); self.with_ctx(|ctx| { - let (_, res) = ctx.attr_to_derive_macro_call(adt, src)?; + let (.., res) = ctx.attr_to_derive_macro_call(adt, src)?; Some(res.to_vec()) }) } + fn is_derive_annotated(&self, adt: &ast::Adt) -> bool { + let file_id = self.find_file(adt.syntax()).file_id; + let adt = InFile::new(file_id, adt); + self.with_ctx(|ctx| ctx.has_derives(adt)) + } + fn is_attr_macro_call(&self, item: &ast::Item) -> bool { let file_id = self.find_file(item.syntax()).file_id; let src = InFile::new(file_id, item.clone()); @@ -668,7 +668,36 @@ impl<'db> SemanticsImpl<'db> { // FIXME replace map.while_some with take_while once stable token.value.ancestors().map(ast::TokenTree::cast).while_some().last() { - let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; + let parent = tt.syntax().parent()?; + // check for derive attribute here + let macro_call = match_ast! { + match parent { + ast::MacroCall(mcall) => mcall, + // attribute we failed expansion for earlier, this might be a derive invocation + // so try downmapping the token into the pseudo derive expansion + // see [hir_expand::builtin_attr_macro] for how the pseudo derive expansion works + ast::Meta(meta) => { + let attr = meta.parent_attr()?; + let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + let call_id = self.with_ctx(|ctx| { + let (_, call_id, _) = ctx.attr_to_derive_macro_call( + token.with_value(&adt), + token.with_value(attr), + )?; + Some(call_id) + })?; + let file_id = call_id.as_file(); + return process_expansion_for_token( + &mut stack, + file_id, + Some(adt.into()), + token.as_ref(), + ); + }, + _ => return None, + } + }; + if tt.left_delimiter_token().map_or(false, |it| it == token.value) { return None; } @@ -898,72 +927,6 @@ impl<'db> SemanticsImpl<'db> { self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat) } - fn resolve_derive_ident( - &self, - derive: &ast::Attr, - ident: &ast::Ident, - ) -> Option<PathResolution> { - debug_assert!(ident.syntax().parent().and_then(ast::TokenTree::cast).is_some()); - debug_assert!(ident.syntax().ancestors().any(|anc| anc == *derive.syntax())); - // derive macros are always at depth 2, tokentree -> meta -> attribute - let syntax = ident.syntax(); - - let tt = derive.token_tree()?; - let file = self.find_file(derive.syntax()); - let adt = derive.syntax().parent().and_then(ast::Adt::cast)?; - let adt_def = ToDef::to_def(self, file.with_value(adt.clone()))?; - let res = self.with_ctx(|ctx| { - let (attr_id, derives) = ctx.attr_to_derive_macro_call( - file.with_value(&adt), - file.with_value(derive.clone()), - )?; - let attrs = adt_def.attrs(self.db); - let mut derive_paths = attrs.get(attr_id)?.parse_path_comma_token_tree()?; - - 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(); - } - Some(Either::Left(mod_path)) - } else { - // otherwise fetch the derive - Some(Either::Right(derives[derive_idx])) - } - })?; - - match res { - Either::Left(path) => { - let len = path.len(); - resolve_hir_path( - self.db, - &self.scope(derive.syntax()).resolver, - &Path::from_known_path(path, vec![None; len]), - ) - .filter(|res| matches!(res, PathResolution::Def(ModuleDef::Module(_)))) - } - Either::Right(derive) => derive - .map(|call| MacroDef { id: self.db.lookup_intern_macro_call(call).def }) - .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) diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index 50618612392..dddb8e33dcc 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -249,9 +249,14 @@ impl SourceToDefCtx<'_, '_> { &mut self, item: InFile<&ast::Adt>, src: InFile<ast::Attr>, - ) -> Option<(AttrId, &[Option<MacroCallId>])> { + ) -> Option<(AttrId, MacroCallId, &[Option<MacroCallId>])> { let map = self.dyn_map(item)?; - map[keys::DERIVE_MACRO_CALL].get(&src.value).map(|(id, ids)| (*id, &**ids)) + map[keys::DERIVE_MACRO_CALL] + .get(&src.value) + .map(|&(attr_id, call_id, ref ids)| (attr_id, call_id, &**ids)) + } + pub(super) fn has_derives(&mut self, adt: InFile<&ast::Adt>) -> bool { + self.dyn_map(adt).as_ref().map_or(false, |map| !map[keys::DERIVE_MACRO_CALL].is_empty()) } fn to_def<Ast: AstNode + 'static, ID: Copy + 'static>( diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 03e4420985e..ef32a5891c7 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -371,10 +371,10 @@ impl SourceAnalyzer { return builtin.map(PathResolution::BuiltinAttr); } return match resolve_hir_path_as_macro(db, &self.resolver, &hir_path) { - res @ Some(m) if m.is_attr() => res.map(PathResolution::Macro), + Some(m) => Some(PathResolution::Macro(m)), // this labels any path that starts with a tool module as the tool itself, this is technically wrong // but there is no benefit in differentiating these two cases for the time being - _ => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| { + None => path.first_segment().and_then(|it| it.name_ref()).and_then(|name_ref| { match self.resolver.krate() { Some(krate) => ToolModule::by_name(db, krate.into(), &name_ref.text()), None => ToolModule::builtin(&name_ref.text()), diff --git a/crates/hir_def/src/child_by_source.rs b/crates/hir_def/src/child_by_source.rs index 5c32a31e443..1e1573d4ae0 100644 --- a/crates/hir_def/src/child_by_source.rs +++ b/crates/hir_def/src/child_by_source.rs @@ -116,11 +116,11 @@ impl ChildBySource for ItemScope { self.derive_macro_invocs().filter(|(id, _)| id.file_id == file_id).for_each( |(ast_id, calls)| { let adt = ast_id.to_node(db.upcast()); - calls.for_each(|(attr_id, calls)| { + calls.for_each(|(attr_id, call_id, calls)| { if let Some(Either::Left(attr)) = adt.doc_comments_and_attrs().nth(attr_id.ast_index as usize) { - res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, calls.into())); + res[keys::DERIVE_MACRO_CALL].insert(attr, (attr_id, call_id, calls.into())); } }); }, diff --git a/crates/hir_def/src/dyn_map.rs b/crates/hir_def/src/dyn_map.rs index 6f269d7b01f..166aa04da04 100644 --- a/crates/hir_def/src/dyn_map.rs +++ b/crates/hir_def/src/dyn_map.rs @@ -54,6 +54,7 @@ pub trait Policy { fn insert(map: &mut DynMap, key: Self::K, value: Self::V); fn get<'a>(map: &'a DynMap, key: &Self::K) -> Option<&'a Self::V>; + fn is_empty(map: &DynMap) -> bool; } impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) { @@ -65,6 +66,9 @@ impl<K: Hash + Eq + 'static, V: 'static> Policy for (K, V) { fn get<'a>(map: &'a DynMap, key: &K) -> Option<&'a V> { map.map.get::<FxHashMap<K, V>>()?.get(key) } + fn is_empty(map: &DynMap) -> bool { + map.map.get::<FxHashMap<K, V>>().map_or(true, |it| it.is_empty()) + } } pub struct DynMap { @@ -90,6 +94,10 @@ impl<P: Policy> KeyMap<Key<P::K, P::V, P>> { pub fn get(&self, key: &P::K) -> Option<&P::V> { P::get(&self.map, key) } + + pub fn is_empty(&self) -> bool { + P::is_empty(&self.map) + } } impl<P: Policy> Index<Key<P::K, P::V, P>> for DynMap { diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index 258d1e0f6c5..fffec96bab9 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs @@ -66,8 +66,10 @@ pub struct ItemScope { attr_macros: FxHashMap<AstId<ast::Item>, MacroCallId>, /// The derive macro invocations in this scope, keyed by the owner item over the actual derive attributes /// paired with the derive macro invocations for the specific attribute. - derive_macros: - FxHashMap<AstId<ast::Adt>, SmallVec<[(AttrId, SmallVec<[Option<MacroCallId>; 1]>); 1]>>, + derive_macros: FxHashMap< + AstId<ast::Adt>, + SmallVec<[(AttrId, MacroCallId, SmallVec<[Option<MacroCallId>; 1]>); 1]>, + >, } pub(crate) static BUILTIN_SCOPE: Lazy<FxHashMap<Name, PerNs>> = Lazy::new(|| { @@ -210,7 +212,7 @@ impl ItemScope { idx: usize, ) { if let Some(derives) = self.derive_macros.get_mut(&adt) { - if let Some((_, invocs)) = derives.iter_mut().find(|&&mut (id, _)| id == attr_id) { + if let Some((.., invocs)) = derives.iter_mut().find(|&&mut (id, ..)| id == attr_id) { invocs[idx] = Some(call); } } @@ -223,19 +225,23 @@ impl ItemScope { &mut self, adt: AstId<ast::Adt>, attr_id: AttrId, + call_id: MacroCallId, len: usize, ) { - self.derive_macros.entry(adt).or_default().push((attr_id, smallvec![None; len])); + self.derive_macros.entry(adt).or_default().push((attr_id, call_id, smallvec![None; len])); } pub(crate) fn derive_macro_invocs( &self, ) -> impl Iterator< - Item = (AstId<ast::Adt>, impl Iterator<Item = (AttrId, &[Option<MacroCallId>])>), + Item = ( + AstId<ast::Adt>, + impl Iterator<Item = (AttrId, MacroCallId, &[Option<MacroCallId>])>, + ), > + '_ { - self.derive_macros - .iter() - .map(|(k, v)| (*k, v.iter().map(|(attr_id, invocs)| (*attr_id, &**invocs)))) + self.derive_macros.iter().map(|(k, v)| { + (*k, v.iter().map(|&(attr_id, call_id, ref invocs)| (attr_id, call_id, &**invocs))) + }) } pub(crate) fn unnamed_trait_vis(&self, tr: TraitId) -> Option<Visibility> { diff --git a/crates/hir_def/src/keys.rs b/crates/hir_def/src/keys.rs index 93c92c1b9c9..8cd2d771721 100644 --- a/crates/hir_def/src/keys.rs +++ b/crates/hir_def/src/keys.rs @@ -34,7 +34,8 @@ pub const CONST_PARAM: Key<ast::ConstParam, ConstParamId> = Key::new(); pub const MACRO: Key<ast::Macro, MacroDefId> = Key::new(); pub const ATTR_MACRO_CALL: Key<ast::Item, MacroCallId> = Key::new(); -pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, Box<[Option<MacroCallId>]>)> = Key::new(); +pub const DERIVE_MACRO_CALL: Key<ast::Attr, (AttrId, MacroCallId, Box<[Option<MacroCallId>]>)> = + Key::new(); /// XXX: AST Nodes and SyntaxNodes have identity equality semantics: nodes are /// equal if they point to exactly the same object. @@ -60,4 +61,7 @@ impl<AST: AstNode + 'static, ID: 'static> Policy for AstPtrPolicy<AST, ID> { let key = AstPtr::new(key); map.map.get::<FxHashMap<AstPtr<AST>, ID>>()?.get(&key) } + fn is_empty(map: &DynMap) -> bool { + map.map.get::<FxHashMap<AstPtr<AST>, ID>>().map_or(true, |it| it.is_empty()) + } } diff --git a/crates/hir_def/src/lib.rs b/crates/hir_def/src/lib.rs index 7e33e535998..bb65d1dec87 100644 --- a/crates/hir_def/src/lib.rs +++ b/crates/hir_def/src/lib.rs @@ -690,9 +690,9 @@ impl AsMacroCall for InFile<&ast::MacroCall> { }; macro_call_as_call_id( + db, &AstIdWithPath::new(ast_id.file_id, ast_id.value, path), expands_to, - db, krate, resolver, error_sink, @@ -714,9 +714,9 @@ impl<T: ast::AstNode> AstIdWithPath<T> { } fn macro_call_as_call_id( + db: &dyn db::DefDatabase, call: &AstIdWithPath<ast::MacroCall>, expand_to: ExpandTo, - db: &dyn db::DefDatabase, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, error_sink: &mut dyn FnMut(ExpandError), @@ -739,10 +739,10 @@ fn macro_call_as_call_id( } fn derive_macro_as_call_id( + db: &dyn db::DefDatabase, item_attr: &AstIdWithPath<ast::Adt>, derive_attr: AttrId, derive_pos: u32, - db: &dyn db::DefDatabase, krate: CrateId, resolver: impl Fn(path::ModPath) -> Option<MacroDefId>, ) -> Result<MacroCallId, UnresolvedMacro> { @@ -761,11 +761,12 @@ fn derive_macro_as_call_id( } fn attr_macro_as_call_id( + db: &dyn db::DefDatabase, item_attr: &AstIdWithPath<ast::Item>, macro_attr: &Attr, - db: &dyn db::DefDatabase, krate: CrateId, def: MacroDefId, + is_derive: bool, ) -> MacroCallId { let mut arg = match macro_attr.input.as_deref() { Some(attr::AttrInput::TokenTree(tt, map)) => (tt.clone(), map.clone()), @@ -782,6 +783,7 @@ fn attr_macro_as_call_id( ast_id: item_attr.ast_id, attr_args: Arc::new(arg), invoc_attr_index: macro_attr.id.ast_index, + is_derive, }, ); res diff --git a/crates/hir_def/src/nameres/attr_resolution.rs b/crates/hir_def/src/nameres/attr_resolution.rs index 4a7211b5c98..4c436250db3 100644 --- a/crates/hir_def/src/nameres/attr_resolution.rs +++ b/crates/hir_def/src/nameres/attr_resolution.rs @@ -54,7 +54,7 @@ impl DefMap { None => return Err(UnresolvedMacro { path: ast_id.path.clone() }), }; - Ok(ResolvedAttr::Macro(attr_macro_as_call_id(&ast_id, attr, db, self.krate, def))) + Ok(ResolvedAttr::Macro(attr_macro_as_call_id(db, &ast_id, attr, self.krate, def, false))) } pub(crate) fn is_builtin_or_registered_attr(&self, path: &ModPath) -> bool { diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index ec6af65a921..2dd7cc48596 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs @@ -1055,9 +1055,9 @@ impl DefCollector<'_> { match &directive.kind { MacroDirectiveKind::FnLike { ast_id, expand_to } => { let call_id = macro_call_as_call_id( + self.db, ast_id, *expand_to, - self.db, self.def_map.krate, &resolver, &mut |_err| (), @@ -1070,10 +1070,10 @@ impl DefCollector<'_> { } MacroDirectiveKind::Derive { ast_id, derive_attr, derive_pos } => { let call_id = derive_macro_as_call_id( + self.db, ast_id, *derive_attr, *derive_pos as u32, - self.db, self.def_map.krate, &resolver, ); @@ -1170,9 +1170,20 @@ impl DefCollector<'_> { len = idx; } + // We treat the #[derive] macro as an attribute call, but we do not resolve it for nameres collection. + // This is just a trick to be able to resolve the input to derives as proper paths. + // Check the comment in [`builtin_attr_macro`]. + let call_id = attr_macro_as_call_id( + self.db, + file_ast_id, + attr, + self.def_map.krate, + def, + true, + ); self.def_map.modules[directive.module_id] .scope - .init_derive_attribute(ast_id, attr.id, len + 1); + .init_derive_attribute(ast_id, attr.id, call_id, len + 1); } None => { let diag = DefDiagnostic::malformed_derive( @@ -1192,8 +1203,14 @@ impl DefCollector<'_> { } // Not resolved to a derive helper or the derive attribute, so try to treat as a normal attribute. - let call_id = - attr_macro_as_call_id(file_ast_id, attr, self.db, self.def_map.krate, def); + let call_id = attr_macro_as_call_id( + self.db, + file_ast_id, + attr, + self.def_map.krate, + def, + false, + ); let loc: MacroCallLoc = self.db.lookup_intern_macro_call(call_id); // Skip #[test]/#[bench] expansion, which would merely result in more memory usage @@ -1310,9 +1327,9 @@ impl DefCollector<'_> { match &directive.kind { MacroDirectiveKind::FnLike { ast_id, expand_to } => { let macro_call_as_call_id = macro_call_as_call_id( + self.db, ast_id, *expand_to, - self.db, self.def_map.krate, |path| { let resolved_res = self.def_map.resolve_path_fp_with_macro( @@ -1959,9 +1976,9 @@ impl ModCollector<'_, '_> { // Case 1: try to resolve in legacy scope and expand macro_rules let mut error = None; match macro_call_as_call_id( + self.def_collector.db, &ast_id, mac.expand_to, - self.def_collector.db, self.def_collector.def_map.krate, |path| { path.as_ident().and_then(|name| { diff --git a/crates/hir_expand/src/builtin_attr_macro.rs b/crates/hir_expand/src/builtin_attr_macro.rs index 907ee02e332..6301da1c832 100644 --- a/crates/hir_expand/src/builtin_attr_macro.rs +++ b/crates/hir_expand/src/builtin_attr_macro.rs @@ -1,9 +1,11 @@ //! Builtin attributes. +use itertools::Itertools; use syntax::ast; use crate::{ - db::AstDatabase, name, AstId, CrateId, ExpandResult, MacroCallId, MacroDefId, MacroDefKind, + db::AstDatabase, name, AstId, CrateId, ExpandResult, MacroCallId, MacroCallKind, MacroDefId, + MacroDefKind, }; macro_rules! register_builtin { @@ -53,7 +55,7 @@ register_builtin! { (bench, Bench) => dummy_attr_expand, (cfg_accessible, CfgAccessible) => dummy_attr_expand, (cfg_eval, CfgEval) => dummy_attr_expand, - (derive, Derive) => dummy_attr_expand, + (derive, Derive) => derive_attr_expand, (global_allocator, GlobalAllocator) => dummy_attr_expand, (test, Test) => dummy_attr_expand, (test_case, TestCase) => dummy_attr_expand @@ -79,3 +81,68 @@ fn dummy_attr_expand( ) -> ExpandResult<tt::Subtree> { ExpandResult::ok(tt.clone()) } + +/// We generate a very specific expansion here, as we do not actually expand the `#[derive]` attribute +/// itself in name res, but we do want to expand it to something for the IDE layer, so that the input +/// derive attributes can be downmapped, and resolved as proper paths. +/// This is basically a hack, that simplifies the hacks we need in a lot of ide layer places to +/// somewhat inconsistently resolve derive attributes. +/// +/// As such, we expand `#[derive(Foo, bar::Bar)]` into +/// ``` +/// #[Foo] +/// #[bar::Bar] +/// (); +/// ``` +/// which allows fallback path resolution in hir::Semantics to properly identify our derives. +/// Since we do not expand the attribute in nameres though, we keep the original item. +/// +/// The ideal expansion here would be for the `#[derive]` to re-emit the annotated item and somehow +/// use the input paths in its output as well. +/// But that would bring two problems with it, for one every derive would duplicate the item token tree +/// wasting a lot of memory, and it would also require some way to use a path in a way that makes it +/// always resolve as a derive without nameres recollecting them. +/// So this hacky approach is a lot more friendly for us, though it does require a bit of support in +/// [`hir::Semantics`] to make this work. +fn derive_attr_expand( + db: &dyn AstDatabase, + id: MacroCallId, + tt: &tt::Subtree, +) -> ExpandResult<tt::Subtree> { + let loc = db.lookup_intern_macro_call(id); + let derives = match &loc.kind { + MacroCallKind::Attr { attr_args, .. } => &attr_args.0, + _ => return ExpandResult::ok(tt.clone()), + }; + + let mk_leaf = |char| { + tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { + char, + spacing: tt::Spacing::Alone, + id: tt::TokenId::unspecified(), + })) + }; + + let mut token_trees = Vec::new(); + for (comma, group) in &derives + .token_trees + .iter() + .filter_map(|tt| match tt { + tt::TokenTree::Leaf(l) => Some(l), + tt::TokenTree::Subtree(_) => None, + }) + .group_by(|l| matches!(l, tt::Leaf::Punct(tt::Punct { char: ',', .. }))) + { + if comma { + continue; + } + token_trees.push(mk_leaf('#')); + token_trees.push(mk_leaf('[')); + token_trees.extend(group.cloned().map(tt::TokenTree::Leaf)); + token_trees.push(mk_leaf(']')); + } + token_trees.push(mk_leaf('(')); + token_trees.push(mk_leaf(')')); + token_trees.push(mk_leaf(';')); + ExpandResult::ok(tt::Subtree { delimiter: tt.delimiter, token_trees }) +} diff --git a/crates/hir_expand/src/db.rs b/crates/hir_expand/src/db.rs index 91c1631e817..d6d33b4cd72 100644 --- a/crates/hir_expand/src/db.rs +++ b/crates/hir_expand/src/db.rs @@ -342,6 +342,7 @@ fn censor_for_macro_input(loc: &MacroCallLoc, node: &SyntaxNode) -> FxHashSet<Sy .map(|it| it.syntax().clone()) .collect() } + MacroCallKind::Attr { is_derive: true, .. } => return None, MacroCallKind::Attr { invoc_attr_index, .. } => { cov_mark::hit!(attribute_macro_attr_censoring); ast::Item::cast(node.clone())? diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index 27c3f097abb..cc38faa1369 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -166,6 +166,8 @@ pub enum MacroCallKind { /// Outer attributes are counted first, then inner attributes. This does not support /// out-of-line modules, which may have attributes spread across 2 files! invoc_attr_index: u32, + /// Whether this attribute is the `#[derive]` attribute. + is_derive: bool, }, } @@ -218,9 +220,18 @@ impl HirFileId { let arg_tt = loc.kind.arg(db)?; + let macro_def = db.macro_def(loc.def).ok()?; + let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?; + let macro_arg = db.macro_arg(macro_file.macro_call_id)?; + let def = loc.def.ast_id().left().and_then(|id| { let def_tt = match id.to_node(db) { ast::Macro::MacroRules(mac) => mac.token_tree()?, + ast::Macro::MacroDef(_) + if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) => + { + return None + } ast::Macro::MacroDef(mac) => mac.body()?, }; Some(InFile::new(id.file_id, def_tt)) @@ -238,10 +249,6 @@ impl HirFileId { _ => None, }); - let macro_def = db.macro_def(loc.def).ok()?; - let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?; - let macro_arg = db.macro_arg(macro_file.macro_call_id)?; - Some(ExpansionInfo { expanded: InFile::new(self, parse.syntax_node()), arg: InFile::new(loc.kind.file_id(), arg_tt), @@ -256,16 +263,16 @@ impl HirFileId { } /// Indicate it is macro file generated for builtin derive - pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Item>> { + pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Attr>> { match self.0 { HirFileIdRepr::FileId(_) => None, HirFileIdRepr::MacroFile(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let item = match loc.def.kind { + let attr = match loc.def.kind { MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db), _ => return None, }; - Some(item.with_value(ast::Item::cast(item.value.clone())?)) + Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) } } } @@ -291,7 +298,7 @@ impl HirFileId { } } - /// Return whether this file is an include macro + /// Return whether this file is an attr macro pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool { match self.0 { HirFileIdRepr::MacroFile(macro_file) => { @@ -302,6 +309,17 @@ impl HirFileId { } } + /// Return whether this file is the pseudo expansion of the derive attribute. + pub fn is_derive_attr_macro(&self, db: &dyn db::AstDatabase) -> bool { + match self.0 { + HirFileIdRepr::MacroFile(macro_file) => { + let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); + matches!(loc.kind, MacroCallKind::Attr { is_derive: true, .. }) + } + _ => false, + } + } + pub fn is_macro(self) -> bool { matches!(self.0, HirFileIdRepr::MacroFile(_)) } @@ -366,8 +384,29 @@ impl MacroCallKind { MacroCallKind::FnLike { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) } - MacroCallKind::Derive { ast_id, .. } => { - ast_id.with_value(ast_id.to_node(db).syntax().clone()) + MacroCallKind::Derive { ast_id, derive_attr_index, .. } => { + // FIXME: handle `cfg_attr` + ast_id.with_value(ast_id.to_node(db)).map(|it| { + it.doc_comments_and_attrs() + .nth(*derive_attr_index as usize) + .and_then(|it| match it { + Either::Left(attr) => Some(attr.syntax().clone()), + Either::Right(_) => None, + }) + .unwrap_or_else(|| it.syntax().clone()) + }) + } + MacroCallKind::Attr { ast_id, is_derive: true, invoc_attr_index, .. } => { + // FIXME: handle `cfg_attr` + ast_id.with_value(ast_id.to_node(db)).map(|it| { + it.doc_comments_and_attrs() + .nth(*invoc_attr_index as usize) + .and_then(|it| match it { + Either::Left(attr) => Some(attr.syntax().clone()), + Either::Right(_) => None, + }) + .unwrap_or_else(|| it.syntax().clone()) + }) } MacroCallKind::Attr { ast_id, .. } => { ast_id.with_value(ast_id.to_node(db).syntax().clone()) @@ -431,6 +470,7 @@ impl MacroCallKind { match self { MacroCallKind::FnLike { expand_to, .. } => *expand_to, MacroCallKind::Derive { .. } => ExpandTo::Items, + MacroCallKind::Attr { is_derive: true, .. } => ExpandTo::Statements, MacroCallKind::Attr { .. } => ExpandTo::Items, // is this always correct? } } @@ -497,7 +537,7 @@ impl ExpansionInfo { let token_range = token.value.text_range(); match &loc.kind { - MacroCallKind::Attr { attr_args, invoc_attr_index, .. } => { + MacroCallKind::Attr { attr_args, invoc_attr_index, is_derive, .. } => { let attr = item .doc_comments_and_attrs() .nth(*invoc_attr_index as usize) @@ -511,9 +551,13 @@ impl ExpansionInfo { let relative_range = token.value.text_range().checked_sub(attr_input_start)?; // shift by the item's tree's max id - let token_id = self - .macro_arg_shift - .shift(attr_args.1.token_by_range(relative_range)?); + let token_id = attr_args.1.token_by_range(relative_range)?; + let token_id = if *is_derive { + // we do not shift for `#[derive]`, as we only need to downmap the derive attribute tokens + token_id + } else { + self.macro_arg_shift.shift(token_id) + }; Some(token_id) } _ => None, @@ -561,6 +605,9 @@ impl ExpansionInfo { // Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item. let (token_map, tt) = match &loc.kind { + MacroCallKind::Attr { attr_args, is_derive: true, .. } => { + (&attr_args.1, self.attr_input_or_mac_def.clone()?.syntax().cloned()) + } MacroCallKind::Attr { attr_args, .. } => { // try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input // note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this @@ -716,6 +763,13 @@ impl<'a> InFile<&'a SyntaxNode> { } } +impl InFile<SyntaxToken> { + pub fn upmap(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxToken>> { + let expansion = self.file_id.expansion_info(db)?; + expansion.map_token_up(db, self.as_ref()).map(|(it, _)| it) + } +} + fn ascend_node_border_tokens( db: &dyn db::AstDatabase, InFile { file_id, value: node }: InFile<&SyntaxNode>, diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs index 32dbd9070b9..f7326747253 100644 --- a/crates/ide/src/expand_macro.rs +++ b/crates/ide/src/expand_macro.rs @@ -3,8 +3,7 @@ use ide_db::{ helpers::{insert_whitespace_into_node::insert_ws_into, pick_best_token}, RootDatabase, }; -use itertools::Itertools; -use syntax::{ast, ted, AstNode, SyntaxKind, SyntaxNode}; +use syntax::{ast, ted, AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T}; use crate::FilePosition; @@ -41,20 +40,28 @@ pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option< // struct Bar; // ``` - let derive = sema.descend_into_macros(tok.clone()).iter().find_map(|descended| { - let attr = descended.ancestors().find_map(ast::Attr::cast)?; - let (path, tt) = attr.as_simple_call()?; - if path == "derive" { - let mut tt = tt.syntax().children_with_tokens().skip(1).join(""); - tt.pop(); - let expansions = sema.expand_derive_macro(&attr)?; - Some(ExpandedMacro { - name: tt, - expansion: expansions.into_iter().map(insert_ws_into).join(""), - }) - } else { - None + let derive = sema.descend_into_macros(tok.clone()).into_iter().find_map(|descended| { + let hir_file = sema.hir_file_for(&descended.parent()?); + if !hir_file.is_derive_attr_macro(db) { + return None; } + + let name = descended.ancestors().filter_map(ast::Path::cast).last()?.to_string(); + // up map out of the #[derive] expansion + let token = hir::InFile::new(hir_file, descended).upmap(db)?.value; + let attr = token.ancestors().find_map(ast::Attr::cast)?; + let expansions = sema.expand_derive_macro(&attr)?; + let idx = attr + .token_tree()? + .token_trees_and_tokens() + .filter_map(NodeOrToken::into_token) + .take_while(|it| it == &token) + .filter(|it| it.kind() == T![,]) + .count(); + Some(ExpandedMacro { + name, + expansion: expansions.get(idx).cloned().map(insert_ws_into)?.to_string(), + }) }); if derive.is_some() { @@ -372,11 +379,9 @@ struct Foo {} struct Foo {} "#, expect![[r#" - Copy, Clone + Copy impl < >core::marker::Copy for Foo< >{} - impl < >core::clone::Clone for Foo< >{} - "#]], ); } diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index c5c531c30b1..a232ebd4fb7 100644 --- a/crates/ide/src/hover/tests.rs +++ b/crates/ide/src/hover/tests.rs @@ -15,6 +15,7 @@ fn check_hover_no_result(ra_fixture: &str) { assert!(hover.is_none(), "hover not expected but found: {:?}", hover.unwrap()); } +#[track_caller] fn check(ra_fixture: &str, expect: Expect) { let (analysis, position) = fixture::position(ra_fixture); let hover = analysis diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 91c311fe94e..38edda9c1c0 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -1564,7 +1564,6 @@ fn func$0() {} ); } - // FIXME #[test] fn derive() { check( @@ -1575,7 +1574,11 @@ fn func$0() {} #[derive(proc_macros::DeriveIdentity$0)] struct Foo; "#, - expect![[r#""#]], + expect![[r#" + derive_identity Derive FileId(2) 1..107 45..60 + + FileId(0) 23..37 + "#]], ) } } diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index f09f291e96a..7d92c5051b1 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -237,6 +237,20 @@ fn traverse( continue; } Some(item) if sema.is_attr_macro_call(&item) => current_attr_call = Some(item), + Some(item) if current_attr_call.is_none() => { + let adt = match item { + ast::Item::Enum(it) => Some(ast::Adt::Enum(it)), + ast::Item::Struct(it) => Some(ast::Adt::Struct(it)), + ast::Item::Union(it) => Some(ast::Adt::Union(it)), + _ => None, + }; + match adt { + Some(adt) if sema.is_derive_annotated(&adt) => { + current_attr_call = Some(adt.into()); + } + _ => (), + } + } None if ast::Attr::can_cast(node.kind()) => inside_attribute = true, _ => (), }, @@ -361,7 +375,7 @@ fn traverse( syntactic_name_ref_highlighting, node, ), - NodeOrToken::Token(token) => highlight::token(sema, krate, token).zip(Some(None)), + NodeOrToken::Token(token) => highlight::token(sema, token).zip(Some(None)), }; if let Some((mut highlight, binding_hash)) = element { if inside_attribute { diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs index c869db3b8b7..85c0c1b286e 100644 --- a/crates/ide/src/syntax_highlighting/highlight.rs +++ b/crates/ide/src/syntax_highlighting/highlight.rs @@ -18,11 +18,7 @@ use crate::{ Highlight, HlMod, HlTag, }; -pub(super) fn token( - sema: &Semantics<RootDatabase>, - krate: Option<hir::Crate>, - token: SyntaxToken, -) -> Option<Highlight> { +pub(super) fn token(sema: &Semantics<RootDatabase>, token: SyntaxToken) -> Option<Highlight> { if let Some(comment) = ast::Comment::cast(token.clone()) { let h = HlTag::Comment; return Some(match comment.kind().doc { @@ -39,17 +35,10 @@ pub(super) fn token( INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), BYTE => HlTag::ByteLiteral.into(), CHAR => HlTag::CharLiteral.into(), - IDENT => { - let tt = ast::TokenTree::cast(token.parent()?)?; - let ident = ast::Ident::cast(token)?; + IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => { // from this point on we are inside a token tree, this only happens for identifiers // that were not mapped down into macro invocations - (|| { - let attr = tt.parent_meta()?.parent_attr()?; - let res = sema.resolve_derive_ident(&attr, &ident)?; - Some(highlight_def(sema, krate, Definition::from(res))) - })() - .unwrap_or_else(|| HlTag::None.into()) + HlTag::None.into() } p if p.is_punct() => punctuation(sema, token, p), k if k.is_keyword() => keyword(sema, token, k)?, diff --git a/crates/ide_assists/src/handlers/auto_import.rs b/crates/ide_assists/src/handlers/auto_import.rs index cac736ff850..9c0233b028f 100644 --- a/crates/ide_assists/src/handlers/auto_import.rs +++ b/crates/ide_assists/src/handlers/auto_import.rs @@ -3,7 +3,7 @@ use ide_db::helpers::{ insert_use::{insert_use, ImportScope}, mod_path_to_ast, }; -use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxElement}; +use syntax::{ast, AstNode, NodeOrToken, SyntaxElement}; use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; @@ -139,9 +139,7 @@ pub(super) fn find_importable_node(ctx: &AssistContext) -> Option<(ImportAssets, { ImportAssets::for_ident_pat(&ctx.sema, &pat).zip(Some(pat.syntax().clone().into())) } else { - // FIXME: Descend? - let ident = ctx.find_token_at_offset()?; - ImportAssets::for_derive_ident(&ctx.sema, &ident).zip(Some(ident.syntax().clone().into())) + None } } diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index b3723710a86..8ac05bf5ff5 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs @@ -1,13 +1,13 @@ -use hir::ModuleDef; -use ide_db::helpers::insert_whitespace_into_node::insert_ws_into; -use ide_db::helpers::{ - get_path_at_cursor_in_tt, import_assets::NameToImport, mod_path_to_ast, - parse_tt_as_comma_sep_paths, +use hir::{InFile, ModuleDef}; +use ide_db::{ + helpers::{ + import_assets::NameToImport, insert_whitespace_into_node::insert_ws_into, mod_path_to_ast, + }, + items_locator, }; -use ide_db::items_locator; use itertools::Itertools; use syntax::{ - ast::{self, AstNode, AstToken, HasName}, + ast::{self, AstNode, HasName}, SyntaxKind::WHITESPACE, }; @@ -25,6 +25,7 @@ use crate::{ // Converts a `derive` impl into a manual one. // // ``` +// # //- minicore: derive // # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } // #[derive(Deb$0ug, Display)] // struct S; @@ -45,20 +46,30 @@ pub(crate) fn replace_derive_with_manual_impl( acc: &mut Assists, ctx: &AssistContext, ) -> Option<()> { - let attr = ctx.find_node_at_offset::<ast::Attr>()?; - let (name, args) = attr.as_simple_call()?; - if name != "derive" { + let attr = ctx.find_node_at_offset_with_descend::<ast::Attr>()?; + let path = attr.path()?; + let hir_file = ctx.sema.hir_file_for(attr.syntax()); + if !hir_file.is_derive_attr_macro(ctx.db()) { return None; } - if !args.syntax().text_range().contains(ctx.offset()) { - cov_mark::hit!(outside_of_attr_args); + let InFile { file_id, value } = hir_file.call_node(ctx.db())?; + if file_id.is_macro() { + // FIXME: make this work in macro files return None; } + // collect the derive paths from the #[derive] expansion + let current_derives = ctx + .sema + .parse_or_expand(hir_file)? + .descendants() + .filter_map(ast::Attr::cast) + .filter_map(|attr| attr.path()) + .collect::<Vec<_>>(); - let ident = args.syntax().token_at_offset(ctx.offset()).find_map(ast::Ident::cast)?; - let trait_path = get_path_at_cursor_in_tt(&ident)?; - let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; + let adt = value.parent().and_then(ast::Adt::cast)?; + let attr = ast::Attr::cast(value)?; + let args = attr.token_tree()?; let current_module = ctx.sema.scope(adt.syntax()).module()?; let current_crate = current_module.krate(); @@ -66,7 +77,7 @@ pub(crate) fn replace_derive_with_manual_impl( let found_traits = items_locator::items_with_name( &ctx.sema, current_crate, - NameToImport::exact_case_sensitive(trait_path.segments().last()?.to_string()), + NameToImport::exact_case_sensitive(path.segments().last()?.to_string()), items_locator::AssocItemSearch::Exclude, Some(items_locator::DEFAULT_QUERY_SEARCH_LIMIT.inner()), ) @@ -83,8 +94,6 @@ pub(crate) fn replace_derive_with_manual_impl( }); let mut no_traits_found = true; - let current_derives = parse_tt_as_comma_sep_paths(args.clone())?; - let current_derives = current_derives.as_slice(); for (replace_trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { add_assist( acc, @@ -92,14 +101,14 @@ pub(crate) fn replace_derive_with_manual_impl( &attr, ¤t_derives, &args, - &trait_path, + &path, &replace_trait_path, Some(trait_), &adt, )?; } if no_traits_found { - add_assist(acc, ctx, &attr, ¤t_derives, &args, &trait_path, &trait_path, None, &adt)?; + add_assist(acc, ctx, &attr, ¤t_derives, &args, &path, &path, None, &adt)?; } Some(()) } @@ -128,7 +137,7 @@ fn add_assist( let impl_def_with_items = impl_def_from_trait(&ctx.sema, adt, &annotated_name, trait_, replace_trait_path); update_attribute(builder, old_derives, old_tree, old_trait_path, attr); - let trait_path = format!("{}", replace_trait_path); + let trait_path = replace_trait_path.to_string(); match (ctx.config.snippet_cap, impl_def_with_items) { (None, _) => { builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, "")) @@ -258,7 +267,7 @@ mod tests { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] struct Foo { bar: String, @@ -282,7 +291,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] struct Foo(String, usize); "#, @@ -301,7 +310,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] struct Foo; "#, @@ -321,7 +330,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] enum Foo { Bar, @@ -351,7 +360,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] enum Foo { Bar(usize, usize), @@ -380,7 +389,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(Debu$0g)] enum Foo { Bar { @@ -415,7 +424,7 @@ impl core::fmt::Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: default +//- minicore: default, derive #[derive(Defau$0lt)] struct Foo { foo: usize, @@ -439,7 +448,7 @@ impl Default for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: default +//- minicore: default, derive #[derive(Defau$0lt)] struct Foo(usize); "#, @@ -459,7 +468,7 @@ impl Default for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: default +//- minicore: default, derive #[derive(Defau$0lt)] struct Foo; "#, @@ -480,7 +489,7 @@ impl Default for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: hash +//- minicore: hash, derive #[derive(Has$0h)] struct Foo { bin: usize, @@ -508,7 +517,7 @@ impl core::hash::Hash for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: hash +//- minicore: hash, derive #[derive(Has$0h)] struct Foo(usize, usize); "#, @@ -530,7 +539,7 @@ impl core::hash::Hash for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: hash +//- minicore: hash, derive #[derive(Has$0h)] enum Foo { Bar, @@ -557,7 +566,7 @@ impl core::hash::Hash for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] struct Foo { bin: usize, @@ -584,7 +593,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] struct Foo(usize, usize); "#, @@ -605,7 +614,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] struct Foo; "#, @@ -626,7 +635,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] enum Foo { Bar, @@ -656,7 +665,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] enum Foo { Bar(String), @@ -686,7 +695,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] enum Foo { Bar { @@ -720,7 +729,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: ord +//- minicore: ord, derive #[derive(Partial$0Ord)] struct Foo { bin: usize, @@ -745,7 +754,7 @@ impl PartialOrd for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: ord +//- minicore: ord, derive #[derive(Partial$0Ord)] struct Foo { bin: usize, @@ -782,7 +791,7 @@ impl PartialOrd for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: ord +//- minicore: ord, derive #[derive(Partial$0Ord)] struct Foo(usize, usize, usize); "#, @@ -811,7 +820,7 @@ impl PartialOrd for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] struct Foo { bin: usize, @@ -838,7 +847,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] struct Foo(usize, usize); "#, @@ -859,7 +868,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] struct Foo; "#, @@ -880,7 +889,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] enum Foo { Bar, @@ -907,7 +916,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] enum Foo { Bar(String), @@ -937,7 +946,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: eq +//- minicore: eq, derive #[derive(Partial$0Eq)] enum Foo { Bar { @@ -981,6 +990,7 @@ impl PartialEq for Foo { check_assist( replace_derive_with_manual_impl, r#" +//- minicore: derive mod foo { pub trait Bar { type Qux; @@ -1026,10 +1036,11 @@ impl foo::Bar for Foo { ) } #[test] - fn add_custom_impl_for_unique_input() { + fn add_custom_impl_for_unique_input_unknown() { check_assist( replace_derive_with_manual_impl, r#" +//- minicore: derive #[derive(Debu$0g)] struct Foo { bar: String, @@ -1052,6 +1063,7 @@ impl Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" +//- minicore: derive #[derive(Debug$0)] pub struct Foo { bar: String, @@ -1074,6 +1086,7 @@ impl Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" +//- minicore: derive #[derive(Display, Debug$0, Serialize)] struct Foo {} "#, @@ -1093,7 +1106,7 @@ impl Debug for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: default +//- minicore: default, derive #[derive(Defau$0lt)] struct Foo<T, U> { foo: T, @@ -1120,7 +1133,7 @@ impl<T, U> Default for Foo<T, U> { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(Clo$0ne)] struct Foo<T: Clone>(T, usize); "#, @@ -1141,6 +1154,7 @@ impl<T: Clone> Clone for Foo<T> { check_assist_not_applicable( replace_derive_with_manual_impl, r#" +//- minicore: derive #[derive($0)] struct Foo {} "#, @@ -1152,6 +1166,7 @@ struct Foo {} check_assist_not_applicable( replace_derive_with_manual_impl, r#" +//- minicore: derive, fmt #[derive$0(Debug)] struct Foo {} "#, @@ -1160,6 +1175,7 @@ struct Foo {} check_assist_not_applicable( replace_derive_with_manual_impl, r#" +//- minicore: derive, fmt #[derive(Debug)$0] struct Foo {} "#, @@ -1171,6 +1187,7 @@ struct Foo {} check_assist_not_applicable( replace_derive_with_manual_impl, r#" +//- minicore: derive #[allow(non_camel_$0case_types)] struct Foo {} "#, @@ -1179,10 +1196,10 @@ struct Foo {} #[test] fn works_at_start_of_file() { - cov_mark::check!(outside_of_attr_args); check_assist_not_applicable( replace_derive_with_manual_impl, r#" +//- minicore: derive, fmt $0#[derive(Debug)] struct S; "#, @@ -1194,7 +1211,7 @@ struct S; check_assist( replace_derive_with_manual_impl, r#" -//- minicore: clone +//- minicore: clone, derive #[derive(std::fmt::Debug, Clo$0ne)] pub struct Foo; "#, @@ -1216,7 +1233,7 @@ impl Clone for Foo { check_assist( replace_derive_with_manual_impl, r#" -//- minicore: fmt +//- minicore: fmt, derive #[derive(core::fmt::Deb$0ug, Clone)] pub struct Foo; "#, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 0ad4b3bc345..485b807d055 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -1766,6 +1766,7 @@ fn doctest_replace_derive_with_manual_impl() { check_doc_test( "replace_derive_with_manual_impl", r#####" +//- minicore: derive trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } #[derive(Deb$0ug, Display)] struct S; diff --git a/crates/ide_completion/src/completions/trait_impl.rs b/crates/ide_completion/src/completions/trait_impl.rs index 4b1de8058de..0d59f77a55c 100644 --- a/crates/ide_completion/src/completions/trait_impl.rs +++ b/crates/ide_completion/src/completions/trait_impl.rs @@ -58,15 +58,15 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext ( hir::AssocItem::Function(fn_item), ImplCompletionKind::All | ImplCompletionKind::Fn, - ) => add_function_impl(&trigger, acc, ctx, fn_item, hir_impl), + ) => add_function_impl(acc, ctx, &trigger, fn_item, hir_impl), ( hir::AssocItem::TypeAlias(type_item), ImplCompletionKind::All | ImplCompletionKind::TypeAlias, - ) => add_type_alias_impl(&trigger, acc, ctx, type_item), + ) => add_type_alias_impl(acc, ctx, &trigger, type_item), ( hir::AssocItem::Const(const_item), ImplCompletionKind::All | ImplCompletionKind::Const, - ) => add_const_impl(&trigger, acc, ctx, const_item, hir_impl), + ) => add_const_impl(acc, ctx, &trigger, const_item, hir_impl), _ => {} } }); @@ -126,9 +126,9 @@ fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, Synta } fn add_function_impl( - fn_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, + fn_def_node: &SyntaxNode, func: hir::Function, impl_def: hir::Impl, ) { @@ -199,9 +199,9 @@ fn get_transformed_assoc_item( } fn add_type_alias_impl( - type_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, + type_def_node: &SyntaxNode, type_alias: hir::TypeAlias, ) { let alias_name = type_alias.name(ctx.db).to_smol_str(); @@ -217,9 +217,9 @@ fn add_type_alias_impl( } fn add_const_impl( - const_def_node: &SyntaxNode, acc: &mut Completions, ctx: &CompletionContext, + const_def_node: &SyntaxNode, const_: hir::Const, impl_def: hir::Impl, ) { diff --git a/crates/ide_completion/src/tests/attribute.rs b/crates/ide_completion/src/tests/attribute.rs index 2e643453afc..ae7ba7e055c 100644 --- a/crates/ide_completion/src/tests/attribute.rs +++ b/crates/ide_completion/src/tests/attribute.rs @@ -736,6 +736,26 @@ mod derive { } #[test] + fn derive_no_attrs() { + check_derive( + r#" +//- proc_macros: identity +//- minicore: derive +#[derive($0)] struct Test; +"#, + expect![[r#""#]], + ); + check_derive( + r#" +//- proc_macros: identity +//- minicore: derive +#[derive(i$0)] struct Test; +"#, + expect![[r#""#]], + ); + } + + #[test] fn derive_flyimport() { check_derive( r#" diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index 5a4cfe6e941..08104efcdc2 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs @@ -14,7 +14,7 @@ use hir::{ use stdx::impl_from; use syntax::{ ast::{self, AstNode}, - match_ast, AstToken, SyntaxKind, SyntaxNode, SyntaxToken, + match_ast, SyntaxKind, SyntaxNode, SyntaxToken, }; use crate::RootDatabase; @@ -142,16 +142,6 @@ impl IdentClass { token: &SyntaxToken, ) -> Option<IdentClass> { let parent = token.parent()?; - // resolve derives if possible - if let Some(ident) = ast::Ident::cast(token.clone()) { - 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 NameRefClass::classify_derive(sema, &attr, &ident) - .map(IdentClass::NameRefClass); - } - } Self::classify_node(sema, &parent) } @@ -461,14 +451,6 @@ impl NameRefClass { _ => None, } } - - pub fn classify_derive( - sema: &Semantics<RootDatabase>, - attr: &ast::Attr, - ident: &ast::Ident, - ) -> Option<NameRefClass> { - sema.resolve_derive_ident(&attr, &ident).map(Definition::from).map(NameRefClass::Definition) - } } impl_from!( diff --git a/crates/ide_db/src/helpers.rs b/crates/ide_db/src/helpers.rs index c355016c5df..cbe4adf1b9c 100644 --- a/crates/ide_db/src/helpers.rs +++ b/crates/ide_db/src/helpers.rs @@ -9,15 +9,14 @@ pub mod node_ext; pub mod rust_doc; pub mod format_string; -use std::{collections::VecDeque, iter}; +use std::collections::VecDeque; use base_db::FileId; -use hir::{ItemInNs, MacroDef, ModuleDef, Name, PathResolution, Semantics}; +use hir::{ItemInNs, MacroDef, ModuleDef, Name, Semantics}; use itertools::Itertools; use syntax::{ ast::{self, make, HasLoopBody}, - AstNode, AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, - T, + AstNode, AstToken, SyntaxKind, SyntaxToken, TokenAtOffset, WalkEvent, T, }; use crate::{defs::Definition, RootDatabase}; @@ -32,49 +31,6 @@ pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> { } } -/// Parses and returns the derive 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. -/// -/// The returned path is synthesized from TokenTree tokens and as such cannot be used with the [`Semantics`]. -pub fn get_path_in_derive_attr( - sema: &hir::Semantics<RootDatabase>, - attr: &ast::Attr, - cursor: &ast::Ident, -) -> Option<ast::Path> { - let path = attr.path()?; - let tt = attr.token_tree()?; - if !tt.syntax().text_range().contains_range(cursor.syntax().text_range()) { - return None; - } - let scope = sema.scope(attr.syntax()); - let resolved_attr = sema.resolve_path(&path)?; - let derive = FamousDefs(sema, scope.krate()).core_macros_builtin_derive()?; - if PathResolution::Macro(derive) != resolved_attr { - return None; - } - get_path_at_cursor_in_tt(cursor) -} - -/// Parses the path the identifier is part of inside a token tree. -pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option<ast::Path> { - let cursor = cursor.syntax(); - let first = cursor - .siblings_with_tokens(Direction::Prev) - .filter_map(SyntaxElement::into_token) - .take_while(|tok| tok.kind() != T!['('] && tok.kind() != T![,]) - .last()?; - let path_tokens = first - .siblings_with_tokens(Direction::Next) - .filter_map(SyntaxElement::into_token) - .take_while(|tok| tok != cursor); - - syntax::hacks::parse_expr_from_str(&path_tokens.chain(iter::once(cursor.clone())).join("")) - .and_then(|expr| match expr { - ast::Expr::PathExpr(it) => it.path(), - _ => None, - }) -} - /// Picks the token with the highest rank returned by the passed in function. pub fn pick_best_token( tokens: TokenAtOffset<SyntaxToken>, diff --git a/crates/ide_db/src/helpers/import_assets.rs b/crates/ide_db/src/helpers/import_assets.rs index c037c3e0f87..319a2173529 100644 --- a/crates/ide_db/src/helpers/import_assets.rs +++ b/crates/ide_db/src/helpers/import_assets.rs @@ -8,11 +8,10 @@ use rustc_hash::FxHashSet; use syntax::{ ast::{self, HasName}, utils::path_to_string_stripping_turbo_fish, - AstNode, AstToken, SyntaxNode, + AstNode, SyntaxNode, }; use crate::{ - helpers::get_path_in_derive_attr, items_locator::{self, AssocItemSearch, DEFAULT_QUERY_SEARCH_LIMIT}, RootDatabase, }; @@ -139,23 +138,6 @@ impl ImportAssets { }) } - pub fn for_derive_ident(sema: &Semantics<RootDatabase>, ident: &ast::Ident) -> Option<Self> { - let attr = ident.syntax().ancestors().find_map(ast::Attr::cast)?; - let path = get_path_in_derive_attr(sema, &attr, ident)?; - - 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 { - import_candidate: ImportCandidate::Path(PathImportCandidate { qualifier: None, name }), - module_with_candidate: sema.scope(&candidate_node).module()?, - candidate_node, - }) - } - pub fn for_fuzzy_path( module_with_candidate: Module, qualifier: Option<ast::Path>, diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 5ff6519c9cc..1b916e91bfd 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -705,6 +705,15 @@ impl ast::RangePat { } impl ast::TokenTree { + pub fn token_trees_and_tokens( + &self, + ) -> impl Iterator<Item = NodeOrToken<ast::TokenTree, SyntaxToken>> { + self.syntax().children_with_tokens().filter_map(|not| match not { + NodeOrToken::Node(node) => ast::TokenTree::cast(node).map(NodeOrToken::Node), + NodeOrToken::Token(t) => Some(NodeOrToken::Token(t)), + }) + } + pub fn left_delimiter_token(&self) -> Option<SyntaxToken> { self.syntax() .first_child_or_token()? diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 2f2c4351c73..99ddc188d81 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -111,7 +111,7 @@ env UPDATE_EXPECT=1 cargo qt After adding a new inline test you need to run `cargo test -p xtask` and also update the test data as described above. -Note [`api_walkthrough`](https://github.com/rust-analyzer/rust-analyzer/blob/2fb6af89eb794f775de60b82afe56b6f986c2a40/crates/ra_syntax/src/lib.rs#L190-L348) +Note [`api_walkthrough`](https://github.com/rust-analyzer/rust-analyzer/blob/2fb6af89eb794f775de60b82afe56b6f986c2a40/crates/ra_syntax/src/lib.rs#L190-L348) in particular: it shows off various methods of working with syntax tree. See [#93](https://github.com/rust-analyzer/rust-analyzer/pull/93) for an example PR which fixes a bug in the grammar. |
