From 195c9f47e9d92ceb110fdcb31056fe9f6780171c Mon Sep 17 00:00:00 2001 From: Oliver Schneider Date: Thu, 19 Apr 2018 13:17:09 +0200 Subject: [PATCH] Allow variant discriminant initializers to refer to other initializers of the same enum --- src/librustc/ty/mod.rs | 40 ++++++++------ src/librustc_mir/hair/cx/expr.rs | 78 ++++++++++++++++++++++++++-- src/test/ui/const-eval/enum_discr.rs | 36 +++++++++++++ src/test/ui/issue-23302-1.stderr | 12 +++-- src/test/ui/issue-23302-2.stderr | 12 +++-- src/test/ui/issue-36163.stderr | 17 +++--- 6 files changed, 158 insertions(+), 37 deletions(-) create mode 100644 src/test/ui/const-eval/enum_discr.rs diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index c3d2d5675de05..2a10720f0749f 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -1972,32 +1972,38 @@ impl<'a, 'gcx, 'tcx> AdtDef { tcx: TyCtxt<'a, 'gcx, 'tcx>, variant_index: usize) -> Discr<'tcx> { - let repr_type = self.repr.discr_type(); - let mut explicit_value = repr_type.initial_discriminant(tcx.global_tcx()); + let (val, offset) = self.discriminant_def_for_variant(variant_index); + let explicit_value = val + .and_then(|expr_did| self.eval_explicit_discr(tcx, expr_did)) + .unwrap_or_else(|| self.repr.discr_type().initial_discriminant(tcx.global_tcx())); + explicit_value.checked_add(tcx, offset as u128).0 + } + + /// Yields a DefId for the discriminant and an offset to add to it + /// Alternatively, if there is no explicit discriminant, returns the + /// inferred discriminant directly + pub fn discriminant_def_for_variant( + &self, + variant_index: usize, + ) -> (Option, usize) { let mut explicit_index = variant_index; + let expr_did; loop { match self.variants[explicit_index].discr { - ty::VariantDiscr::Relative(0) => break, + ty::VariantDiscr::Relative(0) => { + expr_did = None; + break; + }, ty::VariantDiscr::Relative(distance) => { explicit_index -= distance; } - ty::VariantDiscr::Explicit(expr_did) => { - match self.eval_explicit_discr(tcx, expr_did) { - Some(discr) => { - explicit_value = discr; - break; - }, - None => { - if explicit_index == 0 { - break; - } - explicit_index -= 1; - } - } + ty::VariantDiscr::Explicit(did) => { + expr_did = Some(did); + break; } } } - explicit_value.checked_add(tcx, (variant_index - explicit_index) as u128).0 + (expr_did, variant_index - explicit_index) } pub fn destructor(&self, tcx: TyCtxt<'a, 'gcx, 'tcx>) -> Option { diff --git a/src/librustc_mir/hair/cx/expr.rs b/src/librustc_mir/hair/cx/expr.rs index c0d2828094695..d0c352319c8c5 100644 --- a/src/librustc_mir/hair/cx/expr.rs +++ b/src/librustc_mir/hair/cx/expr.rs @@ -589,12 +589,84 @@ fn make_mirror_unadjusted<'a, 'gcx, 'tcx>(cx: &mut Cx<'a, 'gcx, 'tcx>, // Check to see if this cast is a "coercion cast", where the cast is actually done // using a coercion (or is a no-op). if let Some(&TyCastKind::CoercionCast) = cx.tables() - .cast_kinds() - .get(source.hir_id) { + .cast_kinds() + .get(source.hir_id) { // Convert the lexpr to a vexpr. ExprKind::Use { source: source.to_ref() } } else { - ExprKind::Cast { source: source.to_ref() } + // check whether this is casting an enum variant discriminant + // to prevent cycles, we refer to the discriminant initializer + // which is always an integer and thus doesn't need to know the + // enum's layout (or its tag type) to compute it during const eval + // Example: + // enum Foo { + // A, + // B = A as isize + 4, + // } + // The correct solution would be to add symbolic computations to miri, + // so we wouldn't have to compute and store the actual value + let var = if let hir::ExprPath(ref qpath) = source.node { + let def = cx.tables().qpath_def(qpath, source.hir_id); + cx + .tables() + .node_id_to_type(source.hir_id) + .ty_adt_def() + .and_then(|adt_def| { + match def { + Def::VariantCtor(variant_id, CtorKind::Const) => { + let idx = adt_def.variant_index_with_id(variant_id); + let (d, o) = adt_def.discriminant_def_for_variant(idx); + use rustc::ty::util::IntTypeExt; + let ty = adt_def.repr.discr_type().to_ty(cx.tcx()); + Some((d, o, ty)) + } + _ => None, + } + }) + } else { + None + }; + let source = if let Some((did, offset, ty)) = var { + let mk_const = |val| Expr { + temp_lifetime, + ty, + span: expr.span, + kind: ExprKind::Literal { + literal: Literal::Value { + value: cx.tcx().mk_const(ty::Const { + val, + ty, + }), + }, + }, + }.to_ref(); + let offset = mk_const( + ConstVal::Value(Value::ByVal(PrimVal::Bytes(offset as u128))), + ); + match did { + Some(did) => { + // in case we are offsetting from a computed discriminant + // and not the beginning of discriminants (which is always `0`) + let substs = Substs::identity_for_item(cx.tcx(), did); + let lhs = mk_const(ConstVal::Unevaluated(did, substs)); + let bin = ExprKind::Binary { + op: BinOp::Add, + lhs, + rhs: offset, + }; + Expr { + temp_lifetime, + ty, + span: expr.span, + kind: bin, + }.to_ref() + }, + None => offset, + } + } else { + source.to_ref() + }; + ExprKind::Cast { source } } } hir::ExprType(ref source, _) => return source.make_mirror(cx), diff --git a/src/test/ui/const-eval/enum_discr.rs b/src/test/ui/const-eval/enum_discr.rs new file mode 100644 index 0000000000000..ba38a42092e00 --- /dev/null +++ b/src/test/ui/const-eval/enum_discr.rs @@ -0,0 +1,36 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-pass +// run-pass + +enum Foo { + X = 42, + Y = Foo::X as isize - 3, +} + +enum Bar { + X, + Y = Bar::X as isize + 2, +} + +enum Boo { + X = Boo::Y as isize * 2, + Y = 9, +} + +fn main() { + assert_eq!(Foo::X as isize, 42); + assert_eq!(Foo::Y as isize, 39); + assert_eq!(Bar::X as isize, 0); + assert_eq!(Bar::Y as isize, 2); + assert_eq!(Boo::X as isize, 18); + assert_eq!(Boo::Y as isize, 9); +} diff --git a/src/test/ui/issue-23302-1.stderr b/src/test/ui/issue-23302-1.stderr index 0fbe2f7a41177..6ff46a7e21fda 100644 --- a/src/test/ui/issue-23302-1.stderr +++ b/src/test/ui/issue-23302-1.stderr @@ -1,11 +1,15 @@ -error[E0391]: cycle detected when const-evaluating `X::A::{{initializer}}` +error[E0391]: cycle detected when processing `X::A::{{initializer}}` --> $DIR/issue-23302-1.rs:14:9 | LL | A = X::A as isize, //~ ERROR E0391 - | ^^^^ + | ^^^^^^^^^^^^^ | -note: ...which requires computing layout of `X`... - = note: ...which again requires const-evaluating `X::A::{{initializer}}`, completing the cycle + = note: ...which again requires processing `X::A::{{initializer}}`, completing the cycle +note: cycle used when const-evaluating `X::A::{{initializer}}` + --> $DIR/issue-23302-1.rs:14:9 + | +LL | A = X::A as isize, //~ ERROR E0391 + | ^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/issue-23302-2.stderr b/src/test/ui/issue-23302-2.stderr index 313cfa0c16260..6b72a5b544dce 100644 --- a/src/test/ui/issue-23302-2.stderr +++ b/src/test/ui/issue-23302-2.stderr @@ -1,11 +1,15 @@ -error[E0391]: cycle detected when const-evaluating `Y::A::{{initializer}}` +error[E0391]: cycle detected when processing `Y::A::{{initializer}}` --> $DIR/issue-23302-2.rs:14:9 | LL | A = Y::B as isize, //~ ERROR E0391 - | ^^^^ + | ^^^^^^^^^^^^^ | -note: ...which requires computing layout of `Y`... - = note: ...which again requires const-evaluating `Y::A::{{initializer}}`, completing the cycle + = note: ...which again requires processing `Y::A::{{initializer}}`, completing the cycle +note: cycle used when const-evaluating `Y::A::{{initializer}}` + --> $DIR/issue-23302-2.rs:14:9 + | +LL | A = Y::B as isize, //~ ERROR E0391 + | ^^^^^^^^^^^^^ error: aborting due to previous error diff --git a/src/test/ui/issue-36163.stderr b/src/test/ui/issue-36163.stderr index 541f54ca76891..7199fffd386b4 100644 --- a/src/test/ui/issue-36163.stderr +++ b/src/test/ui/issue-36163.stderr @@ -1,21 +1,20 @@ -error[E0391]: cycle detected when const-evaluating `Foo::B::{{initializer}}` +error[E0391]: cycle detected when processing `Foo::B::{{initializer}}` --> $DIR/issue-36163.rs:14:9 | LL | B = A, //~ ERROR E0391 | ^ | -note: ...which requires processing `Foo::B::{{initializer}}`... +note: ...which requires processing `A`... + --> $DIR/issue-36163.rs:11:18 + | +LL | const A: isize = Foo::B as isize; + | ^^^^^^^^^^^^^^^ + = note: ...which again requires processing `Foo::B::{{initializer}}`, completing the cycle +note: cycle used when const-evaluating `Foo::B::{{initializer}}` --> $DIR/issue-36163.rs:14:9 | LL | B = A, //~ ERROR E0391 | ^ -note: ...which requires const-evaluating `A`... - --> $DIR/issue-36163.rs:11:18 - | -LL | const A: isize = Foo::B as isize; - | ^^^^^^ -note: ...which requires computing layout of `Foo`... - = note: ...which again requires const-evaluating `Foo::B::{{initializer}}`, completing the cycle error: aborting due to previous error