Skip to content

Commit d9cb2bf

Browse files
committed
Auto merge of rust-lang#122412 - WaffleLapkin:if-we-ask-question-mark-operator-to-not-screw-with-inference-will-it-obey, r=<try>
Stop skewing inference in ?'s desugaring **NB**: this is a breaking change (although arguably a bug fix) and as such shouldn't be taken lightly. This changes `expr?`'s desugaring like so (simplified, see code for more info): ```rust // old match expr { Ok(val) => val, Err(err) => return Err(err), } // new match expr { Ok(val) => val, Err(err) => core::convert::absurd(return Err(err)), } // core::convert pub const fn absurd<T>(x: !) -> T { x } ``` This prevents `!` from the `return` from skewing inference: ```rust // previously: ok (never type spontaneous decay skews inference, `T = ()`) // with this pr: can't infer the type for `T` Err(())?; ``` Fixes rust-lang#51125 Closes rust-lang#39216
2 parents 96eaf55 + 9cd668c commit d9cb2bf

25 files changed

+145
-65
lines changed

compiler/rustc_ast_lowering/src/expr.rs

+22-8
Original file line numberDiff line numberDiff line change
@@ -1758,9 +1758,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
17581758
/// ControlFlow::Break(residual) =>
17591759
/// #[allow(unreachable_code)]
17601760
/// // If there is an enclosing `try {...}`:
1761-
/// break 'catch_target Try::from_residual(residual),
1761+
/// absurd(break 'catch_target Try::from_residual(residual)),
17621762
/// // Otherwise:
1763-
/// return Try::from_residual(residual),
1763+
/// absurd(return Try::from_residual(residual)),
17641764
/// }
17651765
/// ```
17661766
fn lower_expr_try(&mut self, span: Span, sub_expr: &Expr) -> hir::ExprKind<'hir> {
@@ -1769,6 +1769,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
17691769
span,
17701770
Some(self.allow_try_trait.clone()),
17711771
);
1772+
1773+
let absurd_allowed_span = self.mark_span_with_reason(
1774+
DesugaringKind::QuestionMark,
1775+
span,
1776+
Some(self.allow_convert_absurd.clone()),
1777+
);
1778+
17721779
let try_span = self.tcx.sess.source_map().end_point(span);
17731780
let try_span = self.mark_span_with_reason(
17741781
DesugaringKind::QuestionMark,
@@ -1810,7 +1817,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
18101817

18111818
// `ControlFlow::Break(residual) =>
18121819
// #[allow(unreachable_code)]
1813-
// return Try::from_residual(residual),`
1820+
// absurd(return Try::from_residual(residual)),`
18141821
let break_arm = {
18151822
let residual_ident = Ident::with_dummy_span(sym::residual);
18161823
let (residual_local, residual_local_nid) = self.pat_ident(try_span, residual_ident);
@@ -1823,20 +1830,27 @@ impl<'hir> LoweringContext<'_, 'hir> {
18231830
);
18241831
let ret_expr = if let Some(catch_node) = self.catch_scope {
18251832
let target_id = Ok(self.lower_node_id(catch_node));
1826-
self.arena.alloc(self.expr(
1833+
self.expr(
18271834
try_span,
18281835
hir::ExprKind::Break(
18291836
hir::Destination { label: None, target_id },
18301837
Some(from_residual_expr),
18311838
),
1832-
))
1839+
)
18331840
} else {
1834-
self.arena.alloc(self.expr(try_span, hir::ExprKind::Ret(Some(from_residual_expr))))
1841+
self.expr(try_span, hir::ExprKind::Ret(Some(from_residual_expr)))
18351842
};
1836-
self.lower_attrs(ret_expr.hir_id, &attrs);
1843+
1844+
let absurd_expr = self.expr_call_lang_item_fn(
1845+
absurd_allowed_span,
1846+
hir::LangItem::Absurd,
1847+
arena_vec![self; ret_expr],
1848+
);
1849+
1850+
self.lower_attrs(absurd_expr.hir_id, &attrs);
18371851

18381852
let break_pat = self.pat_cf_break(try_span, residual_local);
1839-
self.arm(break_pat, ret_expr)
1853+
self.arm(break_pat, absurd_expr)
18401854
};
18411855

18421856
hir::ExprKind::Match(

compiler/rustc_ast_lowering/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ struct LoweringContext<'a, 'hir> {
130130
node_id_to_local_id: NodeMap<hir::ItemLocalId>,
131131

132132
allow_try_trait: Lrc<[Symbol]>,
133+
allow_convert_absurd: Lrc<[Symbol]>,
133134
allow_gen_future: Lrc<[Symbol]>,
134135
allow_async_iterator: Lrc<[Symbol]>,
135136
allow_for_await: Lrc<[Symbol]>,
@@ -173,6 +174,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
173174
impl_trait_defs: Vec::new(),
174175
impl_trait_bounds: Vec::new(),
175176
allow_try_trait: [sym::try_trait_v2, sym::yeet_desugar_details].into(),
177+
allow_convert_absurd: [sym::convert_absurd].into(),
176178
allow_gen_future: if tcx.features().async_fn_track_caller {
177179
[sym::gen_future, sym::closure_track_caller].into()
178180
} else {

compiler/rustc_hir/src/lang_items.rs

+2
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ language_item_table! {
317317
TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
318318
TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None;
319319

320+
Absurd, sym::absurd, absurd, Target::Fn, GenericRequirement::Exact(1);
321+
320322
PointerLike, sym::pointer_like, pointer_like, Target::Trait, GenericRequirement::Exact(0);
321323

322324
ConstParamTy, sym::const_param_ty, const_param_ty_trait, Target::Trait, GenericRequirement::Exact(0);

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ symbols! {
354354
abi_vectorcall,
355355
abi_x86_interrupt,
356356
abort,
357+
absurd,
357358
add,
358359
add_assign,
359360
add_with_overflow,
@@ -601,6 +602,7 @@ symbols! {
601602
const_try,
602603
constant,
603604
constructor,
605+
convert_absurd,
604606
convert_identity,
605607
copy,
606608
copy_closures,

library/core/src/convert/mod.rs

+60
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,66 @@ pub const fn identity<T>(x: T) -> T {
105105
x
106106
}
107107

108+
/// Converts [`!`] (the never type) to any type.
109+
///
110+
/// This is possible because `!` is uninhabited (has no values), so this function can't actually
111+
/// be ever called at runtime.
112+
///
113+
/// Even though `!` can be coerced to any type implicitly anyway (and indeed this function
114+
/// implemented by just "returning" the argument), this is still useful, as this prevents the
115+
/// fallback from happening during typechecking.
116+
///
117+
/// For example, this snippet type checks:
118+
///
119+
/// ```rust
120+
/// let x: Result<_, ()> = Err(());
121+
/// let y = match x {
122+
/// Ok(v) => v,
123+
/// Err(()) => return,
124+
/// };
125+
/// ```
126+
///
127+
/// This is a bit unexpected, because the type of `y` is seemingly unbound (indeed, it can be any
128+
/// type). However, the `match` unifies type of `v` with type of `return` (which is `!`), so `y`
129+
/// becomes `!` (or `()`, because of backwards compatibility shenanigans).
130+
///
131+
/// This can be avoided by adding `absurd`;
132+
///
133+
/// ```compile_fail,E0282
134+
/// use core::convert::absurd;
135+
///
136+
/// let x: Result<_, ()> = Err(());
137+
/// let y = match x { //~ error[E0282]: type annotations needed
138+
/// Ok(v) => v,
139+
///
140+
/// // the call to `absurd` *is* unreachable, but it's still important for type check reasons
141+
/// #[allow(unreachable_code)]
142+
/// Err(()) => absurd(return),
143+
/// };
144+
/// ```
145+
///
146+
/// This might be handy when writing macros.
147+
///
148+
/// `absurd` can also be passed to higher order functions, just like any other function:
149+
///
150+
/// ```
151+
/// #![feature(never_type)]
152+
/// use core::convert::absurd;
153+
///
154+
/// let x: Result<_, !> = Ok(1);
155+
/// let x: u32 = x.unwrap_or_else(absurd);
156+
/// ```
157+
///
158+
/// [`!`]: ../../primitive.never.html
159+
#[inline(always)]
160+
#[lang = "absurd"]
161+
#[unstable(feature = "convert_absurd", issue = "none")]
162+
#[rustc_const_unstable(feature = "convert_absurd", issue = "none")]
163+
#[cfg(not(bootstrap))]
164+
pub const fn absurd<T>(x: !) -> T {
165+
x
166+
}
167+
108168
/// Used to do a cheap reference-to-reference conversion.
109169
///
110170
/// This trait is similar to [`AsMut`] which is used for converting between mutable references.

src/tools/opt-dist/src/training.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use humansize::BINARY;
88

99
const LLVM_PGO_CRATES: &[&str] = &[
1010
"syn-1.0.89",
11-
"cargo-0.60.0",
11+
//"cargo-0.60.0",
1212
"serde-1.0.136",
1313
"ripgrep-13.0.0",
1414
"regex-1.5.5",
@@ -19,7 +19,7 @@ const LLVM_PGO_CRATES: &[&str] = &[
1919
const RUSTC_PGO_CRATES: &[&str] = &[
2020
"externs",
2121
"ctfe-stress-5",
22-
"cargo-0.60.0",
22+
//"cargo-0.60.0",
2323
"token-stream-stress",
2424
"match-stress",
2525
"tuple-stress",

tests/ui/async-await/issue-67765-async-diagnostic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ async fn func<'a>() -> Result<(), &'a str> {
1010

1111
let b = &s[..];
1212

13-
Err(b)?; //~ ERROR cannot return value referencing local variable `s`
13+
Err::<(), _>(b)?; //~ ERROR cannot return value referencing local variable `s`
1414

1515
Ok(())
1616
}

tests/ui/async-await/issue-67765-async-diagnostic.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ error[E0515]: cannot return value referencing local variable `s`
44
LL | let b = &s[..];
55
| - `s` is borrowed here
66
LL |
7-
LL | Err(b)?;
8-
| ^^^^^^^ returns a value referencing data owned by the current function
7+
LL | Err::<(), _>(b)?;
8+
| ^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
99

1010
error: aborting due to 1 previous error
1111

tests/ui/consts/try-operator.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77

88
fn main() {
99
const fn result() -> Result<bool, ()> {
10-
Err(())?;
10+
Err::<(), _>(())?;
1111
Ok(true)
1212
}
1313

1414
const FOO: Result<bool, ()> = result();
1515
assert_eq!(Err(()), FOO);
1616

1717
const fn option() -> Option<()> {
18-
None?;
18+
None::<()>?;
1919
Some(())
2020
}
2121
const BAR: Option<()> = option();

tests/ui/consts/try-operator.stderr

+8-8
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ LL | #![feature(const_convert)]
77
error[E0015]: `?` cannot determine the branch of `Result<(), ()>` in constant functions
88
--> $DIR/try-operator.rs:10:9
99
|
10-
LL | Err(())?;
11-
| ^^^^^^^^
10+
LL | Err::<(), _>(())?;
11+
| ^^^^^^^^^^^^^^^^^
1212
|
1313
note: impl defined here, but it is not `const`
1414
--> $SRC_DIR/core/src/result.rs:LL:COL
@@ -21,8 +21,8 @@ LL + #![feature(effects)]
2121
error[E0015]: `?` cannot convert from residual of `Result<bool, ()>` in constant functions
2222
--> $DIR/try-operator.rs:10:9
2323
|
24-
LL | Err(())?;
25-
| ^^^^^^^^
24+
LL | Err::<(), _>(())?;
25+
| ^^^^^^^^^^^^^^^^^
2626
|
2727
note: impl defined here, but it is not `const`
2828
--> $SRC_DIR/core/src/result.rs:LL:COL
@@ -35,8 +35,8 @@ LL + #![feature(effects)]
3535
error[E0015]: `?` cannot determine the branch of `Option<()>` in constant functions
3636
--> $DIR/try-operator.rs:18:9
3737
|
38-
LL | None?;
39-
| ^^^^^
38+
LL | None::<()>?;
39+
| ^^^^^^^^^^^
4040
|
4141
note: impl defined here, but it is not `const`
4242
--> $SRC_DIR/core/src/option.rs:LL:COL
@@ -49,8 +49,8 @@ LL + #![feature(effects)]
4949
error[E0015]: `?` cannot convert from residual of `Option<()>` in constant functions
5050
--> $DIR/try-operator.rs:18:9
5151
|
52-
LL | None?;
53-
| ^^^^^
52+
LL | None::<()>?;
53+
| ^^^^^^^^^^^
5454
|
5555
note: impl defined here, but it is not `const`
5656
--> $SRC_DIR/core/src/option.rs:LL:COL

tests/ui/did_you_mean/compatible-variants.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,6 @@ LL + Some(())
6161
error[E0308]: `?` operator has incompatible types
6262
--> $DIR/compatible-variants.rs:35:5
6363
|
64-
LL | fn d() -> Option<()> {
65-
| ---------- expected `Option<()>` because of return type
6664
LL | c()?
6765
| ^^^^ expected `Option<()>`, found `()`
6866
|

tests/ui/impl-trait/cross-return-site-inference.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,34 @@
22

33
fn foo(b: bool) -> impl std::fmt::Debug {
44
if b {
5-
return vec![42]
5+
return vec![42];
66
}
77
[].into_iter().collect()
88
}
99

1010
fn bar(b: bool) -> impl std::fmt::Debug {
1111
if b {
12-
return [].into_iter().collect()
12+
return [].into_iter().collect();
1313
}
1414
vec![42]
1515
}
1616

1717
fn bak(b: bool) -> impl std::fmt::Debug {
1818
if b {
19-
return std::iter::empty().collect()
19+
return std::iter::empty().collect();
2020
}
2121
vec![42]
2222
}
2323

2424
fn baa(b: bool) -> impl std::fmt::Debug {
2525
if b {
26-
return [42].into_iter().collect()
26+
return [42].into_iter().collect();
2727
}
2828
vec![]
2929
}
3030

3131
fn muh() -> Result<(), impl std::fmt::Debug> {
32-
Err("whoops")?;
32+
Err::<(), _>("whoops")?;
3333
Ok(())
3434
//~^ ERROR type annotations needed
3535
}

tests/ui/inference/cannot-infer-closure.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
fn main() {
22
let x = |a: (), b: ()| {
3-
Err(a)?;
3+
Err::<(), _>(a)?;
44
Ok(b)
55
//~^ ERROR type annotations needed
66
};

tests/ui/issues/issue-51632-try-desugar-incompatible-types.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
error[E0308]: `?` operator has incompatible types
22
--> $DIR/issue-51632-try-desugar-incompatible-types.rs:8:5
33
|
4-
LL | fn forbidden_narratives() -> Result<isize, ()> {
5-
| ----------------- expected `Result<isize, ()>` because of return type
64
LL | missing_discourses()?
75
| ^^^^^^^^^^^^^^^^^^^^^ expected `Result<isize, ()>`, found `isize`
86
|

tests/ui/label/label_break_value_desugared_break.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
fn main() {
66
let _: Result<(), ()> = try {
77
'foo: {
8-
Err(())?;
8+
Err::<(), _>(())?;
99
break 'foo;
1010
}
1111
};
1212

1313
'foo: {
1414
let _: Result<(), ()> = try {
15-
Err(())?;
15+
Err::<(), _>(())?;
1616
break 'foo;
1717
};
1818
}

tests/ui/parser/try-with-nonterminal-block.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ macro_rules! create_try {
1111

1212
fn main() {
1313
let x: Option<&str> = create_try! {{
14-
None?;
14+
None::<()>?;
1515
"Hello world"
1616
}};
1717

tests/ui/suggestions/remove-question-symbol-with-paren.stderr

-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
error[E0308]: `?` operator has incompatible types
22
--> $DIR/remove-question-symbol-with-paren.rs:5:6
33
|
4-
LL | fn foo() -> Option<()> {
5-
| ---------- expected `Option<()>` because of return type
6-
LL | let x = Some(());
74
LL | (x?)
85
| ^^ expected `Option<()>`, found `()`
96
|

tests/ui/suggestions/suggest-box.fixed

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
fn main() {
44
let _x: Box<dyn Fn() -> Result<(), ()>> = Box::new(|| { //~ ERROR mismatched types
5-
Err(())?;
5+
Err::<(), _>(())?;
66
Ok(())
77
});
88
}

tests/ui/suggestions/suggest-box.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
fn main() {
44
let _x: Box<dyn Fn() -> Result<(), ()>> = || { //~ ERROR mismatched types
5-
Err(())?;
5+
Err::<(), _>(())?;
66
Ok(())
77
};
88
}

0 commit comments

Comments
 (0)