mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 14:51:48 +00:00
Proper span representation with syntax context
This commit is contained in:
parent
890eb17b4e
commit
e36b3f7b8c
16 changed files with 414 additions and 470 deletions
|
@ -1,7 +1,7 @@
|
|||
//! A higher level attributes based on TokenTree, with also some shortcuts.
|
||||
use std::{fmt, ops};
|
||||
|
||||
use ::tt::Span;
|
||||
use ::tt::SpanAnchor as _;
|
||||
use base_db::{span::SpanAnchor, CrateId};
|
||||
use cfg::CfgExpr;
|
||||
use either::Either;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Builtin macro
|
||||
|
||||
use ::tt::Span;
|
||||
use base_db::{
|
||||
span::{SpanAnchor, ROOT_ERASED_FILE_AST_ID},
|
||||
AnchoredPath, Edition, FileId,
|
||||
|
@ -15,7 +16,7 @@ use syntax::{
|
|||
use crate::{
|
||||
db::ExpandDatabase,
|
||||
name, quote,
|
||||
tt::{self, Span},
|
||||
tt::{self},
|
||||
EagerCallInfo, ExpandError, ExpandResult, HirFileIdExt, MacroCallId, MacroCallLoc,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
//! Defines database & queries for macro expansion.
|
||||
|
||||
use ::tt::SyntaxContext;
|
||||
use base_db::{
|
||||
salsa,
|
||||
span::{SpanAnchor, ROOT_ERASED_FILE_AST_ID},
|
||||
span::{SpanAnchor, SyntaxContextId, ROOT_ERASED_FILE_AST_ID},
|
||||
CrateId, Edition, SourceDatabase,
|
||||
};
|
||||
use either::Either;
|
||||
|
@ -15,11 +16,13 @@ use syntax::{
|
|||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
ast_id_map::AstIdMap, builtin_attr_macro::pseudo_derive_attr_expansion,
|
||||
builtin_fn_macro::EagerExpander, hygiene::HygieneFrame, tt, AstId, BuiltinAttrExpander,
|
||||
BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo, ExpandError, ExpandResult,
|
||||
ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind, MacroCallLoc, MacroDefId,
|
||||
MacroDefKind, MacroFile, ProcMacroExpander, SpanMap, SyntaxContext, SyntaxContextId,
|
||||
ast_id_map::AstIdMap,
|
||||
builtin_attr_macro::pseudo_derive_attr_expansion,
|
||||
builtin_fn_macro::EagerExpander,
|
||||
hygiene::{self, HygieneFrame, SyntaxContextData},
|
||||
tt, AstId, BuiltinAttrExpander, BuiltinDeriveExpander, BuiltinFnLikeExpander, EagerCallInfo,
|
||||
ExpandError, ExpandResult, ExpandTo, HirFileId, HirFileIdRepr, MacroCallId, MacroCallKind,
|
||||
MacroCallLoc, MacroDefId, MacroDefKind, MacroFile, ProcMacroExpander, SpanMap,
|
||||
};
|
||||
|
||||
/// Total limit on the number of tokens produced by any macro invocation.
|
||||
|
@ -89,7 +92,15 @@ pub trait ExpandDatabase: SourceDatabase {
|
|||
#[salsa::interned]
|
||||
fn intern_macro_call(&self, macro_call: MacroCallLoc) -> MacroCallId;
|
||||
#[salsa::interned]
|
||||
fn intern_syntax_context(&self, ctx: SyntaxContext) -> SyntaxContextId;
|
||||
fn intern_syntax_context(&self, ctx: SyntaxContextData) -> SyntaxContextId;
|
||||
#[salsa::transparent]
|
||||
#[salsa::invoke(hygiene::apply_mark)]
|
||||
fn apply_mark(
|
||||
&self,
|
||||
ctxt: SyntaxContextData,
|
||||
file_id: HirFileId,
|
||||
transparency: hygiene::Transparency,
|
||||
) -> SyntaxContextId;
|
||||
|
||||
/// Lowers syntactic macro call to a token tree representation. That's a firewall
|
||||
/// query, only typing in the macro call itself changes the returned
|
||||
|
@ -225,6 +236,7 @@ pub fn expand_speculative(
|
|||
.ranges_with_span(tt::SpanData {
|
||||
range: token_to_map.text_range(),
|
||||
anchor: SpanAnchor { file_id, ast_id: ROOT_ERASED_FILE_AST_ID },
|
||||
ctx: SyntaxContextId::DUMMY,
|
||||
})
|
||||
.filter_map(|range| syntax_node.covering_element(range).into_token())
|
||||
.min_by_key(|t| {
|
||||
|
|
|
@ -2,71 +2,92 @@
|
|||
//!
|
||||
//! Specifically, `ast` + `Hygiene` allows you to create a `Name`. Note that, at
|
||||
//! this moment, this is horribly incomplete and handles only `$crate`.
|
||||
use base_db::CrateId;
|
||||
use db::TokenExpander;
|
||||
use base_db::{span::SyntaxContextId, CrateId};
|
||||
use either::Either;
|
||||
use syntax::{
|
||||
ast::{self, HasDocComments},
|
||||
AstNode, SyntaxNode, TextRange, TextSize,
|
||||
ast::{self},
|
||||
TextRange,
|
||||
};
|
||||
use triomphe::Arc;
|
||||
|
||||
use crate::{
|
||||
db::{self, ExpandDatabase},
|
||||
db::ExpandDatabase,
|
||||
name::{AsName, Name},
|
||||
HirFileId, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile, SpanMap,
|
||||
HirFileId, InFile,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Hygiene {
|
||||
frames: Option<HygieneFrames>,
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct SyntaxContextData {
|
||||
// FIXME: This might only need to be Option<MacroCallId>?
|
||||
outer_expn: HirFileId,
|
||||
outer_transparency: Transparency,
|
||||
parent: SyntaxContextId,
|
||||
/// This context, but with all transparent and semi-transparent expansions filtered away.
|
||||
opaque: SyntaxContextId,
|
||||
/// This context, but with all transparent expansions filtered away.
|
||||
opaque_and_semitransparent: SyntaxContextId,
|
||||
/// Name of the crate to which `$crate` with this context would resolve.
|
||||
dollar_crate_name: Name,
|
||||
}
|
||||
|
||||
/// A property of a macro expansion that determines how identifiers
|
||||
/// produced by that expansion are resolved.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
|
||||
pub enum Transparency {
|
||||
/// Identifier produced by a transparent expansion is always resolved at call-site.
|
||||
/// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
|
||||
Transparent,
|
||||
/// Identifier produced by a semi-transparent expansion may be resolved
|
||||
/// either at call-site or at definition-site.
|
||||
/// If it's a local variable, label or `$crate` then it's resolved at def-site.
|
||||
/// Otherwise it's resolved at call-site.
|
||||
/// `macro_rules` macros behave like this, built-in macros currently behave like this too,
|
||||
/// but that's an implementation detail.
|
||||
SemiTransparent,
|
||||
/// Identifier produced by an opaque expansion is always resolved at definition-site.
|
||||
/// Def-site spans in procedural macros, identifiers from `macro` by default use this.
|
||||
Opaque,
|
||||
}
|
||||
|
||||
pub(super) fn apply_mark(
|
||||
_db: &dyn ExpandDatabase,
|
||||
_ctxt: SyntaxContextData,
|
||||
_file_id: HirFileId,
|
||||
_transparency: Transparency,
|
||||
) -> SyntaxContextId {
|
||||
_db.intern_syntax_context(_ctxt)
|
||||
}
|
||||
|
||||
// pub(super) fn with_ctxt_from_mark(db: &ExpandDatabase, file_id: HirFileId) {
|
||||
// self.with_ctxt_from_mark(expn_id, Transparency::Transparent)
|
||||
// }
|
||||
// pub(super) fn with_call_site_ctxt(db: &ExpandDatabase, file_id: HirFileId) {
|
||||
// self.with_ctxt_from_mark(expn_id, Transparency::Transparent)
|
||||
// }
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Hygiene {}
|
||||
|
||||
impl Hygiene {
|
||||
pub fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Hygiene {
|
||||
Hygiene { frames: Some(HygieneFrames::new(db, file_id)) }
|
||||
pub fn new(_: &dyn ExpandDatabase, _: HirFileId) -> Hygiene {
|
||||
Hygiene {}
|
||||
}
|
||||
|
||||
pub fn new_unhygienic() -> Hygiene {
|
||||
Hygiene { frames: None }
|
||||
Hygiene {}
|
||||
}
|
||||
|
||||
// FIXME: this should just return name
|
||||
pub fn name_ref_to_name(
|
||||
&self,
|
||||
db: &dyn ExpandDatabase,
|
||||
_: &dyn ExpandDatabase,
|
||||
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(db, name_ref.syntax()) {
|
||||
return Either::Right(krate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Either::Left(name_ref.as_name())
|
||||
}
|
||||
|
||||
pub fn local_inner_macros(&self, _db: &dyn ExpandDatabase, path: ast::Path) -> Option<CrateId> {
|
||||
let mut _token = path.syntax().first_token()?.text_range();
|
||||
let frames = self.frames.as_ref()?;
|
||||
let mut _current = &frames.0;
|
||||
|
||||
// FIXME: Hygiene ...
|
||||
return None;
|
||||
// loop {
|
||||
// let (mapped, origin) = current.expansion.as_ref()?.map_ident_up(db, token)?;
|
||||
// if origin == Origin::Def {
|
||||
// return if current.local_inner {
|
||||
// frames.root_crate(db, path.syntax())
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
// }
|
||||
// current = current.call_site.as_ref()?;
|
||||
// token = mapped.value;
|
||||
// }
|
||||
pub fn local_inner_macros(&self, _: &dyn ExpandDatabase, _: ast::Path) -> Option<CrateId> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,150 +95,19 @@ impl Hygiene {
|
|||
struct HygieneFrames(Arc<HygieneFrame>);
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct HygieneFrame {
|
||||
expansion: Option<HygieneInfo>,
|
||||
|
||||
// Indicate this is a local inner macro
|
||||
local_inner: bool,
|
||||
krate: Option<CrateId>,
|
||||
|
||||
call_site: Option<Arc<HygieneFrame>>,
|
||||
def_site: Option<Arc<HygieneFrame>>,
|
||||
}
|
||||
|
||||
impl HygieneFrames {
|
||||
fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> Self {
|
||||
// Note that this intentionally avoids the `hygiene_frame` query to avoid blowing up memory
|
||||
// usage. The query is only helpful for nested `HygieneFrame`s as it avoids redundant work.
|
||||
HygieneFrames(Arc::new(HygieneFrame::new(db, file_id)))
|
||||
}
|
||||
|
||||
fn root_crate(&self, _db: &dyn ExpandDatabase, node: &SyntaxNode) -> Option<CrateId> {
|
||||
let mut _token = node.first_token()?.text_range();
|
||||
let mut _result = self.0.krate;
|
||||
let mut _current = self.0.clone();
|
||||
|
||||
return None;
|
||||
|
||||
// while let Some((mapped, origin)) =
|
||||
// current.expansion.as_ref().and_then(|it| it.map_ident_up(db, token))
|
||||
// {
|
||||
// result = current.krate;
|
||||
|
||||
// let site = match origin {
|
||||
// Origin::Def => ¤t.def_site,
|
||||
// Origin::Call => ¤t.call_site,
|
||||
// };
|
||||
|
||||
// let site = match site {
|
||||
// None => break,
|
||||
// Some(it) => it,
|
||||
// };
|
||||
|
||||
// current = site.clone();
|
||||
// token = mapped.value;
|
||||
// }
|
||||
|
||||
// result
|
||||
}
|
||||
}
|
||||
pub struct HygieneFrame {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct HygieneInfo {
|
||||
file: MacroFile,
|
||||
/// The start offset of the `macro_rules!` arguments or attribute input.
|
||||
attr_input_or_mac_def_start: Option<InFile<TextSize>>,
|
||||
|
||||
macro_def: TokenExpander,
|
||||
macro_arg: Arc<crate::tt::Subtree>,
|
||||
exp_map: Arc<SpanMap>,
|
||||
}
|
||||
struct HygieneInfo {}
|
||||
|
||||
impl HygieneInfo {
|
||||
fn _map_ident_up(
|
||||
&self,
|
||||
_db: &dyn ExpandDatabase,
|
||||
_token: TextRange,
|
||||
) -> Option<InFile<TextRange>> {
|
||||
// self.exp_map.token_by_range(token).map(|span| InFile::new(span.anchor, span.range))
|
||||
fn _map_ident_up(&self, _: &dyn ExpandDatabase, _: TextRange) -> Option<InFile<TextRange>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn make_hygiene_info(
|
||||
db: &dyn ExpandDatabase,
|
||||
macro_file: MacroFile,
|
||||
loc: &MacroCallLoc,
|
||||
) -> HygieneInfo {
|
||||
let def = loc.def.ast_id().left().and_then(|id| {
|
||||
let def_tt = match id.to_node(db) {
|
||||
ast::Macro::MacroRules(mac) => mac.token_tree()?,
|
||||
ast::Macro::MacroDef(mac) => mac.body()?,
|
||||
};
|
||||
Some(InFile::new(id.file_id, def_tt))
|
||||
});
|
||||
let attr_input_or_mac_def = def.or_else(|| match loc.kind {
|
||||
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
|
||||
let tt = ast_id
|
||||
.to_node(db)
|
||||
.doc_comments_and_attrs()
|
||||
.nth(invoc_attr_index.ast_index())
|
||||
.and_then(Either::left)?
|
||||
.token_tree()?;
|
||||
Some(InFile::new(ast_id.file_id, tt))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let macro_def = db.macro_expander(loc.def);
|
||||
let (_, exp_map) = db.parse_macro_expansion(macro_file).value;
|
||||
let macro_arg = db.macro_arg(macro_file.macro_call_id).value.unwrap_or_else(|| {
|
||||
Arc::new(tt::Subtree { delimiter: tt::Delimiter::UNSPECIFIED, token_trees: Vec::new() })
|
||||
});
|
||||
|
||||
HygieneInfo {
|
||||
file: macro_file,
|
||||
attr_input_or_mac_def_start: attr_input_or_mac_def
|
||||
.map(|it| it.map(|tt| tt.syntax().text_range().start())),
|
||||
macro_arg,
|
||||
macro_def,
|
||||
exp_map,
|
||||
}
|
||||
}
|
||||
|
||||
impl HygieneFrame {
|
||||
pub(crate) fn new(db: &dyn ExpandDatabase, file_id: HirFileId) -> HygieneFrame {
|
||||
let (info, krate, local_inner) = match file_id.macro_file() {
|
||||
None => (None, None, false),
|
||||
Some(macro_file) => {
|
||||
let loc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
||||
let info = Some((make_hygiene_info(db, macro_file, &loc), loc.kind.file_id()));
|
||||
match loc.def.kind {
|
||||
MacroDefKind::Declarative(_) => {
|
||||
(info, Some(loc.def.krate), loc.def.local_inner)
|
||||
}
|
||||
MacroDefKind::BuiltIn(..) => (info, Some(loc.def.krate), false),
|
||||
MacroDefKind::BuiltInAttr(..) => (info, None, false),
|
||||
MacroDefKind::BuiltInDerive(..) => (info, None, false),
|
||||
MacroDefKind::BuiltInEager(..) => (info, None, false),
|
||||
MacroDefKind::ProcMacro(..) => (info, None, false),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let Some((info, calling_file)) = info else {
|
||||
return HygieneFrame {
|
||||
expansion: None,
|
||||
local_inner,
|
||||
krate,
|
||||
call_site: None,
|
||||
def_site: None,
|
||||
};
|
||||
};
|
||||
|
||||
let def_site = info.attr_input_or_mac_def_start.map(|it| db.hygiene_frame(it.file_id));
|
||||
let call_site = Some(db.hygiene_frame(calling_file));
|
||||
|
||||
HygieneFrame { expansion: Some(info), local_inner, krate, call_site, def_site }
|
||||
pub(crate) fn new(_: &dyn ExpandDatabase, _: HirFileId) -> HygieneFrame {
|
||||
HygieneFrame {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ pub type DeclarativeMacro = ::mbe::DeclarativeMacro<tt::SpanData>;
|
|||
|
||||
pub mod tt {
|
||||
pub use base_db::span::SpanData;
|
||||
pub use tt::{DelimiterKind, Spacing, Span};
|
||||
pub use tt::{DelimiterKind, Spacing, Span, SpanAnchor};
|
||||
|
||||
pub type Delimiter = ::tt::Delimiter<SpanData>;
|
||||
pub type Subtree = ::tt::Subtree<SpanData>;
|
||||
|
@ -97,44 +97,6 @@ impl fmt::Display for ExpandError {
|
|||
}
|
||||
}
|
||||
|
||||
/// `MacroCallId` identifies a particular macro invocation, like
|
||||
/// `println!("Hello, {}", world)`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct SyntaxContextId(base_db::salsa::InternId);
|
||||
base_db::impl_intern_key!(SyntaxContextId);
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct SyntaxContext {
|
||||
outer_expn: HirFileId,
|
||||
outer_transparency: Transparency,
|
||||
parent: SyntaxContextId,
|
||||
/// This context, but with all transparent and semi-transparent expansions filtered away.
|
||||
opaque: SyntaxContextId,
|
||||
/// This context, but with all transparent expansions filtered away.
|
||||
opaque_and_semitransparent: SyntaxContextId,
|
||||
/// Name of the crate to which `$crate` with this context would resolve.
|
||||
dollar_crate_name: name::Name,
|
||||
}
|
||||
|
||||
/// A property of a macro expansion that determines how identifiers
|
||||
/// produced by that expansion are resolved.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
|
||||
pub enum Transparency {
|
||||
/// Identifier produced by a transparent expansion is always resolved at call-site.
|
||||
/// Call-site spans in procedural macros, hygiene opt-out in `macro` should use this.
|
||||
Transparent,
|
||||
/// Identifier produced by a semi-transparent expansion may be resolved
|
||||
/// either at call-site or at definition-site.
|
||||
/// If it's a local variable, label or `$crate` then it's resolved at def-site.
|
||||
/// Otherwise it's resolved at call-site.
|
||||
/// `macro_rules` macros behave like this, built-in macros currently behave like this too,
|
||||
/// but that's an implementation detail.
|
||||
SemiTransparent,
|
||||
/// Identifier produced by an opaque expansion is always resolved at definition-site.
|
||||
/// Def-site spans in procedural macros, identifiers from `macro` by default use this.
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MacroCallLoc {
|
||||
pub def: MacroDefId,
|
||||
|
|
|
@ -247,8 +247,8 @@ mod tests {
|
|||
assert_eq!(quoted.to_string(), "hello");
|
||||
let t = format!("{quoted:?}");
|
||||
expect![[r#"
|
||||
SUBTREE $$ SpanData { range: 0..0, anchor: SpanAnchor { file_id: FileId(0), ast_id: Idx::<RustLanguage>>(0) } } SpanData { range: 0..0, anchor: SpanAnchor { file_id: FileId(0), ast_id: Idx::<RustLanguage>>(0) } }
|
||||
IDENT hello SpanData { range: 0..0, anchor: SpanAnchor { file_id: FileId(0), ast_id: Idx::<RustLanguage>>(0) } }"#]].assert_eq(&t);
|
||||
SUBTREE $$ SpanData { range: 0..0, anchor: SpanAnchor(FileId(0), 0), ctx: SyntaxContextId(0) } SpanData { range: 0..0, anchor: SpanAnchor(FileId(0), 0), ctx: SyntaxContextId(0) }
|
||||
IDENT hello SpanData { range: 0..0, anchor: SpanAnchor(FileId(0), 0), ctx: SyntaxContextId(0) }"#]].assert_eq(&t);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue