Skip to content

Commit

Permalink
Improve Eq deriving
Browse files Browse the repository at this point in the history
  • Loading branch information
petrochenkov committed Sep 10, 2016
1 parent 2a2c9d3 commit 62cb751
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 36 deletions.
13 changes: 12 additions & 1 deletion src/libcore/cmp.rs
Expand Up @@ -129,7 +129,7 @@ pub trait PartialEq<Rhs: ?Sized = Self> {
/// This trait can be used with `#[derive]`. When `derive`d, because `Eq` has
/// no extra methods, it is only informing the compiler that this is an
/// equivalence relation rather than a partial equivalence relation. Note that
/// the `derive` strategy requires all fields are `PartialEq`, which isn't
/// the `derive` strategy requires all fields are `Eq`, which isn't
/// always desired.
///
/// ## How can I implement `Eq`?
Expand Down Expand Up @@ -165,6 +165,17 @@ pub trait Eq: PartialEq<Self> {
fn assert_receiver_is_total_eq(&self) {}
}

// FIXME: this struct is used solely by #[derive] to
// assert that every component of a type implements Eq.
//
// This struct should never appear in user code.
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[unstable(feature = "derive_eq",
reason = "deriving hack, should not be public",
issue = "0")]
pub struct AssertParamIsEq<T: Eq + ?Sized> { _field: ::marker::PhantomData<T> }

/// An `Ordering` is the result of a comparison between two values.
///
/// # Examples
Expand Down
18 changes: 18 additions & 0 deletions src/libsyntax/ext/build.rs
Expand Up @@ -97,6 +97,7 @@ pub trait AstBuilder {
typ: P<ast::Ty>,
ex: P<ast::Expr>)
-> P<ast::Stmt>;
fn stmt_let_type_only(&self, span: Span, ty: P<ast::Ty>) -> ast::Stmt;
fn stmt_item(&self, sp: Span, item: P<ast::Item>) -> ast::Stmt;

// blocks
Expand Down Expand Up @@ -577,6 +578,23 @@ impl<'a> AstBuilder for ExtCtxt<'a> {
})
}

// Generate `let _: Type;`, usually used for type assertions.
fn stmt_let_type_only(&self, span: Span, ty: P<ast::Ty>) -> ast::Stmt {
let local = P(ast::Local {
pat: self.pat_wild(span),
ty: Some(ty),
init: None,
id: ast::DUMMY_NODE_ID,
span: span,
attrs: ast::ThinVec::new(),
});
ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Local(local),
span: span,
}
}

fn stmt_item(&self, sp: Span, item: P<ast::Item>) -> ast::Stmt {
ast::Stmt {
id: ast::DUMMY_NODE_ID,
Expand Down
15 changes: 1 addition & 14 deletions src/libsyntax_ext/deriving/clone.rs
Expand Up @@ -115,20 +115,7 @@ fn cs_clone_shallow(name: &str,
let assert_path = cx.path_all(span, true,
cx.std_path(&["clone", helper_name]),
vec![], vec![ty], vec![]);
let local = P(ast::Local {
pat: cx.pat_wild(span),
ty: Some(cx.ty_path(assert_path)),
init: None,
id: ast::DUMMY_NODE_ID,
span: span,
attrs: ast::ThinVec::new(),
});
let stmt = ast::Stmt {
id: ast::DUMMY_NODE_ID,
node: ast::StmtKind::Local(local),
span: span,
};
stmts.push(stmt);
stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
}
fn process_variant(cx: &mut ExtCtxt, stmts: &mut Vec<ast::Stmt>, variant: &VariantData) {
for field in variant.fields() {
Expand Down
55 changes: 36 additions & 19 deletions src/libsyntax_ext/deriving/cmp/eq.rs
Expand Up @@ -11,7 +11,7 @@
use deriving::generic::*;
use deriving::generic::ty::*;

use syntax::ast::{Expr, MetaItem};
use syntax::ast::{self, Expr, MetaItem};
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::parse::token::InternedString;
Expand All @@ -23,22 +23,6 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt,
mitem: &MetaItem,
item: &Annotatable,
push: &mut FnMut(Annotatable)) {
fn cs_total_eq_assert(cx: &mut ExtCtxt, span: Span, substr: &Substructure) -> P<Expr> {
cs_same_method(|cx, span, exprs| {
// create `a.<method>(); b.<method>(); c.<method>(); ...`
// (where method is `assert_receiver_is_total_eq`)
let stmts = exprs.into_iter().map(|e| cx.stmt_expr(e)).collect();
let block = cx.block(span, stmts);
cx.expr_block(block)
},
Box::new(|cx, sp, _, _| {
cx.span_bug(sp, "non matching enums in derive(Eq)?")
}),
cx,
span,
substr)
}

let inline = cx.meta_word(span, InternedString::new("inline"));
let hidden = cx.meta_list_item_word(span, InternedString::new("hidden"));
let doc = cx.meta_list(span, InternedString::new("doc"), vec![hidden]);
Expand All @@ -50,7 +34,7 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt,
additional_bounds: Vec::new(),
generics: LifetimeBounds::empty(),
is_unsafe: false,
supports_unions: false,
supports_unions: true,
methods: vec![MethodDef {
name: "assert_receiver_is_total_eq",
generics: LifetimeBounds::empty(),
Expand All @@ -66,5 +50,38 @@ pub fn expand_deriving_eq(cx: &mut ExtCtxt,
}],
associated_types: Vec::new(),
};
trait_def.expand(cx, mitem, item, push)
trait_def.expand_ext(cx, mitem, item, push, true)
}

fn cs_total_eq_assert(cx: &mut ExtCtxt, trait_span: Span, substr: &Substructure) -> P<Expr> {
fn assert_ty_bounds(cx: &mut ExtCtxt, stmts: &mut Vec<ast::Stmt>,
ty: P<ast::Ty>, span: Span, helper_name: &str) {
// Generate statement `let _: helper_name<ty>;`,
// set the expn ID so we can use the unstable struct.
let span = super::allow_unstable(cx, span, "derive(Eq)");
let assert_path = cx.path_all(span, true,
cx.std_path(&["cmp", helper_name]),
vec![], vec![ty], vec![]);
stmts.push(cx.stmt_let_type_only(span, cx.ty_path(assert_path)));
}
fn process_variant(cx: &mut ExtCtxt, stmts: &mut Vec<ast::Stmt>, variant: &ast::VariantData) {
for field in variant.fields() {
// let _: AssertParamIsEq<FieldTy>;
assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsEq");
}
}

let mut stmts = Vec::new();
match *substr.fields {
StaticStruct(vdata, ..) => {
process_variant(cx, &mut stmts, vdata);
}
StaticEnum(enum_def, ..) => {
for variant in &enum_def.variants {
process_variant(cx, &mut stmts, &variant.node.data);
}
}
_ => cx.span_bug(trait_span, "unexpected substructure in `derive(Eq)`")
}
cx.expr_block(cx.block(trait_span, stmts))
}
30 changes: 30 additions & 0 deletions src/test/compile-fail/union/union-derive-eq.rs
@@ -0,0 +1,30 @@
// Copyright 2016 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(untagged_unions)]

#[derive(Eq)] // OK
union U1 {
a: u8,
}

impl PartialEq for U1 { fn eq(&self, rhs: &Self) -> bool { true } }

#[derive(PartialEq)]
struct PartialEqNotEq;

#[derive(Eq)]
union U2 {
a: PartialEqNotEq, //~ ERROR the trait bound `PartialEqNotEq: std::cmp::Eq` is not satisfied
}

impl PartialEq for U2 { fn eq(&self, rhs: &Self) -> bool { true } }

fn main() {}
1 change: 0 additions & 1 deletion src/test/compile-fail/union/union-derive.rs
Expand Up @@ -14,7 +14,6 @@

#[derive(
PartialEq, //~ ERROR this trait cannot be derived for unions
Eq, //~ ERROR this trait cannot be derived for unions
PartialOrd, //~ ERROR this trait cannot be derived for unions
Ord, //~ ERROR this trait cannot be derived for unions
Hash, //~ ERROR this trait cannot be derived for unions
Expand Down
13 changes: 12 additions & 1 deletion src/test/run-pass/union/union-derive.rs
Expand Up @@ -15,22 +15,33 @@
#[derive(
Copy,
Clone,
Eq,
)]
union U {
a: u8,
b: u16,
}

#[derive(Clone, Copy)]
impl PartialEq for U { fn eq(&self, rhs: &Self) -> bool { true } }

#[derive(
Clone,
Copy,
Eq
)]
union W<T> {
a: T,
}

impl<T> PartialEq for W<T> { fn eq(&self, rhs: &Self) -> bool { true } }

fn main() {
let u = U { b: 0 };
let u1 = u;
let u2 = u.clone();
assert!(u1 == u2);

let w = W { a: 0 };
let w1 = w.clone();
assert!(w == w1);
}

0 comments on commit 62cb751

Please sign in to comment.