Skip to content

Commit 1f3a747

Browse files
committed
Implement #[derive(From)]
1 parent c0839ea commit 1f3a747

File tree

10 files changed

+530
-6
lines changed

10 files changed

+530
-6
lines changed

compiler/rustc_builtin_macros/messages.ftl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,15 @@ builtin_macros_format_unused_args = multiple unused formatting arguments
222222
223223
builtin_macros_format_use_positional = consider using a positional formatting argument instead
224224
225+
builtin_macros_derive_from_wrong_target = `#[derive(From)]` used on {$kind}
226+
227+
builtin_macros_derive_from_wrong_field_count = `#[derive(From)]` used on a struct with {$multiple_fields ->
228+
[true] multiple fields
229+
*[false] no fields
230+
}
231+
232+
builtin_macros_derive_from_usage_note = `#[derive(From)]` can only be used on structs with exactly one field
233+
225234
builtin_macros_multiple_default_attrs = multiple `#[default]` attributes
226235
.note = only one `#[default]` attribute is needed
227236
.label = `#[default]` used here
Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,132 @@
11
use rustc_ast as ast;
2-
use rustc_expand::base::{Annotatable, ExtCtxt};
3-
use rustc_span::{Span, sym};
2+
use rustc_ast::{ItemKind, VariantData};
3+
use rustc_errors::MultiSpan;
4+
use rustc_expand::base::{Annotatable, DummyResult, ExtCtxt};
5+
use rustc_span::{Ident, Span, kw, sym};
6+
use thin_vec::thin_vec;
47

8+
use crate::deriving::generic::ty::{Bounds, Path, PathKind, Ty};
9+
use crate::deriving::generic::{
10+
BlockOrExpr, FieldlessVariantsStrategy, MethodDef, SubstructureFields, TraitDef,
11+
combine_substructure,
12+
};
13+
use crate::deriving::pathvec_std;
14+
use crate::errors;
15+
16+
/// Generate an implementation of the `From` trait, provided that `item`
17+
/// is a struct or a tuple struct with exactly one field.
518
pub(crate) fn expand_deriving_from(
619
cx: &ExtCtxt<'_>,
720
span: Span,
821
mitem: &ast::MetaItem,
9-
item: &Annotatable,
22+
annotatable: &Annotatable,
1023
push: &mut dyn FnMut(Annotatable),
1124
is_const: bool,
1225
) {
26+
let Annotatable::Item(item) = &annotatable else {
27+
cx.dcx().bug("derive(From) used on something else than an item");
28+
};
29+
30+
// #[derive(From)] is currently usable only on structs with exactly one field.
31+
let field = if let ItemKind::Struct(_, _, data) = &item.kind
32+
&& let [field] = data.fields()
33+
{
34+
Some(field.clone())
35+
} else {
36+
None
37+
};
38+
39+
let from_type = match &field {
40+
Some(field) => Ty::AstTy(field.ty.clone()),
41+
// We don't have a type to put into From<...> if we don't have a single field, so just put
42+
// unit there.
43+
None => Ty::Unit,
44+
};
45+
let path =
46+
Path::new_(pathvec_std!(convert::From), vec![Box::new(from_type.clone())], PathKind::Std);
47+
48+
// Generate code like this:
49+
//
50+
// struct S(u32);
51+
// #[automatically_derived]
52+
// impl ::core::convert::From<u32> for S {
53+
// #[inline]
54+
// fn from(value: u32) -> S {
55+
// Self(value)
56+
// }
57+
// }
58+
let from_trait_def = TraitDef {
59+
span,
60+
path,
61+
skip_path_as_bound: true,
62+
needs_copy_as_bound_if_packed: false,
63+
additional_bounds: Vec::new(),
64+
supports_unions: false,
65+
methods: vec![MethodDef {
66+
name: sym::from,
67+
generics: Bounds { bounds: vec![] },
68+
explicit_self: false,
69+
nonself_args: vec![(from_type, sym::value)],
70+
ret_ty: Ty::Self_,
71+
attributes: thin_vec![cx.attr_word(sym::inline, span)],
72+
fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
73+
combine_substructure: combine_substructure(Box::new(|cx, span, substructure| {
74+
let Some(field) = &field else {
75+
let item_span = item.kind.ident().map(|ident| ident.span).unwrap_or(item.span);
76+
let err_span = MultiSpan::from_spans(vec![span, item_span]);
77+
let error = match &item.kind {
78+
ItemKind::Struct(_, _, data) => {
79+
cx.dcx().emit_err(errors::DeriveFromWrongFieldCount {
80+
span: err_span,
81+
multiple_fields: data.fields().len() > 1,
82+
})
83+
}
84+
ItemKind::Enum(_, _, _) | ItemKind::Union(_, _, _) => {
85+
cx.dcx().emit_err(errors::DeriveFromWrongTarget {
86+
span: err_span,
87+
kind: &format!("{} {}", item.kind.article(), item.kind.descr()),
88+
})
89+
}
90+
_ => cx.dcx().bug("Invalid derive(From) ADT input"),
91+
};
92+
93+
return BlockOrExpr::new_expr(DummyResult::raw_expr(span, Some(error)));
94+
};
95+
96+
let self_kw = Ident::new(kw::SelfUpper, span);
97+
let expr: Box<ast::Expr> = match substructure.fields {
98+
SubstructureFields::StaticStruct(variant, _) => match variant {
99+
// Self {
100+
// field: value
101+
// }
102+
VariantData::Struct { .. } => cx.expr_struct_ident(
103+
span,
104+
self_kw,
105+
thin_vec![cx.field_imm(
106+
span,
107+
field.ident.unwrap(),
108+
cx.expr_ident(span, Ident::new(sym::value, span))
109+
)],
110+
),
111+
// Self(value)
112+
VariantData::Tuple(_, _) => cx.expr_call_ident(
113+
span,
114+
self_kw,
115+
thin_vec![cx.expr_ident(span, Ident::new(sym::value, span))],
116+
),
117+
variant => {
118+
cx.dcx().bug(format!("Invalid derive(From) ADT variant: {variant:?}"));
119+
}
120+
},
121+
_ => cx.dcx().bug("Invalid derive(From) ADT input"),
122+
};
123+
BlockOrExpr::new_expr(expr)
124+
})),
125+
}],
126+
associated_types: Vec::new(),
127+
is_const,
128+
is_staged_api_crate: cx.ecfg.features.staged_api(),
129+
};
130+
131+
from_trait_def.expand(cx, mitem, annotatable, push);
13132
}

compiler/rustc_builtin_macros/src/deriving/generic/ty.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! when specifying impls to be derived.
33
44
pub(crate) use Ty::*;
5-
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
5+
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind, TyKind};
66
use rustc_expand::base::ExtCtxt;
77
use rustc_span::source_map::respan;
88
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw};
@@ -65,7 +65,7 @@ impl Path {
6565
}
6666
}
6767

68-
/// A type. Supports pointers, Self, and literals.
68+
/// A type. Supports pointers, Self, literals, unit or an arbitrary AST path.
6969
#[derive(Clone)]
7070
pub(crate) enum Ty {
7171
Self_,
@@ -76,6 +76,8 @@ pub(crate) enum Ty {
7676
Path(Path),
7777
/// For () return types.
7878
Unit,
79+
/// An arbitrary type.
80+
AstTy(Box<ast::Ty>),
7981
}
8082

8183
pub(crate) fn self_ref() -> Ty {
@@ -101,6 +103,7 @@ impl Ty {
101103
let ty = ast::TyKind::Tup(ThinVec::new());
102104
cx.ty(span, ty)
103105
}
106+
AstTy(ty) => ty.clone(),
104107
}
105108
}
106109

@@ -132,6 +135,10 @@ impl Ty {
132135
cx.path_all(span, false, vec![self_ty], params)
133136
}
134137
Path(p) => p.to_path(cx, span, self_ty, generics),
138+
AstTy(ty) => match &ty.kind {
139+
TyKind::Path(_, path) => path.clone(),
140+
_ => cx.dcx().span_bug(span, "non-path in a path in generic `derive`"),
141+
},
135142
Ref(..) => cx.dcx().span_bug(span, "ref in a path in generic `derive`"),
136143
Unit => cx.dcx().span_bug(span, "unit in a path in generic `derive`"),
137144
}

compiler/rustc_builtin_macros/src/errors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,24 @@ pub(crate) struct DefaultHasArg {
446446
pub(crate) span: Span,
447447
}
448448

449+
#[derive(Diagnostic)]
450+
#[diag(builtin_macros_derive_from_wrong_target)]
451+
#[note(builtin_macros_derive_from_usage_note)]
452+
pub(crate) struct DeriveFromWrongTarget<'a> {
453+
#[primary_span]
454+
pub(crate) span: MultiSpan,
455+
pub(crate) kind: &'a str,
456+
}
457+
458+
#[derive(Diagnostic)]
459+
#[diag(builtin_macros_derive_from_wrong_field_count)]
460+
#[note(builtin_macros_derive_from_usage_note)]
461+
pub(crate) struct DeriveFromWrongFieldCount {
462+
#[primary_span]
463+
pub(crate) span: MultiSpan,
464+
pub(crate) multiple_fields: bool,
465+
}
466+
449467
#[derive(Diagnostic)]
450468
#[diag(builtin_macros_derive_macro_call)]
451469
pub(crate) struct DeriveMacroCall {

compiler/rustc_span/src/symbol.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@ symbols! {
392392
__D,
393393
__H,
394394
__S,
395+
__T,
395396
__awaitee,
396397
__try_var,
397398
_t,
@@ -745,6 +746,7 @@ symbols! {
745746
contracts_ensures,
746747
contracts_internals,
747748
contracts_requires,
749+
convert,
748750
convert_identity,
749751
copy,
750752
copy_closures,
@@ -2331,6 +2333,7 @@ symbols! {
23312333
va_start,
23322334
val,
23332335
validity,
2336+
value,
23342337
values,
23352338
var,
23362339
variant_count,

tests/ui/deriving/deriving-all-codegen.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#![crate_type = "lib"]
1717
#![allow(dead_code)]
1818
#![allow(deprecated)]
19+
#![feature(derive_from)]
1920

2021
// Empty struct.
2122
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
@@ -38,6 +39,14 @@ struct PackedPoint {
3839
y: u32,
3940
}
4041

42+
#[derive(Clone, Copy, Debug, Default, From, Hash, PartialEq, Eq, PartialOrd, Ord)]
43+
struct TupleSingleField(u32);
44+
45+
#[derive(Clone, Copy, Debug, Default, From, Hash, PartialEq, Eq, PartialOrd, Ord)]
46+
struct SingleField {
47+
foo: bool,
48+
}
49+
4150
// A large struct. Note: because this derives `Copy`, it gets the simple
4251
// `clone` implemention that just does `*self`.
4352
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
@@ -86,7 +95,7 @@ struct PackedManualCopy(u32);
8695
impl Copy for PackedManualCopy {}
8796

8897
// A struct with an unsized field. Some derives are not usable in this case.
89-
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
98+
#[derive(Debug, From, Hash, PartialEq, Eq, PartialOrd, Ord)]
9099
struct Unsized([u32]);
91100

92101
trait Trait {

0 commit comments

Comments
 (0)