]> git.lizzy.rs Git - rust.git/commitdiff
Proper handling $crate Take 2
authorEdwin Cheng <edwin0cheng@gmail.com>
Mon, 4 Jan 2021 02:53:31 +0000 (10:53 +0800)
committerEdwin Cheng <edwin0cheng@gmail.com>
Thu, 7 Jan 2021 05:08:32 +0000 (13:08 +0800)
crates/hir/src/db.rs
crates/hir_def/src/path/lower.rs
crates/hir_expand/src/db.rs
crates/hir_expand/src/hygiene.rs
crates/hir_ty/src/tests/macros.rs
crates/ide_db/src/apply_change.rs
crates/mbe/src/mbe_expander/matcher.rs
crates/mbe/src/mbe_expander/transcriber.rs
crates/mbe/src/parser.rs

index d01e1b33d990914e4843bdf2d320f6fa95cb5f28..d5d4cf5b6569bd4d2e2c6b91666c15c69ec07eb0 100644 (file)
@@ -10,8 +10,8 @@
     TypeAliasDataQuery, UnionDataQuery,
 };
 pub use hir_expand::db::{
-    AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery,
-    MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
+    AstDatabase, AstDatabaseStorage, AstIdMapQuery, HygieneFrameQuery, InternEagerExpansionQuery,
+    InternMacroQuery, MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroExpansionQuery,
 };
 pub use hir_ty::db::*;
 
index 8a01e6eead0fa41c9997df1e04398222ceab1736..9518ac109be5f6f4858fecc0839bc6617be9225e 100644 (file)
@@ -123,7 +123,7 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
     // We follow what it did anyway :)
     if segments.len() == 1 && kind == PathKind::Plain {
         if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
-            if let Some(crate_id) = hygiene.local_inner_macros() {
+            if let Some(crate_id) = hygiene.local_inner_macros(path) {
                 kind = PathKind::DollarCrate(crate_id);
             }
         }
index 0a0d021e055381e871162f938dc8739e16242ac7..ab2637b8ca0a50e7aa9d26b48ed39bb35c4c73f4 100644 (file)
@@ -8,9 +8,9 @@
 use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
 
 use crate::{
-    ast_id_map::AstIdMap, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallLoc, EagerMacroId,
-    HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind,
-    MacroFile, ProcMacroExpander,
+    ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander,
+    EagerCallLoc, EagerMacroId, HirFileId, HirFileIdRepr, LazyMacroId, MacroCallId, MacroCallLoc,
+    MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander,
 };
 
 /// Total limit on the number of tokens produced by any macro invocation.
@@ -94,6 +94,8 @@ fn parse_macro_expansion(
     fn intern_eager_expansion(&self, eager: EagerCallLoc) -> EagerMacroId;
 
     fn expand_proc_macro(&self, call: MacroCallId) -> Result<tt::Subtree, mbe::ExpandError>;
+
+    fn hygiene_frame(&self, file_id: HirFileId) -> Arc<HygieneFrame>;
 }
 
 /// This expands the given macro call, but with different arguments. This is
@@ -369,6 +371,10 @@ fn parse_macro_with_arg(
     }
 }
 
+fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
+    Arc::new(HygieneFrame::new(db, file_id))
+}
+
 /// Given a `MacroCallId`, return what `FragmentKind` it belongs to.
 /// FIXME: Not completed
 fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
index 7ab0a5e52eb071751df865f6d9cb4d9448cd8460..8db581b774aad2f9fbc3cf2627f59d8c6fd3b412 100644 (file)
 //!
 //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
 //! this moment, this is horribly incomplete and handles only `$crate`.
+use std::sync::Arc;
+
 use base_db::CrateId;
 use either::Either;
-use syntax::ast;
+use mbe::Origin;
+use parser::SyntaxKind;
+use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize};
 
 use crate::{
-    db::AstDatabase,
+    db::{self, AstDatabase},
     name::{AsName, Name},
-    HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
+    HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile,
 };
 
 #[derive(Clone, Debug)]
 pub struct Hygiene {
-    // This is what `$crate` expands to
-    def_crate: Option<CrateId>,
-
-    // Indicate this is a local inner macro
-    local_inner: bool,
+    frames: Option<HygieneFrames>,
 }
 
 impl Hygiene {
     pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
-        let (def_crate, local_inner) = match file_id.0 {
-            HirFileIdRepr::FileId(_) => (None, false),
-            HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
-                MacroCallId::LazyMacro(id) => {
-                    let loc = db.lookup_intern_macro(id);
-                    match loc.def.kind {
-                        MacroDefKind::Declarative => (Some(loc.def.krate), loc.def.local_inner),
-                        MacroDefKind::BuiltIn(_) => (Some(loc.def.krate), false),
-                        MacroDefKind::BuiltInDerive(_) => (None, false),
-                        MacroDefKind::BuiltInEager(_) => (None, false),
-                        MacroDefKind::ProcMacro(_) => (None, false),
-                    }
-                }
-                MacroCallId::EagerMacro(_id) => (None, false),
-            },
-        };
-        Hygiene { def_crate, local_inner }
+        Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) }
     }
 
     pub fn new_unhygienic() -> Hygiene {
-        Hygiene { def_crate: None, local_inner: false }
+        Hygiene { frames: None }
     }
 
     // FIXME: this should just return name
     pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
-        if let Some(def_crate) = self.def_crate {
+        if let Some(frames) = &self.frames {
             if name_ref.text() == "$crate" {
-                return Either::Right(def_crate);
+                if let Some(krate) = frames.root_crate(name_ref.syntax()) {
+                    return Either::Right(krate);
+                }
             }
         }
+
         Either::Left(name_ref.as_name())
     }
 
-    pub fn local_inner_macros(&self) -> Option<CrateId> {
-        if self.local_inner {
-            self.def_crate
-        } else {
-            None
+    pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
+        let mut token = path.syntax().first_token()?.text_range();
+        let frames = self.frames.as_ref()?;
+        let mut current = frames.0.clone();
+
+        loop {
+            let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(token)?;
+            if origin == Origin::Def {
+                return if current.local_inner { frames.root_crate(path.syntax()) } else { None };
+            }
+            current = current.call_site.as_ref()?.clone();
+            token = mapped.value;
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+struct HygieneFrames(Arc<HygieneFrame>);
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct HygieneFrame {
+    expansion: Option<HygieneInfo>,
+
+    // Indicate this is a local inner macro
+    local_inner: bool,
+    krate: Option<CrateId>,
+
+    call_site: Option<Arc<HygieneFrame>>,
+    def_site: Option<Arc<HygieneFrame>>,
+}
+
+impl HygieneFrames {
+    fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
+        HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
+    }
+
+    fn root_crate(&self, node: &SyntaxNode) -> Option<CrateId> {
+        let mut token = node.first_token()?.text_range();
+        let mut result = self.0.krate;
+        let mut current = self.0.clone();
+
+        while let Some((mapped, origin)) =
+            current.expansion.as_ref().and_then(|it| it.map_ident_up(token))
+        {
+            result = current.krate;
+
+            let site = match origin {
+                Origin::Def => &current.def_site,
+                Origin::Call => &current.call_site,
+            };
+
+            let site = match site {
+                None => break,
+                Some(it) => it,
+            };
+
+            current = site.clone();
+            token = mapped.value;
         }
+
+        result
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+struct HygieneInfo {
+    arg_start: InFile<TextSize>,
+    /// The `macro_rules!` arguments.
+    def_start: Option<InFile<TextSize>>,
+
+    macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
+    macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
+    exp_map: Arc<mbe::TokenMap>,
+}
+
+impl HygieneInfo {
+    fn map_ident_up(&self, token: TextRange) -> Option<(InFile<TextRange>, Origin)> {
+        let token_id = self.exp_map.token_by_range(token)?;
+
+        let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
+        let (token_map, tt) = match origin {
+            mbe::Origin::Call => (&self.macro_arg.1, self.arg_start),
+            mbe::Origin::Def => (
+                &self.macro_def.1,
+                self.def_start
+                    .as_ref()
+                    .expect("`Origin::Def` used with non-`macro_rules!` macro")
+                    .clone(),
+            ),
+        };
+
+        let range = token_map.range_by_token(token_id)?.by_kind(SyntaxKind::IDENT)?;
+        Some((tt.with_value(range + tt.value), origin))
+    }
+}
+
+fn make_hygiene_info(
+    db: &dyn AstDatabase,
+    macro_file: MacroFile,
+    loc: &MacroCallLoc,
+) -> Option<HygieneInfo> {
+    let arg_tt = loc.kind.arg(db)?;
+
+    let def_offset = loc.def.ast_id.and_then(|id| {
+        let def_tt = match id.to_node(db) {
+            ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
+            ast::Macro::MacroDef(_) => return None,
+        };
+        Some(InFile::new(id.file_id, def_tt))
+    });
+
+    let macro_def = db.macro_def(loc.def)?;
+    let (_, exp_map) = db.parse_macro_expansion(macro_file).value?;
+    let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
+
+    Some(HygieneInfo {
+        arg_start: InFile::new(loc.kind.file_id(), arg_tt.text_range().start()),
+        def_start: def_offset,
+        macro_arg,
+        macro_def,
+        exp_map,
+    })
+}
+
+impl HygieneFrame {
+    pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame {
+        let (info, krate, local_inner) = match file_id.0 {
+            HirFileIdRepr::FileId(_) => (None, None, false),
+            HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
+                MacroCallId::EagerMacro(_id) => (None, None, false),
+                MacroCallId::LazyMacro(id) => {
+                    let loc = db.lookup_intern_macro(id);
+                    let info = make_hygiene_info(db, macro_file, &loc);
+                    match loc.def.kind {
+                        MacroDefKind::Declarative => {
+                            (info, Some(loc.def.krate), loc.def.local_inner)
+                        }
+                        MacroDefKind::BuiltIn(_) => (info, Some(loc.def.krate), false),
+                        MacroDefKind::BuiltInDerive(_) => (info, None, false),
+                        MacroDefKind::BuiltInEager(_) => (info, None, false),
+                        MacroDefKind::ProcMacro(_) => (info, None, false),
+                    }
+                }
+            },
+        };
+
+        let info = match info {
+            None => {
+                return HygieneFrame {
+                    expansion: None,
+                    local_inner,
+                    krate,
+                    call_site: None,
+                    def_site: None,
+                };
+            }
+            Some(it) => it,
+        };
+
+        let def_site = info.def_start.map(|it| db.hygiene_frame(it.file_id));
+        let call_site = Some(db.hygiene_frame(info.arg_start.file_id));
+
+        HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
     }
 }
index 1953da7beb453ec5e15c82bdd8675884d063e4e0..c64f0b5b53a8d100c955263baeb8c4e9ae568160 100644 (file)
@@ -370,6 +370,37 @@ fn deref(&self) ->  &Self::Target {
     );
 }
 
+#[test]
+fn infer_macro_with_dollar_crate_in_def_site() {
+    check_types(
+        r#"
+//- /main.rs crate:main deps:foo
+use foo::expand;
+
+macro_rules! list {
+    ($($tt:tt)*) => { $($tt)* }
+}
+
+fn test() {
+    let r = expand!();
+    r;
+  //^ u128
+}
+
+//- /lib.rs crate:foo
+#[macro_export]
+macro_rules! expand {
+    () => { list!($crate::m!()) };
+}
+
+#[macro_export]
+macro_rules! m {
+    () => { 0u128 };
+}
+"#,
+    );
+}
+
 #[test]
 fn infer_type_value_non_legacy_macro_use_as() {
     check_infer(
index 71c19ed138ba3edcbcc8d0da6efdb146b982735a..c770a236b0906f85e9012ffd282a61b52042c056 100644 (file)
@@ -145,6 +145,7 @@ macro_rules! sweep_each_query {
             hir::db::MacroDefQuery
             hir::db::ParseMacroExpansionQuery
             hir::db::MacroExpandQuery
+            hir::db::HygieneFrameQuery
 
             // DefDatabase
             hir::db::ItemTreeQuery
index ab5f87c487ce6b910fdff0eb7ac6012340a7803f..385b4660187f8f29a369cac5c1d2dc146e8ed41f 100644 (file)
@@ -150,7 +150,7 @@ fn match_subtree(
                     res.add_err(err!("leftover tokens"));
                 }
             }
-            Op::Var { name, kind } => {
+            Op::Var { name, kind, .. } => {
                 let kind = match kind {
                     Some(k) => k,
                     None => {
index 7205312371d16686392dac48c7921e0d75b52258..57f3f104dcaa77707c5be1f53bc63299086cf1aa 100644 (file)
@@ -100,8 +100,8 @@ fn expand_subtree(
                 err = err.or(e);
                 arena.push(tt.into());
             }
-            Op::Var { name, .. } => {
-                let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name);
+            Op::Var { name, id, .. } => {
+                let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name, *id);
                 err = err.or(e);
                 push_fragment(arena, fragment);
             }
@@ -118,12 +118,10 @@ fn expand_subtree(
     ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err }
 }
 
-fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> {
+fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
     if v == "crate" {
         // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
-        let tt =
-            tt::Leaf::from(tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() })
-                .into();
+        let tt = tt::Leaf::from(tt::Ident { text: "$crate".into(), id }).into();
         ExpandResult::ok(Fragment::Tokens(tt))
     } else if !ctx.bindings.contains(v) {
         // Note that it is possible to have a `$var` inside a macro which is not bound.
@@ -142,14 +140,8 @@ fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> {
         let tt = tt::Subtree {
             delimiter: None,
             token_trees: vec![
-                tt::Leaf::from(tt::Punct {
-                    char: '$',
-                    spacing: tt::Spacing::Alone,
-                    id: tt::TokenId::unspecified(),
-                })
-                .into(),
-                tt::Leaf::from(tt::Ident { text: v.clone(), id: tt::TokenId::unspecified() })
-                    .into(),
+                tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
+                tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
             ],
         }
         .into();
index 2f3ebc831397675f95741762ac11eaa3280acb50..77cc739b65707d6a3e09df2d955bb361df466451 100644 (file)
@@ -8,7 +8,7 @@
 
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub(crate) enum Op {
-    Var { name: SmolStr, kind: Option<SmolStr> },
+    Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
     Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
     Leaf(tt::Leaf),
     Subtree(MetaTemplate),
@@ -106,18 +106,21 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
                         }
                         let name = UNDERSCORE.clone();
                         let kind = eat_fragment_kind(src, mode)?;
-                        Op::Var { name, kind }
+                        let id = punct.id;
+                        Op::Var { name, kind, id }
                     }
                     tt::Leaf::Ident(ident) => {
                         let name = ident.text.clone();
                         let kind = eat_fragment_kind(src, mode)?;
-                        Op::Var { name, kind }
+                        let id = ident.id;
+                        Op::Var { name, kind, id }
                     }
                     tt::Leaf::Literal(lit) => {
                         if is_boolean_literal(&lit) {
                             let name = lit.text.clone();
                             let kind = eat_fragment_kind(src, mode)?;
-                            Op::Var { name, kind }
+                            let id = lit.id;
+                            Op::Var { name, kind, id }
                         } else {
                             bail!("bad var 2");
                         }