Uh oh!
There was an error while loading. Please reload this page.
Make typeck aware of uninhabited types - #108993
Conversation
rustbot
commented
Mar 10, 2023
(rustbot has picked a reviewer for you, use r? to override) |
rustbot
commented
Mar 10, 2023
Some changes occurred in src/tools/rust-analyzer cc @rust-lang/wg-rls-2 |
eed5cd7 to
bbf2a43Comparebors
commented
Mar 11, 2023
☔ The latest upstream changes (presumably #109015) made this pull request unmergeable. Please resolve the merge conflicts. |
bbf2a43 to
9bfac82Comparerustbot
commented
Mar 11, 2023
The Miri subtree was changed cc @rust-lang/miri |
bors
commented
Mar 13, 2023
☔ The latest upstream changes (presumably #108872) made this pull request unmergeable. Please resolve the merge conflicts. |
9bfac82 to
5e396d2Comparemichaelwoerister
commented
Mar 14, 2023
I know too little about this area. |
bors
commented
Mar 16, 2023
☔ The latest upstream changes (presumably #107270) made this pull request unmergeable. Please resolve the merge conflicts. |
tmandry
commented
Mar 21, 2023
The lang team discussed this in our meeting (notes). We didn't see any obvious concerns but want to understand more about what this change does that we and/or the types team should be looking closely at. (Is it just the fact that the compiler accepts more programs now that it didn't before?) |
lcnr
left a comment
There was a problem hiding this comment.
some questions and nits, apart from that r=me on the impl 🤔
There was a problem hiding this comment.
i am a bit confused why that doesn't warn 🤔
Constructors are carved out on purpose, to avoid an "unreachable call" diagnostic on them, which is not very useful as they are inert.
I don't understand that comment 🤔 I would assume the outer constructor to keep warning here?
structFoo<T>(T);fnfoo() -> Foo<String>{Foo(returnFoo(String::new()))}There was a problem hiding this comment.
please add a comment here. is this about which expressions can actually introduce an uninhabited type. E.g. arrays is not considered because in [return;] its the nested expression that is uninhabited 🤔 but then why do we return false for ExprKind::Continue and ExprKind::Return?
There was a problem hiding this comment.
why not remove that branch in this case 😅
There was a problem hiding this comment.
same here? do we warn for unreachable code but you still can't remove the match arm or sth?
There was a problem hiding this comment.
Yes, removing the arm gives a non-exhaustive pattern error.
There was a problem hiding this comment.
i think it's a pretty big issue if the unreachable code lint has false positives. I guess we could instead write match Dlsym {} here. Or what's actually happening is that the fact that Dlsym is uninhabited is conceptionally private to the linux shim.
I think the fix should be to instead change Dlsym to
pubstructDlsym(Void);Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
i guess one issue is that by doing it eagerly during hir typeck, we're missing cases which only diverge after type inference: (playground) structWrapper<T>(Option<T>);impl<T>Wrapper<T>{fnsomething(&self) -> T{panic!()}}// using `FnOnce` bound to use `!` on stable.fninfer<F:FnOnce() -> R,R>(_:Wrapper<R>){}pubfnwith_never(){let x = Wrapper(None);iffalse{// currently doesn't warn as liveness analysis intentionally// skips `!`, won't warn with this PR//// warns that `y` is unused which just seems buggy?let y = x.something();let _ = y;}infer::<fn() -> !,_>(x)}enumVoid{}pubfnwith_void(){let x = Wrapper(None);iffalse{// currently warns that the assignment is unreachable,// will stop warning with this PRlet y = x.something();let _ = y;}infer::<fn() -> Void,_>(x)}i do think that is acceptable though 🤷 |
lcnr
commented
Mar 24, 2023
From what I can tell the only meaningful change is that expressions which have an uninhabited type are now also considered as diverging by typeck (instead of only being considered so for the tests/ui/uninhabited/break-diverging-value.rs and tests/ui/uninhabited/break-diverging-value-pass.rs for examples of that change it feels pretty straightforward to me and should be the responsibility of t-types, so @rfcbot fcp merge |
Team member @lcnr has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. 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. |
5e396d2 to
8b92f6aCompare
This comment has been minimized.
This comment has been minimized.
82200be to
fbc635fCompare
This comment has been minimized.
This comment has been minimized.
Nadrieril
commented
Apr 7, 2024
@WaffleLapkin this may be interesting to you re: the never type |
| fn wild_let() -> u32 { | ||
| let ptr: *const Void = std::ptr::null(); | ||
| unsafe { | ||
| //~^ ERROR: mismatched types | ||
| let _ = *ptr; | ||
| } | ||
| } |
There was a problem hiding this comment.
This is incorrect: we should not infer that this function diverges, because let _ = *ptr; does not cause a read. In fact let _ = *(std::ptr::null::<Void>()); is valid code that creates a null pointer and does nothing with it (or maybe we need dangling(), not sure). Typechecking should distinguish place expressions from value expressions in order to be correct.
There was a problem hiding this comment.
Is this not pre-existing though? I did not have time to work on #117288 and neither anyone else as far as I'm aware. Do we have any infrastructure to distinguish places & values in hir?
There was a problem hiding this comment.
Idk if that's the same issue, but we shouldn't regress behavior.
Place vs value is just a matter of matching on ExprKind, there's probably a helper somewhere to distinguish the two
There was a problem hiding this comment.
So what you are saying is that an expression is a place iff it's Unary(Deref, _), Index(..) or Field(..)? But are those always places? Or does it not matter because in cases where we care they are places?...
There was a problem hiding this comment.
Please use MemCategorizationContext, I think that's the right helper for this
There was a problem hiding this comment.
There is
rust/compiler/rustc_hir/src/hir.rs
Lines 1622 to 1626 in bb78dba
MemCategorizationContext looks right for this, thanks for the pointer!| match secretely_void { | ||
| _ => {} //~ ERROR unreachable | ||
| _ => {} | ||
| } |
There was a problem hiding this comment.
I'm confused, how come this "unreachable" is removed but functions like option_never2 above still compile? Can you try running just this on its own:
fnoption_never(x:Void) -> implCopy{iffalse{matchoption_never(x){None => {}}}Some(x)}This should error as non-exhaustive, right?
There was a problem hiding this comment.
Oh, I know: when there are no explicit patterns like in match secretely_void { _ => {} }, exhaustiveness calls inhabited_predicate on the whole type. When there are patterns, it calls it on the subtypes after normalizing. So in my option_never example it sees that Some contains Void.
In other words, this makes exhaustiveness inconsistent:
let secretely_void = SecretelyVoid(opaque_void);match secretely_void {SecretelyVoid(_) => {}// WARN: unreachable}match secretely_void {}// ERROR: non_exhaustiveThere was a problem hiding this comment.
I think the simple thing to do is keep the old normalizing behavior when calling is_uninhabited for exhaustiveness checking. This also solves the breaking change issue with min_exhaustive_patterns.
| pub union U { u: (), e: E, } | ||
| pub const C: () = { | ||
| pub const C: () = { //~ ERROR evaluation of constant value failed | ||
| let E::A(ref a) = unsafe { &(&U { u: () }).e}; | ||
| }; |
There was a problem hiding this comment.
Same remark regarding places vs expressions: there is no value of type E created here, so I don't think there should be any unreachable code reached.
| if !span.is_desugaring(DesugaringKind::CondTemporary) | ||
| && !span.is_desugaring(DesugaringKind::Async) | ||
| && !orig_span.is_desugaring(DesugaringKind::Await) |
There was a problem hiding this comment.
There was a distinction between span and orig_span that has been lost, was that intentional?
| @@ -0,0 +1,26 @@ | |||
| // Verify that we do not warn on type-dependent constructors (`Self::A` below). | |||
There was a problem hiding this comment.
This comment no longer makes sense now that you warn for constructors
bors
commented
Apr 8, 2024
☔ The latest upstream changes (presumably #123569) made this pull request unmergeable. Please resolve the merge conflicts. |
WaffleLapkin
commented
Apr 8, 2024
@Nadrieril thanks for the ping <3 I'm actually proposing to remove diverging block special casing in the next edition: #123590. And I don't really see how semantic changes in this PR are useful. I do not think this is a good idea. The lint changes are probably positive though, can we change the lint without changing semantics? |
fbc635f to
756cb48Compare
This comment has been minimized.
This comment has been minimized.
lcnr
commented
Aug 14, 2024
in case it's difficult to find my new comment |
lcnr
commented
Aug 14, 2024
there has also been some discussion between @WaffleLapkin and @Nadrieril here on whether this approach is desirable I think? May also be good for one of you two to take the review here |
WaffleLapkin
commented
Aug 14, 2024
So the change proposed in #123590 did not work out, because language team didn't feel that the simpler semantics justify the breakage (esp in the light that there are code patterns that become more awkward). The sentiment generally being "if we would create a new language now: yes. but given the state of rust, probably not". Still, I'm opposed to adding more such special cases. I don't think that the code example in the PR description should compile: enumVoid{}fnget_void() -> Void{panic!()}fnloop_break_void() -> i32{let loop_value = loop{breakget_void()};}(block ends in a statement, why do we want to allow this?) As I already said in #108993 (comment) changing the lint seems like a good idea. But changing the language semantics doesn't sound good. |
Nadrieril
commented
Aug 14, 2024
I don't recall all the details of this here PR, but I do remember reaching the conclusion that "exhaustiveness checking must not change as a result of this PR". Also it seems that |
cjgillot
commented
Aug 15, 2024
Haven't accounted for all the comments yet. |
This comment has been minimized.
This comment has been minimized.
bors
commented
Aug 20, 2024
☔ The latest upstream changes (presumably #122551) made this pull request unmergeable. Please resolve the merge conflicts. |
Avoid deriving PartialOrd on Diverges since it includes fields which should not affect ordering.
7e37e36 to
b777188Comparerust-log-analyzer
commented
Oct 30, 2024
The job Click to see the possible cause of the failure (guessed by this bot) |
bors
commented
Nov 4, 2024
☔ The latest upstream changes (presumably #132581) made this pull request unmergeable. Please resolve the merge conflicts. |
Rebase of #100288, thanks @camsteffen for doing the hard work!
This PR modifies the unreachable code lint to operate from within typeck, based on inhabitedness of types discovered as the analysis goes. As a consequence, the lint fires in many extra places. Constructors are carved out on purpose, to avoid an "unreachable call" diagnostic on them, which is not very useful as they are inert.
In addition, inhabitedness information is used from within typeck to guide inference. This allows the following snippet to compile, as the compiler is not able to prove that
loop_break_voiddiverges on all paths.Inhabitedness information is privacy aware, so this is only possible if
Voidis visibly uninhabited at the use site. Seetests/ui/break-diverging-value.rstest for a case where it is not.