Auto merge of #140283 - adwinwhite:fn-pointer-coercion, r=jackh726

Fix accidental type inference in array coercion


Fixes rust-lang/rust#136420.

If the expectation of array element is a type variable, we should avoid resolving it to the first element's type and wait until LUB coercion is completed.
We create a free type variable instead which is only used in this `CoerceMany`.

[`check_expr_match`](847e3ee6b0/compiler/rustc_hir_typeck/src/_match.rs (L72)) and [`check_expr_if`](847e3ee6b0/compiler/rustc_hir_typeck/src/expr.rs (L1329)) where `CoerceMany` is also used do the [same](847e3ee6b0/compiler/rustc_hir_typeck/src/expectation.rs (L50)). 

### [FCP Proposal](https://github.com/rust-lang/rust/pull/140283#issuecomment-2933771068):
> Array expressions normally lub their element expressions' types to ensure that things like `[5, 5_u8]` work and don't result in type mismatches. When invoking a generic function `fn foo<T>(_: [T; N])` with an array expression, we end up with an infer var for the element type of the array in the signature. So when typecking the first array element we compare its type with the infer var and thus subsequently require all other elements to be the same type.
> 
> This PR changes that to instead fall back to "not knowing" that the argument type is array of infer var, but just having an infer var for the entire argument. Thus we typeck the array expression normally, lubbing the element expressions, and then in the end comparing the array expression's type with the array of infer var type.
> 
> Things like
> 
> ```rust
> fn foo() {}
> fn bar() {} 
> fn f<T>(_: [T; 2]) {}
> 
> f([foo, bar]);
> ```
> 
> and
> 
> ```rust
> struct Foo;
> struct Bar;
> trait Trait {}
> impl Trait for Foo {}
> impl Trait for Bar {} 
> fn f<T>(_: [T; 2]) {}
> 
> f([&Foo, &Bar as &dyn Trait]);
> ```

### Remaining inconsistency with `if` and `match`(rust-lang/rust#145048):
The typeck of array always uses the element coercion target type as the expectation of element exprs while `if` and `match` use `NoExpectation` if the expected type is an infer var.
This causes that array doesn't support nested coercion. 
```rust
fn foo() {}
fn bar() {}
fn main() {
    let _ =  [foo, if false { bar } else { foo }]; // type mismatch when trying to coerce `bar` into `foo` in if-then branch coercion.
}
```
But we can't simply change this behavior to be the same as `if` and `match` since [many code](https://github.com/rust-lang/rust/pull/140283#issuecomment-3190564399) depends on using the first element's type as expectation.
This commit is contained in:
bors 2026-01-29 21:36:31 +00:00
commit 35a31ba763
5 changed files with 92 additions and 1 deletions

View file

@ -1365,6 +1365,7 @@ pub fn can_coerce<'tcx>(
/// - WARNING: I don't believe this final type is guaranteed to be /// - WARNING: I don't believe this final type is guaranteed to be
/// related to your initial `expected_ty` in any particular way, /// related to your initial `expected_ty` in any particular way,
/// although it will typically be a subtype, so you should check it. /// 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 /// - Invoking `complete()` may cause us to go and adjust the "adjustments" on
/// previously coerced expressions. /// previously coerced expressions.
/// ///
@ -1378,6 +1379,28 @@ pub fn can_coerce<'tcx>(
/// } /// }
/// let final_ty = coerce.complete(fcx); /// 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::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
/// "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> { pub(crate) struct CoerceMany<'tcx> {
expected_ty: Ty<'tcx>, expected_ty: Ty<'tcx>,
final_ty: Option<Ty<'tcx>>, final_ty: Option<Ty<'tcx>>,

View file

@ -1670,11 +1670,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let coerce_to = expected let coerce_to = expected
.to_option(self) .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)); .unwrap_or_else(|| self.next_ty_var(expr.span));
let mut coerce = CoerceMany::with_capacity(coerce_to, args.len()); let mut coerce = CoerceMany::with_capacity(coerce_to, args.len());
for e in args { 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 e_ty = self.check_expr_with_hint(e, coerce_to);
let cause = self.misc(e.span); let cause = self.misc(e.span);
coerce.coerce(self, &cause, e, e_ty); coerce.coerce(self, &cause, e, e_ty);

View file

@ -0,0 +1,7 @@
// Weakened closure sig inference by #140283.
fn foo<F: FnOnce(&str) -> usize, const N: usize>(x: [F; N]) {}
fn main() {
foo([|s| s.len()])
//~^ ERROR: type annotations needed
}

View file

@ -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`.

View file

@ -0,0 +1,36 @@
//@ 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>(_: T) {}
fn infer_array_element<T>(_: [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,
});
}