Auto merge of #16834 - Veykril:macarons, r=Veykril

feat: Support macro calls in eager macros for IDE features

Basically hovering `concat` and `env` in `include!(concat!(env!("OUT_DIR"), "/foo.rs"))` now works and highlights as expected.

This also fixes a few bugs/problems to make it work. Prior we set the call site span to the entire macro call which is kind of wrong, typing inside the call would invalidate the span causing us to leak `MacroCallLoc`s whenever that happened. The same happened for attributes both of which now define their path as the call site.
This commit is contained in:
bors 2024-03-14 15:34:53 +00:00
commit 14558af15e
26 changed files with 456 additions and 262 deletions

View file

@ -191,9 +191,9 @@ impl StructData {
let krate = loc.container.krate; let krate = loc.container.krate;
let item_tree = loc.id.item_tree(db); let item_tree = loc.id.item_tree(db);
let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); let cfg_options = db.crate_graph()[krate].cfg_options.clone();
let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
let mut flags = StructFlags::NO_FLAGS; let mut flags = StructFlags::NO_FLAGS;
if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() {
@ -248,9 +248,9 @@ impl StructData {
let krate = loc.container.krate; let krate = loc.container.krate;
let item_tree = loc.id.item_tree(db); let item_tree = loc.id.item_tree(db);
let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into()); let repr = repr_from_value(db, krate, &item_tree, ModItem::from(loc.id.value).into());
let cfg_options = db.crate_graph()[loc.container.krate].cfg_options.clone(); let cfg_options = db.crate_graph()[krate].cfg_options.clone();
let attrs = item_tree.attrs(db, loc.container.krate, ModItem::from(loc.id.value).into()); let attrs = item_tree.attrs(db, krate, ModItem::from(loc.id.value).into());
let mut flags = StructFlags::NO_FLAGS; let mut flags = StructFlags::NO_FLAGS;
if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() { if attrs.by_key("rustc_has_incoherent_inherent_impls").exists() {
flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL; flags |= StructFlags::IS_RUSTC_HAS_INCOHERENT_INHERENT_IMPL;

View file

@ -309,13 +309,10 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()), kind: kind(loc.expander, loc.id.file_id(), makro.ast_id.upcast()),
local_inner: false, local_inner: false,
allow_internal_unsafe: loc.allow_internal_unsafe, allow_internal_unsafe: loc.allow_internal_unsafe,
span: db span: makro.def_site,
.span_map(loc.id.file_id())
.span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()),
edition: loc.edition, edition: loc.edition,
} }
} }
MacroId::MacroRulesId(it) => { MacroId::MacroRulesId(it) => {
let loc: MacroRulesLoc = it.lookup(db); let loc: MacroRulesLoc = it.lookup(db);
@ -328,9 +325,7 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
allow_internal_unsafe: loc allow_internal_unsafe: loc
.flags .flags
.contains(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE), .contains(MacroRulesLocFlags::ALLOW_INTERNAL_UNSAFE),
span: db span: makro.def_site,
.span_map(loc.id.file_id())
.span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()),
edition: loc.edition, edition: loc.edition,
} }
} }
@ -348,6 +343,7 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
), ),
local_inner: false, local_inner: false,
allow_internal_unsafe: false, allow_internal_unsafe: false,
// FIXME: This is wrong, this should point to the name
span: db span: db
.span_map(loc.id.file_id()) .span_map(loc.id.file_id())
.span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()), .span_for_range(db.ast_id_map(loc.id.file_id()).get(makro.ast_id).text_range()),

View file

@ -790,7 +790,6 @@ pub struct MacroCall {
pub path: Interned<ModPath>, pub path: Interned<ModPath>,
pub ast_id: FileAstId<ast::MacroCall>, pub ast_id: FileAstId<ast::MacroCall>,
pub expand_to: ExpandTo, pub expand_to: ExpandTo,
// FIXME: We need to move this out. It invalidates the item tree when typing inside the macro call.
pub call_site: Span, pub call_site: Span,
} }
@ -799,6 +798,7 @@ pub struct MacroRules {
/// The name of the declared macro. /// The name of the declared macro.
pub name: Name, pub name: Name,
pub ast_id: FileAstId<ast::MacroRules>, pub ast_id: FileAstId<ast::MacroRules>,
pub def_site: Span,
} }
/// "Macros 2.0" macro definition. /// "Macros 2.0" macro definition.
@ -807,6 +807,7 @@ pub struct Macro2 {
pub name: Name, pub name: Name,
pub visibility: RawVisibilityId, pub visibility: RawVisibilityId,
pub ast_id: FileAstId<ast::MacroDef>, pub ast_id: FileAstId<ast::MacroDef>,
pub def_site: Span,
} }
impl Use { impl Use {

View file

@ -560,35 +560,34 @@ impl<'a> Ctx<'a> {
fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> { fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
let span_map = self.span_map(); let span_map = self.span_map();
let path = Interned::new(ModPath::from_src(self.db.upcast(), m.path()?, &mut |range| { let path = m.path()?;
let range = path.syntax().text_range();
let path = Interned::new(ModPath::from_src(self.db.upcast(), path, &mut |range| {
span_map.span_for_range(range).ctx span_map.span_for_range(range).ctx
})?); })?);
let ast_id = self.source_ast_id_map.ast_id(m); let ast_id = self.source_ast_id_map.ast_id(m);
let expand_to = hir_expand::ExpandTo::from_call_site(m); let expand_to = hir_expand::ExpandTo::from_call_site(m);
let res = MacroCall { let res = MacroCall { path, ast_id, expand_to, call_site: span_map.span_for_range(range) };
path,
ast_id,
expand_to,
call_site: span_map.span_for_range(m.syntax().text_range()),
};
Some(id(self.data().macro_calls.alloc(res))) Some(id(self.data().macro_calls.alloc(res)))
} }
fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option<FileItemTreeId<MacroRules>> { fn lower_macro_rules(&mut self, m: &ast::MacroRules) -> Option<FileItemTreeId<MacroRules>> {
let name = m.name().map(|it| it.as_name())?; let name = m.name()?;
let def_site = self.span_map().span_for_range(name.syntax().text_range());
let ast_id = self.source_ast_id_map.ast_id(m); let ast_id = self.source_ast_id_map.ast_id(m);
let res = MacroRules { name, ast_id }; let res = MacroRules { name: name.as_name(), ast_id, def_site };
Some(id(self.data().macro_rules.alloc(res))) Some(id(self.data().macro_rules.alloc(res)))
} }
fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option<FileItemTreeId<Macro2>> { fn lower_macro_def(&mut self, m: &ast::MacroDef) -> Option<FileItemTreeId<Macro2>> {
let name = m.name().map(|it| it.as_name())?; let name = m.name()?;
let def_site = self.span_map().span_for_range(name.syntax().text_range());
let ast_id = self.source_ast_id_map.ast_id(m); let ast_id = self.source_ast_id_map.ast_id(m);
let visibility = self.lower_visibility(m); let visibility = self.lower_visibility(m);
let res = Macro2 { name, ast_id, visibility }; let res = Macro2 { name: name.as_name(), ast_id, visibility, def_site };
Some(id(self.data().macro_defs.alloc(res))) Some(id(self.data().macro_defs.alloc(res)))
} }

View file

@ -498,13 +498,23 @@ impl Printer<'_> {
wln!(self, "{}!(...);", path.display(self.db.upcast())); wln!(self, "{}!(...);", path.display(self.db.upcast()));
} }
ModItem::MacroRules(it) => { ModItem::MacroRules(it) => {
let MacroRules { name, ast_id } = &self.tree[it]; let MacroRules { name, ast_id, def_site } = &self.tree[it];
self.print_ast_id(ast_id.erase()); let _ = writeln!(
self,
"// AstId: {:?}, Span: {}",
ast_id.erase().into_raw(),
def_site,
);
wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast())); wln!(self, "macro_rules! {} {{ ... }}", name.display(self.db.upcast()));
} }
ModItem::Macro2(it) => { ModItem::Macro2(it) => {
let Macro2 { name, visibility, ast_id } = &self.tree[it]; let Macro2 { name, visibility, ast_id, def_site } = &self.tree[it];
self.print_ast_id(ast_id.erase()); let _ = writeln!(
self,
"// AstId: {:?}, Span: {}",
ast_id.erase().into_raw(),
def_site,
);
self.print_visibility(*visibility); self.print_visibility(*visibility);
wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast())); wln!(self, "macro {} {{ ... }}", name.display(self.db.upcast()));
} }

View file

@ -272,13 +272,13 @@ pub macro m2() {}
m!(); m!();
"#, "#,
expect![[r#" expect![[r#"
// AstId: 1 // AstId: 1, Span: 0:1@13..14#0
macro_rules! m { ... } macro_rules! m { ... }
// AstId: 2 // AstId: 2, Span: 0:2@10..12#0
pub macro m2 { ... } pub macro m2 { ... }
// AstId: 3, Span: 0:3@0..5#0, ExpandTo: Items // AstId: 3, Span: 0:3@0..1#0, ExpandTo: Items
m!(...); m!(...);
"#]], "#]],
); );

View file

@ -1342,17 +1342,18 @@ impl AsMacroCall for InFile<&ast::MacroCall> {
let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value));
let span_map = db.span_map(self.file_id); let span_map = db.span_map(self.file_id);
let path = self.value.path().and_then(|path| { let path = self.value.path().and_then(|path| {
path::ModPath::from_src(db, path, &mut |range| { let range = path.syntax().text_range();
let mod_path = path::ModPath::from_src(db, path, &mut |range| {
span_map.as_ref().span_for_range(range).ctx span_map.as_ref().span_for_range(range).ctx
}) })?;
let call_site = span_map.span_for_range(range);
Some((call_site, mod_path))
}); });
let Some(path) = path else { let Some((call_site, path)) = path else {
return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation"))); return Ok(ExpandResult::only_err(ExpandError::other("malformed macro invocation")));
}; };
let call_site = span_map.span_for_range(self.value.syntax().text_range());
macro_call_as_call_id_with_eager( macro_call_as_call_id_with_eager(
db, db,
&AstIdWithPath::new(ast_id.file_id, ast_id.value, path), &AstIdWithPath::new(ast_id.file_id, ast_id.value, path),

View file

@ -171,7 +171,7 @@ fn main(foo: ()) {
} }
fn main(foo: ()) { fn main(foo: ()) {
/* error: unresolved macro unresolved */"helloworld!"#0:3@207..323#2#; /* error: unresolved macro unresolved */"helloworld!"#0:3@236..321#0#;
} }
} }

View file

@ -201,10 +201,12 @@ impl Attr {
span_map: SpanMapRef<'_>, span_map: SpanMapRef<'_>,
id: AttrId, id: AttrId,
) -> Option<Attr> { ) -> Option<Attr> {
let path = Interned::new(ModPath::from_src(db, ast.path()?, &mut |range| { let path = ast.path()?;
let range = path.syntax().text_range();
let path = Interned::new(ModPath::from_src(db, path, &mut |range| {
span_map.span_for_range(range).ctx span_map.span_for_range(range).ctx
})?); })?);
let span = span_map.span_for_range(ast.syntax().text_range()); let span = span_map.span_for_range(range);
let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() { let input = if let Some(ast::Expr::Literal(lit)) = ast.expr() {
let value = match lit.kind() { let value = match lit.kind() {
ast::LiteralKind::String(string) => string.value()?.into(), ast::LiteralKind::String(string) => string.value()?.into(),

View file

@ -19,14 +19,14 @@ use crate::{
}; };
macro_rules! register_builtin { macro_rules! register_builtin {
( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { ( $LAZY:ident: $(($name:ident, $kind: ident) => $expand:ident),* , $EAGER:ident: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuiltinFnLikeExpander { pub enum $LAZY {
$($kind),* $($kind),*
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EagerExpander { pub enum $EAGER {
$($e_kind),* $($e_kind),*
} }
@ -84,6 +84,17 @@ impl EagerExpander {
pub fn is_include(&self) -> bool { pub fn is_include(&self) -> bool {
matches!(self, EagerExpander::Include) matches!(self, EagerExpander::Include)
} }
pub fn is_include_like(&self) -> bool {
matches!(
self,
EagerExpander::Include | EagerExpander::IncludeStr | EagerExpander::IncludeBytes
)
}
pub fn is_env_or_option_env(&self) -> bool {
matches!(self, EagerExpander::Env | EagerExpander::OptionEnv)
}
} }
pub fn find_builtin_macro( pub fn find_builtin_macro(
@ -93,7 +104,7 @@ pub fn find_builtin_macro(
} }
register_builtin! { register_builtin! {
LAZY: BuiltinFnLikeExpander:
(column, Column) => line_expand, (column, Column) => line_expand,
(file, File) => file_expand, (file, File) => file_expand,
(line, Line) => line_expand, (line, Line) => line_expand,
@ -114,7 +125,7 @@ register_builtin! {
(format_args_nl, FormatArgsNl) => format_args_nl_expand, (format_args_nl, FormatArgsNl) => format_args_nl_expand,
(quote, Quote) => quote_expand, (quote, Quote) => quote_expand,
EAGER: EagerExpander:
(compile_error, CompileError) => compile_error_expand, (compile_error, CompileError) => compile_error_expand,
(concat, Concat) => concat_expand, (concat, Concat) => concat_expand,
(concat_idents, ConcatIdents) => concat_idents_expand, (concat_idents, ConcatIdents) => concat_idents_expand,
@ -426,22 +437,25 @@ fn use_panic_2021(db: &dyn ExpandDatabase, span: Span) -> bool {
} }
} }
fn unquote_str(lit: &tt::Literal) -> Option<String> { fn unquote_str(lit: &tt::Literal) -> Option<(String, Span)> {
let span = lit.span;
let lit = ast::make::tokens::literal(&lit.to_string()); let lit = ast::make::tokens::literal(&lit.to_string());
let token = ast::String::cast(lit)?; let token = ast::String::cast(lit)?;
token.value().map(|it| it.into_owned()) token.value().map(|it| (it.into_owned(), span))
} }
fn unquote_char(lit: &tt::Literal) -> Option<char> { fn unquote_char(lit: &tt::Literal) -> Option<(char, Span)> {
let span = lit.span;
let lit = ast::make::tokens::literal(&lit.to_string()); let lit = ast::make::tokens::literal(&lit.to_string());
let token = ast::Char::cast(lit)?; let token = ast::Char::cast(lit)?;
token.value() token.value().zip(Some(span))
} }
fn unquote_byte_string(lit: &tt::Literal) -> Option<Vec<u8>> { fn unquote_byte_string(lit: &tt::Literal) -> Option<(Vec<u8>, Span)> {
let span = lit.span;
let lit = ast::make::tokens::literal(&lit.to_string()); let lit = ast::make::tokens::literal(&lit.to_string());
let token = ast::ByteString::cast(lit)?; let token = ast::ByteString::cast(lit)?;
token.value().map(|it| it.into_owned()) token.value().map(|it| (it.into_owned(), span))
} }
fn compile_error_expand( fn compile_error_expand(
@ -452,7 +466,7 @@ fn compile_error_expand(
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let err = match &*tt.token_trees { let err = match &*tt.token_trees {
[tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) {
Some(unquoted) => ExpandError::other(unquoted.into_boxed_str()), Some((unquoted, _)) => ExpandError::other(unquoted.into_boxed_str()),
None => ExpandError::other("`compile_error!` argument must be a string"), None => ExpandError::other("`compile_error!` argument must be a string"),
}, },
_ => ExpandError::other("`compile_error!` argument must be a string"), _ => ExpandError::other("`compile_error!` argument must be a string"),
@ -465,10 +479,16 @@ fn concat_expand(
_db: &dyn ExpandDatabase, _db: &dyn ExpandDatabase,
_arg_id: MacroCallId, _arg_id: MacroCallId,
tt: &tt::Subtree, tt: &tt::Subtree,
span: Span, _: Span,
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let mut err = None; let mut err = None;
let mut text = String::new(); let mut text = String::new();
let mut span: Option<Span> = None;
let mut record_span = |s: Span| match &mut span {
Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
Some(_) => (),
None => span = Some(s),
};
for (i, mut t) in tt.token_trees.iter().enumerate() { for (i, mut t) in tt.token_trees.iter().enumerate() {
// FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses // FIXME: hack on top of a hack: `$e:expr` captures get surrounded in parentheses
// to ensure the right parsing order, so skip the parentheses here. Ideally we'd // to ensure the right parsing order, so skip the parentheses here. Ideally we'd
@ -486,11 +506,14 @@ fn concat_expand(
// concat works with string and char literals, so remove any quotes. // concat works with string and char literals, so remove any quotes.
// It also works with integer, float and boolean literals, so just use the rest // It also works with integer, float and boolean literals, so just use the rest
// as-is. // as-is.
if let Some(c) = unquote_char(it) { if let Some((c, span)) = unquote_char(it) {
text.push(c); text.push(c);
record_span(span);
} else { } else {
let component = unquote_str(it).unwrap_or_else(|| it.text.to_string()); let (component, span) =
unquote_str(it).unwrap_or_else(|| (it.text.to_string(), it.span));
text.push_str(&component); text.push_str(&component);
record_span(span);
} }
} }
// handle boolean literals // handle boolean literals
@ -498,6 +521,7 @@ fn concat_expand(
if i % 2 == 0 && (id.text == "true" || id.text == "false") => if i % 2 == 0 && (id.text == "true" || id.text == "false") =>
{ {
text.push_str(id.text.as_str()); text.push_str(id.text.as_str());
record_span(id.span);
} }
tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
_ => { _ => {
@ -505,6 +529,7 @@ fn concat_expand(
} }
} }
} }
let span = span.unwrap_or(tt.delimiter.open);
ExpandResult { value: quote!(span =>#text), err } ExpandResult { value: quote!(span =>#text), err }
} }
@ -512,18 +537,25 @@ fn concat_bytes_expand(
_db: &dyn ExpandDatabase, _db: &dyn ExpandDatabase,
_arg_id: MacroCallId, _arg_id: MacroCallId,
tt: &tt::Subtree, tt: &tt::Subtree,
span: Span, call_site: Span,
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
let mut err = None; let mut err = None;
let mut span: Option<Span> = None;
let mut record_span = |s: Span| match &mut span {
Some(span) if span.anchor == s.anchor => span.range = span.range.cover(s.range),
Some(_) => (),
None => span = Some(s),
};
for (i, t) in tt.token_trees.iter().enumerate() { for (i, t) in tt.token_trees.iter().enumerate() {
match t { match t {
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
let token = ast::make::tokens::literal(&lit.to_string()); let token = ast::make::tokens::literal(&lit.to_string());
record_span(lit.span);
match token.kind() { match token.kind() {
syntax::SyntaxKind::BYTE => bytes.push(token.text().to_owned()), syntax::SyntaxKind::BYTE => bytes.push(token.text().to_owned()),
syntax::SyntaxKind::BYTE_STRING => { syntax::SyntaxKind::BYTE_STRING => {
let components = unquote_byte_string(lit).unwrap_or_default(); let components = unquote_byte_string(lit).map_or(vec![], |(it, _)| it);
components.into_iter().for_each(|it| bytes.push(it.to_string())); components.into_iter().for_each(|it| bytes.push(it.to_string()));
} }
_ => { _ => {
@ -534,7 +566,7 @@ fn concat_bytes_expand(
} }
tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (), tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => { tt::TokenTree::Subtree(tree) if tree.delimiter.kind == tt::DelimiterKind::Bracket => {
if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes) { if let Err(e) = concat_bytes_expand_subtree(tree, &mut bytes, &mut record_span) {
err.get_or_insert(e); err.get_or_insert(e);
break; break;
} }
@ -546,17 +578,24 @@ fn concat_bytes_expand(
} }
} }
let value = tt::Subtree { let value = tt::Subtree {
delimiter: tt::Delimiter { open: span, close: span, kind: tt::DelimiterKind::Bracket }, delimiter: tt::Delimiter {
open: call_site,
close: call_site,
kind: tt::DelimiterKind::Bracket,
},
token_trees: { token_trees: {
Itertools::intersperse_with( Itertools::intersperse_with(
bytes.into_iter().map(|it| { bytes.into_iter().map(|it| {
tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { text: it.into(), span })) tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
text: it.into(),
span: span.unwrap_or(call_site),
}))
}), }),
|| { || {
tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct { tt::TokenTree::Leaf(tt::Leaf::Punct(tt::Punct {
char: ',', char: ',',
spacing: tt::Spacing::Alone, spacing: tt::Spacing::Alone,
span, span: call_site,
})) }))
}, },
) )
@ -569,13 +608,15 @@ fn concat_bytes_expand(
fn concat_bytes_expand_subtree( fn concat_bytes_expand_subtree(
tree: &tt::Subtree, tree: &tt::Subtree,
bytes: &mut Vec<String>, bytes: &mut Vec<String>,
mut record_span: impl FnMut(Span),
) -> Result<(), ExpandError> { ) -> Result<(), ExpandError> {
for (ti, tt) in tree.token_trees.iter().enumerate() { for (ti, tt) in tree.token_trees.iter().enumerate() {
match tt { match tt {
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => { tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => {
let lit = ast::make::tokens::literal(&lit.to_string()); let lit = ast::make::tokens::literal(&it.to_string());
match lit.kind() { match lit.kind() {
syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => { syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => {
record_span(it.span);
bytes.push(lit.text().to_owned()) bytes.push(lit.text().to_owned())
} }
_ => { _ => {
@ -635,7 +676,7 @@ fn relative_file(
} }
} }
fn parse_string(tt: &tt::Subtree) -> Result<String, ExpandError> { fn parse_string(tt: &tt::Subtree) -> Result<(String, Span), ExpandError> {
tt.token_trees tt.token_trees
.first() .first()
.and_then(|tt| match tt { .and_then(|tt| match tt {
@ -675,7 +716,7 @@ pub fn include_input_to_file_id(
arg_id: MacroCallId, arg_id: MacroCallId,
arg: &tt::Subtree, arg: &tt::Subtree,
) -> Result<FileId, ExpandError> { ) -> Result<FileId, ExpandError> {
relative_file(db, arg_id, &parse_string(arg)?, false) relative_file(db, arg_id, &parse_string(arg)?.0, false)
} }
fn include_bytes_expand( fn include_bytes_expand(
@ -701,7 +742,7 @@ fn include_str_expand(
tt: &tt::Subtree, tt: &tt::Subtree,
span: Span, span: Span,
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let path = match parse_string(tt) { let (path, span) = match parse_string(tt) {
Ok(it) => it, Ok(it) => it,
Err(e) => { Err(e) => {
return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e)
@ -736,7 +777,7 @@ fn env_expand(
tt: &tt::Subtree, tt: &tt::Subtree,
span: Span, span: Span,
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let key = match parse_string(tt) { let (key, span) = match parse_string(tt) {
Ok(it) => it, Ok(it) => it,
Err(e) => { Err(e) => {
return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e)
@ -766,18 +807,24 @@ fn option_env_expand(
db: &dyn ExpandDatabase, db: &dyn ExpandDatabase,
arg_id: MacroCallId, arg_id: MacroCallId,
tt: &tt::Subtree, tt: &tt::Subtree,
span: Span, call_site: Span,
) -> ExpandResult<tt::Subtree> { ) -> ExpandResult<tt::Subtree> {
let key = match parse_string(tt) { let (key, span) = match parse_string(tt) {
Ok(it) => it, Ok(it) => it,
Err(e) => { Err(e) => {
return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e) return ExpandResult::new(
tt::Subtree::empty(DelimSpan { open: call_site, close: call_site }),
e,
)
} }
}; };
let dollar_crate = dollar_crate(span); let dollar_crate = dollar_crate(call_site);
let expanded = match get_env_inner(db, arg_id, &key) { let expanded = match get_env_inner(db, arg_id, &key) {
None => quote! {span => #dollar_crate::option::Option::None::<&str> }, None => quote! {call_site => #dollar_crate::option::Option::None::<&str> },
Some(s) => quote! {span => #dollar_crate::option::Option::Some(#s) }, Some(s) => {
let s = quote! (span => #s);
quote! {call_site => #dollar_crate::option::Option::Some(#s) }
}
}; };
ExpandResult::ok(expanded) ExpandResult::ok(expanded)

View file

@ -323,6 +323,9 @@ impl HirFileIdExt for HirFileId {
} }
pub trait MacroFileIdExt { pub trait MacroFileIdExt {
fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool;
fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool;
fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option<MacroCallId>;
fn expansion_level(self, db: &dyn ExpandDatabase) -> u32; fn expansion_level(self, db: &dyn ExpandDatabase) -> u32;
/// If this is a macro call, returns the syntax node of the call. /// If this is a macro call, returns the syntax node of the call.
fn call_node(self, db: &dyn ExpandDatabase) -> InFile<SyntaxNode>; fn call_node(self, db: &dyn ExpandDatabase) -> InFile<SyntaxNode>;
@ -389,18 +392,34 @@ impl MacroFileIdExt for MacroFileId {
db.lookup_intern_macro_call(self.macro_call_id).def.is_include() db.lookup_intern_macro_call(self.macro_call_id).def.is_include()
} }
fn is_include_like_macro(&self, db: &dyn ExpandDatabase) -> bool {
db.lookup_intern_macro_call(self.macro_call_id).def.is_include_like()
}
fn is_env_or_option_env(&self, db: &dyn ExpandDatabase) -> bool {
db.lookup_intern_macro_call(self.macro_call_id).def.is_env_or_option_env()
}
fn is_eager(&self, db: &dyn ExpandDatabase) -> bool { fn is_eager(&self, db: &dyn ExpandDatabase) -> bool {
let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); let loc = db.lookup_intern_macro_call(self.macro_call_id);
matches!(loc.def.kind, MacroDefKind::BuiltInEager(..)) matches!(loc.def.kind, MacroDefKind::BuiltInEager(..))
} }
fn eager_arg(&self, db: &dyn ExpandDatabase) -> Option<MacroCallId> {
let loc = db.lookup_intern_macro_call(self.macro_call_id);
match &loc.kind {
MacroCallKind::FnLike { eager, .. } => eager.as_ref().map(|it| it.arg_id),
_ => None,
}
}
fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool { fn is_attr_macro(&self, db: &dyn ExpandDatabase) -> bool {
let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); let loc = db.lookup_intern_macro_call(self.macro_call_id);
matches!(loc.kind, MacroCallKind::Attr { .. }) matches!(loc.kind, MacroCallKind::Attr { .. })
} }
fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool { fn is_derive_attr_pseudo_expansion(&self, db: &dyn ExpandDatabase) -> bool {
let loc: MacroCallLoc = db.lookup_intern_macro_call(self.macro_call_id); let loc = db.lookup_intern_macro_call(self.macro_call_id);
loc.def.is_attribute_derive() loc.def.is_attribute_derive()
} }
} }
@ -478,6 +497,14 @@ impl MacroDefId {
pub fn is_include(&self) -> bool { pub fn is_include(&self) -> bool {
matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_include()) matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_include())
} }
pub fn is_include_like(&self) -> bool {
matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_include_like())
}
pub fn is_env_or_option_env(&self) -> bool {
matches!(self.kind, MacroDefKind::BuiltInEager(expander, ..) if expander.is_env_or_option_env())
}
} }
impl MacroCallLoc { impl MacroCallLoc {
@ -659,7 +686,7 @@ impl MacroCallKind {
/// ExpansionInfo mainly describes how to map text range between src and expanded macro /// ExpansionInfo mainly describes how to map text range between src and expanded macro
// FIXME: can be expensive to create, we should check the use sites and maybe replace them with // FIXME: can be expensive to create, we should check the use sites and maybe replace them with
// simpler function calls if the map is only used once // simpler function calls if the map is only used once
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExpansionInfo { pub struct ExpansionInfo {
pub expanded: InMacroFile<SyntaxNode>, pub expanded: InMacroFile<SyntaxNode>,
/// The argument TokenTree or item for attributes /// The argument TokenTree or item for attributes
@ -689,6 +716,22 @@ impl ExpansionInfo {
/// Maps the passed in file range down into a macro expansion if it is the input to a macro call. /// Maps the passed in file range down into a macro expansion if it is the input to a macro call.
/// ///
/// Note this does a linear search through the entire backing vector of the spanmap. /// Note this does a linear search through the entire backing vector of the spanmap.
pub fn map_range_down_exact(
&self,
span: Span,
) -> Option<InMacroFile<impl Iterator<Item = SyntaxToken> + '_>> {
let tokens = self
.exp_map
.ranges_with_span_exact(span)
.flat_map(move |range| self.expanded.value.covering_element(range).into_token());
Some(InMacroFile::new(self.expanded.file_id, tokens))
}
/// Maps the passed in file range down into a macro expansion if it is the input to a macro call.
/// Unlike [`map_range_down_exact`], this will consider spans that contain the given span.
///
/// Note this does a linear search through the entire backing vector of the spanmap.
pub fn map_range_down( pub fn map_range_down(
&self, &self,
span: Span, span: Span,
@ -745,7 +788,7 @@ impl ExpansionInfo {
InFile::new( InFile::new(
self.arg.file_id, self.arg.file_id,
arg_map arg_map
.ranges_with_span(span) .ranges_with_span_exact(span)
.filter(|range| range.intersect(arg_range).is_some()) .filter(|range| range.intersect(arg_range).is_some())
.collect(), .collect(),
) )

View file

@ -266,10 +266,11 @@ mod tests {
let quoted = quote!(DUMMY =>#a); let quoted = quote!(DUMMY =>#a);
assert_eq!(quoted.to_string(), "hello"); assert_eq!(quoted.to_string(), "hello");
let t = format!("{quoted:?}"); let t = format!("{quoted:#?}");
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) } SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) } SUBTREE $$ 937550:0@0..0#0 937550:0@0..0#0
IDENT hello SpanData { range: 0..0, anchor: SpanAnchor(FileId(937550), 0), ctx: SyntaxContextId(0) }"#]].assert_eq(&t); IDENT hello 937550:0@0..0#0"#]]
.assert_eq(&t);
} }
#[test] #[test]

View file

@ -1,4 +1,5 @@
//! Span maps for real files and macro expansions. //! Span maps for real files and macro expansions.
use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId}; use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
use syntax::{AstNode, TextRange}; use syntax::{AstNode, TextRange};
use triomphe::Arc; use triomphe::Arc;

View file

@ -2617,6 +2617,15 @@ impl Macro {
} }
} }
pub fn is_env_or_option_env(&self, db: &dyn HirDatabase) -> bool {
match self.id {
MacroId::Macro2Id(it) => {
matches!(it.lookup(db.upcast()).expander, MacroExpander::BuiltInEager(eager) if eager.is_env_or_option_env())
}
MacroId::MacroRulesId(_) | MacroId::ProcMacroId(_) => false,
}
}
pub fn is_attr(&self, db: &dyn HirDatabase) -> bool { pub fn is_attr(&self, db: &dyn HirDatabase) -> bool {
matches!(self.kind(db), MacroKind::Attr) matches!(self.kind(db), MacroKind::Attr)
} }

View file

@ -681,19 +681,20 @@ impl<'db> SemanticsImpl<'db> {
.filter(|&(_, include_file_id)| include_file_id == file_id) .filter(|&(_, include_file_id)| include_file_id == file_id)
{ {
let macro_file = invoc.as_macro_file(); let macro_file = invoc.as_macro_file();
let expansion_info = cache let expansion_info = cache.entry(macro_file).or_insert_with(|| {
.entry(macro_file) let exp_info = macro_file.expansion_info(self.db.upcast());
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
let InMacroFile { file_id, value } = exp_info.expanded();
self.cache(value, file_id.into());
exp_info
});
// Create the source analyzer for the macro call scope // Create the source analyzer for the macro call scope
let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file())) let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file()))
else { else {
continue; continue;
}; };
{
let InMacroFile { file_id: macro_file, value } = expansion_info.expanded();
self.cache(value, macro_file.into());
}
// get mapped token in the include! macro file // get mapped token in the include! macro file
let span = span::SpanData { let span = span::SpanData {
@ -702,7 +703,7 @@ impl<'db> SemanticsImpl<'db> {
ctx: SyntaxContextId::ROOT, ctx: SyntaxContextId::ROOT,
}; };
let Some(InMacroFile { file_id, value: mut mapped_tokens }) = let Some(InMacroFile { file_id, value: mut mapped_tokens }) =
expansion_info.map_range_down(span) expansion_info.map_range_down_exact(span)
else { else {
continue; continue;
}; };
@ -753,22 +754,20 @@ impl<'db> SemanticsImpl<'db> {
let def_map = sa.resolver.def_map(); let def_map = sa.resolver.def_map();
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])]; let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| { let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let expansion_info = cache let exp_info = cache.entry(macro_file).or_insert_with(|| {
.entry(macro_file) let exp_info = macro_file.expansion_info(self.db.upcast());
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
{ let InMacroFile { file_id, value } = exp_info.expanded();
let InMacroFile { file_id, value } = expansion_info.expanded();
self.cache(value, file_id.into()); self.cache(value, file_id.into());
}
let InMacroFile { file_id, value: mapped_tokens } = exp_info
expansion_info.map_range_down(span)?; });
let InMacroFile { file_id, value: mapped_tokens } = exp_info.map_range_down(span)?;
let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect(); let mapped_tokens: SmallVec<[_; 2]> = mapped_tokens.collect();
// if the length changed we have found a mapping for the token // we have found a mapping for the token if the vec is non-empty
let res = mapped_tokens.is_empty().not().then_some(()); let res = mapped_tokens.is_empty().not().then_some(());
// requeue the tokens we got from mapping our current token down // requeue the tokens we got from mapping our current token down
stack.push((HirFileId::from(file_id), mapped_tokens)); stack.push((HirFileId::from(file_id), mapped_tokens));
@ -851,7 +850,13 @@ impl<'db> SemanticsImpl<'db> {
// remove any other token in this macro input, all their mappings are the // remove any other token in this macro input, all their mappings are the
// same as this one // same as this one
tokens.retain(|t| !text_range.contains_range(t.text_range())); tokens.retain(|t| !text_range.contains_range(t.text_range()));
process_expansion_for_token(&mut stack, file_id)
process_expansion_for_token(&mut stack, file_id).or(file_id
.eager_arg(self.db.upcast())
.and_then(|arg| {
// also descend into eager expansions
process_expansion_for_token(&mut stack, arg.as_macro_file())
}))
} else if let Some(meta) = ast::Meta::cast(parent) { } else if let Some(meta) = ast::Meta::cast(parent) {
// attribute we failed expansion for earlier, this might be a derive invocation // attribute we failed expansion for earlier, this might be a derive invocation
// or derive helper attribute // or derive helper attribute

View file

@ -1,7 +1,10 @@
//! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html) //! Completes environment variables defined by Cargo (https://doc.rust-lang.org/cargo/reference/environment-variables.html)
use hir::Semantics; use hir::MacroFileIdExt;
use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase}; use ide_db::syntax_helpers::node_ext::macro_call_for_string_token;
use syntax::ast::{self, IsString}; use syntax::{
ast::{self, IsString},
AstToken,
};
use crate::{ use crate::{
completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind, completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
@ -32,10 +35,24 @@ const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
pub(crate) fn complete_cargo_env_vars( pub(crate) fn complete_cargo_env_vars(
acc: &mut Completions, acc: &mut Completions,
ctx: &CompletionContext<'_>, ctx: &CompletionContext<'_>,
original: &ast::String,
expanded: &ast::String, expanded: &ast::String,
) -> Option<()> { ) -> Option<()> {
guard_env_macro(expanded, &ctx.sema)?; let is_in_env_expansion = ctx
let range = expanded.text_range_between_quotes()?; .sema
.hir_file_for(&expanded.syntax().parent()?)
.macro_file()
.map_or(false, |it| it.is_env_or_option_env(ctx.sema.db));
if !is_in_env_expansion {
let call = macro_call_for_string_token(expanded)?;
let makro = ctx.sema.resolve_macro_call(&call)?;
// We won't map into `option_env` as that generates `None` for non-existent env vars
// so fall back to this lookup
if !makro.is_env_or_option_env(ctx.sema.db) {
return None;
}
}
let range = original.text_range_between_quotes()?;
CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| { CARGO_DEFINED_VARS.iter().for_each(|&(var, detail)| {
let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var); let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
@ -46,18 +63,6 @@ pub(crate) fn complete_cargo_env_vars(
Some(()) Some(())
} }
fn guard_env_macro(string: &ast::String, semantics: &Semantics<'_, RootDatabase>) -> Option<()> {
let call = macro_call_for_string_token(string)?;
let name = call.path()?.segment()?.name_ref()?;
let makro = semantics.resolve_macro_call(&call)?;
let db = semantics.db;
match name.text().as_str() {
"env" | "option_env" if makro.kind(db) == hir::MacroKind::BuiltIn => Some(()),
_ => None,
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::{check_edit, completion_list}; use crate::tests::{check_edit, completion_list};
@ -68,7 +73,7 @@ mod tests {
&format!( &format!(
r#" r#"
#[rustc_builtin_macro] #[rustc_builtin_macro]
macro_rules! {macro_name} {{ macro {macro_name} {{
($var:literal) => {{ 0 }} ($var:literal) => {{ 0 }}
}} }}
@ -80,7 +85,7 @@ mod tests {
&format!( &format!(
r#" r#"
#[rustc_builtin_macro] #[rustc_builtin_macro]
macro_rules! {macro_name} {{ macro {macro_name} {{
($var:literal) => {{ 0 }} ($var:literal) => {{ 0 }}
}} }}

View file

@ -207,7 +207,7 @@ pub fn completions(
CompletionAnalysis::String { original, expanded: Some(expanded) } => { CompletionAnalysis::String { original, expanded: Some(expanded) } => {
completions::extern_abi::complete_extern_abi(acc, ctx, expanded); completions::extern_abi::complete_extern_abi(acc, ctx, expanded);
completions::format_string::format_string(acc, ctx, original, expanded); completions::format_string::format_string(acc, ctx, original, expanded);
completions::env_vars::complete_cargo_env_vars(acc, ctx, expanded); completions::env_vars::complete_cargo_env_vars(acc, ctx, original, expanded);
} }
CompletionAnalysis::UnexpandedAttrTT { CompletionAnalysis::UnexpandedAttrTT {
colon_prefix, colon_prefix,

View file

@ -1,10 +1,10 @@
use std::mem::discriminant; use std::{iter, mem::discriminant};
use crate::{ use crate::{
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
RangeInfo, TryToNav, RangeInfo, TryToNav,
}; };
use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics}; use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics};
use ide_db::{ use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader}, base_db::{AnchoredPath, FileId, FileLoader},
defs::{Definition, IdentClass}, defs::{Definition, IdentClass},
@ -74,11 +74,13 @@ pub(crate) fn goto_definition(
.filter_map(|token| { .filter_map(|token| {
let parent = token.parent()?; let parent = token.parent()?;
if let Some(tt) = ast::TokenTree::cast(parent.clone()) { if let Some(token) = ast::String::cast(token.clone()) {
if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) { if let Some(x) = try_lookup_include_path(sema, token, file_id) {
return Some(vec![x]); return Some(vec![x]);
} }
}
if ast::TokenTree::can_cast(parent.kind()) {
if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token) { if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token) {
return Some(vec![x]); return Some(vec![x]);
} }
@ -111,24 +113,17 @@ pub(crate) fn goto_definition(
fn try_lookup_include_path( fn try_lookup_include_path(
sema: &Semantics<'_, RootDatabase>, sema: &Semantics<'_, RootDatabase>,
tt: ast::TokenTree, token: ast::String,
token: SyntaxToken,
file_id: FileId, file_id: FileId,
) -> Option<NavigationTarget> { ) -> Option<NavigationTarget> {
let token = ast::String::cast(token)?; let file = sema.hir_file_for(&token.syntax().parent()?).macro_file()?;
let path = token.value()?.into_owned(); if !iter::successors(Some(file), |file| file.parent(sema.db).macro_file())
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?; // Check that we are in the eager argument expansion of an include macro
let name = macro_call.path()?.segment()?.name_ref()?; .any(|file| file.is_include_like_macro(sema.db) && file.eager_arg(sema.db).is_none())
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") { {
return None; return None;
} }
let path = token.value()?;
// Ignore non-built-in macros to account for shadowing
if let Some(it) = sema.resolve_macro_call(&macro_call) {
if !matches!(it.kind(sema.db), hir::MacroKind::BuiltIn) {
return None;
}
}
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?; let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
let size = sema.db.file_text(file_id).len().try_into().ok()?; let size = sema.db.file_text(file_id).len().try_into().ok()?;
@ -1531,6 +1526,26 @@ fn main() {
); );
} }
#[test]
fn goto_include_has_eager_input() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include_str {}
#[rustc_builtin_macro]
macro_rules! concat {}
fn main() {
let str = include_str!(concat!("foo", ".tx$0t"));
}
//- /foo.txt
// empty
//^file
"#,
);
}
#[test] #[test]
fn goto_doc_include_str() { fn goto_doc_include_str() {
check( check(

View file

@ -94,7 +94,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
<span class="brace">}</span> <span class="brace">}</span>
<span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="none macro">concat</span><span class="punctuation macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="comma macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro default_library library">include</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="macro default_library library macro">concat</span><span class="macro_bang macro">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"foo/"</span><span class="string_literal macro">,</span> <span class="string_literal macro">"foo.rs"</span><span class="parenthesis macro">)</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> <span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
<span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="parenthesis macro">(</span><span class="numeric_literal macro">92</span><span class="comma macro">,</span><span class="parenthesis macro">)</span><span class="operator macro">.</span><span class="field library macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span> <span class="macro default_library library">format_args</span><span class="macro_bang">!</span><span class="parenthesis macro">(</span><span class="string_literal macro">"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal macro">!"</span><span class="comma macro">,</span> <span class="parenthesis macro">(</span><span class="numeric_literal macro">92</span><span class="comma macro">,</span><span class="parenthesis macro">)</span><span class="operator macro">.</span><span class="field library macro">0</span><span class="parenthesis macro">)</span><span class="semicolon">;</span>

View file

@ -8,7 +8,12 @@ use expect_test::expect;
#[test] #[test]
fn test_derive_empty() { fn test_derive_empty() {
assert_expand("DeriveEmpty", r#"struct S;"#, expect!["SUBTREE $$ 1 1"], expect!["SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"]); assert_expand(
"DeriveEmpty",
r#"struct S;"#,
expect!["SUBTREE $$ 1 1"],
expect!["SUBTREE $$ 42:2@0..100#0 42:2@0..100#0"],
);
} }
#[test] #[test]
@ -21,15 +26,15 @@ fn test_derive_error() {
IDENT compile_error 1 IDENT compile_error 1
PUNCH ! [alone] 1 PUNCH ! [alone] 1
SUBTREE () 1 1 SUBTREE () 1 1
LITERAL "#[derive(DeriveError)] struct S ;" 1 LITERAL "#[derive(DeriveError)] struct S ;"1
PUNCH ; [alone] 1"##]], PUNCH ; [alone] 1"##]],
expect![[r##" expect![[r##"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT compile_error 42:2@0..100#0
PUNCH ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH ! [alone] 42:2@0..100#0
SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE () 42:2@0..100#0 42:2@0..100#0
LITERAL "#[derive(DeriveError)] struct S ;" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL "#[derive(DeriveError)] struct S ;"42:2@0..100#0
PUNCH ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]], PUNCH ; [alone] 42:2@0..100#0"##]],
); );
} }
@ -42,20 +47,20 @@ fn test_fn_like_macro_noop() {
SUBTREE $$ 1 1 SUBTREE $$ 1 1
IDENT ident 1 IDENT ident 1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL 0 1 LITERAL 01
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL 1 1 LITERAL 11
PUNCH , [alone] 1 PUNCH , [alone] 1
SUBTREE [] 1 1"#]], SUBTREE [] 1 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT ident 42:2@0..5#0
PUNCH , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@5..6#0
LITERAL 0 SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 042:2@7..8#0
PUNCH , [alone] SpanData { range: 8..9, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@8..9#0
LITERAL 1 SpanData { range: 10..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 142:2@10..11#0
PUNCH , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@11..12#0
SUBTREE [] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 14..15, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], SUBTREE [] 42:2@13..14#0 42:2@14..15#0"#]],
); );
} }
@ -70,10 +75,10 @@ fn test_fn_like_macro_clone_ident_subtree() {
PUNCH , [alone] 1 PUNCH , [alone] 1
SUBTREE [] 1 1"#]], SUBTREE [] 1 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT ident SpanData { range: 0..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT ident 42:2@0..5#0
PUNCH , [alone] SpanData { range: 5..6, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@5..6#0
SUBTREE [] SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 7..8, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], SUBTREE [] 42:2@7..8#0 42:2@7..8#0"#]],
); );
} }
@ -86,8 +91,8 @@ fn test_fn_like_macro_clone_raw_ident() {
SUBTREE $$ 1 1 SUBTREE $$ 1 1
IDENT r#async 1"#]], IDENT r#async 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT r#async SpanData { range: 0..7, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], IDENT r#async 42:2@0..7#0"#]],
); );
} }
@ -100,8 +105,8 @@ fn test_fn_like_fn_like_span_join() {
SUBTREE $$ 1 1 SUBTREE $$ 1 1
IDENT r#joined 1"#]], IDENT r#joined 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT r#joined SpanData { range: 0..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], IDENT r#joined 42:2@0..11#0"#]],
); );
} }
@ -116,10 +121,10 @@ fn test_fn_like_fn_like_span_ops() {
IDENT resolved_at_def_site 1 IDENT resolved_at_def_site 1
IDENT start_span 1"#]], IDENT start_span 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT set_def_site SpanData { range: 0..150, anchor: SpanAnchor(FileId(41), 1), ctx: SyntaxContextId(0) } IDENT set_def_site 41:1@0..150#0
IDENT resolved_at_def_site SpanData { range: 13..33, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT resolved_at_def_site 42:2@13..33#0
IDENT start_span SpanData { range: 34..34, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], IDENT start_span 42:2@34..34#0"#]],
); );
} }
@ -130,22 +135,22 @@ fn test_fn_like_mk_literals() {
r#""#, r#""#,
expect![[r#" expect![[r#"
SUBTREE $$ 1 1 SUBTREE $$ 1 1
LITERAL b"byte_string" 1 LITERAL b"byte_string"1
LITERAL 'c' 1 LITERAL 'c'1
LITERAL "string" 1 LITERAL "string"1
LITERAL 3.14f64 1 LITERAL 3.14f641
LITERAL 3.14 1 LITERAL 3.141
LITERAL 123i64 1 LITERAL 123i641
LITERAL 123 1"#]], LITERAL 1231"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
LITERAL b"byte_string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL b"byte_string"42:2@0..100#0
LITERAL 'c' SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 'c'42:2@0..100#0
LITERAL "string" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL "string"42:2@0..100#0
LITERAL 3.14f64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 3.14f6442:2@0..100#0
LITERAL 3.14 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 3.1442:2@0..100#0
LITERAL 123i64 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 123i6442:2@0..100#0
LITERAL 123 SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], LITERAL 12342:2@0..100#0"#]],
); );
} }
@ -159,9 +164,9 @@ fn test_fn_like_mk_idents() {
IDENT standard 1 IDENT standard 1
IDENT r#raw 1"#]], IDENT r#raw 1"#]],
expect![[r#" expect![[r#"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT standard SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT standard 42:2@0..100#0
IDENT r#raw SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"#]], IDENT r#raw 42:2@0..100#0"#]],
); );
} }
@ -172,48 +177,48 @@ fn test_fn_like_macro_clone_literals() {
r###"1u16, 2_u32, -4i64, 3.14f32, "hello bridge", "suffixed"suffix, r##"raw"##, 'a', b'b', c"null""###, r###"1u16, 2_u32, -4i64, 3.14f32, "hello bridge", "suffixed"suffix, r##"raw"##, 'a', b'b', c"null""###,
expect![[r###" expect![[r###"
SUBTREE $$ 1 1 SUBTREE $$ 1 1
LITERAL 1u16 1 LITERAL 1u161
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL 2_u32 1 LITERAL 2_u321
PUNCH , [alone] 1 PUNCH , [alone] 1
PUNCH - [alone] 1 PUNCH - [alone] 1
LITERAL 4i64 1 LITERAL 4i641
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL 3.14f32 1 LITERAL 3.14f321
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL "hello bridge" 1 LITERAL "hello bridge"1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL "suffixed"suffix 1 LITERAL "suffixed"suffix1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL r##"raw"## 1 LITERAL r##"raw"##1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL 'a' 1 LITERAL 'a'1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL b'b' 1 LITERAL b'b'1
PUNCH , [alone] 1 PUNCH , [alone] 1
LITERAL c"null" 1"###]], LITERAL c"null"1"###]],
expect![[r###" expect![[r###"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
LITERAL 1u16 SpanData { range: 0..4, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 1u1642:2@0..4#0
PUNCH , [alone] SpanData { range: 4..5, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@4..5#0
LITERAL 2_u32 SpanData { range: 6..11, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 2_u3242:2@6..11#0
PUNCH , [alone] SpanData { range: 11..12, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@11..12#0
PUNCH - [alone] SpanData { range: 13..14, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH - [alone] 42:2@13..14#0
LITERAL 4i64 SpanData { range: 14..18, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 4i6442:2@14..18#0
PUNCH , [alone] SpanData { range: 18..19, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@18..19#0
LITERAL 3.14f32 SpanData { range: 20..27, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 3.14f3242:2@20..27#0
PUNCH , [alone] SpanData { range: 27..28, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@27..28#0
LITERAL "hello bridge" SpanData { range: 29..43, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL "hello bridge"42:2@29..43#0
PUNCH , [alone] SpanData { range: 43..44, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@43..44#0
LITERAL "suffixed"suffix SpanData { range: 45..61, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL "suffixed"suffix42:2@45..61#0
PUNCH , [alone] SpanData { range: 61..62, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@61..62#0
LITERAL r##"raw"## SpanData { range: 63..73, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL r##"raw"##42:2@63..73#0
PUNCH , [alone] SpanData { range: 73..74, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@73..74#0
LITERAL 'a' SpanData { range: 75..78, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL 'a'42:2@75..78#0
PUNCH , [alone] SpanData { range: 78..79, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@78..79#0
LITERAL b'b' SpanData { range: 80..84, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL b'b'42:2@80..84#0
PUNCH , [alone] SpanData { range: 84..85, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH , [alone] 42:2@84..85#0
LITERAL c"null" SpanData { range: 86..93, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"###]], LITERAL c"null"42:2@86..93#0"###]],
); );
} }
@ -231,15 +236,15 @@ fn test_attr_macro() {
IDENT compile_error 1 IDENT compile_error 1
PUNCH ! [alone] 1 PUNCH ! [alone] 1
SUBTREE () 1 1 SUBTREE () 1 1
LITERAL "#[attr_error(some arguments)] mod m {}" 1 LITERAL "#[attr_error(some arguments)] mod m {}"1
PUNCH ; [alone] 1"##]], PUNCH ; [alone] 1"##]],
expect![[r##" expect![[r##"
SUBTREE $$ SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE $$ 42:2@0..100#0 42:2@0..100#0
IDENT compile_error SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } IDENT compile_error 42:2@0..100#0
PUNCH ! [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } PUNCH ! [alone] 42:2@0..100#0
SUBTREE () SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } SUBTREE () 42:2@0..100#0 42:2@0..100#0
LITERAL "#[attr_error(some arguments)] mod m {}" SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) } LITERAL "#[attr_error(some arguments)] mod m {}"42:2@0..100#0
PUNCH ; [alone] SpanData { range: 0..100, anchor: SpanAnchor(FileId(42), 2), ctx: SyntaxContextId(0) }"##]], PUNCH ; [alone] 42:2@0..100#0"##]],
); );
} }

View file

@ -91,7 +91,7 @@ fn assert_expand_impl(
let res = expander let res = expander
.expand(macro_name, fixture.into_subtree(call_site), attr, def_site, call_site, mixed_site) .expand(macro_name, fixture.into_subtree(call_site), attr, def_site, call_site, mixed_site)
.unwrap(); .unwrap();
expect_s.assert_eq(&format!("{res:?}")); expect_s.assert_eq(&format!("{res:#?}"));
} }
pub(crate) fn list() -> Vec<String> { pub(crate) fn list() -> Vec<String> {

View file

@ -26,9 +26,19 @@ use salsa::{InternId, InternValue};
use crate::MacroCallId; use crate::MacroCallId;
/// Interned [`SyntaxContextData`]. /// Interned [`SyntaxContextData`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SyntaxContextId(InternId); pub struct SyntaxContextId(InternId);
impl fmt::Debug for SyntaxContextId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "{}", self.0.as_u32())
} else {
f.debug_tuple("SyntaxContextId").field(&self.0).finish()
}
}
}
impl salsa::InternKey for SyntaxContextId { impl salsa::InternKey for SyntaxContextId {
fn from_intern_id(v: salsa::InternId) -> Self { fn from_intern_id(v: salsa::InternId) -> Self {
SyntaxContextId(v) SyntaxContextId(v)

View file

@ -44,7 +44,7 @@ pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId =
pub type Span = SpanData<SyntaxContextId>; pub type Span = SpanData<SyntaxContextId>;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpanData<Ctx> { pub struct SpanData<Ctx> {
/// The text range of this span, relative to the anchor. /// The text range of this span, relative to the anchor.
/// We need the anchor for incrementality, as storing absolute ranges will require /// We need the anchor for incrementality, as storing absolute ranges will require
@ -56,6 +56,26 @@ pub struct SpanData<Ctx> {
pub ctx: Ctx, pub ctx: Ctx,
} }
impl<Ctx: fmt::Debug> fmt::Debug for SpanData<Ctx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
fmt::Debug::fmt(&self.anchor.file_id.index(), f)?;
f.write_char(':')?;
fmt::Debug::fmt(&self.anchor.ast_id.into_raw(), f)?;
f.write_char('@')?;
fmt::Debug::fmt(&self.range, f)?;
f.write_char('#')?;
self.ctx.fmt(f)
} else {
f.debug_struct("SpanData")
.field("range", &self.range)
.field("anchor", &self.anchor)
.field("ctx", &self.ctx)
.finish()
}
}
}
impl<Ctx: Copy> SpanData<Ctx> { impl<Ctx: Copy> SpanData<Ctx> {
pub fn eq_ignoring_ctx(self, other: Self) -> bool { pub fn eq_ignoring_ctx(self, other: Self) -> bool {
self.anchor == other.anchor && self.range == other.range self.anchor == other.anchor && self.range == other.range

View file

@ -1,7 +1,7 @@
//! A map that maps a span to every position in a file. Usually maps a span to some range of positions. //! A map that maps a span to every position in a file. Usually maps a span to some range of positions.
//! Allows bidirectional lookup. //! Allows bidirectional lookup.
use std::hash::Hash; use std::{fmt, hash::Hash};
use stdx::{always, itertools::Itertools}; use stdx::{always, itertools::Itertools};
use syntax::{TextRange, TextSize}; use syntax::{TextRange, TextSize};
@ -52,7 +52,7 @@ where
/// Returns all [`TextRange`]s that correspond to the given span. /// Returns all [`TextRange`]s that correspond to the given span.
/// ///
/// Note this does a linear search through the entire backing vector. /// Note this does a linear search through the entire backing vector.
pub fn ranges_with_span(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_ pub fn ranges_with_span_exact(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_
where where
S: Copy, S: Copy,
{ {
@ -65,6 +65,25 @@ where
}) })
} }
/// Returns all [`TextRange`]s whose spans contain the given span.
///
/// Note this does a linear search through the entire backing vector.
pub fn ranges_with_span(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_
where
S: Copy,
{
self.spans.iter().enumerate().filter_map(move |(idx, &(end, s))| {
if s.anchor != span.anchor {
return None;
}
if !s.range.contains_range(span.range) {
return None;
}
let start = idx.checked_sub(1).map_or(TextSize::new(0), |prev| self.spans[prev].0);
Some(TextRange::new(start, end))
})
}
/// Returns the span at the given position. /// Returns the span at the given position.
pub fn span_at(&self, offset: TextSize) -> SpanData<S> { pub fn span_at(&self, offset: TextSize) -> SpanData<S> {
let entry = self.spans.partition_point(|&(it, _)| it <= offset); let entry = self.spans.partition_point(|&(it, _)| it <= offset);
@ -94,6 +113,16 @@ pub struct RealSpanMap {
end: TextSize, end: TextSize,
} }
impl fmt::Display for RealSpanMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "RealSpanMap({:?}):", self.file_id)?;
for span in self.pairs.iter() {
writeln!(f, "{}: {}", u32::from(span.0), span.1.into_raw().into_u32())?;
}
Ok(())
}
}
impl RealSpanMap { impl RealSpanMap {
/// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id). /// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id).
pub fn absolute(file_id: FileId) -> Self { pub fn absolute(file_id: FileId) -> Self {

View file

@ -194,21 +194,6 @@ impl<'a, A: ?Sized + Downcast, V: IntoBox<A>> VacantEntry<'a, A, V> {
mod tests { mod tests {
use super::*; use super::*;
#[derive(Clone, Debug, PartialEq)]
struct A(i32);
#[derive(Clone, Debug, PartialEq)]
struct B(i32);
#[derive(Clone, Debug, PartialEq)]
struct C(i32);
#[derive(Clone, Debug, PartialEq)]
struct D(i32);
#[derive(Clone, Debug, PartialEq)]
struct E(i32);
#[derive(Clone, Debug, PartialEq)]
struct F(i32);
#[derive(Clone, Debug, PartialEq)]
struct J(i32);
#[test] #[test]
fn test_varieties() { fn test_varieties() {
fn assert_send<T: Send>() {} fn assert_send<T: Send>() {}

View file

@ -177,17 +177,19 @@ fn print_debug_subtree<S: fmt::Debug>(
let align = " ".repeat(level); let align = " ".repeat(level);
let Delimiter { kind, open, close } = &subtree.delimiter; let Delimiter { kind, open, close } = &subtree.delimiter;
let aux = match kind { let delim = match kind {
DelimiterKind::Invisible => format!("$$ {:?} {:?}", open, close), DelimiterKind::Invisible => "$$",
DelimiterKind::Parenthesis => format!("() {:?} {:?}", open, close), DelimiterKind::Parenthesis => "()",
DelimiterKind::Brace => format!("{{}} {:?} {:?}", open, close), DelimiterKind::Brace => "{}",
DelimiterKind::Bracket => format!("[] {:?} {:?}", open, close), DelimiterKind::Bracket => "[]",
}; };
if subtree.token_trees.is_empty() { write!(f, "{align}SUBTREE {delim} ",)?;
write!(f, "{align}SUBTREE {aux}")?; fmt::Debug::fmt(&open, f)?;
} else { write!(f, " ")?;
writeln!(f, "{align}SUBTREE {aux}")?; fmt::Debug::fmt(&close, f)?;
if !subtree.token_trees.is_empty() {
writeln!(f)?;
for (idx, child) in subtree.token_trees.iter().enumerate() { for (idx, child) in subtree.token_trees.iter().enumerate() {
print_debug_token(f, child, level + 1)?; print_debug_token(f, child, level + 1)?;
if idx != subtree.token_trees.len() - 1 { if idx != subtree.token_trees.len() - 1 {
@ -208,16 +210,24 @@ fn print_debug_token<S: fmt::Debug>(
match tkn { match tkn {
TokenTree::Leaf(leaf) => match leaf { TokenTree::Leaf(leaf) => match leaf {
Leaf::Literal(lit) => write!(f, "{}LITERAL {} {:?}", align, lit.text, lit.span)?, Leaf::Literal(lit) => {
Leaf::Punct(punct) => write!( write!(f, "{}LITERAL {}", align, lit.text)?;
f, fmt::Debug::fmt(&lit.span, f)?;
"{}PUNCH {} [{}] {:?}", }
align, Leaf::Punct(punct) => {
punct.char, write!(
if punct.spacing == Spacing::Alone { "alone" } else { "joint" }, f,
punct.span "{}PUNCH {} [{}] ",
)?, align,
Leaf::Ident(ident) => write!(f, "{}IDENT {} {:?}", align, ident.text, ident.span)?, punct.char,
if punct.spacing == Spacing::Alone { "alone" } else { "joint" },
)?;
fmt::Debug::fmt(&punct.span, f)?;
}
Leaf::Ident(ident) => {
write!(f, "{}IDENT {} ", align, ident.text)?;
fmt::Debug::fmt(&ident.span, f)?;
}
}, },
TokenTree::Subtree(subtree) => { TokenTree::Subtree(subtree) => {
print_debug_subtree(f, subtree, level)?; print_debug_subtree(f, subtree, level)?;