feat: Support macro calls in eager macros for IDE features

This commit is contained in:
Lukas Wirth 2024-03-14 12:02:23 +01:00
parent 9767156a29
commit d2f8eae2ec
22 changed files with 420 additions and 226 deletions

View file

@ -298,6 +298,7 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
}
};
// FIXME: The def site spans here are wrong, those should point to the name, not the whole ast node
match id {
MacroId::Macro2Id(it) => {
let loc: Macro2Loc = it.lookup(db);
@ -315,7 +316,6 @@ fn macro_def(db: &dyn DefDatabase, id: MacroId) -> MacroDefId {
edition: loc.edition,
}
}
MacroId::MacroRulesId(it) => {
let loc: MacroRulesLoc = it.lookup(db);

View file

@ -560,17 +560,14 @@ impl<'a> Ctx<'a> {
fn lower_macro_call(&mut self, m: &ast::MacroCall) -> Option<FileItemTreeId<MacroCall>> {
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
})?);
let ast_id = self.source_ast_id_map.ast_id(m);
let expand_to = hir_expand::ExpandTo::from_call_site(m);
let res = MacroCall {
path,
ast_id,
expand_to,
call_site: span_map.span_for_range(m.syntax().text_range()),
};
let res = MacroCall { path, ast_id, expand_to, call_site: span_map.span_for_range(range) };
Some(id(self.data().macro_calls.alloc(res)))
}

View file

@ -278,7 +278,7 @@ m!();
// AstId: 2
pub macro m2 { ... }
// AstId: 3, Span: 0:3@0..5#0, ExpandTo: Items
// AstId: 3, Span: 0:3@0..1#0, ExpandTo: Items
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 span_map = db.span_map(self.file_id);
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
})
})?;
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")));
};
let call_site = span_map.span_for_range(self.value.syntax().text_range());
macro_call_as_call_id_with_eager(
db,
&AstIdWithPath::new(ast_id.file_id, ast_id.value, path),

View file

@ -171,7 +171,7 @@ 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<'_>,
id: AttrId,
) -> 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
})?);
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 value = match lit.kind() {
ast::LiteralKind::String(string) => string.value()?.into(),

View file

@ -19,14 +19,14 @@ use crate::{
};
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)]
pub enum BuiltinFnLikeExpander {
pub enum $LAZY {
$($kind),*
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EagerExpander {
pub enum $EAGER {
$($e_kind),*
}
@ -84,6 +84,17 @@ impl EagerExpander {
pub fn is_include(&self) -> bool {
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(
@ -93,7 +104,7 @@ pub fn find_builtin_macro(
}
register_builtin! {
LAZY:
BuiltinFnLikeExpander:
(column, Column) => line_expand,
(file, File) => file_expand,
(line, Line) => line_expand,
@ -114,7 +125,7 @@ register_builtin! {
(format_args_nl, FormatArgsNl) => format_args_nl_expand,
(quote, Quote) => quote_expand,
EAGER:
EagerExpander:
(compile_error, CompileError) => compile_error_expand,
(concat, Concat) => concat_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 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 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 token = ast::ByteString::cast(lit)?;
token.value().map(|it| it.into_owned())
token.value().map(|it| (it.into_owned(), span))
}
fn compile_error_expand(
@ -452,7 +466,7 @@ fn compile_error_expand(
) -> ExpandResult<tt::Subtree> {
let err = match &*tt.token_trees {
[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"),
},
_ => ExpandError::other("`compile_error!` argument must be a string"),
@ -465,10 +479,16 @@ fn concat_expand(
_db: &dyn ExpandDatabase,
_arg_id: MacroCallId,
tt: &tt::Subtree,
span: Span,
_: Span,
) -> ExpandResult<tt::Subtree> {
let mut err = None;
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() {
// 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
@ -486,11 +506,14 @@ fn concat_expand(
// 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
// as-is.
if let Some(c) = unquote_char(it) {
if let Some((c, span)) = unquote_char(it) {
text.push(c);
record_span(span);
} 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);
record_span(span);
}
}
// handle boolean literals
@ -498,6 +521,7 @@ fn concat_expand(
if i % 2 == 0 && (id.text == "true" || id.text == "false") =>
{
text.push_str(id.text.as_str());
record_span(id.span);
}
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 }
}
@ -512,18 +537,25 @@ fn concat_bytes_expand(
_db: &dyn ExpandDatabase,
_arg_id: MacroCallId,
tt: &tt::Subtree,
span: Span,
call_site: Span,
) -> ExpandResult<tt::Subtree> {
let mut bytes = Vec::new();
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() {
match t {
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
let token = ast::make::tokens::literal(&lit.to_string());
record_span(lit.span);
match token.kind() {
syntax::SyntaxKind::BYTE => bytes.push(token.text().to_owned()),
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()));
}
_ => {
@ -534,7 +566,7 @@ fn concat_bytes_expand(
}
tt::TokenTree::Leaf(tt::Leaf::Punct(punct)) if i % 2 == 1 && punct.char == ',' => (),
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);
break;
}
@ -546,17 +578,24 @@ fn concat_bytes_expand(
}
}
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: {
Itertools::intersperse_with(
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 {
char: ',',
spacing: tt::Spacing::Alone,
span,
span: call_site,
}))
},
)
@ -569,13 +608,15 @@ fn concat_bytes_expand(
fn concat_bytes_expand_subtree(
tree: &tt::Subtree,
bytes: &mut Vec<String>,
mut record_span: impl FnMut(Span),
) -> Result<(), ExpandError> {
for (ti, tt) in tree.token_trees.iter().enumerate() {
match tt {
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
let lit = ast::make::tokens::literal(&lit.to_string());
tt::TokenTree::Leaf(tt::Leaf::Literal(it)) => {
let lit = ast::make::tokens::literal(&it.to_string());
match lit.kind() {
syntax::SyntaxKind::BYTE | syntax::SyntaxKind::INT_NUMBER => {
record_span(it.span);
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
.first()
.and_then(|tt| match tt {
@ -675,7 +716,7 @@ pub fn include_input_to_file_id(
arg_id: MacroCallId,
arg: &tt::Subtree,
) -> 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(
@ -701,7 +742,7 @@ fn include_str_expand(
tt: &tt::Subtree,
span: Span,
) -> ExpandResult<tt::Subtree> {
let path = match parse_string(tt) {
let (path, span) = match parse_string(tt) {
Ok(it) => it,
Err(e) => {
return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e)
@ -736,7 +777,7 @@ fn env_expand(
tt: &tt::Subtree,
span: Span,
) -> ExpandResult<tt::Subtree> {
let key = match parse_string(tt) {
let (key, span) = match parse_string(tt) {
Ok(it) => it,
Err(e) => {
return ExpandResult::new(tt::Subtree::empty(DelimSpan { open: span, close: span }), e)
@ -766,18 +807,24 @@ fn option_env_expand(
db: &dyn ExpandDatabase,
arg_id: MacroCallId,
tt: &tt::Subtree,
span: Span,
call_site: Span,
) -> ExpandResult<tt::Subtree> {
let key = match parse_string(tt) {
let (key, span) = match parse_string(tt) {
Ok(it) => it,
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) {
None => quote! {span => #dollar_crate::option::Option::None::<&str> },
Some(s) => quote! {span => #dollar_crate::option::Option::Some(#s) },
None => quote! {call_site => #dollar_crate::option::Option::None::<&str> },
Some(s) => {
let s = quote! (span => #s);
quote! {call_site => #dollar_crate::option::Option::Some(#s) }
}
};
ExpandResult::ok(expanded)

View file

@ -323,6 +323,9 @@ impl HirFileIdExt for HirFileId {
}
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;
/// If this is a macro call, returns the syntax node of the call.
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()
}
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 {
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(..))
}
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 {
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 { .. })
}
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()
}
}
@ -478,6 +497,14 @@ impl MacroDefId {
pub fn is_include(&self) -> bool {
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 {
@ -659,7 +686,7 @@ impl MacroCallKind {
/// 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
// simpler function calls if the map is only used once
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExpansionInfo {
pub expanded: InMacroFile<SyntaxNode>,
/// 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.
///
/// 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(
&self,
span: Span,
@ -745,7 +788,7 @@ impl ExpansionInfo {
InFile::new(
self.arg.file_id,
arg_map
.ranges_with_span(span)
.ranges_with_span_exact(span)
.filter(|range| range.intersect(arg_range).is_some())
.collect(),
)

View file

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

View file

@ -1,4 +1,5 @@
//! Span maps for real files and macro expansions.
use span::{FileId, HirFileId, HirFileIdRepr, MacroFileId, Span, SyntaxContextId};
use syntax::{AstNode, TextRange};
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 {
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)
{
let macro_file = invoc.as_macro_file();
let expansion_info = cache
.entry(macro_file)
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
let expansion_info = cache.entry(macro_file).or_insert_with(|| {
let exp_info = 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
let Some(sa) = self.analyze_no_infer(&self.parse_or_expand(expansion_info.call_file()))
else {
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
let span = span::SpanData {
@ -702,7 +703,7 @@ impl<'db> SemanticsImpl<'db> {
ctx: SyntaxContextId::ROOT,
};
let Some(InMacroFile { file_id, value: mut mapped_tokens }) =
expansion_info.map_range_down(span)
expansion_info.map_range_down_exact(span)
else {
continue;
};
@ -753,22 +754,20 @@ impl<'db> SemanticsImpl<'db> {
let def_map = sa.resolver.def_map();
let mut stack: Vec<(_, SmallVec<[_; 2]>)> = vec![(file_id, smallvec![token])];
let mut process_expansion_for_token = |stack: &mut Vec<_>, macro_file| {
let expansion_info = cache
.entry(macro_file)
.or_insert_with(|| macro_file.expansion_info(self.db.upcast()));
let exp_info = cache.entry(macro_file).or_insert_with(|| {
let exp_info = macro_file.expansion_info(self.db.upcast());
{
let InMacroFile { file_id, value } = expansion_info.expanded();
let InMacroFile { file_id, value } = exp_info.expanded();
self.cache(value, file_id.into());
}
let InMacroFile { file_id, value: mapped_tokens } =
expansion_info.map_range_down(span)?;
exp_info
});
let InMacroFile { file_id, value: mapped_tokens } = exp_info.map_range_down(span)?;
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(());
// requeue the tokens we got from mapping our current token down
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
// same as this one
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) {
// attribute we failed expansion for earlier, this might be a derive invocation
// 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)
use hir::Semantics;
use ide_db::{syntax_helpers::node_ext::macro_call_for_string_token, RootDatabase};
use syntax::ast::{self, IsString};
use hir::MacroFileIdExt;
use ide_db::syntax_helpers::node_ext::macro_call_for_string_token;
use syntax::{
ast::{self, IsString},
AstToken,
};
use crate::{
completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
@ -32,10 +35,24 @@ const CARGO_DEFINED_VARS: &[(&str, &str)] = &[
pub(crate) fn complete_cargo_env_vars(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
original: &ast::String,
expanded: &ast::String,
) -> Option<()> {
guard_env_macro(expanded, &ctx.sema)?;
let range = expanded.text_range_between_quotes()?;
let is_in_env_expansion = ctx
.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)| {
let mut item = CompletionItem::new(CompletionItemKind::Keyword, range, var);
@ -46,18 +63,6 @@ pub(crate) fn complete_cargo_env_vars(
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)]
mod tests {
use crate::tests::{check_edit, completion_list};
@ -68,7 +73,7 @@ mod tests {
&format!(
r#"
#[rustc_builtin_macro]
macro_rules! {macro_name} {{
macro {macro_name} {{
($var:literal) => {{ 0 }}
}}
@ -80,7 +85,7 @@ mod tests {
&format!(
r#"
#[rustc_builtin_macro]
macro_rules! {macro_name} {{
macro {macro_name} {{
($var:literal) => {{ 0 }}
}}

View file

@ -207,7 +207,7 @@ pub fn completions(
CompletionAnalysis::String { original, expanded: Some(expanded) } => {
completions::extern_abi::complete_extern_abi(acc, ctx, 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 {
colon_prefix,

View file

@ -1,10 +1,10 @@
use std::mem::discriminant;
use std::{iter, mem::discriminant};
use crate::{
doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
RangeInfo, TryToNav,
};
use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics};
use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileId, FileLoader},
defs::{Definition, IdentClass},
@ -74,11 +74,13 @@ pub(crate) fn goto_definition(
.filter_map(|token| {
let parent = token.parent()?;
if let Some(tt) = ast::TokenTree::cast(parent.clone()) {
if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) {
if let Some(token) = ast::String::cast(token.clone()) {
if let Some(x) = try_lookup_include_path(sema, token, file_id) {
return Some(vec![x]);
}
}
if ast::TokenTree::can_cast(parent.kind()) {
if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token) {
return Some(vec![x]);
}
@ -111,24 +113,17 @@ pub(crate) fn goto_definition(
fn try_lookup_include_path(
sema: &Semantics<'_, RootDatabase>,
tt: ast::TokenTree,
token: SyntaxToken,
token: ast::String,
file_id: FileId,
) -> Option<NavigationTarget> {
let token = ast::String::cast(token)?;
let path = token.value()?.into_owned();
let macro_call = tt.syntax().parent().and_then(ast::MacroCall::cast)?;
let name = macro_call.path()?.segment()?.name_ref()?;
if !matches!(&*name.text(), "include" | "include_str" | "include_bytes") {
let file = sema.hir_file_for(&token.syntax().parent()?).macro_file()?;
if !iter::successors(Some(file), |file| file.parent(sema.db).macro_file())
// Check that we are in the eager argument expansion of an include macro
.any(|file| file.is_include_like_macro(sema.db) && file.eager_arg(sema.db).is_none())
{
return None;
}
// 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 path = token.value()?;
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()?;
@ -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]
fn goto_doc_include_str() {
check(

View file

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

View file

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

View file

@ -26,9 +26,19 @@ use salsa::{InternId, InternValue};
use crate::MacroCallId;
/// Interned [`SyntaxContextData`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
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 {
fn from_intern_id(v: salsa::InternId) -> Self {
SyntaxContextId(v)

View file

@ -44,7 +44,7 @@ pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId =
pub type Span = SpanData<SyntaxContextId>;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpanData<Ctx> {
/// The text range of this span, relative to the anchor.
/// We need the anchor for incrementality, as storing absolute ranges will require
@ -56,6 +56,26 @@ pub struct SpanData<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> {
pub fn eq_ignoring_ctx(self, other: Self) -> bool {
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.
//! Allows bidirectional lookup.
use std::hash::Hash;
use std::{fmt, hash::Hash};
use stdx::{always, itertools::Itertools};
use syntax::{TextRange, TextSize};
@ -52,7 +52,7 @@ where
/// Returns all [`TextRange`]s that correspond to 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> + '_
pub fn ranges_with_span_exact(&self, span: SpanData<S>) -> impl Iterator<Item = TextRange> + '_
where
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.
pub fn span_at(&self, offset: TextSize) -> SpanData<S> {
let entry = self.spans.partition_point(|&(it, _)| it <= offset);
@ -94,6 +113,16 @@ pub struct RealSpanMap {
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 {
/// Creates a real file span map that returns absolute ranges (relative ranges to the root ast id).
pub fn absolute(file_id: FileId) -> Self {

View file

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