Fix error spans for include! and compile_error!

This commit is contained in:
Lukas Wirth 2024-07-29 15:57:01 +02:00
parent ae9c553902
commit d46060b168
4 changed files with 56 additions and 29 deletions

View file

@ -460,10 +460,10 @@ fn compile_error_expand(
let err = match &*tt.token_trees { let err = match &*tt.token_trees {
[tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { [tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
symbol: text, symbol: text,
span, span: _,
kind: tt::LitKind::Str | tt::LitKind::StrRaw(_), kind: tt::LitKind::Str | tt::LitKind::StrRaw(_),
suffix: _, suffix: _,
}))] => ExpandError::other(*span, Box::from(unescape_str(text).as_str())), }))] => ExpandError::other(span, Box::from(unescape_str(text).as_str())),
_ => ExpandError::other(span, "`compile_error!` argument must be a string"), _ => ExpandError::other(span, "`compile_error!` argument must be a string"),
}; };
@ -706,18 +706,19 @@ fn relative_file(
fn parse_string(tt: &tt::Subtree) -> Result<(Symbol, Span), ExpandError> { fn parse_string(tt: &tt::Subtree) -> Result<(Symbol, Span), ExpandError> {
tt.token_trees tt.token_trees
.first() .first()
.ok_or(tt.delimiter.open.cover(tt.delimiter.close))
.and_then(|tt| match tt { .and_then(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
symbol: text, symbol: text,
span, span,
kind: tt::LitKind::Str, kind: tt::LitKind::Str,
suffix: _, suffix: _,
})) => Some((unescape_str(text), *span)), })) => Ok((unescape_str(text), *span)),
// FIXME: We wrap expression fragments in parentheses which can break this expectation // FIXME: We wrap expression fragments in parentheses which can break this expectation
// here // here
// Remove this once we handle none delims correctly // Remove this once we handle none delims correctly
tt::TokenTree::Subtree(t) if t.delimiter.kind == DelimiterKind::Parenthesis => { tt::TokenTree::Subtree(tt) if tt.delimiter.kind == DelimiterKind::Parenthesis => {
t.token_trees.first().and_then(|tt| match tt { tt.token_trees.first().and_then(|tt| match tt {
tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal { tt::TokenTree::Leaf(tt::Leaf::Literal(tt::Literal {
symbol: text, symbol: text,
span, span,
@ -727,9 +728,11 @@ fn parse_string(tt: &tt::Subtree) -> Result<(Symbol, Span), ExpandError> {
_ => None, _ => None,
}) })
} }
_ => None, .ok_or(tt.delimiter.open.cover(tt.delimiter.close)),
::tt::TokenTree::Leaf(l) => Err(*l.span()),
::tt::TokenTree::Subtree(tt) => Err(tt.delimiter.open.cover(tt.delimiter.close)),
}) })
.ok_or(ExpandError::other(tt.delimiter.open, "expected string literal")) .map_err(|span| ExpandError::other(span, "expected string literal"))
} }
fn include_expand( fn include_expand(
@ -763,7 +766,8 @@ pub fn include_input_to_file_id(
arg_id: MacroCallId, arg_id: MacroCallId,
arg: &tt::Subtree, arg: &tt::Subtree,
) -> Result<EditionedFileId, ExpandError> { ) -> Result<EditionedFileId, ExpandError> {
relative_file(db, arg_id, parse_string(arg)?.0.as_str(), false, arg.delimiter.open) let (s, span) = parse_string(arg)?;
relative_file(db, arg_id, s.as_str(), false, span)
} }
fn include_bytes_expand( fn include_bytes_expand(

View file

@ -837,13 +837,17 @@ fn macro_call_diagnostics(
let node = let node =
InFile::new(file_id, db.ast_id_map(file_id).get_erased(loc.kind.erased_ast_id())); InFile::new(file_id, db.ast_id_map(file_id).get_erased(loc.kind.erased_ast_id()));
let (message, error) = err.render_to_string(db.upcast()); let (message, error) = err.render_to_string(db.upcast());
let precise_location = Some( let precise_location = if err.span().anchor.file_id == file_id {
err.span().range Some(
+ db.ast_id_map(err.span().anchor.file_id.into()) err.span().range
.get_erased(err.span().anchor.ast_id) + db.ast_id_map(err.span().anchor.file_id.into())
.text_range() .get_erased(err.span().anchor.ast_id)
.start(), .text_range()
); .start(),
)
} else {
None
};
acc.push(MacroError { node, precise_location, message, error }.into()); acc.push(MacroError { node, precise_location, message, error }.into());
} }
@ -1798,13 +1802,17 @@ impl DefWithBody {
BodyDiagnostic::MacroError { node, err } => { BodyDiagnostic::MacroError { node, err } => {
let (message, error) = err.render_to_string(db.upcast()); let (message, error) = err.render_to_string(db.upcast());
let precise_location = Some( let precise_location = if err.span().anchor.file_id == node.file_id {
err.span().range Some(
+ db.ast_id_map(err.span().anchor.file_id.into()) err.span().range
.get_erased(err.span().anchor.ast_id) + db.ast_id_map(err.span().anchor.file_id.into())
.text_range() .get_erased(err.span().anchor.ast_id)
.start(), .text_range()
); .start(),
)
} else {
None
};
MacroError { MacroError {
node: (*node).map(|it| it.into()), node: (*node).map(|it| it.into()),
precise_location, precise_location,

View file

@ -48,7 +48,7 @@ macro_rules! include { () => {} }
macro_rules! compile_error { () => {} } macro_rules! compile_error { () => {} }
include!("doesntexist"); include!("doesntexist");
//^^^^^^^ error: failed to load file `doesntexist` //^^^^^^^^^^^^^ error: failed to load file `doesntexist`
compile_error!("compile_error macro works"); compile_error!("compile_error macro works");
//^^^^^^^^^^^^^ error: compile_error macro works //^^^^^^^^^^^^^ error: compile_error macro works
@ -128,7 +128,7 @@ macro_rules! env { () => {} }
macro_rules! concat { () => {} } macro_rules! concat { () => {} }
include!(concat!(env!("OUT_DIR"), "/out.rs")); include!(concat!(env!("OUT_DIR"), "/out.rs"));
//^^^^^^^ error: `OUT_DIR` not set, enable "build scripts" to fix //^^^^^^^^^ error: `OUT_DIR` not set, enable "build scripts" to fix
"#, "#,
); );
} }
@ -163,20 +163,25 @@ macro_rules! include {}
#[rustc_builtin_macro] #[rustc_builtin_macro]
macro_rules! compile_error {} macro_rules! compile_error {}
#[rustc_builtin_macro]
macro_rules! concat {}
fn main() { fn main() {
// Test a handful of built-in (eager) macros: // Test a handful of built-in (eager) macros:
include!(invalid); include!(invalid);
//^^^^^^^ error: expected string literal //^^^^^^^ error: expected string literal
include!("does not exist"); include!("does not exist");
//^^^^^^^ error: failed to load file `does not exist` //^^^^^^^^^^^^^^^^ error: failed to load file `does not exist`
include!(concat!("does ", "not ", "exist"));
//^^^^^^^^^^^^^^^^^^^^^^^^^^ error: failed to load file `does not exist`
env!(invalid); env!(invalid);
//^^^ error: expected string literal //^^^^^^^ error: expected string literal
env!("OUT_DIR"); env!("OUT_DIR");
//^^^ error: `OUT_DIR` not set, enable "build scripts" to fix //^^^^^^^^^ error: `OUT_DIR` not set, enable "build scripts" to fix
compile_error!("compile_error works"); compile_error!("compile_error works");
//^^^^^^^^^^^^^ error: compile_error works //^^^^^^^^^^^^^ error: compile_error works
@ -201,7 +206,7 @@ fn f() {
m!(); m!();
m!(hi); m!(hi);
//^ error: leftover tokens //^ error: leftover tokens
} }
"#, "#,
); );

View file

@ -33,6 +33,16 @@ pub const FIXUP_ERASED_FILE_AST_ID_MARKER: ErasedFileAstId =
pub type Span = SpanData<SyntaxContextId>; pub type Span = SpanData<SyntaxContextId>;
impl Span {
pub fn cover(self, other: Span) -> Span {
if self.anchor != other.anchor {
return self;
}
let range = self.range.cover(other.range);
Span { range, ..self }
}
}
/// Spans represent a region of code, used by the IDE to be able link macro inputs and outputs /// Spans represent a region of code, used by the IDE to be able link macro inputs and outputs
/// together. Positions in spans are relative to some [`SpanAnchor`] to make them more incremental /// together. Positions in spans are relative to some [`SpanAnchor`] to make them more incremental
/// friendly. /// friendly.