Allow variant discriminant initializers to refer to other initializers of the same enum

This commit is contained in:
Oliver Schneider 2018-04-19 13:17:09 +02:00
parent 883bf4ba2e
commit 195c9f47e9
No known key found for this signature in database
GPG Key ID: A69F8D225B3AD7D9
6 changed files with 159 additions and 38 deletions

View File

@ -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<DefId>, 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<Destructor> {

View File

@ -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),

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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);
}

View File

@ -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

View File

@ -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

View File

@ -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}}`...
--> $DIR/issue-36163.rs:14:9
|
LL | B = A, //~ ERROR E0391
| ^
note: ...which requires const-evaluating `A`...
note: ...which requires processing `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
| ^^^^^^^^^^^^^^^
= 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
| ^
error: aborting due to previous error