-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Fix accidental type inference in array coercion #140283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
rustbot has assigned @petrochenkov. Use |
There was a problem hiding this 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.
r? types |
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.
} |
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 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
fdbaf03
to
b31fa29
Compare
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. |
@rustbot ready |
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 |
☔ The latest upstream changes (presumably #141716) made this pull request unmergeable. Please resolve the merge conflicts. |
Array expressions normally lub their element expressions' types to ensure that things like 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 |
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. |
@bors try |
Fix accidental type inference in array coercion
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
💔 Test failed (CI). Failed jobs:
|
The first error:
Source: rust/compiler/rustc_metadata/src/locator.rs Lines 432 to 435 in 3507a74
Trying to build stage2 compiler locally:
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. |
that's tragic :/ alright. @rustbot resolve concern try going all the way, no expectation Please revert the PR to the previous state |
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 |
Would it be possible/desired to add a FCW for the "all the way" case? |
a7b44b9
to
6ab8f33
Compare
woops @rfcbot resolve concern try going all the way, no expectation
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 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 |
@rfcbot resolve try going all the way, no expectation |
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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`.
} |
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()])
} |
@bors try |
This comment has been minimized.
This comment has been minimized.
Fix accidental type inference in array coercion
@craterbot check |
👌 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
🚧 Experiment ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more |
// 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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. |
|
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
andcheck_expr_if
whereCoerceMany
is also used do the same.FCP Proposal:
Remaining inconsistency with
if
andmatch
(#145048):The typeck of array always uses the element coercion target type as the expectation of element exprs while
if
andmatch
useNoExpectation
if the expected type is an infer var.This causes that array doesn't support nested coercion.
But we can't simply change this behavior to be the same as
if
andmatch
since many code depends on using the first element's type as expectation.