Don't require unsafe for struct and array patterns against union fields - #161771
Jules-Bertholet wants to merge 10 commits into
Conversation
|
r? @folkertdev rustbot has assigned @folkertdev. Use Why was this reviewer chosen?The reviewer was selected based on:
|
f7e9593 to
2c56306
Compare
unsafe for PatKind::Leaf against union fieldsunsafe for struct and array patterns against union fields
|
You should actually nominate for T-lang if you want them to look at this. |
|
Worth noting, for our discussion, that the example in the PR description compiles today, i.e., ahead of this PR landing: union Foo {
field: u8,
}
fn bar(foo: Foo) {
match foo {
Foo { field: _ } => (), // OK!
}
}What fails are things like this: union Foo {
field: (u8,),
}
fn bar(foo: Foo) {
match foo {
Foo { field: (..) } => (), // ERROR: Needs `unsafe`.
}
} |
|
Thanks for the correction, I edited the OP |
|
Just checking: we still Assuming we do, then I agree that allowing these categories of irrefutable patterns in union patterns makes sense 👍 |
| | PatKind::Deref { .. } | ||
| | PatKind::DerefPattern { .. } | ||
| | PatKind::Range { .. } | ||
| | PatKind::Slice { .. } |
There was a problem hiding this comment.
Pondering: [..] is also an irrefutable pattern but isn't updated here (right?)
I don't know if it's possible, but could this whole match change to being about irrefutable pattern instead, or something? If we have to whack-a-mole a whole bunch of things here, that makes me less "oh yeah let's do it" than I was before, since I don't know why people would write this.
(Notably if you're using a pat_param from a macro it'd actually be easier for it to always be unsafe so you don't need to suppress the unneeded-unsafe if they pass something simple.)
Part of why we said that unsafeck is on THIR is that it's more of a lexical check than a flow-sensitive one, so being a bit more unsafe than strictly necessary is generally fine if it's something that the human description of the thing is something that people would say "it's unsafe to do that".
There was a problem hiding this comment.
Pondering:
[..]is also an irrefutable pattern but isn't updated here (right?)
Yes it is. There's even a test.
could this whole match change to being about irrefutable pattern instead
No, irrefutability isn't sufficient. x is an irrefutable pattern but still needs to be unsafe; & _ probably should be as well. Nor is it even necessary; the unstable guard patterns are refutable, but shouldn't require unsafe.
What we care about is that the pattern does not perform a read/assert validity.
|
We talked about this in today's lang meeting. This change seems fine individually, but along the lines of @scottmcm's comments above, I'd like to do it in a way that preserves (or improves) the overall consistency and simplicity of the language. One way to evaluate that would be to review a reference PR for the change. @Jules-Bertholet would you be willing to draft one for us to review? |
|
@tmandry Here you go: rust-lang/reference#2350
|
This comment was marked as resolved.
This comment was marked as resolved.
|
@theemathas see the newly added test |
This comment was marked as resolved.
This comment was marked as resolved.
264893b to
fea33d3
Compare
|
Whoops, I forgot the important part… 🤦 fixed |
fea33d3 to
3f6a9c5
Compare
|
Some changes occurred in exhaustiveness checking cc @Nadrieril Some changes occurred in match lowering cc @Nadrieril Some changes occurred in match checking cc @Nadrieril |
These patterns don't access the union directly, only their subpatterns do. So there is no need to require `unsafe`.
75bde64 to
da31d71
Compare
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
I've addressed the semver hazard issue as it pertains to this PR. The Reference PR has also been updated. |
da31d71 to
950d61e
Compare
| if self.in_union_destructure | ||
| && !has_rest | ||
| && (single_variant.field_list_has_applicable_non_exhaustive() | ||
| || single_variant | ||
| .fields | ||
| .iter() | ||
| .any(|f| !f.vis.is_accessible_from(scope, self.tcx))) | ||
| { | ||
| // This pattern must have been lowered from a constant. | ||
| // Changes to private implementation details of said constant | ||
| // must not affect whether we require `unsafe`. | ||
| self.requires_unsafe(pat.span, AccessToUnionField); | ||
| return; | ||
| } |
There was a problem hiding this comment.
I'm confused by the logic here. Are you using has_rest/privacy to detect constant patterns? I'd prefer to add a thir pattern node that remembers patterns that were lowered from a constant (this keeps coming up and will be added by #155216 anyway), and use that to know whether we're inside a constant.
There was a problem hiding this comment.
I'm generally confused by what has_rest has to do with this PR, given that it's equivalent to a bunch of wildcard patterns anyway.
There was a problem hiding this comment.
Are you using has_rest/privacy to detect constant patterns?
Yes, specifically constant patterns whose equivalent expanded pattern could not have been written directly at the location the pattern is being used. If a struct has non-visible fields or is foreign non_exhaustive, then you need a rest pattern to do a direct pattern match; the absence of such a pattern (constants don't have rest patterns) means the pattern was expanded from a constant in another crate.
I was going for the smallest possible change; if you have a suggestion for a cleaner way to carry though this information, that's fine, will gladly do it your way.
There was a problem hiding this comment.
I'd prefer to add a thir pattern node that remembers patterns that were lowered from a constant (this keeps coming up and will be added by #155216 anyway), and use that to know whether we're inside a constant.
We already track patterns lowered from constants, I think. It's not a dedicated node anymore, but the constant's DefId gets stored in PatExtra's expanded_const field. #155216 also stores the valtree evaluation in PatExtra, but hopefully that's not needed here?
There was a problem hiding this comment.
the constant's
DefIdgets stored inPatExtra'sexpanded_constfield
I believe that is only true for the outer pattern, not its subpatterns. We could add another field to UnsafetyVisitor to track it, but that's annoying to get right (e.g. #161771 (comment)). Nadri's suggestion of a dedicated HIR pattern node has the same issue.
There was a problem hiding this comment.
the absence of such a pattern (constants don't have rest patterns) means the pattern was expanded from a constant in another crate.
Ok, I agree this works, but that's mixing semantic and syntactic concerns in a way that does not feel robust to me.
We could add another field to UnsafetyVisitor to track it, but that's annoying to get right
The logic imo is much simpler: a constant pattern inside a union pattern is unconditionally unsafe. I'd like to even reserve the right to turn the comparison into PartialEq::eq instead of expanding the pattern, see my comment on the reference PR. The difference should only be observable for ZSTs, as they are the only ones that can have constant that don't access anything.
There was a problem hiding this comment.
does not feel robust to me.
Arguably, assuming that constants are the only way to refer to private fields from a pattern is not robust either.
If you would like, I can turn the debug_assert! at https://github.com/rust-lang/rust/pull/161771/changes#diff-db8b06c3d8dde7b9f002e070c4e72fd41fc8487d988324232cd85355c63b2423R313-R321 into a full assert! or proper error.
a constant pattern inside a union pattern is unconditionally unsafe
I don't think we want to be this restrictive. It would be unexpected for e.g. () and UnitStruct {} to be safe patterns, but UnitStruct to be unsafe. If the pattern is an effective no-op, then replacing it with a PartialEq::eq call should always be valid, and also never be necessary.
There was a problem hiding this comment.
If the pattern is an effective no-op, then replacing it with a PartialEq::eq call should always be valid, and also never be necessary.
hmm, PartialEq::eq takes a reference to the place but I guess for unit structs that cannot cause extra UB?
It would be unexpected for e.g. () and UnitStruct {} to be safe patterns, but UnitStruct to be unsafe.
UnitStruct is treated as a constant pattern? In that case I agree that would be surprising, but I'm also on team "err on the side of more unsafe because what are you even trying to do here".
There was a problem hiding this comment.
It would be unexpected for e.g.
()andUnitStruct {}to be safe patterns, butUnitStructto be unsafe.
UnitStruct shouldn't be treated as a constant if it refers to a unit struct constructor; it'd get lowered with lower_variant_or_leaf, not const-to-pat. only path patterns that refer to named constant items and associated constant items get the expanded_const marker currently, I think.
notably, I think literals don't get any marker currently even though they do go through const-to-pat. this matters with deref_patterns enabled, since I think under this PR, that would let b"" match on a [u8; 0] union field (as it would lower to a PatKind::Array with no subpatterns)
There was a problem hiding this comment.
UnitStructshouldn't be treated as a constant if it refers to a unit struct constructor; it'd get lowered withlower_variant_or_leaf, not const-to-pat. only path patterns that refer to named constant items and associated constant items get theexpanded_constmarker currently, I think.
Oh, huh. But that sounds like the sort of implementation detail we don't want to enshrine in observable language behavior. The Reference explicitly promises that a unit struct is equivalent to a fieldless braced struct + a constant. So actually, this is strong evidence that we don't want to base this check around expanded_const.
Additionally, fixing #162213 will require doing this same sort of reasoning anyway.
this matters with
deref_patternsenabled, since I think under this PR, that would letb""match on a[u8; 0]union field (as it would lower to aPatKind::Arraywith no subpatterns)
Huh, didn't know deref_patterns allowed matching a T value with an &T pattern. I see no reason why we shouldn't allow this, though.
|
r? Nadrieril |
|
|
View all comments
These patterns don't access the union directly, only their subpatterns do. So there is no need to require
unsafe.For example, the following now compiles:
Also removes the
unsaferequirement for the unstable guard patterns (#129967) in this position.Reference PR: rust-lang/reference#2350
@rustbot label T-lang needs-fcp A-patterns F-guard_patterns I-lang-nominated