Skip to content
Merged
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
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 @@ -25,6 +25,7 @@ pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod from;
pub(crate) mod hash;
pub(crate) mod reborrow;

#[path = "cmp/eq.rs"]
pub(crate) mod eq;
Expand Down
235 changes: 235 additions & 0 deletions compiler/rustc_builtin_macros/src/deriving/reborrow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
use rustc_ast::{
self as ast, AttrArgs, GenericArg, GenericParamKind, Generics, ItemKind, MetaItem, token,
};
use rustc_errors::E0802;
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_macros::Diagnostic;
use rustc_span::{Ident, Span, Symbol, sym};
use thin_vec::ThinVec;

macro_rules! path {
($span:expr, $($part:ident)::*) => { vec![$(Ident::new(sym::$part, $span),)*] }
}

pub(crate) fn expand_deriving_reborrow(
cx: &ExtCtxt<'_>,
span: Span,
_mitem: &MetaItem,
item: &Annotatable,
push: &mut dyn FnMut(Annotatable),
_is_const: bool,
) {
let Some((ident, generics)) = struct_def(cx, span, item, sym::Reborrow) else {
return;
};

push_marker_impl(cx, span, ident, generics, sym::Reborrow, Vec::new(), push);
}

pub(crate) fn expand_deriving_coerce_shared(
cx: &ExtCtxt<'_>,
span: Span,
_mitem: &MetaItem,
item: &Annotatable,
push: &mut dyn FnMut(Annotatable),
_is_const: bool,
) {
let Some((ident, generics)) = struct_def(cx, span, item, sym::CoerceShared) else {
return;
};
let Some(target) = coerce_shared_target(cx, span, item) else {
return;
};

push_marker_impl(
cx,
span,
ident,
generics,
sym::CoerceShared,
vec![GenericArg::Type(target)],
push,
);
}

fn struct_def<'a>(
cx: &ExtCtxt<'_>,
span: Span,
item: &'a Annotatable,
trait_name: Symbol,
) -> Option<(Ident, &'a Generics)> {
match item {
Annotatable::Item(item) => match &item.kind {
ItemKind::Struct(ident, generics, _) => Some((*ident, generics)),
ItemKind::Enum(..) => {
cx.dcx().emit_err(UnsupportedItem { span, trait_name, kind: "enum" });
None
}
ItemKind::Union(..) => {
cx.dcx().emit_err(UnsupportedItem { span, trait_name, kind: "union" });
None
}
_ => {
cx.dcx().emit_err(UnsupportedItem { span, trait_name, kind: "item" });
None
}
},
_ => {
cx.dcx().emit_err(UnsupportedItem { span, trait_name, kind: "item" });
None
}
}
}

fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &Annotatable) -> Option<Box<ast::Ty>> {
let Annotatable::Item(item) = item else {
cx.dcx().emit_err(MissingTarget { span });
return None;
};

let mut attrs = item.attrs.iter().filter(|attr| attr.has_name(sym::coerce_shared));
let Some(attr) = attrs.next() else {
cx.dcx().emit_err(MissingTarget { span });
return None;
};
if let Some(duplicate) = attrs.next() {
cx.dcx().emit_err(DuplicateTarget { first: attr.span, duplicate: duplicate.span });
return None;
}

let AttrArgs::Delimited(args) = &attr.get_normal_item().args else {
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
};
if args.delim != token::Delimiter::Parenthesis || args.tokens.is_empty() {
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
}

let mut parser = cx.new_parser_from_tts(args.tokens.clone());
let target = match parser.parse_ty() {
Ok(target) => target,
Err(err) => {
err.cancel();
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
}
};
if parser.token != token::Eof {
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
}

Some(target)
}

fn push_marker_impl(
cx: &ExtCtxt<'_>,
span: Span,
ident: Ident,
generics: &Generics,
trait_name: Symbol,
trait_args: Vec<GenericArg>,
push: &mut dyn FnMut(Annotatable),
) {
let mut trait_parts = path!(span, core::marker);
trait_parts.push(Ident::new(trait_name, span));
let trait_path = cx.path_all(span, true, trait_parts, trait_args);
let trait_ref = cx.trait_ref(trait_path);

let self_params: Vec<_> = generics
.params
.iter()
.map(|param| match param.kind {
GenericParamKind::Lifetime => {
GenericArg::Lifetime(cx.lifetime(param.span(), param.ident))
}
GenericParamKind::Type { .. } => {
GenericArg::Type(cx.ty_ident(param.span(), param.ident))
}
GenericParamKind::Const { .. } => {
GenericArg::Const(cx.const_ident(param.span(), param.ident))
}
})
.collect();
let self_ty = cx.ty_path(cx.path_all(span, false, vec![ident], self_params));

push(Annotatable::Item(cx.item(
span,
thin_vec::thin_vec![cx.attr_word(sym::automatically_derived, span)],
ast::ItemKind::Impl(ast::Impl {
generics: impl_generics(cx, generics),
of_trait: Some(Box::new(ast::TraitImplHeader {
safety: ast::Safety::Default,
polarity: ast::ImplPolarity::Positive,
defaultness: ast::Defaultness::Implicit,
trait_ref,
})),
constness: ast::Const::No,
self_ty,
items: ThinVec::new(),
}),
)));
}

fn impl_generics(cx: &ExtCtxt<'_>, generics: &Generics) -> Generics {
Comment thread
P8L1 marked this conversation as resolved.
// Rebuild the generic parameter declarations because defaults are allowed on structs but
// rejected on impls. Preserve lifetime, type, and const parameters and their bounds, const
// parameter types, and the where-clause, while omitting type and const defaults.
Generics {
params: generics
.params
.iter()
.map(|param| match &param.kind {
GenericParamKind::Lifetime => {
cx.lifetime_param(param.span(), param.ident, param.bounds.clone())
}
GenericParamKind::Type { default: _ } => {
cx.typaram(param.span(), param.ident, param.bounds.clone(), None)
}
GenericParamKind::Const { ty, span: _, default: _ } => cx.const_param(
param.span(),
param.ident,
param.bounds.clone(),
ty.clone(),
None,
),
})
.collect(),
where_clause: generics.where_clause.clone(),
span: generics.span,
}
}

#[derive(Diagnostic)]
#[diag("`derive({$trait_name})` is only supported for structs, not {$kind}s", code = E0802)]
struct UnsupportedItem {
#[primary_span]
span: Span,
trait_name: Symbol,
kind: &'static str,
}

#[derive(Diagnostic)]
#[diag("`derive(CoerceShared)` requires exactly one `#[coerce_shared(Target)]` attribute", code = E0802)]

@aapoalas aapoalas May 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: I would've gone with derive(CoerceShared(Target)).

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those are hard to support in the current derive infra.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should def be an open question on the tracking issue, as #[coerce_shared(Thing<'a, T>)] looks a bit unclear to me, maybe #[coerce_shared_target = Thing<'a, T>] if our attribute syntax permits that?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I checked the available attribute forms. The current grammar does not accept '#[coerce_shared_target = Thing<'a, T>], since the right-hand side of an = attribute is parsed as an expression and stored as AttrArgs::Eq { expr: Box<Expr>, .. }, while Thing<'a, T> is type syntax and fails expression parsing.

Also, since the current derive pipeline diagnoses arguments on derive paths and resolves only the trait path, we currently reject derive(CoerceShared(Target))

A structured helper such as #[coerce_shared(target = Thing<'a, T>)] is technically feasible because delimited attributes retain their token stream, allowing the builtin derive to parse target = followed by parse_ty().

struct MissingTarget {
#[primary_span]
span: Span,
}

#[derive(Diagnostic)]
#[diag("duplicate `#[coerce_shared(Target)]` attribute for `derive(CoerceShared)`", code = E0802)]
struct DuplicateTarget {
#[primary_span]
duplicate: Span,
#[note("first `#[coerce_shared(Target)]` attribute is here")]
first: Span,
}

#[derive(Diagnostic)]
#[diag("malformed `#[coerce_shared(Target)]` attribute for `derive(CoerceShared)`", code = E0802)]
#[note("expected a single target type, for example `#[coerce_shared(Target<'a, T>)]`")]
struct MalformedTarget {
#[primary_span]
span: Span,
}
2 changes: 2 additions & 0 deletions compiler/rustc_builtin_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {

register_derive! {
Clone: clone::expand_deriving_clone,
CoerceShared: reborrow::expand_deriving_coerce_shared,
Copy: bounds::expand_deriving_copy,
ConstParamTy: bounds::expand_deriving_const_param_ty,
Debug: debug::expand_deriving_debug,
Expand All @@ -140,6 +141,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,
Reborrow: reborrow::expand_deriving_reborrow,
From: from::expand_deriving_from,
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ symbols! {
Clone,
CoercePointee,
CoercePointeeValidated,
CoerceShared,
CoerceUnsized,
Const,
ConstParamTy,
Expand Down
18 changes: 18 additions & 0 deletions library/core/src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1373,10 +1373,28 @@ pub trait Reborrow {
/* compiler built-in */
}

/// Derive macro generating an impl of the trait `Reborrow`.
#[rustc_builtin_macro(Reborrow)]
#[allow_internal_unstable(reborrow)]
#[unstable(feature = "reborrow", issue = "145612")]
pub macro Reborrow($item:item) {
/* compiler built-in */
}

/// Allows reborrowable value to be reborrowed as shared, creating a copy
/// that disables the source for writes for the lifetime of the copy.
#[lang = "coerce_shared"]
#[unstable(feature = "reborrow", issue = "145612")]
pub trait CoerceShared<Target: Copy>: Reborrow {
/* compiler built-in */
}

/// Derive macro generating an impl of the trait `CoerceShared`.
///
/// The shared target type must be specified with `#[coerce_shared(Target)]`.
#[rustc_builtin_macro(CoerceShared, attributes(coerce_shared))]
#[allow_internal_unstable(reborrow)]
#[unstable(feature = "reborrow", issue = "145612")]
pub macro CoerceShared($item:item) {
/* compiler built-in */
}
12 changes: 11 additions & 1 deletion tests/ui/feature-gates/feature-gate-reborrow-coerce-shared.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
use std::marker::CoerceShared; //~ ERROR use of unstable library feature `reborrow`
use std::marker::CoerceShared; //~ ERROR use of unstable library feature `reborrow`
//~^ ERROR use of unstable library feature `reborrow`

#[derive(Clone, Copy)]
struct CustomRef<'a>(&'a ());

#[derive(std::marker::Reborrow, std::marker::CoerceShared)]
//~^ ERROR use of unstable library feature `reborrow`
//~| ERROR use of unstable library feature `reborrow`
#[coerce_shared(CustomRef<'a>)]
struct CustomMut<'a>(&'a mut ());

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
error[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow-coerce-shared.rs:7:10
|
LL | #[derive(std::marker::Reborrow, std::marker::CoerceShared)]
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` 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[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow-coerce-shared.rs:7:33
|
LL | #[derive(std::marker::Reborrow, std::marker::CoerceShared)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` 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[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow-coerce-shared.rs:1:5
|
LL | use std::marker::CoerceShared;
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` 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[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow-coerce-shared.rs:1:5
|
Expand All @@ -7,7 +37,8 @@ LL | use std::marker::CoerceShared;
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 1 previous error
error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0658`.
6 changes: 5 additions & 1 deletion tests/ui/feature-gates/feature-gate-reborrow.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use std::marker::Reborrow; //~ ERROR use of unstable library feature `reborrow`
use std::marker::Reborrow; //~ ERROR use of unstable library feature `reborrow`
//~^ ERROR use of unstable library feature `reborrow`

#[derive(std::marker::Reborrow)] //~ ERROR use of unstable library feature `reborrow`
struct CustomMut<'a>(&'a mut ());

fn main() {}
23 changes: 22 additions & 1 deletion tests/ui/feature-gates/feature-gate-reborrow.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
error[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow.rs:4:10
|
LL | #[derive(std::marker::Reborrow)]
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` 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[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow.rs:1:5
|
LL | use std::marker::Reborrow;
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` 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[E0658]: use of unstable library feature `reborrow`
--> $DIR/feature-gate-reborrow.rs:1:5
|
Expand All @@ -7,7 +27,8 @@ LL | use std::marker::Reborrow;
= note: see issue #145612 <https://github.com/rust-lang/rust/issues/145612> for more information
= help: add `#![feature(reborrow)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 1 previous error
error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0658`.
Loading
Loading