Auto merge of #31414 - durka:clone-copy, r=alexcrichton

special-case #[derive(Copy, Clone)] with a shallow clone

If a type is Copy then its Clone implementation can be a no-op. Currently `#[derive(Clone)]` generates a deep clone anyway. This can lead to lots of code bloat.

This PR detects the case where Copy and Clone are both being derived (the general case of "is this type Copy" can't be determined by a syntax extension) and generates the shallow Clone impl. Right now this can only be done if there are no type parameters (see https://github.com/rust-lang/rust/issues/31085#issuecomment-178988663), but this restriction can be removed after specialization.

Fixes #31085.
This commit is contained in:
bors 2016-04-26 14:54:37 -07:00
commit 8f55218189
11 changed files with 203 additions and 40 deletions

View File

@ -75,6 +75,17 @@ pub trait Clone : Sized {
} }
} }
// FIXME(aburka): this method is used solely by #[derive] to
// assert that every component of a type implements Clone.
//
// This should never be called by user code.
#[doc(hidden)]
#[inline(always)]
#[unstable(feature = "derive_clone_copy",
reason = "deriving hack, should not be public",
issue = "0")]
pub fn assert_receiver_is_clone<T: Clone + ?Sized>(_: &T) {}
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
impl<'a, T: ?Sized> Clone for &'a T { impl<'a, T: ?Sized> Clone for &'a T {
/// Returns a shallow copy of the reference. /// Returns a shallow copy of the reference.

View File

@ -11,26 +11,68 @@
use deriving::generic::*; use deriving::generic::*;
use deriving::generic::ty::*; use deriving::generic::ty::*;
use syntax::ast::{MetaItem, Expr, VariantData}; use syntax::ast::{Expr, ItemKind, Generics, MetaItem, VariantData};
use syntax::attr::{self, AttrMetaMethods};
use syntax::codemap::Span; use syntax::codemap::Span;
use syntax::ext::base::{ExtCtxt, Annotatable}; use syntax::ext::base::{ExtCtxt, Annotatable};
use syntax::ext::build::AstBuilder; use syntax::ext::build::AstBuilder;
use syntax::parse::token::InternedString; use syntax::parse::token::InternedString;
use syntax::ptr::P; use syntax::ptr::P;
#[derive(PartialEq)]
enum Mode { Deep, Shallow }
pub fn expand_deriving_clone(cx: &mut ExtCtxt, pub fn expand_deriving_clone(cx: &mut ExtCtxt,
span: Span, span: Span,
mitem: &MetaItem, mitem: &MetaItem,
item: &Annotatable, item: &Annotatable,
push: &mut FnMut(Annotatable)) push: &mut FnMut(Annotatable))
{ {
// check if we can use a short form
//
// the short form is `fn clone(&self) -> Self { *self }`
//
// we can use the short form if:
// - the item is Copy (unfortunately, all we can check is whether it's also deriving Copy)
// - there are no generic parameters (after specialization this limitation can be removed)
// 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.
let bounds;
let substructure;
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, "derive_Copy") => {
bounds = vec![Literal(path_std!(cx, core::marker::Copy))];
substructure = combine_substructure(Box::new(|c, s, sub| {
cs_clone("Clone", c, s, sub, Mode::Shallow)
}));
}
_ => {
bounds = vec![];
substructure = combine_substructure(Box::new(|c, s, sub| {
cs_clone("Clone", c, s, sub, Mode::Deep)
}));
}
}
}
_ => cx.span_bug(span, "#[derive(Clone)] on trait item or impl item")
}
let inline = cx.meta_word(span, InternedString::new("inline")); let inline = cx.meta_word(span, InternedString::new("inline"));
let attrs = vec!(cx.attribute(span, inline)); let attrs = vec!(cx.attribute(span, inline));
let trait_def = TraitDef { let trait_def = TraitDef {
span: span, span: span,
attributes: Vec::new(), attributes: Vec::new(),
path: path_std!(cx, core::clone::Clone), path: path_std!(cx, core::clone::Clone),
additional_bounds: Vec::new(), additional_bounds: bounds,
generics: LifetimeBounds::empty(), generics: LifetimeBounds::empty(),
is_unsafe: false, is_unsafe: false,
methods: vec!( methods: vec!(
@ -42,9 +84,7 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
ret_ty: Self_, ret_ty: Self_,
attributes: attrs, attributes: attrs,
is_unsafe: false, is_unsafe: false,
combine_substructure: combine_substructure(Box::new(|c, s, sub| { combine_substructure: substructure,
cs_clone("Clone", c, s, sub)
})),
} }
), ),
associated_types: Vec::new(), associated_types: Vec::new(),
@ -56,14 +96,24 @@ pub fn expand_deriving_clone(cx: &mut ExtCtxt,
fn cs_clone( fn cs_clone(
name: &str, name: &str,
cx: &mut ExtCtxt, trait_span: Span, cx: &mut ExtCtxt, trait_span: Span,
substr: &Substructure) -> P<Expr> { substr: &Substructure,
mode: Mode) -> P<Expr> {
let ctor_path; let ctor_path;
let all_fields; let all_fields;
let fn_path = cx.std_path(&["clone", "Clone", "clone"]); let fn_path = match mode {
Mode::Shallow => cx.std_path(&["clone", "assert_receiver_is_clone"]),
Mode::Deep => cx.std_path(&["clone", "Clone", "clone"]),
};
let subcall = |field: &FieldInfo| { let subcall = |field: &FieldInfo| {
let args = vec![cx.expr_addr_of(field.span, field.self_.clone())]; let args = vec![cx.expr_addr_of(field.span, field.self_.clone())];
cx.expr_call_global(field.span, fn_path.clone(), args) let span = if mode == Mode::Shallow {
// set the expn ID so we can call the unstable method
Span { expn_id: cx.backtrace(), .. trait_span }
} else {
field.span
};
cx.expr_call_global(span, fn_path.clone(), args)
}; };
let vdata; let vdata;
@ -89,29 +139,41 @@ fn cs_clone(
} }
} }
match *vdata { match mode {
VariantData::Struct(..) => { Mode::Shallow => {
let fields = all_fields.iter().map(|field| { cx.expr_block(cx.block(trait_span,
let ident = match field.name { all_fields.iter()
Some(i) => i, .map(subcall)
None => { .map(|e| cx.stmt_expr(e))
cx.span_bug(trait_span, .collect(),
&format!("unnamed field in normal struct in \ Some(cx.expr_deref(trait_span, cx.expr_self(trait_span)))))
`derive({})`", name)) }
} Mode::Deep => {
}; match *vdata {
cx.field_imm(field.span, ident, subcall(field)) VariantData::Struct(..) => {
}).collect::<Vec<_>>(); 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))
}
};
cx.field_imm(field.span, ident, subcall(field))
}).collect::<Vec<_>>();
cx.expr_struct(trait_span, ctor_path, fields) cx.expr_struct(trait_span, ctor_path, fields)
} }
VariantData::Tuple(..) => { VariantData::Tuple(..) => {
let subcalls = all_fields.iter().map(subcall).collect(); let subcalls = all_fields.iter().map(subcall).collect();
let path = cx.expr_path(ctor_path); let path = cx.expr_path(ctor_path);
cx.expr_call(trait_span, path, subcalls) cx.expr_call(trait_span, path, subcalls)
} }
VariantData::Unit(..) => { VariantData::Unit(..) => {
cx.expr_path(ctor_path) cx.expr_path(ctor_path)
}
}
} }
} }
} }

View File

@ -74,7 +74,7 @@ fn expand_deriving_decodable_imp(cx: &mut ExtCtxt,
}, },
explicit_self: None, explicit_self: None,
args: vec!(Ptr(Box::new(Literal(Path::new_local(typaram))), args: vec!(Ptr(Box::new(Literal(Path::new_local(typaram))),
Borrowed(None, Mutability::Mutable))), Borrowed(None, Mutability::Mutable))),
ret_ty: Literal(Path::new_( ret_ty: Literal(Path::new_(
pathvec_std!(cx, core::result::Result), pathvec_std!(cx, core::result::Result),
None, None,

View File

@ -150,7 +150,7 @@ fn expand_deriving_encodable_imp(cx: &mut ExtCtxt,
}, },
explicit_self: borrowed_explicit_self(), explicit_self: borrowed_explicit_self(),
args: vec!(Ptr(Box::new(Literal(Path::new_local(typaram))), args: vec!(Ptr(Box::new(Literal(Path::new_local(typaram))),
Borrowed(None, Mutability::Mutable))), Borrowed(None, Mutability::Mutable))),
ret_ty: Literal(Path::new_( ret_ty: Literal(Path::new_(
pathvec_std!(cx, core::result::Result), pathvec_std!(cx, core::result::Result),
None, None,

View File

@ -857,6 +857,7 @@ impl<'a> MethodDef<'a> {
explicit_self: ast::ExplicitSelf, explicit_self: ast::ExplicitSelf,
arg_types: Vec<(Ident, P<ast::Ty>)> , arg_types: Vec<(Ident, P<ast::Ty>)> ,
body: P<Expr>) -> ast::ImplItem { body: P<Expr>) -> ast::ImplItem {
// create the generics that aren't for Self // create the generics that aren't for Self
let fn_generics = self.generics.to_generics(cx, trait_.span, type_ident, generics); let fn_generics = self.generics.to_generics(cx, trait_.span, type_ident, generics);
@ -990,6 +991,7 @@ impl<'a> MethodDef<'a> {
body = cx.expr_match(trait_.span, arg_expr.clone(), body = cx.expr_match(trait_.span, arg_expr.clone(),
vec!( cx.arm(trait_.span, vec!(pat.clone()), body) )) vec!( cx.arm(trait_.span, vec!(pat.clone()), body) ))
} }
body body
} }

View File

@ -0,0 +1,48 @@
// 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.
// this will get a no-op Clone impl
#[derive(Copy, Clone)]
struct A {
a: i32,
b: i64
}
// this will get a deep Clone impl
#[derive(Copy, Clone)]
struct B<T> {
a: i32,
b: T
}
struct C; // not Copy or Clone
#[derive(Clone)] struct D; // Clone but not Copy
fn is_copy<T: Copy>(_: T) {}
fn is_clone<T: Clone>(_: T) {}
fn main() {
// A can be copied and cloned
is_copy(A { a: 1, b: 2 });
is_clone(A { a: 1, b: 2 });
// B<i32> can be copied and cloned
is_copy(B { a: 1, b: 2 });
is_clone(B { a: 1, b: 2 });
// B<C> cannot be copied or cloned
is_copy(B { a: 1, b: C }); //~ERROR Copy
is_clone(B { a: 1, b: C }); //~ERROR Clone
// B<D> can be cloned but not copied
is_copy(B { a: 1, b: D }); //~ERROR Copy
is_clone(B { a: 1, b: D });
}

View File

@ -12,8 +12,6 @@
// //
// (Compare with compile-fail/move-out-of-array-1.rs) // (Compare with compile-fail/move-out-of-array-1.rs)
// pretty-expanded FIXME #23616
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct C { _x: u8 } struct C { _x: u8 }

View File

@ -0,0 +1,48 @@
// 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.
//! Test that #[derive(Copy, Clone)] produces a shallow copy
//! even when a member violates RFC 1521
use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT, Ordering};
/// A struct that pretends to be Copy, but actually does something
/// in its Clone impl
#[derive(Copy)]
struct Liar;
/// Static cooperating with the rogue Clone impl
static CLONED: AtomicBool = ATOMIC_BOOL_INIT;
impl Clone for Liar {
fn clone(&self) -> Self {
// this makes Clone vs Copy observable
CLONED.store(true, Ordering::SeqCst);
*self
}
}
/// This struct is actually Copy... at least, it thinks it is!
#[derive(Copy, Clone)]
struct Innocent(Liar);
impl Innocent {
fn new() -> Self {
Innocent(Liar)
}
}
fn main() {
let _ = Innocent::new().clone();
// if Innocent was byte-for-byte copied, CLONED will still be false
assert!(!CLONED.load(Ordering::SeqCst));
}

View File

@ -16,8 +16,6 @@
// did not consider that a match (something I would like to revise in // did not consider that a match (something I would like to revise in
// a later PR). // a later PR).
// pretty-expanded FIXME #23616
#![allow(dead_code)] #![allow(dead_code)]
use std::marker::PhantomData; use std::marker::PhantomData;

View File

@ -8,8 +8,6 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
// pretty-expanded FIXME #23616
use std::ops::Deref; use std::ops::Deref;
struct Root { struct Root {

View File

@ -8,8 +8,6 @@
// option. This file may not be copied, modified, or distributed // option. This file may not be copied, modified, or distributed
// except according to those terms. // except according to those terms.
// pretty-expanded FIXME #23616
#![allow(unknown_features)] #![allow(unknown_features)]
#![feature(box_syntax)] #![feature(box_syntax)]