Rollup merge of #148725 - scottmcm:experiment-new-try-block-v3, r=petrochenkov

Implement the alternative `try` block desugaring

As discussed in https://github.com/rust-lang/rfcs/pull/3721#issuecomment-3208342727, update the `try` in nightly to match the RFC as a way to experiment.

This addresses the following unresolved issue from https://github.com/rust-lang/rust/issues/31436

>  Address issues with type inference (`try { expr? }?` currently requires an explicit type annotation somewhere).
This commit is contained in:
Stuart Cook 2025-11-14 19:57:06 +11:00 committed by GitHub
commit f61bfb0037
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 81 additions and 21 deletions

View file

@ -1929,7 +1929,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
/// ControlFlow::Break(residual) =>
/// #[allow(unreachable_code)]
/// // If there is an enclosing `try {...}`:
/// break 'catch_target Try::from_residual(residual),
/// break 'catch_target Residual::into_try_type(residual),
/// // Otherwise:
/// return Try::from_residual(residual),
/// }
@ -1979,7 +1979,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
let (residual_local, residual_local_nid) = self.pat_ident(try_span, residual_ident);
let residual_expr = self.expr_ident_mut(try_span, residual_ident, residual_local_nid);
let from_residual_expr = self.wrap_in_try_constructor(
hir::LangItem::TryTraitFromResidual,
if self.catch_scope.is_some() {
hir::LangItem::ResidualIntoTryType
} else {
hir::LangItem::TryTraitFromResidual
},
try_span,
self.arena.alloc(residual_expr),
unstable_span,

View file

@ -183,7 +183,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
impl_trait_defs: Vec::new(),
impl_trait_bounds: Vec::new(),
allow_contracts: [sym::contracts_internals].into(),
allow_try_trait: [sym::try_trait_v2, sym::yeet_desugar_details].into(),
allow_try_trait: [
sym::try_trait_v2,
sym::try_trait_v2_residual,
sym::yeet_desugar_details,
]
.into(),
allow_pattern_type: [sym::pattern_types, sym::pattern_type_range_trait].into(),
allow_gen_future: if tcx.features().async_fn_track_caller() {
[sym::gen_future, sym::closure_track_caller].into()

View file

@ -370,6 +370,7 @@ language_item_table! {
TryTraitFromOutput, sym::from_output, from_output_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
TryTraitBranch, sym::branch, branch_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None;
TryTraitFromYeet, sym::from_yeet, from_yeet_fn, Target::Fn, GenericRequirement::None;
ResidualIntoTryType, sym::into_try_type, into_try_type_fn, Target::Fn, GenericRequirement::None;
CoercePointeeValidated, sym::coerce_pointee_validated, coerce_pointee_validated_trait, Target::Trait, GenericRequirement::Exact(0);

View file

@ -1259,6 +1259,7 @@ symbols! {
into_async_iter_into_iter,
into_future,
into_iter,
into_try_type,
intra_doc_pointers,
intrinsics,
intrinsics_unaligned_volatile_load,
@ -2280,6 +2281,7 @@ symbols! {
try_from_fn,
try_into,
try_trait_v2,
try_trait_v2_residual,
try_update,
tt,
tuple,

View file

@ -359,11 +359,24 @@ where
/// and in the other direction,
/// `<Result<Infallible, E> as Residual<T>>::TryType = Result<T, E>`.
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
#[rustc_const_unstable(feature = "const_try", issue = "74935")]
pub const trait Residual<O> {
#[rustc_const_unstable(feature = "const_try_residual", issue = "91285")]
pub const trait Residual<O>: Sized {
/// The "return" type of this meta-function.
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
type TryType: Try<Output = O, Residual = Self>;
type TryType: [const] Try<Output = O, Residual = Self>;
}
/// Used in `try {}` blocks so the type produced in the `?` desugaring
/// depends on the residual type `R` and the output type of the block `O`,
/// but importantly not on the contextual type the way it would be if
/// we called `<_ as FromResidual>::from_residual(r)` directly.
#[unstable(feature = "try_trait_v2_residual", issue = "91285")]
// needs to be `pub` to avoid `private type` errors
#[expect(unreachable_pub)]
#[inline] // FIXME: force would be nice, but fails -- see #148915
#[lang = "into_try_type"]
pub fn residual_into_try_type<R: Residual<O>, O>(r: R) -> <R as Residual<O>>::TryType {
FromResidual::from_residual(r)
}
#[unstable(feature = "pub_crate_should_not_need_unstable_attr", issue = "none")]

View file

@ -50,7 +50,7 @@ fn copy_specialization() -> Result<()> {
"inner Take allowed reading beyond end of file, some bytes should be left"
);
let mut sink = sink.into_inner()?;
let mut sink = sink.into_inner().map_err(io::Error::from)?;
sink.seek(SeekFrom::Start(0))?;
let mut copied = Vec::new();
sink.read_to_end(&mut copied)?;

View file

@ -34,7 +34,7 @@ fn option_traits(_1: Option<u32>) -> Option<u32> {
}
bb3: {
_0 = <Option<u32> as FromResidual<Option<Infallible>>>::from_residual(const Option::<Infallible>::None) -> [return: bb4, unwind unreachable];
_0 = ops::try_trait::residual_into_try_type::<Option<Infallible>, u32>(const Option::<Infallible>::None) -> [return: bb4, unwind unreachable];
}
bb4: {

View file

@ -34,7 +34,7 @@ fn option_traits(_1: Option<u32>) -> Option<u32> {
}
bb3: {
_0 = <Option<u32> as FromResidual<Option<Infallible>>>::from_residual(const Option::<Infallible>::None) -> [return: bb4, unwind continue];
_0 = ops::try_trait::residual_into_try_type::<Option<Infallible>, u32>(const Option::<Infallible>::None) -> [return: bb4, unwind continue];
}
bb4: {

View file

@ -4,7 +4,7 @@
pub fn main() {
let res: Result<u32, std::array::TryFromSliceError> = try {
Err("")?; //~ ERROR `?` couldn't convert the error
Err("")?; //~ ERROR mismatched types
5
};

View file

@ -1,16 +1,15 @@
error[E0277]: `?` couldn't convert the error to `TryFromSliceError`
--> $DIR/try-block-bad-type.rs:7:16
error[E0308]: mismatched types
--> $DIR/try-block-bad-type.rs:7:9
|
LL | Err("")?;
| -------^ the trait `From<&str>` is not implemented for `TryFromSliceError`
| |
| this can't be annotated with `?` because it has type `Result<_, &str>`
| ^^^^^^^^ expected `Result<u32, TryFromSliceError>`, found `Result<_, &str>`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
help: the trait `From<&str>` is not implemented for `TryFromSliceError`
but trait `From<Infallible>` is implemented for it
--> $SRC_DIR/core/src/array/mod.rs:LL:COL
= help: for that trait implementation, expected `Infallible`, found `&str`
= note: expected enum `Result<u32, TryFromSliceError>`
found enum `Result<_, &str>`
help: consider using `Result::expect` to unwrap the `Result<_, &str>` value, panicking if the value is a `Result::Err`
|
LL | Err("")?.expect("REASON");
| +++++++++++++++++
error[E0271]: type mismatch resolving `<Result<i32, i32> as Try>::Output == &str`
--> $DIR/try-block-bad-type.rs:12:9
@ -42,5 +41,5 @@ LL | let res: i32 = try { 5 };
error: aborting due to 5 previous errors
Some errors have detailed explanations: E0271, E0277.
Some errors have detailed explanations: E0271, E0277, E0308.
For more information about an error, try `rustc --explain E0271`.

View file

@ -0,0 +1,36 @@
//@ check-pass
//@ edition: 2018
#![feature(try_blocks)]
#![crate_type = "lib"]
// A few examples of code that only works with homogeneous `try`
pub fn index_or_zero(x: &[i32], i: usize, j: usize) -> i32 {
// With heterogeneous `try` this fails because
// it tries to call a method on a type variable.
try { x.get(i)? + x.get(j)? }.unwrap_or(0)
}
pub fn do_nothing_on_errors(a: &str, b: &str) {
// With heterogeneous `try` this fails because
// an underscore pattern doesn't constrain the output type.
let _ = try {
let a = a.parse::<i32>()?;
let b = b.parse::<u32>()?;
println!("{a} {b}");
};
}
pub fn print_error_once(a: &str, b: &str) {
match try {
let a = a.parse::<i32>()?;
let b = b.parse::<u32>()?;
(a, b)
} {
Ok(_pair) => {}
// With heterogeneous `try` this fails because
// nothing constrains the error type in `e`.
Err(e) => eprintln!("{e}"),
}
}