aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_expand/src/hygiene.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_expand/src/hygiene.rs')
-rw-r--r--crates/hir_expand/src/hygiene.rs210
1 files changed, 177 insertions, 33 deletions
diff --git a/crates/hir_expand/src/hygiene.rs b/crates/hir_expand/src/hygiene.rs
index 7ab0a5e52..8db581b77 100644
--- a/crates/hir_expand/src/hygiene.rs
+++ b/crates/hir_expand/src/hygiene.rs
@@ -2,65 +2,209 @@
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 parser::SyntaxKind;
11use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize};
8 12
9use crate::{ 13use crate::{
10 db::AstDatabase, 14 db::{self, AstDatabase},
11 name::{AsName, Name}, 15 name::{AsName, Name},
12 HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind, 16 HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile,
13}; 17};
14 18
15#[derive(Clone, Debug)] 19#[derive(Clone, Debug)]
16pub struct Hygiene { 20pub struct Hygiene {
17 // This is what `$crate` expands to 21 frames: Option<HygieneFrames>,
18 def_crate: Option<CrateId>,
19
20 // Indicate this is a local inner macro
21 local_inner: bool,
22} 22}
23 23
24impl Hygiene { 24impl Hygiene {
25 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene { 25 pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
26 let (def_crate, local_inner) = match file_id.0 { 26 Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) }
27 HirFileIdRepr::FileId(_) => (None, false),
28 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
29 MacroCallId::LazyMacro(id) => {
30 let loc = db.lookup_intern_macro(id);
31 match loc.def.kind {
32 MacroDefKind::Declarative => (Some(loc.def.krate), loc.def.local_inner),
33 MacroDefKind::BuiltIn(_) => (Some(loc.def.krate), false),
34 MacroDefKind::BuiltInDerive(_) => (None, false),
35 MacroDefKind::BuiltInEager(_) => (None, false),
36 MacroDefKind::ProcMacro(_) => (None, false),
37 }
38 }
39 MacroCallId::EagerMacro(_id) => (None, false),
40 },
41 };
42 Hygiene { def_crate, local_inner }
43 } 27 }
44 28
45 pub fn new_unhygienic() -> Hygiene { 29 pub fn new_unhygienic() -> Hygiene {
46 Hygiene { def_crate: None, local_inner: false } 30 Hygiene { frames: None }
47 } 31 }
48 32
49 // FIXME: this should just return name 33 // FIXME: this should just return name
50 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> { 34 pub fn name_ref_to_name(&self, name_ref: ast::NameRef) -> Either<Name, CrateId> {
51 if let Some(def_crate) = self.def_crate { 35 if let Some(frames) = &self.frames {
52 if name_ref.text() == "$crate" { 36 if name_ref.text() == "$crate" {
53 return Either::Right(def_crate); 37 if let Some(krate) = frames.root_crate(name_ref.syntax()) {
38 return Either::Right(krate);
39 }
54 } 40 }
55 } 41 }
42
56 Either::Left(name_ref.as_name()) 43 Either::Left(name_ref.as_name())
57 } 44 }
58 45
59 pub fn local_inner_macros(&self) -> Option<CrateId> { 46 pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
60 if self.local_inner { 47 let mut token = path.syntax().first_token()?.text_range();
61 self.def_crate 48 let frames = self.frames.as_ref()?;
62 } else { 49 let mut current = frames.0.clone();
63 None 50
51 loop {
52 let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(token)?;
53 if origin == Origin::Def {
54 return if current.local_inner { frames.root_crate(path.syntax()) } else { None };
55 }
56 current = current.call_site.as_ref()?.clone();
57 token = mapped.value;
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
63struct HygieneFrames(Arc<HygieneFrame>);
64
65#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct HygieneFrame {
67 expansion: Option<HygieneInfo>,
68
69 // Indicate this is a local inner macro
70 local_inner: bool,
71 krate: Option<CrateId>,
72
73 call_site: Option<Arc<HygieneFrame>>,
74 def_site: Option<Arc<HygieneFrame>>,
75}
76
77impl HygieneFrames {
78 fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
79 HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
80 }
81
82 fn root_crate(&self, node: &SyntaxNode) -> Option<CrateId> {
83 let mut token = node.first_token()?.text_range();
84 let mut result = self.0.krate;
85 let mut current = self.0.clone();
86
87 while let Some((mapped, origin)) =
88 current.expansion.as_ref().and_then(|it| it.map_ident_up(token))
89 {
90 result = current.krate;
91
92 let site = match origin {
93 Origin::Def => &current.def_site,
94 Origin::Call => &current.call_site,
95 };
96
97 let site = match site {
98 None => break,
99 Some(it) => it,
100 };
101
102 current = site.clone();
103 token = mapped.value;
64 } 104 }
105
106 result
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq)]
111struct HygieneInfo {
112 arg_start: InFile<TextSize>,
113 /// The `macro_rules!` arguments.
114 def_start: Option<InFile<TextSize>>,
115
116 macro_def: Arc<(db::TokenExpander, mbe::TokenMap)>,
117 macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
118 exp_map: Arc<mbe::TokenMap>,
119}
120
121impl HygieneInfo {
122 fn map_ident_up(&self, token: TextRange) -> Option<(InFile<TextRange>, Origin)> {
123 let token_id = self.exp_map.token_by_range(token)?;
124
125 let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
126 let (token_map, tt) = match origin {
127 mbe::Origin::Call => (&self.macro_arg.1, self.arg_start),
128 mbe::Origin::Def => (
129 &self.macro_def.1,
130 self.def_start
131 .as_ref()
132 .expect("`Origin::Def` used with non-`macro_rules!` macro")
133 .clone(),
134 ),
135 };
136
137 let range = token_map.range_by_token(token_id)?.by_kind(SyntaxKind::IDENT)?;
138 Some((tt.with_value(range + tt.value), origin))
139 }
140}
141
142fn make_hygiene_info(
143 db: &dyn AstDatabase,
144 macro_file: MacroFile,
145 loc: &MacroCallLoc,
146) -> Option<HygieneInfo> {
147 let arg_tt = loc.kind.arg(db)?;
148
149 let def_offset = loc.def.ast_id.and_then(|id| {
150 let def_tt = match id.to_node(db) {
151 ast::Macro::MacroRules(mac) => mac.token_tree()?.syntax().text_range().start(),
152 ast::Macro::MacroDef(_) => return None,
153 };
154 Some(InFile::new(id.file_id, def_tt))
155 });
156
157 let macro_def = db.macro_def(loc.def)?;
158 let (_, exp_map) = db.parse_macro_expansion(macro_file).value?;
159 let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
160
161 Some(HygieneInfo {
162 arg_start: InFile::new(loc.kind.file_id(), arg_tt.text_range().start()),
163 def_start: def_offset,
164 macro_arg,
165 macro_def,
166 exp_map,
167 })
168}
169
170impl HygieneFrame {
171 pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame {
172 let (info, krate, local_inner) = match file_id.0 {
173 HirFileIdRepr::FileId(_) => (None, None, false),
174 HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
175 MacroCallId::EagerMacro(_id) => (None, None, false),
176 MacroCallId::LazyMacro(id) => {
177 let loc = db.lookup_intern_macro(id);
178 let info = make_hygiene_info(db, macro_file, &loc);
179 match loc.def.kind {
180 MacroDefKind::Declarative => {
181 (info, Some(loc.def.krate), loc.def.local_inner)
182 }
183 MacroDefKind::BuiltIn(_) => (info, Some(loc.def.krate), false),
184 MacroDefKind::BuiltInDerive(_) => (info, None, false),
185 MacroDefKind::BuiltInEager(_) => (info, None, false),
186 MacroDefKind::ProcMacro(_) => (info, None, false),
187 }
188 }
189 },
190 };
191
192 let info = match info {
193 None => {
194 return HygieneFrame {
195 expansion: None,
196 local_inner,
197 krate,
198 call_site: None,
199 def_site: None,
200 };
201 }
202 Some(it) => it,
203 };
204
205 let def_site = info.def_start.map(|it| db.hygiene_frame(it.file_id));
206 let call_site = Some(db.hygiene_frame(info.arg_start.file_id));
207
208 HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
65 } 209 }
66} 210}