Skip to content

Commit fe5340d

Browse files
committed
Introduce HygieneFrames for proper token hyginee
1 parent 51d29fe commit fe5340d

File tree

7 files changed

+182
-52
lines changed

7 files changed

+182
-52
lines changed

crates/hir_def/src/path/lower.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
123123
// We follow what it did anyway :)
124124
if segments.len() == 1 && kind == PathKind::Plain {
125125
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) {
127127
kind = PathKind::DollarCrate(crate_id);
128128
}
129129
}

crates/hir_expand/src/hygiene.rs

Lines changed: 132 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,96 @@
22
//!
33
//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
44
//! this moment, this is horribly incomplete and handles only `$crate`.
5+
use std::sync::Arc;
6+
57
use base_db::CrateId;
68
use either::Either;
7-
use syntax::ast;
9+
use mbe::Origin;
10+
use syntax::{ast, AstNode};
811

912
use crate::{
1013
db::AstDatabase,
1114
name::{AsName, Name},
12-
HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
15+
ExpansionInfo, HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
1316
};
1417

1518
#[derive(Clone, Debug)]
1619
pub struct Hygiene {
17-
// This is what `$crate` expands to
18-
def_crate: Option<CrateId>,
20+
frames: Option<Arc<HygieneFrames>>,
21+
}
22+
23+
impl 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)]
66+
struct HygieneFrameId(usize);
67+
68+
#[derive(Clone, Debug, Default)]
69+
struct HygieneFrames(Vec<HygieneFrame>);
70+
71+
#[derive(Clone, Debug)]
72+
struct HygieneFrame {
73+
expansion: Option<ExpansionInfo>,
1974

2075
// Indicate this is a local inner macro
2176
local_inner: bool,
77+
krate: Option<CrateId>,
78+
79+
call_site: Option<HygieneFrameId>,
80+
def_site: Option<HygieneFrameId>,
2281
}
2382

24-
impl Hygiene {
25-
pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
26-
let (def_crate, local_inner) = match file_id.0 {
83+
impl HygieneFrames {
84+
fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
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 {
2792
HirFileIdRepr::FileId(_) => (None, false),
2893
HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
94+
MacroCallId::EagerMacro(_id) => (None, false),
2995
MacroCallId::LazyMacro(id) => {
3096
let loc = db.lookup_intern_macro(id);
3197
match loc.def.kind {
@@ -36,31 +102,72 @@ impl Hygiene {
36102
MacroDefKind::ProcMacro(_) => (None, false),
37103
}
38104
}
39-
MacroCallId::EagerMacro(_id) => (None, false),
40105
},
41106
};
42-
Hygiene { def_crate, local_inner }
43-
}
44107

45-
pub fn new_unhygienic() -> Hygiene {
46-
Hygiene { def_crate: None, local_inner: false }
108+
let expansion = file_id.expansion_info(db);
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))
47140
}
48141

49-
// FIXME: this should just return name
50-
pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
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())
142+
fn get(&self, id: HygieneFrameId) -> Option<&HygieneFrame> {
143+
self.0.get(id.0)
57144
}
58145

59-
pub fn local_inner_macros(&self) -> Option<CrateId> {
60-
if self.local_inner {
61-
self.def_crate
62-
} else {
63-
None
146+
fn root_crate(&self, name_ref: &ast::NameRef) -> Option<CrateId> {
147+
let mut token = name_ref.syntax().first_token()?;
148+
let first = self.0.first()?;
149+
let mut result = first.krate;
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;
64169
}
170+
171+
result
65172
}
66173
}

crates/hir_expand/src/lib.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -340,11 +340,8 @@ impl ExpansionInfo {
340340
Some(self.expanded.with_value(token))
341341
}
342342

343-
pub fn map_token_up(
344-
&self,
345-
token: InFile<&SyntaxToken>,
346-
) -> Option<(InFile<SyntaxToken>, Origin)> {
347-
let token_id = self.exp_map.token_by_range(token.value.text_range())?;
343+
pub fn map_token_up(&self, token: &SyntaxToken) -> Option<(InFile<SyntaxToken>, Origin)> {
344+
let token_id = self.exp_map.token_by_range(token.text_range())?;
348345

349346
let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
350347
let (token_map, tt) = match origin {
@@ -359,7 +356,7 @@ impl ExpansionInfo {
359356
),
360357
};
361358

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())?;
363360
let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start())
364361
.into_token()?;
365362
Some((tt.with_value(token), origin))
@@ -495,7 +492,7 @@ fn ascend_call_token(
495492
expansion: &ExpansionInfo,
496493
token: InFile<SyntaxToken>,
497494
) -> Option<InFile<SyntaxToken>> {
498-
let (mapped, origin) = expansion.map_token_up(token.as_ref())?;
495+
let (mapped, origin) = expansion.map_token_up(&token.value)?;
499496
if origin != Origin::Call {
500497
return None;
501498
}

crates/hir_ty/src/tests/macros.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,37 @@ expand!();
370370
);
371371
}
372372

373+
#[test]
374+
fn infer_macro_with_dollar_crate_in_def_site() {
375+
check_types(
376+
r#"
377+
//- /main.rs crate:main deps:foo
378+
use foo::expand;
379+
380+
macro_rules! list {
381+
($($tt:tt)*) => { $($tt)* }
382+
}
383+
384+
fn test() {
385+
let r = expand!();
386+
r;
387+
//^ u128
388+
}
389+
390+
//- /lib.rs crate:foo
391+
#[macro_export]
392+
macro_rules! expand {
393+
() => { list!($crate::m!()) };
394+
}
395+
396+
#[macro_export]
397+
macro_rules! m {
398+
() => { 0u128 };
399+
}
400+
"#,
401+
);
402+
}
403+
373404
#[test]
374405
fn infer_type_value_non_legacy_macro_use_as() {
375406
check_infer(

crates/mbe/src/mbe_expander/matcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ fn match_subtree(
150150
res.add_err(err!("leftover tokens"));
151151
}
152152
}
153-
Op::Var { name, kind } => {
153+
Op::Var { name, kind, .. } => {
154154
let kind = match kind {
155155
Some(k) => k,
156156
None => {

crates/mbe/src/mbe_expander/transcriber.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ fn expand_subtree(
100100
err = err.or(e);
101101
arena.push(tt.into());
102102
}
103-
Op::Var { name, .. } => {
104-
let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name);
103+
Op::Var { name, id, .. } => {
104+
let ExpandResult { value: fragment, err: e } = expand_var(ctx, &name, *id);
105105
err = err.or(e);
106106
push_fragment(arena, fragment);
107107
}
@@ -118,12 +118,10 @@ fn expand_subtree(
118118
ExpandResult { value: tt::Subtree { delimiter: template.delimiter, token_trees: tts }, err }
119119
}
120120

121-
fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> ExpandResult<Fragment> {
121+
fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr, id: tt::TokenId) -> ExpandResult<Fragment> {
122122
if v == "crate" {
123123
// We simply produce identifier `$crate` here. And it will be resolved when lowering ast to Path.
124-
let tt =
125-
tt::Leaf::from(tt::Ident { text: "$crate".into(), id: tt::TokenId::unspecified() })
126-
.into();
124+
let tt = tt::Leaf::from(tt::Ident { text: "$crate".into(), id }).into();
127125
ExpandResult::ok(Fragment::Tokens(tt))
128126
} else if !ctx.bindings.contains(v) {
129127
// 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> {
142140
let tt = tt::Subtree {
143141
delimiter: None,
144142
token_trees: vec![
145-
tt::Leaf::from(tt::Punct {
146-
char: '$',
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(),
143+
tt::Leaf::from(tt::Punct { char: '$', spacing: tt::Spacing::Alone, id }).into(),
144+
tt::Leaf::from(tt::Ident { text: v.clone(), id }).into(),
153145
],
154146
}
155147
.into();

crates/mbe/src/parser.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{tt_iter::TtIter, ExpandError, MetaTemplate};
88

99
#[derive(Clone, Debug, PartialEq, Eq)]
1010
pub(crate) enum Op {
11-
Var { name: SmolStr, kind: Option<SmolStr> },
11+
Var { name: SmolStr, kind: Option<SmolStr>, id: tt::TokenId },
1212
Repeat { subtree: MetaTemplate, kind: RepeatKind, separator: Option<Separator> },
1313
Leaf(tt::Leaf),
1414
Subtree(MetaTemplate),
@@ -106,18 +106,21 @@ fn next_op<'a>(first: &tt::TokenTree, src: &mut TtIter<'a>, mode: Mode) -> Resul
106106
}
107107
let name = UNDERSCORE.clone();
108108
let kind = eat_fragment_kind(src, mode)?;
109-
Op::Var { name, kind }
109+
let id = punct.id;
110+
Op::Var { name, kind, id }
110111
}
111112
tt::Leaf::Ident(ident) => {
112113
let name = ident.text.clone();
113114
let kind = eat_fragment_kind(src, mode)?;
114-
Op::Var { name, kind }
115+
let id = ident.id;
116+
Op::Var { name, kind, id }
115117
}
116118
tt::Leaf::Literal(lit) => {
117119
if is_boolean_literal(&lit) {
118120
let name = lit.text.clone();
119121
let kind = eat_fragment_kind(src, mode)?;
120-
Op::Var { name, kind }
122+
let id = lit.id;
123+
Op::Var { name, kind, id }
121124
} else {
122125
bail!("bad var 2");
123126
}

0 commit comments

Comments
 (0)