Skip to content

Commit 964d6e3

Browse files
committed
Implement #[derive(From)]
1 parent ba0667e commit 964d6e3

File tree

8 files changed

+351
-3
lines changed

8 files changed

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

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
pub(crate) use Ty::*;
55
use rustc_ast::ptr::P;
6-
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
6+
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind, TyKind};
77
use rustc_expand::base::ExtCtxt;
88
use rustc_span::source_map::respan;
99
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw};
@@ -66,7 +66,7 @@ impl Path {
6666
}
6767
}
6868

69-
/// A type. Supports pointers, Self, and literals.
69+
/// A type. Supports pointers, Self, literals, unit or an arbitrary AST path.
7070
#[derive(Clone)]
7171
pub(crate) enum Ty {
7272
Self_,
@@ -77,6 +77,8 @@ pub(crate) enum Ty {
7777
Path(Path),
7878
/// For () return types.
7979
Unit,
80+
/// An arbitrary type.
81+
AstTy(P<ast::Ty>),
8082
}
8183

8284
pub(crate) fn self_ref() -> Ty {
@@ -102,6 +104,7 @@ impl Ty {
102104
let ty = ast::TyKind::Tup(ThinVec::new());
103105
cx.ty(span, ty)
104106
}
107+
AstTy(ty) => ty.clone(),
105108
}
106109
}
107110

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

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
@@ -390,6 +390,7 @@ symbols! {
390390
__D,
391391
__H,
392392
__S,
393+
__T,
393394
__awaitee,
394395
__try_var,
395396
_t,
@@ -741,6 +742,7 @@ symbols! {
741742
contracts_ensures,
742743
contracts_internals,
743744
contracts_requires,
745+
convert,
744746
convert_identity,
745747
copy,
746748
copy_closures,
@@ -2321,6 +2323,7 @@ symbols! {
23212323
va_start,
23222324
val,
23232325
validity,
2326+
value,
23242327
values,
23252328
var,
23262329
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)