Skip to content

Commit

Permalink
Auto merge of #24270 - pnkfelix:use-disr-val-for-derive-ord, r=brson
Browse files Browse the repository at this point in the history
Use `discriminant_value` intrinsic for `derive(PartialOrd)`

[breaking-change]

This is a [breaking-change] because it can change the result of comparison operators when enum discriminants have been explicitly assigned.  Notably in a case like:
```rust
#[derive(PartialOrd)]
enum E { A = 2, B = 1}
```

Under the old deriving, `A < B` held, because `A` came before `B` in the order of declaration.  But now we use the ordering according to the provided values, and thus `A > B`.  (However, this change is very unlikely to break much, if any, code, since the orderings themselves should all remain well-defined, total, etc.)

Fix #15523
  • Loading branch information
bors committed Apr 10, 2015
2 parents c897ac0 + 847a897 commit 0877916
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 29 deletions.
6 changes: 6 additions & 0 deletions src/libcore/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,4 +569,10 @@ extern "rust-intrinsic" {
pub fn overflowing_sub<T>(a: T, b: T) -> T;
/// Returns (a * b) mod 2^N, where N is the width of N in bits.
pub fn overflowing_mul<T>(a: T, b: T) -> T;

/// Returns the value of the discriminant for the variant in 'v',
/// cast to a `u64`; if `T` has no discriminant, returns 0.
// SNAP 5520801
#[cfg(not(stage0))]
pub fn discriminant_value<T>(v: &T) -> u64;
}
1 change: 1 addition & 0 deletions src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ mod tuple;

#[doc(hidden)]
mod core {
pub use intrinsics;
pub use panicking;
pub use fmt;
pub use clone;
Expand Down
12 changes: 12 additions & 0 deletions src/librustc_trans/trans/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use llvm;
use llvm::{SequentiallyConsistent, Acquire, Release, AtomicXchg, ValueRef, TypeKind};
use middle::subst;
use middle::subst::FnSpace;
use trans::adt;
use trans::base::*;
use trans::build::*;
use trans::callee;
Expand Down Expand Up @@ -683,6 +684,17 @@ pub fn trans_intrinsic_call<'a, 'blk, 'tcx>(mut bcx: Block<'blk, 'tcx>,
}
}

(_, "discriminant_value") => {
let val_ty = substs.types.get(FnSpace, 0);
match val_ty.sty {
ty::ty_enum(..) => {
let repr = adt::represent_type(ccx, *val_ty);
adt::trans_get_discr(bcx, &*repr, llargs[0], Some(llret_ty))
}
_ => C_null(llret_ty)
}
}

// This requires that atomic intrinsics follow a specific naming pattern:
// "atomic_<operation>[_<ordering>]", and no ordering means SeqCst
(_, name) if name.starts_with("atomic_") => {
Expand Down
6 changes: 6 additions & 0 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5073,6 +5073,12 @@ pub fn check_intrinsic_type(ccx: &CrateCtxt, it: &ast::ForeignItem) {

"assume" => (0, vec![tcx.types.bool], ty::mk_nil(tcx)),

"discriminant_value" => (1, vec![
ty::mk_imm_rptr(tcx,
tcx.mk_region(ty::ReLateBound(ty::DebruijnIndex::new(1),
ty::BrAnon(0))),
param(ccx, 0))], tcx.types.u64),

ref other => {
span_err!(tcx.sess, it.span, E0093,
"unrecognized intrinsic function: `{}`", *other);
Expand Down
92 changes: 63 additions & 29 deletions src/libsyntax/ext/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ use ext::base::ExtCtxt;
use ext::build::AstBuilder;
use codemap::{self, DUMMY_SP};
use codemap::Span;
use diagnostic::SpanHandler;
use fold::MoveMap;
use owned_slice::OwnedSlice;
use parse::token::InternedString;
Expand Down Expand Up @@ -391,6 +392,7 @@ impl<'a> TraitDef<'a> {
ast::ItemEnum(ref enum_def, ref generics) => {
self.expand_enum_def(cx,
enum_def,
&item.attrs[..],
item.ident,
generics)
}
Expand Down Expand Up @@ -653,6 +655,7 @@ impl<'a> TraitDef<'a> {
fn expand_enum_def(&self,
cx: &mut ExtCtxt,
enum_def: &EnumDef,
type_attrs: &[ast::Attribute],
type_ident: Ident,
generics: &Generics) -> P<ast::Item> {
let mut field_tys = Vec::new();
Expand Down Expand Up @@ -687,6 +690,7 @@ impl<'a> TraitDef<'a> {
method_def.expand_enum_method_body(cx,
self,
enum_def,
type_attrs,
type_ident,
self_args,
&nonself_args[..])
Expand All @@ -706,13 +710,30 @@ impl<'a> TraitDef<'a> {
}
}

fn variant_to_pat(cx: &mut ExtCtxt, sp: Span, enum_ident: ast::Ident, variant: &ast::Variant)
-> P<ast::Pat> {
let path = cx.path(sp, vec![enum_ident, variant.node.name]);
cx.pat(sp, match variant.node.kind {
ast::TupleVariantKind(..) => ast::PatEnum(path, None),
ast::StructVariantKind(..) => ast::PatStruct(path, Vec::new(), true),
})
fn find_repr_type_name(diagnostic: &SpanHandler,
type_attrs: &[ast::Attribute]) -> &'static str {
let mut repr_type_name = "i32";
for a in type_attrs {
for r in &attr::find_repr_attrs(diagnostic, a) {
repr_type_name = match *r {
attr::ReprAny | attr::ReprPacked => continue,
attr::ReprExtern => "i32",

attr::ReprInt(_, attr::SignedInt(ast::TyIs)) => "isize",
attr::ReprInt(_, attr::SignedInt(ast::TyI8)) => "i8",
attr::ReprInt(_, attr::SignedInt(ast::TyI16)) => "i16",
attr::ReprInt(_, attr::SignedInt(ast::TyI32)) => "i32",
attr::ReprInt(_, attr::SignedInt(ast::TyI64)) => "i64",

attr::ReprInt(_, attr::UnsignedInt(ast::TyUs)) => "usize",
attr::ReprInt(_, attr::UnsignedInt(ast::TyU8)) => "u8",
attr::ReprInt(_, attr::UnsignedInt(ast::TyU16)) => "u16",
attr::ReprInt(_, attr::UnsignedInt(ast::TyU32)) => "u32",
attr::ReprInt(_, attr::UnsignedInt(ast::TyU64)) => "u64",
}
}
}
repr_type_name
}

impl<'a> MethodDef<'a> {
Expand Down Expand Up @@ -983,12 +1004,13 @@ impl<'a> MethodDef<'a> {
cx: &mut ExtCtxt,
trait_: &TraitDef,
enum_def: &EnumDef,
type_attrs: &[ast::Attribute],
type_ident: Ident,
self_args: Vec<P<Expr>>,
nonself_args: &[P<Expr>])
-> P<Expr> {
self.build_enum_match_tuple(
cx, trait_, enum_def, type_ident, self_args, nonself_args)
cx, trait_, enum_def, type_attrs, type_ident, self_args, nonself_args)
}


Expand Down Expand Up @@ -1022,6 +1044,7 @@ impl<'a> MethodDef<'a> {
cx: &mut ExtCtxt,
trait_: &TraitDef,
enum_def: &EnumDef,
type_attrs: &[ast::Attribute],
type_ident: Ident,
self_args: Vec<P<Expr>>,
nonself_args: &[P<Expr>]) -> P<Expr> {
Expand All @@ -1044,8 +1067,8 @@ impl<'a> MethodDef<'a> {
.collect::<Vec<ast::Ident>>();

// The `vi_idents` will be bound, solely in the catch-all, to
// a series of let statements mapping each self_arg to a usize
// corresponding to its variant index.
// a series of let statements mapping each self_arg to an int
// value corresponding to its discriminant.
let vi_idents: Vec<ast::Ident> = self_arg_names.iter()
.map(|name| { let vi_suffix = format!("{}_vi", &name[..]);
cx.ident_of(&vi_suffix[..]) })
Expand Down Expand Up @@ -1160,33 +1183,44 @@ impl<'a> MethodDef<'a> {
// unreachable-pattern error.
//
if variants.len() > 1 && self_args.len() > 1 {
let arms: Vec<ast::Arm> = variants.iter().enumerate()
.map(|(index, variant)| {
let pat = variant_to_pat(cx, sp, type_ident, &**variant);
let lit = ast::LitInt(index as u64, ast::UnsignedIntLit(ast::TyUs));
cx.arm(sp, vec![pat], cx.expr_lit(sp, lit))
}).collect();

// Build a series of let statements mapping each self_arg
// to a usize corresponding to its variant index.
// to its discriminant value. If this is a C-style enum
// with a specific repr type, then casts the values to
// that type. Otherwise casts to `i32` (the default repr
// type).
//
// i.e. for `enum E<T> { A, B(1), C(T, T) }`, and a deriving
// with three Self args, builds three statements:
//
// ```
// let __self0_vi = match self {
// A => 0, B(..) => 1, C(..) => 2
// };
// let __self1_vi = match __arg1 {
// A => 0, B(..) => 1, C(..) => 2
// };
// let __self2_vi = match __arg2 {
// A => 0, B(..) => 1, C(..) => 2
// };
// let __self0_vi = unsafe {
// std::intrinsics::discriminant_value(&self) } as i32;
// let __self1_vi = unsafe {
// std::intrinsics::discriminant_value(&__arg1) } as i32;
// let __self2_vi = unsafe {
// std::intrinsics::discriminant_value(&__arg2) } as i32;
// ```
let mut index_let_stmts: Vec<P<ast::Stmt>> = Vec::new();

let target_type_name =
find_repr_type_name(&cx.parse_sess.span_diagnostic, type_attrs);

for (&ident, self_arg) in vi_idents.iter().zip(self_args.iter()) {
let variant_idx = cx.expr_match(sp, self_arg.clone(), arms.clone());
let let_stmt = cx.stmt_let(sp, false, ident, variant_idx);
let path = vec![cx.ident_of_std("core"),
cx.ident_of("intrinsics"),
cx.ident_of("discriminant_value")];
let call = cx.expr_call_global(
sp, path, vec![cx.expr_addr_of(sp, self_arg.clone())]);
let variant_value = cx.expr_block(P(ast::Block {
stmts: vec![],
expr: Some(call),
id: ast::DUMMY_NODE_ID,
rules: ast::UnsafeBlock(ast::CompilerGenerated),
span: sp }));

let target_ty = cx.ty_ident(sp, cx.ident_of(target_type_name));
let variant_disr = cx.expr_cast(sp, variant_value, target_ty);
let let_stmt = cx.stmt_let(sp, false, ident, variant_disr);
index_let_stmts.push(let_stmt);
}

Expand Down
77 changes: 77 additions & 0 deletions src/test/run-pass/discriminant_value.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2014 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.

#![feature(core)]

extern crate core;
use core::intrinsics::discriminant_value;

enum CLike1 {
A,
B,
C,
D
}

enum CLike2 {
A = 5,
B = 2,
C = 19,
D
}

#[repr(i8)]
enum CLike3 {
A = 5,
B,
C = -1,
D
}

enum ADT {
First(u32, u32),
Second(u64)
}

enum NullablePointer {
Something(&'static u32),
Nothing
}

static CONST : u32 = 0xBEEF;

pub fn main() {
unsafe {

assert_eq!(discriminant_value(&CLike1::A), 0);
assert_eq!(discriminant_value(&CLike1::B), 1);
assert_eq!(discriminant_value(&CLike1::C), 2);
assert_eq!(discriminant_value(&CLike1::D), 3);

assert_eq!(discriminant_value(&CLike2::A), 5);
assert_eq!(discriminant_value(&CLike2::B), 2);
assert_eq!(discriminant_value(&CLike2::C), 19);
assert_eq!(discriminant_value(&CLike2::D), 20);

assert_eq!(discriminant_value(&CLike3::A), 5);
assert_eq!(discriminant_value(&CLike3::B), 6);
assert_eq!(discriminant_value(&CLike3::C), -1_i8 as u64);
assert_eq!(discriminant_value(&CLike3::D), 0);

assert_eq!(discriminant_value(&ADT::First(0,0)), 0);
assert_eq!(discriminant_value(&ADT::Second(5)), 1);

assert_eq!(discriminant_value(&NullablePointer::Nothing), 1);
assert_eq!(discriminant_value(&NullablePointer::Something(&CONST)), 0);

assert_eq!(discriminant_value(&10), 0);
assert_eq!(discriminant_value(&"test"), 0);
}
}
48 changes: 48 additions & 0 deletions src/test/run-pass/issue-15523-big.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2015 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.

// Issue 15523: derive(PartialOrd) should use the provided
// discriminant values for the derived ordering.
//
// This test is checking corner cases that arise when you have
// 64-bit values in the variants.

#[derive(PartialEq, PartialOrd)]
#[repr(u64)]
enum Eu64 {
Pos2 = 2,
PosMax = !0,
Pos1 = 1,
}

#[derive(PartialEq, PartialOrd)]
#[repr(i64)]
enum Ei64 {
Pos2 = 2,
Neg1 = -1,
NegMin = 1 << 63,
PosMax = !(1 << 63),
Pos1 = 1,
}

fn main() {
assert!(Eu64::Pos2 > Eu64::Pos1);
assert!(Eu64::Pos2 < Eu64::PosMax);
assert!(Eu64::Pos1 < Eu64::PosMax);


assert!(Ei64::Pos2 > Ei64::Pos1);
assert!(Ei64::Pos2 > Ei64::Neg1);
assert!(Ei64::Pos1 > Ei64::Neg1);
assert!(Ei64::Pos2 > Ei64::NegMin);
assert!(Ei64::Pos1 > Ei64::NegMin);
assert!(Ei64::Pos2 < Ei64::PosMax);
assert!(Ei64::Pos1 < Ei64::PosMax);
}
Loading

0 comments on commit 0877916

Please sign in to comment.