Skip to content

Conversation

@adwinwhite
Copy link
Contributor

@adwinwhite adwinwhite commented Apr 25, 2025

Fixes #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 and check_expr_if where CoerceMany is also used do the same.

FCP Proposal:

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

fn foo() {}
fn bar() {} 
fn f<T>(_: [T; 2]) {}

f([foo, bar]);

and

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(#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.

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 depends on using the first element's type as expectation.

@rustbot
Copy link
Collaborator

rustbot commented Apr 25, 2025

r? @petrochenkov

rustbot has assigned @petrochenkov.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 25, 2025
Copy link
Member

@jieyouxu jieyouxu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a test to demonstrate the effect of this change. Especially since with this change, we would be accepting more code than on master (this is the snippet in the original issue):

fn foo() {}
fn bar() {}

fn main() {
    let _a = if true { foo } else { bar };
    let _b = vec![foo, bar];
    let _c = [foo, bar];
    d(if true { foo } else { bar });
    e(vec![foo, bar]);
    f([foo, bar]);  // <- this PR now accepts this
}

fn d<T>(_: T) {}
fn e<T>(_: Vec<T>) {}
fn f<T>(_: [T; 2]) {}

whereas on master this snippet does not compile w/

error[E0308]: mismatched types
  --> src/main.rs:10:7
   |
10 |     f([foo, bar]);
   |     - ^^^^^^^^^^ expected `[fn() {foo}; 2]`, found `[fn(); 2]`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected array `[fn() {foo}; 2]`
              found array `[fn(); 2]`
note: function defined here
  --> src/main.rs:15:4
   |
15 | fn f<T>(_: [T; 2]) {}
   |    ^    ---------

For more information about this error, try `rustc --explain E0308`.

I'm surprised there are no ui test diffs.

@petrochenkov
Copy link
Contributor

r? types

@rustbot rustbot added the T-types Relevant to the types team, which will review and decide on the PR/issue. label Apr 25, 2025
@rustbot rustbot assigned oli-obk and unassigned petrochenkov Apr 25, 2025
@adwinwhite
Copy link
Contributor Author

adwinwhite commented Apr 26, 2025

There're some similar errors, but I'm unsure whether it's okay to allow these code. The Rust Reference.

fn foo() {}
fn bar() {}

fn main() {
    let block_var = 'a: { // Easy to fix, but not specified by the Rust Reference.
        if false {
            break 'a foo;
        }
        break 'a bar;
    };

    let loop_var = loop {  // Easy to fix, but not specified by the Rust Reference.
        if false {
            break foo;
        }
        break bar;
    };

    let closure_var = || { // More complicated. But this should work according to the Rust Reference.
        if false {
            return foo;
        }
        return bar;
    };
}

@oli-obk oli-obk removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 28, 2025
@oli-obk
Copy link
Contributor

oli-obk commented Apr 28, 2025

There're some similar errors, but I'm unsure whether it's okay to allow these code

Yea I think these all should work, too. Please fix the easy ones and add tests for all of them if we don't already have any

@oli-obk oli-obk added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 28, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@adwinwhite adwinwhite force-pushed the fn-pointer-coercion branch from fdbaf03 to b31fa29 Compare May 6, 2025 02:24
@adwinwhite
Copy link
Contributor Author

Turns out the ‘easy’ cases aren’t so easy after all. I can't fix the inference regressions for now, but I might revisit them later once I understand type inference better.

@adwinwhite
Copy link
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 6, 2025
@oli-obk
Copy link
Contributor

oli-obk commented May 7, 2025

I am not going to get to this in the next 10 days. I'll need to review the general state of inference here and write a T-types FCP text

@bors
Copy link
Collaborator

bors commented May 29, 2025

☔ The latest upstream changes (presumably #141716) made this pull request unmergeable. Please resolve the merge conflicts.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 3, 2025

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

fn foo() {}
fn bar() {} 
fn f<T>(_: [T; 2]) {}

f([foo, bar]);

and

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]);

@rfcbot merge

@rfcbot
Copy link

rfcbot commented Jun 3, 2025

Team member @oli-obk has proposed to merge this. The next step is review by the rest of the tagged team members:

Concerns:

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jun 3, 2025
Veykril added a commit to Veykril/rust-analyzer that referenced this pull request Jun 3, 2025
@BoxyUwU
Copy link
Member

BoxyUwU commented Jan 28, 2026

I think that was previously tried upthread somewhere and it broke a lot of stuff, i can't remember where though. but note that other places in inference that use CoerceMany already have such an issue. e.g. if else has weird behaviour around expectations with inference vars

@jackh726
Copy link
Member

I think that was previously tried upthread somewhere and it broke a lot of stuff

I don't think the specific variant I am suggesting (checking if coerce_to contains any inference variable) has been checked, only with the try_structurally_resolve_and_adjust_for_branches call, which removes any expectation if coerce_to is an infer var. This is both stronger (because of NoExpectation but also weaker because of is_ty_var()`).

@adwinwhite
Copy link
Contributor Author

adwinwhite commented Jan 28, 2026

I'm realizing, that I think this PR is insufficient to prevent all array coercion from the first element. You can have a coerce_to that contains an inference variables, but is not a var itself.

You're right that it's insufficient. I think it's not observable now. Since coercion only works for the outmost type, the code will fail lub coercion anyway.
Nonetheless, I'll change them to use has_infer_vars in case lub coercion is enhanced in the future.

But the coercion might need the expectation type to make progress even if it contains ty vars.
It seems too strict to replace the expectation with a new infer var in that case.
Let me test the changes locally first.

@adwinwhite
Copy link
Contributor Author

adwinwhite commented Jan 28, 2026

new branch diff

    let _: Vec<Box<dyn Fn(isize) -> _>> = vec![
        Box::new(|x| (x as u8)),
        Box::new(|x| (x as i16 as u8)),
    ];

This fails with log:

error[E0308]: mismatched types
  --> /home/adwin/Code/rust/rust/base/tests/ui/coercion/coerce-expect-unsized.rs:39:43
   |
LL |       let _: Vec<Box<dyn Fn(isize) -> _>> = vec![
   |  ___________________________________________^
LL | |         Box::new(|x| (x as u8)),
   | |                  --- the found closure
LL | |         Box::new(|x| (x as i16 as u8)),
LL | |     ];
   | |     ^
   | |     |
   | |_____expected `Box<[Box<dyn Fn(isize) -> _>]>`, found `Box<[Box<{closure@...}>; 2]>`
   |       arguments to this function are incorrect

@jackh726
Copy link
Member

Ah, yep. That one. I ran across that one too, and I think was the one that made me just not change array coercion in my testing (370fc9b#diff-502fc8fe28ac40b5ad34931ece468fa6755533b1aeb4ce89c528718ec9cef106R1684), because I don't actually know how to solve that.

@jackh726
Copy link
Member

Alright, so, I'm good to land the PR as-is, and it's been FCPed. Can you rebase?

@rustbot
Copy link
Collaborator

rustbot commented Jan 29, 2026

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@rust-log-analyzer

This comment has been minimized.

@jackh726
Copy link
Member

I see the date that this was opened and am terribly sorry that you've waited this long @adwinwhite. That being said, I appreciate you opening this and seeing it through :)

Overall here, even though I am doing other experimentation in this area, these changes here align with the principles that I am trying to achieve (that the LUB inference is "somewhat" independent from the "expected type" - and also doesn't directly result in any additional order-dependent behavior).

@bors r+ rollup=never

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 29, 2026

📌 Commit b235cc2 has been approved by jackh726

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 29, 2026
@rust-bors

This comment has been minimized.

@rust-bors rust-bors bot added merged-by-bors This PR was explicitly merged by bors. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jan 30, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 30, 2026

☀️ Test successful - CI
Approved by: jackh726
Duration: 3h 7m 24s
Pushing 35a31ba to main...

@rust-bors rust-bors bot merged commit 35a31ba into rust-lang:main Jan 30, 2026
12 checks passed
@rustbot rustbot added this to the 1.95.0 milestone Jan 30, 2026
@github-actions
Copy link
Contributor

What is this? This is an experimental post-merge analysis report that shows differences in test outcomes between the merged PR and its parent PR.

Comparing 842bd5b (parent) -> 35a31ba (this PR)

Test differences

Show 14 test diffs

Stage 1

  • [ui] tests/ui/coercion/closure-in-array.rs: [missing] -> pass (J1)
  • [ui] tests/ui/coercion/coerce-many-with-ty-var.rs: [missing] -> pass (J1)

Stage 2

  • [ui] tests/ui/coercion/closure-in-array.rs: [missing] -> pass (J0)
  • [ui] tests/ui/coercion/coerce-many-with-ty-var.rs: [missing] -> pass (J0)

Additionally, 10 doctest diffs were found. These are ignored, as they are noisy.

Job group index

Test dashboard

Run

cargo run --manifest-path src/ci/citool/Cargo.toml -- \
    test-dashboard 35a31ba763976907cd38ba88743a3aefbaf8ffff --output-dir test-dashboard

And then open test-dashboard/index.html in your browser to see an overview of all executed tests.

Job duration changes

  1. aarch64-apple: 12512.1s -> 8964.1s (-28.4%)
  2. x86_64-gnu-gcc: 4165.1s -> 3407.2s (-18.2%)
  3. pr-check-1: 1942.5s -> 1611.1s (-17.1%)
  4. dist-aarch64-apple: 7968.9s -> 6754.1s (-15.2%)
  5. x86_64-gnu-tools: 3801.2s -> 3304.8s (-13.1%)
  6. i686-gnu-2: 6028.9s -> 5251.1s (-12.9%)
  7. x86_64-gnu-miri: 4789.1s -> 4172.3s (-12.9%)
  8. x86_64-rust-for-linux: 3098.3s -> 2704.6s (-12.7%)
  9. aarch64-gnu-llvm-20-1: 3875.2s -> 3427.0s (-11.6%)
  10. aarch64-msvc-2: 5890.6s -> 6546.6s (+11.1%)
How to interpret the job duration changes?

Job durations can vary a lot, based on the actual runner instance
that executed the job, system noise, invalidated caches, etc. The table above is provided
mostly for t-infra members, for simpler debugging of potential CI slow-downs.

@rust-timer
Copy link
Collaborator

Finished benchmarking commit (35a31ba): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

This benchmark run did not return any relevant results for this metric.

Cycles

This benchmark run did not return any relevant results for this metric.

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 473.781s -> 476.214s (0.51%)
Artifact size: 397.87 MiB -> 397.88 MiB (0.00%)

github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Jan 30, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
github-actions bot pushed a commit to rust-lang/rustc-dev-guide that referenced this pull request Jan 31, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
github-actions bot pushed a commit to rust-lang/compiler-builtins that referenced this pull request Jan 31, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
Kobzol pushed a commit to Kobzol/portable-simd that referenced this pull request Feb 3, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
github-actions bot pushed a commit to rust-lang/stdarch that referenced this pull request Feb 5, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
github-actions bot pushed a commit to rust-lang/rust-analyzer that referenced this pull request Feb 5, 2026
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`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/_match.rs#L72) and [`check_expr_if`](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expr.rs#L1329) where `CoerceMany` is also used do the [same](https://github.com/rust-lang/rust/blob/847e3ee6b0e614937eee4e6d8f61094411eadcc0/compiler/rustc_hir_typeck/src/expectation.rs#L50). 

### [FCP Proposal](rust-lang/rust#140283 (comment)):
> 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](rust-lang/rust#140283 (comment)) depends on using the first element's type as expectation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. merged-by-bors This PR was explicitly merged by bors. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. T-types Relevant to the types team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rust fails to coerce to a fn pointer when passing an array as an argument to a generic function.