|
2 | 2 | //!
|
3 | 3 | //! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
|
4 | 4 | //! this moment, this is horribly incomplete and handles only `$crate`.
|
| 5 | +use std::sync::Arc; |
| 6 | + |
5 | 7 | use base_db::CrateId;
|
6 | 8 | use either::Either;
|
7 |
| -use syntax::ast; |
| 9 | +use mbe::Origin; |
| 10 | +use parser::SyntaxKind; |
| 11 | +use syntax::{ast, AstNode, SyntaxNode, TextRange, TextSize}; |
8 | 12 |
|
9 | 13 | use crate::{
|
10 |
| - db::AstDatabase, |
| 14 | + db::{self, AstDatabase}, |
11 | 15 | name::{AsName, Name},
|
12 |
| - HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind, |
| 16 | + HirFileId, HirFileIdRepr, InFile, MacroCallId, MacroCallLoc, MacroDefKind, MacroFile, |
13 | 17 | };
|
14 | 18 |
|
15 | 19 | #[derive(Clone, Debug)]
|
16 | 20 | pub struct Hygiene {
|
17 |
| - // This is what `$crate` expands to |
18 |
| - def_crate: Option<CrateId>, |
19 |
| - |
20 |
| - // Indicate this is a local inner macro |
21 |
| - local_inner: bool, |
| 21 | + frames: Option<HygieneFrames>, |
22 | 22 | }
|
23 | 23 |
|
24 | 24 | impl Hygiene {
|
25 | 25 | pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
|
26 |
| - let (def_crate, local_inner) = match file_id.0 { |
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 } |
| 26 | + Hygiene { frames: Some(HygieneFrames::new(db, file_id.clone())) } |
43 | 27 | }
|
44 | 28 |
|
45 | 29 | pub fn new_unhygienic() -> Hygiene {
|
46 |
| - Hygiene { def_crate: None, local_inner: false } |
| 30 | + Hygiene { frames: None } |
47 | 31 | }
|
48 | 32 |
|
49 | 33 | // FIXME: this should just return name
|
50 | 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 | 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 | 43 | Either::Left(name_ref.as_name())
|
57 | 44 | }
|
58 | 45 |
|
59 |
| - pub fn local_inner_macros(&self) -> Option<CrateId> { |
60 |
| - if self.local_inner { |
61 |
| - self.def_crate |
62 |
| - } else { |
63 |
| - None |
| 46 | + pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> { |
| 47 | + let mut token = path.syntax().first_token()?.text_range(); |
| 48 | + let frames = self.frames.as_ref()?; |
| 49 | + let mut current = frames.0.clone(); |
| 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)] |
| 63 | +struct HygieneFrames(Arc<HygieneFrame>); |
| 64 | + |
| 65 | +#[derive(Clone, Debug, Eq, PartialEq)] |
| 66 | +pub 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 | + |
| 77 | +impl 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 => ¤t.def_site, |
| 94 | + Origin::Call => ¤t.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)] |
| 111 | +struct 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 | + |
| 121 | +impl 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 | + |
| 142 | +fn 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 | + |
| 170 | +impl 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 | }
|
0 commit comments