Skip to content
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

Closed
wants to merge 2 commits into from
Closed

Conversation

zhamlin
Copy link

@zhamlin zhamlin commented Oct 10, 2021

Proof of concept for enum variant types.

@rust-highfive
Copy link
Collaborator

Some changes occured to the CTFE / Miri engine

cc @rust-lang/miri

@rust-highfive
Copy link
Collaborator

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.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 10, 2021
@rust-log-analyzer

This comment has been minimized.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 11, 2021

r? @oli-obk

Copy link
Contributor

@oli-obk oli-obk left a 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.

Comment on lines +747 to +762
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()),
},
Copy link
Contributor

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) }
Copy link
Contributor

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

Comment on lines 829 to +836
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()),
},
Copy link
Contributor

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

Copy link
Author

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?

Copy link
Contributor

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

Comment on lines 690 to 693
ty::Variant(ty, _) => match ty.kind() {
ty::Adt(adt, _) => adt.discriminants(*self.tcx).find(|(_, var)| var.val == discr_bits),
_ => bug!("unexpected type: {:?}", ty.kind()),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,

Copy link
Author

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?

Copy link
Contributor

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() {
Copy link
Contributor

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

Copy link
Member

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),
Copy link
Contributor

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() {
Copy link
Contributor

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

Comment on lines +7 to +8
= note: expected enum variant `Foo::Variant1`
found enum variant `Foo::Variant2`
Copy link
Contributor

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());
Copy link
Contributor

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);
Copy link
Contributor

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) {}

Copy link
Author

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.

Copy link
Contributor

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

Copy link
Member

@jackh726 jackh726 left a 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),
Copy link
Member

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)?

Copy link
Contributor

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.

Copy link
Member

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 matches 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() => {
Copy link
Member

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() {
Copy link
Member

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() {
Copy link
Member

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.

@rust-log-analyzer

This comment has been minimized.

@zhamlin zhamlin force-pushed the enum-variant-types branch from 14a70a9 to ccf569f Compare October 12, 2021 14:03
@rust-log-analyzer
Copy link
Collaborator

The job mingw-check failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
    Checking tracing-tree v0.1.9
    Checking rustdoc-json-types v0.1.0 (/checkout/src/rustdoc-json-types)
    Checking tera v1.10.0
    Checking rustdoc v0.0.0 (/checkout/src/librustdoc)
error[E0004]: non-exhaustive patterns: `Variant(_, _)` not covered
    --> src/librustdoc/clean/mod.rs:1398:15
     |
1398 |         match *ty.kind() {
     |               ^^^^^^^^^^ pattern `Variant(_, _)` not covered
    ::: /checkout/compiler/rustc_middle/src/ty/sty.rs:199:5
     |
     |
199  |     Variant(Ty<'tcx>, VariantIdx),
     |
     = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
     = note: the matched value is of type `rustc_middle::ty::TyKind`


error[E0004]: non-exhaustive patterns: `Variant(_, _)` not covered
    |
    |
539 |         Some(match *self.cx.tcx.type_of(ty_id).kind() {
    |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pattern `Variant(_, _)` not covered
   ::: /checkout/compiler/rustc_middle/src/ty/sty.rs:199:5
    |
    |
199 |     Variant(Ty<'tcx>, VariantIdx),
    |
    = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
    = note: the matched value is of type `rustc_middle::ty::TyKind`

@@ -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();
Copy link
Member

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() {
Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Member

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.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 12, 2021

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:

  1. a new PR that avoids any user visible changes in the first round, and just has the PlaceTy::variant_index changes. This by itself should be fairly self-contained, as you can likely only touch some PlaceTy users (codegen, miri) and avoid going all in in the compiler
  2. a second PR makes Layout::for_variant return ty::Variant and fix up the (presumably larger) fallout that occurs in miri and codegen
  3. add user visible effects behind a feature gate with some simple tests, get that merged
  4. more PRs follow that flesh out the feature with more tests (which are likely obtainable by temporarily deactivating the feature gate and seeing how libcore explodes).

@zhamlin
Copy link
Author

zhamlin commented Oct 13, 2021

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.
I don't want to put much too time into this without making sure this is something needed/wanted.

@oli-obk
Copy link
Contributor

oli-obk commented Oct 13, 2021

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?

@zhamlin
Copy link
Author

zhamlin commented Oct 13, 2021

Would zulip be the best place to discuss this going forward?
I have some questions before I can get started on 1

@oli-obk
Copy link
Contributor

oli-obk commented Oct 13, 2021

Yea a zulip thread in the T-compiler stream is probably the best place to discuss this

@zhamlin
Copy link
Author

zhamlin commented Oct 13, 2021

I'll start a thread and tag you in it, whats your zulip name?

@oli-obk
Copy link
Contributor

oli-obk commented Oct 14, 2021

@oli

@bors
Copy link
Contributor

bors commented Oct 16, 2021

☔ The latest upstream changes (presumably #89939) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants