aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdwin Cheng <[email protected]>2021-01-02 12:25:05 +0000
committerEdwin Cheng <[email protected]>2021-01-02 12:39:57 +0000
commitfe5340d970b764175ebf8b8c32c971fa04c49efb (patch)
treea6409d67ffee8f5c013ca587db39c371cd0c8ffb
parent51d29fe55456e6e7af69d23982aa57c7fcf91e81 (diff)
Introduce HygieneFrames for proper token hyginee
-rw-r--r--crates/hir_def/src/path/lower.rs2
-rw-r--r--crates/hir_expand/src/hygiene.rs157
-rw-r--r--crates/hir_expand/src/lib.rs11
-rw-r--r--crates/hir_ty/src/tests/macros.rs31
-rw-r--r--crates/mbe/src/mbe_expander/matcher.rs2
-rw-r--r--crates/mbe/src/mbe_expander/transcriber.rs20
-rw-r--r--crates/mbe/src/parser.rs11
7 files changed, 182 insertions, 52 deletions
diff --git a/crates/hir_def/src/path/lower.rs b/crates/hir_def/src/path/lower.rs
index 8a01e6eea..9518ac109 100644
--- a/crates/hir_def/src/path/lower.rs
+++ b/crates/hir_def/src/path/lower.rs
@@ -123,7 +123,7 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
123 // We follow what it did anyway :) 123 // We follow what it did anyway :)
124 if segments.len() == 1 && kind == PathKind::Plain { 124 if segments.len() == 1 && kind == PathKind::Plain {
125 if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) { 125 if let Some(_macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
126 if let Some(crate_id) = hygiene.local_inner_macros() { 126 if let Some(crate_id) = hygiene.local_inner_macros(path) {
127 kind = PathKind::DollarCrate(crate_id); 127 kind = PathKind::DollarCrate(crate_id);
128 } 128 }
129 } 129 }
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs
index 7ab0a5e52..d49a67195 100644
--- a/crates/hir_expand/src/hygiene.rs
+++ b/crates/hir_expand/src/hygiene.rs
@@ -2,30 +2,96 @@
2//! 2//!
3//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at 3//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
4//! this moment, this is horribly incomplete and handles only `$crate`. 4//! this moment, this is horribly incomplete and handles only `$crate`.
5use std::sync::Arc;
6
5use base_db::CrateId; 7use base_db::CrateId;
6use either::Either; 8use either::Either;
7use syntax::ast; 9use mbe::Origin;
10use syntax::{ast, AstNode};
8 11
9use crate::{ 12use crate::{
10 db::AstDatabase, 13 db::AstDatabase,
11 name::{AsName, Name}, 14 name::{AsName, Name},
12 HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind, 15 ExpansionInfo, HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
13}; 16};
14 17
15#[derive(Clone, Debug)] 18#[derive(Clone, Debug)]
16pub struct Hygiene { 19pub struct Hygiene {
17 // This is what `$crate` expands to 20 frames: Option<Arc<HygieneFrames>>,
18 def_crate: Option<CrateId>, 21}
22
23impl Hygiene {
24 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
25 Hygiene { frames: Some(Arc::new(HygieneFrames::new(db, file_id.clone()))) }
26 }
27
28 pub fn new_unhygienic() -> Hygiene {
29 Hygiene { frames: None }
30 }
31
32 // FIXME: this should just return name
33 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
34 if let Some(frames) = &self.frames {
35 if name_ref.text() == "$crate" {
36 if let Some(krate) = frames.root_crate(&name_ref) {
37 return Either::Right(krate);
38 }
39 }
40 }
41
42 Either::Left(name_ref.as_name())
43 }
44
45 pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
46 let frames = self.frames.as_ref()?;
47
48 let mut token = path.syntax().first_token()?;
49 let mut current = frames.0.first();
50
51 while let Some((frame, data)) =
52 current.and_then(|it| Some((it, it.expansion.as_ref()?.map_token_up(&token)?)))
53 {
54 let (mapped, origin) = data;
55 if origin == Origin::Def {
56 return if frame.local_inner { frame.krate } else { None };
57 }
58 current = frames.get(frame.call_site?);
59 token = mapped.value;
60 }
61 None
62 }
63}
64
65#[derive(Clone, Debug, Copy)]
66struct HygieneFrameId(usize);
67
68#[derive(Clone, Debug, Default)]
69struct HygieneFrames(Vec<HygieneFrame>);
70
71#[derive(Clone, Debug)]
72struct HygieneFrame {
73 expansion: Option<ExpansionInfo>,
19 74
20 // Indicate this is a local inner macro 75 // Indicate this is a local inner macro
21 local_inner: bool, 76 local_inner: bool,
77 krate: Option<CrateId>,
78
79 call_site: Option<HygieneFrameId>,
80 def_site: Option<HygieneFrameId>,
22} 81}
23 82
24impl Hygiene { 83impl HygieneFrames {
25 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { 84 fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
26 let (def_crate, local_inner) = match file_id.0 { 85 let mut frames = HygieneFrames::default();
86 frames.add(db, file_id);
87 frames
88 }
89
90 fn add(&mut self, db: &dyn AstDatabase, file_id: HirFileId) -> Option<HygieneFrameId> {
91 let (krate, local_inner) = match file_id.0 {
27 HirFileIdRepr::FileId(_) => (None, false), 92 HirFileIdRepr::FileId(_) => (None, false),
28 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id { 93 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
94 MacroCallId::EagerMacro(_id) => (None, false),
29 MacroCallId::LazyMacro(id) => { 95 MacroCallId::LazyMacro(id) => {
30 let loc = db.lookup_intern_macro(id); 96 let loc = db.lookup_intern_macro(id);
31 match loc.def.kind { 97 match loc.def.kind {
@@ -36,31 +102,72 @@ impl Hygiene {
36 MacroDefKind::ProcMacro(_) => (None, false), 102 MacroDefKind::ProcMacro(_) => (None, false),
37 } 103 }
38 } 104 }
39 MacroCallId::EagerMacro(_id) => (None, false),
40 }, 105 },
41 }; 106 };
42 Hygiene { def_crate, local_inner }
43 }
44 107
45 pub fn new_unhygienic() -> Hygiene { 108 let expansion = file_id.expansion_info(db);
46 Hygiene { def_crate: None, local_inner: false } 109 let expansion = match expansion {
110 None => {
111 let idx = self.0.len();
112 self.0.push(HygieneFrame {
113 expansion: None,
114 local_inner,
115 krate,
116 call_site: None,
117 def_site: None,
118 });
119 return Some(HygieneFrameId(idx));
120 }
121 Some(it) => it,
122 };
123
124 let def_site = expansion.def.clone();
125 let call_site = expansion.arg.file_id;
126
127 let idx = self.0.len();
128 self.0.push(HygieneFrame {
129 expansion: Some(expansion),
130 local_inner,
131 krate,
132 call_site: None,
133 def_site: None,
134 });
135
136 self.0[idx].call_site = self.add(db, call_site);
137 self.0[idx].def_site = def_site.and_then(|it| self.add(db, it.file_id));
138
139 Some(HygieneFrameId(idx))
47 } 140 }
48 141
49 // FIXME: this should just return name 142 fn get(&self, id: HygieneFrameId) -> Option<&HygieneFrame> {
50 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> { 143 self.0.get(id.0)
51 if let Some(def_crate) = self.def_crate {
52 if name_ref.text() == "$crate" {
53 return Either::Right(def_crate);
54 }
55 }
56 Either::Left(name_ref.as_name())
57 } 144 }
58 145
59 pub fn local_inner_macros(&self) -> Option<CrateId> { 146 fn root_crate(&self, name_ref: &ast::NameRef) -> Option<CrateId> {
60 if self.local_inner { 147 let mut token = name_ref.syntax().first_token()?;
61 self.def_crate 148 let first = self.0.first()?;
62 } else { 149 let mut result = first.krate;
63 None 150 let mut current = Some(first);
151
152 while let Some((frame, (mapped, origin))) =
153 current.and_then(|it| Some((it, it.expansion.as_ref()?.map_token_up(&token)?)))
154 {
155 result = frame.krate;
156
157 let site = match origin {
158 Origin::Def => frame.def_site,
159 Origin::Call => frame.call_site,
160 };
161
162 let site = match site {
163 None => break,
164 Some(it) => it,
165 };
166
167 current = self.get(site);
168 token = mapped.value;
64 } 169 }
170
171 result
65 } 172 }
66} 173}
diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs
index 3fa1b1d77..5b6734a5f 100644
--- a/crates/hir_expand/src/lib.rs
+++ b/crates/hir_expand/src/lib.rs
@@ -340,11 +340,8 @@ impl ExpansionInfo {
340 Some(self.expanded.with_value(token)) 340 Some(self.expanded.with_value(token))
341 } 341 }
342 342
343 pub fn map_token_up( 343 pub fn map_token_up(&self, token: &SyntaxToken) -> Option<(InFile<SyntaxToken>, Origin)> {
344 &self, 344 let token_id = self.exp_map.token_by_range(token.text_range())?;
345 token: InFile<&SyntaxToken>,
346 ) -> Option<(InFile<SyntaxToken>, Origin)> {
347 let token_id = self.exp_map.token_by_range(token.value.text_range())?;
348 345
349 let (token_id, origin) = self.macro_def.0.map_id_up(token_id); 346 let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
350 let (token_map, tt) = match origin { 347 let (token_map, tt) = match origin {
@@ -359,7 +356,7 @@ impl ExpansionInfo {
359 ), 356 ),
360 }; 357 };
361 358
362 let range = token_map.range_by_token(token_id)?.by_kind(token.value.kind())?; 359 let range = token_map.range_by_token(token_id)?.by_kind(token.kind())?;
363 let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start()) 360 let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start())
364 .into_token()?; 361 .into_token()?;
365 Some((tt.with_value(token), origin)) 362 Some((tt.with_value(token), origin))
@@ -495,7 +492,7 @@ fn ascend_call_token(
495 expansion: &ExpansionInfo, 492 expansion: &ExpansionInfo,
496 token: InFile<SyntaxToken>, 493 token: InFile<SyntaxToken>,
497) -> Option<InFile<SyntaxToken>> { 494) -> Option<InFile<SyntaxToken>> {
498 let (mapped, origin) = expansion.map_token_up(token.as_ref())?; 495 let (mapped, origin) = expansion.map_token_up(&token.value)?;
499 if origin != Origin::Call { 496 if origin != Origin::Call {
500 return None; 497 return None;
501 } 498 }
diff --git a/crates/hir_ty/src/tests/macros.rs b/crates/hir_ty/src/tests/macros.rs
index a7656b864..23b79abc4 100644
--- a/crates/hir_ty/src/tests/macros.rs
+++ b/crates/hir_ty/src/tests/macros.rs
@@ -371,6 +371,37 @@ expand!();
371} 371}
372 372
373#[test] 373#[test]
374fn infer_macro_with_dollar_crate_in_def_site() {
375 check_types(
376 r#"
377//- /main.rs crate:main deps:foo
378use foo::expand;
379
380macro_rules! list {
381 ($($tt:tt)*) => { $($tt)* }
382}
383
384fn test() {
385 let r = expand!();
386 r;
387 //^ u128
388}
389
390//- /lib.rs crate:foo
391#[macro_export]
392macro_rules! expand {
393 () => { list!($crate::m!()) };
394}
395
396#[macro_export]
397macro_rules! m {
398 () => { 0u128 };
399}
400"#,
401 );
402}
403
404#[test]
374fn infer_type_value_non_legacy_macro_use_as() { 405fn infer_type_value_non_legacy_macro_use_as() {
375 check_infer( 406 check_infer(
376 r#" 407 r#"
diff --git a/crates/mbe/src/mbe_expander/matcher.rs b/crates/mbe/src/mbe_expander/matcher.rs
index ab5f87c48..385b46601 100644
--- a/crates/mbe/src/mbe_expander/matcher.rs
+++ b/crates/mbe/src/mbe_expander/matcher.rs
@@ -150,7 +150,7 @@ fn match_subtree(
150 res.add_err(err!("leftover tokens")); 150 res.add_err(err!("leftover tokens"));
151 } 151 }
152 } 152 }
153 Op::Var { name, kind } => { 153 Op::Var { name, kind, .. } => {
154 let kind = match kind { 154 let kind = match kind {
155 Some(k) => k, 155 Some(k) => k,
156 None => { 156 None => {
diff --git a/crates/mbe/src/mbe_expander/transcriber.rs b/crates/mbe/src/mbe_expander/transcriber.rs
index 720531237..57f3f104d 100644
--- a/crates/mbe/src/mbe_expander/transcriber.rs
+++ b/crates/mbe/src/mbe_expander/transcriber.rs
@@ -100,8 +100,8 @@ fn expand_subtree(
100 err = err.or(e); 100 err = err.or(e);
101 arena.push(tt.into()); 101 arena.push(tt.into());
102 } 102 }
103 Op::Var { name, .. } => { 103 Op::Var { name, id, .. } => {
104 let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name); 104 let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name, *id);
105 err = err.or(e); 105 err = err.or(e);
106 push_fragment(arena, fragment); 106 push_fragment(arena, fragment);
107 } 107 }
@@ -118,12 +118,10 @@ fn expand_subtree(
118 ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err } 118 ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err }
119} 119}
120 120
121fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> { 121fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
122 if v == "crate" { 122 if v == "crate" {
123 // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path. 123 // We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
124 let tt = 124 let tt = tt::Leaf::from(tt::Ident { text: "$crate".into(), id }).into();
125 tt::Leaf::from(tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() })
126 .into();
127 ExpandResult::ok(Fragment::Tokens(tt)) 125 ExpandResult::ok(Fragment::Tokens(tt))
128 } else if !ctx.bindings.contains(v) { 126 } else if !ctx.bindings.contains(v) {
129 // Note that it is possible to have a `$var` inside a macro which is not bound. 127 // 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> {
142 let tt = tt::Subtree { 140 let tt = tt::Subtree {
143 delimiter: None, 141 delimiter: None,
144 token_trees: vec![ 142 token_trees: vec![
145 tt::Leaf::from(tt::Punct { 143 tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
146 char: '$', 144 tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
147 spacing: tt::Spacing::Alone,
148 id: tt::TokenId::unspecified(),
149 })
150 .into(),
151 tt::Leaf::from(tt::Ident { text: v.clone(), id: tt::TokenId::unspecified() })
152 .into(),
153 ], 145 ],
154 } 146 }
155 .into(); 147 .into();
diff --git a/crates/mbe/src/parser.rs b/crates/mbe/src/parser.rs
index 2f3ebc831..77cc739b6 100644
--- a/crates/mbe/src/parser.rs
+++ b/crates/mbe/src/parser.rs
@@ -8,7 +8,7 @@ use crate::{tt_iter::TtIter, ExpandError, MetaTemplate};
8 8
9#[derive(Clone, Debug, PartialEq, Eq)] 9#[derive(Clone, Debug, PartialEq, Eq)]
10pub(crate) enum Op { 10pub(crate) enum Op {
11 Var { name: SmolStr, kind: Option<SmolStr> }, 11 Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
12 Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> }, 12 Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
13 Leaf(tt::Leaf), 13 Leaf(tt::Leaf),
14 Subtree(MetaTemplate), 14 Subtree(MetaTemplate),
@@ -106,18 +106,21 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
106 } 106 }
107 let name = UNDERSCORE.clone(); 107 let name = UNDERSCORE.clone();
108 let kind = eat_fragment_kind(src, mode)?; 108 let kind = eat_fragment_kind(src, mode)?;
109 Op::Var { name, kind } 109 let id = punct.id;
110 Op::Var { name, kind, id }
110 } 111 }
111 tt::Leaf::Ident(ident) => { 112 tt::Leaf::Ident(ident) => {
112 let name = ident.text.clone(); 113 let name = ident.text.clone();
113 let kind = eat_fragment_kind(src, mode)?; 114 let kind = eat_fragment_kind(src, mode)?;
114 Op::Var { name, kind } 115 let id = ident.id;
116 Op::Var { name, kind, id }
115 } 117 }
116 tt::Leaf::Literal(lit) => { 118 tt::Leaf::Literal(lit) => {
117 if is_boolean_literal(&lit) { 119 if is_boolean_literal(&lit) {
118 let name = lit.text.clone(); 120 let name = lit.text.clone();
119 let kind = eat_fragment_kind(src, mode)?; 121 let kind = eat_fragment_kind(src, mode)?;
120 Op::Var { name, kind } 122 let id = lit.id;
123 Op::Var { name, kind, id }
121 } else { 124 } else {
122 bail!("bad var 2"); 125 bail!("bad var 2");
123 } 126 }