From c2dcb41763883657646ff0e035931db6ca63193c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 4 Aug 2025 17:22:46 +0200 Subject: [PATCH 1/4] Add `derive_from` unstable feature --- compiler/rustc_feature/src/unstable.rs | 2 ++ compiler/rustc_span/src/symbol.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 1303b3317e05a..e2c53fefeafed 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -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(...))]`. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index d54175548e30e..3f20f2cff2357 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -842,6 +842,7 @@ symbols! { derive_const, derive_const_issue: "118304", derive_default_enum, + derive_from, derive_smart_pointer, destruct, destructuring_assignment, From 60cdc2ba037729ab1aa4016130650916cd37fc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 4 Aug 2025 17:24:18 +0200 Subject: [PATCH 2/4] Create unstable `From` builtin macro and register it --- compiler/rustc_builtin_macros/src/deriving/from.rs | 13 +++++++++++++ compiler/rustc_builtin_macros/src/deriving/mod.rs | 1 + compiler/rustc_builtin_macros/src/lib.rs | 1 + library/core/src/macros/mod.rs | 11 +++++++++++ library/core/src/prelude/v1.rs | 7 +++++++ 5 files changed, 33 insertions(+) create mode 100644 compiler/rustc_builtin_macros/src/deriving/from.rs diff --git a/compiler/rustc_builtin_macros/src/deriving/from.rs b/compiler/rustc_builtin_macros/src/deriving/from.rs new file mode 100644 index 0000000000000..79fd4caa4403a --- /dev/null +++ b/compiler/rustc_builtin_macros/src/deriving/from.rs @@ -0,0 +1,13 @@ +use rustc_ast as ast; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_span::{Span, sym}; + +pub(crate) fn expand_deriving_from( + cx: &ExtCtxt<'_>, + span: Span, + mitem: &ast::MetaItem, + item: &Annotatable, + push: &mut dyn FnMut(Annotatable), + is_const: bool, +) { +} diff --git a/compiler/rustc_builtin_macros/src/deriving/mod.rs b/compiler/rustc_builtin_macros/src/deriving/mod.rs index e45d09b57960c..560c2803969f4 100644 --- a/compiler/rustc_builtin_macros/src/deriving/mod.rs +++ b/compiler/rustc_builtin_macros/src/deriving/mod.rs @@ -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"] diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index 7bc448a9acb8b..86a4927f3903f 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -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); diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index c59290a757b67..0767e9ff472bb 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -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 */ + } } diff --git a/library/core/src/prelude/v1.rs b/library/core/src/prelude/v1.rs index a4be66b90cab3..d8d82afb0e625 100644 --- a/library/core/src/prelude/v1.rs +++ b/library/core/src/prelude/v1.rs @@ -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; From ba0667e966ef7ffee2e493cf7801320a97d38a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 4 Aug 2025 17:32:22 +0200 Subject: [PATCH 3/4] Add feature gate test --- tests/ui/feature-gates/feature-gate-derive-from.rs | 6 ++++++ .../feature-gates/feature-gate-derive-from.stderr | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/ui/feature-gates/feature-gate-derive-from.rs create mode 100644 tests/ui/feature-gates/feature-gate-derive-from.stderr diff --git a/tests/ui/feature-gates/feature-gate-derive-from.rs b/tests/ui/feature-gates/feature-gate-derive-from.rs new file mode 100644 index 0000000000000..12440356ddf25 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-derive-from.rs @@ -0,0 +1,6 @@ +//@ edition: 2021 + +#[derive(From)] //~ ERROR use of unstable library feature `derive_from` +struct Foo(u32); + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-derive-from.stderr b/tests/ui/feature-gates/feature-gate-derive-from.stderr new file mode 100644 index 0000000000000..d58dcdd754111 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-derive-from.stderr @@ -0,0 +1,13 @@ +error[E0658]: use of unstable library feature `derive_from` + --> $DIR/feature-gate-derive-from.rs:3:10 + | +LL | #[derive(From)] + | ^^^^ + | + = note: see issue #144889 for more information + = help: add `#![feature(derive_from)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. From 964d6e38f87c969e6113b02592aff60225e21791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 4 Aug 2025 21:11:30 +0200 Subject: [PATCH 4/4] Implement `#[derive(From)]` --- compiler/rustc_builtin_macros/messages.ftl | 2 + .../rustc_builtin_macros/src/deriving/from.rs | 110 ++++++++++++++++- .../src/deriving/generic/ty.rs | 11 +- compiler/rustc_builtin_macros/src/errors.rs | 7 ++ compiler/rustc_span/src/symbol.rs | 3 + .../ui/deriving/deriving-from-wrong-target.rs | 52 ++++++++ .../deriving-from-wrong-target.stderr | 111 ++++++++++++++++++ tests/ui/deriving/deriving-from.rs | 58 +++++++++ 8 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 tests/ui/deriving/deriving-from-wrong-target.rs create mode 100644 tests/ui/deriving/deriving-from-wrong-target.stderr create mode 100644 tests/ui/deriving/deriving-from.rs diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index ae186d744c40e..0d7d64b437aa0 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -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 diff --git a/compiler/rustc_builtin_macros/src/deriving/from.rs b/compiler/rustc_builtin_macros/src/deriving/from.rs index 79fd4caa4403a..ad20b10f7a5d1 100644 --- a/compiler/rustc_builtin_macros/src/deriving/from.rs +++ b/compiler/rustc_builtin_macros/src/deriving/from.rs @@ -1,7 +1,20 @@ 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::{Span, sym}; +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, @@ -10,4 +23,99 @@ pub(crate) fn expand_deriving_from( 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 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 = 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, +} + +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 }); + } + } } diff --git a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs index f34a6ae1d9829..c546e8be89e37 100644 --- a/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs +++ b/compiler/rustc_builtin_macros/src/deriving/generic/ty.rs @@ -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}; @@ -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_, @@ -77,6 +77,8 @@ pub(crate) enum Ty { Path(Path), /// For () return types. Unit, + /// An arbitrary type. + AstTy(P), } pub(crate) fn self_ref() -> Ty { @@ -102,6 +104,7 @@ impl Ty { let ty = ast::TyKind::Tup(ThinVec::new()); cx.ty(span, ty) } + AstTy(ty) => ty.clone(), } } @@ -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`"), } diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 6bcf4d3e0a2ee..d00b8e07ddf28 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -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 { diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3f20f2cff2357..8abd8bfb62ffa 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -390,6 +390,7 @@ symbols! { __D, __H, __S, + __T, __awaitee, __try_var, _t, @@ -741,6 +742,7 @@ symbols! { contracts_ensures, contracts_internals, contracts_requires, + convert, convert_identity, copy, copy_closures, @@ -2321,6 +2323,7 @@ symbols! { va_start, val, validity, + value, values, var, variant_count, diff --git a/tests/ui/deriving/deriving-from-wrong-target.rs b/tests/ui/deriving/deriving-from-wrong-target.rs new file mode 100644 index 0000000000000..8bafa5246c153 --- /dev/null +++ b/tests/ui/deriving/deriving-from-wrong-target.rs @@ -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 { + last: T, + //~^ ERROR the size for values of type `T` cannot be known at compilation time [E0277] +} + +fn main() {} diff --git a/tests/ui/deriving/deriving-from-wrong-target.stderr b/tests/ui/deriving/deriving-from-wrong-target.stderr new file mode 100644 index 0000000000000..3464b5275b35f --- /dev/null +++ b/tests/ui/deriving/deriving-from-wrong-target.stderr @@ -0,0 +1,111 @@ +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:8:1 + | +LL | struct S1; + | ^^^^^^^^^^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:12:1 + | +LL | struct S2 {} + | ^^^^^^^^^^^^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:16:1 + | +LL | struct S3(u32, bool); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:21:1 + | +LL | / struct S4 { +LL | | a: u32, +LL | | b: bool, +LL | | } + | |_^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:27:1 + | +LL | enum E1 {} + | ^^^^^^^^^^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:32:1 + | +LL | / enum E2 { +LL | | V1, +LL | | V2, +LL | | } + | |_^ + +error: `#[derive(From)]` can only be used on structs with a single field + --> $DIR/deriving-from-wrong-target.rs:39:1 + | +LL | / enum E3 { +LL | | V1(u32), +LL | | V2(bool), +LL | | } + | |_^ + +error[E0277]: the size for values of type `T` cannot be known at compilation time + --> $DIR/deriving-from-wrong-target.rs:44:10 + | +LL | #[derive(From)] + | ^^^^ doesn't have a size known at compile-time +... +LL | struct SUnsizedField { + | - this type parameter needs to be `Sized` + | +note: required by an implicit `Sized` bound in `From` + --> $SRC_DIR/core/src/convert/mod.rs:LL:COL +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +LL - struct SUnsizedField { +LL + struct SUnsizedField { + | + +error[E0277]: the size for values of type `T` cannot be known at compilation time + --> $DIR/deriving-from-wrong-target.rs:44:10 + | +LL | #[derive(From)] + | ^^^^ doesn't have a size known at compile-time +... +LL | struct SUnsizedField { + | - this type parameter needs to be `Sized` + | +note: required because it appears within the type `SUnsizedField` + --> $DIR/deriving-from-wrong-target.rs:47:8 + | +LL | struct SUnsizedField { + | ^^^^^^^^^^^^^ + = note: the return type of a function must have a statically known size +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +LL - struct SUnsizedField { +LL + struct SUnsizedField { + | + +error[E0277]: the size for values of type `T` cannot be known at compilation time + --> $DIR/deriving-from-wrong-target.rs:48:11 + | +LL | struct SUnsizedField { + | - this type parameter needs to be `Sized` +LL | last: T, + | ^ doesn't have a size known at compile-time + | + = help: unsized fn params are gated as an unstable feature +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +LL - struct SUnsizedField { +LL + struct SUnsizedField { + | +help: function arguments must have a statically known size, borrowed types always have a known size + | +LL | last: &T, + | + + +error: aborting due to 10 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/deriving/deriving-from.rs b/tests/ui/deriving/deriving-from.rs new file mode 100644 index 0000000000000..ff4c5b4c426a6 --- /dev/null +++ b/tests/ui/deriving/deriving-from.rs @@ -0,0 +1,58 @@ +//@ edition: 2021 +//@ run-pass + +#![feature(derive_from)] + +#[derive(From)] +struct TupleSimple(u32); + +#[derive(From)] +struct TupleNonPathType([u32; 4]); + +#[derive(From)] +struct TupleWithRef<'a, T>(&'a T); + +#[derive(From)] +struct TupleSWithBound(T); + +#[derive(From)] +struct RawIdentifier { + r#use: u32, +} + +#[derive(From)] +struct Field { + foo: bool, +} + +#[derive(From)] +struct Const { + foo: [u32; C], +} + +fn main() { + let a = 42u32; + let b: [u32; 4] = [0, 1, 2, 3]; + let c = true; + + let s1: TupleSimple = a.into(); + assert_eq!(s1.0, a); + + let s2: TupleNonPathType = b.into(); + assert_eq!(s2.0, b); + + let s3: TupleWithRef = (&a).into(); + assert_eq!(s3.0, &a); + + let s4: TupleSWithBound = a.into(); + assert_eq!(s4.0, a); + + let s5: RawIdentifier = a.into(); + assert_eq!(s5.r#use, a); + + let s6: Field = c.into(); + assert_eq!(s6.foo, c); + + let s7: Const<4> = b.into(); + assert_eq!(s7.foo, b); +}