Skip to content

Commit 77a1eec

Browse files
authored
Unrolled build for rust-lang#123654
Rollup merge of rust-lang#123654 - jieyouxu:question-mark-span, r=Nadrieril typeck: fix `?` suggestion span Noticed in <rust-lang#112043 (comment)>, if the ``` use the `?` operator to extract the `Result<(), std::fmt::Error>` value, propagating a `Result::Err` value to the caller ``` suggestion is applied to a macro that comes from a non-local crate (e.g. the stdlib), the suggestion span can become non-local, which will cause newer rustfix versions to fail. This PR tries to remedy the problem by recursively probing ancestors of the expression span, trying to identify the most ancestor span that is (1) still local, and (2) still shares the same syntax context as the expression. This is the same strategy used in rust-lang#112043. The test unfortunately cannot `//@ run-rustfix` because there are two conflicting MaybeIncorrect suggestions that when collectively applied, cause the fixed source file to become non-compilable. Also avoid running `//@ run-rustfix` for `tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.rs` because that also contains conflicting suggestions. cc `@ehuss` who noticed this. This question mark span fix + not running rustfix on the tests containing conflicting MaybeIncorrect suggestions should hopefully unblock rustfix from updating.
2 parents 22a2425 + 420e3f1 commit 77a1eec

7 files changed

+118
-56
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+3-12
Original file line numberDiff line numberDiff line change
@@ -1287,12 +1287,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12871287
ty::TraitRef::new(self.tcx, into_def_id, [expr_ty, expected_ty]),
12881288
))
12891289
{
1290-
let mut span = expr.span;
1291-
while expr.span.eq_ctxt(span)
1292-
&& let Some(parent_callsite) = span.parent_callsite()
1293-
{
1294-
span = parent_callsite;
1295-
}
1290+
let span = expr.span.find_oldest_ancestor_in_same_ctxt();
12961291

12971292
let mut sugg = if expr.precedence().order() >= PREC_POSTFIX {
12981293
vec![(span.shrink_to_hi(), ".into()".to_owned())]
@@ -1897,12 +1892,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18971892
None => sugg.to_string(),
18981893
};
18991894

1900-
err.span_suggestion_verbose(
1901-
expr.span.shrink_to_hi(),
1902-
msg,
1903-
sugg,
1904-
Applicability::HasPlaceholders,
1905-
);
1895+
let span = expr.span.find_oldest_ancestor_in_same_ctxt();
1896+
err.span_suggestion_verbose(span.shrink_to_hi(), msg, sugg, Applicability::HasPlaceholders);
19061897
return true;
19071898
}
19081899

compiler/rustc_span/src/lib.rs

+39
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,45 @@ impl Span {
743743
Some(self)
744744
}
745745

746+
/// Recursively walk down the expansion ancestors to find the oldest ancestor span with the same
747+
/// [`SyntaxContext`] the initial span.
748+
///
749+
/// This method is suitable for peeling through *local* macro expansions to find the "innermost"
750+
/// span that is still local and shares the same [`SyntaxContext`]. For example, given
751+
///
752+
/// ```ignore (illustrative example, contains type error)
753+
/// macro_rules! outer {
754+
/// ($x: expr) => {
755+
/// inner!($x)
756+
/// }
757+
/// }
758+
///
759+
/// macro_rules! inner {
760+
/// ($x: expr) => {
761+
/// format!("error: {}", $x)
762+
/// //~^ ERROR mismatched types
763+
/// }
764+
/// }
765+
///
766+
/// fn bar(x: &str) -> Result<(), Box<dyn std::error::Error>> {
767+
/// Err(outer!(x))
768+
/// }
769+
/// ```
770+
///
771+
/// if provided the initial span of `outer!(x)` inside `bar`, this method will recurse
772+
/// the parent callsites until we reach `format!("error: {}", $x)`, at which point it is the
773+
/// oldest ancestor span that is both still local and shares the same [`SyntaxContext`] as the
774+
/// initial span.
775+
pub fn find_oldest_ancestor_in_same_ctxt(self) -> Span {
776+
let mut cur = self;
777+
while cur.eq_ctxt(self)
778+
&& let Some(parent_callsite) = cur.parent_callsite()
779+
{
780+
cur = parent_callsite;
781+
}
782+
cur
783+
}
784+
746785
/// Edition of the crate from which this span came.
747786
pub fn edition(self) -> edition::Edition {
748787
self.ctxt().edition()

tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.fixed

-37
This file was deleted.

tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
//@ run-rustfix
2-
#![allow(dead_code)]
1+
// Check that we don't leak stdlib implementation details through suggestions.
2+
// Also check that the suggestion provided tries as hard as it can to see through local macros.
3+
//
4+
// FIXME(jieyouxu): this test is NOT run-rustfix because this test contains conflicting
5+
// MaybeIncorrect suggestions:
6+
//
7+
// 1. `return ... ;`
8+
// 2. `?`
9+
//
10+
// when the suggestions are applied to the same file, it becomes uncompilable.
311

412
// https://github.com/rust-lang/rust/issues/112007
5-
fn bug_report<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
13+
pub fn bug_report<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
614
if true {
715
writeln!(w, "`;?` here ->")?;
816
} else {
@@ -25,7 +33,7 @@ macro_rules! bar {
2533
}
2634
}
2735

28-
fn foo<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
36+
pub fn foo<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
2937
if true {
3038
writeln!(w, "`;?` here ->")?;
3139
} else {
@@ -34,4 +42,4 @@ fn foo<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
3442
Ok(())
3543
}
3644

37-
fn main() {}
45+
pub fn main() {}

tests/ui/typeck/issue-112007-leaked-writeln-macro-internals.stderr

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0308]: mismatched types
2-
--> $DIR/issue-112007-leaked-writeln-macro-internals.rs:9:9
2+
--> $DIR/issue-112007-leaked-writeln-macro-internals.rs:17:9
33
|
44
LL | / if true {
55
LL | | writeln!(w, "`;?` here ->")?;
@@ -21,9 +21,13 @@ help: you might have meant to return this value
2121
|
2222
LL | return writeln!(w, "but not here");
2323
| ++++++ +
24+
help: use the `?` operator to extract the `Result<(), std::fmt::Error>` value, propagating a `Result::Err` value to the caller
25+
|
26+
LL | writeln!(w, "but not here")?
27+
| +
2428

2529
error[E0308]: mismatched types
26-
--> $DIR/issue-112007-leaked-writeln-macro-internals.rs:32:9
30+
--> $DIR/issue-112007-leaked-writeln-macro-internals.rs:40:9
2731
|
2832
LL | / if true {
2933
LL | | writeln!(w, "`;?` here ->")?;
@@ -44,6 +48,10 @@ help: you might have meant to return this value
4448
|
4549
LL | return baz!(w);
4650
| ++++++ +
51+
help: use the `?` operator to extract the `Result<(), std::fmt::Error>` value, propagating a `Result::Err` value to the caller
52+
|
53+
LL | writeln!($w, "but not here")?
54+
| +
4755

4856
error: aborting due to 2 previous errors
4957

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Check that we don't construct a span for `?` suggestions that point into non-local macros
2+
// like into the stdlib where the user has no control over.
3+
//
4+
// FIXME(jieyouxu): this test is currently NOT run-rustfix because there are conflicting
5+
// MaybeIncorrect suggestions:
6+
//
7+
// 1. adding `return ... ;`, and
8+
// 2. adding `?`.
9+
//
10+
// When rustfix puts those together, the fixed file now contains uncompilable code.
11+
12+
#![crate_type = "lib"]
13+
14+
pub fn bug_report<W: std::fmt::Write>(w: &mut W) -> std::fmt::Result {
15+
if true {
16+
writeln!(w, "`;?` here ->")?;
17+
} else {
18+
writeln!(w, "but not here")
19+
//~^ ERROR mismatched types
20+
}
21+
Ok(())
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/question-mark-operator-suggestion-span.rs:18:9
3+
|
4+
LL | / if true {
5+
LL | | writeln!(w, "`;?` here ->")?;
6+
LL | | } else {
7+
LL | | writeln!(w, "but not here")
8+
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `Result<(), Error>`
9+
LL | |
10+
LL | | }
11+
| |_____- expected this to be `()`
12+
|
13+
= note: expected unit type `()`
14+
found enum `Result<(), std::fmt::Error>`
15+
= note: this error originates in the macro `writeln` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
help: consider using a semicolon here
17+
|
18+
LL | };
19+
| +
20+
help: you might have meant to return this value
21+
|
22+
LL | return writeln!(w, "but not here");
23+
| ++++++ +
24+
help: use the `?` operator to extract the `Result<(), std::fmt::Error>` value, propagating a `Result::Err` value to the caller
25+
|
26+
LL | writeln!(w, "but not here")?
27+
| +
28+
29+
error: aborting due to 1 previous error
30+
31+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)