//!
//! 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 syntax::{ast, AstNode};
use crate::{
db::AstDatabase,
name::{AsName, Name},
- HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
+ ExpansionInfo, HirFileId, HirFileIdRepr, MacroCallId, MacroDefKind,
};
#[derive(Clone, Debug)]
pub struct Hygiene {
- // This is what `$crate` expands to
- def_crate: Option<CrateId>,
+ frames: Option<Arc<HygieneFrames>>,
+}
+
+impl Hygiene {
+ pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
+ Hygiene { frames: Some(Arc::new(HygieneFrames::new(db, file_id.clone()))) }
+ }
+
+ pub fn new_unhygienic() -> Hygiene {
+ 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(frames) = &self.frames {
+ if name_ref.text() == "$crate" {
+ if let Some(krate) = frames.root_crate(&name_ref) {
+ return Either::Right(krate);
+ }
+ }
+ }
+
+ Either::Left(name_ref.as_name())
+ }
+
+ pub fn local_inner_macros(&self, path: ast::Path) -> Option<CrateId> {
+ let frames = self.frames.as_ref()?;
+
+ let mut token = path.syntax().first_token()?;
+ let mut current = frames.0.first();
+
+ while let Some((frame, data)) =
+ current.and_then(|it| Some((it, it.expansion.as_ref()?.map_token_up(&token)?)))
+ {
+ let (mapped, origin) = data;
+ if origin == Origin::Def {
+ return if frame.local_inner { frame.krate } else { None };
+ }
+ current = frames.get(frame.call_site?);
+ token = mapped.value;
+ }
+ None
+ }
+}
+
+#[derive(Clone, Debug, Copy)]
+struct HygieneFrameId(usize);
+
+#[derive(Clone, Debug, Default)]
+struct HygieneFrames(Vec<HygieneFrame>);
+
+#[derive(Clone, Debug)]
+struct HygieneFrame {
+ expansion: Option<ExpansionInfo>,
// Indicate this is a local inner macro
local_inner: bool,
+ krate: Option<CrateId>,
+
+ call_site: Option<HygieneFrameId>,
+ def_site: Option<HygieneFrameId>,
}
-impl Hygiene {
- pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
- let (def_crate, local_inner) = match file_id.0 {
+impl HygieneFrames {
+ fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Self {
+ let mut frames = HygieneFrames::default();
+ frames.add(db, file_id);
+ frames
+ }
+
+ fn add(&mut self, db: &dyn AstDatabase, file_id: HirFileId) -> Option<HygieneFrameId> {
+ let (krate, local_inner) = match file_id.0 {
HirFileIdRepr::FileId(_) => (None, false),
HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
+ MacroCallId::EagerMacro(_id) => (None, false),
MacroCallId::LazyMacro(id) => {
let loc = db.lookup_intern_macro(id);
match loc.def.kind {
MacroDefKind::ProcMacro(_) => (None, false),
}
}
- MacroCallId::EagerMacro(_id) => (None, false),
},
};
- Hygiene { def_crate, local_inner }
- }
- pub fn new_unhygienic() -> Hygiene {
- Hygiene { def_crate: None, local_inner: false }
+ let expansion = file_id.expansion_info(db);
+ let expansion = match expansion {
+ None => {
+ let idx = self.0.len();
+ self.0.push(HygieneFrame {
+ expansion: None,
+ local_inner,
+ krate,
+ call_site: None,
+ def_site: None,
+ });
+ return Some(HygieneFrameId(idx));
+ }
+ Some(it) => it,
+ };
+
+ let def_site = expansion.def.clone();
+ let call_site = expansion.arg.file_id;
+
+ let idx = self.0.len();
+ self.0.push(HygieneFrame {
+ expansion: Some(expansion),
+ local_inner,
+ krate,
+ call_site: None,
+ def_site: None,
+ });
+
+ self.0[idx].call_site = self.add(db, call_site);
+ self.0[idx].def_site = def_site.and_then(|it| self.add(db, it.file_id));
+
+ Some(HygieneFrameId(idx))
}
- // 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 name_ref.text() == "$crate" {
- return Either::Right(def_crate);
- }
- }
- Either::Left(name_ref.as_name())
+ fn get(&self, id: HygieneFrameId) -> Option<&HygieneFrame> {
+ self.0.get(id.0)
}
- pub fn local_inner_macros(&self) -> Option<CrateId> {
- if self.local_inner {
- self.def_crate
- } else {
- None
+ fn root_crate(&self, name_ref: &ast::NameRef) -> Option<CrateId> {
+ let mut token = name_ref.syntax().first_token()?;
+ let first = self.0.first()?;
+ let mut result = first.krate;
+ let mut current = Some(first);
+
+ while let Some((frame, (mapped, origin))) =
+ current.and_then(|it| Some((it, it.expansion.as_ref()?.map_token_up(&token)?)))
+ {
+ result = frame.krate;
+
+ let site = match origin {
+ Origin::Def => frame.def_site,
+ Origin::Call => frame.call_site,
+ };
+
+ let site = match site {
+ None => break,
+ Some(it) => it,
+ };
+
+ current = self.get(site);
+ token = mapped.value;
}
+
+ result
}
}
Some(self.expanded.with_value(token))
}
- pub fn map_token_up(
- &self,
- token: InFile<&SyntaxToken>,
- ) -> Option<(InFile<SyntaxToken>, Origin)> {
- let token_id = self.exp_map.token_by_range(token.value.text_range())?;
+ pub fn map_token_up(&self, token: &SyntaxToken) -> Option<(InFile<SyntaxToken>, Origin)> {
+ let token_id = self.exp_map.token_by_range(token.text_range())?;
let (token_id, origin) = self.macro_def.0.map_id_up(token_id);
let (token_map, tt) = match origin {
),
};
- let range = token_map.range_by_token(token_id)?.by_kind(token.value.kind())?;
+ let range = token_map.range_by_token(token_id)?.by_kind(token.kind())?;
let token = algo::find_covering_element(&tt.value, range + tt.value.text_range().start())
.into_token()?;
Some((tt.with_value(token), origin))
expansion: &ExpansionInfo,
token: InFile<SyntaxToken>,
) -> Option<InFile<SyntaxToken>> {
- let (mapped, origin) = expansion.map_token_up(token.as_ref())?;
+ let (mapped, origin) = expansion.map_token_up(&token.value)?;
if origin != Origin::Call {
return None;
}
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);
}
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.
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();
#[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),
}
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");
}