Skip to content

Commit 2b1a288

Browse files
authored
Rollup merge of rust-lang#144922 - Kobzol:derive-from, r=nnethercote
Implement `#[derive(From)]` Implements the `#[derive(From)]` feature ([tracking issue](rust-lang#144889), [RFC](rust-lang/rfcs#3809)). It allows deriving the `From` impl on structs and tuple structs with exactly one field. Some implementation notes: - I wasn't exactly sure which spans to use in the derive generating code, so I just used `span` everywhere. I don't know if it's the Right Thing To Do. In particular the errors when `#[derive(From)]` is used on a struct with an unsized field are weirdly duplicated. - I had to solve an import stability problem, where if I just added the unstable `macro From` to `core::convert`, previously working code like `use std::convert::From` would suddenly require an unstable feature gate, because rustc would think that you're trying to import the unstable macro. `@petrochenkov` suggested that I add the macro the the core prelude instead. This has worked well, although it only works in edition 2021+. Not sure if I botched the prelude somehow and it should live elsewhere (?). - I had to add `Ty::AstTy`, because the `from` function receives an argument with the type of the single field, and the existing variants of the `Ty` enum couldn't represent an arbitrary type.
2 parents d077146 + 1f3a747 commit 2b1a288

File tree

17 files changed

+582
-3
lines changed

17 files changed

+582
-3
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: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use rustc_ast as ast;
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;
7+
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.
18+
pub(crate) fn expand_deriving_from(
19+
cx: &ExtCtxt<'_>,
20+
span: Span,
21+
mitem: &ast::MetaItem,
22+
annotatable: &Annotatable,
23+
push: &mut dyn FnMut(Annotatable),
24+
is_const: bool,
25+
) {
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);
132+
}

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/deriving/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub(crate) mod clone;
2323
pub(crate) mod coerce_pointee;
2424
pub(crate) mod debug;
2525
pub(crate) mod default;
26+
pub(crate) mod from;
2627
pub(crate) mod hash;
2728

2829
#[path = "cmp/eq.rs"]

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_builtin_macros/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
139139
PartialEq: partial_eq::expand_deriving_partial_eq,
140140
PartialOrd: partial_ord::expand_deriving_partial_ord,
141141
CoercePointee: coerce_pointee::expand_deriving_coerce_pointee,
142+
From: from::expand_deriving_from,
142143
}
143144

144145
let client = rustc_proc_macro::bridge::client::Client::expand1(rustc_proc_macro::quote);

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,8 @@ declare_features! (
470470
(unstable, deprecated_suggestion, "1.61.0", Some(94785)),
471471
/// Allows deref patterns.
472472
(incomplete, deref_patterns, "1.79.0", Some(87121)),
473+
/// Allows deriving the From trait on single-field structs.
474+
(unstable, derive_from, "CURRENT_RUSTC_VERSION", Some(144889)),
473475
/// Tells rustdoc to automatically generate `#[doc(cfg(...))]`.
474476
(unstable, doc_auto_cfg, "1.58.0", Some(43781)),
475477
/// Allows `#[doc(cfg(...))]`.

compiler/rustc_span/src/symbol.rs

Lines changed: 4 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,
@@ -746,6 +747,7 @@ symbols! {
746747
contracts_ensures,
747748
contracts_internals,
748749
contracts_requires,
750+
convert,
749751
convert_identity,
750752
copy,
751753
copy_closures,
@@ -847,6 +849,7 @@ symbols! {
847849
derive_const,
848850
derive_const_issue: "118304",
849851
derive_default_enum,
852+
derive_from,
850853
derive_smart_pointer,
851854
destruct,
852855
destructuring_assignment,
@@ -2331,6 +2334,7 @@ symbols! {
23312334
va_start,
23322335
val,
23332336
validity,
2337+
value,
23342338
values,
23352339
var,
23362340
variant_count,

library/core/src/macros/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,4 +1770,15 @@ pub(crate) mod builtin {
17701770
pub macro deref($pat:pat) {
17711771
builtin # deref($pat)
17721772
}
1773+
1774+
/// Derive macro generating an impl of the trait `From`.
1775+
/// Currently, it can only be used on single-field structs.
1776+
// Note that the macro is in a different module than the `From` trait,
1777+
// to avoid triggering an unstable feature being used if someone imports
1778+
// `std::convert::From`.
1779+
#[rustc_builtin_macro]
1780+
#[unstable(feature = "derive_from", issue = "144889")]
1781+
pub macro From($item: item) {
1782+
/* compiler built-in */
1783+
}
17731784
}

library/core/src/prelude/v1.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,10 @@ pub use crate::macros::builtin::deref;
117117
reason = "`type_alias_impl_trait` has open design concerns"
118118
)]
119119
pub use crate::macros::builtin::define_opaque;
120+
121+
#[unstable(
122+
feature = "derive_from",
123+
issue = "144889",
124+
reason = "`derive(From)` is unstable"
125+
)]
126+
pub use crate::macros::builtin::From;

0 commit comments

Comments
 (0)