From 6ab8f33b9e3ecdfcf9121cac884f1ad8a19af7a1 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Thu, 7 Aug 2025 19:41:57 +0800 Subject: [PATCH 1/5] fix accidental type variable resolution in array coercion --- compiler/rustc_hir_typeck/src/expr.rs | 13 ++++++- tests/ui/coercion/coerce-many-with-ty-var.rs | 38 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 tests/ui/coercion/coerce-many-with-ty-var.rs diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 454ec7ddcafbe..f0ae57ce36ec6 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1817,11 +1817,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let element_ty = if !args.is_empty() { let coerce_to = expected .to_option(self) - .and_then(|uty| self.try_structurally_resolve_type(expr.span, uty).builtin_index()) + .and_then(|uty| { + self.try_structurally_resolve_type(expr.span, uty) + .builtin_index() + // Avoid using the original type variable as the coerce_to type, as it may resolve + // during the first coercion instead of being the LUB type. + .filter(|t| !self.try_structurally_resolve_type(expr.span, *t).is_ty_var()) + }) .unwrap_or_else(|| self.next_ty_var(expr.span)); let mut coerce = CoerceMany::with_coercion_sites(coerce_to, args); assert_eq!(self.diverges.get(), Diverges::Maybe); for e in args { + // FIXME: the element expectation should use + // `try_structurally_resolve_and_adjust_for_branches` just like in `if` and `match`. + // While that fixes nested coercion, it will break [some + // code like this](https://github.com/rust-lang/rust/pull/140283#issuecomment-2958776528). + // If we find a way to support recursive tuple coercion, this break can be avoided. let e_ty = self.check_expr_with_hint(e, coerce_to); let cause = self.misc(e.span); coerce.coerce(self, &cause, e, e_ty); diff --git a/tests/ui/coercion/coerce-many-with-ty-var.rs b/tests/ui/coercion/coerce-many-with-ty-var.rs new file mode 100644 index 0000000000000..7e440e400b781 --- /dev/null +++ b/tests/ui/coercion/coerce-many-with-ty-var.rs @@ -0,0 +1,38 @@ +//@ run-pass +// Check that least upper bound coercions don't resolve type variable merely based on the first +// coercion. Check issue #136420. + +fn foo() {} +fn bar() {} + +fn infer(_: T) {} + +fn infer_array_element(_: [T; 2]) {} + +fn main() { + // Previously the element type's ty var will be unified with `foo`. + let _: [_; 2] = [foo, bar]; + infer_array_element([foo, bar]); + + let _ = if false { + foo + } else { + bar + }; + infer(if false { + foo + } else { + bar + }); + + let _ = match false { + true => foo, + false => bar, + }; + infer(match false { + true => foo, + false => bar, + }); + + +} From 668b954a420dda5e0583b8acbe775bf32ab9208e Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 24 Sep 2025 15:27:43 +0800 Subject: [PATCH 2/5] add a test for weakened closure signature inference --- tests/ui/coercion/closure-in-array.rs | 7 +++++++ tests/ui/coercion/closure-in-array.stderr | 14 ++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 tests/ui/coercion/closure-in-array.rs create mode 100644 tests/ui/coercion/closure-in-array.stderr diff --git a/tests/ui/coercion/closure-in-array.rs b/tests/ui/coercion/closure-in-array.rs new file mode 100644 index 0000000000000..ca5c021c77a7f --- /dev/null +++ b/tests/ui/coercion/closure-in-array.rs @@ -0,0 +1,7 @@ +// Weakened closure sig inference by #140283. +fn foo usize, const N: usize>(x: [F; N]) {} + +fn main() { + foo([|s| s.len()]) + //~^ ERROR: type annotations needed +} diff --git a/tests/ui/coercion/closure-in-array.stderr b/tests/ui/coercion/closure-in-array.stderr new file mode 100644 index 0000000000000..90cf590c09e73 --- /dev/null +++ b/tests/ui/coercion/closure-in-array.stderr @@ -0,0 +1,14 @@ +error[E0282]: type annotations needed + --> $DIR/closure-in-array.rs:5:11 + | +LL | foo([|s| s.len()]) + | ^ - type must be known at this point + | +help: consider giving this closure parameter an explicit type + | +LL | foo([|s: /* Type */| s.len()]) + | ++++++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0282`. From 9d0fe04f20cd848827665053cb61ace0374db3ab Mon Sep 17 00:00:00 2001 From: Adwin White Date: Wed, 24 Sep 2025 15:30:16 +0800 Subject: [PATCH 3/5] style: remove trailing empty lines in test --- tests/ui/coercion/coerce-many-with-ty-var.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ui/coercion/coerce-many-with-ty-var.rs b/tests/ui/coercion/coerce-many-with-ty-var.rs index 7e440e400b781..cbd16f58ea5b5 100644 --- a/tests/ui/coercion/coerce-many-with-ty-var.rs +++ b/tests/ui/coercion/coerce-many-with-ty-var.rs @@ -33,6 +33,4 @@ fn main() { true => foo, false => bar, }); - - } From d04050504b75c73516c7a70f75e90e96857262d3 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Thu, 9 Oct 2025 15:32:05 +0800 Subject: [PATCH 4/5] doc: explain why `expected_ty` participates in the LUB of `CoerceMany` --- compiler/rustc_hir_typeck/src/coercion.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index e66601631fcc8..17cfb05a45245 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1381,6 +1381,7 @@ pub fn can_coerce<'tcx>( /// - WARNING: I don't believe this final type is guaranteed to be /// related to your initial `expected_ty` in any particular way, /// although it will typically be a subtype, so you should check it. +/// Check the note below for more details. /// - Invoking `complete()` may cause us to go and adjust the "adjustments" on /// previously coerced expressions. /// @@ -1394,6 +1395,28 @@ pub fn can_coerce<'tcx>( /// } /// let final_ty = coerce.complete(fcx); /// ``` +/// +/// NOTE: Why does the `expected_ty` participate in the LUB? +/// When coercing, each branch should use the following expectations for type inference: +/// - The branch can be coerced to the expected type of the match/if/whatever. +/// - The branch can be coercion lub'd with the types of the previous branches. +/// Ideally we'd have some sort of `Expectation::ParticipatsInCoerceLub(ongoing_lub_ty, final_ty)`, +/// but adding and using this feels very challenging. +/// What we instead do is to use the expected type of the match/if/whatever as +/// the initial coercion lub. This allows us to use the lub of "expected type of match" with +/// "types from previous branches" as the coercion target, which can contains both expectations. +/// +/// Two concerns with this approach: +/// - We may have incompatible `final_ty` if that lub is different from the expected +/// type of the match. However, in this case coercing the final type of the +/// `CoerceMany` to its expected type would have error'd anyways, so we don't care. +/// - We may constrain the `expected_ty` too early. For some branches with +/// type `a` and `b`, we end up with `(a lub expected_ty) lub b` instead of +/// `(a lub b) lub expected_ty`. They should be the same type. However, +/// `a lub expected_ty` may constrain inference variables in `expected_ty`. +/// In this case the difference does matter and we get actually incorrect results. +/// FIXME: Ideally we'd compute the final type without unnecessarily constraining +/// the expected type of the match when computing the types of its branches. pub(crate) struct CoerceMany<'tcx, 'exprs, E: AsCoercionSite> { expected_ty: Ty<'tcx>, final_ty: Option>, From ec874ee602c75893bf70d2ed15e3f72ff64770e3 Mon Sep 17 00:00:00 2001 From: Adwin White Date: Thu, 9 Oct 2025 15:58:53 +0800 Subject: [PATCH 5/5] fix: please spellcheck --- compiler/rustc_hir_typeck/src/coercion.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index 17cfb05a45245..133abb0610768 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1400,7 +1400,7 @@ pub fn can_coerce<'tcx>( /// When coercing, each branch should use the following expectations for type inference: /// - The branch can be coerced to the expected type of the match/if/whatever. /// - The branch can be coercion lub'd with the types of the previous branches. -/// Ideally we'd have some sort of `Expectation::ParticipatsInCoerceLub(ongoing_lub_ty, final_ty)`, +/// Ideally we'd have some sort of `Expectation::ParticipatesInCoerceLub(ongoing_lub_ty, final_ty)`, /// but adding and using this feels very challenging. /// What we instead do is to use the expected type of the match/if/whatever as /// the initial coercion lub. This allows us to use the lub of "expected type of match" with