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;
    };
    // I can't come up with an example of function return type for now.
}

@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
@lcnr
Copy link
Contributor

lcnr commented Aug 8, 2025

@bors try

@rust-bors
Copy link

rust-bors bot commented Aug 8, 2025

⌛ Trying commit a7b44b9 with merge 025d58a

To cancel the try build, run the command @bors try cancel.

rust-bors bot added a commit that referenced this pull request Aug 8, 2025
Fix accidental type inference in array coercion
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-bors
Copy link

rust-bors bot commented Aug 8, 2025

💔 Test failed (CI). Failed jobs:

@adwinwhite
Copy link
Contributor Author

The first error:

error[E0308]: mismatched types
     --> compiler/rustc_metadata/src/locator.rs:435:17
      |
  435 |                 (dylib_prefix, dylib_suffix, CrateFlavor::Dylib),
      |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `(&str, &str, CrateFlavor)`, found `(&String, &Cow<'_, str>, CrateFlavor)`
      |
      = note: expected tuple `(&str, &str, CrateFlavor)`
                 found tuple `(&std::string::String, &Cow<'_, str>, CrateFlavor)`

Source:

for (prefix, suffix, kind) in [
(rlib_prefix.as_str(), rlib_suffix, CrateFlavor::Rlib),
(rmeta_prefix.as_str(), rmeta_suffix, CrateFlavor::Rmeta),
(dylib_prefix, dylib_suffix, CrateFlavor::Dylib),

Trying to build stage2 compiler locally:

error[E0308]: mismatched types
   --> /home/adwin/.cargo/registry/src/crates.io-xxxxxxxx/tracing-log-0.2.0/src/lib.rs:187:17
    |
187 |                 (&keys.target, Some(&record.target())),
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `(&Field, Option<&dyn Value>)`, found `(&Field, Option<&&str>)`
    |

Source: https://docs.rs/crate/tracing-log/latest/source/src/lib.rs#185-187

I guess there's a large amount of code depending on the first tuple's type as typeck expectation.

@lcnr
Copy link
Contributor

lcnr commented Aug 15, 2025

that's tragic :/ alright.

@rustbot resolve concern try going all the way, no expectation

Please revert the PR to the previous state

@lcnr
Copy link
Contributor

lcnr commented Aug 15, 2025

and if you can, please take the FCP proposal, affected tests + an explanation of how and why this differs from if and match, and move them to the PR description

@jackh726
Copy link
Member

Would it be possible/desired to add a FCW for the "all the way" case?

@adwinwhite adwinwhite force-pushed the fn-pointer-coercion branch from a7b44b9 to 6ab8f33 Compare August 18, 2025 07:07
@lcnr
Copy link
Contributor

lcnr commented Aug 18, 2025

woops

@rfcbot resolve concern try going all the way, no expectation

Would it be possible/desired to add a FCW for the "all the way" case?

seems hard 🤔 this doesn't impact the coercion itself, having an infer var as expectation influences recursively type checking the element. We currently eagerly error in FnCtxt::check_X and to FCW we'd need to be able to somehow use these functions without getting eager errors if it goes wrong.

I feel like having a mode where we stash errors during HIR typeck would be generally nice, but I expect that to be a larger change

@lcnr
Copy link
Contributor

lcnr commented Aug 26, 2025

@rfcbot resolve try going all the way, no expectation

@oli-obk oli-obk removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 16, 2025
Comment on lines 36 to 37


Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change

@lcnr
Copy link
Contributor

lcnr commented Sep 22, 2025

@adwinwhite do you still have your changes when writing #140283 (comment). Talking about this with @BoxyUwU right now and I don't quite get why this test fails with my proposed change 🤔

fn foo() {}
fn bar() {}
fn main() {
    let _ =  [foo, if false { bar } else { foo }]; // type mismatch when trying to coerce `bar` into `foo`.
}

@lcnr
Copy link
Contributor

lcnr commented Sep 22, 2025

please add the following as a test

fn foo<F: FnOnce(&str) -> usize, const N: usize>(x: [F; N]) {}

fn main() {
    foo([|s| s.len()])
}

@lcnr
Copy link
Contributor

lcnr commented Sep 22, 2025

@bors try

@rust-bors

This comment has been minimized.

rust-bors bot added a commit that referenced this pull request Sep 22, 2025
Fix accidental type inference in array coercion
@rust-bors
Copy link

rust-bors bot commented Sep 22, 2025

☀️ Try build successful (CI)
Build commit: 2c4e58e (2c4e58eac5a232e49065e517c1cf50b2715ccf85, parent: 9f32ccf35fb877270bc44a86a126440f04d676d0)

@BoxyUwU
Copy link
Member

BoxyUwU commented Sep 22, 2025

@craterbot check

@craterbot
Copy link
Collaborator

👌 Experiment pr-140283 created and queued.
🤖 Automatically detected try build 2c4e58e
🔍 You can check out the queue and this experiment's details.

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot craterbot added the S-waiting-on-crater Status: Waiting on a crater run to be completed. label Sep 22, 2025
@craterbot
Copy link
Collaborator

🚧 Experiment pr-140283 is now running

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

Comment on lines +1835 to 1836
// 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);
Copy link
Member

Choose a reason for hiding this comment

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

This feels somewhat weird 🤔 I would expect we use coerce.merged_ty() here to typeck each element with an expectation of the currently known coerce-LUB'd type of the array element, rather than whatever the first element happend to have the type of.

Not sure of a test where this would actually matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, coerce.merged_ty() is better than the first element's type.
But it's still best effort. We can only know the true LUB type after all typeck is done, not before.

It may introduce the problem that adjusted types of array elements are not the same since merged_ty() can change gradually.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah 👍 My thoughts here is that we've already given up on having the expected type stay the same across branches. If we wanted this we'd do what match/if do and use the original expectation adjusted for branches for each expr we check.

Instead here we're accepting that we need the expectation to change across element expressions as too much code relies on that happening in order to have nested coercions work properly. From this POV using the (in progress) LUB type seems more principled than the type of the first element's expression.

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
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand this FIXME 🤔 If we have an expected type of some infer var and we turn that into NoExpectation that ought to only affect closure signature inference, or if we were to be able to make inference progress and resolve it to more than just an infer var 🤔

Both of those cases ought to already be broken under this PR in a lot of cases though. It also seems like the examples that broke didn't actually care about either of those things.

Reading further it sounds like the original impl was wrong? Would like to see this comment updated with more of an explanation of what went wrong here.

Copy link
Contributor Author

@adwinwhite adwinwhite Sep 24, 2025

Choose a reason for hiding this comment

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

This FIXME is about this issue.
To make array coercion consistent with if and match, we ought not to use ty var as expectation here.
But a lot of code already depends on the behavior that ty var expectation gets resolved in CoerceMany thus it can guide array element's typeck. E.g. the example in my linked comment and others.
This comment isn't out of sync with the code. Perhaps I should word it better or omit it to avoid confusion.

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, what i don't understand is why using the try_structurally_resolve_and_adjust_for_branches function would break anything. That should only affect the case where we have an unconstrained infer var, but the case we care about supporting is one where that variable has been inferred to some type like fn(...) in which case we wouldn't replace the expectation with NoExpectation.

I agree that as the CoerceMany "makes progress" we need to propagate that information into the expectation of the element expressions as otherwise nested coercions will fail.

Did you try to implement this previously and found it broke stuff, do you still have that impl around for me to look at?

@adwinwhite
Copy link
Contributor Author

@adwinwhite do you still have your changes when writing #140283 (comment). Talking about this with @BoxyUwU right now and I don't quite get why this test fails with my proposed change 🤔

fn foo() {}
fn bar() {}
fn main() {
    let _ =  [foo, if false { bar } else { foo }]; // type mismatch when trying to coerce `bar` into `foo`.
}

Probably this one. I have too many local branches on this issue and the differences are subtle so I can't be sure.

This test fails with current compiler as well.
We have a ty var expectation for array element. And it gets unified with foo when coercing the first array element. Then we have foo as expectation for the if expression. But we can't coerce bar into foo as the first coercion in CoerceMany doesn't use LUB. It doesn't matter whether we're using the original ty var or another ty var as expectation as long as it's a ty var.

@rustbot
Copy link
Collaborator

rustbot commented Sep 24, 2025

⚠️ Warning ⚠️

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. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-crater Status: Waiting on a crater run to be completed. 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.