-
Notifications
You must be signed in to change notification settings - Fork 13k
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
enum variant support #89745
enum variant support #89745
Conversation
Some changes occured to the CTFE / Miri engine cc @rust-lang/miri |
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @michaelwoerister (or someone else) soon. Please see the contribution instructions for more information. |
a5f1cef
to
43f9467
Compare
This comment has been minimized.
This comment has been minimized.
r? @oli-obk |
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.
First of all, this is amazing. I thought an MVP would be significantly more involved, but you even got surface syntax working from the start.
I left a lot of comments, I do realize this is a draft and as a first iteration the copy paste is expected to get things running quickly. Please let me know how nit-picky you want the review to be. I'm also available on zulip for chatting if you prefer, just open a thread in the compiler team stream.
One general thing is that I think we should also change TyLayout::for_variant
to return a ty::Variant
in the type field instead of just returning the original type.
ty::Variant(ty, _) => match ty.kind() { | ||
ty::Adt(adt_def, _substs) if adt_def.is_enum() => { | ||
if index.as_usize() >= adt_def.variants.len() { | ||
PlaceTy::from_ty(span_mirbug_and_err!( | ||
self, | ||
place, | ||
"cast to variant #{:?} but enum only has {:?}", | ||
index, | ||
adt_def.variants.len() | ||
)) | ||
} else { | ||
PlaceTy { ty: *ty, variant_index: Some(index) } | ||
} | ||
} | ||
_ => bug!("unexpected type: {:?}", ty.kind()), | ||
}, |
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.
is downcasting a variant type an operation we would ever encounter?
I think for now this arm can just mir-bug if index != var_index
where var_index
is the second field of ty::Variant
.
that would allow re-downcasting to the same variant, but nothing else
@@ -744,6 +744,22 @@ impl<'a, 'b, 'tcx> TypeVerifier<'a, 'b, 'tcx> { | |||
PlaceTy { ty: base_ty, variant_index: Some(index) } |
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.
Ideally we'd remove the variant_index
field and replace ty
here with the variant type
PlaceTy { ty, variant_index: Some(variant_index) } => match *ty.kind() { | ||
ty::Adt(adt_def, substs) => (&adt_def.variants[variant_index], substs), | ||
ty::Variant(ty, _) => match ty.kind() { | ||
ty::Adt(adt_def, substs) => { | ||
(adt_def.variants.get(variant_index).expect(""), *substs) | ||
} | ||
_ => bug!("unexpected type: {:?}", ty.kind()), | ||
}, |
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.
if we get rid of variant_index
as mentioned earlier, then we can grab the variant index from the ty::Variant
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.
If we remove variant_index
how would we handle the Adt match arm above?
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.
If the above comment is implemented, then we should never encounter that arm, it should always be ty::Variant if we would have had a variant index in the PlaceTy
ty::Variant(ty, _) => match ty.kind() { | ||
ty::Adt(adt, _) => adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits), | ||
_ => bug!("unexpected type: {:?}", ty.kind()), | ||
} |
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.
ty::Variant(ty, _) => match ty.kind() { | |
ty::Adt(adt, _) => adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits), | |
_ => bug!("unexpected type: {:?}", ty.kind()), | |
} | |
ty::Variant(_ty, idx) => idx, |
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 should return (VariantIdx, Discr) not just the index so this wouldn't work?
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.
ah, yea, that makes sense. we should probably still assert somewhere that the index matches the expected one here
@@ -219,6 +219,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' | |||
if tag_field == field { | |||
return match layout.ty.kind() { | |||
ty::Adt(def, ..) if def.is_enum() => PathElem::EnumTag, | |||
ty::Variant(ty, ..) => match ty.kind() { |
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 any of the changes in this function, we could do a
if let ty::Variant(ty, ..) = *layout.ty.kind() {
return self.aggregate_field_path_elem(self.ecx.layout_of(ty), field),
}
at the start
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 is in a Variants::Multiple
match arm; we should never see ty::Variant
here I think.
@@ -308,7 +373,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |||
self.check_expr_closure(expr, capture, &decl, body_id, gen, expected) | |||
} | |||
ExprKind::Block(body, _) => self.check_block_with_expected(&body, expected), | |||
ExprKind::Call(callee, args) => self.check_call(expr, &callee, args, expected), |
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.
Same here, we can probably make the type of tuple variant constructors be fn(args) -> Enum::Variant
instead of fn(args) -> Enum
@@ -1685,6 +1764,45 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | |||
while let Some((base_t, _)) = autoderef.next() { | |||
debug!("base_t: {:?}", base_t); | |||
match base_t.kind() { | |||
ty::Variant(ty, idx) => match ty.kind() { |
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 think as a first prototype we could start without this, and require let Foo::Bar { x, y } = ...
in order to get the fields out
= note: expected enum variant `Foo::Variant1` | ||
found enum variant `Foo::Variant2` |
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.
nice!
} | ||
|
||
fn main() { | ||
let f: Foo::Variant1 = Foo::Variant1(3, "test".to_string()); |
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.
will this work without f having a type specified?
} | ||
|
||
fn main() { | ||
let f: Foo::Variant2 = Foo::Variant2(9); |
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.
Will the following also work with the current impl?
let y = Foo::Variant2(9);
foo(y);
fn foo(z: Foo::Variant2) {}
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 yet, currently everything must have the type explicitly set.
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.
Ah, ok. so that's coupled with the ExpectedTy
situation I commented on earlier in this PR.
If you want you can also commit failing tests like this and others you come up with, then we can see the test changes as you touch the logic
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.
Oli beat me to the review here, but I'm just as excited :)
Just one comment for now!
@@ -195,6 +195,9 @@ pub enum TyKind<'tcx> { | |||
/// A type variable used during type checking. | |||
Infer(InferTy), | |||
|
|||
/// An enum (TyKind::Adt) and its variant | |||
Variant(Ty<'tcx>, VariantIdx), |
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.
So, is the Ty
here ever going to be anything other than an Adt
? If not, maybe we should just have (&'tcx AdtDef, SubstsRef<'tcx>, VariantIdx)
?
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 personally think it should some day allow more than just enums. E.g. if we allowed multiple variant indices at some point, we could also think about ranged integers. As long as there are some assertions right now limiting it to adts, that's fine with me, we don't need to check everywhere though. I mean, not all Adts make sense here either, so it's not like limiting to Adts will get us freedom from misuse by construction. E.g. structs don't really make sense inside a ty::Variant right now.
And we should def re-use it for generators, if just for keeping the special cases and repetition down.
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.
Using Adt could cut down a lot of the match
es that need to get the underlying AdtDef for the enum this is a variant of.
I wouldn't complicate the code now for extremely hypothetical extensions such as ranged integers.
@@ -272,6 +276,20 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' | |||
} | |||
} | |||
|
|||
ty::Variant(ty, ..) => match ty.kind() { | |||
ty::Adt(def, ..) if def.is_enum() => { |
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 seems to be a very common pattern to get the adtdef of the enum for a ty::Variant
; should maybe ty_adt_def
have support for ty::Variant
so it could be used instead? That would also avoid all the rightwards drift.
@@ -567,6 +585,17 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' | |||
self.check_safe_pointer(value, "box")?; | |||
Ok(true) | |||
} | |||
ty::Variant(ty, _) => match ty.kind() { | |||
ty::Adt(def, _) => { | |||
if def.is_box() { |
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.
Box is not an enum, this should be impossible. I think this can be handled together with the Adt
fallback case below.
@@ -729,6 +758,10 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> | |||
) -> InterpResult<'tcx> { | |||
let name = match old_op.layout.ty.kind() { | |||
ty::Adt(adt, _) => PathElem::Variant(adt.variants[variant_id].ident.name), | |||
ty::Variant(ty, ..) => match ty.kind() { |
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.
Is this reachable? We should only reach this function for types with a multi-variant layout, which ty::Variant
is not.
This comment has been minimized.
This comment has been minimized.
14a70a9
to
ccf569f
Compare
The job Click to see the possible cause of the failure (guessed by this bot)
|
@@ -756,15 +729,12 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M> | |||
variant_id: VariantIdx, | |||
new_op: &OpTy<'tcx, M::PointerTag>, | |||
) -> InterpResult<'tcx> { | |||
let name = match old_op.layout.ty.kind() { | |||
let ty = old_op.layout.ty.strip_variant_type(); |
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.
Variant types don't have variants, so something seems wrong if we see a variant type here.
@@ -217,12 +217,8 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, ' | |||
match layout.variants { | |||
Variants::Multiple { tag_field, .. } => { | |||
if tag_field == field { | |||
return match layout.ty.kind() { | |||
return match layout.ty.strip_variant_type().kind() { |
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.
Variant types should never have Variants::Multiple
layout so this should be unreachable.
Generally I am not very happy with strip_variant_type
, from a conceptual perspective this rarely seems like the right thing to do -- but maybe my intuition is leading me astray here.
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.
strip_variant_type is the correct thing wherever we don't actually handle variant things on generators or adts right now, so here it is indeed wrong, we should not ever encounter adt/generator anymore, but for that Layout::for_variant
needs to be fixed to return ty::Variant.
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.
layout
is the outer layout of the enum here, why should we not encounter adt/generators here?
It's the other match
below in the same function where we should not encounter them any more.
So... a general point about this PR. It's a huge amount of work to implement this all at once. In order to get this mergeable (and reviewable) in small steps, how about the following plan:
|
Thanks for taking the time to lay out the next steps, and looking over this PR. I'd be more than happy to start on 1, but it does seem like this is skipping a few steps that are outlined here. |
1 and 2 are invisible to users. They are essentially cleanups of the compiler's logic. For 3 we'd at least need an accepted MCP, from the zulip discussion it sounded like there was nothing speaking against it, so maybe just asking for a "second" to be able to experiment is all that is needed at this stage? |
Would zulip be the best place to discuss this going forward? |
Yea a zulip thread in the T-compiler stream is probably the best place to discuss this |
I'll start a thread and tag you in it, whats your zulip name? |
|
☔ The latest upstream changes (presumably #89939) made this pull request unmergeable. Please resolve the merge conflicts. |
Proof of concept for enum variant types.