From f6e2fca74b572ed0f858d612cf7844db8298f8a0 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Fri, 26 Jul 2024 14:36:13 +0200 Subject: Internal: Cleanup proc-macro error handling --- .../rust-analyzer/crates/base-db/src/input.rs | 4 +- src/tools/rust-analyzer/crates/hir-def/src/body.rs | 5 +- .../rust-analyzer/crates/hir-def/src/body/lower.rs | 21 +- src/tools/rust-analyzer/crates/hir-def/src/data.rs | 13 +- .../rust-analyzer/crates/hir-def/src/expander.rs | 2 +- src/tools/rust-analyzer/crates/hir-def/src/lib.rs | 4 +- .../hir-def/src/macro_expansion_tests/mod.rs | 2 +- .../rust-analyzer/crates/hir-def/src/nameres.rs | 8 - .../crates/hir-def/src/nameres/collector.rs | 79 +- .../crates/hir-def/src/nameres/diagnostics.rs | 67 +- .../rust-analyzer/crates/hir-expand/src/builtin.rs | 14 + .../crates/hir-expand/src/builtin/attr_macro.rs | 146 ++++ .../crates/hir-expand/src/builtin/derive_macro.rs | 868 ++++++++++++++++++++ .../crates/hir-expand/src/builtin/fn_macro.rs | 905 +++++++++++++++++++++ .../crates/hir-expand/src/builtin/quote.rs | 335 ++++++++ .../crates/hir-expand/src/builtin_attr_macro.rs | 146 ---- .../crates/hir-expand/src/builtin_derive_macro.rs | 868 -------------------- .../crates/hir-expand/src/builtin_fn_macro.rs | 905 --------------------- .../rust-analyzer/crates/hir-expand/src/change.rs | 3 +- .../rust-analyzer/crates/hir-expand/src/db.rs | 9 +- .../rust-analyzer/crates/hir-expand/src/lib.rs | 67 +- .../crates/hir-expand/src/proc_macro.rs | 193 +++-- .../rust-analyzer/crates/hir-expand/src/quote.rs | 333 -------- .../rust-analyzer/crates/hir/src/diagnostics.rs | 17 +- src/tools/rust-analyzer/crates/hir/src/lib.rs | 57 +- .../rust-analyzer/crates/hir/src/semantics.rs | 2 +- .../ide-diagnostics/src/handlers/macro_error.rs | 5 +- .../src/handlers/unresolved_proc_macro.rs | 45 - .../crates/ide-diagnostics/src/lib.rs | 2 - src/tools/rust-analyzer/crates/ide/src/lib.rs | 5 - .../crates/ide/src/shuffle_crate_graph.rs | 58 -- .../rust-analyzer/crates/load-cargo/src/lib.rs | 18 +- .../crates/project-model/src/workspace.rs | 9 +- .../crates/rust-analyzer/src/handlers/request.rs | 5 - .../crates/rust-analyzer/src/lsp/ext.rs | 8 - .../crates/rust-analyzer/src/main_loop.rs | 1 - .../crates/rust-analyzer/src/reload.rs | 78 +- .../rust-analyzer/crates/test-fixture/src/lib.rs | 8 +- src/tools/rust-analyzer/docs/dev/lsp-extensions.md | 10 +- src/tools/rust-analyzer/editors/code/package.json | 5 - .../rust-analyzer/editors/code/src/commands.ts | 6 - .../rust-analyzer/editors/code/src/lsp_ext.ts | 1 - src/tools/rust-analyzer/editors/code/src/main.ts | 1 - 43 files changed, 2598 insertions(+), 2740 deletions(-) create mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin.rs create mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs create mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs delete mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs delete mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs delete mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs delete mode 100644 src/tools/rust-analyzer/crates/hir-expand/src/quote.rs delete mode 100644 src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs delete mode 100644 src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs (limited to 'src') diff --git a/src/tools/rust-analyzer/crates/base-db/src/input.rs b/src/tools/rust-analyzer/crates/base-db/src/input.rs index 3936fd3555e..460581f4a6c 100644 --- a/src/tools/rust-analyzer/crates/base-db/src/input.rs +++ b/src/tools/rust-analyzer/crates/base-db/src/input.rs @@ -16,9 +16,7 @@ use span::{Edition, EditionedFileId}; use triomphe::Arc; use vfs::{file_set::FileSet, AbsPathBuf, AnchoredPath, FileId, VfsPath}; -// Map from crate id to the name of the crate and path of the proc-macro. If the value is `None`, -// then the crate for the proc-macro hasn't been build yet as the build data is missing. -pub type ProcMacroPaths = FxHashMap, AbsPathBuf), String>>; +pub type ProcMacroPaths = FxHashMap>; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct SourceRootId(pub u32); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body.rs b/src/tools/rust-analyzer/crates/hir-def/src/body.rs index 9e1bff98f8a..d3c134f3266 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body.rs @@ -10,7 +10,7 @@ use std::ops::{Deref, Index}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use hir_expand::{name::Name, InFile}; +use hir_expand::{name::Name, ExpandError, InFile}; use la_arena::{Arena, ArenaMap, Idx, RawIdx}; use rustc_hash::FxHashMap; use smallvec::SmallVec; @@ -115,8 +115,7 @@ pub struct SyntheticSyntax; #[derive(Debug, Eq, PartialEq)] pub enum BodyDiagnostic { InactiveCode { node: InFile, cfg: CfgExpr, opts: CfgOptions }, - MacroError { node: InFile>, message: String }, - UnresolvedProcMacro { node: InFile>, krate: CrateId }, + MacroError { node: InFile>, err: ExpandError }, UnresolvedMacroCall { node: InFile>, path: ModPath }, UnreachableLabel { node: InFile>, name: Name }, UndeclaredLabel { node: InFile>, name: Name }, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs index fe5264674a6..9e30aff8fe9 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/body/lower.rs @@ -7,7 +7,7 @@ use base_db::CrateId; use either::Either; use hir_expand::{ name::{AsName, Name}, - ExpandError, InFile, + InFile, }; use intern::{sym, Interned, Symbol}; use rustc_hash::FxHashMap; @@ -992,20 +992,11 @@ impl ExprCollector<'_> { } }; if record_diagnostics { - match &res.err { - Some(ExpandError::UnresolvedProcMacro(krate)) => { - self.source_map.diagnostics.push(BodyDiagnostic::UnresolvedProcMacro { - node: InFile::new(outer_file, syntax_ptr), - krate: *krate, - }); - } - Some(err) => { - self.source_map.diagnostics.push(BodyDiagnostic::MacroError { - node: InFile::new(outer_file, syntax_ptr), - message: err.to_string(), - }); - } - None => {} + if let Some(err) = res.err { + self.source_map.diagnostics.push(BodyDiagnostic::MacroError { + node: InFile::new(outer_file, syntax_ptr), + err, + }); } } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/data.rs b/src/tools/rust-analyzer/crates/hir-def/src/data.rs index 3a3b540c132..286694db263 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/data.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/data.rs @@ -657,22 +657,17 @@ impl<'a> AssocItemCollector<'a> { // crate failed), skip expansion like we would if it was // disabled. This is analogous to the handling in // `DefCollector::collect_macros`. - if exp.is_dummy() { - self.diagnostics.push(DefDiagnostic::unresolved_proc_macro( + if let Some(err) = exp.as_expand_error(self.module_id.krate) { + self.diagnostics.push(DefDiagnostic::macro_error( self.module_id.local_id, - loc.kind, - loc.def.krate, + ast_id, + err, )); - - continue 'attrs; - } - if exp.is_disabled() { continue 'attrs; } } self.macro_calls.push((ast_id, call_id)); - let res = self.expander.enter_expand_id::(self.db, call_id); self.collect_macro_items(res); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/expander.rs b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs index d1640ad7e5b..8230c7cc097 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/expander.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/expander.rs @@ -179,7 +179,7 @@ impl Expander { value: match err { // If proc-macro is disabled or unresolved, we want to expand to a missing expression // instead of an empty tree which might end up in an empty block. - Some(ExpandError::UnresolvedProcMacro(_)) => None, + Some(ExpandError::MissingProcMacroExpander(_)) => None, _ => (|| { let parse = res.value.0.cast::()?; diff --git a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs index e96581e1b30..512daa41548 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/lib.rs @@ -75,9 +75,7 @@ use base_db::{ CrateId, }; use hir_expand::{ - builtin_attr_macro::BuiltinAttrExpander, - builtin_derive_macro::BuiltinDeriveExpander, - builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, + builtin::{BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, eager::expand_eager_macro_input, impl_intern_lookup, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs index b6c6e4b3973..d34f0afc3ef 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/mod.rs @@ -122,7 +122,7 @@ pub fn identity_when_valid(_attr: TokenStream, item: TokenStream) -> TokenStream let mut expn_text = String::new(); if let Some(err) = exp.err { - format_to!(expn_text, "/* error: {} */", err); + format_to!(expn_text, "/* error: {} */", err.render_to_string(&db).0); } let (parse, token_map) = exp.value; if expect_errors { diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs index 08a4eab1bc4..8825e463363 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres.rs @@ -145,8 +145,6 @@ struct DefMapCrateData { /// Side table for resolving derive helpers. exported_derives: FxHashMap>, fn_proc_macro_mapping: FxHashMap, - /// The error that occurred when failing to load the proc-macro dll. - proc_macro_loading_error: Option>, /// Custom attributes registered with `#![register_attr]`. registered_attrs: Vec, @@ -169,7 +167,6 @@ impl DefMapCrateData { extern_prelude: FxIndexMap::default(), exported_derives: FxHashMap::default(), fn_proc_macro_mapping: FxHashMap::default(), - proc_macro_loading_error: None, registered_attrs: Vec::new(), registered_tools: PREDEFINED_TOOLS.iter().map(|it| Symbol::intern(it)).collect(), unstable_features: FxHashSet::default(), @@ -189,7 +186,6 @@ impl DefMapCrateData { registered_attrs, registered_tools, unstable_features, - proc_macro_loading_error: _, rustc_coherence_is_core: _, no_core: _, no_std: _, @@ -474,10 +470,6 @@ impl DefMap { self.data.fn_proc_macro_mapping.get(&id).copied() } - pub fn proc_macro_loading_error(&self) -> Option<&str> { - self.data.proc_macro_loading_error.as_deref() - } - pub fn krate(&self) -> CrateId { self.krate } diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs index c51eea22dcb..9553b6aa8cf 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/collector.rs @@ -10,9 +10,7 @@ use cfg::{CfgExpr, CfgOptions}; use either::Either; use hir_expand::{ attrs::{Attr, AttrId}, - builtin_attr_macro::find_builtin_attr, - builtin_derive_macro::find_builtin_derive, - builtin_fn_macro::find_builtin_macro, + builtin::{find_builtin_attr, find_builtin_derive, find_builtin_macro}, name::{AsName, Name}, proc_macro::CustomProcMacroExpander, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, @@ -76,34 +74,11 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, def_map: DefMap, tree_id: TreeI } let proc_macros = if krate.is_proc_macro { - match db.proc_macros().get(&def_map.krate) { - Some(Ok(proc_macros)) => Ok({ - let ctx = db.syntax_context(tree_id.file_id()); - proc_macros - .iter() - .enumerate() - .map(|(idx, it)| { - let name = Name::new_symbol(it.name.clone(), ctx); - ( - name, - if !db.expand_proc_attr_macros() { - CustomProcMacroExpander::dummy() - } else if it.disabled { - CustomProcMacroExpander::disabled() - } else { - CustomProcMacroExpander::new( - hir_expand::proc_macro::ProcMacroId::new(idx as u32), - ) - }, - ) - }) - .collect() - }), - Some(Err(e)) => Err(e.clone().into_boxed_str()), - None => Err("No proc-macros present for crate".to_owned().into_boxed_str()), - } + db.proc_macros() + .for_crate(def_map.krate, db.syntax_context(tree_id.file_id())) + .unwrap_or_default() } else { - Ok(vec![]) + Default::default() }; let mut collector = DefCollector { @@ -252,10 +227,10 @@ struct DefCollector<'a> { mod_dirs: FxHashMap, cfg_options: &'a CfgOptions, /// List of procedural macros defined by this crate. This is read from the dynamic library - /// built by the build system, and is the list of proc. macros we can actually expand. It is - /// empty when proc. macro support is disabled (in which case we still do name resolution for - /// them). - proc_macros: Result, Box>, + /// built by the build system, and is the list of proc-macros we can actually expand. It is + /// empty when proc-macro support is disabled (in which case we still do name resolution for + /// them). The bool signals whether the proc-macro has been explicitly disabled for name-resolution. + proc_macros: Box<[(Name, CustomProcMacroExpander, bool)]>, is_proc_macro: bool, from_glob_import: PerNsGlobImports, /// If we fail to resolve an attribute on a `ModItem`, we fall back to ignoring the attribute. @@ -278,10 +253,6 @@ impl DefCollector<'_> { let attrs = item_tree.top_level_attrs(self.db, self.def_map.krate); let crate_data = Arc::get_mut(&mut self.def_map.data).unwrap(); - if let Err(e) = &self.proc_macros { - crate_data.proc_macro_loading_error = Some(e.clone()); - } - let mut process = true; // Process other crate-level attributes. @@ -608,11 +579,17 @@ impl DefCollector<'_> { fn_id: FunctionId, ) { let kind = def.kind.to_basedb_kind(); - let (expander, kind) = - match self.proc_macros.as_ref().map(|it| it.iter().find(|(n, _)| n == &def.name)) { - Ok(Some(&(_, expander))) => (expander, kind), - _ => (CustomProcMacroExpander::dummy(), kind), - }; + let (expander, kind) = match self.proc_macros.iter().find(|(n, _, _)| n == &def.name) { + Some(_) + if kind == hir_expand::proc_macro::ProcMacroKind::Attr + && !self.db.expand_proc_attr_macros() => + { + (CustomProcMacroExpander::disabled_proc_attr(), kind) + } + Some(&(_, _, true)) => (CustomProcMacroExpander::disabled(), kind), + Some(&(_, expander, false)) => (expander, kind), + None => (CustomProcMacroExpander::missing_expander(), kind), + }; let proc_macro_id = ProcMacroLoc { container: self.def_map.crate_root(), @@ -1338,25 +1315,22 @@ impl DefCollector<'_> { return recollect_without(self); } - let call_id = call_id(); if let MacroDefKind::ProcMacro(_, exp, _) = def.kind { // If there's no expander for the proc macro (e.g. // because proc macros are disabled, or building the // proc macro crate failed), report this and skip // expansion like we would if it was disabled - if exp.is_dummy() { - self.def_map.diagnostics.push(DefDiagnostic::unresolved_proc_macro( + if let Some(err) = exp.as_expand_error(def.krate) { + self.def_map.diagnostics.push(DefDiagnostic::macro_error( directive.module_id, - self.db.lookup_intern_macro_call(call_id).kind, - def.krate, + ast_id, + err, )); return recollect_without(self); } - if exp.is_disabled() { - return recollect_without(self); - } } + let call_id = call_id(); self.def_map.modules[directive.module_id] .scope .add_attr_macro_invoc(ast_id, call_id); @@ -1395,7 +1369,6 @@ impl DefCollector<'_> { } let file_id = macro_call_id.as_file(); - // Then, fetch and process the item tree. This will reuse the expansion result from above. let item_tree = self.db.file_item_tree(file_id); let mod_dir = if macro_call_id.as_macro_file().is_include_macro(self.db.upcast()) { @@ -2433,7 +2406,7 @@ mod tests { unresolved_macros: Vec::new(), mod_dirs: FxHashMap::default(), cfg_options: &CfgOptions::default(), - proc_macros: Ok(vec![]), + proc_macros: Default::default(), from_glob_import: Default::default(), skip_attrs: Default::default(), is_proc_macro: false, diff --git a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs index e1bd6966f39..23837ff661b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/nameres/diagnostics.rs @@ -2,9 +2,8 @@ use std::ops::Not; -use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; -use hir_expand::{attrs::AttrId, MacroCallKind}; +use hir_expand::{attrs::AttrId, ExpandError, MacroCallKind}; use la_arena::Idx; use syntax::ast; @@ -17,48 +16,16 @@ use crate::{ #[derive(Debug, PartialEq, Eq)] pub enum DefDiagnosticKind { - UnresolvedModule { - ast: AstId, - candidates: Box<[String]>, - }, - UnresolvedExternCrate { - ast: AstId, - }, - UnresolvedImport { - id: ItemTreeId, - index: Idx, - }, - UnconfiguredCode { - tree: TreeId, - item: AttrOwner, - cfg: CfgExpr, - opts: CfgOptions, - }, - /// A proc-macro that is lacking an expander, this might be due to build scripts not yet having - /// run or proc-macro expansion being disabled. - UnresolvedProcMacro { - ast: MacroCallKind, - krate: CrateId, - }, - UnresolvedMacroCall { - ast: MacroCallKind, - path: ModPath, - }, - UnimplementedBuiltinMacro { - ast: AstId, - }, - InvalidDeriveTarget { - ast: AstId, - id: usize, - }, - MalformedDerive { - ast: AstId, - id: usize, - }, - MacroDefError { - ast: AstId, - message: String, - }, + UnresolvedModule { ast: AstId, candidates: Box<[String]> }, + UnresolvedExternCrate { ast: AstId }, + UnresolvedImport { id: ItemTreeId, index: Idx }, + UnconfiguredCode { tree: TreeId, item: AttrOwner, cfg: CfgExpr, opts: CfgOptions }, + UnresolvedMacroCall { ast: MacroCallKind, path: ModPath }, + UnimplementedBuiltinMacro { ast: AstId }, + InvalidDeriveTarget { ast: AstId, id: usize }, + MalformedDerive { ast: AstId, id: usize }, + MacroDefError { ast: AstId, message: String }, + MacroError { ast: AstId, err: ExpandError }, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -115,6 +82,10 @@ impl DefDiagnostic { Self { in_module: container, kind: DefDiagnosticKind::UnresolvedImport { id, index } } } + pub fn macro_error(container: LocalModuleId, ast: AstId, err: ExpandError) -> Self { + Self { in_module: container, kind: DefDiagnosticKind::MacroError { ast, err } } + } + pub fn unconfigured_code( container: LocalModuleId, tree: TreeId, @@ -128,14 +99,6 @@ impl DefDiagnostic { } } - pub fn unresolved_proc_macro( - container: LocalModuleId, - ast: MacroCallKind, - krate: CrateId, - ) -> Self { - Self { in_module: container, kind: DefDiagnosticKind::UnresolvedProcMacro { ast, krate } } - } - // FIXME: Whats the difference between this and unresolved_proc_macro pub(crate) fn unresolved_macro_call( container: LocalModuleId, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin.rs new file mode 100644 index 00000000000..4e6349c31da --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin.rs @@ -0,0 +1,14 @@ +#[macro_use] +mod quote; + +mod attr_macro; +mod derive_macro; +mod fn_macro; + +pub use self::{ + attr_macro::{find_builtin_attr, pseudo_derive_attr_expansion, BuiltinAttrExpander}, + derive_macro::{find_builtin_derive, BuiltinDeriveExpander}, + fn_macro::{ + find_builtin_macro, include_input_to_file_id, BuiltinFnLikeExpander, EagerExpander, + }, +}; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs new file mode 100644 index 00000000000..b9afc666f75 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/attr_macro.rs @@ -0,0 +1,146 @@ +//! Builtin attributes. +use intern::sym; +use span::{MacroCallId, Span}; + +use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallKind}; + +macro_rules! register_builtin { + ($(($name:ident, $variant:ident) => $expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinAttrExpander { + $($variant),* + } + + impl BuiltinAttrExpander { + pub fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { + match *self { + $( BuiltinAttrExpander::$variant => $expand, )* + } + } + + fn find_by_name(name: &name::Name) -> Option { + match name { + $( id if id == &sym::$name => Some(BuiltinAttrExpander::$variant), )* + _ => None, + } + } + } + + }; +} + +impl BuiltinAttrExpander { + pub fn expand( + &self, + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, + ) -> ExpandResult { + self.expander()(db, id, tt, span) + } + + pub fn is_derive(self) -> bool { + matches!(self, BuiltinAttrExpander::Derive | BuiltinAttrExpander::DeriveConst) + } + pub fn is_test(self) -> bool { + matches!(self, BuiltinAttrExpander::Test) + } + pub fn is_bench(self) -> bool { + matches!(self, BuiltinAttrExpander::Bench) + } +} + +register_builtin! { + (bench, Bench) => dummy_attr_expand, + (cfg_accessible, CfgAccessible) => dummy_attr_expand, + (cfg_eval, CfgEval) => dummy_attr_expand, + (derive, Derive) => derive_expand, + // derive const is equivalent to derive for our proposes. + (derive_const, DeriveConst) => derive_expand, + (global_allocator, GlobalAllocator) => dummy_attr_expand, + (test, Test) => dummy_attr_expand, + (test_case, TestCase) => dummy_attr_expand +} + +pub fn find_builtin_attr(ident: &name::Name) -> Option { + BuiltinAttrExpander::find_by_name(ident) +} + +fn dummy_attr_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + _span: Span, +) -> ExpandResult { + 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_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let loc = db.lookup_intern_macro_call(id); + let derives = match &loc.kind { + MacroCallKind::Attr { attr_args: Some(attr_args), .. } if loc.def.is_attribute_derive() => { + attr_args + } + _ => { + return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan { open: span, close: span })) + } + }; + pseudo_derive_attr_expansion(tt, derives, span) +} + +pub fn pseudo_derive_attr_expansion( + _: &tt::Subtree, + args: &tt::Subtree, + call_site: Span, +) -> ExpandResult { + let mk_leaf = |char| { + tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { + char, + spacing: tt::Spacing::Alone, + span: call_site, + })) + }; + + let mut token_trees = Vec::new(); + for tt in args + .token_trees + .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. })))) + { + token_trees.push(mk_leaf('#')); + token_trees.push(mk_leaf('!')); + token_trees.push(mk_leaf('[')); + token_trees.extend(tt.iter().cloned()); + token_trees.push(mk_leaf(']')); + } + ExpandResult::ok(tt::Subtree { + delimiter: args.delimiter, + token_trees: token_trees.into_boxed_slice(), + }) +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs new file mode 100644 index 00000000000..1f36cd19954 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/derive_macro.rs @@ -0,0 +1,868 @@ +//! Builtin derives. + +use intern::sym; +use itertools::izip; +use mbe::DocCommentDesugarMode; +use rustc_hash::FxHashSet; +use span::{MacroCallId, Span}; +use stdx::never; +use tracing::debug; + +use crate::{ + builtin::quote::{dollar_crate, quote}, + db::ExpandDatabase, + hygiene::span_with_def_site_ctxt, + name, + name::{AsName, Name}, + span_map::ExpansionSpanMap, + tt, ExpandError, ExpandResult, +}; +use syntax::ast::{ + self, AstNode, FieldList, HasAttrs, HasGenericParams, HasModuleItem, HasName, HasTypeBounds, +}; + +macro_rules! register_builtin { + ( $($trait:ident => $expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum BuiltinDeriveExpander { + $($trait),* + } + + impl BuiltinDeriveExpander { + pub fn expander(&self) -> fn(Span, &tt::Subtree) -> ExpandResult { + match *self { + $( BuiltinDeriveExpander::$trait => $expand, )* + } + } + + fn find_by_name(name: &name::Name) -> Option { + match name { + $( id if id == &sym::$trait => Some(BuiltinDeriveExpander::$trait), )* + _ => None, + } + } + } + + }; +} + +impl BuiltinDeriveExpander { + pub fn expand( + &self, + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, + ) -> ExpandResult { + let span = span_with_def_site_ctxt(db, span, id); + self.expander()(span, tt) + } +} + +register_builtin! { + Copy => copy_expand, + Clone => clone_expand, + Default => default_expand, + Debug => debug_expand, + Hash => hash_expand, + Ord => ord_expand, + PartialOrd => partial_ord_expand, + Eq => eq_expand, + PartialEq => partial_eq_expand +} + +pub fn find_builtin_derive(ident: &name::Name) -> Option { + BuiltinDeriveExpander::find_by_name(ident) +} + +enum VariantShape { + Struct(Vec), + Tuple(usize), + Unit, +} + +fn tuple_field_iterator(span: Span, n: usize) -> impl Iterator { + (0..n).map(move |it| tt::Ident::new(&format!("f{it}"), span)) +} + +impl VariantShape { + fn as_pattern(&self, path: tt::Subtree, span: Span) -> tt::Subtree { + self.as_pattern_map(path, span, |it| quote!(span => #it)) + } + + fn field_names(&self, span: Span) -> Vec { + match self { + VariantShape::Struct(s) => s.clone(), + VariantShape::Tuple(n) => tuple_field_iterator(span, *n).collect(), + VariantShape::Unit => vec![], + } + } + + fn as_pattern_map( + &self, + path: tt::Subtree, + span: Span, + field_map: impl Fn(&tt::Ident) -> tt::Subtree, + ) -> tt::Subtree { + match self { + VariantShape::Struct(fields) => { + let fields = fields.iter().map(|it| { + let mapped = field_map(it); + quote! {span => #it : #mapped , } + }); + quote! {span => + #path { ##fields } + } + } + &VariantShape::Tuple(n) => { + let fields = tuple_field_iterator(span, n).map(|it| { + let mapped = field_map(&it); + quote! {span => + #mapped , + } + }); + quote! {span => + #path ( ##fields ) + } + } + VariantShape::Unit => path, + } + } + + fn from(tm: &ExpansionSpanMap, value: Option) -> Result { + let r = match value { + None => VariantShape::Unit, + Some(FieldList::RecordFieldList(it)) => VariantShape::Struct( + it.fields() + .map(|it| it.name()) + .map(|it| name_to_token(tm, it)) + .collect::>()?, + ), + Some(FieldList::TupleFieldList(it)) => VariantShape::Tuple(it.fields().count()), + }; + Ok(r) + } +} + +enum AdtShape { + Struct(VariantShape), + Enum { variants: Vec<(tt::Ident, VariantShape)>, default_variant: Option }, + Union, +} + +impl AdtShape { + fn as_pattern(&self, span: Span, name: &tt::Ident) -> Vec { + self.as_pattern_map(name, |it| quote!(span =>#it), span) + } + + fn field_names(&self, span: Span) -> Vec> { + match self { + AdtShape::Struct(s) => { + vec![s.field_names(span)] + } + AdtShape::Enum { variants, .. } => { + variants.iter().map(|(_, fields)| fields.field_names(span)).collect() + } + AdtShape::Union => { + never!("using fields of union in derive is always wrong"); + vec![] + } + } + } + + fn as_pattern_map( + &self, + name: &tt::Ident, + field_map: impl Fn(&tt::Ident) -> tt::Subtree, + span: Span, + ) -> Vec { + match self { + AdtShape::Struct(s) => { + vec![s.as_pattern_map(quote! {span => #name }, span, field_map)] + } + AdtShape::Enum { variants, .. } => variants + .iter() + .map(|(v, fields)| { + fields.as_pattern_map(quote! {span => #name :: #v }, span, &field_map) + }) + .collect(), + AdtShape::Union => { + never!("pattern matching on union is always wrong"); + vec![quote! {span => un }] + } + } + } +} + +struct BasicAdtInfo { + name: tt::Ident, + shape: AdtShape, + /// first field is the name, and + /// second field is `Some(ty)` if it's a const param of type `ty`, `None` if it's a type param. + /// third fields is where bounds, if any + param_types: Vec<(tt::Subtree, Option, Option)>, + where_clause: Vec, + associated_types: Vec, +} + +fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result { + let (parsed, tm) = &mbe::token_tree_to_syntax_node( + tt, + mbe::TopEntryPoint::MacroItems, + parser::Edition::CURRENT_FIXME, + ); + let macro_items = ast::MacroItems::cast(parsed.syntax_node()) + .ok_or_else(|| ExpandError::other("invalid item definition"))?; + let item = macro_items.items().next().ok_or_else(|| ExpandError::other("no item found"))?; + let adt = &ast::Adt::cast(item.syntax().clone()) + .ok_or_else(|| ExpandError::other("expected struct, enum or union"))?; + let (name, generic_param_list, where_clause, shape) = match adt { + ast::Adt::Struct(it) => ( + it.name(), + it.generic_param_list(), + it.where_clause(), + AdtShape::Struct(VariantShape::from(tm, it.field_list())?), + ), + ast::Adt::Enum(it) => { + let default_variant = it + .variant_list() + .into_iter() + .flat_map(|it| it.variants()) + .position(|it| it.attrs().any(|it| it.simple_name() == Some("default".into()))); + ( + it.name(), + it.generic_param_list(), + it.where_clause(), + AdtShape::Enum { + default_variant, + variants: it + .variant_list() + .into_iter() + .flat_map(|it| it.variants()) + .map(|it| { + Ok(( + name_to_token(tm, it.name())?, + VariantShape::from(tm, it.field_list())?, + )) + }) + .collect::>()?, + }, + ) + } + ast::Adt::Union(it) => { + (it.name(), it.generic_param_list(), it.where_clause(), AdtShape::Union) + } + }; + + let mut param_type_set: FxHashSet = FxHashSet::default(); + let param_types = generic_param_list + .into_iter() + .flat_map(|param_list| param_list.type_or_const_params()) + .map(|param| { + let name = { + let this = param.name(); + match this { + Some(it) => { + param_type_set.insert(it.as_name()); + mbe::syntax_node_to_token_tree( + it.syntax(), + tm, + call_site, + DocCommentDesugarMode::ProcMacro, + ) + } + None => { + tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) + } + } + }; + let bounds = match ¶m { + ast::TypeOrConstParam::Type(it) => it.type_bound_list().map(|it| { + mbe::syntax_node_to_token_tree( + it.syntax(), + tm, + call_site, + DocCommentDesugarMode::ProcMacro, + ) + }), + ast::TypeOrConstParam::Const(_) => None, + }; + let ty = if let ast::TypeOrConstParam::Const(param) = param { + let ty = param + .ty() + .map(|ty| { + mbe::syntax_node_to_token_tree( + ty.syntax(), + tm, + call_site, + DocCommentDesugarMode::ProcMacro, + ) + }) + .unwrap_or_else(|| { + tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) + }); + Some(ty) + } else { + None + }; + (name, ty, bounds) + }) + .collect(); + + let where_clause = if let Some(w) = where_clause { + w.predicates() + .map(|it| { + mbe::syntax_node_to_token_tree( + it.syntax(), + tm, + call_site, + DocCommentDesugarMode::ProcMacro, + ) + }) + .collect() + } else { + vec![] + }; + + // For a generic parameter `T`, when shorthand associated type `T::Assoc` appears in field + // types (of any variant for enums), we generate trait bound for it. It sounds reasonable to + // also generate trait bound for qualified associated type `::Assoc`, but rustc + // does not do that for some unknown reason. + // + // See the analogous function in rustc [find_type_parameters()] and rust-lang/rust#50730. + // [find_type_parameters()]: https://github.com/rust-lang/rust/blob/1.70.0/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs#L378 + + // It's cumbersome to deal with the distinct structures of ADTs, so let's just get untyped + // `SyntaxNode` that contains fields and look for descendant `ast::PathType`s. Of note is that + // we should not inspect `ast::PathType`s in parameter bounds and where clauses. + let field_list = match adt { + ast::Adt::Enum(it) => it.variant_list().map(|list| list.syntax().clone()), + ast::Adt::Struct(it) => it.field_list().map(|list| list.syntax().clone()), + ast::Adt::Union(it) => it.record_field_list().map(|list| list.syntax().clone()), + }; + let associated_types = field_list + .into_iter() + .flat_map(|it| it.descendants()) + .filter_map(ast::PathType::cast) + .filter_map(|p| { + let name = p.path()?.qualifier()?.as_single_name_ref()?.as_name(); + param_type_set.contains(&name).then_some(p) + }) + .map(|it| { + mbe::syntax_node_to_token_tree( + it.syntax(), + tm, + call_site, + DocCommentDesugarMode::ProcMacro, + ) + }) + .collect(); + let name_token = name_to_token(tm, name)?; + Ok(BasicAdtInfo { name: name_token, shape, param_types, where_clause, associated_types }) +} + +fn name_to_token( + token_map: &ExpansionSpanMap, + name: Option, +) -> Result { + let name = name.ok_or_else(|| { + debug!("parsed item has no name"); + ExpandError::other("missing name") + })?; + let span = token_map.span_at(name.syntax().text_range().start()); + + let name_token = tt::Ident::new(name.text().as_ref(), span); + Ok(name_token) +} + +/// Given that we are deriving a trait `DerivedTrait` for a type like: +/// +/// ```ignore (only-for-syntax-highlight) +/// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z> where C: WhereTrait { +/// a: A, +/// b: B::Item, +/// b1: ::Item, +/// c1: ::Item, +/// c2: Option<::Item>, +/// ... +/// } +/// ``` +/// +/// create an impl like: +/// +/// ```ignore (only-for-syntax-highlight) +/// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ... Z> where +/// C: WhereTrait, +/// A: DerivedTrait + B1 + ... + BN, +/// B: DerivedTrait + B1 + ... + BN, +/// C: DerivedTrait + B1 + ... + BN, +/// B::Item: DerivedTrait + B1 + ... + BN, +/// ::Item: DerivedTrait + B1 + ... + BN, +/// ... +/// { +/// ... +/// } +/// ``` +/// +/// where B1, ..., BN are the bounds given by `bounds_paths`. Z is a phantom type, and +/// therefore does not get bound by the derived trait. +fn expand_simple_derive( + invoc_span: Span, + tt: &tt::Subtree, + trait_path: tt::Subtree, + make_trait_body: impl FnOnce(&BasicAdtInfo) -> tt::Subtree, +) -> ExpandResult { + let info = match parse_adt(tt, invoc_span) { + Ok(info) => info, + Err(e) => { + return ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: invoc_span, close: invoc_span }), + e, + ) + } + }; + let trait_body = make_trait_body(&info); + let mut where_block: Vec<_> = + info.where_clause.into_iter().map(|w| quote! {invoc_span => #w , }).collect(); + let (params, args): (Vec<_>, Vec<_>) = info + .param_types + .into_iter() + .map(|(ident, param_ty, bound)| { + let ident_ = ident.clone(); + if let Some(b) = bound { + let ident = ident.clone(); + where_block.push(quote! {invoc_span => #ident : #b , }); + } + if let Some(ty) = param_ty { + (quote! {invoc_span => const #ident : #ty , }, quote! {invoc_span => #ident_ , }) + } else { + let bound = trait_path.clone(); + (quote! {invoc_span => #ident : #bound , }, quote! {invoc_span => #ident_ , }) + } + }) + .unzip(); + + where_block.extend(info.associated_types.iter().map(|it| { + let it = it.clone(); + let bound = trait_path.clone(); + quote! {invoc_span => #it : #bound , } + })); + + let name = info.name; + let expanded = quote! {invoc_span => + impl < ##params > #trait_path for #name < ##args > where ##where_block { #trait_body } + }; + ExpandResult::ok(expanded) +} + +fn copy_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::marker::Copy }, |_| quote! {span =>}) +} + +fn clone_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::clone::Clone }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; + return quote! {span => + fn clone(&self) -> Self { + #star self + } + }; + } + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; + return quote! {span => + fn clone(&self) -> Self { + match #star self {} + } + }; + } + let name = &adt.name; + let patterns = adt.shape.as_pattern(span, name); + let exprs = adt.shape.as_pattern_map(name, |it| quote! {span => #it .clone() }, span); + let arms = patterns.into_iter().zip(exprs).map(|(pat, expr)| { + let fat_arrow = fat_arrow(span); + quote! {span => + #pat #fat_arrow #expr, + } + }); + + quote! {span => + fn clone(&self) -> Self { + match self { + ##arms + } + } + } + }) +} + +/// This function exists since `quote! {span => => }` doesn't work. +fn fat_arrow(span: Span) -> tt::Subtree { + let eq = tt::Punct { char: '=', spacing: ::tt::Spacing::Joint, span }; + quote! {span => #eq> } +} + +/// This function exists since `quote! {span => && }` doesn't work. +fn and_and(span: Span) -> tt::Subtree { + let and = tt::Punct { char: '&', spacing: ::tt::Spacing::Joint, span }; + quote! {span => #and& } +} + +fn default_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = &dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::default::Default }, |adt| { + let body = match &adt.shape { + AdtShape::Struct(fields) => { + let name = &adt.name; + fields.as_pattern_map( + quote!(span =>#name), + span, + |_| quote!(span =>#krate::default::Default::default()), + ) + } + AdtShape::Enum { default_variant, variants } => { + if let Some(d) = default_variant { + let (name, fields) = &variants[*d]; + let adt_name = &adt.name; + fields.as_pattern_map( + quote!(span =>#adt_name :: #name), + span, + |_| quote!(span =>#krate::default::Default::default()), + ) + } else { + // FIXME: Return expand error here + quote!(span =>) + } + } + AdtShape::Union => { + // FIXME: Return expand error here + quote!(span =>) + } + }; + quote! {span => + fn default() -> Self { + #body + } + } + }) +} + +fn debug_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = &dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::fmt::Debug }, |adt| { + let for_variant = |name: String, v: &VariantShape| match v { + VariantShape::Struct(fields) => { + let for_fields = fields.iter().map(|it| { + let x_string = it.to_string(); + quote! {span => + .field(#x_string, & #it) + } + }); + quote! {span => + f.debug_struct(#name) ##for_fields .finish() + } + } + VariantShape::Tuple(n) => { + let for_fields = tuple_field_iterator(span, *n).map(|it| { + quote! {span => + .field( & #it) + } + }); + quote! {span => + f.debug_tuple(#name) ##for_fields .finish() + } + } + VariantShape::Unit => quote! {span => + f.write_str(#name) + }, + }; + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; + return quote! {span => + fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { + match #star self {} + } + }; + } + let arms = match &adt.shape { + AdtShape::Struct(fields) => { + let fat_arrow = fat_arrow(span); + let name = &adt.name; + let pat = fields.as_pattern(quote!(span =>#name), span); + let expr = for_variant(name.to_string(), fields); + vec![quote! {span => #pat #fat_arrow #expr }] + } + AdtShape::Enum { variants, .. } => variants + .iter() + .map(|(name, v)| { + let fat_arrow = fat_arrow(span); + let adt_name = &adt.name; + let pat = v.as_pattern(quote!(span =>#adt_name :: #name), span); + let expr = for_variant(name.to_string(), v); + quote! {span => + #pat #fat_arrow #expr , + } + }) + .collect(), + AdtShape::Union => { + // FIXME: Return expand error here + vec![] + } + }; + quote! {span => + fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { + match self { + ##arms + } + } + } + }) +} + +fn hash_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = &dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::hash::Hash }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote! {span =>}; + } + if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { + let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; + return quote! {span => + fn hash(&self, ra_expand_state: &mut H) { + match #star self {} + } + }; + } + let arms = + adt.shape.as_pattern(span, &adt.name).into_iter().zip(adt.shape.field_names(span)).map( + |(pat, names)| { + let expr = { + let it = + names.iter().map(|it| quote! {span => #it . hash(ra_expand_state); }); + quote! {span => { + ##it + } } + }; + let fat_arrow = fat_arrow(span); + quote! {span => + #pat #fat_arrow #expr , + } + }, + ); + let check_discriminant = if matches!(&adt.shape, AdtShape::Enum { .. }) { + quote! {span => #krate::mem::discriminant(self).hash(ra_expand_state); } + } else { + quote! {span =>} + }; + quote! {span => + fn hash(&self, ra_expand_state: &mut H) { + #check_discriminant + match self { + ##arms + } + } + } + }) +} + +fn eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::cmp::Eq }, |_| quote! {span =>}) +} + +fn partial_eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialEq }, |adt| { + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote! {span =>}; + } + let name = &adt.name; + + let (self_patterns, other_patterns) = self_and_other_patterns(adt, name, span); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( + |(pat1, pat2, names)| { + let fat_arrow = fat_arrow(span); + let body = match &*names { + [] => { + quote!(span =>true) + } + [first, rest @ ..] => { + let rest = rest.iter().map(|it| { + let t1 = tt::Ident::new(&format!("{}_self", it.sym), it.span); + let t2 = tt::Ident::new(&format!("{}_other", it.sym), it.span); + let and_and = and_and(span); + quote!(span =>#and_and #t1 .eq( #t2 )) + }); + let first = { + let t1 = tt::Ident::new(&format!("{}_self", first.sym), first.span); + let t2 = tt::Ident::new(&format!("{}_other", first.sym), first.span); + quote!(span =>#t1 .eq( #t2 )) + }; + quote!(span =>#first ##rest) + } + }; + quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + + let fat_arrow = fat_arrow(span); + quote! {span => + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ##arms + _unused #fat_arrow false + } + } + } + }) +} + +fn self_and_other_patterns( + adt: &BasicAdtInfo, + name: &tt::Ident, + span: Span, +) -> (Vec, Vec) { + let self_patterns = adt.shape.as_pattern_map( + name, + |it| { + let t = tt::Ident::new(&format!("{}_self", it.sym), it.span); + quote!(span =>#t) + }, + span, + ); + let other_patterns = adt.shape.as_pattern_map( + name, + |it| { + let t = tt::Ident::new(&format!("{}_other", it.sym), it.span); + quote!(span =>#t) + }, + span, + ); + (self_patterns, other_patterns) +} + +fn ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = &dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::cmp::Ord }, |adt| { + fn compare( + krate: &tt::Ident, + left: tt::Subtree, + right: tt::Subtree, + rest: tt::Subtree, + span: Span, + ) -> tt::Subtree { + let fat_arrow1 = fat_arrow(span); + let fat_arrow2 = fat_arrow(span); + quote! {span => + match #left.cmp(&#right) { + #krate::cmp::Ordering::Equal #fat_arrow1 { + #rest + } + c #fat_arrow2 return c, + } + } + } + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote!(span =>); + } + let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name, span); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( + |(pat1, pat2, fields)| { + let mut body = quote!(span =>#krate::cmp::Ordering::Equal); + for f in fields.into_iter().rev() { + let t1 = tt::Ident::new(&format!("{}_self", f.sym), f.span); + let t2 = tt::Ident::new(&format!("{}_other", f.sym), f.span); + body = compare(krate, quote!(span =>#t1), quote!(span =>#t2), body, span); + } + let fat_arrow = fat_arrow(span); + quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + let fat_arrow = fat_arrow(span); + let mut body = quote! {span => + match (self, other) { + ##arms + _unused #fat_arrow #krate::cmp::Ordering::Equal + } + }; + if matches!(&adt.shape, AdtShape::Enum { .. }) { + let left = quote!(span =>#krate::intrinsics::discriminant_value(self)); + let right = quote!(span =>#krate::intrinsics::discriminant_value(other)); + body = compare(krate, left, right, body, span); + } + quote! {span => + fn cmp(&self, other: &Self) -> #krate::cmp::Ordering { + #body + } + } + }) +} + +fn partial_ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { + let krate = &dollar_crate(span); + expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialOrd }, |adt| { + fn compare( + krate: &tt::Ident, + left: tt::Subtree, + right: tt::Subtree, + rest: tt::Subtree, + span: Span, + ) -> tt::Subtree { + let fat_arrow1 = fat_arrow(span); + let fat_arrow2 = fat_arrow(span); + quote! {span => + match #left.partial_cmp(&#right) { + #krate::option::Option::Some(#krate::cmp::Ordering::Equal) #fat_arrow1 { + #rest + } + c #fat_arrow2 return c, + } + } + } + if matches!(adt.shape, AdtShape::Union) { + // FIXME: Return expand error here + return quote!(span =>); + } + let left = quote!(span =>#krate::intrinsics::discriminant_value(self)); + let right = quote!(span =>#krate::intrinsics::discriminant_value(other)); + + let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name, span); + let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( + |(pat1, pat2, fields)| { + let mut body = + quote!(span =>#krate::option::Option::Some(#krate::cmp::Ordering::Equal)); + for f in fields.into_iter().rev() { + let t1 = tt::Ident::new(&format!("{}_self", f.sym), f.span); + let t2 = tt::Ident::new(&format!("{}_other", f.sym), f.span); + body = compare(krate, quote!(span =>#t1), quote!(span =>#t2), body, span); + } + let fat_arrow = fat_arrow(span); + quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } + }, + ); + let fat_arrow = fat_arrow(span); + let body = compare( + krate, + left, + right, + quote! {span => + match (self, other) { + ##arms + _unused #fat_arrow #krate::option::Option::Some(#krate::cmp::Ordering::Equal) + } + }, + span, + ); + quote! {span => + fn partial_cmp(&self, other: &Self) -> #krate::option::Option::Option<#krate::cmp::Ordering> { + #body + } + } + }) +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs new file mode 100644 index 00000000000..5edfdcae1c1 --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/fn_macro.rs @@ -0,0 +1,905 @@ +//! Builtin macro + +use base_db::AnchoredPath; +use cfg::CfgExpr; +use either::Either; +use intern::{sym, Symbol}; +use mbe::{parse_exprs_with_sep, parse_to_token_tree, DelimiterKind}; +use span::{Edition, EditionedFileId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; +use stdx::format_to; +use syntax::{ + format_smolstr, + unescape::{unescape_byte, unescape_char, unescape_unicode, Mode}, +}; + +use crate::{ + builtin::quote::{dollar_crate, quote}, + db::ExpandDatabase, + hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt}, + name, + tt::{self, DelimSpan}, + ExpandError, ExpandResult, HirFileIdExt, Lookup as _, MacroCallId, +}; + +macro_rules! register_builtin { + ( $LAZY:ident: $(($name:ident, $kind: ident) => $expand:ident),* , $EAGER:ident: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum $LAZY { + $($kind),* + } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum $EAGER { + $($e_kind),* + } + + impl BuiltinFnLikeExpander { + fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { + match *self { + $( BuiltinFnLikeExpander::$kind => $expand, )* + } + } + } + + impl EagerExpander { + fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { + match *self { + $( EagerExpander::$e_kind => $e_expand, )* + } + } + } + + fn find_by_name(ident: &name::Name) -> Option> { + match ident { + $( id if id == &sym::$name => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )* + $( id if id == &sym::$e_name => Some(Either::Right(EagerExpander::$e_kind)), )* + _ => return None, + } + } + }; +} + +impl BuiltinFnLikeExpander { + pub fn expand( + &self, + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, + ) -> ExpandResult { + let span = span_with_def_site_ctxt(db, span, id); + self.expander()(db, id, tt, span) + } + + pub fn is_asm(&self) -> bool { + matches!(self, Self::Asm | Self::GlobalAsm) + } +} + +impl EagerExpander { + pub fn expand( + &self, + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, + ) -> ExpandResult { + let span = span_with_def_site_ctxt(db, span, id); + self.expander()(db, id, tt, span) + } + + pub fn is_include(&self) -> bool { + matches!(self, EagerExpander::Include) + } + + pub fn is_include_like(&self) -> bool { + matches!( + self, + EagerExpander::Include | EagerExpander::IncludeStr | EagerExpander::IncludeBytes + ) + } + + pub fn is_env_or_option_env(&self) -> bool { + matches!(self, EagerExpander::Env | EagerExpander::OptionEnv) + } +} + +pub fn find_builtin_macro( + ident: &name::Name, +) -> Option> { + find_by_name(ident) +} + +register_builtin! { + BuiltinFnLikeExpander: + (column, Column) => line_expand, + (file, File) => file_expand, + (line, Line) => line_expand, + (module_path, ModulePath) => module_path_expand, + (assert, Assert) => assert_expand, + (stringify, Stringify) => stringify_expand, + (llvm_asm, LlvmAsm) => asm_expand, + (asm, Asm) => asm_expand, + (global_asm, GlobalAsm) => global_asm_expand, + (cfg, Cfg) => cfg_expand, + (core_panic, CorePanic) => panic_expand, + (std_panic, StdPanic) => panic_expand, + (unreachable, Unreachable) => unreachable_expand, + (log_syntax, LogSyntax) => log_syntax_expand, + (trace_macros, TraceMacros) => trace_macros_expand, + (format_args, FormatArgs) => format_args_expand, + (const_format_args, ConstFormatArgs) => format_args_expand, + (format_args_nl, FormatArgsNl) => format_args_nl_expand, + (quote, Quote) => quote_expand, + + EagerExpander: + (compile_error, CompileError) => compile_error_expand, + (concat, Concat) => concat_expand, + (concat_idents, ConcatIdents) => concat_idents_expand, + (concat_bytes, ConcatBytes) => concat_bytes_expand, + (include, Include) => include_expand, + (include_bytes, IncludeBytes) => include_bytes_expand, + (include_str, IncludeStr) => include_str_expand, + (env, Env) => env_expand, + (option_env, OptionEnv) => option_env_expand +} + +fn mk_pound(span: Span) -> tt::Subtree { + crate::builtin::quote::IntoTt::to_subtree( + vec![crate::tt::Leaf::Punct(crate::tt::Punct { + char: '#', + spacing: crate::tt::Spacing::Alone, + span, + }) + .into()], + span, + ) +} + +fn module_path_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // Just return a dummy result. + ExpandResult::ok(quote! {span => + "module::path" + }) +} + +fn line_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // dummy implementation for type-checking purposes + // Note that `line!` and `column!` will never be implemented properly, as they are by definition + // not incremental + ExpandResult::ok(tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(span), + token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: sym::INTEGER_0.clone(), + span, + kind: tt::LitKind::Integer, + suffix: Some(sym::u32.clone()), + }))]), + }) +} + +fn log_syntax_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + ExpandResult::ok(quote! {span =>}) +} + +fn trace_macros_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + ExpandResult::ok(quote! {span =>}) +} + +fn stringify_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let pretty = ::tt::pretty(&tt.token_trees); + + let expanded = quote! {span => + #pretty + }; + + ExpandResult::ok(expanded) +} + +fn assert_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let call_site_span = span_with_call_site_ctxt(db, span, id); + let args = parse_exprs_with_sep(tt, ',', call_site_span, Edition::CURRENT_FIXME); + let dollar_crate = dollar_crate(span); + let expanded = match &*args { + [cond, panic_args @ ..] => { + let comma = tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(call_site_span), + token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { + char: ',', + spacing: tt::Spacing::Alone, + span: call_site_span, + }))]), + }; + let cond = cond.clone(); + let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); + let mac = if use_panic_2021(db, span) { + quote! {call_site_span => #dollar_crate::panic::panic_2021!(##panic_args) } + } else { + quote! {call_site_span => #dollar_crate::panic!(##panic_args) } + }; + quote! {call_site_span =>{ + if !(#cond) { + #mac; + } + }} + } + [] => quote! {call_site_span =>{}}, + }; + + ExpandResult::ok(expanded) +} + +fn file_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // FIXME: RA purposefully lacks knowledge of absolute file names + // so just return "". + let file_name = "file"; + + let expanded = quote! {span => + #file_name + }; + + ExpandResult::ok(expanded) +} + +fn format_args_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let pound = mk_pound(span); + let mut tt = tt.clone(); + tt.delimiter.kind = tt::DelimiterKind::Parenthesis; + ExpandResult::ok(quote! {span => + builtin #pound format_args #tt + }) +} + +fn format_args_nl_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let pound = mk_pound(span); + let mut tt = tt.clone(); + tt.delimiter.kind = tt::DelimiterKind::Parenthesis; + if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + kind: tt::LitKind::Str, + .. + }))) = tt.token_trees.first_mut() + { + *text = Symbol::intern(&format_smolstr!("{}\\n", text.as_str())); + } + ExpandResult::ok(quote! {span => + builtin #pound format_args #tt + }) +} + +fn asm_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // We expand all assembly snippets to `format_args!` invocations to get format syntax + // highlighting for them. + let mut literals = Vec::new(); + for tt in tt.token_trees.chunks(2) { + match tt { + [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] + | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] => + { + let dollar_krate = dollar_crate(span); + literals.push(quote!(span=>#dollar_krate::format_args!(#lit);)); + } + _ => break, + } + } + + let pound = mk_pound(span); + let expanded = quote! {span => + builtin #pound asm ( + {##literals} + ) + }; + ExpandResult::ok(expanded) +} + +fn global_asm_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // Expand to nothing (at item-level) + ExpandResult::ok(quote! {span =>}) +} + +fn cfg_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let loc = db.lookup_intern_macro_call(id); + let expr = CfgExpr::parse(tt); + let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false); + let expanded = if enabled { quote!(span=>true) } else { quote!(span=>false) }; + ExpandResult::ok(expanded) +} + +fn panic_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let dollar_crate = dollar_crate(span); + let call_site_span = span_with_call_site_ctxt(db, span, id); + + let mac = if use_panic_2021(db, call_site_span) { + sym::panic_2021.clone() + } else { + sym::panic_2015.clone() + }; + + // Expand to a macro call `$crate::panic::panic_{edition}` + let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); + + // Pass the original arguments + let mut subtree = tt.clone(); + subtree.delimiter = tt::Delimiter { + open: call_site_span, + close: call_site_span, + kind: tt::DelimiterKind::Parenthesis, + }; + + // FIXME(slow): quote! have a way to expand to builder to make this a vec! + call.push(tt::TokenTree::Subtree(subtree)); + + ExpandResult::ok(call) +} + +fn unreachable_expand( + db: &dyn ExpandDatabase, + id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let dollar_crate = dollar_crate(span); + let call_site_span = span_with_call_site_ctxt(db, span, id); + + let mac = if use_panic_2021(db, call_site_span) { + sym::unreachable_2021.clone() + } else { + sym::unreachable_2015.clone() + }; + + // Expand to a macro call `$crate::panic::panic_{edition}` + let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); + + // Pass the original arguments + let mut subtree = tt.clone(); + subtree.delimiter = tt::Delimiter { + open: call_site_span, + close: call_site_span, + kind: tt::DelimiterKind::Parenthesis, + }; + + // FIXME(slow): quote! have a way to expand to builder to make this a vec! + call.push(tt::TokenTree::Subtree(subtree)); + + ExpandResult::ok(call) +} + +#[allow(clippy::never_loop)] +fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool { + // To determine the edition, we check the first span up the expansion + // stack that does not have #[allow_internal_unstable(edition_panic)]. + // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.) + loop { + let Some(expn) = db.lookup_intern_syntax_context(span.ctx).outer_expn else { + break false; + }; + let expn = db.lookup_intern_macro_call(expn); + // FIXME: Record allow_internal_unstable in the macro def (not been done yet because it + // would consume quite a bit extra memory for all call locs...) + // if let Some(features) = expn.def.allow_internal_unstable { + // if features.iter().any(|&f| f == sym::edition_panic.clone()) { + // span = expn.call_site; + // continue; + // } + // } + break expn.def.edition >= Edition::Edition2021; + } +} + +fn compile_error_expand( + _db: &dyn ExpandDatabase, + _id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let err = match &*tt.token_trees { + [tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span: _, + kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), + suffix: _, + }))] => + // FIXME: Use the span here! + { + ExpandError::other(Box::from(unescape_str(text).as_str())) + } + _ => ExpandError::other("`compile_error!` argument must be a string"), + }; + + ExpandResult { value: quote! {span =>}, err: Some(err) } +} + +fn concat_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, + _: Span, +) -> ExpandResult { + let mut err = None; + let mut text = String::new(); + let mut span: Option = None; + let mut record_span = |s: Span| match &mut span { + Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), + Some(_) => (), + None => span = Some(s), + }; + for (i, mut t) in tt.token_trees.iter().enumerate() { + // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses + // to ensure the right parsing order, so skip the parentheses here. Ideally we'd + // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623 + if let tt::TokenTree::Subtree(tt::Subtree { delimiter: delim, token_trees }) = t { + if let [tt] = &**token_trees { + if delim.kind == tt::DelimiterKind::Parenthesis { + t = tt; + } + } + } + match t { + tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { + // concat works with string and char literals, so remove any quotes. + // It also works with integer, float and boolean literals, so just use the rest + // as-is. + match it.kind { + tt::LitKind::Char => { + if let Ok(c) = unescape_char(it.symbol.as_str()) { + text.extend(c.escape_default()); + } + record_span(it.span); + } + tt::LitKind::Integer | tt::LitKind::Float => { + format_to!(text, "{}", it.symbol.as_str()) + } + tt::LitKind::Str => { + text.push_str(it.symbol.as_str()); + record_span(it.span); + } + tt::LitKind::StrRaw(_) => { + format_to!(text, "{}", it.symbol.as_str().escape_debug()); + record_span(it.span); + } + tt::LitKind::Byte + | tt::LitKind::ByteStr + | tt::LitKind::ByteStrRaw(_) + | tt::LitKind::CStr + | tt::LitKind::CStrRaw(_) + | tt::LitKind::Err(_) => err = Some(ExpandError::other("unexpected literal")), + } + } + // handle boolean literals + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) + if i % 2 == 0 && (id.sym == sym::true_ || id.sym == sym::false_) => + { + text.push_str(id.sym.as_str()); + record_span(id.span); + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); + } + } + } + let span = span.unwrap_or(tt.delimiter.open); + ExpandResult { value: quote!(span =>#text), err } +} + +fn concat_bytes_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, + _: Span, +) -> ExpandResult { + let mut bytes = String::new(); + let mut err = None; + let mut span: Option = None; + let mut record_span = |s: Span| match &mut span { + Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), + Some(_) => (), + None => span = Some(s), + }; + for (i, t) in tt.token_trees.iter().enumerate() { + match t { + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span, + kind, + suffix: _, + })) => { + record_span(*span); + match kind { + tt::LitKind::Byte => { + if let Ok(b) = unescape_byte(text.as_str()) { + bytes.extend( + b.escape_ascii().filter_map(|it| char::from_u32(it as u32)), + ); + } + } + tt::LitKind::ByteStr => { + bytes.push_str(text.as_str()); + } + tt::LitKind::ByteStrRaw(_) => { + bytes.extend(text.as_str().escape_debug()); + } + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); + break; + } + } + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), + tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => { + if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes, &mut record_span) { + err.get_or_insert(e); + break; + } + } + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); + break; + } + } + } + let span = span.unwrap_or(tt.delimiter.open); + ExpandResult { + value: tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(span), + token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: Symbol::intern(&bytes), + span, + kind: tt::LitKind::ByteStr, + suffix: None, + }))] + .into(), + }, + err, + } +} + +fn concat_bytes_expand_subtree( + tree: &tt::Subtree, + bytes: &mut String, + mut record_span: impl FnMut(Span), +) -> Result<(), ExpandError> { + for (ti, tt) in tree.token_trees.iter().enumerate() { + match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span, + kind: tt::LitKind::Byte, + suffix: _, + })) => { + if let Ok(b) = unescape_byte(text.as_str()) { + bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); + } + record_span(*span); + } + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span, + kind: tt::LitKind::Integer, + suffix: _, + })) => { + record_span(*span); + if let Ok(b) = text.as_str().parse::() { + bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); + } + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (), + _ => { + return Err(mbe::ExpandError::UnexpectedToken.into()); + } + } + } + Ok(()) +} + +fn concat_idents_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let mut err = None; + let mut ident = String::new(); + for (i, t) in tt.token_trees.iter().enumerate() { + match t { + tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => { + ident.push_str(id.sym.as_str()); + } + tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), + _ => { + err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); + } + } + } + // FIXME merge spans + let ident = tt::Ident { sym: Symbol::intern(&ident), span, is_raw: tt::IdentIsRaw::No }; + ExpandResult { value: quote!(span =>#ident), err } +} + +fn relative_file( + db: &dyn ExpandDatabase, + call_id: MacroCallId, + path_str: &str, + allow_recursion: bool, +) -> Result { + let lookup = call_id.lookup(db); + let call_site = lookup.kind.file_id().original_file_respecting_includes(db).file_id(); + let path = AnchoredPath { anchor: call_site, path: path_str }; + let res = db + .resolve_path(path) + .ok_or_else(|| ExpandError::other(format!("failed to load file `{path_str}`")))?; + // Prevent include itself + if res == call_site && !allow_recursion { + Err(ExpandError::other(format!("recursive inclusion of `{path_str}`"))) + } else { + Ok(EditionedFileId::new(res, db.crate_graph()[lookup.krate].edition)) + } +} + +fn parse_string(tt: &tt::Subtree) -> Result<(Symbol, Span), ExpandError> { + tt.token_trees + .first() + .and_then(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span, + kind: tt::LitKind::Str, + suffix: _, + })) => Some((unescape_str(text), *span)), + // FIXME: We wrap expression fragments in parentheses which can break this expectation + // here + // Remove this once we handle none delims correctly + tt::TokenTree::Subtree(t) if t.delimiter.kind == DelimiterKind::Parenthesis => { + t.token_trees.first().and_then(|tt| match tt { + tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: text, + span, + kind: tt::LitKind::Str, + suffix: _, + })) => Some((unescape_str(text), *span)), + _ => None, + }) + } + _ => None, + }) + .ok_or(mbe::ExpandError::ConversionError.into()) +} + +fn include_expand( + db: &dyn ExpandDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let file_id = match include_input_to_file_id(db, arg_id, tt) { + Ok(it) => it, + Err(e) => { + return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) + } + }; + match parse_to_token_tree( + file_id.edition(), + SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID }, + SyntaxContextId::ROOT, + &db.file_text(file_id.file_id()), + ) { + Some(it) => ExpandResult::ok(it), + None => ExpandResult::new( + tt::Subtree::empty(DelimSpan { open: span, close: span }), + ExpandError::other("failed to parse included file"), + ), + } +} + +pub fn include_input_to_file_id( + db: &dyn ExpandDatabase, + arg_id: MacroCallId, + arg: &tt::Subtree, +) -> Result { + relative_file(db, arg_id, parse_string(arg)?.0.as_str(), false) +} + +fn include_bytes_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + // FIXME: actually read the file here if the user asked for macro expansion + let res = tt::Subtree { + delimiter: tt::Delimiter::invisible_spanned(span), + token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { + symbol: Symbol::empty(), + span, + kind: tt::LitKind::ByteStrRaw(1), + suffix: None, + }))]), + }; + ExpandResult::ok(res) +} + +fn include_str_expand( + db: &dyn ExpandDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let (path, span) = match parse_string(tt) { + Ok(it) => it, + Err(e) => { + return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) + } + }; + + // FIXME: we're not able to read excluded files (which is most of them because + // it's unusual to `include_str!` a Rust file), but we can return an empty string. + // Ideally, we'd be able to offer a precise expansion if the user asks for macro + // expansion. + let file_id = match relative_file(db, arg_id, path.as_str(), true) { + Ok(file_id) => file_id, + Err(_) => { + return ExpandResult::ok(quote!(span =>"")); + } + }; + + let text = db.file_text(file_id.file_id()); + let text = &*text; + + ExpandResult::ok(quote!(span =>#text)) +} + +fn get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &Symbol) -> Option { + let krate = db.lookup_intern_macro_call(arg_id).krate; + db.crate_graph()[krate].env.get(key.as_str()).map(|it| it.escape_debug().to_string()) +} + +fn env_expand( + db: &dyn ExpandDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + let (key, span) = match parse_string(tt) { + Ok(it) => it, + Err(e) => { + return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) + } + }; + + let mut err = None; + let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { + // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid + // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. + if key.as_str() == "OUT_DIR" { + err = Some(ExpandError::other(r#"`OUT_DIR` not set, enable "build scripts" to fix"#)); + } + + // If the variable is unset, still return a dummy string to help type inference along. + // We cannot use an empty string here, because for + // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become + // `include!("foo.rs"), which might go to infinite loop + "UNRESOLVED_ENV_VAR".to_owned() + }); + let expanded = quote! {span => #s }; + + ExpandResult { value: expanded, err } +} + +fn option_env_expand( + db: &dyn ExpandDatabase, + arg_id: MacroCallId, + tt: &tt::Subtree, + call_site: Span, +) -> ExpandResult { + let (key, span) = match parse_string(tt) { + Ok(it) => it, + Err(e) => { + return ExpandResult::new( + tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }), + e, + ) + } + }; + let dollar_crate = dollar_crate(call_site); + let expanded = match get_env_inner(db, arg_id, &key) { + None => quote! {call_site => #dollar_crate::option::Option::None::<&str> }, + Some(s) => { + let s = quote! (span => #s); + quote! {call_site => #dollar_crate::option::Option::Some(#s) } + } + }; + + ExpandResult::ok(expanded) +} + +fn quote_expand( + _db: &dyn ExpandDatabase, + _arg_id: MacroCallId, + _tt: &tt::Subtree, + span: Span, +) -> ExpandResult { + ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), + ExpandError::other("quote! is not implemented"), + ) +} + +fn unescape_str(s: &Symbol) -> Symbol { + if s.as_str().contains('\\') { + let s = s.as_str(); + let mut buf = String::with_capacity(s.len()); + unescape_unicode(s, Mode::Str, &mut |_, c| { + if let Ok(c) = c { + buf.push(c) + } + }); + Symbol::intern(&buf) + } else { + s.clone() + } +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs new file mode 100644 index 00000000000..5c33f817f9e --- /dev/null +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin/quote.rs @@ -0,0 +1,335 @@ +//! A simplified version of quote-crate like quasi quote macro +#![allow(clippy::crate_in_macro_def)] + +use intern::{sym, Symbol}; +use span::Span; +use tt::IdentIsRaw; + +use crate::name::Name; + +pub(crate) fn dollar_crate(span: Span) -> tt::Ident { + tt::Ident { sym: sym::dollar_crate.clone(), span, is_raw: tt::IdentIsRaw::No } +} + +// A helper macro quote macro +// FIXME: +// 1. Not all puncts are handled +// 2. #()* pattern repetition not supported now +// * But we can do it manually, see `test_quote_derive_copy_hack` +#[doc(hidden)] +macro_rules! quote_impl__ { + ($span:ident) => { + Vec::<$crate::tt::TokenTree>::new() + }; + + ( @SUBTREE($span:ident) $delim:ident $($tt:tt)* ) => { + { + let children = $crate::builtin::quote::__quote!($span $($tt)*); + $crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::$delim, + open: $span, + close: $span, + }, + token_trees: $crate::builtin::quote::IntoTt::to_tokens(children).into_boxed_slice(), + } + } + }; + + ( @PUNCT($span:ident) $first:literal ) => { + { + vec![ + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $first, + spacing: crate::tt::Spacing::Alone, + span: $span, + }).into() + ] + } + }; + + ( @PUNCT($span:ident) $first:literal, $sec:literal ) => { + { + vec![ + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $first, + spacing: crate::tt::Spacing::Joint, + span: $span, + }).into(), + crate::tt::Leaf::Punct(crate::tt::Punct { + char: $sec, + spacing: crate::tt::Spacing::Alone, + span: $span, + }).into() + ] + } + }; + + // hash variable + ($span:ident # $first:ident $($tail:tt)* ) => { + { + let token = $crate::builtin::quote::ToTokenTree::to_token($first, $span); + let mut tokens = vec![token.into()]; + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + + ($span:ident ## $first:ident $($tail:tt)* ) => { + { + let mut tokens = $first.into_iter().map(|it| $crate::builtin::quote::ToTokenTree::to_token(it, $span)).collect::>(); + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + tokens.append(&mut tail_tokens); + tokens + } + }; + + // Brace + ($span:ident { $($tt:tt)* } ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Brace $($tt)*) }; + // Bracket + ($span:ident [ $($tt:tt)* ] ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Bracket $($tt)*) }; + // Parenthesis + ($span:ident ( $($tt:tt)* ) ) => { $crate::builtin::quote::__quote!(@SUBTREE($span) Parenthesis $($tt)*) }; + + // Literal + ($span:ident $tt:literal ) => { vec![$crate::builtin::quote::ToTokenTree::to_token($tt, $span).into()] }; + // Ident + ($span:ident $tt:ident ) => { + vec![ { + crate::tt::Leaf::Ident(crate::tt::Ident { + sym: intern::Symbol::intern(stringify!($tt)), + span: $span, + is_raw: tt::IdentIsRaw::No, + }).into() + }] + }; + + // Puncts + // FIXME: Not all puncts are handled + ($span:ident -> ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '-', '>')}; + ($span:ident & ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '&')}; + ($span:ident , ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ',')}; + ($span:ident : ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ':')}; + ($span:ident ; ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ';')}; + ($span:ident :: ) => {$crate::builtin::quote::__quote!(@PUNCT($span) ':', ':')}; + ($span:ident . ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '.')}; + ($span:ident < ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '<')}; + ($span:ident > ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '>')}; + ($span:ident ! ) => {$crate::builtin::quote::__quote!(@PUNCT($span) '!')}; + + ($span:ident $first:tt $($tail:tt)+ ) => { + { + let mut tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $first )); + let mut tail_tokens = $crate::builtin::quote::IntoTt::to_tokens($crate::builtin::quote::__quote!($span $($tail)*)); + + tokens.append(&mut tail_tokens); + tokens + } + }; +} +pub(super) use quote_impl__ as __quote; + +/// FIXME: +/// It probably should implement in proc-macro +macro_rules! quote_impl { + ($span:ident=> $($tt:tt)* ) => { + $crate::builtin::quote::IntoTt::to_subtree($crate::builtin::quote::__quote!($span $($tt)*), $span) + } +} +pub(super) use quote_impl as quote; + +pub(crate) trait IntoTt { + fn to_subtree(self, span: Span) -> crate::tt::Subtree; + fn to_tokens(self) -> Vec; +} + +impl IntoTt for Vec { + fn to_subtree(self, span: Span) -> crate::tt::Subtree { + crate::tt::Subtree { + delimiter: crate::tt::Delimiter::invisible_spanned(span), + token_trees: self.into_boxed_slice(), + } + } + + fn to_tokens(self) -> Vec { + self + } +} + +impl IntoTt for crate::tt::Subtree { + fn to_subtree(self, _: Span) -> crate::tt::Subtree { + self + } + + fn to_tokens(self) -> Vec { + vec![crate::tt::TokenTree::Subtree(self)] + } +} + +pub(crate) trait ToTokenTree { + fn to_token(self, span: Span) -> crate::tt::TokenTree; +} + +impl ToTokenTree for crate::tt::TokenTree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { + self + } +} + +impl ToTokenTree for crate::tt::Subtree { + fn to_token(self, _: Span) -> crate::tt::TokenTree { + self.into() + } +} + +macro_rules! impl_to_to_tokentrees { + ($($span:ident: $ty:ty => $this:ident $im:block;)*) => { + $( + impl ToTokenTree for $ty { + fn to_token($this, $span: Span) -> crate::tt::TokenTree { + let leaf: crate::tt::Leaf = $im.into(); + leaf.into() + } + } + )* + } +} + +impl ToTokenTree for &T { + fn to_token(self, span: Span) -> crate::tt::TokenTree { + self.clone().to_token(span) + } +} + +impl_to_to_tokentrees! { + span: u32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: usize => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: i32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; + span: bool => self { crate::tt::Ident{sym: if self { sym::true_.clone() } else { sym::false_.clone() }, span, is_raw: tt::IdentIsRaw::No } }; + _span: crate::tt::Leaf => self { self }; + _span: crate::tt::Literal => self { self }; + _span: crate::tt::Ident => self { self }; + _span: crate::tt::Punct => self { self }; + span: &str => self { crate::tt::Literal{symbol: Symbol::intern(self), span, kind: tt::LitKind::Str, suffix: None }}; + span: String => self { crate::tt::Literal{symbol: Symbol::intern(&self), span, kind: tt::LitKind::Str, suffix: None }}; + span: Name => self { + let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); + crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } + }; + span: Symbol => self { + let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); + crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } + }; +} + +#[cfg(test)] +mod tests { + use crate::tt; + use ::tt::IdentIsRaw; + use expect_test::expect; + use intern::Symbol; + use span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; + use syntax::{TextRange, TextSize}; + + use super::quote; + + const DUMMY: tt::Span = tt::Span { + range: TextRange::empty(TextSize::new(0)), + anchor: SpanAnchor { + file_id: span::EditionedFileId::new( + span::FileId::from_raw(0xe4e4e), + span::Edition::CURRENT, + ), + ast_id: ROOT_ERASED_FILE_AST_ID, + }, + ctx: SyntaxContextId::ROOT, + }; + + #[test] + fn test_quote_delimiters() { + assert_eq!(quote!(DUMMY =>{}).to_string(), "{}"); + assert_eq!(quote!(DUMMY =>()).to_string(), "()"); + assert_eq!(quote!(DUMMY =>[]).to_string(), "[]"); + } + + #[test] + fn test_quote_idents() { + assert_eq!(quote!(DUMMY =>32).to_string(), "32"); + assert_eq!(quote!(DUMMY =>struct).to_string(), "struct"); + } + + #[test] + fn test_quote_hash_simple_literal() { + let a = 20; + assert_eq!(quote!(DUMMY =>#a).to_string(), "20"); + let s: String = "hello".into(); + assert_eq!(quote!(DUMMY =>#s).to_string(), "\"hello\""); + } + + fn mk_ident(name: &str) -> crate::tt::Ident { + let (is_raw, s) = IdentIsRaw::split_from_symbol(name); + crate::tt::Ident { sym: Symbol::intern(s), span: DUMMY, is_raw } + } + + #[test] + fn test_quote_hash_token_tree() { + let a = mk_ident("hello"); + + let quoted = quote!(DUMMY =>#a); + assert_eq!(quoted.to_string(), "hello"); + let t = format!("{quoted:#?}"); + expect![[r#" + SUBTREE $$ 937550:0@0..0#0 937550:0@0..0#0 + IDENT hello 937550:0@0..0#0"#]] + .assert_eq(&t); + } + + #[test] + fn test_quote_simple_derive_copy() { + let name = mk_ident("Foo"); + + let quoted = quote! {DUMMY => + impl Clone for #name { + fn clone(&self) -> Self { + Self {} + } + } + }; + + assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}"); + } + + #[test] + fn test_quote_derive_copy_hack() { + // Assume the given struct is: + // struct Foo { + // name: String, + // id: u32, + // } + let struct_name = mk_ident("Foo"); + let fields = [mk_ident("name"), mk_ident("id")]; + let fields = fields + .iter() + .flat_map(|it| quote!(DUMMY =>#it: self.#it.clone(), ).token_trees.into_vec()); + + let list = crate::tt::Subtree { + delimiter: crate::tt::Delimiter { + kind: crate::tt::DelimiterKind::Brace, + open: DUMMY, + close: DUMMY, + }, + token_trees: fields.collect(), + }; + + let quoted = quote! {DUMMY => + impl Clone for #struct_name { + fn clone(&self) -> Self { + Self #list + } + } + }; + + assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}"); + } +} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs deleted file mode 100644 index b9afc666f75..00000000000 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_attr_macro.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! Builtin attributes. -use intern::sym; -use span::{MacroCallId, Span}; - -use crate::{db::ExpandDatabase, name, tt, ExpandResult, MacroCallKind}; - -macro_rules! register_builtin { - ($(($name:ident, $variant:ident) => $expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinAttrExpander { - $($variant),* - } - - impl BuiltinAttrExpander { - pub fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { - match *self { - $( BuiltinAttrExpander::$variant => $expand, )* - } - } - - fn find_by_name(name: &name::Name) -> Option { - match name { - $( id if id == &sym::$name => Some(BuiltinAttrExpander::$variant), )* - _ => None, - } - } - } - - }; -} - -impl BuiltinAttrExpander { - pub fn expand( - &self, - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, - ) -> ExpandResult { - self.expander()(db, id, tt, span) - } - - pub fn is_derive(self) -> bool { - matches!(self, BuiltinAttrExpander::Derive | BuiltinAttrExpander::DeriveConst) - } - pub fn is_test(self) -> bool { - matches!(self, BuiltinAttrExpander::Test) - } - pub fn is_bench(self) -> bool { - matches!(self, BuiltinAttrExpander::Bench) - } -} - -register_builtin! { - (bench, Bench) => dummy_attr_expand, - (cfg_accessible, CfgAccessible) => dummy_attr_expand, - (cfg_eval, CfgEval) => dummy_attr_expand, - (derive, Derive) => derive_expand, - // derive const is equivalent to derive for our proposes. - (derive_const, DeriveConst) => derive_expand, - (global_allocator, GlobalAllocator) => dummy_attr_expand, - (test, Test) => dummy_attr_expand, - (test_case, TestCase) => dummy_attr_expand -} - -pub fn find_builtin_attr(ident: &name::Name) -> Option { - BuiltinAttrExpander::find_by_name(ident) -} - -fn dummy_attr_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - _span: Span, -) -> ExpandResult { - 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_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let loc = db.lookup_intern_macro_call(id); - let derives = match &loc.kind { - MacroCallKind::Attr { attr_args: Some(attr_args), .. } if loc.def.is_attribute_derive() => { - attr_args - } - _ => { - return ExpandResult::ok(tt::Subtree::empty(tt::DelimSpan { open: span, close: span })) - } - }; - pseudo_derive_attr_expansion(tt, derives, span) -} - -pub fn pseudo_derive_attr_expansion( - _: &tt::Subtree, - args: &tt::Subtree, - call_site: Span, -) -> ExpandResult { - let mk_leaf = |char| { - tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { - char, - spacing: tt::Spacing::Alone, - span: call_site, - })) - }; - - let mut token_trees = Vec::new(); - for tt in args - .token_trees - .split(|tt| matches!(tt, tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', .. })))) - { - token_trees.push(mk_leaf('#')); - token_trees.push(mk_leaf('!')); - token_trees.push(mk_leaf('[')); - token_trees.extend(tt.iter().cloned()); - token_trees.push(mk_leaf(']')); - } - ExpandResult::ok(tt::Subtree { - delimiter: args.delimiter, - token_trees: token_trees.into_boxed_slice(), - }) -} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs deleted file mode 100644 index d168bad7030..00000000000 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_derive_macro.rs +++ /dev/null @@ -1,868 +0,0 @@ -//! Builtin derives. - -use intern::sym; -use itertools::izip; -use mbe::DocCommentDesugarMode; -use rustc_hash::FxHashSet; -use span::{MacroCallId, Span}; -use stdx::never; -use tracing::debug; - -use crate::{ - hygiene::span_with_def_site_ctxt, - name::{AsName, Name}, - quote::dollar_crate, - span_map::ExpansionSpanMap, - tt, -}; -use syntax::ast::{ - self, AstNode, FieldList, HasAttrs, HasGenericParams, HasModuleItem, HasName, HasTypeBounds, -}; - -use crate::{db::ExpandDatabase, name, quote, ExpandError, ExpandResult}; - -macro_rules! register_builtin { - ( $($trait:ident => $expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum BuiltinDeriveExpander { - $($trait),* - } - - impl BuiltinDeriveExpander { - pub fn expander(&self) -> fn(Span, &tt::Subtree) -> ExpandResult { - match *self { - $( BuiltinDeriveExpander::$trait => $expand, )* - } - } - - fn find_by_name(name: &name::Name) -> Option { - match name { - $( id if id == &sym::$trait => Some(BuiltinDeriveExpander::$trait), )* - _ => None, - } - } - } - - }; -} - -impl BuiltinDeriveExpander { - pub fn expand( - &self, - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, - ) -> ExpandResult { - let span = span_with_def_site_ctxt(db, span, id); - self.expander()(span, tt) - } -} - -register_builtin! { - Copy => copy_expand, - Clone => clone_expand, - Default => default_expand, - Debug => debug_expand, - Hash => hash_expand, - Ord => ord_expand, - PartialOrd => partial_ord_expand, - Eq => eq_expand, - PartialEq => partial_eq_expand -} - -pub fn find_builtin_derive(ident: &name::Name) -> Option { - BuiltinDeriveExpander::find_by_name(ident) -} - -enum VariantShape { - Struct(Vec), - Tuple(usize), - Unit, -} - -fn tuple_field_iterator(span: Span, n: usize) -> impl Iterator { - (0..n).map(move |it| tt::Ident::new(&format!("f{it}"), span)) -} - -impl VariantShape { - fn as_pattern(&self, path: tt::Subtree, span: Span) -> tt::Subtree { - self.as_pattern_map(path, span, |it| quote!(span => #it)) - } - - fn field_names(&self, span: Span) -> Vec { - match self { - VariantShape::Struct(s) => s.clone(), - VariantShape::Tuple(n) => tuple_field_iterator(span, *n).collect(), - VariantShape::Unit => vec![], - } - } - - fn as_pattern_map( - &self, - path: tt::Subtree, - span: Span, - field_map: impl Fn(&tt::Ident) -> tt::Subtree, - ) -> tt::Subtree { - match self { - VariantShape::Struct(fields) => { - let fields = fields.iter().map(|it| { - let mapped = field_map(it); - quote! {span => #it : #mapped , } - }); - quote! {span => - #path { ##fields } - } - } - &VariantShape::Tuple(n) => { - let fields = tuple_field_iterator(span, n).map(|it| { - let mapped = field_map(&it); - quote! {span => - #mapped , - } - }); - quote! {span => - #path ( ##fields ) - } - } - VariantShape::Unit => path, - } - } - - fn from(tm: &ExpansionSpanMap, value: Option) -> Result { - let r = match value { - None => VariantShape::Unit, - Some(FieldList::RecordFieldList(it)) => VariantShape::Struct( - it.fields() - .map(|it| it.name()) - .map(|it| name_to_token(tm, it)) - .collect::>()?, - ), - Some(FieldList::TupleFieldList(it)) => VariantShape::Tuple(it.fields().count()), - }; - Ok(r) - } -} - -enum AdtShape { - Struct(VariantShape), - Enum { variants: Vec<(tt::Ident, VariantShape)>, default_variant: Option }, - Union, -} - -impl AdtShape { - fn as_pattern(&self, span: Span, name: &tt::Ident) -> Vec { - self.as_pattern_map(name, |it| quote!(span =>#it), span) - } - - fn field_names(&self, span: Span) -> Vec> { - match self { - AdtShape::Struct(s) => { - vec![s.field_names(span)] - } - AdtShape::Enum { variants, .. } => { - variants.iter().map(|(_, fields)| fields.field_names(span)).collect() - } - AdtShape::Union => { - never!("using fields of union in derive is always wrong"); - vec![] - } - } - } - - fn as_pattern_map( - &self, - name: &tt::Ident, - field_map: impl Fn(&tt::Ident) -> tt::Subtree, - span: Span, - ) -> Vec { - match self { - AdtShape::Struct(s) => { - vec![s.as_pattern_map(quote! {span => #name }, span, field_map)] - } - AdtShape::Enum { variants, .. } => variants - .iter() - .map(|(v, fields)| { - fields.as_pattern_map(quote! {span => #name :: #v }, span, &field_map) - }) - .collect(), - AdtShape::Union => { - never!("pattern matching on union is always wrong"); - vec![quote! {span => un }] - } - } - } -} - -struct BasicAdtInfo { - name: tt::Ident, - shape: AdtShape, - /// first field is the name, and - /// second field is `Some(ty)` if it's a const param of type `ty`, `None` if it's a type param. - /// third fields is where bounds, if any - param_types: Vec<(tt::Subtree, Option, Option)>, - where_clause: Vec, - associated_types: Vec, -} - -fn parse_adt(tt: &tt::Subtree, call_site: Span) -> Result { - let (parsed, tm) = &mbe::token_tree_to_syntax_node( - tt, - mbe::TopEntryPoint::MacroItems, - parser::Edition::CURRENT_FIXME, - ); - let macro_items = ast::MacroItems::cast(parsed.syntax_node()) - .ok_or_else(|| ExpandError::other("invalid item definition"))?; - let item = macro_items.items().next().ok_or_else(|| ExpandError::other("no item found"))?; - let adt = &ast::Adt::cast(item.syntax().clone()) - .ok_or_else(|| ExpandError::other("expected struct, enum or union"))?; - let (name, generic_param_list, where_clause, shape) = match adt { - ast::Adt::Struct(it) => ( - it.name(), - it.generic_param_list(), - it.where_clause(), - AdtShape::Struct(VariantShape::from(tm, it.field_list())?), - ), - ast::Adt::Enum(it) => { - let default_variant = it - .variant_list() - .into_iter() - .flat_map(|it| it.variants()) - .position(|it| it.attrs().any(|it| it.simple_name() == Some("default".into()))); - ( - it.name(), - it.generic_param_list(), - it.where_clause(), - AdtShape::Enum { - default_variant, - variants: it - .variant_list() - .into_iter() - .flat_map(|it| it.variants()) - .map(|it| { - Ok(( - name_to_token(tm, it.name())?, - VariantShape::from(tm, it.field_list())?, - )) - }) - .collect::>()?, - }, - ) - } - ast::Adt::Union(it) => { - (it.name(), it.generic_param_list(), it.where_clause(), AdtShape::Union) - } - }; - - let mut param_type_set: FxHashSet = FxHashSet::default(); - let param_types = generic_param_list - .into_iter() - .flat_map(|param_list| param_list.type_or_const_params()) - .map(|param| { - let name = { - let this = param.name(); - match this { - Some(it) => { - param_type_set.insert(it.as_name()); - mbe::syntax_node_to_token_tree( - it.syntax(), - tm, - call_site, - DocCommentDesugarMode::ProcMacro, - ) - } - None => { - tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) - } - } - }; - let bounds = match ¶m { - ast::TypeOrConstParam::Type(it) => it.type_bound_list().map(|it| { - mbe::syntax_node_to_token_tree( - it.syntax(), - tm, - call_site, - DocCommentDesugarMode::ProcMacro, - ) - }), - ast::TypeOrConstParam::Const(_) => None, - }; - let ty = if let ast::TypeOrConstParam::Const(param) = param { - let ty = param - .ty() - .map(|ty| { - mbe::syntax_node_to_token_tree( - ty.syntax(), - tm, - call_site, - DocCommentDesugarMode::ProcMacro, - ) - }) - .unwrap_or_else(|| { - tt::Subtree::empty(::tt::DelimSpan { open: call_site, close: call_site }) - }); - Some(ty) - } else { - None - }; - (name, ty, bounds) - }) - .collect(); - - let where_clause = if let Some(w) = where_clause { - w.predicates() - .map(|it| { - mbe::syntax_node_to_token_tree( - it.syntax(), - tm, - call_site, - DocCommentDesugarMode::ProcMacro, - ) - }) - .collect() - } else { - vec![] - }; - - // For a generic parameter `T`, when shorthand associated type `T::Assoc` appears in field - // types (of any variant for enums), we generate trait bound for it. It sounds reasonable to - // also generate trait bound for qualified associated type `::Assoc`, but rustc - // does not do that for some unknown reason. - // - // See the analogous function in rustc [find_type_parameters()] and rust-lang/rust#50730. - // [find_type_parameters()]: https://github.com/rust-lang/rust/blob/1.70.0/compiler/rustc_builtin_macros/src/deriving/generic/mod.rs#L378 - - // It's cumbersome to deal with the distinct structures of ADTs, so let's just get untyped - // `SyntaxNode` that contains fields and look for descendant `ast::PathType`s. Of note is that - // we should not inspect `ast::PathType`s in parameter bounds and where clauses. - let field_list = match adt { - ast::Adt::Enum(it) => it.variant_list().map(|list| list.syntax().clone()), - ast::Adt::Struct(it) => it.field_list().map(|list| list.syntax().clone()), - ast::Adt::Union(it) => it.record_field_list().map(|list| list.syntax().clone()), - }; - let associated_types = field_list - .into_iter() - .flat_map(|it| it.descendants()) - .filter_map(ast::PathType::cast) - .filter_map(|p| { - let name = p.path()?.qualifier()?.as_single_name_ref()?.as_name(); - param_type_set.contains(&name).then_some(p) - }) - .map(|it| { - mbe::syntax_node_to_token_tree( - it.syntax(), - tm, - call_site, - DocCommentDesugarMode::ProcMacro, - ) - }) - .collect(); - let name_token = name_to_token(tm, name)?; - Ok(BasicAdtInfo { name: name_token, shape, param_types, where_clause, associated_types }) -} - -fn name_to_token( - token_map: &ExpansionSpanMap, - name: Option, -) -> Result { - let name = name.ok_or_else(|| { - debug!("parsed item has no name"); - ExpandError::other("missing name") - })?; - let span = token_map.span_at(name.syntax().text_range().start()); - - let name_token = tt::Ident::new(name.text().as_ref(), span); - Ok(name_token) -} - -/// Given that we are deriving a trait `DerivedTrait` for a type like: -/// -/// ```ignore (only-for-syntax-highlight) -/// struct Struct<'a, ..., 'z, A, B: DeclaredTrait, C, ..., Z> where C: WhereTrait { -/// a: A, -/// b: B::Item, -/// b1: ::Item, -/// c1: ::Item, -/// c2: Option<::Item>, -/// ... -/// } -/// ``` -/// -/// create an impl like: -/// -/// ```ignore (only-for-syntax-highlight) -/// impl<'a, ..., 'z, A, B: DeclaredTrait, C, ... Z> where -/// C: WhereTrait, -/// A: DerivedTrait + B1 + ... + BN, -/// B: DerivedTrait + B1 + ... + BN, -/// C: DerivedTrait + B1 + ... + BN, -/// B::Item: DerivedTrait + B1 + ... + BN, -/// ::Item: DerivedTrait + B1 + ... + BN, -/// ... -/// { -/// ... -/// } -/// ``` -/// -/// where B1, ..., BN are the bounds given by `bounds_paths`. Z is a phantom type, and -/// therefore does not get bound by the derived trait. -fn expand_simple_derive( - invoc_span: Span, - tt: &tt::Subtree, - trait_path: tt::Subtree, - make_trait_body: impl FnOnce(&BasicAdtInfo) -> tt::Subtree, -) -> ExpandResult { - let info = match parse_adt(tt, invoc_span) { - Ok(info) => info, - Err(e) => { - return ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan { open: invoc_span, close: invoc_span }), - e, - ) - } - }; - let trait_body = make_trait_body(&info); - let mut where_block: Vec<_> = - info.where_clause.into_iter().map(|w| quote! {invoc_span => #w , }).collect(); - let (params, args): (Vec<_>, Vec<_>) = info - .param_types - .into_iter() - .map(|(ident, param_ty, bound)| { - let ident_ = ident.clone(); - if let Some(b) = bound { - let ident = ident.clone(); - where_block.push(quote! {invoc_span => #ident : #b , }); - } - if let Some(ty) = param_ty { - (quote! {invoc_span => const #ident : #ty , }, quote! {invoc_span => #ident_ , }) - } else { - let bound = trait_path.clone(); - (quote! {invoc_span => #ident : #bound , }, quote! {invoc_span => #ident_ , }) - } - }) - .unzip(); - - where_block.extend(info.associated_types.iter().map(|it| { - let it = it.clone(); - let bound = trait_path.clone(); - quote! {invoc_span => #it : #bound , } - })); - - let name = info.name; - let expanded = quote! {invoc_span => - impl < ##params > #trait_path for #name < ##args > where ##where_block { #trait_body } - }; - ExpandResult::ok(expanded) -} - -fn copy_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::marker::Copy }, |_| quote! {span =>}) -} - -fn clone_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::clone::Clone }, |adt| { - if matches!(adt.shape, AdtShape::Union) { - let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; - return quote! {span => - fn clone(&self) -> Self { - #star self - } - }; - } - if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { - let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; - return quote! {span => - fn clone(&self) -> Self { - match #star self {} - } - }; - } - let name = &adt.name; - let patterns = adt.shape.as_pattern(span, name); - let exprs = adt.shape.as_pattern_map(name, |it| quote! {span => #it .clone() }, span); - let arms = patterns.into_iter().zip(exprs).map(|(pat, expr)| { - let fat_arrow = fat_arrow(span); - quote! {span => - #pat #fat_arrow #expr, - } - }); - - quote! {span => - fn clone(&self) -> Self { - match self { - ##arms - } - } - } - }) -} - -/// This function exists since `quote! {span => => }` doesn't work. -fn fat_arrow(span: Span) -> tt::Subtree { - let eq = tt::Punct { char: '=', spacing: ::tt::Spacing::Joint, span }; - quote! {span => #eq> } -} - -/// This function exists since `quote! {span => && }` doesn't work. -fn and_and(span: Span) -> tt::Subtree { - let and = tt::Punct { char: '&', spacing: ::tt::Spacing::Joint, span }; - quote! {span => #and& } -} - -fn default_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = &dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::default::Default }, |adt| { - let body = match &adt.shape { - AdtShape::Struct(fields) => { - let name = &adt.name; - fields.as_pattern_map( - quote!(span =>#name), - span, - |_| quote!(span =>#krate::default::Default::default()), - ) - } - AdtShape::Enum { default_variant, variants } => { - if let Some(d) = default_variant { - let (name, fields) = &variants[*d]; - let adt_name = &adt.name; - fields.as_pattern_map( - quote!(span =>#adt_name :: #name), - span, - |_| quote!(span =>#krate::default::Default::default()), - ) - } else { - // FIXME: Return expand error here - quote!(span =>) - } - } - AdtShape::Union => { - // FIXME: Return expand error here - quote!(span =>) - } - }; - quote! {span => - fn default() -> Self { - #body - } - } - }) -} - -fn debug_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = &dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::fmt::Debug }, |adt| { - let for_variant = |name: String, v: &VariantShape| match v { - VariantShape::Struct(fields) => { - let for_fields = fields.iter().map(|it| { - let x_string = it.to_string(); - quote! {span => - .field(#x_string, & #it) - } - }); - quote! {span => - f.debug_struct(#name) ##for_fields .finish() - } - } - VariantShape::Tuple(n) => { - let for_fields = tuple_field_iterator(span, *n).map(|it| { - quote! {span => - .field( & #it) - } - }); - quote! {span => - f.debug_tuple(#name) ##for_fields .finish() - } - } - VariantShape::Unit => quote! {span => - f.write_str(#name) - }, - }; - if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { - let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; - return quote! {span => - fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { - match #star self {} - } - }; - } - let arms = match &adt.shape { - AdtShape::Struct(fields) => { - let fat_arrow = fat_arrow(span); - let name = &adt.name; - let pat = fields.as_pattern(quote!(span =>#name), span); - let expr = for_variant(name.to_string(), fields); - vec![quote! {span => #pat #fat_arrow #expr }] - } - AdtShape::Enum { variants, .. } => variants - .iter() - .map(|(name, v)| { - let fat_arrow = fat_arrow(span); - let adt_name = &adt.name; - let pat = v.as_pattern(quote!(span =>#adt_name :: #name), span); - let expr = for_variant(name.to_string(), v); - quote! {span => - #pat #fat_arrow #expr , - } - }) - .collect(), - AdtShape::Union => { - // FIXME: Return expand error here - vec![] - } - }; - quote! {span => - fn fmt(&self, f: &mut #krate::fmt::Formatter) -> #krate::fmt::Result { - match self { - ##arms - } - } - } - }) -} - -fn hash_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = &dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::hash::Hash }, |adt| { - if matches!(adt.shape, AdtShape::Union) { - // FIXME: Return expand error here - return quote! {span =>}; - } - if matches!(&adt.shape, AdtShape::Enum { variants, .. } if variants.is_empty()) { - let star = tt::Punct { char: '*', spacing: ::tt::Spacing::Alone, span }; - return quote! {span => - fn hash(&self, ra_expand_state: &mut H) { - match #star self {} - } - }; - } - let arms = - adt.shape.as_pattern(span, &adt.name).into_iter().zip(adt.shape.field_names(span)).map( - |(pat, names)| { - let expr = { - let it = - names.iter().map(|it| quote! {span => #it . hash(ra_expand_state); }); - quote! {span => { - ##it - } } - }; - let fat_arrow = fat_arrow(span); - quote! {span => - #pat #fat_arrow #expr , - } - }, - ); - let check_discriminant = if matches!(&adt.shape, AdtShape::Enum { .. }) { - quote! {span => #krate::mem::discriminant(self).hash(ra_expand_state); } - } else { - quote! {span =>} - }; - quote! {span => - fn hash(&self, ra_expand_state: &mut H) { - #check_discriminant - match self { - ##arms - } - } - } - }) -} - -fn eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::cmp::Eq }, |_| quote! {span =>}) -} - -fn partial_eq_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialEq }, |adt| { - if matches!(adt.shape, AdtShape::Union) { - // FIXME: Return expand error here - return quote! {span =>}; - } - let name = &adt.name; - - let (self_patterns, other_patterns) = self_and_other_patterns(adt, name, span); - let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( - |(pat1, pat2, names)| { - let fat_arrow = fat_arrow(span); - let body = match &*names { - [] => { - quote!(span =>true) - } - [first, rest @ ..] => { - let rest = rest.iter().map(|it| { - let t1 = tt::Ident::new(&format!("{}_self", it.sym), it.span); - let t2 = tt::Ident::new(&format!("{}_other", it.sym), it.span); - let and_and = and_and(span); - quote!(span =>#and_and #t1 .eq( #t2 )) - }); - let first = { - let t1 = tt::Ident::new(&format!("{}_self", first.sym), first.span); - let t2 = tt::Ident::new(&format!("{}_other", first.sym), first.span); - quote!(span =>#t1 .eq( #t2 )) - }; - quote!(span =>#first ##rest) - } - }; - quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } - }, - ); - - let fat_arrow = fat_arrow(span); - quote! {span => - fn eq(&self, other: &Self) -> bool { - match (self, other) { - ##arms - _unused #fat_arrow false - } - } - } - }) -} - -fn self_and_other_patterns( - adt: &BasicAdtInfo, - name: &tt::Ident, - span: Span, -) -> (Vec, Vec) { - let self_patterns = adt.shape.as_pattern_map( - name, - |it| { - let t = tt::Ident::new(&format!("{}_self", it.sym), it.span); - quote!(span =>#t) - }, - span, - ); - let other_patterns = adt.shape.as_pattern_map( - name, - |it| { - let t = tt::Ident::new(&format!("{}_other", it.sym), it.span); - quote!(span =>#t) - }, - span, - ); - (self_patterns, other_patterns) -} - -fn ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = &dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::cmp::Ord }, |adt| { - fn compare( - krate: &tt::Ident, - left: tt::Subtree, - right: tt::Subtree, - rest: tt::Subtree, - span: Span, - ) -> tt::Subtree { - let fat_arrow1 = fat_arrow(span); - let fat_arrow2 = fat_arrow(span); - quote! {span => - match #left.cmp(&#right) { - #krate::cmp::Ordering::Equal #fat_arrow1 { - #rest - } - c #fat_arrow2 return c, - } - } - } - if matches!(adt.shape, AdtShape::Union) { - // FIXME: Return expand error here - return quote!(span =>); - } - let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name, span); - let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( - |(pat1, pat2, fields)| { - let mut body = quote!(span =>#krate::cmp::Ordering::Equal); - for f in fields.into_iter().rev() { - let t1 = tt::Ident::new(&format!("{}_self", f.sym), f.span); - let t2 = tt::Ident::new(&format!("{}_other", f.sym), f.span); - body = compare(krate, quote!(span =>#t1), quote!(span =>#t2), body, span); - } - let fat_arrow = fat_arrow(span); - quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } - }, - ); - let fat_arrow = fat_arrow(span); - let mut body = quote! {span => - match (self, other) { - ##arms - _unused #fat_arrow #krate::cmp::Ordering::Equal - } - }; - if matches!(&adt.shape, AdtShape::Enum { .. }) { - let left = quote!(span =>#krate::intrinsics::discriminant_value(self)); - let right = quote!(span =>#krate::intrinsics::discriminant_value(other)); - body = compare(krate, left, right, body, span); - } - quote! {span => - fn cmp(&self, other: &Self) -> #krate::cmp::Ordering { - #body - } - } - }) -} - -fn partial_ord_expand(span: Span, tt: &tt::Subtree) -> ExpandResult { - let krate = &dollar_crate(span); - expand_simple_derive(span, tt, quote! {span => #krate::cmp::PartialOrd }, |adt| { - fn compare( - krate: &tt::Ident, - left: tt::Subtree, - right: tt::Subtree, - rest: tt::Subtree, - span: Span, - ) -> tt::Subtree { - let fat_arrow1 = fat_arrow(span); - let fat_arrow2 = fat_arrow(span); - quote! {span => - match #left.partial_cmp(&#right) { - #krate::option::Option::Some(#krate::cmp::Ordering::Equal) #fat_arrow1 { - #rest - } - c #fat_arrow2 return c, - } - } - } - if matches!(adt.shape, AdtShape::Union) { - // FIXME: Return expand error here - return quote!(span =>); - } - let left = quote!(span =>#krate::intrinsics::discriminant_value(self)); - let right = quote!(span =>#krate::intrinsics::discriminant_value(other)); - - let (self_patterns, other_patterns) = self_and_other_patterns(adt, &adt.name, span); - let arms = izip!(self_patterns, other_patterns, adt.shape.field_names(span)).map( - |(pat1, pat2, fields)| { - let mut body = - quote!(span =>#krate::option::Option::Some(#krate::cmp::Ordering::Equal)); - for f in fields.into_iter().rev() { - let t1 = tt::Ident::new(&format!("{}_self", f.sym), f.span); - let t2 = tt::Ident::new(&format!("{}_other", f.sym), f.span); - body = compare(krate, quote!(span =>#t1), quote!(span =>#t2), body, span); - } - let fat_arrow = fat_arrow(span); - quote! {span => ( #pat1 , #pat2 ) #fat_arrow #body , } - }, - ); - let fat_arrow = fat_arrow(span); - let body = compare( - krate, - left, - right, - quote! {span => - match (self, other) { - ##arms - _unused #fat_arrow #krate::option::Option::Some(#krate::cmp::Ordering::Equal) - } - }, - span, - ); - quote! {span => - fn partial_cmp(&self, other: &Self) -> #krate::option::Option::Option<#krate::cmp::Ordering> { - #body - } - } - }) -} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs deleted file mode 100644 index 2725bdb7684..00000000000 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ /dev/null @@ -1,905 +0,0 @@ -//! Builtin macro - -use base_db::AnchoredPath; -use cfg::CfgExpr; -use either::Either; -use intern::{sym, Symbol}; -use mbe::{parse_exprs_with_sep, parse_to_token_tree, DelimiterKind}; -use span::{Edition, EditionedFileId, Span, SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; -use stdx::format_to; -use syntax::{ - format_smolstr, - unescape::{unescape_byte, unescape_char, unescape_unicode, Mode}, -}; - -use crate::{ - db::ExpandDatabase, - hygiene::{span_with_call_site_ctxt, span_with_def_site_ctxt}, - name, quote, - quote::dollar_crate, - tt::{self, DelimSpan}, - ExpandError, ExpandResult, HirFileIdExt, Lookup as _, MacroCallId, -}; - -macro_rules! register_builtin { - ( $LAZY:ident: $(($name:ident, $kind: ident) => $expand:ident),* , $EAGER:ident: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum $LAZY { - $($kind),* - } - - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum $EAGER { - $($e_kind),* - } - - impl BuiltinFnLikeExpander { - fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { - match *self { - $( BuiltinFnLikeExpander::$kind => $expand, )* - } - } - } - - impl EagerExpander { - fn expander(&self) -> fn (&dyn ExpandDatabase, MacroCallId, &tt::Subtree, Span) -> ExpandResult { - match *self { - $( EagerExpander::$e_kind => $e_expand, )* - } - } - } - - fn find_by_name(ident: &name::Name) -> Option> { - match ident { - $( id if id == &sym::$name => Some(Either::Left(BuiltinFnLikeExpander::$kind)), )* - $( id if id == &sym::$e_name => Some(Either::Right(EagerExpander::$e_kind)), )* - _ => return None, - } - } - }; -} - -impl BuiltinFnLikeExpander { - pub fn expand( - &self, - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, - ) -> ExpandResult { - let span = span_with_def_site_ctxt(db, span, id); - self.expander()(db, id, tt, span) - } - - pub fn is_asm(&self) -> bool { - matches!(self, Self::Asm | Self::GlobalAsm) - } -} - -impl EagerExpander { - pub fn expand( - &self, - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, - ) -> ExpandResult { - let span = span_with_def_site_ctxt(db, span, id); - self.expander()(db, id, tt, span) - } - - pub fn is_include(&self) -> bool { - matches!(self, EagerExpander::Include) - } - - pub fn is_include_like(&self) -> bool { - matches!( - self, - EagerExpander::Include | EagerExpander::IncludeStr | EagerExpander::IncludeBytes - ) - } - - pub fn is_env_or_option_env(&self) -> bool { - matches!(self, EagerExpander::Env | EagerExpander::OptionEnv) - } -} - -pub fn find_builtin_macro( - ident: &name::Name, -) -> Option> { - find_by_name(ident) -} - -register_builtin! { - BuiltinFnLikeExpander: - (column, Column) => line_expand, - (file, File) => file_expand, - (line, Line) => line_expand, - (module_path, ModulePath) => module_path_expand, - (assert, Assert) => assert_expand, - (stringify, Stringify) => stringify_expand, - (llvm_asm, LlvmAsm) => asm_expand, - (asm, Asm) => asm_expand, - (global_asm, GlobalAsm) => global_asm_expand, - (cfg, Cfg) => cfg_expand, - (core_panic, CorePanic) => panic_expand, - (std_panic, StdPanic) => panic_expand, - (unreachable, Unreachable) => unreachable_expand, - (log_syntax, LogSyntax) => log_syntax_expand, - (trace_macros, TraceMacros) => trace_macros_expand, - (format_args, FormatArgs) => format_args_expand, - (const_format_args, ConstFormatArgs) => format_args_expand, - (format_args_nl, FormatArgsNl) => format_args_nl_expand, - (quote, Quote) => quote_expand, - - EagerExpander: - (compile_error, CompileError) => compile_error_expand, - (concat, Concat) => concat_expand, - (concat_idents, ConcatIdents) => concat_idents_expand, - (concat_bytes, ConcatBytes) => concat_bytes_expand, - (include, Include) => include_expand, - (include_bytes, IncludeBytes) => include_bytes_expand, - (include_str, IncludeStr) => include_str_expand, - (env, Env) => env_expand, - (option_env, OptionEnv) => option_env_expand -} - -fn mk_pound(span: Span) -> tt::Subtree { - crate::quote::IntoTt::to_subtree( - vec![crate::tt::Leaf::Punct(crate::tt::Punct { - char: '#', - spacing: crate::tt::Spacing::Alone, - span, - }) - .into()], - span, - ) -} - -fn module_path_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // Just return a dummy result. - ExpandResult::ok(quote! {span => - "module::path" - }) -} - -fn line_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // dummy implementation for type-checking purposes - // Note that `line!` and `column!` will never be implemented properly, as they are by definition - // not incremental - ExpandResult::ok(tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(span), - token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: sym::INTEGER_0.clone(), - span, - kind: tt::LitKind::Integer, - suffix: Some(sym::u32.clone()), - }))]), - }) -} - -fn log_syntax_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - ExpandResult::ok(quote! {span =>}) -} - -fn trace_macros_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - ExpandResult::ok(quote! {span =>}) -} - -fn stringify_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let pretty = ::tt::pretty(&tt.token_trees); - - let expanded = quote! {span => - #pretty - }; - - ExpandResult::ok(expanded) -} - -fn assert_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let call_site_span = span_with_call_site_ctxt(db, span, id); - let args = parse_exprs_with_sep(tt, ',', call_site_span, Edition::CURRENT_FIXME); - let dollar_crate = dollar_crate(span); - let expanded = match &*args { - [cond, panic_args @ ..] => { - let comma = tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(call_site_span), - token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { - char: ',', - spacing: tt::Spacing::Alone, - span: call_site_span, - }))]), - }; - let cond = cond.clone(); - let panic_args = itertools::Itertools::intersperse(panic_args.iter().cloned(), comma); - let mac = if use_panic_2021(db, span) { - quote! {call_site_span => #dollar_crate::panic::panic_2021!(##panic_args) } - } else { - quote! {call_site_span => #dollar_crate::panic!(##panic_args) } - }; - quote! {call_site_span =>{ - if !(#cond) { - #mac; - } - }} - } - [] => quote! {call_site_span =>{}}, - }; - - ExpandResult::ok(expanded) -} - -fn file_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // FIXME: RA purposefully lacks knowledge of absolute file names - // so just return "". - let file_name = "file"; - - let expanded = quote! {span => - #file_name - }; - - ExpandResult::ok(expanded) -} - -fn format_args_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let pound = mk_pound(span); - let mut tt = tt.clone(); - tt.delimiter.kind = tt::DelimiterKind::Parenthesis; - ExpandResult::ok(quote! {span => - builtin #pound format_args #tt - }) -} - -fn format_args_nl_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let pound = mk_pound(span); - let mut tt = tt.clone(); - tt.delimiter.kind = tt::DelimiterKind::Parenthesis; - if let Some(tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - kind: tt::LitKind::Str, - .. - }))) = tt.token_trees.first_mut() - { - *text = Symbol::intern(&format_smolstr!("{}\\n", text.as_str())); - } - ExpandResult::ok(quote! {span => - builtin #pound format_args #tt - }) -} - -fn asm_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // We expand all assembly snippets to `format_args!` invocations to get format syntax - // highlighting for them. - let mut literals = Vec::new(); - for tt in tt.token_trees.chunks(2) { - match tt { - [tt::TokenTree::Leaf(tt::Leaf::Literal(lit))] - | [tt::TokenTree::Leaf(tt::Leaf::Literal(lit)), tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { char: ',', span: _, spacing: _ }))] => - { - let dollar_krate = dollar_crate(span); - literals.push(quote!(span=>#dollar_krate::format_args!(#lit);)); - } - _ => break, - } - } - - let pound = mk_pound(span); - let expanded = quote! {span => - builtin #pound asm ( - {##literals} - ) - }; - ExpandResult::ok(expanded) -} - -fn global_asm_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // Expand to nothing (at item-level) - ExpandResult::ok(quote! {span =>}) -} - -fn cfg_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let loc = db.lookup_intern_macro_call(id); - let expr = CfgExpr::parse(tt); - let enabled = db.crate_graph()[loc.krate].cfg_options.check(&expr) != Some(false); - let expanded = if enabled { quote!(span=>true) } else { quote!(span=>false) }; - ExpandResult::ok(expanded) -} - -fn panic_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let dollar_crate = dollar_crate(span); - let call_site_span = span_with_call_site_ctxt(db, span, id); - - let mac = if use_panic_2021(db, call_site_span) { - sym::panic_2021.clone() - } else { - sym::panic_2015.clone() - }; - - // Expand to a macro call `$crate::panic::panic_{edition}` - let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); - - // Pass the original arguments - let mut subtree = tt.clone(); - subtree.delimiter = tt::Delimiter { - open: call_site_span, - close: call_site_span, - kind: tt::DelimiterKind::Parenthesis, - }; - - // FIXME(slow): quote! have a way to expand to builder to make this a vec! - call.push(tt::TokenTree::Subtree(subtree)); - - ExpandResult::ok(call) -} - -fn unreachable_expand( - db: &dyn ExpandDatabase, - id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let dollar_crate = dollar_crate(span); - let call_site_span = span_with_call_site_ctxt(db, span, id); - - let mac = if use_panic_2021(db, call_site_span) { - sym::unreachable_2021.clone() - } else { - sym::unreachable_2015.clone() - }; - - // Expand to a macro call `$crate::panic::panic_{edition}` - let mut call = quote!(call_site_span =>#dollar_crate::panic::#mac!); - - // Pass the original arguments - let mut subtree = tt.clone(); - subtree.delimiter = tt::Delimiter { - open: call_site_span, - close: call_site_span, - kind: tt::DelimiterKind::Parenthesis, - }; - - // FIXME(slow): quote! have a way to expand to builder to make this a vec! - call.push(tt::TokenTree::Subtree(subtree)); - - ExpandResult::ok(call) -} - -#[allow(clippy::never_loop)] -fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool { - // To determine the edition, we check the first span up the expansion - // stack that does not have #[allow_internal_unstable(edition_panic)]. - // (To avoid using the edition of e.g. the assert!() or debug_assert!() definition.) - loop { - let Some(expn) = db.lookup_intern_syntax_context(span.ctx).outer_expn else { - break false; - }; - let expn = db.lookup_intern_macro_call(expn); - // FIXME: Record allow_internal_unstable in the macro def (not been done yet because it - // would consume quite a bit extra memory for all call locs...) - // if let Some(features) = expn.def.allow_internal_unstable { - // if features.iter().any(|&f| f == sym::edition_panic.clone()) { - // span = expn.call_site; - // continue; - // } - // } - break expn.def.edition >= Edition::Edition2021; - } -} - -fn compile_error_expand( - _db: &dyn ExpandDatabase, - _id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let err = match &*tt.token_trees { - [tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span: _, - kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), - suffix: _, - }))] => - // FIXME: Use the span here! - { - ExpandError::other(Box::from(unescape_str(text).as_str())) - } - _ => ExpandError::other("`compile_error!` argument must be a string"), - }; - - ExpandResult { value: quote! {span =>}, err: Some(err) } -} - -fn concat_expand( - _db: &dyn ExpandDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, - _: Span, -) -> ExpandResult { - let mut err = None; - let mut text = String::new(); - let mut span: Option = None; - let mut record_span = |s: Span| match &mut span { - Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), - Some(_) => (), - None => span = Some(s), - }; - for (i, mut t) in tt.token_trees.iter().enumerate() { - // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses - // to ensure the right parsing order, so skip the parentheses here. Ideally we'd - // implement rustc's model. cc https://github.com/rust-lang/rust-analyzer/pull/10623 - if let tt::TokenTree::Subtree(tt::Subtree { delimiter: delim, token_trees }) = t { - if let [tt] = &**token_trees { - if delim.kind == tt::DelimiterKind::Parenthesis { - t = tt; - } - } - } - match t { - tt::TokenTree::Leaf(tt::Leaf::Literal(it)) if i % 2 == 0 => { - // concat works with string and char literals, so remove any quotes. - // It also works with integer, float and boolean literals, so just use the rest - // as-is. - match it.kind { - tt::LitKind::Char => { - if let Ok(c) = unescape_char(it.symbol.as_str()) { - text.extend(c.escape_default()); - } - record_span(it.span); - } - tt::LitKind::Integer | tt::LitKind::Float => { - format_to!(text, "{}", it.symbol.as_str()) - } - tt::LitKind::Str => { - text.push_str(it.symbol.as_str()); - record_span(it.span); - } - tt::LitKind::StrRaw(_) => { - format_to!(text, "{}", it.symbol.as_str().escape_debug()); - record_span(it.span); - } - tt::LitKind::Byte - | tt::LitKind::ByteStr - | tt::LitKind::ByteStrRaw(_) - | tt::LitKind::CStr - | tt::LitKind::CStrRaw(_) - | tt::LitKind::Err(_) => err = Some(ExpandError::other("unexpected literal")), - } - } - // handle boolean literals - tt::TokenTree::Leaf(tt::Leaf::Ident(id)) - if i % 2 == 0 && (id.sym == sym::true_ || id.sym == sym::false_) => - { - text.push_str(id.sym.as_str()); - record_span(id.span); - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); - } - } - } - let span = span.unwrap_or(tt.delimiter.open); - ExpandResult { value: quote!(span =>#text), err } -} - -fn concat_bytes_expand( - _db: &dyn ExpandDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, - _: Span, -) -> ExpandResult { - let mut bytes = String::new(); - let mut err = None; - let mut span: Option = None; - let mut record_span = |s: Span| match &mut span { - Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range), - Some(_) => (), - None => span = Some(s), - }; - for (i, t) in tt.token_trees.iter().enumerate() { - match t { - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind, - suffix: _, - })) => { - record_span(*span); - match kind { - tt::LitKind::Byte => { - if let Ok(b) = unescape_byte(text.as_str()) { - bytes.extend( - b.escape_ascii().filter_map(|it| char::from_u32(it as u32)), - ); - } - } - tt::LitKind::ByteStr => { - bytes.push_str(text.as_str()); - } - tt::LitKind::ByteStrRaw(_) => { - bytes.extend(text.as_str().escape_debug()); - } - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); - break; - } - } - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => { - if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes, &mut record_span) { - err.get_or_insert(e); - break; - } - } - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); - break; - } - } - } - let span = span.unwrap_or(tt.delimiter.open); - ExpandResult { - value: tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(span), - token_trees: vec![tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: Symbol::intern(&bytes), - span, - kind: tt::LitKind::ByteStr, - suffix: None, - }))] - .into(), - }, - err, - } -} - -fn concat_bytes_expand_subtree( - tree: &tt::Subtree, - bytes: &mut String, - mut record_span: impl FnMut(Span), -) -> Result<(), ExpandError> { - for (ti, tt) in tree.token_trees.iter().enumerate() { - match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Byte, - suffix: _, - })) => { - if let Ok(b) = unescape_byte(text.as_str()) { - bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); - } - record_span(*span); - } - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Integer, - suffix: _, - })) => { - record_span(*span); - if let Ok(b) = text.as_str().parse::() { - bytes.extend(b.escape_ascii().filter_map(|it| char::from_u32(it as u32))); - } - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if ti % 2 == 1 && punct.char == ',' => (), - _ => { - return Err(mbe::ExpandError::UnexpectedToken.into()); - } - } - } - Ok(()) -} - -fn concat_idents_expand( - _db: &dyn ExpandDatabase, - _arg_id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let mut err = None; - let mut ident = String::new(); - for (i, t) in tt.token_trees.iter().enumerate() { - match t { - tt::TokenTree::Leaf(tt::Leaf::Ident(id)) => { - ident.push_str(id.sym.as_str()); - } - tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), - _ => { - err.get_or_insert(mbe::ExpandError::UnexpectedToken.into()); - } - } - } - // FIXME merge spans - let ident = tt::Ident { sym: Symbol::intern(&ident), span, is_raw: tt::IdentIsRaw::No }; - ExpandResult { value: quote!(span =>#ident), err } -} - -fn relative_file( - db: &dyn ExpandDatabase, - call_id: MacroCallId, - path_str: &str, - allow_recursion: bool, -) -> Result { - let lookup = call_id.lookup(db); - let call_site = lookup.kind.file_id().original_file_respecting_includes(db).file_id(); - let path = AnchoredPath { anchor: call_site, path: path_str }; - let res = db - .resolve_path(path) - .ok_or_else(|| ExpandError::other(format!("failed to load file `{path_str}`")))?; - // Prevent include itself - if res == call_site && !allow_recursion { - Err(ExpandError::other(format!("recursive inclusion of `{path_str}`"))) - } else { - Ok(EditionedFileId::new(res, db.crate_graph()[lookup.krate].edition)) - } -} - -fn parse_string(tt: &tt::Subtree) -> Result<(Symbol, Span), ExpandError> { - tt.token_trees - .first() - .and_then(|tt| match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Str, - suffix: _, - })) => Some((unescape_str(text), *span)), - // FIXME: We wrap expression fragments in parentheses which can break this expectation - // here - // Remove this once we handle none delims correctly - tt::TokenTree::Subtree(t) if t.delimiter.kind == DelimiterKind::Parenthesis => { - t.token_trees.first().and_then(|tt| match tt { - tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: text, - span, - kind: tt::LitKind::Str, - suffix: _, - })) => Some((unescape_str(text), *span)), - _ => None, - }) - } - _ => None, - }) - .ok_or(mbe::ExpandError::ConversionError.into()) -} - -fn include_expand( - db: &dyn ExpandDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let file_id = match include_input_to_file_id(db, arg_id, tt) { - Ok(it) => it, - Err(e) => { - return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) - } - }; - match parse_to_token_tree( - file_id.edition(), - SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID }, - SyntaxContextId::ROOT, - &db.file_text(file_id.file_id()), - ) { - Some(it) => ExpandResult::ok(it), - None => ExpandResult::new( - tt::Subtree::empty(DelimSpan { open: span, close: span }), - ExpandError::other("failed to parse included file"), - ), - } -} - -pub fn include_input_to_file_id( - db: &dyn ExpandDatabase, - arg_id: MacroCallId, - arg: &tt::Subtree, -) -> Result { - relative_file(db, arg_id, parse_string(arg)?.0.as_str(), false) -} - -fn include_bytes_expand( - _db: &dyn ExpandDatabase, - _arg_id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - // FIXME: actually read the file here if the user asked for macro expansion - let res = tt::Subtree { - delimiter: tt::Delimiter::invisible_spanned(span), - token_trees: Box::new([tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { - symbol: Symbol::empty(), - span, - kind: tt::LitKind::ByteStrRaw(1), - suffix: None, - }))]), - }; - ExpandResult::ok(res) -} - -fn include_str_expand( - db: &dyn ExpandDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let (path, span) = match parse_string(tt) { - Ok(it) => it, - Err(e) => { - return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) - } - }; - - // FIXME: we're not able to read excluded files (which is most of them because - // it's unusual to `include_str!` a Rust file), but we can return an empty string. - // Ideally, we'd be able to offer a precise expansion if the user asks for macro - // expansion. - let file_id = match relative_file(db, arg_id, path.as_str(), true) { - Ok(file_id) => file_id, - Err(_) => { - return ExpandResult::ok(quote!(span =>"")); - } - }; - - let text = db.file_text(file_id.file_id()); - let text = &*text; - - ExpandResult::ok(quote!(span =>#text)) -} - -fn get_env_inner(db: &dyn ExpandDatabase, arg_id: MacroCallId, key: &Symbol) -> Option { - let krate = db.lookup_intern_macro_call(arg_id).krate; - db.crate_graph()[krate].env.get(key.as_str()).map(|it| it.escape_debug().to_string()) -} - -fn env_expand( - db: &dyn ExpandDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - let (key, span) = match parse_string(tt) { - Ok(it) => it, - Err(e) => { - return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) - } - }; - - let mut err = None; - let s = get_env_inner(db, arg_id, &key).unwrap_or_else(|| { - // The only variable rust-analyzer ever sets is `OUT_DIR`, so only diagnose that to avoid - // unnecessary diagnostics for eg. `CARGO_PKG_NAME`. - if key.as_str() == "OUT_DIR" { - err = Some(ExpandError::other(r#"`OUT_DIR` not set, enable "build scripts" to fix"#)); - } - - // If the variable is unset, still return a dummy string to help type inference along. - // We cannot use an empty string here, because for - // `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` will become - // `include!("foo.rs"), which might go to infinite loop - "UNRESOLVED_ENV_VAR".to_owned() - }); - let expanded = quote! {span => #s }; - - ExpandResult { value: expanded, err } -} - -fn option_env_expand( - db: &dyn ExpandDatabase, - arg_id: MacroCallId, - tt: &tt::Subtree, - call_site: Span, -) -> ExpandResult { - let (key, span) = match parse_string(tt) { - Ok(it) => it, - Err(e) => { - return ExpandResult::new( - tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }), - e, - ) - } - }; - let dollar_crate = dollar_crate(call_site); - let expanded = match get_env_inner(db, arg_id, &key) { - None => quote! {call_site => #dollar_crate::option::Option::None::<&str> }, - Some(s) => { - let s = quote! (span => #s); - quote! {call_site => #dollar_crate::option::Option::Some(#s) } - } - }; - - ExpandResult::ok(expanded) -} - -fn quote_expand( - _db: &dyn ExpandDatabase, - _arg_id: MacroCallId, - _tt: &tt::Subtree, - span: Span, -) -> ExpandResult { - ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan { open: span, close: span }), - ExpandError::other("quote! is not implemented"), - ) -} - -fn unescape_str(s: &Symbol) -> Symbol { - if s.as_str().contains('\\') { - let s = s.as_str(); - let mut buf = String::with_capacity(s.len()); - unescape_unicode(s, Mode::Str, &mut |_, c| { - if let Ok(c) = c { - buf.push(c) - } - }); - Symbol::intern(&buf) - } else { - s.clone() - } -} diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/change.rs b/src/tools/rust-analyzer/crates/hir-expand/src/change.rs index 08491db3726..1a3dd0e7ddb 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/change.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/change.rs @@ -25,8 +25,7 @@ impl ChangeWithProcMacros { pub fn apply(self, db: &mut (impl ExpandDatabase + SourceDatabaseExt)) { self.source_change.apply(db); - if let Some(mut proc_macros) = self.proc_macros { - proc_macros.shrink_to_fit(); + if let Some(proc_macros) = self.proc_macros { db.set_proc_macros_with_durability(Arc::new(proc_macros), Durability::HIGH); } if let Some(target_data_layouts) = self.target_data_layouts { diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index e78ab2460a7..dd1d292fb64 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -11,8 +11,7 @@ use triomphe::Arc; use crate::{ attrs::{collect_attrs, AttrId}, - builtin_attr_macro::pseudo_derive_attr_expansion, - builtin_fn_macro::EagerExpander, + builtin::pseudo_derive_attr_expansion, cfg_process, declarative::DeclarativeMacroExpander, fixup::{self, SyntaxFixupUndoInfo}, @@ -20,9 +19,9 @@ use crate::{ proc_macro::ProcMacros, span_map::{RealSpanMap, SpanMap, SpanMapRef}, tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, - CustomProcMacroExpander, EagerCallInfo, ExpandError, ExpandResult, ExpandTo, ExpansionSpanMap, - HirFileId, HirFileIdRepr, Lookup, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId, - MacroDefKind, MacroFileId, + CustomProcMacroExpander, EagerCallInfo, EagerExpander, ExpandError, ExpandResult, ExpandTo, + ExpansionSpanMap, HirFileId, HirFileIdRepr, Lookup, MacroCallId, MacroCallKind, MacroCallLoc, + MacroDefId, MacroDefKind, MacroFileId, }; /// This is just to ensure the types of smart_macro_arg and macro_arg are the same type MacroArgResult = (Arc, SyntaxFixupUndoInfo, Span); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index c262fcae473..64b4bd48bac 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -6,9 +6,7 @@ #![cfg_attr(feature = "in-rust-tree", feature(rustc_private))] pub mod attrs; -pub mod builtin_attr_macro; -pub mod builtin_derive_macro; -pub mod builtin_fn_macro; +pub mod builtin; pub mod change; pub mod db; pub mod declarative; @@ -19,7 +17,6 @@ pub mod inert_attr_macro; pub mod mod_path; pub mod name; pub mod proc_macro; -pub mod quote; pub mod span_map; mod cfg_process; @@ -29,7 +26,7 @@ use attrs::collect_attrs; use rustc_hash::FxHashMap; use triomphe::Arc; -use std::{fmt, hash::Hash}; +use std::hash::Hash; use base_db::{salsa::InternValueTrivial, CrateId}; use either::Either; @@ -44,9 +41,10 @@ use syntax::{ use crate::{ attrs::AttrId, - builtin_attr_macro::BuiltinAttrExpander, - builtin_derive_macro::BuiltinDeriveExpander, - builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, + builtin::{ + include_input_to_file_id, BuiltinAttrExpander, BuiltinDeriveExpander, + BuiltinFnLikeExpander, EagerExpander, + }, db::ExpandDatabase, mod_path::ModPath, proc_macro::{CustomProcMacroExpander, ProcMacroKind}, @@ -127,7 +125,8 @@ pub type ExpandResult = ValueResult; #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum ExpandError { - UnresolvedProcMacro(CrateId), + ProcMacroAttrExpansionDisabled, + MissingProcMacroExpander(CrateId), /// The macro expansion is disabled. MacroDisabled, MacroDefinition, @@ -141,31 +140,33 @@ impl ExpandError { pub fn other(msg: impl Into>) -> Self { ExpandError::Other(Arc::new(msg.into())) } -} - -impl From for ExpandError { - fn from(mbe: mbe::ExpandError) -> Self { - Self::Mbe(mbe) - } -} -impl fmt::Display for ExpandError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + pub fn render_to_string(&self, db: &dyn ExpandDatabase) -> (String, bool) { match self { - ExpandError::UnresolvedProcMacro(_) => f.write_str("unresolved proc-macro"), - ExpandError::Mbe(it) => it.fmt(f), - ExpandError::RecursionOverflow => f.write_str("overflow expanding the original macro"), - ExpandError::ProcMacroPanic(it) => { - f.write_str("proc-macro panicked: ")?; - f.write_str(it) + Self::ProcMacroAttrExpansionDisabled => { + ("procedural attribute macro expansion is disabled".to_owned(), false) } - ExpandError::Other(it) => f.write_str(it), - ExpandError::MacroDisabled => f.write_str("macro disabled"), - ExpandError::MacroDefinition => f.write_str("macro definition has parse errors"), + Self::MacroDisabled => ("proc-macro is explicitly disabled".to_owned(), false), + &Self::MissingProcMacroExpander(def_crate) => { + match db.proc_macros().get_error_for_crate(def_crate) { + Some((e, hard_err)) => (e.to_owned(), hard_err), + None => ("missing expander".to_owned(), true), + } + } + Self::MacroDefinition => ("macro definition has parse errors".to_owned(), true), + Self::Mbe(e) => (e.to_string(), true), + Self::RecursionOverflow => ("overflow expanding the original macro".to_owned(), true), + Self::Other(e) => ((***e).to_owned(), true), + Self::ProcMacroPanic(e) => ((***e).to_owned(), true), } } } +impl From for ExpandError { + fn from(mbe: mbe::ExpandError) -> Self { + Self::Mbe(mbe) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroCallLoc { pub def: MacroDefId, @@ -277,11 +278,9 @@ impl HirFileIdExt for HirFileId { let loc = db.lookup_intern_macro_call(file.macro_call_id); if loc.def.is_include() { if let MacroCallKind::FnLike { eager: Some(eager), .. } = &loc.kind { - if let Ok(it) = builtin_fn_macro::include_input_to_file_id( - db, - file.macro_call_id, - &eager.arg, - ) { + if let Ok(it) = + include_input_to_file_id(db, file.macro_call_id, &eager.arg) + { break it; } } @@ -572,9 +571,7 @@ impl MacroCallLoc { ) -> Option { if self.def.is_include() { if let MacroCallKind::FnLike { eager: Some(eager), .. } = &self.kind { - if let Ok(it) = - builtin_fn_macro::include_input_to_file_id(db, macro_call_id, &eager.arg) - { + if let Ok(it) = include_input_to_file_id(db, macro_call_id, &eager.arg) { return Some(it); } } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs index 39599bfe02e..b5dc9a76499 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/proc_macro.rs @@ -7,20 +7,10 @@ use base_db::{CrateId, Env}; use intern::Symbol; use rustc_hash::FxHashMap; use span::Span; -use stdx::never; use triomphe::Arc; use crate::{db::ExpandDatabase, tt, ExpandError, ExpandResult}; -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct ProcMacroId(u32); - -impl ProcMacroId { - pub fn new(u32: u32) -> Self { - ProcMacroId(u32) - } -} - #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] pub enum ProcMacroKind { CustomDerive, @@ -28,7 +18,10 @@ pub enum ProcMacroKind { Attr, } +/// A proc-macro expander implementation. pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe { + /// Run the expander with the given input subtree, optional attribute input subtree (for + /// [`ProcMacroKind::Attr`]), environment variables, and span information. fn expand( &self, subtree: &tt::Subtree, @@ -42,57 +35,162 @@ pub trait ProcMacroExpander: fmt::Debug + Send + Sync + RefUnwindSafe { #[derive(Debug)] pub enum ProcMacroExpansionError { + /// The proc-macro panicked. Panic(String), - /// Things like "proc macro server was killed by OOM". + /// The server itself errored out. System(String), } -pub type ProcMacroLoadResult = Result, String>; +pub type ProcMacroLoadResult = Result, (String, bool)>; +type StoredProcMacroLoadResult = Result, (Box, bool)>; + +#[derive(Default, Debug)] +pub struct ProcMacrosBuilder(FxHashMap); +impl ProcMacrosBuilder { + pub fn insert(&mut self, proc_macros_crate: CrateId, proc_macro: ProcMacroLoadResult) { + self.0.insert( + proc_macros_crate, + match proc_macro { + Ok(it) => Ok(it.into_boxed_slice()), + Err((e, hard_err)) => Err((e.into_boxed_str(), hard_err)), + }, + ); + } + pub fn build(mut self) -> ProcMacros { + self.0.shrink_to_fit(); + ProcMacros(self.0) + } +} + +#[derive(Default, Debug)] +pub struct ProcMacros(FxHashMap); + +impl FromIterator<(CrateId, ProcMacroLoadResult)> for ProcMacros { + fn from_iter>(iter: T) -> Self { + let mut builder = ProcMacrosBuilder::default(); + for (k, v) in iter { + builder.insert(k, v); + } + builder.build() + } +} + +impl ProcMacros { + fn get(&self, krate: CrateId, idx: u32) -> Result<&ProcMacro, ExpandError> { + let proc_macros = match self.0.get(&krate) { + Some(Ok(proc_macros)) => proc_macros, + Some(Err(_)) | None => { + return Err(ExpandError::other("internal error: no proc macros for crate")); + } + }; + proc_macros.get(idx as usize).ok_or_else(|| { + ExpandError::other( + format!( + "internal error: proc-macro index out of bounds: the length is {} but the index is {}", + proc_macros.len(), + idx + ) + ) + } + ) + } + + pub fn get_error_for_crate(&self, krate: CrateId) -> Option<(&str, bool)> { + self.0.get(&krate).and_then(|it| it.as_ref().err()).map(|(e, hard_err)| (&**e, *hard_err)) + } -pub type ProcMacros = FxHashMap; + /// Fetch the [`CustomProcMacroExpander`]s and their corresponding names for the given crate. + pub fn for_crate( + &self, + krate: CrateId, + def_site_ctx: span::SyntaxContextId, + ) -> Option> { + match self.0.get(&krate) { + Some(Ok(proc_macros)) => Some({ + proc_macros + .iter() + .enumerate() + .map(|(idx, it)| { + let name = crate::name::Name::new_symbol(it.name.clone(), def_site_ctx); + (name, CustomProcMacroExpander::new(idx as u32), it.disabled) + }) + .collect() + }), + _ => None, + } + } +} +/// A loaded proc-macro. #[derive(Debug, Clone)] pub struct ProcMacro { + /// The name of the proc macro. pub name: Symbol, pub kind: ProcMacroKind, + /// The expander handle for this proc macro. pub expander: sync::Arc, + /// Whether this proc-macro is disabled for early name resolution. Notably, the + /// [`Self::expander`] is still usable. pub disabled: bool, } +/// A custom proc-macro expander handle. This handle together with its crate resolves to a [`ProcMacro`] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct CustomProcMacroExpander { - proc_macro_id: ProcMacroId, + proc_macro_id: u32, } impl CustomProcMacroExpander { - const DUMMY_ID: u32 = !0; + const MISSING_EXPANDER: u32 = !0; const DISABLED_ID: u32 = !1; + const PROC_MACRO_ATTR_DISABLED: u32 = !2; - pub fn new(proc_macro_id: ProcMacroId) -> Self { - assert_ne!(proc_macro_id.0, Self::DUMMY_ID); - assert_ne!(proc_macro_id.0, Self::DISABLED_ID); + pub fn new(proc_macro_id: u32) -> Self { + assert_ne!(proc_macro_id, Self::MISSING_EXPANDER); + assert_ne!(proc_macro_id, Self::DISABLED_ID); + assert_ne!(proc_macro_id, Self::PROC_MACRO_ATTR_DISABLED); Self { proc_macro_id } } - /// A dummy expander that always errors. This is used for proc-macros that are missing, usually - /// due to them not being built yet. - pub const fn dummy() -> Self { - Self { proc_macro_id: ProcMacroId(Self::DUMMY_ID) } - } - - /// The macro was not yet resolved. - pub const fn is_dummy(&self) -> bool { - self.proc_macro_id.0 == Self::DUMMY_ID + /// An expander that always errors due to the actual proc-macro expander missing. + pub const fn missing_expander() -> Self { + Self { proc_macro_id: Self::MISSING_EXPANDER } } /// A dummy expander that always errors. This expander is used for macros that have been disabled. pub const fn disabled() -> Self { - Self { proc_macro_id: ProcMacroId(Self::DISABLED_ID) } + Self { proc_macro_id: Self::DISABLED_ID } + } + + /// A dummy expander that always errors. This expander is used for attribute macros when + /// proc-macro attribute expansion is disabled. + pub const fn disabled_proc_attr() -> Self { + Self { proc_macro_id: Self::PROC_MACRO_ATTR_DISABLED } + } + + /// The macro-expander is missing or has yet to be build. + pub const fn is_missing(&self) -> bool { + self.proc_macro_id == Self::MISSING_EXPANDER } /// The macro is explicitly disabled and cannot be expanded. pub const fn is_disabled(&self) -> bool { - self.proc_macro_id.0 == Self::DISABLED_ID + self.proc_macro_id == Self::DISABLED_ID + } + + /// The macro is explicitly disabled due to proc-macro attribute expansion being disabled. + pub const fn is_disabled_proc_attr(&self) -> bool { + self.proc_macro_id == Self::PROC_MACRO_ATTR_DISABLED + } + + /// The macro is explicitly disabled due to proc-macro attribute expansion being disabled. + pub const fn as_expand_error(&self, def_crate: CrateId) -> Option { + match self.proc_macro_id { + Self::PROC_MACRO_ATTR_DISABLED => Some(ExpandError::ProcMacroAttrExpansionDisabled), + Self::DISABLED_ID => Some(ExpandError::MacroDisabled), + Self::MISSING_EXPANDER => Some(ExpandError::MissingProcMacroExpander(def_crate)), + _ => None, + } } pub fn expand( @@ -107,38 +205,27 @@ impl CustomProcMacroExpander { mixed_site: Span, ) -> ExpandResult { match self.proc_macro_id { - ProcMacroId(Self::DUMMY_ID) => ExpandResult::new( + Self::PROC_MACRO_ATTR_DISABLED => ExpandResult::new( + tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), + ExpandError::ProcMacroAttrExpansionDisabled, + ), + Self::MISSING_EXPANDER => ExpandResult::new( tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), - ExpandError::UnresolvedProcMacro(def_crate), + ExpandError::MissingProcMacroExpander(def_crate), ), - ProcMacroId(Self::DISABLED_ID) => ExpandResult::new( + Self::DISABLED_ID => ExpandResult::new( tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), ExpandError::MacroDisabled, ), - ProcMacroId(id) => { + id => { let proc_macros = db.proc_macros(); - let proc_macros = match proc_macros.get(&def_crate) { - Some(Ok(proc_macros)) => proc_macros, - Some(Err(_)) | None => { - never!("Non-dummy expander even though there are no proc macros"); - return ExpandResult::new( - tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), - ExpandError::other("Internal error"), - ); - } - }; - let proc_macro = match proc_macros.get(id as usize) { - Some(proc_macro) => proc_macro, - None => { - never!( - "Proc macro index out of bounds: the length is {} but the index is {}", - proc_macros.len(), - id - ); + let proc_macro = match proc_macros.get(def_crate, id) { + Ok(proc_macro) => proc_macro, + Err(e) => { return ExpandResult::new( tt::Subtree::empty(tt::DelimSpan { open: call_site, close: call_site }), - ExpandError::other("Internal error: proc-macro index out of bounds"), - ); + e, + ) } }; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs b/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs deleted file mode 100644 index da02f3aaf9a..00000000000 --- a/src/tools/rust-analyzer/crates/hir-expand/src/quote.rs +++ /dev/null @@ -1,333 +0,0 @@ -//! A simplified version of quote-crate like quasi quote macro -#![allow(clippy::crate_in_macro_def)] - -use intern::{sym, Symbol}; -use span::Span; -use tt::IdentIsRaw; - -use crate::name::Name; - -pub(crate) fn dollar_crate(span: Span) -> tt::Ident { - tt::Ident { sym: sym::dollar_crate.clone(), span, is_raw: tt::IdentIsRaw::No } -} - -// A helper macro quote macro -// FIXME: -// 1. Not all puncts are handled -// 2. #()* pattern repetition not supported now -// * But we can do it manually, see `test_quote_derive_copy_hack` -#[doc(hidden)] -#[macro_export] -macro_rules! __quote { - ($span:ident) => { - Vec::<$crate::tt::TokenTree>::new() - }; - - ( @SUBTREE($span:ident) $delim:ident $($tt:tt)* ) => { - { - let children = $crate::__quote!($span $($tt)*); - $crate::tt::Subtree { - delimiter: crate::tt::Delimiter { - kind: crate::tt::DelimiterKind::$delim, - open: $span, - close: $span, - }, - token_trees: $crate::quote::IntoTt::to_tokens(children).into_boxed_slice(), - } - } - }; - - ( @PUNCT($span:ident) $first:literal ) => { - { - vec![ - crate::tt::Leaf::Punct(crate::tt::Punct { - char: $first, - spacing: crate::tt::Spacing::Alone, - span: $span, - }).into() - ] - } - }; - - ( @PUNCT($span:ident) $first:literal, $sec:literal ) => { - { - vec![ - crate::tt::Leaf::Punct(crate::tt::Punct { - char: $first, - spacing: crate::tt::Spacing::Joint, - span: $span, - }).into(), - crate::tt::Leaf::Punct(crate::tt::Punct { - char: $sec, - spacing: crate::tt::Spacing::Alone, - span: $span, - }).into() - ] - } - }; - - // hash variable - ($span:ident # $first:ident $($tail:tt)* ) => { - { - let token = $crate::quote::ToTokenTree::to_token($first, $span); - let mut tokens = vec![token.into()]; - let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($span $($tail)*)); - tokens.append(&mut tail_tokens); - tokens - } - }; - - ($span:ident ## $first:ident $($tail:tt)* ) => { - { - let mut tokens = $first.into_iter().map(|it| $crate::quote::ToTokenTree::to_token(it, $span)).collect::>(); - let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($span $($tail)*)); - tokens.append(&mut tail_tokens); - tokens - } - }; - - // Brace - ($span:ident { $($tt:tt)* } ) => { $crate::__quote!(@SUBTREE($span) Brace $($tt)*) }; - // Bracket - ($span:ident [ $($tt:tt)* ] ) => { $crate::__quote!(@SUBTREE($span) Bracket $($tt)*) }; - // Parenthesis - ($span:ident ( $($tt:tt)* ) ) => { $crate::__quote!(@SUBTREE($span) Parenthesis $($tt)*) }; - - // Literal - ($span:ident $tt:literal ) => { vec![$crate::quote::ToTokenTree::to_token($tt, $span).into()] }; - // Ident - ($span:ident $tt:ident ) => { - vec![ { - crate::tt::Leaf::Ident(crate::tt::Ident { - sym: intern::Symbol::intern(stringify!($tt)), - span: $span, - is_raw: tt::IdentIsRaw::No, - }).into() - }] - }; - - // Puncts - // FIXME: Not all puncts are handled - ($span:ident -> ) => {$crate::__quote!(@PUNCT($span) '-', '>')}; - ($span:ident & ) => {$crate::__quote!(@PUNCT($span) '&')}; - ($span:ident , ) => {$crate::__quote!(@PUNCT($span) ',')}; - ($span:ident : ) => {$crate::__quote!(@PUNCT($span) ':')}; - ($span:ident ; ) => {$crate::__quote!(@PUNCT($span) ';')}; - ($span:ident :: ) => {$crate::__quote!(@PUNCT($span) ':', ':')}; - ($span:ident . ) => {$crate::__quote!(@PUNCT($span) '.')}; - ($span:ident < ) => {$crate::__quote!(@PUNCT($span) '<')}; - ($span:ident > ) => {$crate::__quote!(@PUNCT($span) '>')}; - ($span:ident ! ) => {$crate::__quote!(@PUNCT($span) '!')}; - - ($span:ident $first:tt $($tail:tt)+ ) => { - { - let mut tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($span $first )); - let mut tail_tokens = $crate::quote::IntoTt::to_tokens($crate::__quote!($span $($tail)*)); - - tokens.append(&mut tail_tokens); - tokens - } - }; -} - -/// FIXME: -/// It probably should implement in proc-macro -#[macro_export] -macro_rules! quote { - ($span:ident=> $($tt:tt)* ) => { - $crate::quote::IntoTt::to_subtree($crate::__quote!($span $($tt)*), $span) - } -} - -pub(crate) trait IntoTt { - fn to_subtree(self, span: Span) -> crate::tt::Subtree; - fn to_tokens(self) -> Vec; -} - -impl IntoTt for Vec { - fn to_subtree(self, span: Span) -> crate::tt::Subtree { - crate::tt::Subtree { - delimiter: crate::tt::Delimiter::invisible_spanned(span), - token_trees: self.into_boxed_slice(), - } - } - - fn to_tokens(self) -> Vec { - self - } -} - -impl IntoTt for crate::tt::Subtree { - fn to_subtree(self, _: Span) -> crate::tt::Subtree { - self - } - - fn to_tokens(self) -> Vec { - vec![crate::tt::TokenTree::Subtree(self)] - } -} - -pub(crate) trait ToTokenTree { - fn to_token(self, span: Span) -> crate::tt::TokenTree; -} - -impl ToTokenTree for crate::tt::TokenTree { - fn to_token(self, _: Span) -> crate::tt::TokenTree { - self - } -} - -impl ToTokenTree for crate::tt::Subtree { - fn to_token(self, _: Span) -> crate::tt::TokenTree { - self.into() - } -} - -macro_rules! impl_to_to_tokentrees { - ($($span:ident: $ty:ty => $this:ident $im:block;)*) => { - $( - impl ToTokenTree for $ty { - fn to_token($this, $span: Span) -> crate::tt::TokenTree { - let leaf: crate::tt::Leaf = $im.into(); - leaf.into() - } - } - )* - } -} - -impl ToTokenTree for &T { - fn to_token(self, span: Span) -> crate::tt::TokenTree { - self.clone().to_token(span) - } -} - -impl_to_to_tokentrees! { - span: u32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; - span: usize => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; - span: i32 => self { crate::tt::Literal{symbol: Symbol::integer(self as _), span, kind: tt::LitKind::Integer, suffix: None } }; - span: bool => self { crate::tt::Ident{sym: if self { sym::true_.clone() } else { sym::false_.clone() }, span, is_raw: tt::IdentIsRaw::No } }; - _span: crate::tt::Leaf => self { self }; - _span: crate::tt::Literal => self { self }; - _span: crate::tt::Ident => self { self }; - _span: crate::tt::Punct => self { self }; - span: &str => self { crate::tt::Literal{symbol: Symbol::intern(self), span, kind: tt::LitKind::Str, suffix: None }}; - span: String => self { crate::tt::Literal{symbol: Symbol::intern(&self), span, kind: tt::LitKind::Str, suffix: None }}; - span: Name => self { - let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); - crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } - }; - span: Symbol => self { - let (is_raw, s) = IdentIsRaw::split_from_symbol(self.as_str()); - crate::tt::Ident{sym: Symbol::intern(s), span, is_raw } - }; -} - -#[cfg(test)] -mod tests { - use crate::tt; - use ::tt::IdentIsRaw; - use expect_test::expect; - use intern::Symbol; - use span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID}; - use syntax::{TextRange, TextSize}; - - const DUMMY: tt::Span = tt::Span { - range: TextRange::empty(TextSize::new(0)), - anchor: SpanAnchor { - file_id: span::EditionedFileId::new( - span::FileId::from_raw(0xe4e4e), - span::Edition::CURRENT, - ), - ast_id: ROOT_ERASED_FILE_AST_ID, - }, - ctx: SyntaxContextId::ROOT, - }; - - #[test] - fn test_quote_delimiters() { - assert_eq!(quote!(DUMMY =>{}).to_string(), "{}"); - assert_eq!(quote!(DUMMY =>()).to_string(), "()"); - assert_eq!(quote!(DUMMY =>[]).to_string(), "[]"); - } - - #[test] - fn test_quote_idents() { - assert_eq!(quote!(DUMMY =>32).to_string(), "32"); - assert_eq!(quote!(DUMMY =>struct).to_string(), "struct"); - } - - #[test] - fn test_quote_hash_simple_literal() { - let a = 20; - assert_eq!(quote!(DUMMY =>#a).to_string(), "20"); - let s: String = "hello".into(); - assert_eq!(quote!(DUMMY =>#s).to_string(), "\"hello\""); - } - - fn mk_ident(name: &str) -> crate::tt::Ident { - let (is_raw, s) = IdentIsRaw::split_from_symbol(name); - crate::tt::Ident { sym: Symbol::intern(s), span: DUMMY, is_raw } - } - - #[test] - fn test_quote_hash_token_tree() { - let a = mk_ident("hello"); - - let quoted = quote!(DUMMY =>#a); - assert_eq!(quoted.to_string(), "hello"); - let t = format!("{quoted:#?}"); - expect![[r#" - SUBTREE $$ 937550:0@0..0#0 937550:0@0..0#0 - IDENT hello 937550:0@0..0#0"#]] - .assert_eq(&t); - } - - #[test] - fn test_quote_simple_derive_copy() { - let name = mk_ident("Foo"); - - let quoted = quote! {DUMMY => - impl Clone for #name { - fn clone(&self) -> Self { - Self {} - } - } - }; - - assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {}}}"); - } - - #[test] - fn test_quote_derive_copy_hack() { - // Assume the given struct is: - // struct Foo { - // name: String, - // id: u32, - // } - let struct_name = mk_ident("Foo"); - let fields = [mk_ident("name"), mk_ident("id")]; - let fields = fields - .iter() - .flat_map(|it| quote!(DUMMY =>#it: self.#it.clone(), ).token_trees.into_vec()); - - let list = crate::tt::Subtree { - delimiter: crate::tt::Delimiter { - kind: crate::tt::DelimiterKind::Brace, - open: DUMMY, - close: DUMMY, - }, - token_trees: fields.collect(), - }; - - let quoted = quote! {DUMMY => - impl Clone for #struct_name { - fn clone(&self) -> Self { - Self #list - } - } - }; - - assert_eq!(quoted.to_string(), "impl Clone for Foo {fn clone (& self) -> Self {Self {name : self . name . clone () , id : self . id . clone () ,}}}"); - } -} diff --git a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs index 72272934ab7..4bb8c140a1f 100644 --- a/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/diagnostics.rs @@ -6,7 +6,6 @@ pub use hir_ty::diagnostics::{CaseType, IncorrectCase}; use hir_ty::{db::HirDatabase, diagnostics::BodyValidationDiagnostic, InferenceDiagnostic}; -use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use either::Either; pub use hir_def::VariantId; @@ -15,7 +14,7 @@ use hir_expand::{name::Name, HirFileId, InFile}; use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange}; use triomphe::Arc; -use crate::{AssocItem, Field, Local, MacroKind, Trait, Type}; +use crate::{AssocItem, Field, Local, Trait, Type}; macro_rules! diagnostics { ($($diag:ident,)*) => { @@ -90,7 +89,6 @@ diagnostics![ UnresolvedMethodCall, UnresolvedModule, UnresolvedIdent, - UnresolvedProcMacro, UnusedMut, UnusedVariable, ]; @@ -150,23 +148,12 @@ pub struct InactiveCode { pub opts: CfgOptions, } -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct UnresolvedProcMacro { - pub node: InFile, - /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange` - /// to use instead. - pub precise_location: Option, - pub macro_name: Option, - pub kind: MacroKind, - /// The crate id of the proc-macro this macro belongs to, or `None` if the proc-macro can't be found. - pub krate: CrateId, -} - #[derive(Debug, Clone, Eq, PartialEq)] pub struct MacroError { pub node: InFile, pub precise_location: Option, pub message: String, + pub error: bool, } #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 70f4a632fb3..875cf87cb83 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -137,7 +137,7 @@ pub use { hygiene::{marks_rev, SyntaxContextExt}, inert_attr_macro::AttributeTemplate, name::Name, - proc_macro::ProcMacros, + proc_macro::{ProcMacros, ProcMacrosBuilder}, tt, ExpandResult, HirFileId, HirFileIdExt, MacroFileId, MacroFileIdExt, }, hir_ty::{ @@ -833,14 +833,10 @@ fn macro_call_diagnostics( let ValueResult { value: parse_errors, err } = &*e; if let Some(err) = err { let loc = db.lookup_intern_macro_call(macro_call_id); - let (node, precise_location, macro_name, kind) = precise_macro_call_location(&loc.kind, db); - let diag = match err { - &hir_expand::ExpandError::UnresolvedProcMacro(krate) => { - UnresolvedProcMacro { node, precise_location, macro_name, kind, krate }.into() - } - err => MacroError { node, precise_location, message: err.to_string() }.into(), - }; - acc.push(diag); + let (node, precise_location, _macro_name, _kind) = + precise_macro_call_location(&loc.kind, db); + let (message, error) = err.render_to_string(db.upcast()); + acc.push(MacroError { node, precise_location, message, error }.into()); } if !parse_errors.is_empty() { @@ -895,6 +891,19 @@ fn emit_def_diagnostic_( acc.push(UnresolvedExternCrate { decl: InFile::new(ast.file_id, item) }.into()); } + DefDiagnosticKind::MacroError { ast, err } => { + let item = ast.to_ptr(db.upcast()); + let (message, error) = err.render_to_string(db.upcast()); + acc.push( + MacroError { + node: InFile::new(ast.file_id, item.syntax_node_ptr()), + precise_location: None, + message, + error, + } + .into(), + ) + } DefDiagnosticKind::UnresolvedImport { id, index } => { let file_id = id.file_id(); let item_tree = id.item_tree(db.upcast()); @@ -991,13 +1000,6 @@ fn emit_def_diagnostic_( Some(()) })(); } - DefDiagnosticKind::UnresolvedProcMacro { ast, krate } => { - let (node, precise_location, macro_name, kind) = precise_macro_call_location(ast, db); - acc.push( - UnresolvedProcMacro { node, precise_location, macro_name, kind, krate: *krate } - .into(), - ); - } DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { let (node, precise_location, _, _) = precise_macro_call_location(ast, db); acc.push( @@ -1795,20 +1797,17 @@ impl DefWithBody { BodyDiagnostic::InactiveCode { node, cfg, opts } => { InactiveCode { node: *node, cfg: cfg.clone(), opts: opts.clone() }.into() } - BodyDiagnostic::MacroError { node, message } => MacroError { - node: (*node).map(|it| it.into()), - precise_location: None, - message: message.to_string(), - } - .into(), - BodyDiagnostic::UnresolvedProcMacro { node, krate } => UnresolvedProcMacro { - node: (*node).map(|it| it.into()), - precise_location: None, - macro_name: None, - kind: MacroKind::ProcMacro, - krate: *krate, + BodyDiagnostic::MacroError { node, err } => { + let (message, error) = err.render_to_string(db.upcast()); + + MacroError { + node: (*node).map(|it| it.into()), + precise_location: None, + message, + error, + } + .into() } - .into(), BodyDiagnostic::UnresolvedMacroCall { node, path } => UnresolvedMacroCall { macro_call: (*node).map(|ast_ptr| ast_ptr.into()), precise_location: None, diff --git a/src/tools/rust-analyzer/crates/hir/src/semantics.rs b/src/tools/rust-analyzer/crates/hir/src/semantics.rs index c053a659b3f..29f98972dcd 100644 --- a/src/tools/rust-analyzer/crates/hir/src/semantics.rs +++ b/src/tools/rust-analyzer/crates/hir/src/semantics.rs @@ -19,7 +19,7 @@ use hir_def::{ }; use hir_expand::{ attrs::collect_attrs, - builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander}, + builtin::{BuiltinFnLikeExpander, EagerExpander}, db::ExpandDatabase, files::InRealFile, name::AsName, diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs index 2cd6a71c001..702fba448a0 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/macro_error.rs @@ -7,7 +7,10 @@ pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> // Use more accurate position if available. let display_range = ctx.resolve_precise_location(&d.node, d.precise_location); Diagnostic::new( - DiagnosticCode::Ra("macro-error", Severity::Error), + DiagnosticCode::Ra( + "macro-error", + if d.error { Severity::Error } else { Severity::WeakWarning }, + ), d.message.clone(), display_range, ) diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs deleted file mode 100644 index 7ea50c496fb..00000000000 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs +++ /dev/null @@ -1,45 +0,0 @@ -use hir::db::DefDatabase; - -use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity}; - -// Diagnostic: unresolved-proc-macro -// -// This diagnostic is shown when a procedural macro can not be found. This usually means that -// procedural macro support is simply disabled (and hence is only a weak hint instead of an error), -// but can also indicate project setup problems. -// -// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the -// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can -// enable support for procedural macros (see `rust-analyzer.procMacro.attributes.enable`). -pub(crate) fn unresolved_proc_macro( - ctx: &DiagnosticsContext<'_>, - d: &hir::UnresolvedProcMacro, - proc_macros_enabled: bool, - proc_attr_macros_enabled: bool, -) -> Diagnostic { - // Use more accurate position if available. - let display_range = ctx.resolve_precise_location(&d.node, d.precise_location); - - let config_enabled = match d.kind { - hir::MacroKind::Attr => proc_macros_enabled && proc_attr_macros_enabled, - _ => proc_macros_enabled, - }; - - let not_expanded_message = match &d.macro_name { - Some(name) => format!("proc macro `{name}` not expanded"), - None => "proc macro not expanded".to_owned(), - }; - let severity = if config_enabled { Severity::Error } else { Severity::WeakWarning }; - let def_map = ctx.sema.db.crate_def_map(d.krate); - let message = if config_enabled { - def_map.proc_macro_loading_error().unwrap_or("internal error") - } else { - match d.kind { - hir::MacroKind::Attr if proc_macros_enabled => "attribute macro expansion is disabled", - _ => "proc-macro expansion is disabled", - } - }; - let message = format!("{not_expanded_message}: {message}"); - - Diagnostic::new(DiagnosticCode::Ra("unresolved-proc-macro", severity), message, display_range) -} diff --git a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs index 9523cc81b8f..263ab747559 100644 --- a/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-diagnostics/src/lib.rs @@ -62,7 +62,6 @@ mod handlers { pub(crate) mod unresolved_macro_call; pub(crate) mod unresolved_method; pub(crate) mod unresolved_module; - pub(crate) mod unresolved_proc_macro; pub(crate) mod unused_variables; // The handlers below are unusual, the implement the diagnostics as well. @@ -405,7 +404,6 @@ pub fn diagnostics( AnyDiagnostic::UnresolvedMacroCall(d) => handlers::unresolved_macro_call::unresolved_macro_call(&ctx, &d), AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d), AnyDiagnostic::UnresolvedModule(d) => handlers::unresolved_module::unresolved_module(&ctx, &d), - AnyDiagnostic::UnresolvedProcMacro(d) => handlers::unresolved_proc_macro::unresolved_proc_macro(&ctx, &d, config.proc_macros_enabled, config.proc_attr_macros_enabled), AnyDiagnostic::UnusedMut(d) => match handlers::mutability_errors::unused_mut(&ctx, &d) { Some(it) => it, None => continue, diff --git a/src/tools/rust-analyzer/crates/ide/src/lib.rs b/src/tools/rust-analyzer/crates/ide/src/lib.rs index 34a6f154db7..8cb81a9cc45 100644 --- a/src/tools/rust-analyzer/crates/ide/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide/src/lib.rs @@ -43,7 +43,6 @@ mod parent_module; mod references; mod rename; mod runnables; -mod shuffle_crate_graph; mod signature_help; mod ssr; mod static_index; @@ -202,10 +201,6 @@ impl AnalysisHost { pub fn raw_database_mut(&mut self) -> &mut RootDatabase { &mut self.db } - - pub fn shuffle_crate_graph(&mut self) { - shuffle_crate_graph::shuffle_crate_graph(&mut self.db); - } } impl Default for AnalysisHost { diff --git a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs b/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs deleted file mode 100644 index 453d1836e16..00000000000 --- a/src/tools/rust-analyzer/crates/ide/src/shuffle_crate_graph.rs +++ /dev/null @@ -1,58 +0,0 @@ -use hir::{db::ExpandDatabase, ProcMacros}; -use ide_db::{ - base_db::{salsa::Durability, CrateGraph, SourceDatabase}, - FxHashMap, RootDatabase, -}; -use triomphe::Arc; - -// Feature: Shuffle Crate Graph -// -// Randomizes all crate IDs in the crate graph, for debugging. -// -// |=== -// | Editor | Action Name -// -// | VS Code | **rust-analyzer: Shuffle Crate Graph** -// |=== -pub(crate) fn shuffle_crate_graph(db: &mut RootDatabase) { - let crate_graph = db.crate_graph(); - let proc_macros = db.proc_macros(); - - let mut shuffled_ids = crate_graph.iter().collect::>(); - - let mut rng = oorandom::Rand32::new(stdx::rand::seed()); - stdx::rand::shuffle(&mut shuffled_ids, |i| rng.rand_range(0..i as u32) as usize); - - let mut new_graph = CrateGraph::default(); - let mut new_proc_macros = ProcMacros::default(); - - let mut map = FxHashMap::default(); - for old_id in shuffled_ids.iter().copied() { - let data = &crate_graph[old_id]; - let new_id = new_graph.add_crate_root( - data.root_file_id, - data.edition, - data.display_name.clone(), - data.version.clone(), - data.cfg_options.clone(), - data.potential_cfg_options.clone(), - data.env.clone(), - data.is_proc_macro, - data.origin.clone(), - ); - new_proc_macros.insert(new_id, proc_macros[&old_id].clone()); - map.insert(old_id, new_id); - } - - for old_id in shuffled_ids.iter().copied() { - let data = &crate_graph[old_id]; - for dep in &data.dependencies { - let mut new_dep = dep.clone(); - new_dep.crate_id = map[&dep.crate_id]; - new_graph.add_dep(map[&old_id], new_dep).unwrap(); - } - } - - db.set_crate_graph_with_durability(Arc::new(new_graph), Durability::HIGH); - db.set_proc_macros_with_durability(Arc::new(new_proc_macros), Durability::HIGH); -} diff --git a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs index 9ad5c68a559..8737f2246be 100644 --- a/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs +++ b/src/tools/rust-analyzer/crates/load-cargo/src/lib.rs @@ -68,11 +68,14 @@ pub fn load_workspace( let proc_macro_server = match &load_config.with_proc_macro_server { ProcMacroServerChoice::Sysroot => ws .find_sysroot_proc_macro_srv() - .and_then(|it| ProcMacroServer::spawn(&it, extra_env).map_err(Into::into)), + .and_then(|it| ProcMacroServer::spawn(&it, extra_env).map_err(Into::into)) + .map_err(|e| (e, true)), ProcMacroServerChoice::Explicit(path) => { - ProcMacroServer::spawn(path, extra_env).map_err(Into::into) + ProcMacroServer::spawn(path, extra_env).map_err(Into::into).map_err(|e| (e, true)) + } + ProcMacroServerChoice::None => { + Err((anyhow::format_err!("proc macro server disabled"), false)) } - ProcMacroServerChoice::None => Err(anyhow::format_err!("proc macro server disabled")), }; let (crate_graph, proc_macros) = ws.to_crate_graph( @@ -87,7 +90,7 @@ pub fn load_workspace( let proc_macros = { let proc_macro_server = match &proc_macro_server { Ok(it) => Ok(it), - Err(e) => Err(e.to_string()), + Err((e, hard_err)) => Err((e.to_string(), *hard_err)), }; proc_macros .into_iter() @@ -95,7 +98,7 @@ pub fn load_workspace( ( crate_id, path.map_or_else( - |_| Err("proc macro crate is missing dylib".to_owned()), + |e| Err((e, true)), |(_, path)| { proc_macro_server.as_ref().map_err(Clone::clone).and_then( |proc_macro_server| load_proc_macro(proc_macro_server, &path, &[]), @@ -355,8 +358,7 @@ impl SourceRootConfig { } } -/// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace` -/// with an identity dummy expander. +/// Load the proc-macros for the given lib path, disabling all expanders whose names are in `ignored_macros`. pub fn load_proc_macro( server: &ProcMacroServer, path: &AbsPath, @@ -383,7 +385,7 @@ pub fn load_proc_macro( } Err(e) => { tracing::warn!("proc-macro loading for {path} failed: {e}"); - Err(e) + Err((e, true)) } } } diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index bfbdf038357..31d1c77fd07 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -934,7 +934,10 @@ fn project_json_to_crate_graph( if *is_proc_macro { if let Some(path) = proc_macro_dylib_path.clone() { let node = Ok(( - display_name.as_ref().map(|it| it.canonical_name().as_str().to_owned()), + display_name + .as_ref() + .map(|it| it.canonical_name().as_str().to_owned()) + .unwrap_or_else(|| format!("crate{}", idx.0)), path, )); proc_macros.insert(crate_graph_crate_id, node); @@ -1355,8 +1358,8 @@ fn add_target_crate_root( ); if let TargetKind::Lib { is_proc_macro: true } = kind { let proc_macro = match build_data.as_ref().map(|it| it.proc_macro_dylib_path.as_ref()) { - Some(it) => it.cloned().map(|path| Ok((Some(cargo_name.to_owned()), path))), - None => Some(Err("crate has not yet been built".to_owned())), + Some(it) => it.cloned().map(|path| Ok((cargo_name.to_owned(), path))), + None => Some(Err("proc-macro crate is missing its build data".to_owned())), }; if let Some(proc_macro) = proc_macro { proc_macros.insert(crate_id, proc_macro); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs index 44ff273b5ae..f92ed67ae95 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs @@ -134,11 +134,6 @@ pub(crate) fn handle_memory_usage(state: &mut GlobalState, _: ()) -> anyhow::Res Ok(out) } -pub(crate) fn handle_shuffle_crate_graph(state: &mut GlobalState, _: ()) -> anyhow::Result<()> { - state.analysis_host.shuffle_crate_graph(); - Ok(()) -} - pub(crate) fn handle_syntax_tree( snap: GlobalStateSnapshot, params: lsp_ext::SyntaxTreeParams, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs index 813e9fcd476..1fcb636f856 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/ext.rs @@ -75,14 +75,6 @@ impl Request for MemoryUsage { const METHOD: &'static str = "rust-analyzer/memoryUsage"; } -pub enum ShuffleCrateGraph {} - -impl Request for ShuffleCrateGraph { - type Params = (); - type Result = (); - const METHOD: &'static str = "rust-analyzer/shuffleCrateGraph"; -} - pub enum ReloadWorkspace {} impl Request for ReloadWorkspace { diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs index e64095ba9ee..ccc786a21d5 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/main_loop.rs @@ -1018,7 +1018,6 @@ impl GlobalState { .on_sync_mut::(handlers::handle_workspace_reload) .on_sync_mut::(handlers::handle_proc_macros_rebuild) .on_sync_mut::(handlers::handle_memory_usage) - .on_sync_mut::(handlers::handle_shuffle_crate_graph) .on_sync_mut::(handlers::handle_run_test) // Request handlers which are related to the user typing // are run on the main thread to reduce latency: diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index fb16f28a14b..c2463e0ebe2 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -16,8 +16,7 @@ use std::{iter, mem}; use flycheck::{FlycheckConfig, FlycheckHandle}; -use hir::{db::DefDatabase, ChangeWithProcMacros, ProcMacros}; -use ide::CrateId; +use hir::{db::DefDatabase, ChangeWithProcMacros, ProcMacros, ProcMacrosBuilder}; use ide_db::{ base_db::{salsa::Durability, CrateGraph, ProcMacroPaths, Version}, FxHashMap, @@ -371,43 +370,44 @@ impl GlobalState { } }; - let mut res = FxHashMap::default(); + let mut builder = ProcMacrosBuilder::default(); let chain = proc_macro_clients .iter() .map(|res| res.as_ref().map_err(|e| e.to_string())) - .chain(iter::repeat_with(|| Err("Proc macros servers are not running".into()))); + .chain(iter::repeat_with(|| Err("proc-macro-srv is not running".into()))); for (client, paths) in chain.zip(paths) { - res.extend(paths.into_iter().map(move |(crate_id, res)| { - ( - crate_id, - res.map_or_else( - |_| Err("proc macro crate is missing dylib".to_owned()), - |(crate_name, path)| { - progress(path.to_string()); - client.as_ref().map_err(Clone::clone).and_then(|client| { - load_proc_macro( - client, - &path, - crate_name - .as_deref() - .and_then(|crate_name| { - ignored_proc_macros.iter().find_map( - |(name, macros)| { - eq_ignore_underscore(name, crate_name) + paths + .into_iter() + .map(move |(crate_id, res)| { + ( + crate_id, + res.map_or_else( + |e| Err((e, true)), + |(crate_name, path)| { + progress(path.to_string()); + client.as_ref().map_err(|it| (it.clone(), true)).and_then( + |client| { + load_proc_macro( + client, + &path, + ignored_proc_macros + .iter() + .find_map(|(name, macros)| { + eq_ignore_underscore(name, &crate_name) .then_some(&**macros) - }, - ) - }) - .unwrap_or_default(), + }) + .unwrap_or_default(), + ) + }, ) - }) - }, - ), - ) - })); + }, + ), + ) + }) + .for_each(|(krate, res)| builder.insert(krate, res)); } - sender.send(Task::LoadProcMacros(ProcMacroProgress::End(res))).unwrap(); + sender.send(Task::LoadProcMacros(ProcMacroProgress::End(builder.build()))).unwrap(); }); } @@ -667,10 +667,17 @@ impl GlobalState { change.set_proc_macros( crate_graph .iter() - .map(|id| (id, Err("Proc-macros have not been built yet".to_owned()))) + .map(|id| (id, Err(("proc-macro has not been built yet".to_owned(), true)))) .collect(), ); self.fetch_proc_macros_queue.request_op(cause, proc_macro_paths); + } else { + change.set_proc_macros( + crate_graph + .iter() + .map(|id| (id, Err(("proc-macro expansion is disabled".to_owned(), false)))) + .collect(), + ); } change.set_crate_graph(crate_graph); change.set_target_data_layouts(layouts); @@ -809,12 +816,7 @@ pub fn ws_to_crate_graph( workspaces: &[ProjectWorkspace], extra_env: &FxHashMap, mut load: impl FnMut(&AbsPath) -> Option, -) -> ( - CrateGraph, - Vec, AbsPathBuf), String>>>, - Vec, Arc>>, - Vec>, -) { +) -> (CrateGraph, Vec, Vec, Arc>>, Vec>) { let mut crate_graph = CrateGraph::default(); let mut proc_macro_paths = Vec::default(); let mut layouts = Vec::default(); diff --git a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs index c10ff4aede5..e1f40f5da01 100644 --- a/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs +++ b/src/tools/rust-analyzer/crates/test-fixture/src/lib.rs @@ -1,5 +1,5 @@ //! A set of high-level utility fixture methods to use in tests. -use std::{iter, mem, ops::Not, str::FromStr, sync}; +use std::{iter, mem, str::FromStr, sync}; use base_db::{ CrateDisplayName, CrateGraph, CrateId, CrateName, CrateOrigin, Dependency, Env, FileChange, @@ -11,7 +11,7 @@ use hir_expand::{ db::ExpandDatabase, files::FilePosition, proc_macro::{ - ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacros, + ProcMacro, ProcMacroExpander, ProcMacroExpansionError, ProcMacroKind, ProcMacrosBuilder, }, FileRange, }; @@ -303,7 +303,7 @@ impl ChangeFixture { } } - let mut proc_macros = ProcMacros::default(); + let mut proc_macros = ProcMacrosBuilder::default(); if !proc_macro_names.is_empty() { let proc_lib_file = file_id; @@ -354,7 +354,7 @@ impl ChangeFixture { let mut change = ChangeWithProcMacros { source_change, - proc_macros: proc_macros.is_empty().not().then_some(proc_macros), + proc_macros: Some(proc_macros.build()), toolchains: Some(iter::repeat(toolchain).take(crate_graph.len()).collect()), target_data_layouts: Some( iter::repeat(target_data_layout).take(crate_graph.len()).collect(), diff --git a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md index a29b42a857c..e559f88e233 100644 --- a/src/tools/rust-analyzer/docs/dev/lsp-extensions.md +++ b/src/tools/rust-analyzer/docs/dev/lsp-extensions.md @@ -1,5 +1,5 @@