-
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?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
Comment on lines
+1835
to
1836
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels somewhat weird 🤔 I would expect we use Not sure of a test where this would actually matter. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, It may introduce the problem that adjusted types of array elements are not the same since There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just replaced |
||
let cause = self.misc(e.span); | ||
coerce.coerce(self, &cause, e, e_ty); | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -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>(_: 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, | ||||
}); | ||||
|
||||
|
||||
|
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.
Uh oh!
There was an error while loading. Please reload this page.
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
andmatch
, 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 likefn(...)
in which case we wouldn't replace the expectation withNoExpectation
.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?
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.
It can break code like this,
Currently we're using an infer var as expectation for array element. With coercions of the first two elements, the infer var is known to be
(&str, &str)
so it can guide the typeck of the third element, specifically the tuple coercion(&String, &String) -> (&str, &str)
.If we use
try_structurally_resolve_and_adjust_for_branches
, the third element would haveNoExpectation
as expectation and its typeck result would be(&String, &String)
which can't be coerced into or from(&str, &str)
inCoerceMany
.The key point is that field-wise tuple coercion is supported in typeck but not in
CoerceMany
.This branch breaks the code above, but fixes #145048.