2020-08-13 14:26:29 +00:00
|
|
|
//! `hir_expand` deals with macro expansion.
|
2019-10-29 08:15:51 +00:00
|
|
|
//!
|
2019-10-29 13:01:14 +00:00
|
|
|
//! Specifically, it implements a concept of `MacroFile` -- a file whose syntax
|
|
|
|
//! tree originates not from the text of some `FileId`, but from some macro
|
|
|
|
//! expansion.
|
2019-10-29 08:15:51 +00:00
|
|
|
|
2019-10-29 11:55:39 +00:00
|
|
|
pub mod db;
|
2019-10-29 08:15:51 +00:00
|
|
|
pub mod ast_id_map;
|
2019-10-30 15:56:20 +00:00
|
|
|
pub mod name;
|
2019-10-30 16:10:53 +00:00
|
|
|
pub mod hygiene;
|
2021-10-10 12:44:03 +00:00
|
|
|
pub mod builtin_attr_macro;
|
|
|
|
pub mod builtin_derive_macro;
|
|
|
|
pub mod builtin_fn_macro;
|
2020-03-18 09:47:59 +00:00
|
|
|
pub mod proc_macro;
|
2019-11-11 06:14:39 +00:00
|
|
|
pub mod quote;
|
2020-03-02 06:05:15 +00:00
|
|
|
pub mod eager;
|
2022-01-26 17:31:07 +00:00
|
|
|
pub mod mod_path;
|
2019-10-29 11:55:39 +00:00
|
|
|
|
2021-05-29 18:32:57 +00:00
|
|
|
use base_db::ProcMacroKind;
|
2021-03-19 18:56:13 +00:00
|
|
|
use either::Either;
|
2021-05-08 23:36:06 +00:00
|
|
|
|
2021-11-05 14:52:10 +00:00
|
|
|
pub use mbe::{ExpandError, ExpandResult, Origin};
|
2022-01-26 17:31:07 +00:00
|
|
|
use mod_path::ModPath;
|
2020-11-26 15:48:17 +00:00
|
|
|
|
2021-08-20 11:49:28 +00:00
|
|
|
use std::{hash::Hash, iter, sync::Arc};
|
2019-10-29 12:11:42 +00:00
|
|
|
|
2020-12-08 18:01:27 +00:00
|
|
|
use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange};
|
2020-08-12 16:26:51 +00:00
|
|
|
use syntax::{
|
2021-11-03 20:12:36 +00:00
|
|
|
algo::{self, skip_trivia_token},
|
2022-01-07 17:51:10 +00:00
|
|
|
ast::{self, AstNode, HasDocComments},
|
2021-11-03 20:12:36 +00:00
|
|
|
Direction, SyntaxNode, SyntaxToken,
|
2019-11-02 20:11:05 +00:00
|
|
|
};
|
2019-10-29 12:11:42 +00:00
|
|
|
|
2021-08-19 20:53:00 +00:00
|
|
|
use crate::{
|
|
|
|
ast_id_map::FileAstId,
|
2021-10-10 12:44:03 +00:00
|
|
|
builtin_attr_macro::BuiltinAttrExpander,
|
|
|
|
builtin_derive_macro::BuiltinDeriveExpander,
|
|
|
|
builtin_fn_macro::{BuiltinFnLikeExpander, EagerExpander},
|
2021-08-21 16:06:03 +00:00
|
|
|
db::TokenExpander,
|
2021-08-19 20:53:00 +00:00
|
|
|
proc_macro::ProcMacroExpander,
|
|
|
|
};
|
2019-10-29 12:11:42 +00:00
|
|
|
|
|
|
|
/// Input to the analyzer is a set of files, where each file is identified by
|
|
|
|
/// `FileId` and contains source code. However, another source of source code in
|
|
|
|
/// Rust are macros: each macro can be thought of as producing a "temporary
|
|
|
|
/// file". To assign an id to such a file, we use the id of the macro call that
|
|
|
|
/// produced the file. So, a `HirFileId` is either a `FileId` (source code
|
|
|
|
/// written by user), or a `MacroCallId` (source code produced by macro).
|
|
|
|
///
|
|
|
|
/// What is a `MacroCallId`? Simplifying, it's a `HirFileId` of a file
|
|
|
|
/// containing the call plus the offset of the macro call in the file. Note that
|
|
|
|
/// this is a recursive definition! However, the size_of of `HirFileId` is
|
|
|
|
/// finite (because everything bottoms out at the real `FileId`) and small
|
2020-08-11 09:19:02 +00:00
|
|
|
/// (`MacroCallId` uses the location interning. You can check details here:
|
2021-06-14 04:57:10 +00:00
|
|
|
/// <https://en.wikipedia.org/wiki/String_interning>).
|
2019-10-29 12:11:42 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct HirFileId(HirFileIdRepr);
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
enum HirFileIdRepr {
|
|
|
|
FileId(FileId),
|
|
|
|
MacroFile(MacroFile),
|
|
|
|
}
|
|
|
|
impl From<FileId> for HirFileId {
|
|
|
|
fn from(id: FileId) -> Self {
|
|
|
|
HirFileId(HirFileIdRepr::FileId(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl From<MacroFile> for HirFileId {
|
|
|
|
fn from(id: MacroFile) -> Self {
|
|
|
|
HirFileId(HirFileIdRepr::MacroFile(id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-10 16:13:05 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct MacroFile {
|
|
|
|
pub macro_call_id: MacroCallId,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// `MacroCallId` identifies a particular macro invocation, like
|
|
|
|
/// `println!("Hello, {}", world)`.
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct MacroCallId(salsa::InternId);
|
|
|
|
impl_intern_key!(MacroCallId);
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
pub struct MacroCallLoc {
|
|
|
|
pub def: MacroDefId,
|
|
|
|
pub(crate) krate: CrateId,
|
|
|
|
eager: Option<EagerCallInfo>,
|
|
|
|
pub kind: MacroCallKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub struct MacroDefId {
|
|
|
|
pub krate: CrateId,
|
|
|
|
pub kind: MacroDefKind,
|
|
|
|
pub local_inner: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum MacroDefKind {
|
|
|
|
Declarative(AstId<ast::Macro>),
|
|
|
|
BuiltIn(BuiltinFnLikeExpander, AstId<ast::Macro>),
|
|
|
|
// FIXME: maybe just Builtin and rename BuiltinFnLikeExpander to BuiltinExpander
|
|
|
|
BuiltInAttr(BuiltinAttrExpander, AstId<ast::Macro>),
|
|
|
|
BuiltInDerive(BuiltinDeriveExpander, AstId<ast::Macro>),
|
|
|
|
BuiltInEager(EagerExpander, AstId<ast::Macro>),
|
|
|
|
ProcMacro(ProcMacroExpander, ProcMacroKind, AstId<ast::Fn>),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
struct EagerCallInfo {
|
|
|
|
/// NOTE: This can be *either* the expansion result, *or* the argument to the eager macro!
|
|
|
|
arg_or_expansion: Arc<tt::Subtree>,
|
|
|
|
included_file: Option<FileId>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
|
|
pub enum MacroCallKind {
|
|
|
|
FnLike {
|
|
|
|
ast_id: AstId<ast::MacroCall>,
|
|
|
|
expand_to: ExpandTo,
|
|
|
|
},
|
|
|
|
Derive {
|
2022-01-07 13:19:11 +00:00
|
|
|
ast_id: AstId<ast::Adt>,
|
2021-11-04 17:12:05 +00:00
|
|
|
derive_name: Box<str>,
|
2021-10-10 16:13:05 +00:00
|
|
|
/// Syntactical index of the invoking `#[derive]` attribute.
|
|
|
|
///
|
|
|
|
/// Outer attributes are counted first, then inner attributes. This does not support
|
|
|
|
/// out-of-line modules, which may have attributes spread across 2 files!
|
|
|
|
derive_attr_index: u32,
|
|
|
|
},
|
|
|
|
Attr {
|
|
|
|
ast_id: AstId<ast::Item>,
|
2021-11-04 17:12:05 +00:00
|
|
|
attr_name: Box<str>,
|
2021-10-10 16:13:05 +00:00
|
|
|
attr_args: (tt::Subtree, mbe::TokenMap),
|
|
|
|
/// Syntactical index of the invoking `#[attribute]`.
|
|
|
|
///
|
|
|
|
/// Outer attributes are counted first, then inner attributes. This does not support
|
|
|
|
/// out-of-line modules, which may have attributes spread across 2 files!
|
|
|
|
invoc_attr_index: u32,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2019-10-29 12:11:42 +00:00
|
|
|
impl HirFileId {
|
|
|
|
/// For macro-expansion files, returns the file original source file the
|
|
|
|
/// expansion originated from.
|
2019-10-30 13:12:55 +00:00
|
|
|
pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId {
|
2019-10-29 12:11:42 +00:00
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(file_id) => file_id,
|
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2021-05-19 18:19:08 +00:00
|
|
|
let file_id = match &loc.eager {
|
|
|
|
Some(EagerCallInfo { included_file: Some(file), .. }) => (*file).into(),
|
|
|
|
_ => loc.kind.file_id(),
|
2020-02-17 11:32:13 +00:00
|
|
|
};
|
2020-03-02 06:05:15 +00:00
|
|
|
file_id.original_file(db)
|
2019-10-29 12:11:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-03 17:44:23 +00:00
|
|
|
|
2020-07-15 15:18:19 +00:00
|
|
|
pub fn expansion_level(self, db: &dyn db::AstDatabase) -> u32 {
|
|
|
|
let mut level = 0;
|
|
|
|
let mut curr = self;
|
|
|
|
while let HirFileIdRepr::MacroFile(macro_file) = curr.0 {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2021-05-19 18:19:08 +00:00
|
|
|
|
2020-07-15 15:18:19 +00:00
|
|
|
level += 1;
|
2021-05-19 18:19:08 +00:00
|
|
|
curr = loc.kind.file_id();
|
2020-07-15 15:18:19 +00:00
|
|
|
}
|
|
|
|
level
|
|
|
|
}
|
|
|
|
|
2019-12-06 18:30:15 +00:00
|
|
|
/// If this is a macro call, returns the syntax node of the call.
|
|
|
|
pub fn call_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(_) => None,
|
2021-05-19 18:19:08 +00:00
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
Some(loc.kind.to_node(db))
|
2021-05-19 18:19:08 +00:00
|
|
|
}
|
2019-12-06 18:30:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-03 17:44:23 +00:00
|
|
|
/// Return expansion information if it is a macro-expansion file
|
2019-11-09 04:00:46 +00:00
|
|
|
pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option<ExpansionInfo> {
|
2019-11-03 17:44:23 +00:00
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(_) => None,
|
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2019-11-03 17:44:23 +00:00
|
|
|
|
2019-12-05 14:10:33 +00:00
|
|
|
let arg_tt = loc.kind.arg(db)?;
|
2020-12-03 17:38:05 +00:00
|
|
|
|
2021-03-19 18:56:13 +00:00
|
|
|
let def = loc.def.ast_id().left().and_then(|id| {
|
2020-12-15 17:43:19 +00:00
|
|
|
let def_tt = match id.to_node(db) {
|
|
|
|
ast::Macro::MacroRules(mac) => mac.token_tree()?,
|
2021-03-27 05:44:54 +00:00
|
|
|
ast::Macro::MacroDef(mac) => mac.body()?,
|
2020-12-15 17:43:19 +00:00
|
|
|
};
|
2020-12-03 17:38:05 +00:00
|
|
|
Some(InFile::new(id.file_id, def_tt))
|
|
|
|
});
|
2021-08-22 17:12:45 +00:00
|
|
|
let attr_input_or_mac_def = def.or_else(|| match loc.kind {
|
2021-08-21 16:06:03 +00:00
|
|
|
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => {
|
|
|
|
let tt = ast_id
|
|
|
|
.to_node(db)
|
2022-01-07 17:51:10 +00:00
|
|
|
.doc_comments_and_attrs()
|
|
|
|
.nth(invoc_attr_index as usize)
|
|
|
|
.and_then(Either::right)?
|
2021-08-22 17:12:45 +00:00
|
|
|
.token_tree()?;
|
2021-08-21 16:06:03 +00:00
|
|
|
Some(InFile::new(ast_id.file_id, tt))
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
});
|
2019-11-08 20:00:27 +00:00
|
|
|
|
2021-10-09 12:23:55 +00:00
|
|
|
let macro_def = db.macro_def(loc.def).ok()?;
|
2020-11-24 20:57:51 +00:00
|
|
|
let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?;
|
2019-11-09 04:00:46 +00:00
|
|
|
let macro_arg = db.macro_arg(macro_file.macro_call_id)?;
|
2019-11-08 20:00:27 +00:00
|
|
|
|
2019-11-17 17:15:55 +00:00
|
|
|
Some(ExpansionInfo {
|
2019-11-28 09:50:26 +00:00
|
|
|
expanded: InFile::new(self, parse.syntax_node()),
|
2019-12-05 14:10:33 +00:00
|
|
|
arg: InFile::new(loc.kind.file_id(), arg_tt),
|
2021-08-22 17:12:45 +00:00
|
|
|
attr_input_or_mac_def,
|
2021-08-21 16:06:03 +00:00
|
|
|
macro_arg_shift: mbe::Shift::new(¯o_arg.0),
|
2019-11-17 17:15:55 +00:00
|
|
|
macro_arg,
|
|
|
|
macro_def,
|
|
|
|
exp_map,
|
|
|
|
})
|
2019-11-03 17:44:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-12 10:08:53 +00:00
|
|
|
|
|
|
|
/// Indicate it is macro file generated for builtin derive
|
2020-07-29 22:23:03 +00:00
|
|
|
pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option<InFile<ast::Item>> {
|
2020-01-12 10:08:53 +00:00
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(_) => None,
|
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2020-01-12 10:08:53 +00:00
|
|
|
let item = match loc.def.kind {
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db),
|
2020-01-12 10:08:53 +00:00
|
|
|
_ => return None,
|
|
|
|
};
|
2020-07-29 22:23:03 +00:00
|
|
|
Some(item.with_value(ast::Item::cast(item.value.clone())?))
|
2020-01-12 10:08:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-03-21 15:02:01 +00:00
|
|
|
|
2021-10-10 13:50:28 +00:00
|
|
|
pub fn is_custom_derive(&self, db: &dyn db::AstDatabase) -> bool {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(_) => false,
|
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2021-10-10 13:50:28 +00:00
|
|
|
match loc.def.kind {
|
|
|
|
MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _) => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-21 15:02:01 +00:00
|
|
|
/// Return whether this file is an include macro
|
|
|
|
pub fn is_include_macro(&self, db: &dyn db::AstDatabase) -> bool {
|
|
|
|
match self.0 {
|
2021-05-19 18:19:08 +00:00
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2021-05-19 18:19:08 +00:00
|
|
|
matches!(loc.eager, Some(EagerCallInfo { included_file: Some(_), .. }))
|
|
|
|
}
|
|
|
|
_ => false,
|
2021-03-21 15:02:01 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-04 16:02:45 +00:00
|
|
|
|
2021-09-14 12:41:38 +00:00
|
|
|
/// Return whether this file is an include macro
|
|
|
|
pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::MacroFile(macro_file) => {
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id);
|
2021-09-14 12:41:38 +00:00
|
|
|
matches!(loc.kind, MacroCallKind::Attr { .. })
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-04 16:02:45 +00:00
|
|
|
pub fn is_macro(self) -> bool {
|
|
|
|
matches!(self.0, HirFileIdRepr::MacroFile(_))
|
|
|
|
}
|
2021-11-22 16:58:36 +00:00
|
|
|
|
|
|
|
pub fn macro_file(self) -> Option<MacroFile> {
|
|
|
|
match self.0 {
|
|
|
|
HirFileIdRepr::FileId(_) => None,
|
|
|
|
HirFileIdRepr::MacroFile(m) => Some(m),
|
|
|
|
}
|
|
|
|
}
|
2019-10-29 12:11:42 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 17:33:08 +00:00
|
|
|
impl MacroDefId {
|
2020-06-11 10:08:24 +00:00
|
|
|
pub fn as_lazy_macro(
|
|
|
|
self,
|
|
|
|
db: &dyn db::AstDatabase,
|
|
|
|
krate: CrateId,
|
|
|
|
kind: MacroCallKind,
|
2021-05-19 18:19:08 +00:00
|
|
|
) -> MacroCallId {
|
2021-11-14 15:25:40 +00:00
|
|
|
db.intern_macro_call(MacroCallLoc { def: self, krate, eager: None, kind })
|
2019-11-26 17:33:08 +00:00
|
|
|
}
|
2021-03-18 14:37:14 +00:00
|
|
|
|
2021-03-19 18:56:13 +00:00
|
|
|
pub fn ast_id(&self) -> Either<AstId<ast::Macro>, AstId<ast::Fn>> {
|
2021-03-18 14:37:14 +00:00
|
|
|
let id = match &self.kind {
|
2021-05-29 18:32:57 +00:00
|
|
|
MacroDefKind::ProcMacro(.., id) => return Either::Right(*id),
|
2021-08-19 20:53:00 +00:00
|
|
|
MacroDefKind::Declarative(id)
|
|
|
|
| MacroDefKind::BuiltIn(_, id)
|
|
|
|
| MacroDefKind::BuiltInAttr(_, id)
|
|
|
|
| MacroDefKind::BuiltInDerive(_, id)
|
|
|
|
| MacroDefKind::BuiltInEager(_, id) => id,
|
2021-03-18 14:37:14 +00:00
|
|
|
};
|
2021-03-19 18:56:13 +00:00
|
|
|
Either::Left(*id)
|
2021-03-18 14:37:14 +00:00
|
|
|
}
|
2021-05-11 22:27:16 +00:00
|
|
|
|
|
|
|
pub fn is_proc_macro(&self) -> bool {
|
|
|
|
matches!(self.kind, MacroDefKind::ProcMacro(..))
|
|
|
|
}
|
2021-10-21 10:21:34 +00:00
|
|
|
|
|
|
|
pub fn is_attribute(&self) -> bool {
|
|
|
|
matches!(
|
|
|
|
self.kind,
|
|
|
|
MacroDefKind::BuiltInAttr(..) | MacroDefKind::ProcMacro(_, ProcMacroKind::Attr, _)
|
|
|
|
)
|
|
|
|
}
|
2019-11-26 17:33:08 +00:00
|
|
|
}
|
|
|
|
|
2021-05-31 11:37:11 +00:00
|
|
|
// FIXME: attribute indices do not account for `cfg_attr`, which means that we'll strip the whole
|
|
|
|
// `cfg_attr` instead of just one of the attributes it expands to
|
|
|
|
|
2019-12-05 14:10:33 +00:00
|
|
|
impl MacroCallKind {
|
2021-05-19 18:19:08 +00:00
|
|
|
/// Returns the file containing the macro invocation.
|
2020-07-23 13:07:01 +00:00
|
|
|
fn file_id(&self) -> HirFileId {
|
2022-01-07 13:19:11 +00:00
|
|
|
match *self {
|
|
|
|
MacroCallKind::FnLike { ast_id: InFile { file_id, .. }, .. }
|
|
|
|
| MacroCallKind::Derive { ast_id: InFile { file_id, .. }, .. }
|
|
|
|
| MacroCallKind::Attr { ast_id: InFile { file_id, .. }, .. } => file_id,
|
2019-12-05 14:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
pub fn to_node(&self, db: &dyn db::AstDatabase) -> InFile<SyntaxNode> {
|
2019-12-06 18:30:15 +00:00
|
|
|
match self {
|
2021-04-08 18:43:07 +00:00
|
|
|
MacroCallKind::FnLike { ast_id, .. } => {
|
|
|
|
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
|
|
|
}
|
2022-01-07 13:19:11 +00:00
|
|
|
MacroCallKind::Derive { ast_id, .. } => {
|
|
|
|
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
|
|
|
}
|
|
|
|
MacroCallKind::Attr { ast_id, .. } => {
|
2020-03-18 09:47:59 +00:00
|
|
|
ast_id.with_value(ast_id.to_node(db).syntax().clone())
|
|
|
|
}
|
2019-12-06 18:30:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-08 14:38:50 +00:00
|
|
|
/// Returns the original file range that best describes the location of this macro call.
|
|
|
|
///
|
|
|
|
/// Here we try to roughly match what rustc does to improve diagnostics: fn-like macros
|
|
|
|
/// get the whole `ast::MacroCall`, attribute macros get the attribute's range, and derives
|
|
|
|
/// get only the specific derive that is being referred to.
|
|
|
|
pub fn original_call_range(self, db: &dyn db::AstDatabase) -> FileRange {
|
|
|
|
let mut kind = self;
|
|
|
|
loop {
|
|
|
|
match kind.file_id().0 {
|
|
|
|
HirFileIdRepr::MacroFile(file) => {
|
|
|
|
kind = db.lookup_intern_macro_call(file.macro_call_id).kind;
|
|
|
|
}
|
|
|
|
_ => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// `call_id` is now the outermost macro call, so its location is in a real file.
|
|
|
|
let file_id = match kind.file_id().0 {
|
|
|
|
HirFileIdRepr::FileId(it) => it,
|
|
|
|
HirFileIdRepr::MacroFile(_) => unreachable!("encountered unexpected macro file"),
|
|
|
|
};
|
|
|
|
let range = match kind {
|
|
|
|
MacroCallKind::FnLike { ast_id, .. } => ast_id.to_node(db).syntax().text_range(),
|
|
|
|
MacroCallKind::Derive { ast_id, derive_attr_index, .. } => {
|
|
|
|
// FIXME: should be the range of the macro name, not the whole derive
|
|
|
|
ast_id
|
|
|
|
.to_node(db)
|
|
|
|
.doc_comments_and_attrs()
|
|
|
|
.nth(derive_attr_index as usize)
|
|
|
|
.expect("missing derive")
|
|
|
|
.expect_right("derive is a doc comment?")
|
|
|
|
.syntax()
|
|
|
|
.text_range()
|
|
|
|
}
|
|
|
|
MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => ast_id
|
|
|
|
.to_node(db)
|
|
|
|
.doc_comments_and_attrs()
|
|
|
|
.nth(invoc_attr_index as usize)
|
|
|
|
.expect("missing attribute")
|
|
|
|
.expect_right("attribute macro is a doc comment?")
|
|
|
|
.syntax()
|
|
|
|
.text_range(),
|
|
|
|
};
|
|
|
|
|
|
|
|
FileRange { range, file_id }
|
|
|
|
}
|
|
|
|
|
2020-07-23 13:07:01 +00:00
|
|
|
fn arg(&self, db: &dyn db::AstDatabase) -> Option<SyntaxNode> {
|
2019-12-05 14:10:33 +00:00
|
|
|
match self {
|
2021-04-08 18:43:07 +00:00
|
|
|
MacroCallKind::FnLike { ast_id, .. } => {
|
2019-12-05 14:10:33 +00:00
|
|
|
Some(ast_id.to_node(db).token_tree()?.syntax().clone())
|
|
|
|
}
|
2022-01-07 13:19:11 +00:00
|
|
|
MacroCallKind::Derive { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
|
|
|
|
MacroCallKind::Attr { ast_id, .. } => Some(ast_id.to_node(db).syntax().clone()),
|
2019-12-05 14:10:33 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-08 23:36:06 +00:00
|
|
|
|
2021-09-05 19:30:06 +00:00
|
|
|
fn expand_to(&self) -> ExpandTo {
|
2021-05-08 23:36:06 +00:00
|
|
|
match self {
|
2021-09-05 19:30:06 +00:00
|
|
|
MacroCallKind::FnLike { expand_to, .. } => *expand_to,
|
|
|
|
MacroCallKind::Derive { .. } => ExpandTo::Items,
|
|
|
|
MacroCallKind::Attr { .. } => ExpandTo::Items, // is this always correct?
|
2021-05-08 23:36:06 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-29 12:11:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MacroCallId {
|
2019-12-08 08:16:52 +00:00
|
|
|
pub fn as_file(self) -> HirFileId {
|
|
|
|
MacroFile { macro_call_id: self }.into()
|
2019-10-29 12:11:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-03 19:12:19 +00:00
|
|
|
/// ExpansionInfo mainly describes how to map text range between src and expanded macro
|
2019-11-17 17:15:55 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2019-11-03 14:46:12 +00:00
|
|
|
pub struct ExpansionInfo {
|
2019-11-28 09:50:26 +00:00
|
|
|
expanded: InFile<SyntaxNode>,
|
2021-11-22 14:59:41 +00:00
|
|
|
/// The argument TokenTree or item for attributes
|
2019-12-05 14:10:33 +00:00
|
|
|
arg: InFile<SyntaxNode>,
|
2021-11-22 14:59:41 +00:00
|
|
|
/// The `macro_rules!` or attribute input.
|
2021-08-21 16:06:03 +00:00
|
|
|
attr_input_or_mac_def: Option<InFile<ast::TokenTree>>,
|
2019-11-08 20:00:27 +00:00
|
|
|
|
2021-08-21 16:06:03 +00:00
|
|
|
macro_def: Arc<TokenExpander>,
|
2019-11-17 17:15:55 +00:00
|
|
|
macro_arg: Arc<(tt::Subtree, mbe::TokenMap)>,
|
2021-11-22 16:58:36 +00:00
|
|
|
/// A shift built from `macro_arg`'s subtree, relevant for attributes as the item is the macro arg
|
|
|
|
/// and as such we need to shift tokens if they are part of an attributes input instead of their item.
|
2021-08-21 16:06:03 +00:00
|
|
|
macro_arg_shift: mbe::Shift,
|
2019-11-18 13:08:41 +00:00
|
|
|
exp_map: Arc<mbe::TokenMap>,
|
2019-11-03 14:46:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ExpansionInfo {
|
2021-11-05 14:52:10 +00:00
|
|
|
pub fn expanded(&self) -> InFile<SyntaxNode> {
|
|
|
|
self.expanded.clone()
|
|
|
|
}
|
|
|
|
|
2019-12-14 17:20:07 +00:00
|
|
|
pub fn call_node(&self) -> Option<InFile<SyntaxNode>> {
|
|
|
|
Some(self.arg.with_value(self.arg.value.parent()?))
|
|
|
|
}
|
|
|
|
|
2021-11-22 16:58:36 +00:00
|
|
|
/// Map a token down from macro input into the macro expansion.
|
|
|
|
///
|
|
|
|
/// The inner workings of this function differ slightly depending on the type of macro we are dealing with:
|
|
|
|
/// - declarative:
|
|
|
|
/// For declarative macros, we need to accommodate for the macro definition site(which acts as a second unchanging input)
|
|
|
|
/// , as tokens can mapped in and out of it.
|
|
|
|
/// To do this we shift all ids in the expansion by the maximum id of the definition site giving us an easy
|
|
|
|
/// way to map all the tokens.
|
|
|
|
/// - attribute:
|
|
|
|
/// Attributes have two different inputs, the input tokentree in the attribute node and the item
|
|
|
|
/// the attribute is annotating. Similarly as for declarative macros we need to do a shift here
|
|
|
|
/// as well. Currently this is done by shifting the attribute input by the maximum id of the item.
|
|
|
|
/// - function-like and derives:
|
|
|
|
/// Both of these only have one simple call site input so no special handling is required here.
|
2021-08-21 16:06:03 +00:00
|
|
|
pub fn map_token_down(
|
|
|
|
&self,
|
|
|
|
db: &dyn db::AstDatabase,
|
|
|
|
item: Option<ast::Item>,
|
|
|
|
token: InFile<&SyntaxToken>,
|
2021-08-28 19:18:56 +00:00
|
|
|
) -> Option<impl Iterator<Item = InFile<SyntaxToken>> + '_> {
|
2019-11-18 12:08:39 +00:00
|
|
|
assert_eq!(token.file_id, self.arg.file_id);
|
2021-11-22 16:58:36 +00:00
|
|
|
let token_id_in_attr_input = if let Some(item) = item {
|
2021-09-02 16:54:09 +00:00
|
|
|
// check if we are mapping down in an attribute input
|
2021-11-22 16:58:36 +00:00
|
|
|
// this is a special case as attributes can have two inputs
|
|
|
|
let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc = db.lookup_intern_macro_call(call_id);
|
2021-08-21 16:06:03 +00:00
|
|
|
|
|
|
|
let token_range = token.value.text_range();
|
|
|
|
match &loc.kind {
|
2022-01-07 17:51:10 +00:00
|
|
|
MacroCallKind::Attr { attr_args: (_, map), invoc_attr_index, .. } => {
|
|
|
|
let attr = item
|
|
|
|
.doc_comments_and_attrs()
|
|
|
|
.nth(*invoc_attr_index as usize)
|
|
|
|
.and_then(Either::right)?;
|
2021-08-21 16:06:03 +00:00
|
|
|
match attr.token_tree() {
|
|
|
|
Some(token_tree)
|
|
|
|
if token_tree.syntax().text_range().contains_range(token_range) =>
|
|
|
|
{
|
|
|
|
let attr_input_start =
|
|
|
|
token_tree.left_delimiter_token()?.text_range().start();
|
2021-11-22 16:58:36 +00:00
|
|
|
let relative_range =
|
|
|
|
token.value.text_range().checked_sub(attr_input_start)?;
|
|
|
|
// shift by the item's tree's max id
|
2022-01-07 17:51:10 +00:00
|
|
|
let token_id =
|
|
|
|
self.macro_arg_shift.shift(map.token_by_range(relative_range)?);
|
2021-08-21 16:06:03 +00:00
|
|
|
Some(token_id)
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2021-11-22 16:58:36 +00:00
|
|
|
let token_id = match token_id_in_attr_input {
|
2021-08-21 16:06:03 +00:00
|
|
|
Some(token_id) => token_id,
|
2021-11-22 16:58:36 +00:00
|
|
|
// the token is not inside an attribute's input so do the lookup in the macro_arg as ususal
|
2021-08-21 16:06:03 +00:00
|
|
|
None => {
|
2021-11-22 16:58:36 +00:00
|
|
|
let relative_range =
|
2021-08-21 16:06:03 +00:00
|
|
|
token.value.text_range().checked_sub(self.arg.value.text_range().start())?;
|
2021-11-22 16:58:36 +00:00
|
|
|
let token_id = self.macro_arg.1.token_by_range(relative_range)?;
|
|
|
|
// conditionally shift the id by a declaratives macro definition
|
2021-08-21 16:06:03 +00:00
|
|
|
self.macro_def.map_id_down(token_id)
|
|
|
|
}
|
|
|
|
};
|
2019-11-16 13:49:26 +00:00
|
|
|
|
2021-08-28 19:18:56 +00:00
|
|
|
let tokens = self
|
|
|
|
.exp_map
|
|
|
|
.ranges_by_token(token_id, token.value.kind())
|
|
|
|
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
|
2019-11-17 17:15:55 +00:00
|
|
|
|
2021-08-28 19:18:56 +00:00
|
|
|
Some(tokens.map(move |token| self.expanded.with_value(token)))
|
2019-11-16 13:49:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-22 16:58:36 +00:00
|
|
|
/// Map a token up out of the expansion it resides in into the arguments of the macro call of the expansion.
|
2021-01-03 10:47:57 +00:00
|
|
|
pub fn map_token_up(
|
|
|
|
&self,
|
2021-08-21 16:06:03 +00:00
|
|
|
db: &dyn db::AstDatabase,
|
2021-01-03 10:47:57 +00:00
|
|
|
token: InFile<&SyntaxToken>,
|
|
|
|
) -> Option<(InFile<SyntaxToken>, Origin)> {
|
2021-11-22 16:58:36 +00:00
|
|
|
// Fetch the id through its text range,
|
2021-01-03 10:47:57 +00:00
|
|
|
let token_id = self.exp_map.token_by_range(token.value.text_range())?;
|
2021-11-22 16:58:36 +00:00
|
|
|
// conditionally unshifting the id to accommodate for macro-rules def site
|
2021-08-21 16:06:03 +00:00
|
|
|
let (mut token_id, origin) = self.macro_def.map_id_up(token_id);
|
2019-11-09 09:49:35 +00:00
|
|
|
|
2021-11-22 16:58:36 +00:00
|
|
|
let call_id = self.expanded.file_id.macro_file()?.macro_call_id;
|
2021-11-14 15:25:40 +00:00
|
|
|
let loc = db.lookup_intern_macro_call(call_id);
|
2021-08-21 16:06:03 +00:00
|
|
|
|
2021-11-22 16:58:36 +00:00
|
|
|
// Attributes are a bit special for us, they have two inputs, the input tokentree and the annotated item.
|
2021-08-21 16:06:03 +00:00
|
|
|
let (token_map, tt) = match &loc.kind {
|
2021-11-22 16:58:36 +00:00
|
|
|
MacroCallKind::Attr { attr_args: (_, arg_token_map), .. } => {
|
|
|
|
// try unshifting the the token id, if unshifting fails, the token resides in the non-item attribute input
|
|
|
|
// note that the `TokenExpander::map_id_up` earlier only unshifts for declarative macros, so we don't double unshift with this
|
|
|
|
match self.macro_arg_shift.unshift(token_id) {
|
|
|
|
Some(unshifted) => {
|
|
|
|
token_id = unshifted;
|
|
|
|
(arg_token_map, self.attr_input_or_mac_def.clone()?.syntax().cloned())
|
|
|
|
}
|
|
|
|
None => (&self.macro_arg.1, self.arg.clone()),
|
2021-08-21 16:06:03 +00:00
|
|
|
}
|
2021-11-22 16:58:36 +00:00
|
|
|
}
|
2021-08-21 16:06:03 +00:00
|
|
|
_ => match origin {
|
|
|
|
mbe::Origin::Call => (&self.macro_arg.1, self.arg.clone()),
|
2021-08-22 17:12:45 +00:00
|
|
|
mbe::Origin::Def => match (&*self.macro_def, &self.attr_input_or_mac_def) {
|
2021-10-10 18:07:43 +00:00
|
|
|
(TokenExpander::DeclarativeMacro { def_site_token_map, .. }, Some(tt)) => {
|
|
|
|
(def_site_token_map, tt.syntax().cloned())
|
|
|
|
}
|
2021-08-21 16:06:03 +00:00
|
|
|
_ => panic!("`Origin::Def` used with non-`macro_rules!` macro"),
|
|
|
|
},
|
2021-05-04 19:03:16 +00:00
|
|
|
},
|
2019-11-09 04:00:46 +00:00
|
|
|
};
|
2019-11-03 14:46:12 +00:00
|
|
|
|
2021-08-28 19:18:56 +00:00
|
|
|
let range = token_map.first_range_by_token(token_id, token.value.kind())?;
|
2021-01-15 17:15:33 +00:00
|
|
|
let token =
|
|
|
|
tt.value.covering_element(range + tt.value.text_range().start()).into_token()?;
|
2019-12-14 17:20:07 +00:00
|
|
|
Some((tt.with_value(token), origin))
|
2019-11-03 14:46:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-29 12:11:42 +00:00
|
|
|
/// `AstId` points to an AST node in any file.
|
|
|
|
///
|
|
|
|
/// It is stable across reparses, and can be used as salsa key/value.
|
|
|
|
// FIXME: isn't this just a `Source<FileAstId<N>>` ?
|
2019-11-28 13:00:03 +00:00
|
|
|
pub type AstId<N> = InFile<FileAstId<N>>;
|
2019-10-29 12:11:42 +00:00
|
|
|
|
|
|
|
impl<N: AstNode> AstId<N> {
|
2019-10-30 13:12:55 +00:00
|
|
|
pub fn to_node(&self, db: &dyn db::AstDatabase) -> N {
|
2019-10-29 12:22:20 +00:00
|
|
|
let root = db.parse_or_expand(self.file_id).unwrap();
|
2019-11-28 13:00:03 +00:00
|
|
|
db.ast_id_map(self.file_id).get(self.value).to_node(&root)
|
2019-10-29 12:11:42 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-02 20:11:05 +00:00
|
|
|
|
2019-11-28 09:50:26 +00:00
|
|
|
/// `InFile<T>` stores a value of `T` inside a particular file/syntax tree.
|
2019-11-20 06:40:36 +00:00
|
|
|
///
|
|
|
|
/// Typical usages are:
|
|
|
|
///
|
2019-11-28 09:50:26 +00:00
|
|
|
/// * `InFile<SyntaxNode>` -- syntax node in a file
|
|
|
|
/// * `InFile<ast::FnDef>` -- ast node in a file
|
2020-04-24 21:40:41 +00:00
|
|
|
/// * `InFile<TextSize>` -- offset in a file
|
2019-11-14 07:30:30 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
2019-11-28 09:50:26 +00:00
|
|
|
pub struct InFile<T> {
|
2019-11-02 20:11:05 +00:00
|
|
|
pub file_id: HirFileId,
|
2019-11-20 06:40:36 +00:00
|
|
|
pub value: T,
|
2019-11-02 20:11:05 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 09:50:26 +00:00
|
|
|
impl<T> InFile<T> {
|
|
|
|
pub fn new(file_id: HirFileId, value: T) -> InFile<T> {
|
|
|
|
InFile { file_id, value }
|
2019-11-15 20:24:56 +00:00
|
|
|
}
|
|
|
|
|
2019-11-15 21:40:54 +00:00
|
|
|
// Similarly, naming here is stupid...
|
2019-11-28 09:50:26 +00:00
|
|
|
pub fn with_value<U>(&self, value: U) -> InFile<U> {
|
|
|
|
InFile::new(self.file_id, value)
|
2019-11-15 21:40:54 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 09:50:26 +00:00
|
|
|
pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> InFile<U> {
|
|
|
|
InFile::new(self.file_id, f(self.value))
|
2019-11-02 20:11:05 +00:00
|
|
|
}
|
2019-11-28 09:50:26 +00:00
|
|
|
pub fn as_ref(&self) -> InFile<&T> {
|
2019-11-20 10:09:21 +00:00
|
|
|
self.with_value(&self.value)
|
2019-11-14 07:30:30 +00:00
|
|
|
}
|
2020-03-13 15:05:46 +00:00
|
|
|
pub fn file_syntax(&self, db: &dyn db::AstDatabase) -> SyntaxNode {
|
2019-11-02 20:11:05 +00:00
|
|
|
db.parse_or_expand(self.file_id).expect("source created from invalid file")
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 20:46:18 +00:00
|
|
|
|
|
|
|
impl<T: Clone> InFile<&T> {
|
|
|
|
pub fn cloned(&self) -> InFile<T> {
|
|
|
|
self.with_value(self.value.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-12 17:19:55 +00:00
|
|
|
impl<T> InFile<Option<T>> {
|
|
|
|
pub fn transpose(self) -> Option<InFile<T>> {
|
|
|
|
let value = self.value?;
|
|
|
|
Some(InFile::new(self.file_id, value))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-21 12:38:58 +00:00
|
|
|
impl<'a> InFile<&'a SyntaxNode> {
|
2020-02-12 17:19:55 +00:00
|
|
|
pub fn ancestors_with_macros(
|
2019-12-06 20:46:18 +00:00
|
|
|
self,
|
2020-03-13 15:05:46 +00:00
|
|
|
db: &dyn db::AstDatabase,
|
2021-09-14 12:09:52 +00:00
|
|
|
) -> impl Iterator<Item = InFile<SyntaxNode>> + Clone + '_ {
|
2021-12-21 12:38:58 +00:00
|
|
|
iter::successors(Some(self.cloned()), move |node| match node.value.parent() {
|
2019-12-06 20:46:18 +00:00
|
|
|
Some(parent) => Some(node.with_value(parent)),
|
2022-01-14 10:07:53 +00:00
|
|
|
None => node.file_id.call_node(db),
|
2019-12-06 20:46:18 +00:00
|
|
|
})
|
|
|
|
}
|
2021-09-14 12:41:38 +00:00
|
|
|
|
|
|
|
/// Skips the attributed item that caused the macro invocation we are climbing up
|
|
|
|
pub fn ancestors_with_macros_skip_attr_item(
|
|
|
|
self,
|
|
|
|
db: &dyn db::AstDatabase,
|
|
|
|
) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
|
2021-12-21 12:38:58 +00:00
|
|
|
iter::successors(Some(self.cloned()), move |node| match node.value.parent() {
|
2021-09-14 12:41:38 +00:00
|
|
|
Some(parent) => Some(node.with_value(parent)),
|
|
|
|
None => {
|
|
|
|
let parent_node = node.file_id.call_node(db)?;
|
|
|
|
if node.file_id.is_attr_macro(db) {
|
|
|
|
// macro call was an attributed item, skip it
|
|
|
|
// FIXME: does this fail if this is a direct expansion of another macro?
|
|
|
|
parent_node.map(|node| node.parent()).transpose()
|
|
|
|
} else {
|
|
|
|
Some(parent_node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-01-05 20:32:18 +00:00
|
|
|
|
2021-08-19 20:53:00 +00:00
|
|
|
/// Falls back to the macro call range if the node cannot be mapped up fully.
|
2020-12-08 18:01:27 +00:00
|
|
|
pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange {
|
2021-08-19 20:53:00 +00:00
|
|
|
if let Some(res) = self.original_file_range_opt(db) {
|
|
|
|
return res;
|
2020-12-08 18:01:27 +00:00
|
|
|
}
|
|
|
|
|
2020-12-08 18:11:12 +00:00
|
|
|
// Fall back to whole macro call.
|
2022-01-08 14:38:50 +00:00
|
|
|
match self.file_id.0 {
|
|
|
|
HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() },
|
|
|
|
HirFileIdRepr::MacroFile(mac_file) => {
|
|
|
|
let loc = db.lookup_intern_macro_call(mac_file.macro_call_id);
|
|
|
|
loc.kind.original_call_range(db)
|
|
|
|
}
|
2020-12-08 18:01:27 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-19 20:53:00 +00:00
|
|
|
|
|
|
|
/// Attempts to map the syntax node back up its macro calls.
|
|
|
|
pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option<FileRange> {
|
2021-11-03 20:12:36 +00:00
|
|
|
match ascend_node_border_tokens(db, self) {
|
|
|
|
Some(InFile { file_id, value: (first, last) }) => {
|
|
|
|
let original_file = file_id.original_file(db);
|
|
|
|
let range = first.text_range().cover(last.text_range());
|
|
|
|
if file_id != original_file.into() {
|
2021-08-15 12:46:13 +00:00
|
|
|
tracing::error!("Failed mapping up more for {:?}", range);
|
2021-11-03 20:12:36 +00:00
|
|
|
return None;
|
2021-08-19 20:53:00 +00:00
|
|
|
}
|
2021-11-03 20:12:36 +00:00
|
|
|
Some(FileRange { file_id: original_file, range })
|
2021-08-19 20:53:00 +00:00
|
|
|
}
|
|
|
|
_ if !self.file_id.is_macro() => Some(FileRange {
|
|
|
|
file_id: self.file_id.original_file(db),
|
|
|
|
range: self.value.text_range(),
|
|
|
|
}),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
2020-12-08 18:01:27 +00:00
|
|
|
}
|
|
|
|
|
2021-11-03 20:12:36 +00:00
|
|
|
fn ascend_node_border_tokens(
|
2020-12-08 18:01:27 +00:00
|
|
|
db: &dyn db::AstDatabase,
|
2021-11-03 20:12:36 +00:00
|
|
|
InFile { file_id, value: node }: InFile<&SyntaxNode>,
|
|
|
|
) -> Option<InFile<(SyntaxToken, SyntaxToken)>> {
|
|
|
|
let expansion = file_id.expansion_info(db)?;
|
2020-12-08 18:01:27 +00:00
|
|
|
|
|
|
|
// the input node has only one token ?
|
2021-11-03 20:12:36 +00:00
|
|
|
let first = skip_trivia_token(node.first_token()?, Direction::Next)?;
|
|
|
|
let last = skip_trivia_token(node.last_token()?, Direction::Prev)?;
|
|
|
|
let is_single_token = first == last;
|
2020-12-08 18:01:27 +00:00
|
|
|
|
2021-11-03 20:12:36 +00:00
|
|
|
node.descendants().find_map(|it| {
|
2020-12-08 18:01:27 +00:00
|
|
|
let first = skip_trivia_token(it.first_token()?, Direction::Next)?;
|
2021-11-03 20:12:36 +00:00
|
|
|
let first = ascend_call_token(db, &expansion, InFile::new(file_id, first))?;
|
2020-12-08 18:01:27 +00:00
|
|
|
|
|
|
|
let last = skip_trivia_token(it.last_token()?, Direction::Prev)?;
|
2021-11-03 20:12:36 +00:00
|
|
|
let last = ascend_call_token(db, &expansion, InFile::new(file_id, last))?;
|
2020-12-08 18:01:27 +00:00
|
|
|
|
2021-11-03 20:12:36 +00:00
|
|
|
if (!is_single_token && first == last) || (first.file_id != last.file_id) {
|
2020-12-08 18:01:27 +00:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-11-03 20:12:36 +00:00
|
|
|
Some(InFile::new(first.file_id, (first.value, last.value)))
|
2021-03-17 01:19:40 +00:00
|
|
|
})
|
2020-12-08 18:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn ascend_call_token(
|
|
|
|
db: &dyn db::AstDatabase,
|
|
|
|
expansion: &ExpansionInfo,
|
|
|
|
token: InFile<SyntaxToken>,
|
|
|
|
) -> Option<InFile<SyntaxToken>> {
|
2021-08-21 16:06:03 +00:00
|
|
|
let (mapped, origin) = expansion.map_token_up(db, token.as_ref())?;
|
2020-12-08 18:01:27 +00:00
|
|
|
if origin != Origin::Call {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
if let Some(info) = mapped.file_id.expansion_info(db) {
|
|
|
|
return ascend_call_token(db, &info, mapped);
|
|
|
|
}
|
|
|
|
Some(mapped)
|
|
|
|
}
|
|
|
|
|
2020-02-12 17:19:55 +00:00
|
|
|
impl InFile<SyntaxToken> {
|
|
|
|
pub fn ancestors_with_macros(
|
|
|
|
self,
|
2020-03-13 15:05:46 +00:00
|
|
|
db: &dyn db::AstDatabase,
|
2020-02-12 17:19:55 +00:00
|
|
|
) -> impl Iterator<Item = InFile<SyntaxNode>> + '_ {
|
2021-08-20 13:20:18 +00:00
|
|
|
self.value.parent().into_iter().flat_map({
|
|
|
|
let file_id = self.file_id;
|
2021-12-21 12:38:58 +00:00
|
|
|
move |parent| InFile::new(file_id, &parent).ancestors_with_macros(db)
|
2021-08-20 13:20:18 +00:00
|
|
|
})
|
2020-02-12 17:19:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 20:32:18 +00:00
|
|
|
impl<N: AstNode> InFile<N> {
|
|
|
|
pub fn descendants<T: AstNode>(self) -> impl Iterator<Item = InFile<T>> {
|
|
|
|
self.value.syntax().descendants().filter_map(T::cast).map(move |n| self.with_value(n))
|
|
|
|
}
|
|
|
|
|
2021-11-03 20:12:36 +00:00
|
|
|
pub fn original_ast_node(self, db: &dyn db::AstDatabase) -> Option<InFile<N>> {
|
|
|
|
match ascend_node_border_tokens(db, self.syntax()) {
|
|
|
|
Some(InFile { file_id, value: (first, last) }) => {
|
|
|
|
let original_file = file_id.original_file(db);
|
|
|
|
if file_id != original_file.into() {
|
|
|
|
let range = first.text_range().cover(last.text_range());
|
|
|
|
tracing::error!("Failed mapping up more for {:?}", range);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?;
|
|
|
|
Some(InFile::new(file_id, anc.ancestors().find_map(N::cast)?))
|
|
|
|
}
|
|
|
|
_ if !self.file_id.is_macro() => Some(self),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-05 20:32:18 +00:00
|
|
|
pub fn syntax(&self) -> InFile<&SyntaxNode> {
|
|
|
|
self.with_value(self.value.syntax())
|
|
|
|
}
|
|
|
|
}
|
2021-05-08 23:36:06 +00:00
|
|
|
|
2021-09-05 19:30:06 +00:00
|
|
|
/// In Rust, macros expand token trees to token trees. When we want to turn a
|
|
|
|
/// token tree into an AST node, we need to figure out what kind of AST node we
|
|
|
|
/// want: something like `foo` can be a type, an expression, or a pattern.
|
|
|
|
///
|
|
|
|
/// Naively, one would think that "what this expands to" is a property of a
|
|
|
|
/// particular macro: macro `m1` returns an item, while macro `m2` returns an
|
|
|
|
/// expression, etc. That's not the case -- macros are polymorphic in the
|
|
|
|
/// result, and can expand to any type of the AST node.
|
|
|
|
///
|
|
|
|
/// What defines the actual AST node is the syntactic context of the macro
|
|
|
|
/// invocation. As a contrived example, in `let T![*] = T![*];` the first `T`
|
|
|
|
/// expands to a pattern, while the second one expands to an expression.
|
|
|
|
///
|
|
|
|
/// `ExpandTo` captures this bit of information about a particular macro call
|
|
|
|
/// site.
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
|
|
pub enum ExpandTo {
|
|
|
|
Statements,
|
|
|
|
Items,
|
|
|
|
Pattern,
|
|
|
|
Type,
|
|
|
|
Expr,
|
|
|
|
}
|
2021-08-31 11:54:22 +00:00
|
|
|
|
2021-09-05 19:30:06 +00:00
|
|
|
impl ExpandTo {
|
|
|
|
pub fn from_call_site(call: &ast::MacroCall) -> ExpandTo {
|
|
|
|
use syntax::SyntaxKind::*;
|
|
|
|
|
|
|
|
let syn = call.syntax();
|
|
|
|
|
|
|
|
let parent = match syn.parent() {
|
|
|
|
Some(it) => it,
|
|
|
|
None => return ExpandTo::Statements,
|
|
|
|
};
|
|
|
|
|
|
|
|
match parent.kind() {
|
|
|
|
MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => ExpandTo::Items,
|
2021-09-26 09:12:57 +00:00
|
|
|
MACRO_STMTS | EXPR_STMT | STMT_LIST => ExpandTo::Statements,
|
2021-09-05 19:30:06 +00:00
|
|
|
MACRO_PAT => ExpandTo::Pattern,
|
|
|
|
MACRO_TYPE => ExpandTo::Type,
|
|
|
|
|
|
|
|
ARG_LIST | TRY_EXPR | TUPLE_EXPR | PAREN_EXPR | ARRAY_EXPR | FOR_EXPR | PATH_EXPR
|
|
|
|
| CLOSURE_EXPR | CONDITION | BREAK_EXPR | RETURN_EXPR | MATCH_EXPR | MATCH_ARM
|
|
|
|
| MATCH_GUARD | RECORD_EXPR_FIELD | CALL_EXPR | INDEX_EXPR | METHOD_CALL_EXPR
|
|
|
|
| FIELD_EXPR | AWAIT_EXPR | CAST_EXPR | REF_EXPR | PREFIX_EXPR | RANGE_EXPR
|
|
|
|
| BIN_EXPR => ExpandTo::Expr,
|
|
|
|
LET_STMT => {
|
|
|
|
// FIXME: Handle LHS Pattern
|
|
|
|
ExpandTo::Expr
|
|
|
|
}
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
// Unknown , Just guess it is `Items`
|
|
|
|
ExpandTo::Items
|
|
|
|
}
|
2021-05-08 23:36:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-26 17:31:07 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct UnresolvedMacro {
|
|
|
|
pub path: ModPath,
|
|
|
|
}
|