1
Fork 0

Auto merge of #99046 - nnethercote:final-derive-output-improvements, r=Mark-Simulacrum

Final derive output improvements

With all these changes, the derive output in `deriving-all-codegen.stdout` is pretty close to optimal, i.e. very similar to what you'd write by hand.

r? `@ghost`
This commit is contained in:
bors 2022-07-15 14:30:14 +00:00
commit 0fe5390a88
11 changed files with 720 additions and 578 deletions

View file

@ -148,7 +148,7 @@ fn cs_clone_simple(
),
}
}
BlockOrExpr::new_mixed(stmts, cx.expr_deref(trait_span, cx.expr_self(trait_span)))
BlockOrExpr::new_mixed(stmts, Some(cx.expr_deref(trait_span, cx.expr_self(trait_span))))
}
fn cs_clone(
@ -161,7 +161,7 @@ fn cs_clone(
let all_fields;
let fn_path = cx.std_path(&[sym::clone, sym::Clone, sym::clone]);
let subcall = |cx: &mut ExtCtxt<'_>, field: &FieldInfo| {
let args = vec![cx.expr_addr_of(field.span, field.self_expr.clone())];
let args = vec![field.self_expr.clone()];
cx.expr_call_global(field.span, fn_path.clone(), args)
};
@ -177,9 +177,7 @@ fn cs_clone(
all_fields = af;
vdata = &variant.data;
}
EnumNonMatchingCollapsed(..) => {
cx.span_bug(trait_span, &format!("non-matching enum variants in `derive({})`", name,))
}
EnumTag(..) => cx.span_bug(trait_span, &format!("enum tags in `derive({})`", name,)),
StaticEnum(..) | StaticStruct(..) => {
cx.span_bug(trait_span, &format!("associated function in `derive({})`", name))
}

View file

@ -63,10 +63,7 @@ pub fn cs_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> Bl
let [other_expr] = &field.other_selflike_exprs[..] else {
cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
};
let args = vec![
cx.expr_addr_of(field.span, field.self_expr.clone()),
cx.expr_addr_of(field.span, other_expr.clone()),
];
let args = vec![field.self_expr.clone(), other_expr.clone()];
cx.expr_call_global(field.span, cmp_path.clone(), args)
}
CsFold::Combine(span, expr1, expr2) => {
@ -76,16 +73,6 @@ pub fn cs_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>) -> Bl
cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
}
CsFold::Fieldless => cx.expr_path(equal_path.clone()),
CsFold::EnumNonMatching(span, tag_tuple) => {
if tag_tuple.len() != 2 {
cx.span_bug(span, "not exactly 2 arguments in `derive(Ord)`")
} else {
let lft = cx.expr_addr_of(span, cx.expr_ident(span, tag_tuple[0]));
let rgt = cx.expr_addr_of(span, cx.expr_ident(span, tag_tuple[1]));
let fn_cmp_path = cx.std_path(&[sym::cmp, sym::Ord, sym::cmp]);
cx.expr_call_global(span, fn_cmp_path, vec![lft, rgt])
}
}
},
);
BlockOrExpr::new_expr(expr)

View file

@ -2,7 +2,8 @@ use crate::deriving::generic::ty::*;
use crate::deriving::generic::*;
use crate::deriving::{path_local, path_std};
use rustc_ast::{BinOpKind, MetaItem};
use rustc_ast::ptr::P;
use rustc_ast::{BinOpKind, BorrowKind, Expr, ExprKind, MetaItem, Mutability};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::symbol::sym;
use rustc_span::Span;
@ -32,11 +33,24 @@ pub fn expand_deriving_partial_eq(
let [other_expr] = &field.other_selflike_exprs[..] else {
cx.span_bug(field.span, "not exactly 2 arguments in `derive(PartialEq)`");
};
cx.expr_binary(field.span, op, field.self_expr.clone(), other_expr.clone())
// We received `&T` arguments. Convert them to `T` by
// stripping `&` or adding `*`. This isn't necessary for
// type checking, but it results in much better error
// messages if something goes wrong.
let convert = |expr: &P<Expr>| {
if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) =
&expr.kind
{
inner.clone()
} else {
cx.expr_deref(field.span, expr.clone())
}
};
cx.expr_binary(field.span, op, convert(&field.self_expr), convert(other_expr))
}
CsFold::Combine(span, expr1, expr2) => cx.expr_binary(span, combiner, expr1, expr2),
CsFold::Fieldless => cx.expr_bool(span, base),
CsFold::EnumNonMatching(span, _tag_tuple) => cx.expr_bool(span, !base),
},
);
BlockOrExpr::new_expr(expr)

View file

@ -71,10 +71,7 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_
let [other_expr] = &field.other_selflike_exprs[..] else {
cx.span_bug(field.span, "not exactly 2 arguments in `derive(Ord)`");
};
let args = vec![
cx.expr_addr_of(field.span, field.self_expr.clone()),
cx.expr_addr_of(field.span, other_expr.clone()),
];
let args = vec![field.self_expr.clone(), other_expr.clone()];
cx.expr_call_global(field.span, partial_cmp_path.clone(), args)
}
CsFold::Combine(span, expr1, expr2) => {
@ -85,17 +82,6 @@ pub fn cs_partial_cmp(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_
cx.expr_match(span, expr2, vec![eq_arm, neq_arm])
}
CsFold::Fieldless => cx.expr_some(span, cx.expr_path(equal_path.clone())),
CsFold::EnumNonMatching(span, tag_tuple) => {
if tag_tuple.len() != 2 {
cx.span_bug(span, "not exactly 2 arguments in `derive(PartialOrd)`")
} else {
let lft = cx.expr_addr_of(span, cx.expr_ident(span, tag_tuple[0]));
let rgt = cx.expr_addr_of(span, cx.expr_ident(span, tag_tuple[1]));
let fn_partial_cmp_path =
cx.std_path(&[sym::cmp, sym::PartialOrd, sym::partial_cmp]);
cx.expr_call_global(span, fn_partial_cmp_path, vec![lft, rgt])
}
}
},
);
BlockOrExpr::new_expr(expr)

View file

@ -45,7 +45,7 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
let (ident, vdata, fields) = match substr.fields {
Struct(vdata, fields) => (substr.type_ident, *vdata, fields),
EnumMatching(_, _, v, fields) => (v.ident, &v.data, fields),
EnumNonMatchingCollapsed(..) | StaticStruct(..) | StaticEnum(..) => {
EnumTag(..) | StaticStruct(..) | StaticEnum(..) => {
cx.span_bug(span, "nonsensical .fields in `#[derive(Debug)]`")
}
};
@ -95,9 +95,8 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
);
args.push(name);
}
// Use double indirection to make sure this works for unsized types
// Use an extra indirection to make sure this works for unsized types.
let field = cx.expr_addr_of(field.span, field.self_expr.clone());
let field = cx.expr_addr_of(field.span, field);
args.push(field);
}
let expr = cx.expr_call_global(span, fn_path_debug, args);
@ -115,9 +114,9 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
));
}
// Use double indirection to make sure this works for unsized types
let value_ref = cx.expr_addr_of(field.span, field.self_expr.clone());
value_exprs.push(cx.expr_addr_of(field.span, value_ref));
// Use an extra indirection to make sure this works for unsized types.
let field = cx.expr_addr_of(field.span, field.self_expr.clone());
value_exprs.push(field);
}
// `let names: &'static _ = &["field1", "field2"];`
@ -177,6 +176,6 @@ fn show_substructure(cx: &mut ExtCtxt<'_>, span: Span, substr: &Substructure<'_>
stmts.push(names_let.unwrap());
}
stmts.push(values_let);
BlockOrExpr::new_mixed(stmts, expr)
BlockOrExpr::new_mixed(stmts, Some(expr))
}
}

View file

@ -287,7 +287,7 @@ fn encodable_substructure(
fn_emit_enum_path,
vec![encoder, cx.expr_str(trait_span, substr.type_ident.name), blk],
);
BlockOrExpr::new_mixed(vec![me], expr)
BlockOrExpr::new_mixed(vec![me], Some(expr))
}
_ => cx.bug("expected Struct or EnumMatching in derive(Encodable)"),

View file

@ -21,21 +21,14 @@
//! `struct T(i32, char)`).
//! - `EnumMatching`, when `Self` is an enum and all the arguments are the
//! same variant of the enum (e.g., `Some(1)`, `Some(3)` and `Some(4)`)
//! - `EnumNonMatchingCollapsed` when `Self` is an enum and the arguments
//! are not the same variant (e.g., `None`, `Some(1)` and `None`).
//! - `EnumTag` when `Self` is an enum, for comparing the enum tags.
//! - `StaticEnum` and `StaticStruct` for static methods, where the type
//! being derived upon is either an enum or struct respectively. (Any
//! argument with type Self is just grouped among the non-self
//! arguments.)
//!
//! In the first two cases, the values from the corresponding fields in
//! all the arguments are grouped together. For `EnumNonMatchingCollapsed`
//! this isn't possible (different variants have different fields), so the
//! fields are inaccessible. (Previous versions of the deriving infrastructure
//! had a way to expand into code that could access them, at the cost of
//! generating exponential amounts of code; see issue #15375). There are no
//! fields with values in the static cases, so these are treated entirely
//! differently.
//! all the arguments are grouped together.
//!
//! The non-static cases have `Option<ident>` in several places associated
//! with field `expr`s. This represents the name of the field it is
@ -142,21 +135,15 @@
//! }])
//! ```
//!
//! For `C0(a)` and `C1 {x}` ,
//! For the tags,
//!
//! ```{.text}
//! EnumNonMatchingCollapsed(
//! &[<ident for self index value>, <ident of __arg_1 index value>])
//! EnumTag(
//! &[<ident of self tag>, <ident of other tag>], <expr to combine with>)
//! ```
//!
//! It is the same for when the arguments are flipped to `C1 {x}` and
//! `C0(a)`; the only difference is what the values of the identifiers
//! <ident for self index value> and <ident of __arg_1 index value> will
//! be in the generated code.
//!
//! `EnumNonMatchingCollapsed` deliberately provides far less information
//! than is generally available for a given pair of variants; see #15375
//! for discussion.
//! Note that this setup doesn't allow for the brute-force "match every variant
//! against every other variant" approach, which is bad because it produces a
//! quadratic amount of code (see #15375).
//!
//! ## Static
//!
@ -180,10 +167,9 @@ use std::iter;
use std::vec;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, BinOpKind, EnumDef, Expr, Generics, PatKind};
use rustc_ast::{self as ast, EnumDef, Expr, Generics, PatKind};
use rustc_ast::{GenericArg, GenericParamKind, VariantData};
use rustc_attr as attr;
use rustc_data_structures::map_in_place::MapInPlace;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::Span;
@ -236,6 +222,8 @@ pub struct MethodDef<'a> {
pub attributes: Vec<ast::Attribute>,
/// Can we combine fieldless variants for enums into a single match arm?
/// If true, indicates that the trait operation uses the enum tag in some
/// way.
pub unify_fieldless_variants: bool,
pub combine_substructure: RefCell<CombineSubstructureFunc<'a>>,
@ -275,19 +263,22 @@ pub enum StaticFields {
/// A summary of the possible sets of fields.
pub enum SubstructureFields<'a> {
/// A non-static method with `Self` is a struct.
Struct(&'a ast::VariantData, Vec<FieldInfo>),
/// Matching variants of the enum: variant index, variant count, ast::Variant,
/// fields: the field name is only non-`None` in the case of a struct
/// variant.
EnumMatching(usize, usize, &'a ast::Variant, Vec<FieldInfo>),
/// Non-matching variants of the enum, but with all state hidden from the
/// consequent code. The field is a list of `Ident`s bound to the variant
/// index values for each of the actual input `Self` arguments.
EnumNonMatchingCollapsed(&'a [Ident]),
/// The tag of an enum. The first field is a `FieldInfo` for the tags, as
/// if they were fields. The second field is the expression to combine the
/// tag expression with; it will be `None` if no match is necessary.
EnumTag(FieldInfo, Option<P<Expr>>),
/// A static method where `Self` is a struct.
StaticStruct(&'a ast::VariantData, StaticFields),
/// A static method where `Self` is an enum.
StaticEnum(&'a ast::EnumDef, Vec<(Ident, Span, StaticFields)>),
}
@ -325,8 +316,8 @@ impl BlockOrExpr {
BlockOrExpr(vec![], Some(expr))
}
pub fn new_mixed(stmts: Vec<ast::Stmt>, expr: P<Expr>) -> BlockOrExpr {
BlockOrExpr(stmts, Some(expr))
pub fn new_mixed(stmts: Vec<ast::Stmt>, expr: Option<P<Expr>>) -> BlockOrExpr {
BlockOrExpr(stmts, expr)
}
// Converts it into a block.
@ -344,7 +335,14 @@ impl BlockOrExpr {
None => cx.expr_block(cx.block(span, vec![])),
Some(expr) => expr,
}
} else if self.0.len() == 1
&& let ast::StmtKind::Expr(expr) = &self.0[0].kind
&& self.1.is_none()
{
// There's only a single statement expression. Pull it out.
expr.clone()
} else {
// Multiple statements and/or expressions.
cx.expr_block(self.into_block(cx, span))
}
}
@ -455,7 +453,6 @@ impl<'a> TraitDef<'a> {
};
let container_id = cx.current_expansion.id.expn_data().parent.expect_local();
let always_copy = has_no_type_params && cx.resolver.has_derive_copy(container_id);
let use_temporaries = is_packed && always_copy;
let newitem = match item.kind {
ast::ItemKind::Struct(ref struct_def, ref generics) => self.expand_struct_def(
@ -464,11 +461,11 @@ impl<'a> TraitDef<'a> {
item.ident,
generics,
from_scratch,
use_temporaries,
is_packed,
always_copy,
),
ast::ItemKind::Enum(ref enum_def, ref generics) => {
// We ignore `use_temporaries` here, because
// We ignore `is_packed`/`always_copy` here, because
// `repr(packed)` enums cause an error later on.
//
// This can only cause further compilation errors
@ -484,8 +481,8 @@ impl<'a> TraitDef<'a> {
item.ident,
generics,
from_scratch,
use_temporaries,
is_packed,
always_copy,
)
} else {
cx.span_err(mitem.span, "this trait cannot be derived for unions");
@ -766,8 +763,8 @@ impl<'a> TraitDef<'a> {
type_ident: Ident,
generics: &Generics,
from_scratch: bool,
use_temporaries: bool,
is_packed: bool,
always_copy: bool,
) -> P<ast::Item> {
let field_tys: Vec<P<ast::Ty>> =
struct_def.fields().iter().map(|field| field.ty.clone()).collect();
@ -795,8 +792,8 @@ impl<'a> TraitDef<'a> {
type_ident,
&selflike_args,
&nonselflike_args,
use_temporaries,
is_packed,
always_copy,
)
};
@ -937,9 +934,7 @@ impl<'a> MethodDef<'a> {
match ty {
// Selflike (`&Self`) arguments only occur in non-static methods.
Ref(box Self_, _) if !self.is_static() => {
selflike_args.push(cx.expr_deref(span, arg_expr))
}
Ref(box Self_, _) if !self.is_static() => selflike_args.push(arg_expr),
Self_ => cx.span_bug(span, "`Self` in non-return position"),
_ => nonselflike_args.push(arg_expr),
}
@ -1008,7 +1003,7 @@ impl<'a> MethodDef<'a> {
/// ```
/// #[derive(PartialEq)]
/// # struct Dummy;
/// struct A { x: i32, y: i32 }
/// struct A { x: u8, y: u8 }
///
/// // equivalent to:
/// impl PartialEq for A {
@ -1018,11 +1013,27 @@ impl<'a> MethodDef<'a> {
/// }
/// ```
/// But if the struct is `repr(packed)`, we can't use something like
/// `&self.x` on a packed type (as required for e.g. `Debug` and `Hash`)
/// because that might cause an unaligned ref. So we use let-destructuring
/// instead.
/// `&self.x` because that might cause an unaligned ref. So for any trait
/// method that takes a reference, if the struct impls `Copy` then we use a
/// local block to force a copy:
/// ```
/// # struct A { x: i32, y: i32 }
/// # struct A { x: u8, y: u8 }
/// impl PartialEq for A {
/// fn eq(&self, other: &A) -> bool {
/// // Desugars to `{ self.x }.eq(&{ other.y }) && ...`
/// { self.x } == { other.y } && { self.y } == { other.y }
/// }
/// }
/// impl Hash for A {
/// fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) -> () {
/// ::core::hash::Hash::hash(&{ self.x }, state);
/// ::core::hash::Hash::hash(&{ self.y }, state)
/// }
/// }
/// ```
/// If the struct doesn't impl `Copy`, we use let-destructuring with `ref`:
/// ```
/// # struct A { x: u8, y: u8 }
/// impl PartialEq for A {
/// fn eq(&self, other: &A) -> bool {
/// let Self { x: ref __self_0_0, y: ref __self_0_1 } = *self;
@ -1031,6 +1042,8 @@ impl<'a> MethodDef<'a> {
/// }
/// }
/// ```
/// This latter case only works if the fields match the alignment required
/// by the `packed(N)` attribute. (We'll get errors later on if not.)
fn expand_struct_method_body<'b>(
&self,
cx: &mut ExtCtxt<'_>,
@ -1039,8 +1052,8 @@ impl<'a> MethodDef<'a> {
type_ident: Ident,
selflike_args: &[P<Expr>],
nonselflike_args: &[P<Expr>],
use_temporaries: bool,
is_packed: bool,
always_copy: bool,
) -> BlockOrExpr {
let span = trait_.span;
assert!(selflike_args.len() == 1 || selflike_args.len() == 2);
@ -1057,29 +1070,31 @@ impl<'a> MethodDef<'a> {
if !is_packed {
let selflike_fields =
trait_.create_struct_field_access_fields(cx, selflike_args, struct_def);
trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, false);
mk_body(cx, selflike_fields)
} else if always_copy {
let selflike_fields =
trait_.create_struct_field_access_fields(cx, selflike_args, struct_def, true);
mk_body(cx, selflike_fields)
} else {
// Neither packed nor copy. Need to use ref patterns.
let prefixes: Vec<_> =
(0..selflike_args.len()).map(|i| format!("__self_{}", i)).collect();
let addr_of = always_copy;
let selflike_fields =
trait_.create_struct_pattern_fields(cx, struct_def, &prefixes, use_temporaries);
trait_.create_struct_pattern_fields(cx, struct_def, &prefixes, addr_of);
let mut body = mk_body(cx, selflike_fields);
let struct_path = cx.path(span, vec![Ident::new(kw::SelfUpper, type_ident.span)]);
let patterns = trait_.create_struct_patterns(
cx,
struct_path,
struct_def,
&prefixes,
ast::Mutability::Not,
use_temporaries,
);
let use_ref_pat = is_packed && !always_copy;
let patterns =
trait_.create_struct_patterns(cx, struct_path, struct_def, &prefixes, use_ref_pat);
// Do the let-destructuring.
let mut stmts: Vec<_> = iter::zip(selflike_args, patterns)
.map(|(selflike_arg_expr, pat)| {
cx.stmt_let_pat(span, pat, selflike_arg_expr.clone())
let selflike_arg_expr = cx.expr_deref(span, selflike_arg_expr.clone());
cx.stmt_let_pat(span, pat, selflike_arg_expr)
})
.collect();
stmts.extend(std::mem::take(&mut body.0));
@ -1119,82 +1134,128 @@ impl<'a> MethodDef<'a> {
/// impl ::core::cmp::PartialEq for A {
/// #[inline]
/// fn eq(&self, other: &A) -> bool {
/// {
/// let __self_vi = ::core::intrinsics::discriminant_value(&*self);
/// let __arg_1_vi = ::core::intrinsics::discriminant_value(&*other);
/// if true && __self_vi == __arg_1_vi {
/// match (&*self, &*other) {
/// (&A::A2(ref __self_0), &A::A2(ref __arg_1_0)) =>
/// (*__self_0) == (*__arg_1_0),
/// _ => true,
/// }
/// } else {
/// false // catch-all handler
/// let __self_tag = ::core::intrinsics::discriminant_value(self);
/// let __arg1_tag = ::core::intrinsics::discriminant_value(other);
/// __self_tag == __arg1_tag &&
/// match (self, other) {
/// (A::A2(__self_0), A::A2(__arg1_0)) =>
/// *__self_0 == *__arg1_0,
/// _ => true,
/// }
/// }
/// }
/// }
/// ```
/// Creates a match for a tuple of all `selflike_args`, where either all
/// variants match, or it falls into a catch-all for when one variant
/// does not match.
///
/// There are N + 1 cases because is a case for each of the N
/// variants where all of the variants match, and one catch-all for
/// when one does not match.
///
/// As an optimization we generate code which checks whether all variants
/// match first which makes llvm see that C-like enums can be compiled into
/// a simple equality check (for PartialEq).
///
/// The catch-all handler is provided access the variant index values
/// for each of the selflike_args, carried in precomputed variables.
/// Creates a tag check combined with a match for a tuple of all
/// `selflike_args`, with an arm for each variant with fields, possibly an
/// arm for each fieldless variant (if `!unify_fieldless_variants` is not
/// true), and possibly a default arm.
fn expand_enum_method_body<'b>(
&self,
cx: &mut ExtCtxt<'_>,
trait_: &TraitDef<'b>,
enum_def: &'b EnumDef,
type_ident: Ident,
mut selflike_args: Vec<P<Expr>>,
selflike_args: Vec<P<Expr>>,
nonselflike_args: &[P<Expr>],
) -> BlockOrExpr {
let span = trait_.span;
let variants = &enum_def.variants;
// Traits that unify fieldless variants always use the tag(s).
let uses_tags = self.unify_fieldless_variants;
// There is no sensible code to be generated for *any* deriving on a
// zero-variant enum. So we just generate a failing expression.
if variants.is_empty() {
return BlockOrExpr(vec![], Some(deriving::call_unreachable(cx, span)));
}
let prefixes = iter::once("__self".to_string())
.chain(
selflike_args
.iter()
.enumerate()
.skip(1)
.map(|(arg_count, _selflike_arg)| format!("__arg_{}", arg_count)),
.map(|(arg_count, _selflike_arg)| format!("__arg{}", arg_count)),
)
.collect::<Vec<String>>();
// The `vi_idents` will be bound, solely in the catch-all, to
// a series of let statements mapping each selflike_arg to an int
// value corresponding to its discriminant.
let vi_idents = prefixes
.iter()
.map(|name| {
let vi_suffix = format!("{}_vi", name);
Ident::from_str_and_span(&vi_suffix, span)
})
.collect::<Vec<Ident>>();
// Build a series of let statements mapping each selflike_arg
// to its discriminant value.
//
// e.g. for `PartialEq::eq` builds two statements:
// ```
// let __self_tag = ::core::intrinsics::discriminant_value(self);
// let __arg1_tag = ::core::intrinsics::discriminant_value(other);
// ```
let get_tag_pieces = |cx: &ExtCtxt<'_>| {
let tag_idents: Vec<_> = prefixes
.iter()
.map(|name| Ident::from_str_and_span(&format!("{}_tag", name), span))
.collect();
// Builds, via callback to call_substructure_method, the
// delegated expression that handles the catch-all case,
// using `__variants_tuple` to drive logic if necessary.
let catch_all_substructure = EnumNonMatchingCollapsed(&vi_idents);
let mut tag_exprs: Vec<_> = tag_idents
.iter()
.map(|&ident| cx.expr_addr_of(span, cx.expr_ident(span, ident)))
.collect();
let first_fieldless = variants.iter().find(|v| v.data.fields().is_empty());
let self_expr = tag_exprs.remove(0);
let other_selflike_exprs = tag_exprs;
let tag_field = FieldInfo { span, name: None, self_expr, other_selflike_exprs };
let tag_let_stmts: Vec<_> = iter::zip(&tag_idents, &selflike_args)
.map(|(&ident, selflike_arg)| {
let variant_value = deriving::call_intrinsic(
cx,
span,
sym::discriminant_value,
vec![selflike_arg.clone()],
);
cx.stmt_let(span, false, ident, variant_value)
})
.collect();
(tag_field, tag_let_stmts)
};
// There are some special cases involving fieldless enums where no
// match is necessary.
let all_fieldless = variants.iter().all(|v| v.data.fields().is_empty());
if all_fieldless {
if uses_tags && variants.len() > 1 {
// If the type is fieldless and the trait uses the tag and
// there are multiple variants, we need just an operation on
// the tag(s).
let (tag_field, mut tag_let_stmts) = get_tag_pieces(cx);
let mut tag_check = self.call_substructure_method(
cx,
trait_,
type_ident,
nonselflike_args,
&EnumTag(tag_field, None),
);
tag_let_stmts.append(&mut tag_check.0);
return BlockOrExpr(tag_let_stmts, tag_check.1);
}
if variants.len() == 1 {
// If there is a single variant, we don't need an operation on
// the tag(s). Just use the most degenerate result.
return self.call_substructure_method(
cx,
trait_,
type_ident,
nonselflike_args,
&EnumMatching(0, 1, &variants[0], Vec::new()),
);
};
}
// These arms are of the form:
// (Variant1, Variant1, ...) => Body1
// (Variant2, Variant2, ...) => Body2
// ...
// where each tuple has length = selflike_args.len()
let mut match_arms: Vec<ast::Arm> = variants
.iter()
.enumerate()
@ -1203,30 +1264,22 @@ impl<'a> MethodDef<'a> {
// A single arm has form (&VariantK, &VariantK, ...) => BodyK
// (see "Final wrinkle" note below for why.)
let use_temporaries = false; // enums can't be repr(packed)
let fields = trait_.create_struct_pattern_fields(
cx,
&variant.data,
&prefixes,
use_temporaries,
);
let addr_of = false; // because enums can't be repr(packed)
let fields =
trait_.create_struct_pattern_fields(cx, &variant.data, &prefixes, addr_of);
let sp = variant.span.with_ctxt(trait_.span.ctxt());
let variant_path = cx.path(sp, vec![type_ident, variant.ident]);
let mut subpats: Vec<_> = trait_
.create_struct_patterns(
cx,
variant_path,
&variant.data,
&prefixes,
ast::Mutability::Not,
use_temporaries,
)
.into_iter()
.map(|p| cx.pat(span, PatKind::Ref(p, ast::Mutability::Not)))
.collect();
let use_ref_pat = false; // because enums can't be repr(packed)
let mut subpats: Vec<_> = trait_.create_struct_patterns(
cx,
variant_path,
&variant.data,
&prefixes,
use_ref_pat,
);
// Here is the pat = `(&VariantK, &VariantK, ...)`
// `(VariantK, VariantK, ...)` or just `VariantK`.
let single_pat = if subpats.len() == 1 {
subpats.pop().unwrap()
} else {
@ -1256,27 +1309,28 @@ impl<'a> MethodDef<'a> {
})
.collect();
// Add a default arm to the match, if necessary.
let first_fieldless = variants.iter().find(|v| v.data.fields().is_empty());
let default = match first_fieldless {
Some(v) if self.unify_fieldless_variants => {
// We need a default case that handles the fieldless variants.
// The index and actual variant aren't meaningful in this case,
// so just use whatever
let substructure = EnumMatching(0, variants.len(), v, Vec::new());
// We need a default case that handles all the fieldless
// variants. The index and actual variant aren't meaningful in
// this case, so just use dummy values.
Some(
self.call_substructure_method(
cx,
trait_,
type_ident,
nonselflike_args,
&substructure,
&EnumMatching(0, variants.len(), v, Vec::new()),
)
.into_expr(cx, span),
)
}
_ if variants.len() > 1 && selflike_args.len() > 1 => {
// Since we know that all the arguments will match if we reach
// Because we know that all the arguments will match if we reach
// the match expression we add the unreachable intrinsics as the
// result of the catch all which should help llvm in optimizing it
// result of the default which should help llvm in optimizing it.
Some(deriving::call_unreachable(cx, span))
}
_ => None,
@ -1285,111 +1339,41 @@ impl<'a> MethodDef<'a> {
match_arms.push(cx.arm(span, cx.pat_wild(span), arm));
}
// We will usually need the catch-all after matching the
// tuples `(VariantK, VariantK, ...)` for each VariantK of the
// enum. But:
//
// * when there is only one Self arg, the arms above suffice
// (and the deriving we call back into may not be prepared to
// handle EnumNonMatchCollapsed), and,
//
// * when the enum has only one variant, the single arm that
// is already present always suffices.
//
// * In either of the two cases above, if we *did* add a
// catch-all `_` match, it would trigger the
// unreachable-pattern error.
//
if variants.len() > 1 && selflike_args.len() > 1 {
// Build a series of let statements mapping each selflike_arg
// to its discriminant value.
//
// i.e., for `enum E<T> { A, B(1), C(T, T) }`, and a deriving
// with three Self args, builds three statements:
// ```
// let __self_vi = std::intrinsics::discriminant_value(&self);
// let __arg_1_vi = std::intrinsics::discriminant_value(&arg1);
// let __arg_2_vi = std::intrinsics::discriminant_value(&arg2);
// ```
let mut index_let_stmts: Vec<ast::Stmt> = Vec::with_capacity(vi_idents.len() + 1);
// We also build an expression which checks whether all discriminants are equal:
// `__self_vi == __arg_1_vi && __self_vi == __arg_2_vi && ...`
let mut discriminant_test = cx.expr_bool(span, true);
for (i, (&ident, selflike_arg)) in iter::zip(&vi_idents, &selflike_args).enumerate() {
let selflike_addr = cx.expr_addr_of(span, selflike_arg.clone());
let variant_value = deriving::call_intrinsic(
cx,
span,
sym::discriminant_value,
vec![selflike_addr],
);
let let_stmt = cx.stmt_let(span, false, ident, variant_value);
index_let_stmts.push(let_stmt);
if i > 0 {
let id0 = cx.expr_ident(span, vi_idents[0]);
let id = cx.expr_ident(span, ident);
let test = cx.expr_binary(span, BinOpKind::Eq, id0, id);
discriminant_test = if i == 1 {
test
} else {
cx.expr_binary(span, BinOpKind::And, discriminant_test, test)
};
}
}
let arm_expr = self
.call_substructure_method(
cx,
trait_,
type_ident,
nonselflike_args,
&catch_all_substructure,
)
.into_expr(cx, span);
// Final wrinkle: the selflike_args are expressions that deref
// down to desired places, but we cannot actually deref
// them when they are fed as r-values into a tuple
// expression; here add a layer of borrowing, turning
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
selflike_args.map_in_place(|selflike_arg| cx.expr_addr_of(span, selflike_arg));
let match_arg = cx.expr(span, ast::ExprKind::Tup(selflike_args));
// Lastly we create an expression which branches on all discriminants being equal
// if discriminant_test {
// match (...) {
// (Variant1, Variant1, ...) => Body1
// (Variant2, Variant2, ...) => Body2,
// ...
// _ => ::core::intrinsics::unreachable()
// }
// }
// else {
// <delegated expression referring to __self_vi, et al.>
// }
let all_match = cx.expr_match(span, match_arg, match_arms);
let arm_expr = cx.expr_if(span, discriminant_test, all_match, Some(arm_expr));
BlockOrExpr(index_let_stmts, Some(arm_expr))
} else if variants.is_empty() {
// There is no sensible code to be generated for *any* deriving on
// a zero-variant enum. So we just generate a failing expression
// for the zero variant case.
BlockOrExpr(vec![], Some(deriving::call_unreachable(cx, span)))
} else {
// Final wrinkle: the selflike_args are expressions that deref
// down to desired places, but we cannot actually deref
// them when they are fed as r-values into a tuple
// expression; here add a layer of borrowing, turning
// `(*self, *__arg_0, ...)` into `(&*self, &*__arg_0, ...)`.
selflike_args.map_in_place(|selflike_arg| cx.expr_addr_of(span, selflike_arg));
// Create a match expression with one arm per discriminant plus
// possibly a default arm, e.g.:
// match (self, other) {
// (Variant1, Variant1, ...) => Body1
// (Variant2, Variant2, ...) => Body2,
// ...
// _ => ::core::intrinsics::unreachable()
// }
let get_match_expr = |mut selflike_args: Vec<P<Expr>>| {
let match_arg = if selflike_args.len() == 1 {
selflike_args.pop().unwrap()
} else {
cx.expr(span, ast::ExprKind::Tup(selflike_args))
};
BlockOrExpr(vec![], Some(cx.expr_match(span, match_arg, match_arms)))
cx.expr_match(span, match_arg, match_arms)
};
// If the trait uses the tag and there are multiple variants, we need
// to add a tag check operation before the match. Otherwise, the match
// is enough.
if uses_tags && variants.len() > 1 {
let (tag_field, mut tag_let_stmts) = get_tag_pieces(cx);
// Combine a tag check with the match.
let mut tag_check_plus_match = self.call_substructure_method(
cx,
trait_,
type_ident,
nonselflike_args,
&EnumTag(tag_field, Some(get_match_expr(selflike_args))),
);
tag_let_stmts.append(&mut tag_check_plus_match.0);
BlockOrExpr(tag_let_stmts, tag_check_plus_match.1)
} else {
BlockOrExpr(vec![], Some(get_match_expr(selflike_args)))
}
}
@ -1453,8 +1437,7 @@ impl<'a> TraitDef<'a> {
struct_path: ast::Path,
struct_def: &'a VariantData,
prefixes: &[String],
mutbl: ast::Mutability,
use_temporaries: bool,
use_ref_pat: bool,
) -> Vec<P<ast::Pat>> {
prefixes
.iter()
@ -1462,10 +1445,10 @@ impl<'a> TraitDef<'a> {
let pieces_iter =
struct_def.fields().iter().enumerate().map(|(i, struct_field)| {
let sp = struct_field.span.with_ctxt(self.span.ctxt());
let binding_mode = if use_temporaries {
ast::BindingMode::ByValue(ast::Mutability::Not)
let binding_mode = if use_ref_pat {
ast::BindingMode::ByRef(ast::Mutability::Not)
} else {
ast::BindingMode::ByRef(mutbl)
ast::BindingMode::ByValue(ast::Mutability::Not)
};
let ident = self.mk_pattern_ident(prefix, i);
let path = ident.with_span_pos(sp);
@ -1544,7 +1527,7 @@ impl<'a> TraitDef<'a> {
cx: &mut ExtCtxt<'_>,
struct_def: &'a VariantData,
prefixes: &[String],
use_temporaries: bool,
addr_of: bool,
) -> Vec<FieldInfo> {
self.create_fields(struct_def, |i, _struct_field, sp| {
prefixes
@ -1552,7 +1535,7 @@ impl<'a> TraitDef<'a> {
.map(|prefix| {
let ident = self.mk_pattern_ident(prefix, i);
let expr = cx.expr_path(cx.path_ident(sp, ident));
if use_temporaries { expr } else { cx.expr_deref(sp, expr) }
if addr_of { cx.expr_addr_of(sp, expr) } else { expr }
})
.collect()
})
@ -1563,20 +1546,17 @@ impl<'a> TraitDef<'a> {
cx: &mut ExtCtxt<'_>,
selflike_args: &[P<Expr>],
struct_def: &'a VariantData,
copy: bool,
) -> Vec<FieldInfo> {
self.create_fields(struct_def, |i, struct_field, sp| {
selflike_args
.iter()
.map(|mut selflike_arg| {
// We don't the need the deref, if there is one.
if let ast::ExprKind::Unary(ast::UnOp::Deref, inner) = &selflike_arg.kind {
selflike_arg = inner;
}
// Note: we must use `struct_field.span` rather than `span` in the
.map(|selflike_arg| {
// Note: we must use `struct_field.span` rather than `sp` in the
// `unwrap_or_else` case otherwise the hygiene is wrong and we get
// "field `0` of struct `Point` is private" errors on tuple
// structs.
cx.expr(
let mut field_expr = cx.expr(
sp,
ast::ExprKind::Field(
selflike_arg.clone(),
@ -1584,7 +1564,13 @@ impl<'a> TraitDef<'a> {
Ident::from_str_and_span(&i.to_string(), struct_field.span)
}),
),
)
);
if copy {
field_expr = cx.expr_block(
cx.block(struct_field.span, vec![cx.stmt_expr(field_expr)]),
);
}
cx.expr_addr_of(sp, field_expr)
})
.collect()
})
@ -1605,11 +1591,6 @@ pub enum CsFold<'a> {
// The fallback case for a struct or enum variant with no fields.
Fieldless,
/// The fallback case for non-matching enum variants. The slice is the
/// identifiers holding the variant index value for each of the `Self`
/// arguments.
EnumNonMatching(Span, &'a [Ident]),
}
/// Folds over fields, combining the expressions for each field in a sequence.
@ -1624,8 +1605,8 @@ pub fn cs_fold<F>(
where
F: FnMut(&mut ExtCtxt<'_>, CsFold<'_>) -> P<Expr>,
{
match *substructure.fields {
EnumMatching(.., ref all_fields) | Struct(_, ref all_fields) => {
match substructure.fields {
EnumMatching(.., all_fields) | Struct(_, all_fields) => {
if all_fields.is_empty() {
return f(cx, CsFold::Fieldless);
}
@ -1649,7 +1630,18 @@ where
rest.iter().rfold(base_expr, op)
}
}
EnumNonMatchingCollapsed(tuple) => f(cx, CsFold::EnumNonMatching(trait_span, tuple)),
EnumTag(tag_field, match_expr) => {
let tag_check_expr = f(cx, CsFold::Single(tag_field));
if let Some(match_expr) = match_expr {
if use_foldl {
f(cx, CsFold::Combine(trait_span, tag_check_expr, match_expr.clone()))
} else {
f(cx, CsFold::Combine(trait_span, match_expr.clone(), tag_check_expr))
}
} else {
tag_check_expr
}
}
StaticEnum(..) | StaticStruct(..) => cx.span_bug(trait_span, "static function in `derive`"),
}
}

View file

@ -196,9 +196,8 @@ impl Bounds {
}
pub fn get_explicit_self(cx: &ExtCtxt<'_>, span: Span) -> (P<Expr>, ast::ExplicitSelf) {
// this constructs a fresh `self` path
// This constructs a fresh `self` path.
let self_path = cx.expr_self(span);
let self_ty = respan(span, SelfKind::Region(None, ast::Mutability::Not));
let self_expr = cx.expr_deref(span, self_path);
(self_expr, self_ty)
(self_path, self_ty)
}

View file

@ -1,6 +1,6 @@
use crate::deriving::generic::ty::*;
use crate::deriving::generic::*;
use crate::deriving::{self, path_std, pathvec_std};
use crate::deriving::{path_std, pathvec_std};
use rustc_ast::{MetaItem, Mutability};
use rustc_expand::base::{Annotatable, ExtCtxt};
@ -52,39 +52,29 @@ fn hash_substructure(
let [state_expr] = substr.nonselflike_args else {
cx.span_bug(trait_span, "incorrect number of arguments in `derive(Hash)`");
};
let call_hash = |span, thing_expr| {
let call_hash = |span, expr| {
let hash_path = {
let strs = cx.std_path(&[sym::hash, sym::Hash, sym::hash]);
cx.expr_path(cx.path_global(span, strs))
};
let ref_thing = cx.expr_addr_of(span, thing_expr);
let expr = cx.expr_call(span, hash_path, vec![ref_thing, state_expr.clone()]);
let expr = cx.expr_call(span, hash_path, vec![expr, state_expr.clone()]);
cx.stmt_expr(expr)
};
let mut stmts = Vec::new();
let fields = match substr.fields {
Struct(_, fs) | EnumMatching(_, 1, .., fs) => fs,
EnumMatching(.., fs) => {
let variant_value = deriving::call_intrinsic(
cx,
trait_span,
sym::discriminant_value,
vec![cx.expr_self(trait_span)],
);
stmts.push(call_hash(trait_span, variant_value));
fs
let (stmts, match_expr) = match substr.fields {
Struct(_, fields) | EnumMatching(.., fields) => {
let stmts =
fields.iter().map(|field| call_hash(field.span, field.self_expr.clone())).collect();
(stmts, None)
}
EnumTag(tag_field, match_expr) => {
assert!(tag_field.other_selflike_exprs.is_empty());
let stmts = vec![call_hash(tag_field.span, tag_field.self_expr.clone())];
(stmts, match_expr.clone())
}
_ => cx.span_bug(trait_span, "impossible substructure in `derive(Hash)`"),
};
stmts.extend(
fields
.iter()
.map(|FieldInfo { ref self_expr, span, .. }| call_hash(*span, self_expr.clone())),
);
BlockOrExpr::new_stmts(stmts)
BlockOrExpr::new_mixed(stmts, match_expr)
}