Skip to content
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
109 changes: 97 additions & 12 deletions compiler/rustc_builtin_macros/src/deriving/reborrow.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use rustc_ast::{self as ast, AttrArgs, GenericArg, GenericParamKind, Generics, ItemKind, token};
use rustc_ast::{
self as ast, AngleBracketedArg, AttrArgs, DUMMY_NODE_ID, GenericArg, GenericBound,
GenericParam, GenericParamKind, Generics, ItemKind, WherePredicate, WherePredicateKind,
WhereRegionPredicate, token,
};
use rustc_errors::E0802;
use rustc_expand::base::ExtCtxt;
use rustc_macros::Diagnostic;
use rustc_span::{Ident, Span, Symbol, sym};
use thin_vec::ThinVec;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym};
use thin_vec::{ThinVec, thin_vec};

macro_rules! path {
($span:expr, $($part:ident)::*) => { vec![$(Ident::new(sym::$part, $span),)*] }
Expand Down Expand Up @@ -33,15 +37,15 @@ pub(crate) fn expand_deriving_coerce_shared(
let Some((ident, generics)) = struct_def(cx, span, item, sym::CoerceShared) else {
return;
};
let Some(target) = coerce_shared_target(cx, span, item) else {
let Some((target, generics)) = coerce_shared_target(cx, span, item, generics) else {
return;
};

push_marker_impl(
cx,
span,
ident,
generics,
&generics,
sym::CoerceShared,
vec![GenericArg::Type(target)],
push,
Expand Down Expand Up @@ -71,7 +75,12 @@ fn struct_def<'a>(
}
}

fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &ast::Item) -> Option<Box<ast::Ty>> {
fn coerce_shared_target(
cx: &ExtCtxt<'_>,
span: Span,
item: &ast::Item,
generics: &Generics,
) -> Option<(Box<ast::Ty>, Generics)> {
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 });
Expand All @@ -92,7 +101,7 @@ fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &ast::Item) -> Optio
}

let mut parser = cx.new_parser_from_tts(args.tokens.clone());
let target = match parser.parse_ty() {
let mut target = match parser.parse_ty() {
Ok(target) => target,
Err(err) => {
err.cancel();
Expand All @@ -105,7 +114,79 @@ fn coerce_shared_target(cx: &ExtCtxt<'_>, span: Span, item: &ast::Item) -> Optio
return None;
}

Some(target)
let rustc_ast::TyKind::Path(_, path) = &mut target.kind else {
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
};

let Some(last) = path.segments.last_mut() else {
// FIXME(reborrow): we might want to support CoerceShared<Foo> for Bar at some point.
cx.dcx().emit_err(MalformedTarget { span: attr.span });

@oli-obk oli-obk Sep 28, 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.

probably want to use more precise for all the MalformedTarget errors, usually you have a specific item available which has its own span

View changes since the review

return None;
};

let Some(args) = last.args.as_deref_mut() else {
// FIXME(reborrow): same as above.
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
};

let rustc_ast::GenericArgs::AngleBracketed(args) = args else {
cx.dcx().emit_err(MalformedTarget { span: attr.span });
return None;
};

let mut own_params = generics.params.clone();
let mut param_insert_index = own_params
.iter()
.enumerate()
.find(|(_, arg)| {
matches!(
arg.kind,
rustc_ast::GenericParamKind::Lifetime | rustc_ast::GenericParamKind::Type { .. }
)
})
.map(|(i, _)| i)
.unwrap_or(own_params.len());
let mut own_where_clause = generics.where_clause.clone();

for arg in args.args.iter_mut() {
// Replace all lifetime parameters 'a with a new 'a_ where 'a: 'a_.
let AngleBracketedArg::Arg(GenericArg::Lifetime(arg)) = arg else {
continue;
};
let lt = rustc_ast::Lifetime {
id: DUMMY_NODE_ID,
ident: Ident::with_dummy_span(Symbol::intern(&format!("{}_", arg.ident.as_str()))),
};
// FIXME(reborrow): this is terribly inefficient.
own_params.insert(
param_insert_index,
GenericParam {
id: DUMMY_NODE_ID,
ident: lt.ident,
attrs: Default::default(),
bounds: Default::default(),
is_placeholder: false,
kind: GenericParamKind::Lifetime,
colon_span: None,
},
);
param_insert_index += 1;
own_where_clause.predicates.push(WherePredicate {
attrs: Default::default(),
kind: WherePredicateKind::RegionPredicate(WhereRegionPredicate {
lifetime: *arg,
bounds: thin_vec![GenericBound::Outlives(lt)],
}),
id: DUMMY_NODE_ID,
span: DUMMY_SP,
is_placeholder: false,
});
*arg = lt;
}

Some((target, Generics { params: own_params, where_clause: own_where_clause, span }))
}

fn push_marker_impl(
Expand All @@ -125,15 +206,19 @@ fn push_marker_impl(
let self_params: Vec<_> = generics
.params
.iter()
.map(|param| match param.kind {
.filter_map(|param| match param.kind {
GenericParamKind::Lifetime => {
GenericArg::Lifetime(cx.lifetime(param.span(), param.ident))
if trait_name == sym::CoerceShared && !param.ident.as_str().ends_with("_") {

@dingxiangfei2009 dingxiangfei2009 Oct 1, 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.

What would happen if the lifetime generic already has a _ suffix?

View changes since the review

None
} else {
Some(GenericArg::Lifetime(cx.lifetime(param.span(), param.ident)))
}
}
GenericParamKind::Type { .. } => {
GenericArg::Type(cx.ty_ident(param.span(), param.ident))
Some(GenericArg::Type(cx.ty_ident(param.span(), param.ident)))
}
GenericParamKind::Const { .. } => {
GenericArg::Const(cx.const_ident(param.span(), param.ident))
Some(GenericArg::Const(cx.const_ident(param.span(), param.ident)))
}
})
.collect();
Expand Down
18 changes: 1 addition & 17 deletions compiler/rustc_hir_analysis/src/coherence/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ pub(crate) fn reborrow_info<'tcx>(
let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip();
let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip();

if trait_impl_lifetime_params_count(tcx, impl_did) != 1 {
if trait_impl_lifetime_params_count(tcx, impl_did) == 0 {
return Err(tcx
.dcx()
.emit_err(diagnostics::CoerceSharedNotSingleLifetimeParam { span, trait_name }));
Expand All @@ -492,20 +492,8 @@ pub(crate) fn reborrow_info<'tcx>(
}
};

let lifetimes_count = generic_lifetime_params_count(args);
let data_fields = collect_reborrow_data_fields(tcx, def, args);

if lifetimes_count != 1 {
let item = tcx.hir_expect_item(impl_did);
let _span = if let ItemKind::Impl(hir::Impl { of_trait: Some(of_trait), .. }) = &item.kind {
of_trait.trait_ref.path.span
} else {
tcx.def_span(impl_did)
};

return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMulti { span, trait_name }));
}

if data_fields.is_empty() {
return Ok(());
}
Expand Down Expand Up @@ -548,10 +536,6 @@ fn trait_impl_lifetime_params_count(tcx: TyCtxt<'_>, did: LocalDefId) -> usize {
.count()
}

fn generic_lifetime_params_count(args: &[ty::GenericArg<'_>]) -> usize {
args.iter().filter(|arg| arg.as_region().is_some()).count()
}

#[derive(Clone, Copy)]
struct ReborrowDataField<'tcx> {
ident: Ident,
Expand Down
40 changes: 9 additions & 31 deletions compiler/rustc_hir_analysis/src/coherence/builtin/coerce_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use rustc_errors::ErrorGuaranteed;
use rustc_hir as hir;
use rustc_hir::ItemKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, TyCtxtInferExt};
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, SubregionOrigin, TyCtxtInferExt};
use rustc_infer::traits::{Obligation, TraitErrors};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypingMode, Unnormalized};
use rustc_span::Span;
Expand Down Expand Up @@ -239,7 +239,7 @@ pub(super) fn coerce_shared_info<'tcx>(
let source = tcx.type_of(impl_did).instantiate_identity().skip_norm_wip();
let trait_ref = tcx.impl_trait_ref(impl_did).instantiate_identity().skip_norm_wip();

if trait_impl_lifetime_params_count(tcx, impl_did) != 1 {
if trait_impl_lifetime_params_count(tcx, impl_did) == 0 {
return Err(tcx
.dcx()
.emit_err(diagnostics::CoerceSharedNotSingleLifetimeParam { span, trait_name }));
Comment on lines -242 to 245

@oli-obk oli-obk Sep 28, 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.

probably needs updating of the diagnostic (and at minimum of the diagnostic struct name)

View changes since the review

Expand All @@ -266,25 +266,6 @@ pub(super) fn coerce_shared_info<'tcx>(
(&ty::Adt(def_a, args_a), &ty::Adt(def_b, args_b))
if def_a.is_struct() && def_b.is_struct() =>
{
let a_lifetime = single_region_arg(args_a);
let b_lifetime = single_region_arg(args_b);

if a_lifetime.is_none() || b_lifetime.is_none() {
return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedMulti {
span: diagnostic_context.trait_span,
trait_name,
}));
}

if a_lifetime != b_lifetime {
return Err(tcx.dcx().emit_err(diagnostics::CoerceSharedLifetimeMismatch {
span: diagnostic_context.trait_span,
source_lifetime_span: diagnostic_context.source_lifetime_span,
target_lifetime_span: diagnostic_context.target_lifetime_span,
trait_name,
}));
}

validate_reborrow_field_access(
tcx,
impl_did,
Expand Down Expand Up @@ -341,12 +322,6 @@ enum CoerceSharedFieldPairError<'tcx> {
MissingSourceField { target: ReborrowDataField<'tcx> },
}

fn single_region_arg<'tcx>(args: ty::GenericArgsRef<'tcx>) -> Option<ty::Region<'tcx>> {
let mut lifetimes = args.iter().filter_map(|arg| arg.as_region());
let lifetime = lifetimes.next()?;
lifetimes.next().is_none().then_some(lifetime)
}

// This is a coherence/WF check only. It verifies that the CoerceShared impl
// describes a structurally valid field-wise relation. Runtime lowering of the
// operation is not modeled here.
Expand Down Expand Up @@ -789,7 +764,7 @@ fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>(
FieldRelation::Equal => {
if infcx
.at(&cause, param_env)
.relate(DefineOpaqueTypes::Yes, source_ty, ty::Variance::Invariant, target_ty)
.relate(DefineOpaqueTypes::Yes, source_ty, ty::Variance::Covariant, target_ty)
.is_err()
{
return false;
Expand All @@ -803,9 +778,12 @@ fn field_tys_satisfy_relation_after_normalization_and_resolution<'tcx>(
else {
return false;
};
if source_region != target_region {
return false;
}
infcx.sub_regions(
SubregionOrigin::RelateObjectBound(span),
target_region,
source_region,
ty::VisibleForLeakCheck::Yes,
);
if ocx.sup(&cause, param_env, target_referent_ty, source_referent_ty).is_err() {
return false;
}
Comment on lines 765 to 789

@oli-obk oli-obk Sep 28, 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.

I couldn't find any in-tree docs about the lifetime handling. Should probably add some now that you aren't requiring equality anymore. I had to grab a variance table and poke at the code and tests here until I was sure it was all in the right direction 😆

View changes since the review

Expand Down
25 changes: 0 additions & 25 deletions compiler/rustc_hir_analysis/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1360,31 +1360,6 @@ pub(crate) struct CoerceSharedNotSingleLifetimeParam {
pub trait_name: &'static str,
}

#[derive(Diagnostic)]
#[diag(
"implementing `{$trait_name}` requires exactly one lifetime argument in the reborrowed type"
)]
pub(crate) struct CoerceSharedMulti {
#[primary_span]
pub span: Span,
pub trait_name: &'static str,
}

#[derive(Diagnostic)]
#[diag(
"implementing `{$trait_name}` requires source and target to use the same reborrow lifetime \
argument"
)]
pub(crate) struct CoerceSharedLifetimeMismatch {
#[primary_span]
pub span: Span,
#[label("source reborrow lifetime")]
pub source_lifetime_span: Option<Span>,
#[label("target reborrow lifetime")]
pub target_lifetime_span: Option<Span>,
pub trait_name: &'static str,
}

#[derive(Diagnostic)]
#[diag(
"implementing `{$trait_name}` requires corresponding fields to match, \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct CustomMarkerRef<'a>(PhantomData<(Debug, Clone, Copy)>);
//~| ERROR

impl<'a> Reborrow for CustomMarker<'a> {}
impl<'a> CoerceShared<CustomMarkerRef<'a>> for CustomMarker<'a> {}
impl<'a: 'b, 'b> CoerceShared<CustomMarkerRef<'b>> for CustomMarker<'a> {}
//~^ ERROR

fn method<'a>(_a: CustomMarkerRef<'a>) -> 'a () {
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/reborrow/coerce-shared-associated-type-field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct MyRef<'a> {

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
impl<'a: 'b, 'b> CoerceShared<MyRef<'b>> for MyMut<'a> {}
//~^ ERROR

fn main() {}
10 changes: 5 additions & 5 deletions tests/ui/reborrow/coerce-shared-associated-type-field.stderr
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
error: implementing `CoerceShared` currently requires source and target to have at most one non-ZST reborrow data field
--> $DIR/coerce-shared-associated-type-field.rs:30:1
|
LL | impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
| ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^
| | | |
| | | source type has 2 non-ZST reborrow data fields
| | target type has 2 non-ZST reborrow data fields
LL | impl<'a: 'b, 'b> CoerceShared<MyRef<'b>> for MyMut<'a> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^
| | | |
| | | source type has 2 non-ZST reborrow data fields
| | target type has 2 non-ZST reborrow data fields
| in this `CoerceShared` implementation
|
= note: this is a temporary restriction until `CoerceShared` lowering supports non-trivially memcpy-compatible field layouts
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/reborrow/coerce-shared-decl-macro-hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ macro my_macro($field:ident) {

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
impl<'a: 'b, 'b> CoerceShared<MyRef<'b>> for MyMut<'a> {}
//~^ ERROR
}

Expand Down
10 changes: 5 additions & 5 deletions tests/ui/reborrow/coerce-shared-decl-macro-hygiene.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ error: implementing `CoerceShared` currently requires source and target to have
|
LL | macro my_macro($field:ident) {
...
LL | impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
| ^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^
| | | |
| | | source type has 2 non-ZST reborrow data fields
| | target type has 2 non-ZST reborrow data fields
LL | impl<'a: 'b, 'b> CoerceShared<MyRef<'b>> for MyMut<'a> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------^^^^^^---------^^^
| | | |
| | | source type has 2 non-ZST reborrow data fields
| | target type has 2 non-ZST reborrow data fields
| in this `CoerceShared` implementation
...
LL | my_macro!(field);
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/reborrow/coerce-shared-field-lifetime-swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ impl Reborrow for MyMut<'_> {}
#[derive(Copy, Clone)]
struct MyRef<'a> {
x: &'a (),
//~^ ERROR
y: &'static (),
//~^ ERROR
}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
impl<'a: 'b, 'b> CoerceShared<MyRef<'b>> for MyMut<'a> {}

fn main() {}
Loading
Loading