Skip to content

Implement #[derive(From)] #144922

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ builtin_macros_format_unused_args = multiple unused formatting arguments
builtin_macros_format_use_positional = consider using a positional formatting argument instead
builtin_macros_from_wrong_target = `#[derive(From)]` can only be used on structs with a single field
builtin_macros_multiple_default_attrs = multiple `#[default]` attributes
.note = only one `#[default]` attribute is needed
.label = `#[default]` used here
Expand Down
121 changes: 121 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/from.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use rustc_ast as ast;
use rustc_ast::ptr::P;
use rustc_ast::{FieldDef, Item, ItemKind, VariantData};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::{Ident, Span, kw, sym};
use thin_vec::thin_vec;

use crate::deriving::generic::ty::{Bounds, Path, PathKind, Ty};
use crate::deriving::generic::{
BlockOrExpr, FieldlessVariantsStrategy, MethodDef, SubstructureFields, TraitDef,
combine_substructure,
};
use crate::deriving::pathvec_std;
use crate::errors;

/// Generate an implementation of the `From` trait, provided that `item`
/// is a struct or a tuple struct with exactly one field.
pub(crate) fn expand_deriving_from(
cx: &ExtCtxt<'_>,
span: Span,
mitem: &ast::MetaItem,
item: &Annotatable,
push: &mut dyn FnMut(Annotatable),
is_const: bool,
) {
let mut visitor = ExtractNonSingleFieldStruct { cx, field: None };
item.visit_with(&mut visitor);

// Make sure that the derive is only invoked on single-field [tuple] structs.
// From this point below, we know that there is exactly one field.
let Some(field) = visitor.field else { return };

let path = Path::new_(
pathvec_std!(convert::From),
vec![Box::new(Ty::AstTy(field.ty.clone()))],
PathKind::Std,
);

// Generate code like this:
//
// struct S(u32);
// #[automatically_derived]
// impl ::core::convert::From<u32> for S {
// #[inline]
// fn from(value: u32) -> S {
// Self(value)
// }
// }
let from_trait_def = TraitDef {
span,
path,
skip_path_as_bound: true,
needs_copy_as_bound_if_packed: false,
additional_bounds: Vec::new(),
supports_unions: false,
methods: vec![MethodDef {
name: sym::from,
generics: Bounds { bounds: vec![] },
explicit_self: false,
nonself_args: vec![(Ty::AstTy(field.ty), sym::value)],
ret_ty: Ty::Self_,
attributes: thin_vec![cx.attr_word(sym::inline, span)],
fieldless_variants_strategy: FieldlessVariantsStrategy::Default,
combine_substructure: combine_substructure(Box::new(|cx, span, substructure| {
let self_kw = Ident::new(kw::SelfUpper, span);
let expr: P<ast::Expr> = match substructure.fields {
SubstructureFields::StaticStruct(variant, _) => match variant {
// Self {
// field: value
// }
VariantData::Struct { .. } => cx.expr_struct_ident(
span,
self_kw,
thin_vec![cx.field_imm(
span,
field.ident.unwrap(),
cx.expr_ident(span, Ident::new(sym::value, span))
)],
),
// Self(value)
VariantData::Tuple(_, _) => cx.expr_call_ident(
span,
self_kw,
thin_vec![cx.expr_ident(span, Ident::new(sym::value, span))],
),
variant => {
cx.dcx().bug(format!("Invalid derive(From) ADT variant: {variant:?}"));
}
},
_ => cx.dcx().bug("Invalid derive(From) ADT input"),
};
BlockOrExpr::new_expr(expr)
})),
}],
associated_types: Vec::new(),
is_const,
is_staged_api_crate: cx.ecfg.features.staged_api(),
};

from_trait_def.expand(cx, mitem, item, push);
}

struct ExtractNonSingleFieldStruct<'a, 'b> {
cx: &'a ExtCtxt<'b>,
field: Option<FieldDef>,
}

impl<'a, 'b> rustc_ast::visit::Visitor<'a> for ExtractNonSingleFieldStruct<'a, 'b> {
fn visit_item(&mut self, item: &'a Item) -> Self::Result {
match &item.kind {
ItemKind::Struct(_, _, data) => match data.fields() {
[field] => self.field = Some(field.clone()),
_ => {}
},
_ => {}
};
if self.field.is_none() {
self.cx.dcx().emit_err(errors::DeriveFromWrongTarget { span: item.span });
}
}
}
11 changes: 9 additions & 2 deletions compiler/rustc_builtin_macros/src/deriving/generic/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

pub(crate) use Ty::*;
use rustc_ast::ptr::P;
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind};
use rustc_ast::{self as ast, Expr, GenericArg, GenericParamKind, Generics, SelfKind, TyKind};
use rustc_expand::base::ExtCtxt;
use rustc_span::source_map::respan;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw};
Expand Down Expand Up @@ -66,7 +66,7 @@ impl Path {
}
}

/// A type. Supports pointers, Self, and literals.
/// A type. Supports pointers, Self, literals, unit or an arbitrary AST path.
#[derive(Clone)]
pub(crate) enum Ty {
Self_,
Expand All @@ -77,6 +77,8 @@ pub(crate) enum Ty {
Path(Path),
/// For () return types.
Unit,
/// An arbitrary type.
AstTy(P<ast::Ty>),
}

pub(crate) fn self_ref() -> Ty {
Expand All @@ -102,6 +104,7 @@ impl Ty {
let ty = ast::TyKind::Tup(ThinVec::new());
cx.ty(span, ty)
}
AstTy(ty) => ty.clone(),
}
}

Expand Down Expand Up @@ -133,6 +136,10 @@ impl Ty {
cx.path_all(span, false, vec![self_ty], params)
}
Path(p) => p.to_path(cx, span, self_ty, generics),
AstTy(ty) => match &ty.kind {
TyKind::Path(_, path) => path.clone(),
_ => cx.dcx().span_bug(span, "non-path in a path in generic `derive`"),
},
Ref(..) => cx.dcx().span_bug(span, "ref in a path in generic `derive`"),
Unit => cx.dcx().span_bug(span, "unit in a path in generic `derive`"),
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/deriving/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub(crate) mod clone;
pub(crate) mod coerce_pointee;
pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod from;
pub(crate) mod hash;

#[path = "cmp/eq.rs"]
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_builtin_macros/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,13 @@ pub(crate) struct DefaultHasArg {
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_from_wrong_target)]
pub(crate) struct DeriveFromWrongTarget {
#[primary_span]
pub(crate) span: Span,
}

#[derive(Diagnostic)]
#[diag(builtin_macros_derive_macro_call)]
pub(crate) struct DeriveMacroCall {
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
PartialEq: partial_eq::expand_deriving_partial_eq,
PartialOrd: partial_ord::expand_deriving_partial_ord,
CoercePointee: coerce_pointee::expand_deriving_coerce_pointee,
From: from::expand_deriving_from,
}

let client = rustc_proc_macro::bridge::client::Client::expand1(rustc_proc_macro::quote);
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ declare_features! (
(unstable, deprecated_suggestion, "1.61.0", Some(94785)),
/// Allows deref patterns.
(incomplete, deref_patterns, "1.79.0", Some(87121)),
/// Allows deriving the From trait on single-field structs.
(unstable, derive_from, "CURRENT_RUSTC_VERSION", Some(144889)),
/// Tells rustdoc to automatically generate `#[doc(cfg(...))]`.
(unstable, doc_auto_cfg, "1.58.0", Some(43781)),
/// Allows `#[doc(cfg(...))]`.
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ symbols! {
__D,
__H,
__S,
__T,
__awaitee,
__try_var,
_t,
Expand Down Expand Up @@ -741,6 +742,7 @@ symbols! {
contracts_ensures,
contracts_internals,
contracts_requires,
convert,
convert_identity,
copy,
copy_closures,
Expand Down Expand Up @@ -842,6 +844,7 @@ symbols! {
derive_const,
derive_const_issue: "118304",
derive_default_enum,
derive_from,
derive_smart_pointer,
destruct,
destructuring_assignment,
Expand Down Expand Up @@ -2320,6 +2323,7 @@ symbols! {
va_start,
val,
validity,
value,
values,
var,
variant_count,
Expand Down
11 changes: 11 additions & 0 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1767,4 +1767,15 @@ pub(crate) mod builtin {
pub macro deref($pat:pat) {
builtin # deref($pat)
}

/// Derive macro generating an impl of the trait `From`.
/// Currently, it can only be used on single-field structs.
// Note that the macro is in a different module than the `From` trait,
// to avoid triggering an unstable feature being used if someone imports
// `std::convert::From`.
#[rustc_builtin_macro]
#[unstable(feature = "derive_from", issue = "144889")]
pub macro From($item: item) {
/* compiler built-in */
}
}
7 changes: 7 additions & 0 deletions library/core/src/prelude/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,10 @@ pub use crate::macros::builtin::deref;
reason = "`type_alias_impl_trait` has open design concerns"
)]
pub use crate::macros::builtin::define_opaque;

#[unstable(
feature = "derive_from",
issue = "144889",
reason = "`derive(From)` is unstable"
)]
pub use crate::macros::builtin::From;
52 changes: 52 additions & 0 deletions tests/ui/deriving/deriving-from-wrong-target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//@ edition: 2021
//@ check-fail

#![feature(derive_from)]
#![allow(dead_code)]

#[derive(From)]
struct S1;
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field

#[derive(From)]
struct S2 {}
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field

#[derive(From)]
struct S3(u32, bool);
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field

#[derive(From)]
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
struct S4 {
a: u32,
b: bool,
}

#[derive(From)]
enum E1 {}
//~^ ERROR `#[derive(From)]` can only be used on structs with a single field

#[derive(From)]
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
enum E2 {
V1,
V2,
}

#[derive(From)]
//~v ERROR `#[derive(From)]` can only be used on structs with a single field
enum E3 {
V1(u32),
V2(bool),
}

#[derive(From)]
//~^ ERROR the size for values of type `T` cannot be known at compilation time [E0277]
//~| ERROR the size for values of type `T` cannot be known at compilation time [E0277]
struct SUnsizedField<T: ?Sized> {
last: T,
//~^ ERROR the size for values of type `T` cannot be known at compilation time [E0277]
}

fn main() {}
Loading
Loading