-
-
Notifications
You must be signed in to change notification settings - Fork 15.5k
HIR typeck: use correct expectations for negation operators #151539
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
Draft
dianne
wants to merge
2
commits into
rust-lang:main
Choose a base branch
from
dianne:unop-expectations
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| fn match_not_diverging_block() { | ||
| // Due to never-to-any coercion, the type of `{ return }` is a fresh type variable. Since we | ||
| // can't resolve its type immediately, we can't negate it. `!` has a `Not` impl, so if the | ||
| // pre-fallback type of `{ return }` was `!` instead, this would compile. | ||
| match !{ return } {} | ||
| //~^ ERROR type annotations needed | ||
| } | ||
|
|
||
| fn if_not_diverging_block() { | ||
| // Here, `if` uses an expected type of `bool` for its condition, which we previously propagated | ||
| // to the `!` operator's operand; see <https://github.com/rust-lang/rust/issues/151202>. To | ||
| // prevent breakage in fixing that bug, we currently still accept this. | ||
| // TODO: fcw | ||
| if !{ return } {} | ||
| } | ||
|
|
||
| fn main() {} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| error[E0282]: type annotations needed | ||
| --> $DIR/not-diverging-block.rs:5:11 | ||
| | | ||
| LL | match !{ return } {} | ||
| | ^^^^^^^^^^^ cannot infer type | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0282`. |
104 changes: 104 additions & 0 deletions
104
tests/ui/typeck/inference-hack-for-unop-operand-fail.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| //! Tests for the inference hack in <https://github.com/rust-lang/rust/pull/151539>. As of that PR, | ||
| //! we no longer propagate type expectations from `!` and `-` operators to their operands. To | ||
| //! minimize breakage, if we can't infer the type of the operand to `!` or `-`, the operand is a | ||
| //! block or `match` expression, and it previously would have used the expected type of the whole | ||
| //! operation as a coercion target, we use that expected type as the type of the operand. | ||
| //! These tests are for cases where type-checking fails, either because the hack didn't apply or it | ||
| //! didn't help. See `inference-hack-for-unop-operand-pass.rs` for succesful tests. | ||
|
|
||
| fn infer_me<T>() -> T { panic!() } | ||
|
|
||
| // These used to propagate the `i8` expectation to the inner block, through both operators. Since | ||
| // we don't propagate it anymore, the inner negations now have no expectations on them. | ||
| fn not_not_block_inference_failure() { | ||
| let _: i8 = !!{ infer_me() }; //~ ERROR type annotations needed | ||
| } | ||
| fn neg_neg_block_inference_failure() { | ||
| let _: i8 = -(-{ infer_me() }); //~ ERROR type annotations needed | ||
| } | ||
| fn not_match_not_block_inference_failure() { | ||
| let _: i8 = !match infer_me() { x => !{ x } }; //~ ERROR type annotations needed | ||
| } | ||
| fn neg_match_neg_block_inference_failure() { | ||
| let _: i8 = -match infer_me() { x => -{ x } }; //~ ERROR type annotations needed | ||
| } | ||
|
|
||
| // We're not applying the hack for negated blocks and `match`es to negated `if`s or `loop`s, since | ||
| // they seemed much less common in practice. | ||
| fn not_if_inference_failure() { | ||
| let _: i8 = !if true { infer_me() } else { infer_me() }; //~ ERROR type annotations needed | ||
| } | ||
| fn neg_if_inference_failure() { | ||
| let _: i8 = -if true { infer_me() } else { infer_me() }; //~ ERROR type annotations needed | ||
| } | ||
| fn not_loop_inference_failure() { | ||
| let _: i8 = !'l: loop { break 'l infer_me() }; //~ ERROR type annotations needed | ||
| } | ||
| fn neg_loop_inference_failure() { | ||
| let _: i8 = -'l: loop { break 'l infer_me() }; //~ ERROR type annotations needed | ||
| } | ||
|
|
||
| // The following tests are for code that failed to type-check before #151539, to make sure they still | ||
| // fail in the same way now. | ||
|
|
||
| // `match` doesn't coerce its arms to its expected type if that type is `()`. `()` doesn't have a | ||
| // `Neg` or `Not` impl anyway, but the error is about needing type annotations. | ||
| fn unit_not_match_inference_failure() { | ||
| let _: () = !match infer_me() { x => x }; //~ ERROR type annotations needed | ||
| } | ||
| fn unit_neg_match_inference_failure() { | ||
| let _: () = -match infer_me() { x => x }; //~ ERROR type annotations needed | ||
| } | ||
|
|
||
| // Blocks don't special-case `()`, so the error here is about negating `()`. | ||
| fn unit_negated_blocks_inference_success_but_no_impl_exists() { | ||
| let _: () = !{ infer_me() }; //~ ERROR cannot apply unary operator `!` to type `()` | ||
| let _: () = -{ infer_me() }; //~ ERROR cannot apply unary operator `-` to type `()` | ||
| } | ||
|
|
||
| // `match` also doesn't coerce its arms to its expected type if that type is uninferred at the time | ||
| // we start checking the `match` expression. | ||
| fn initially_uninferred_not_match_inference_failure() { | ||
| // This `let`'s initializer will be checked with a fresh type variable as its expected type. | ||
| let _ = 'a: { | ||
| // That type variable is used as the expected type for the `!` operator expression. Since | ||
| // it's not inferred at this point, we'll use a fresh type variable for the `match`. | ||
| !match () { | ||
| _ => { | ||
| // Unify the expected type of the `!` operator expression with `i8`. | ||
| if false { break 'a 0i8 } | ||
| // The `match`'s type is still a fresh type variable, so we can't negate it. | ||
| infer_me() //~ ERROR type annotations needed | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| fn initially_uninferred_neg_match_inference_failure() { | ||
| let _ = 'a: { | ||
| -match () { | ||
| _ => { | ||
| if false { break 'a 0i8 } | ||
| infer_me() //~ ERROR type annotations needed | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
||
| // TODO: funny test: this was previously an occurs check failure because `!{ y }` was checked with | ||
| // `x`'s type as its expected type, but now we don't apply the hack because we know `y` is a `Box` | ||
| fn we_got_rid_of_this_occurs_check_failure_because_y_is_known_to_be_a_box() { | ||
| let x; | ||
| let y = Box::new(x); | ||
| x = !{ y }; //~ ERROR cannot apply unary operator `!` to type `Box<_>` | ||
| } | ||
|
|
||
| // TODO: funny test: we keep this occurs check failure around, but report it differently. we don't | ||
| // know `{ x }`'s type when checking the negation, so the hack fires. do we want this...? do we | ||
| // roll back and prevent the hack from firing here? if we let it through, do we keep the fcw on it? | ||
| fn we_keep_this_occurs_check_failure_around() { | ||
| let x; | ||
| let mut y = Box::new(x); | ||
| y = !{ x }; //~ ERROR overflow assigning `_` to `Box<_>` | ||
| } | ||
|
|
||
| fn main() {} |
170 changes: 170 additions & 0 deletions
170
tests/ui/typeck/inference-hack-for-unop-operand-fail.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:14:21 | ||
| | | ||
| LL | let _: i8 = !!{ infer_me() }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = !!{ infer_me::</* Type */>() }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:17:22 | ||
| | | ||
| LL | let _: i8 = -(-{ infer_me() }); | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = -(-{ infer_me::</* Type */>() }); | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:20:24 | ||
| | | ||
| LL | let _: i8 = !match infer_me() { x => !{ x } }; | ||
| | ^^^^^^^^ ------ type must be known at this point | ||
| | | | ||
| | cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = !match infer_me::</* Type */>() { x => !{ x } }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:23:24 | ||
| | | ||
| LL | let _: i8 = -match infer_me() { x => -{ x } }; | ||
| | ^^^^^^^^ ------ type must be known at this point | ||
| | | | ||
| | cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = -match infer_me::</* Type */>() { x => -{ x } }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:29:28 | ||
| | | ||
| LL | let _: i8 = !if true { infer_me() } else { infer_me() }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = !if true { infer_me::</* Type */>() } else { infer_me() }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:32:28 | ||
| | | ||
| LL | let _: i8 = -if true { infer_me() } else { infer_me() }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = -if true { infer_me::</* Type */>() } else { infer_me() }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:35:38 | ||
| | | ||
| LL | let _: i8 = !'l: loop { break 'l infer_me() }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = !'l: loop { break 'l infer_me::</* Type */>() }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:38:38 | ||
| | | ||
| LL | let _: i8 = -'l: loop { break 'l infer_me() }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: i8 = -'l: loop { break 'l infer_me::</* Type */>() }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:47:24 | ||
| | | ||
| LL | let _: () = !match infer_me() { x => x }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: () = !match infer_me::</* Type */>() { x => x }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:50:24 | ||
| | | ||
| LL | let _: () = -match infer_me() { x => x }; | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | let _: () = -match infer_me::</* Type */>() { x => x }; | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0600]: cannot apply unary operator `!` to type `()` | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:55:17 | ||
| | | ||
| LL | let _: () = !{ infer_me() }; | ||
| | ^^^^^^^^^^^^^^^ cannot apply unary operator `!` | ||
|
|
||
| error[E0600]: cannot apply unary operator `-` to type `()` | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:56:17 | ||
| | | ||
| LL | let _: () = -{ infer_me() }; | ||
| | ^^^^^^^^^^^^^^^ cannot apply unary operator `-` | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:71:17 | ||
| | | ||
| LL | infer_me() | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | infer_me::</* Type */>() | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0282]: type annotations needed | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:81:17 | ||
| | | ||
| LL | infer_me() | ||
| | ^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `infer_me` | ||
| | | ||
| help: consider specifying a concrete type for the type parameter `T` | ||
| | | ||
| LL | infer_me::</* Type */>() | ||
| | ++++++++++++++ | ||
|
|
||
| error[E0600]: cannot apply unary operator `!` to type `Box<_>` | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:92:9 | ||
| | | ||
| LL | x = !{ y }; | ||
| | ^^^^^^ cannot apply unary operator `!` | ||
| | | ||
| note: `Box<_>` does not implement `Not` | ||
| --> $SRC_DIR/alloc/src/boxed.rs:LL:COL | ||
| ::: $SRC_DIR/alloc/src/boxed.rs:LL:COL | ||
| | | ||
| = note: `Box<_>` is defined in another crate | ||
|
|
||
| error[E0275]: overflow assigning `_` to `Box<_>` | ||
| --> $DIR/inference-hack-for-unop-operand-fail.rs:101:12 | ||
| | | ||
| LL | y = !{ x }; | ||
| | ^ | ||
|
|
||
| error: aborting due to 16 previous errors | ||
|
|
||
| Some errors have detailed explanations: E0275, E0282, E0600. | ||
| For more information about an error, try `rustc --explain E0275`. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Where does this type annotations needed come from? I would expect us to wind up with a registered goal such as
?diverging: Notand then have that go from ambiguity to passing with?diverging=!after fallback occurs 🤔Are we structurally resolving the type of the match scrutinee somewhere so we never get to never type fallback? Though, tbh, I don't entirely know what the rules of never type fallback are :)
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.
We structurally resolve the type of the negation operator's operand, so we never get to never type fallback. Negation operators could use
try_structurally_resolve_typeinstead, but getting to never type fallback isn't as helpful as it could be. e.g. after this PR,if !{ return } {}would still fail to type-check; the!{ return }would have type!after fallback, so we'd get a type error because the condition was expected to be abooland but it's a!, since post-fallback we don't do the never-to-any coercion that would otherwise happen there.