Skip to content

Commit

Permalink
Rollup merge of rust-lang#36384 - petrochenkov:derclone, r=alexcrichton
Browse files Browse the repository at this point in the history
Improve shallow `Clone` deriving

`Copy` unions now support `#[derive(Clone)]`.
Less code is generated for `#[derive(Clone, Copy)]`.
+
Unions now support `#[derive(Eq)]`.
Less code is generated for `#[derive(Eq)]`.

---
Example of code reduction:
```
enum E {
	A { a: u8, b: u16 },
	B { c: [u8; 100] },
}
```
Before:
```
fn clone(&self) -> E {
    match (&*self,) {
        (&E::A { a: ref __self_0, b: ref __self_1 },) => {
            ::std::clone::assert_receiver_is_clone(&(*__self_0));
            ::std::clone::assert_receiver_is_clone(&(*__self_1));
            *self
        }
        (&E::B { c: ref __self_0 },) => {
            ::std::clone::assert_receiver_is_clone(&(*__self_0));
            *self
        }
    }
}
```
After:
```
fn clone(&self) -> E {
    {
        let _: ::std::clone::AssertParamIsClone<u8>;
        let _: ::std::clone::AssertParamIsClone<u16>;
        let _: ::std::clone::AssertParamIsClone<[u8; 100]>;
        *self
    }
}
```

All the matches are removed, bound assertions are more lightweight.
`let _: Checker<CheckMe>;`, unlike `checker(&check_me);`, doesn't have to be translated by rustc_trans and then inlined by LLVM, it doesn't even exist in MIR, this means faster compilation.

---
Union impls are generated like this:
```
union U {
	a: u8,
	b: u16,
	c: [u8; 100],
}
```
```
fn clone(&self) -> U {
    {
        let _: ::std::clone::AssertParamIsCopy<Self>;
        *self
    }
}
```

Fixes rust-lang#36043
cc @durka
r? @alexcrichton
  • Loading branch information
Manishearth authored Sep 15, 2016
2 parents 16ff9e2 + 62cb751 commit 7268501
Show file tree
Hide file tree
Showing 14 changed files with 293 additions and 121 deletions.
19 changes: 16 additions & 3 deletions src/libcore/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,23 @@ pub trait Clone : Sized {
}
}

// FIXME(aburka): this method is used solely by #[derive] to
// assert that every component of a type implements Clone.
// FIXME(aburka): these structs are used solely by #[derive] to
// assert that every component of a type implements Clone or Copy.
//
// This should never be called by user code.
// These structs should never appear in user code.
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[unstable(feature = "derive_clone_copy",
reason = "deriving hack, should not be public",
issue = "0")]
pub struct AssertParamIsClone<T: Clone + ?Sized> { _field: ::marker::PhantomData<T> }
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[unstable(feature = "derive_clone_copy",
reason = "deriving hack, should not be public",
issue = "0")]
pub struct AssertParamIsCopy<T: Copy + ?Sized> { _field: ::marker::PhantomData<T> }
#[cfg(stage0)]
#[doc(hidden)]
#[inline(always)]
#[unstable(feature = "derive_clone_copy",
Expand Down
13 changes: 12 additions & 1 deletion src/libcore/cmp.rs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
166 changes: 96 additions & 70 deletions src/libsyntax_ext/deriving/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,14 @@
use deriving::generic::*;
use deriving::generic::ty::*;

use syntax::ast::{Expr, Generics, ItemKind, MetaItem, VariantData};
use syntax::ast::{self, Expr, Generics, ItemKind, MetaItem, VariantData};
use syntax::attr;
use syntax::ext::base::{Annotatable, ExtCtxt};
use syntax::ext::build::AstBuilder;
use syntax::parse::token::InternedString;
use syntax::parse::token::{keywords, InternedString};
use syntax::ptr::P;
use syntax_pos::Span;

#[derive(PartialEq)]
enum Mode {
Deep,
Shallow,
}

pub fn expand_deriving_clone(cx: &mut ExtCtxt,
span: Span,
mitem: &MetaItem,
Expand All @@ -40,29 +34,38 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
// if we used the short form with generics, we'd have to bound the generics with
// Clone + Copy, and then there'd be no Clone impl at all if the user fills in something
// that is Clone but not Copy. and until specialization we can't write both impls.
// - the item is a union with Copy fields
// Unions with generic parameters still can derive Clone because they require Copy
// for deriving, Clone alone is not enough.
// Whever Clone is implemented for fields is irrelevant so we don't assert it.
let bounds;
let unify_fieldless_variants;
let substructure;
let is_shallow;
match *item {
Annotatable::Item(ref annitem) => {
match annitem.node {
ItemKind::Struct(_, Generics { ref ty_params, .. }) |
ItemKind::Enum(_, Generics { ref ty_params, .. })
if ty_params.is_empty() &&
attr::contains_name(&annitem.attrs, "rustc_copy_clone_marker") => {

if attr::contains_name(&annitem.attrs, "rustc_copy_clone_marker") &&
ty_params.is_empty() => {
bounds = vec![];
is_shallow = true;
substructure = combine_substructure(Box::new(|c, s, sub| {
cs_clone_shallow("Clone", c, s, sub, false)
}));
}
ItemKind::Union(..) => {
bounds = vec![Literal(path_std!(cx, core::marker::Copy))];
unify_fieldless_variants = true;
is_shallow = true;
substructure = combine_substructure(Box::new(|c, s, sub| {
cs_clone("Clone", c, s, sub, Mode::Shallow)
cs_clone_shallow("Clone", c, s, sub, true)
}));
}

_ => {
bounds = vec![];
unify_fieldless_variants = false;
is_shallow = false;
substructure = combine_substructure(Box::new(|c, s, sub| {
cs_clone("Clone", c, s, sub, Mode::Deep)
cs_clone("Clone", c, s, sub)
}));
}
}
Expand All @@ -80,7 +83,7 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
additional_bounds: bounds,
generics: LifetimeBounds::empty(),
is_unsafe: false,
supports_unions: false,
supports_unions: true,
methods: vec![MethodDef {
name: "clone",
generics: LifetimeBounds::empty(),
Expand All @@ -89,37 +92,72 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
ret_ty: Self_,
attributes: attrs,
is_unsafe: false,
unify_fieldless_variants: unify_fieldless_variants,
unify_fieldless_variants: false,
combine_substructure: substructure,
}],
associated_types: Vec::new(),
};

trait_def.expand(cx, mitem, item, push)
trait_def.expand_ext(cx, mitem, item, push, is_shallow)
}

fn cs_clone_shallow(name: &str,
cx: &mut ExtCtxt,
trait_span: Span,
substr: &Substructure,
is_union: bool)
-> 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(Clone)");
let assert_path = cx.path_all(span, true,
cx.std_path(&["clone", 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: &VariantData) {
for field in variant.fields() {
// let _: AssertParamIsClone<FieldTy>;
assert_ty_bounds(cx, stmts, field.ty.clone(), field.span, "AssertParamIsClone");
}
}

let mut stmts = Vec::new();
if is_union {
// let _: AssertParamIsCopy<Self>;
let self_ty = cx.ty_path(cx.path_ident(trait_span, keywords::SelfType.ident()));
assert_ty_bounds(cx, &mut stmts, self_ty, trait_span, "AssertParamIsCopy");
} else {
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, &format!("unexpected substructure in \
shallow `derive({})`", name))
}
}
stmts.push(cx.stmt_expr(cx.expr_deref(trait_span, cx.expr_self(trait_span))));
cx.expr_block(cx.block(trait_span, stmts))
}

fn cs_clone(name: &str,
cx: &mut ExtCtxt,
trait_span: Span,
substr: &Substructure,
mode: Mode)
substr: &Substructure)
-> P<Expr> {
let ctor_path;
let all_fields;
let fn_path = match mode {
Mode::Shallow => cx.std_path(&["clone", "assert_receiver_is_clone"]),
Mode::Deep => cx.std_path(&["clone", "Clone", "clone"]),
};
let fn_path = cx.std_path(&["clone", "Clone", "clone"]);
let subcall = |cx: &mut ExtCtxt, field: &FieldInfo| {
let args = vec![cx.expr_addr_of(field.span, field.self_.clone())];

let span = if mode == Mode::Shallow {
// set the expn ID so we can call the unstable method
super::allow_unstable(cx, field.span, "derive(Clone)")
} else {
field.span
};
cx.expr_call_global(span, fn_path.clone(), args)
cx.expr_call_global(field.span, fn_path.clone(), args)
};

let vdata;
Expand All @@ -145,43 +183,31 @@ fn cs_clone(name: &str,
}
}

match mode {
Mode::Shallow => {
let mut stmts = all_fields.iter().map(|f| {
let call = subcall(cx, f);
cx.stmt_expr(call)
}).collect::<Vec<_>>();
stmts.push(cx.stmt_expr(cx.expr_deref(trait_span, cx.expr_self(trait_span))));
cx.expr_block(cx.block(trait_span, stmts))
}
Mode::Deep => {
match *vdata {
VariantData::Struct(..) => {
let fields = all_fields.iter()
.map(|field| {
let ident = match field.name {
Some(i) => i,
None => {
cx.span_bug(trait_span,
&format!("unnamed field in normal struct in \
`derive({})`",
name))
}
};
let call = subcall(cx, field);
cx.field_imm(field.span, ident, call)
})
.collect::<Vec<_>>();
match *vdata {
VariantData::Struct(..) => {
let fields = all_fields.iter()
.map(|field| {
let ident = match field.name {
Some(i) => i,
None => {
cx.span_bug(trait_span,
&format!("unnamed field in normal struct in \
`derive({})`",
name))
}
};
let call = subcall(cx, field);
cx.field_imm(field.span, ident, call)
})
.collect::<Vec<_>>();

cx.expr_struct(trait_span, ctor_path, fields)
}
VariantData::Tuple(..) => {
let subcalls = all_fields.iter().map(|f| subcall(cx, f)).collect();
let path = cx.expr_path(ctor_path);
cx.expr_call(trait_span, path, subcalls)
}
VariantData::Unit(..) => cx.expr_path(ctor_path),
}
cx.expr_struct(trait_span, ctor_path, fields)
}
VariantData::Tuple(..) => {
let subcalls = all_fields.iter().map(|f| subcall(cx, f)).collect();
let path = cx.expr_path(ctor_path);
cx.expr_call(trait_span, path, subcalls)
}
VariantData::Unit(..) => cx.expr_path(ctor_path),
}
}
Loading

0 comments on commit 7268501

Please sign in to comment.