Skip to content

Commit 60eac40

Browse files
committed
Implement #[derive(From)]
1 parent 4f65529 commit 60eac40

File tree

8 files changed

+350
-3
lines changed

8 files changed

+350
-3
lines changed

compiler/rustc_builtin_macros/messages.ftl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ 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_from_wrong_target = `#[derive(From)]` can only be used on structs with a single field
226+
225227
builtin_macros_multiple_default_attrs = multiple `#[default]` attributes
226228
.note = only one `#[default]` attribute is needed
227229
.label = `#[default]` used here
Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
use rustc_ast as ast;
2+
use rustc_ast::{FieldDef, Item, ItemKind, VariantData};
23
use rustc_expand::base::{Annotatable, ExtCtxt};
3-
use rustc_span::{Span, sym};
4+
use rustc_span::{Ident, Span, kw, sym};
5+
use thin_vec::thin_vec;
46

7+
use crate::deriving::generic::ty::{Bounds, Path, PathKind, Ty};
8+
use crate::deriving::generic::{
9+
BlockOrExpr, FieldlessVariantsStrategy, MethodDef, SubstructureFields, TraitDef,
10+
combine_substructure,
11+
};
12+
use crate::deriving::pathvec_std;
13+
use crate::errors;
14+
15+
/// Generate an implementation of the `From` trait, provided that `item`
16+
/// is a struct or a tuple struct with exactly one field.
517
pub(crate) fn expand_deriving_from(
618
cx: &ExtCtxt<'_>,
719
span: Span,
@@ -10,4 +22,99 @@ pub(crate) fn expand_deriving_from(
1022
push: &mut dyn FnMut(Annotatable),
1123
is_const: bool,
1224
) {
25+
let mut visitor = ExtractNonSingleFieldStruct { cx, field: None };
26+
item.visit_with(&mut visitor);
27+
28+
// Make sure that the derive is only invoked on single-field [tuple] structs.
29+
// From this point below, we know that there is exactly one field.
30+
let Some(field) = visitor.field else { return };
31+
32+
let path = Path::new_(
33+
pathvec_std!(convert::From),
34+
vec![Box::new(Ty::AstTy(field.ty.clone()))],
35+
PathKind::Std,
36+
);
37+
38+
// Generate code like this:
39+
//
40+
// struct S(u32);
41+
// #[automatically_derived]
42+
// impl ::core::convert::From<u32> for S {
43+
// #[inline]
44+
// fn from(value: u32) -> S {
45+
// Self(value)
46+
// }
47+
// }
48+
let from_trait_def = TraitDef {
49+
span,
50+
path,
51+
skip_path_as_bound: true,
52+
needs_copy_as_bound_if_packed: false,
53+
additional_bounds: Vec::new(),
54+
supports_unions: false,
55+
methods: vec![MethodDef {
56+
name: sym::from,
57+
generics: Bounds { bounds: vec![] },
58+
explicit_self: false,
59+
nonself_args: vec![(Ty::AstTy(field.ty), sym::value)],
60+
ret_ty: Ty::Self_,
61+
attributes: thin_vec![cx.attr_word(sym::inline, span)],
62+
fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
63+
combine_substructure: combine_substructure(Box::new(|cx, span, substructure| {
64+
let self_kw = Ident::new(kw::SelfUpper, span);
65+
let expr: Box<ast::Expr> = match substructure.fields {
66+
SubstructureFields::StaticStruct(variant, _) => match variant {
67+
// Self {
68+
// field: value
69+
// }
70+
VariantData::Struct { .. } => cx.expr_struct_ident(
71+
span,
72+
self_kw,
73+
thin_vec![cx.field_imm(
74+
span,
75+
field.ident.unwrap(),
76+
cx.expr_ident(span, Ident::new(sym::value, span))
77+
)],
78+
),
79+
// Self(value)
80+
VariantData::Tuple(_, _) => cx.expr_call_ident(
81+
span,
82+
self_kw,
83+
thin_vec![cx.expr_ident(span, Ident::new(sym::value, span))],
84+
),
85+
variant => {
86+
cx.dcx().bug(format!("Invalid derive(From) ADT variant: {variant:?}"));
87+
}
88+
},
89+
_ => cx.dcx().bug("Invalid derive(From) ADT input"),
90+
};
91+
BlockOrExpr::new_expr(expr)
92+
})),
93+
}],
94+
associated_types: Vec::new(),
95+
is_const,
96+
is_staged_api_crate: cx.ecfg.features.staged_api(),
97+
};
98+
99+
from_trait_def.expand(cx, mitem, item, push);
100+
}
101+
102+
struct ExtractNonSingleFieldStruct<'a, 'b> {
103+
cx: &'a ExtCtxt<'b>,
104+
field: Option<FieldDef>,
105+
}
106+
107+
impl<'a, 'b> rustc_ast::visit::Visitor<'a> for ExtractNonSingleFieldStruct<'a, 'b> {
108+
fn visit_item(&mut self, item: &'a Item) -> Self::Result {
109+
match &item.kind {
110+
ItemKind::Struct(_, _, data) => match data.fields() {
111+
[field] => self.field = Some(field.clone()),
112+
_ => {}
113+
},
114+
_ => {}
115+
};
116+
if self.field.is_none() {
117+
self.cx.dcx().emit_err(errors::DeriveFromWrongTarget { span: item.span });
118+
}
119+
}
13120
}

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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,13 @@ pub(crate) struct DefaultHasArg {
446446
pub(crate) span: Span,
447447
}
448448

449+
#[derive(Diagnostic)]
450+
#[diag(builtin_macros_from_wrong_target)]
451+
pub(crate) struct DeriveFromWrongTarget {
452+
#[primary_span]
453+
pub(crate) span: Span,
454+
}
455+
449456
#[derive(Diagnostic)]
450457
#[diag(builtin_macros_derive_macro_call)]
451458
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,
@@ -743,6 +744,7 @@ symbols! {
743744
contracts_ensures,
744745
contracts_internals,
745746
contracts_requires,
747+
convert,
746748
convert_identity,
747749
copy,
748750
copy_closures,
@@ -2325,6 +2327,7 @@ symbols! {
23252327
va_start,
23262328
val,
23272329
validity,
2330+
value,
23282331
values,
23292332
var,
23302333
variant_count,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//@ edition: 2021
2+
//@ check-fail
3+
4+
#![feature(derive_from)]
5+
#![allow(dead_code)]
6+
7+
#[derive(From)]
8+
struct S1;
9+
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field
10+
11+
#[derive(From)]
12+
struct S2 {}
13+
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field
14+
15+
#[derive(From)]
16+
struct S3(u32, bool);
17+
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field
18+
19+
#[derive(From)]
20+
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
21+
struct S4 {
22+
a: u32,
23+
b: bool,
24+
}
25+
26+
#[derive(From)]
27+
enum E1 {}
28+
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field
29+
30+
#[derive(From)]
31+
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
32+
enum E2 {
33+
V1,
34+
V2,
35+
}
36+
37+
#[derive(From)]
38+
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
39+
enum E3 {
40+
V1(u32),
41+
V2(bool),
42+
}
43+
44+
#[derive(From)]
45+
//~^ ERROR the size for values of type `T` cannot be known at compilation time [E0277]
46+
//~| ERROR the size for values of type `T` cannot be known at compilation time [E0277]
47+
struct SUnsizedField<T: ?Sized> {
48+
last: T,
49+
//~^ ERROR the size for values of type `T` cannot be known at compilation time [E0277]
50+
}
51+
52+
fn main() {}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
error: `#[derive(From)]` can only be used on structs with a single field
2+
--> $DIR/deriving-from-wrong-target.rs:8:1
3+
|
4+
LL | struct S1;
5+
| ^^^^^^^^^^
6+
7+
error: `#[derive(From)]` can only be used on structs with a single field
8+
--> $DIR/deriving-from-wrong-target.rs:12:1
9+
|
10+
LL | struct S2 {}
11+
| ^^^^^^^^^^^^
12+
13+
error: `#[derive(From)]` can only be used on structs with a single field
14+
--> $DIR/deriving-from-wrong-target.rs:16:1
15+
|
16+
LL | struct S3(u32, bool);
17+
| ^^^^^^^^^^^^^^^^^^^^^
18+
19+
error: `#[derive(From)]` can only be used on structs with a single field
20+
--> $DIR/deriving-from-wrong-target.rs:21:1
21+
|
22+
LL | / struct S4 {
23+
LL | | a: u32,
24+
LL | | b: bool,
25+
LL | | }
26+
| |_^
27+
28+
error: `#[derive(From)]` can only be used on structs with a single field
29+
--> $DIR/deriving-from-wrong-target.rs:27:1
30+
|
31+
LL | enum E1 {}
32+
| ^^^^^^^^^^
33+
34+
error: `#[derive(From)]` can only be used on structs with a single field
35+
--> $DIR/deriving-from-wrong-target.rs:32:1
36+
|
37+
LL | / enum E2 {
38+
LL | | V1,
39+
LL | | V2,
40+
LL | | }
41+
| |_^
42+
43+
error: `#[derive(From)]` can only be used on structs with a single field
44+
--> $DIR/deriving-from-wrong-target.rs:39:1
45+
|
46+
LL | / enum E3 {
47+
LL | | V1(u32),
48+
LL | | V2(bool),
49+
LL | | }
50+
| |_^
51+
52+
error[E0277]: the size for values of type `T` cannot be known at compilation time
53+
--> $DIR/deriving-from-wrong-target.rs:44:10
54+
|
55+
LL | #[derive(From)]
56+
| ^^^^ doesn't have a size known at compile-time
57+
...
58+
LL | struct SUnsizedField<T: ?Sized> {
59+
| - this type parameter needs to be `Sized`
60+
|
61+
note: required by an implicit `Sized` bound in `From`
62+
--> $SRC_DIR/core/src/convert/mod.rs:LL:COL
63+
help: consider removing the `?Sized` bound to make the type parameter `Sized`
64+
|
65+
LL - struct SUnsizedField<T: ?Sized> {
66+
LL + struct SUnsizedField<T> {
67+
|
68+
69+
error[E0277]: the size for values of type `T` cannot be known at compilation time
70+
--> $DIR/deriving-from-wrong-target.rs:44:10
71+
|
72+
LL | #[derive(From)]
73+
| ^^^^ doesn't have a size known at compile-time
74+
...
75+
LL | struct SUnsizedField<T: ?Sized> {
76+
| - this type parameter needs to be `Sized`
77+
|
78+
note: required because it appears within the type `SUnsizedField<T>`
79+
--> $DIR/deriving-from-wrong-target.rs:47:8
80+
|
81+
LL | struct SUnsizedField<T: ?Sized> {
82+
| ^^^^^^^^^^^^^
83+
= note: the return type of a function must have a statically known size
84+
help: consider removing the `?Sized` bound to make the type parameter `Sized`
85+
|
86+
LL - struct SUnsizedField<T: ?Sized> {
87+
LL + struct SUnsizedField<T> {
88+
|
89+
90+
error[E0277]: the size for values of type `T` cannot be known at compilation time
91+
--> $DIR/deriving-from-wrong-target.rs:48:11
92+
|
93+
LL | struct SUnsizedField<T: ?Sized> {
94+
| - this type parameter needs to be `Sized`
95+
LL | last: T,
96+
| ^ doesn't have a size known at compile-time
97+
|
98+
= help: unsized fn params are gated as an unstable feature
99+
help: consider removing the `?Sized` bound to make the type parameter `Sized`
100+
|
101+
LL - struct SUnsizedField<T: ?Sized> {
102+
LL + struct SUnsizedField<T> {
103+
|
104+
help: function arguments must have a statically known size, borrowed types always have a known size
105+
|
106+
LL | last: &T,
107+
| +
108+
109+
error: aborting due to 10 previous errors
110+
111+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)