Auto merge of #112500 - lukas-code:span-ctxt, r=petrochenkov

Fix argument removal suggestion around macros

Fixes #112437.
Fixes #113866.
Helps with #114255.

The issue was that `span.find_ancestor_inside(outer)` could previously return a span with a different expansion context from `outer`.

This happens for example for the built-in macro `panic!`, which expands to another macro call of `panic_2021!` or `panic_2015!`. Because the call site of `panic_20xx!` has not associated source code, its span currently points to the call site of `panic!` instead.

Something similar also happens items that get desugared in AST->HIR lowering. For example, `for` loops get two spans: One "inner" span that has the `.desugaring_kind()` kind set to `DesugaringKind::ForLoop` and one "outer" span that does not. Similar to the macro situation, both of these spans point to the same source code, but have different expansion contexts.

This causes problems, because joining two spans with different expansion contexts will usually[^1] not actually join them together to avoid creating "spaghetti" spans that go from the macro definition to the macro call. For example, in the following snippet `full_span` might not actually contain the `adjusted_start` and `adjusted_end`. This caused the broken suggestion / debug ICE in the linked issues.
```rust
let adjusted_start = start.find_ancestor_inside(shared_ancestor);
let adjusted_end = end.find_ancestor_inside(shared_ancestor);
let full_span = adjusted_start.to(adjusted_end)
```

To fix the issue, this PR introduces a new method, `find_ancestor_inside_same_ctxt`, which combines the functionality of `find_ancestor_inside` and `find_ancestor_in_same_ctxt`: It finds an ancestor span that is contained within the parent *and* has the same syntax context, and is therefore safe to extend. This new method should probably be used everywhere, where the returned span is extended, but for now it is just used for the argument removal suggestion.

Additionally, this PR fixes a second issue where the function call itself is inside a macro but the arguments come from outside the macro. The test is added in the first commit to include stderr diff, so this is best reviewed commit by commit.

[^1]: If one expansion context is the root context and the other is not.
This commit is contained in:
bors 2023-08-16 14:47:01 +00:00
commit c94cb834d0
5 changed files with 198 additions and 51 deletions

View File

@ -519,7 +519,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// suggestions and labels are (more) correct when an arg is a // suggestions and labels are (more) correct when an arg is a
// macro invocation. // macro invocation.
let normalize_span = |span: Span| -> Span { let normalize_span = |span: Span| -> Span {
let normalized_span = span.find_ancestor_inside(error_span).unwrap_or(span); let normalized_span = span.find_ancestor_inside_same_ctxt(error_span).unwrap_or(span);
// Sometimes macros mess up the spans, so do not normalize the // Sometimes macros mess up the spans, so do not normalize the
// arg span to equal the error span, because that's less useful // arg span to equal the error span, because that's less useful
// than pointing out the arg expr in the wrong context. // than pointing out the arg expr in the wrong context.
@ -929,7 +929,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}; };
labels.push((provided_span, format!("unexpected argument{provided_ty_name}"))); labels.push((provided_span, format!("unexpected argument{provided_ty_name}")));
let mut span = provided_span; let mut span = provided_span;
if span.can_be_used_for_suggestions() { if span.can_be_used_for_suggestions()
&& error_span.can_be_used_for_suggestions()
{
if arg_idx.index() > 0 if arg_idx.index() > 0
&& let Some((_, prev)) = provided_arg_tys && let Some((_, prev)) = provided_arg_tys
.get(ProvidedIdx::from_usize(arg_idx.index() - 1) .get(ProvidedIdx::from_usize(arg_idx.index() - 1)
@ -1220,22 +1222,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}; };
if let Some(suggestion_text) = suggestion_text { if let Some(suggestion_text) = suggestion_text {
let source_map = self.sess().source_map(); let source_map = self.sess().source_map();
let (mut suggestion, suggestion_span) = let (mut suggestion, suggestion_span) = if let Some(call_span) =
if let Some(call_span) = full_call_span.find_ancestor_inside(error_span) { full_call_span.find_ancestor_inside_same_ctxt(error_span)
("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi())) {
} else { ("(".to_string(), call_span.shrink_to_hi().to(error_span.shrink_to_hi()))
( } else {
format!( (
"{}(", format!(
source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| { "{}(",
fn_def_id.map_or("".to_string(), |fn_def_id| { source_map.span_to_snippet(full_call_span).unwrap_or_else(|_| {
tcx.item_name(fn_def_id).to_string() fn_def_id.map_or("".to_string(), |fn_def_id| {
}) tcx.item_name(fn_def_id).to_string()
}) })
), })
error_span, ),
) error_span,
}; )
};
let mut needs_comma = false; let mut needs_comma = false;
for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() { for (expected_idx, provided_idx) in matched_inputs.iter_enumerated() {
if needs_comma { if needs_comma {

View File

@ -686,6 +686,12 @@ impl Span {
} }
/// Walk down the expansion ancestors to find a span that's contained within `outer`. /// Walk down the expansion ancestors to find a span that's contained within `outer`.
///
/// The span returned by this method may have a different [`SyntaxContext`] as `outer`.
/// If you need to extend the span, use [`find_ancestor_inside_same_ctxt`] instead,
/// because joining spans with different syntax contexts can create unexpected results.
///
/// [`find_ancestor_inside_same_ctxt`]: Self::find_ancestor_inside_same_ctxt
pub fn find_ancestor_inside(mut self, outer: Span) -> Option<Span> { pub fn find_ancestor_inside(mut self, outer: Span) -> Option<Span> {
while !outer.contains(self) { while !outer.contains(self) {
self = self.parent_callsite()?; self = self.parent_callsite()?;
@ -693,11 +699,34 @@ impl Span {
Some(self) Some(self)
} }
/// Like `find_ancestor_inside`, but specifically for when spans might not /// Walk down the expansion ancestors to find a span with the same [`SyntaxContext`] as
/// overlaps. Take care when using this, and prefer `find_ancestor_inside` /// `other`.
/// when you know that the spans are nested (modulo macro expansion). ///
/// Like [`find_ancestor_inside_same_ctxt`], but specifically for when spans might not
/// overlap. Take care when using this, and prefer [`find_ancestor_inside`] or
/// [`find_ancestor_inside_same_ctxt`] when you know that the spans are nested (modulo
/// macro expansion).
///
/// [`find_ancestor_inside`]: Self::find_ancestor_inside
/// [`find_ancestor_inside_same_ctxt`]: Self::find_ancestor_inside_same_ctxt
pub fn find_ancestor_in_same_ctxt(mut self, other: Span) -> Option<Span> { pub fn find_ancestor_in_same_ctxt(mut self, other: Span) -> Option<Span> {
while !Span::eq_ctxt(self, other) { while !self.eq_ctxt(other) {
self = self.parent_callsite()?;
}
Some(self)
}
/// Walk down the expansion ancestors to find a span that's contained within `outer` and
/// has the same [`SyntaxContext`] as `outer`.
///
/// This method is the combination of [`find_ancestor_inside`] and
/// [`find_ancestor_in_same_ctxt`] and should be preferred when extending the returned span.
/// If you do not need to modify the span, use [`find_ancestor_inside`] instead.
///
/// [`find_ancestor_inside`]: Self::find_ancestor_inside
/// [`find_ancestor_in_same_ctxt`]: Self::find_ancestor_in_same_ctxt
pub fn find_ancestor_inside_same_ctxt(mut self, outer: Span) -> Option<Span> {
while !outer.contains(self) || !self.eq_ctxt(outer) {
self = self.parent_callsite()?; self = self.parent_callsite()?;
} }
Some(self) Some(self)

View File

@ -1,12 +1,18 @@
fn empty() {} fn empty() {}
fn one_arg(_a: i32) {} fn one_arg<T>(_a: T) {}
fn two_arg_same(_a: i32, _b: i32) {} fn two_arg_same(_a: i32, _b: i32) {}
fn two_arg_diff(_a: i32, _b: &str) {} fn two_arg_diff(_a: i32, _b: &str) {}
macro_rules! foo { macro_rules! foo {
($x:expr) => { ($x:expr, ~) => {
empty($x, 1); //~ ERROR function takes empty($x, 1); //~ ERROR function takes
} };
($x:expr, $y:expr) => {
empty($x, $y); //~ ERROR function takes
};
(~, $y:expr) => {
empty(1, $y); //~ ERROR function takes
};
} }
fn main() { fn main() {
@ -39,5 +45,17 @@ fn main() {
1, 1,
"" ""
); );
foo!(1);
// Check with macro expansions
foo!(1, ~);
foo!(~, 1);
foo!(1, 1);
one_arg(1, panic!()); //~ ERROR function takes
one_arg(panic!(), 1); //~ ERROR function takes
one_arg(stringify!($e), 1); //~ ERROR function takes
// Not a macro, but this also has multiple spans with equal source code,
// but different expansion contexts.
// https://github.com/rust-lang/rust/issues/114255
one_arg(for _ in 1.. {}, 1); //~ ERROR function takes
} }

View File

@ -1,5 +1,5 @@
error[E0061]: this function takes 0 arguments but 1 argument was supplied error[E0061]: this function takes 0 arguments but 1 argument was supplied
--> $DIR/extra_arguments.rs:13:3 --> $DIR/extra_arguments.rs:19:3
| |
LL | empty(""); LL | empty("");
| ^^^^^ -- | ^^^^^ --
@ -14,7 +14,7 @@ LL | fn empty() {}
| ^^^^^ | ^^^^^
error[E0061]: this function takes 0 arguments but 2 arguments were supplied error[E0061]: this function takes 0 arguments but 2 arguments were supplied
--> $DIR/extra_arguments.rs:14:3 --> $DIR/extra_arguments.rs:20:3
| |
LL | empty(1, 1); LL | empty(1, 1);
| ^^^^^ - - unexpected argument of type `{integer}` | ^^^^^ - - unexpected argument of type `{integer}`
@ -33,7 +33,7 @@ LL + empty();
| |
error[E0061]: this function takes 1 argument but 2 arguments were supplied error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:16:3 --> $DIR/extra_arguments.rs:22:3
| |
LL | one_arg(1, 1); LL | one_arg(1, 1);
| ^^^^^^^ --- | ^^^^^^^ ---
@ -44,11 +44,11 @@ LL | one_arg(1, 1);
note: function defined here note: function defined here
--> $DIR/extra_arguments.rs:2:4 --> $DIR/extra_arguments.rs:2:4
| |
LL | fn one_arg(_a: i32) {} LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ ------- | ^^^^^^^ -----
error[E0061]: this function takes 1 argument but 2 arguments were supplied error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:17:3 --> $DIR/extra_arguments.rs:23:3
| |
LL | one_arg(1, ""); LL | one_arg(1, "");
| ^^^^^^^ ---- | ^^^^^^^ ----
@ -59,11 +59,11 @@ LL | one_arg(1, "");
note: function defined here note: function defined here
--> $DIR/extra_arguments.rs:2:4 --> $DIR/extra_arguments.rs:2:4
| |
LL | fn one_arg(_a: i32) {} LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ ------- | ^^^^^^^ -----
error[E0061]: this function takes 1 argument but 3 arguments were supplied error[E0061]: this function takes 1 argument but 3 arguments were supplied
--> $DIR/extra_arguments.rs:18:3 --> $DIR/extra_arguments.rs:24:3
| |
LL | one_arg(1, "", 1.0); LL | one_arg(1, "", 1.0);
| ^^^^^^^ -- --- unexpected argument of type `{float}` | ^^^^^^^ -- --- unexpected argument of type `{float}`
@ -73,8 +73,8 @@ LL | one_arg(1, "", 1.0);
note: function defined here note: function defined here
--> $DIR/extra_arguments.rs:2:4 --> $DIR/extra_arguments.rs:2:4
| |
LL | fn one_arg(_a: i32) {} LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ ------- | ^^^^^^^ -----
help: remove the extra arguments help: remove the extra arguments
| |
LL - one_arg(1, "", 1.0); LL - one_arg(1, "", 1.0);
@ -82,7 +82,7 @@ LL + one_arg(1);
| |
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:20:3 --> $DIR/extra_arguments.rs:26:3
| |
LL | two_arg_same(1, 1, 1); LL | two_arg_same(1, 1, 1);
| ^^^^^^^^^^^^ --- | ^^^^^^^^^^^^ ---
@ -97,7 +97,7 @@ LL | fn two_arg_same(_a: i32, _b: i32) {}
| ^^^^^^^^^^^^ ------- ------- | ^^^^^^^^^^^^ ------- -------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:21:3 --> $DIR/extra_arguments.rs:27:3
| |
LL | two_arg_same(1, 1, 1.0); LL | two_arg_same(1, 1, 1.0);
| ^^^^^^^^^^^^ ----- | ^^^^^^^^^^^^ -----
@ -112,7 +112,7 @@ LL | fn two_arg_same(_a: i32, _b: i32) {}
| ^^^^^^^^^^^^ ------- ------- | ^^^^^^^^^^^^ ------- -------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:23:3 --> $DIR/extra_arguments.rs:29:3
| |
LL | two_arg_diff(1, 1, ""); LL | two_arg_diff(1, 1, "");
| ^^^^^^^^^^^^ --- | ^^^^^^^^^^^^ ---
@ -127,7 +127,7 @@ LL | fn two_arg_diff(_a: i32, _b: &str) {}
| ^^^^^^^^^^^^ ------- -------- | ^^^^^^^^^^^^ ------- --------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:24:3 --> $DIR/extra_arguments.rs:30:3
| |
LL | two_arg_diff(1, "", ""); LL | two_arg_diff(1, "", "");
| ^^^^^^^^^^^^ ---- | ^^^^^^^^^^^^ ----
@ -142,7 +142,7 @@ LL | fn two_arg_diff(_a: i32, _b: &str) {}
| ^^^^^^^^^^^^ ------- -------- | ^^^^^^^^^^^^ ------- --------
error[E0061]: this function takes 2 arguments but 4 arguments were supplied error[E0061]: this function takes 2 arguments but 4 arguments were supplied
--> $DIR/extra_arguments.rs:25:3 --> $DIR/extra_arguments.rs:31:3
| |
LL | two_arg_diff(1, 1, "", ""); LL | two_arg_diff(1, 1, "", "");
| ^^^^^^^^^^^^ - -- unexpected argument of type `&'static str` | ^^^^^^^^^^^^ - -- unexpected argument of type `&'static str`
@ -161,7 +161,7 @@ LL + two_arg_diff(1, "");
| |
error[E0061]: this function takes 2 arguments but 4 arguments were supplied error[E0061]: this function takes 2 arguments but 4 arguments were supplied
--> $DIR/extra_arguments.rs:26:3 --> $DIR/extra_arguments.rs:32:3
| |
LL | two_arg_diff(1, "", 1, ""); LL | two_arg_diff(1, "", 1, "");
| ^^^^^^^^^^^^ - -- unexpected argument of type `&'static str` | ^^^^^^^^^^^^ - -- unexpected argument of type `&'static str`
@ -180,7 +180,7 @@ LL + two_arg_diff(1, "");
| |
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:29:3 --> $DIR/extra_arguments.rs:35:3
| |
LL | two_arg_same(1, 1, ""); LL | two_arg_same(1, 1, "");
| ^^^^^^^^^^^^ -------- | ^^^^^^^^^^^^ --------
@ -195,7 +195,7 @@ LL | fn two_arg_same(_a: i32, _b: i32) {}
| ^^^^^^^^^^^^ ------- ------- | ^^^^^^^^^^^^ ------- -------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:30:3 --> $DIR/extra_arguments.rs:36:3
| |
LL | two_arg_diff(1, 1, ""); LL | two_arg_diff(1, 1, "");
| ^^^^^^^^^^^^ --- | ^^^^^^^^^^^^ ---
@ -210,7 +210,7 @@ LL | fn two_arg_diff(_a: i32, _b: &str) {}
| ^^^^^^^^^^^^ ------- -------- | ^^^^^^^^^^^^ ------- --------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:31:3 --> $DIR/extra_arguments.rs:37:3
| |
LL | two_arg_same( LL | two_arg_same(
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
@ -230,7 +230,7 @@ LL | fn two_arg_same(_a: i32, _b: i32) {}
| ^^^^^^^^^^^^ ------- ------- | ^^^^^^^^^^^^ ------- -------
error[E0061]: this function takes 2 arguments but 3 arguments were supplied error[E0061]: this function takes 2 arguments but 3 arguments were supplied
--> $DIR/extra_arguments.rs:37:3 --> $DIR/extra_arguments.rs:43:3
| |
LL | two_arg_diff( LL | two_arg_diff(
| ^^^^^^^^^^^^ | ^^^^^^^^^^^^
@ -254,11 +254,10 @@ error[E0061]: this function takes 0 arguments but 2 arguments were supplied
LL | empty($x, 1); LL | empty($x, 1);
| ^^^^^ - unexpected argument of type `{integer}` | ^^^^^ - unexpected argument of type `{integer}`
... ...
LL | foo!(1); LL | foo!(1, ~);
| ------- | ----------
| | | | | |
| | unexpected argument of type `{integer}` | | unexpected argument of type `{integer}`
| | help: remove the extra argument
| in this macro invocation | in this macro invocation
| |
note: function defined here note: function defined here
@ -268,6 +267,105 @@ LL | fn empty() {}
| ^^^^^ | ^^^^^
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 16 previous errors error[E0061]: this function takes 0 arguments but 2 arguments were supplied
--> $DIR/extra_arguments.rs:14:9
|
LL | empty(1, $y);
| ^^^^^ - unexpected argument of type `{integer}`
...
LL | foo!(~, 1);
| ----------
| | |
| | unexpected argument of type `{integer}`
| in this macro invocation
|
note: function defined here
--> $DIR/extra_arguments.rs:1:4
|
LL | fn empty() {}
| ^^^^^
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0061]: this function takes 0 arguments but 2 arguments were supplied
--> $DIR/extra_arguments.rs:11:9
|
LL | empty($x, $y);
| ^^^^^
...
LL | foo!(1, 1);
| ----------
| | | |
| | | unexpected argument of type `{integer}`
| | unexpected argument of type `{integer}`
| in this macro invocation
|
note: function defined here
--> $DIR/extra_arguments.rs:1:4
|
LL | fn empty() {}
| ^^^^^
= note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:53:3
|
LL | one_arg(1, panic!());
| ^^^^^^^ ----------
| | |
| | unexpected argument
| help: remove the extra argument
|
note: function defined here
--> $DIR/extra_arguments.rs:2:4
|
LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ -----
error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:54:3
|
LL | one_arg(panic!(), 1);
| ^^^^^^^ ---
| | |
| | unexpected argument of type `{integer}`
| help: remove the extra argument
|
note: function defined here
--> $DIR/extra_arguments.rs:2:4
|
LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ -----
error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:55:3
|
LL | one_arg(stringify!($e), 1);
| ^^^^^^^ ---
| | |
| | unexpected argument of type `{integer}`
| help: remove the extra argument
|
note: function defined here
--> $DIR/extra_arguments.rs:2:4
|
LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ -----
error[E0061]: this function takes 1 argument but 2 arguments were supplied
--> $DIR/extra_arguments.rs:60:3
|
LL | one_arg(for _ in 1.. {}, 1);
| ^^^^^^^ ---
| | |
| | unexpected argument of type `{integer}`
| help: remove the extra argument
|
note: function defined here
--> $DIR/extra_arguments.rs:2:4
|
LL | fn one_arg<T>(_a: T) {}
| ^^^^^^^ -----
error: aborting due to 22 previous errors
For more information about this error, try `rustc --explain E0061`. For more information about this error, try `rustc --explain E0061`.

View File

@ -10,7 +10,6 @@ LL | b"".starts_with(stringify!(foo))
found reference `&'static str` found reference `&'static str`
note: method defined here note: method defined here
--> $SRC_DIR/core/src/slice/mod.rs:LL:COL --> $SRC_DIR/core/src/slice/mod.rs:LL:COL
= note: this error originates in the macro `stringify` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error error: aborting due to previous error