-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Implement untagged unions (RFC 1444) #36016
Conversation
Actually, I'd like to make the implementation a bit more conservative, than written in the RFC. Effectively, |
@@ -181,6 +181,9 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { | |||
ExprKind::Adt { | |||
adt_def, variant_index, substs, fields, base | |||
} => { // see (*) above | |||
let is_union = adt_def.adt_kind() == ty::AdtKind::Union; | |||
let active_field_index = if is_union { Some(fields[0].name.index()) } else { None }; | |||
|
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 might be a better idea to use direct assignment here instead of an Rvalue
- that would remove the need to change AggregateKind::Adt
- alternatively, an Union
AggregateKind
could be added.
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.
alternatively, an Union AggregateKind could be added
IIRC, I tried this and didn't like the result (too much common boilerplate code for Adt
and Union
)
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.
The field assignment approach might be best then.
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.
Not sure what it means exactly. Any example?
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.
Instead of lvalue = union { f: operand }
you'd have lvalue.f = operand
.
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.
Just realized that could make { let u: Union; u.f = x; u }
pass a future MIR move checker - do we want that?
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.
Future move checker with full support for fragments should accept both { let u: Union; u.f = x; u }
and { let s: StructWith1Field; s.f = x; s }
I guess, see the last item in #36016 (comment) and "# Future work" in https://github.com/rust-lang/rust/blob/master/src/librustc_borrowck/borrowck/README.md.
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.
Oh, then the field approach is definitely correct - except for custom Drop
impls, which require whole-value initialization "to be armed", what's the plan there?
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.
Oh, then the field approach is definitely correct - except for custom Drop impls, which require whole-value initialization "to be armed", what's the plan there?
There is needs to be special treatment for types that implement drop, yes. The idea is that you can't have such a type in a "partially initialized" state, so you can't move out from its fields, nor can you write to a field if the type itself is not initialized.
LGTM, great job! r? @nikomatsakis for the borrowck bits & miscellaneous other details. |
That would be nice, this patch set bitrots relatively quickly. |
@@ -178,11 +177,28 @@ impl<'a, 'tcx, 'v> Visitor<'v> for EffectCheckVisitor<'a, 'tcx> { | |||
self.require_unsafe(expr.span, "use of mutable static"); | |||
} | |||
} | |||
hir::ExprField(ref base_expr, field) => { | |||
if let ty::TyUnion(..) = self.tcx.expr_ty_adjusted(base_expr).sty { |
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.
Nit: I feel like it'd be nice to match
the sty (exhaustively) and have a bug!
call for the unexpected cases. This makes me mildly nervous that some bug or addition might slip through.
// Unions | ||
//=----------------------------------------------------------------------------- | ||
|
||
struct UnionMemberDescriptionFactory<'tcx> { |
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.
ping @michaelwoerister -- does this file look roughly right? :)
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.
Looks pretty good to me!
OK, did a review. Here are my thoughts:
|
This is exactly how it turned out to be from implementation point of view. Unions tend to stay close to their syntactic surface representation and therefore treated in the same way as structs by most of compiler passes except for layout calculation and borrow/move checking. |
OK here is my master list of tests. Feel free to check-off ones that you feel you already have (I have already done so in some cases, but I know you've got a lot of these covered already).
Here are some tests we could add, except that I think the impl will not work quite the way I would want (and I'm ok with that):
|
One thing I thought of while doing this: we probably want to consider the borrowck error messages. i.e., I suspect they could give better advice (in general we try to point out when a restriction on path |
What I wrote is just the simplest way to implement it :D
That assert in |
Having thought about the borrowck -- I think I'm inclined to land it with this approach and consider patching it up and refactoring it later. This PR is too good to stall for long. :) |
r=me once tests are set. |
Make parsing of union items backward compatible Add some tests
Fix some typeck bugs blocking drop tests
Fix alignment for packed unions Add some missing privacy test Get rid of `unimplemented_unions` macro
Add some more tests
Fix union debuginfo test on lldb
@bors r=nikomatsakis |
📌 Commit 436cfe5 has been approved by |
⌛ Testing commit 436cfe5 with merge d748fa6... |
Implement untagged unions (RFC 1444) cc #32836 Notes: - The RFC doesn't talk about `#[packed]` unions, this implementation supports them, packing changes union's alignment to 1 and removes trailing padding. - The RFC doesn't talk about dynamically sized unions, this implementation doesn't support them and rejects them during wf-checking (similarly, dynamically sized enums are not supported as well). - The lint for drop fields in unions can't work precisely before monomorphization, so it works pessimistically - non-`Copy` generic fields are reported, types not implementing `Drop` directly, but having non-trivial drop code are reported. ``` struct S(String); // Doesn't implement `Drop` union U<T> { a: S, // Reported b: T, // Reported } ``` - #35764 was indeed helpful and landed timely, I didn't have to implement internal drop flags for unions. - Unions are not permitted in constant patterns, because matching on union fields is unsafe, I didn't want unsafety checker to dig into all constants to uncover this possible unsafety. - The RFC doesn't talk about `#[derive]`, generally trait impls cannot be derived for unions, but some of them can. I implemented only `#[derive(Copy)]` so far. In theory shallow `#[derive(Clone)]` can be derived as well if all union fields are `Copy`, I left it for later though, it requires changing how `Clone` impls are generated. - Moving union fields is implemented as per #32836 (comment). - Testing strategy: union specific behavior is tested, sometimes very basically (e.g. debuginfo), behavior common for all ADTs (e.g. something like coherence checks) is not generally tested. r? @eddyb
Awesome work, @petrochenkov! |
cc #32836
Notes:
The RFC doesn't talk about
#[packed]
unions, this implementation supports them, packing changes union's alignment to 1 and removes trailing padding.The RFC doesn't talk about dynamically sized unions, this implementation doesn't support them and rejects them during wf-checking (similarly, dynamically sized enums are not supported as well).
The lint for drop fields in unions can't work precisely before monomorphization, so it works pessimistically - non-
Copy
generic fields are reported, types not implementingDrop
directly, but having non-trivial drop code are reported.Remove the old AST-based backend from rustc_trans. #35764 was indeed helpful and landed timely, I didn't have to implement internal drop flags for unions.
Unions are not permitted in constant patterns, because matching on union fields is unsafe, I didn't want unsafety checker to dig into all constants to uncover this possible unsafety.
The RFC doesn't talk about
#[derive]
, generally trait impls cannot be derived for unions, but some of them can. I implemented only#[derive(Copy)]
so far. In theory shallow#[derive(Clone)]
can be derived as well if all union fields areCopy
, I left it for later though, it requires changing howClone
impls are generated.Moving union fields is implemented as per Untagged unions (tracking issue for RFC 1444) #32836 (comment).
Testing strategy: union specific behavior is tested, sometimes very basically (e.g. debuginfo), behavior common for all ADTs (e.g. something like coherence
checks) is not generally tested.
r? @eddyb