diff options
| author | Jakub Beránek <berykubik@gmail.com> | 2025-08-15 12:07:15 +0200 |
|---|---|---|
| committer | Jakub Beránek <berykubik@gmail.com> | 2025-08-15 12:07:15 +0200 |
| commit | 1f3a7471bfb05a5fd76309545de0412d265e28be (patch) | |
| tree | 0bacdf710ee6162de09271321ffac77f2137a92b /compiler/rustc_builtin_macros | |
| parent | c0839ea7d242f077cec567af1e4489951efb6570 (diff) | |
| download | rust-1f3a7471bfb05a5fd76309545de0412d265e28be.tar.gz rust-1f3a7471bfb05a5fd76309545de0412d265e28be.zip | |
Implement `#[derive(From)]`
Diffstat (limited to 'compiler/rustc_builtin_macros')
| -rw-r--r-- | compiler/rustc_builtin_macros/messages.ftl | 9 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/deriving/from.rs | 125 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/deriving/generic/ty.rs | 11 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/errors.rs | 18 |
4 files changed, 158 insertions, 5 deletions
diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index eb3c40cc593..358c0d3db46 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -222,6 +222,15 @@ builtin_macros_format_unused_args = multiple unused formatting arguments builtin_macros_format_use_positional = consider using a positional formatting argument instead +builtin_macros_derive_from_wrong_target = `#[derive(From)]` used on {$kind} + +builtin_macros_derive_from_wrong_field_count = `#[derive(From)]` used on a struct with {$multiple_fields -> + [true] multiple fields + *[false] no fields +} + +builtin_macros_derive_from_usage_note = `#[derive(From)]` can only be used on structs with exactly one field + builtin_macros_multiple_default_attrs = multiple `#[default]` attributes .note = only one `#[default]` attribute is needed .label = `#[default]` used here diff --git a/compiler/rustc_builtin_macros/src/deriving/from.rs b/compiler/rustc_builtin_macros/src/deriving/from.rs index 79fd4caa440..ef0e6ca324a 100644 --- a/compiler/rustc_builtin_macros/src/deriving/from.rs +++ b/compiler/rustc_builtin_macros/src/deriving/from.rs @@ -1,13 +1,132 @@ use rustc_ast as ast; -use rustc_expand::base::{Annotatable, ExtCtxt}; -use rustc_span::{Span, sym}; +use rustc_ast::{ItemKind, VariantData}; +use rustc_errors::MultiSpan; +use rustc_expand::base::{Annotatable, DummyResult, 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, + annotatable: &Annotatable, push: &mut dyn FnMut(Annotatable), is_const: bool, ) { + let Annotatable::Item(item) = &annotatable else { + cx.dcx().bug("derive(From) used on something else than an item"); + }; + + // #[derive(From)] is currently usable only on structs with exactly one field. + let field = if let ItemKind::Struct(_, _, data) = &item.kind + && let [field] = data.fields() + { + Some(field.clone()) + } else { + None + }; + + let from_type = match &field { + Some(field) => Ty::AstTy(field.ty.clone()), + // We don't have a type to put into From<...> if we don't have a single field, so just put + // unit there. + None => Ty::Unit, + }; + let path = + Path::new_(pathvec_std!(convert::From), vec![Box::new(from_type.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![(from_type, 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 Some(field) = &field else { + let item_span = item.kind.ident().map(|ident| ident.span).unwrap_or(item.span); + let err_span = MultiSpan::from_spans(vec![span, item_span]); + let error = match &item.kind { + ItemKind::Struct(_, _, data) => { + cx.dcx().emit_err(errors::DeriveFromWrongFieldCount { + span: err_span, + multiple_fields: data.fields().len() > 1, + }) + } + ItemKind::Enum(_, _, _) | ItemKind::Union(_, _, _) => { + cx.dcx().emit_err(errors::DeriveFromWrongTarget { + span: err_span, + kind: &format!("{} {}", item.kind.article(), item.kind.descr()), + }) + } + _ => cx.dcx().bug("Invalid derive(From) ADT input"), + }; + + return BlockOrExpr::new_expr(DummyResult::raw_expr(span, Some(error))); + }; + + let self_kw = Ident::new(kw::SelfUpper, span); + let expr: Box<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, annotatable, push); } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs index 00e70b21cf4..1458553d492 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -2,7 +2,7 @@ //! when specifying impls to be derived. pub(crate) use Ty::*; -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}; @@ -65,7 +65,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_, @@ -76,6 +76,8 @@ pub(crate) enum Ty { Path(Path), /// For () return types. Unit, + /// An arbitrary type. + AstTy(Box<ast::Ty>), } pub(crate) fn self_ref() -> Ty { @@ -101,6 +103,7 @@ impl Ty { let ty = ast::TyKind::Tup(ThinVec::new()); cx.ty(span, ty) } + AstTy(ty) => ty.clone(), } } @@ -132,6 +135,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`"), } diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index bb520db75b9..54e8f750337 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -447,6 +447,24 @@ pub(crate) struct DefaultHasArg { } #[derive(Diagnostic)] +#[diag(builtin_macros_derive_from_wrong_target)] +#[note(builtin_macros_derive_from_usage_note)] +pub(crate) struct DeriveFromWrongTarget<'a> { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) kind: &'a str, +} + +#[derive(Diagnostic)] +#[diag(builtin_macros_derive_from_wrong_field_count)] +#[note(builtin_macros_derive_from_usage_note)] +pub(crate) struct DeriveFromWrongFieldCount { + #[primary_span] + pub(crate) span: MultiSpan, + pub(crate) multiple_fields: bool, +} + +#[derive(Diagnostic)] #[diag(builtin_macros_derive_macro_call)] pub(crate) struct DeriveMacroCall { #[primary_span] |
