Skip to content

Support field-wise CoerceShared reborrows#157101

Open
P8L1 wants to merge 3 commits into
rust-lang:mainfrom
P8L1:fix-reborrow-coerceshared-field-relations
Open

Support field-wise CoerceShared reborrows#157101
P8L1 wants to merge 3 commits into
rust-lang:mainfrom
P8L1:fix-reborrow-coerceshared-field-relations

Conversation

@P8L1
Copy link
Copy Markdown
Contributor

@P8L1 P8L1 commented May 29, 2026

View all comments

This PR extends generic shared reborrows so that CoerceShared validates and lowers source-to-target structs field by field, rather than relying on a same-layout, transmute-like copy.

The core change is a shared rustc_middle::ty::reborrow helper that computes the field correspondence used by CoerceShared validation, borrow checking, const-eval, and codegen.

The correspondence rules are:

  • Named structs match non-PhantomData data fields by name.
  • Tuple structs match non-PhantomData data fields by position.
  • PhantomData fields are ignored.
  • Every target data field must correspond to a source data field.

CoerceShared impl validation now checks each corresponding field relation. A field relation is accepted when the field is:

  • Copy-compatible,
  • recursively CoerceShared-compatible, or
  • a mutable-reference-to-shared-reference reborrow.

The validation also rejects mismatched field styles, missing source fields, mismatched reborrow lifetimes, and inaccessible fields with targeted diagnostics.

Borrowck now adds shared generic reborrow constraints recursively through the validated field relations, while still issuing the loan for the original source place as a whole. This ensures source-only fields remain protected for the inferred target lifetime when the target omits fields from the source.

Const-eval and codegen now lower shared generic reborrows recursively into the target fields instead of treating the operation as a transmute-like copy. This covers nested CoerceShared fields and layout-changing source/target pairs.

Fixes

Fixes #156566.
Fixes #156309.
Fixes #156315.

PR scope

I did not split these into smaller PRs because the issues mostly share the same root cause: CoerceShared needed a field-by-field validation and lowering model. So when making this, I just decided to write it in a way that will fix the issues anyway.

Tracking

#145612
https://rust-lang.github.io/rust-project-goals/2025h2/autoreborrow-traits.html

CC. @aapoalas, @dingxiangfei2009
r? @RalfJung

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels May 29, 2026
@P8L1

This comment was marked as outdated.

@rustbot rustbot added the F-reborrow `#![feature(reborrow)]`; see #145612 label May 29, 2026
@P8L1

This comment was marked as outdated.

@P8L1

This comment was marked as outdated.

@rust-bors

This comment has been minimized.

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 29, 2026

🔨 5 commits were squashed into b8e47e8.

@rust-bors rust-bors Bot force-pushed the fix-reborrow-coerceshared-field-relations branch from 9713677 to b8e47e8 Compare May 29, 2026 09:36
@rust-log-analyzer

This comment has been minimized.

@P8L1 P8L1 force-pushed the fix-reborrow-coerceshared-field-relations branch from b8e47e8 to 83d9f22 Compare May 31, 2026 17:45
@rust-log-analyzer

This comment has been minimized.

@P8L1

This comment was marked as outdated.

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Jun 1, 2026

Unclosed quote in argument. Run @bors help to see available commands.

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 1, 2026

@bors squash msg="Support field-wise CoerceShared reborrows"

@rust-bors

This comment has been minimized.

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Jun 1, 2026

🔨 7 commits were squashed into 8987e4a.

@rust-bors rust-bors Bot force-pushed the fix-reborrow-coerceshared-field-relations branch from 62ec49c to 8987e4a Compare June 1, 2026 06:26
@P8L1 P8L1 marked this pull request as ready for review June 1, 2026 06:28
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jun 1, 2026

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

This PR changes MIR

cc @oli-obk, @RalfJung, @JakobDegen, @vakaras

The Cranelift subtree was changed

cc @bjorn3

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 1, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jun 1, 2026

r? @folkertdev

rustbot has assigned @folkertdev.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 73 candidates
  • Random selection from 17 candidates

@rustbot rustbot assigned RalfJung and unassigned folkertdev Jun 1, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Jun 1, 2026

RalfJung is not on the review rotation at the moment.
They may take a while to respond.

@RalfJung
Copy link
Copy Markdown
Member

RalfJung commented Jun 1, 2026

Why are you asking for my review here? I have nothing to do with this feature and don't have capacity to take on a new feature, especially not a massive PR like this.

@rustbot reroll

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 1, 2026

Why are you asking for my review here? I have nothing to do with this feature and don't have capacity to take on a new feature, especially not a massive PR like this.

@rustbot reroll

Oh I'm Sorry I did not think much.

I inferred from #156955 that you might be the right person to look specifically at the CTFE/Miri part, since that PR also touched reborrow const-eval/interpreter code and you reviewed it. But I understand now that this might be a bit to much to review on top of the work you already have..

Once again sorry, have a nice day

Comment thread compiler/rustc_const_eval/src/interpret/step.rs
@RalfJung
Copy link
Copy Markdown
Member

RalfJung commented Jun 1, 2026

Looks like this is associated with https://rust-lang.github.io/rust-project-goals/2026/reborrow-traits.html ?
Cc @aapoalas -- is there a long-term plan for this proposal? If such non-trivial MIR semantics are needed, a "small" compiler team ask may not quite reflect the amount of work this may take. Also see this thread.

@RalfJung
Copy link
Copy Markdown
Member

RalfJung commented Jun 1, 2026

r? @oli-obk
who is the compiler liaison for that goal.

Also @P8L1 when you do work for a project goal, that's a good thing to mention in the PR description. If you're not aware of the project goal here, please coordinate with @aapoalas and @oli-obk.

@rustbot rustbot assigned oli-obk and unassigned nnethercote Jun 1, 2026
@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 1, 2026

r? @oli-obk who is the compiler liaison for that goal.

Also @P8L1 when you do work for a project goal, that's a good thing to mention in the PR description. If you're not aware of the project goal here, please coordinate with @aapoalas and @oli-obk.

Hi Ralf, sorry, I always thought the tracking issue was enough. Thanks for the correction, I won't make the mistake again!

@RalfJung
Copy link
Copy Markdown
Member

RalfJung commented Jun 1, 2026

Fair, the tracking issue should in principle suffice. But not everyone always clicks through all the links. ;)

@oli-obk oli-obk assigned aapoalas and unassigned oli-obk Jun 1, 2026
@dingxiangfei2009
Copy link
Copy Markdown
Contributor

Hello @P8L1 and thank you for taking interest in the reborrow work.

With per-field reborrowing the proposed design will break away from a few fundamental but important assumptions and intended use cases of this language feature. A design document would certainly bring @aapoalas and me onto the same page and help us understand why the extension would be desirable. Let us chat on Zulip as well if you need any help.

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 1, 2026

Hello @P8L1 and thank you for taking interest in the reborrow work.

With per-field reborrowing the proposed design will break away from a few fundamental but important assumptions and intended use cases of this language feature. A design document would certainly bring @aapoalas and me onto the same page and help us understand why the extension would be desirable. Let us chat on Zulip as well if you need any help.

Thanks, that makes sense.

I wrote up a PR-specific design note here please review it and share your thoughts so this PR can proceed!

Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

Borrow-set gathering records MIR Reborrow loans.
This file now treats malformed generic reborrow shapes as delayed compiler bugs instead of hard panics.

View changes since the review

Comment on lines 305 to +330
} else if let &mir::Rvalue::Reborrow(target, mutability, borrowed_place) = rvalue {
let borrowed_place_ty = borrowed_place.ty(self.body, self.tcx).ty;
let &ty::Adt(reborrowed_adt, _reborrowed_args) = borrowed_place_ty.kind() else {
unreachable!()
self.tcx.dcx().span_delayed_bug(
self.body.source_info(location).span,
format!("generic reborrow source is not an ADT: {borrowed_place_ty:?}"),
);
return;
};
let &ty::Adt(target_adt, assigned_args) = target.kind() else {
self.tcx.dcx().span_delayed_bug(
self.body.source_info(location).span,
format!("generic reborrow target is not an ADT: {target:?}"),
);
return;
};
let &ty::Adt(target_adt, assigned_args) = target.kind() else { unreachable!() };
let Some(ty::GenericArgKind::Lifetime(region)) = assigned_args.get(0).map(|r| r.kind())
else {
bug!(
"hir-typeck passed but {} does not have a lifetime argument",
if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" }
self.tcx.dcx().span_delayed_bug(
self.body.source_info(location).span,
format!(
"hir-typeck passed but {} does not have a lifetime argument",
if mutability == Mutability::Mut { "Reborrow" } else { "CoerceShared" }
),
);
return;
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This replaces unreachable!/bug! assumptions with delayed bugs and early returns.
The borrow-set visitor can now avoid constructing a loan from invalid generic reborrow MIR.

View changes since the review

Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

Borrowck type checking is where generic reborrow MIR becomes region and type constraints.
The main change is replacing ad hoc one-field CoerceShared handling with recursive field-wise constraints.

View changes since the review

Comment on lines +2493 to +2509
let ty::Adt(_, dest_args) = dest_ty.kind() else {
span_mirbug!(
self,
borrowed_place,
"generic reborrow target is not an ADT: {dest_ty:?}"
);
return;
};
let Some(ty::GenericArgKind::Lifetime(dest_region)) = dest_args.get(0).map(|r| r.kind())
else {
span_mirbug!(
self,
borrowed_place,
"generic reborrow target does not have a lifetime argument: {dest_ty:?}"
);
return;
};
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This turns shape assumptions on the reborrow target into MIR bugs with early returns.
Borrowck still records liveness for the target lifetime, but only after proving the first generic arg is a lifetime.

View changes since the review

Comment on lines +2527 to +2543
// CoerceShared relates distinct ADTs field-wise. Impl validation guarantees that
// every target field has the corresponding source field and that each field relation
// is Copy-compatible, recursively CoerceShared-compatible, or `&mut T` to `&T`.
// The loan itself is still issued for `borrowed_place` as a whole, so source-only
// fields remain protected for the inferred target lifetime. Under the current
// single-lifetime impl model, that is the source lifetime.
if self
.add_generic_shared_reborrow_constraints(
location,
borrowed_place,
borrowed_ty,
dest_ty,
category,
)
.is_err()
{
return;
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The previous local name-based loop is replaced by a recursive helper call.
The key invariant is that validation, borrowck, and materialization all consume the same canonical field pairs.

View changes since the review

@theemathas
Copy link
Copy Markdown
Contributor

This PR currently does not correctly handle decl_macro hygiene:

#![feature(reborrow, decl_macro)]

use std::marker::{CoerceShared, Reborrow};

macro my_macro($field:ident) {
    pub struct MyMut<'a> {
        $field: &'a i32,
        field: &'a i64,
    }

    #[derive(Clone, Copy)]
    pub struct MyRef<'a> {
        $field: &'a i32,
        field: &'a i64,
    }

    impl Reborrow for MyMut<'_> {}

    impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
}

my_macro!(field);
error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field
  --> src/lib.rs:14:9
   |
 7 |         $field: &'a i32,
   |         --------------- source field `field` has type `&'a i32`
...
14 |         field: &'a i64,
   |         ^^^^^^^^^^^^^^ target field `field` has type `&'a i64`
...
22 | my_macro!(field);
   | ---------------- in this macro invocation
   |
   = note: this error originates in the macro `my_macro` (in Nightly builds, run with -Z macro-backtrace for more info)

Comment on lines 2546 to +2564
// Exclusive reborrow
self.relate_types(
if let Err(terr) = self.relate_types(
borrowed_ty,
ty::Variance::Covariant,
dest_ty,
location.to_locations(),
category,
)
.unwrap();
) {
span_mirbug!(
self,
borrowed_place,
"bad generic exclusive reborrow relation ({:?}: {:?}): {:?}",
borrowed_ty,
dest_ty,
terr
);
}
}
}
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

Exclusive reborrows keep the ordinary same-type relation but now report a MIR bug instead of unwrapping.
This is defensive behavior for malformed MIR and does not change the success path.

View changes since the review

}
}

fn add_generic_shared_reborrow_constraints(
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This helper recursively walks the validated CoerceShared field relation.
It normalizes field types in borrowck's inference context before deciding whether to relate leaves or recurse.

View changes since the review

Comment on lines +2583 to +2616
if let Err(terr) = self.relate_types_structurally_relating_aliases(
borrowed_ty.peel_refs(),
ty::Variance::Covariant,
dest_ty.peel_refs(),
location.to_locations(),
category,
) {
span_mirbug!(
self,
borrowed_place,
"bad generic shared reborrow field relation ({:?}: {:?}): {:?}",
borrowed_ty,
dest_ty,
terr
);
return Err(());
}

self.constraints.outlives_constraints.push(OutlivesConstraint {
sup: ref_region.as_var(),
sub: borrow_region.as_var(),
locations: location.to_locations(),
span: location.to_locations().span(self.body),
category,
variance_info: ty::VarianceDiagInfo::default(),
from_closure: false,
});
return Ok(());
}

match (borrowed_ty.kind(), dest_ty.kind()) {
(ty::Adt(borrowed_adt, borrowed_args), ty::Adt(dest_adt, dest_args))
if borrowed_adt.did() != dest_adt.did() =>
{
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The &mut T to &T leaf relates pointee types structurally and then emits the target-outlives-source constraint.
This mirrors built-in shared reborrowing while allowing aliases and projections in the pointee type.

View changes since the review

Comment on lines +2617 to +2656
let field_pairs = match reborrow::coerce_shared_field_pairs(
tcx,
*borrowed_adt,
*borrowed_args,
*dest_adt,
*dest_args,
) {
Ok(field_pairs) => field_pairs,
Err(CoerceSharedFieldPairError::FieldStyleMismatch) => {
span_mirbug!(
self,
borrowed_place,
"generic shared reborrow has unsupported field structure: \
{borrowed_ty:?} -> {dest_ty:?}"
);
return Err(());
}
Err(CoerceSharedFieldPairError::MissingSourceField { .. }) => {
span_mirbug!(
self,
borrowed_place,
"generic shared reborrow is missing a source field: \
{borrowed_ty:?} -> {dest_ty:?}"
);
return Err(());
}
};

for field_pair in field_pairs {
self.add_generic_shared_reborrow_constraints(
location,
borrowed_place,
field_pair.source.ty,
field_pair.target.ty,
category,
)?;
}

Ok(())
}
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

Distinct ADTs are expanded through canonical CoerceShared field pairs, then each field pair is related recursively.
This is the borrowck side of nested field-wise reborrows such as OuterMut to OuterRef.

View changes since the review

Comment on lines +2657 to +2677
_ => {
if let Err(terr) = self.relate_types_structurally_relating_aliases(
borrowed_ty,
ty::Variance::Covariant,
dest_ty,
location.to_locations(),
category,
) {
span_mirbug!(
self,
borrowed_place,
"bad generic shared reborrow field relation ({:?}: {:?}): {:?}",
borrowed_ty,
dest_ty,
terr
);
Err(())
} else {
Ok(())
}
}
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The fallback relates non-recursive leaves structurally, including aliases, using the same variance as the old field loop.
This covers copied scalar fields and equal field types after normalization.

View changes since the review

Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

HIR type checking creates the generic reborrow coercion adjustment before MIR borrowck sees it. This file adds a structural shape guard so only field-corresponding CoerceShared impls produce reborrow MIR.

View changes since the review

};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, Unnormalized, reborrow};
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

HIR typeck imports the shared reborrow helper for a structural shape guard before creating a reborrow adjustment. This keeps obviously malformed CoerceShared shapes out of MIR.

View changes since the review

}

/// Applies generic exclusive reborrowing on type implementing `Reborrow`.
/// Applies generic shared reborrowing on types implementing `CoerceShared`.
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The method comment is corrected to describe CoerceShared shared reborrowing, matching the code path below. The new shape guard rejects non-structs, missing lifetimes, and missing field correspondence before registering the trait obligation.

View changes since the review

Comment on lines +1008 to +1012
// This is only a shape guard for producing well-formed MIR adjustments. The field-type
// relation remains owned by the `CoerceShared` obligation and builtin impl validation.
if !self.coerce_shared_reborrow_structurally_well_formed(a, b) {
return Err(TypeError::Mismatch);
}
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This helper checks only ADT struct shape, exactly-one-lifetime presence, and successful canonical field pairing. It does not inspect per-field type compatibility beyond what the pair helper needs for field metadata.

View changes since the review

}
}

fn coerce_shared_reborrow_structurally_well_formed(&self, a: Ty<'tcx>, b: Ty<'tcx>) -> bool {
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This helper is the HIR typeck shape gate for producing ExprKind::Reborrow adjustments. It accepts only struct ADTs with one lifetime argument each and successful canonical field pairing.

View changes since the review

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 2, 2026

This PR currently does not correctly handle decl_macro hygiene:

#![feature(reborrow, decl_macro)]

use std::marker::{CoerceShared, Reborrow};

macro my_macro($field:ident) {
    pub struct MyMut<'a> {
        $field: &'a i32,
        field: &'a i64,
    }

    #[derive(Clone, Copy)]
    pub struct MyRef<'a> {
        $field: &'a i32,
        field: &'a i64,
    }

    impl Reborrow for MyMut<'_> {}

    impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
}

my_macro!(field);
error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field
  --> src/lib.rs:14:9
   |
 7 |         $field: &'a i32,
   |         --------------- source field `field` has type `&'a i32`
...
14 |         field: &'a i64,
   |         ^^^^^^^^^^^^^^ target field `field` has type `&'a i64`
...
22 | my_macro!(field);
   | ---------------- in this macro invocation
   |
   = note: this error originates in the macro `my_macro` (in Nightly builds, run with -Z macro-backtrace for more info)

Ok im going to fix this now then finish comments

Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This new helper module is the canonical home for Reborrow and CoerceShared field metadata: it extracts participating ADT fields, preserves projection indices and spans, filters PhantomData, and computes source-to-target field pairs.

View changes since the review

use crate::ty::{self, Ty, TyCtxt};

#[derive(Clone, Copy, Debug)]
pub struct ReborrowField<'tcx> {
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This struct is the data contract for one participating field. index preserves the real ADT field index for MIR projection, ident is needed for hygiene-aware named-field matching, name is kept for diagnostics, ty is the instantiated field type, and span lets validation point at the field that caused the problem.

The split between ident and name matters: name is useful for user-facing output, but matching named fields must not be reduced to raw textual-name equality.

View changes since the review

}

#[derive(Clone, Copy, Debug)]
pub struct CoerceSharedFieldPair<'tcx> {
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

CoerceSharedFieldPair is the normalized relation later compiler phases operate on: each target field has a specific source field. The error enum keeps correspondence failures field-local, distinguishing mismatched struct styles from a target field that has no valid source-side data field.

View changes since the review

}

/// Returns the instantiated non-`PhantomData` fields that participate in generic reborrows.
pub fn reborrow_data_fields<'tcx>(
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This function extracts the instantiated non-PhantomData fields that actually participate in field-wise reborrowing. It deliberately preserves the original FieldIdx, because matching ignores marker fields but MIR / CTFE / codegen still need to project from the real ADT layout.

View changes since the review

Comment on lines +74 to +78
if source_variant.ctor_kind() != target_variant.ctor_kind() {
return Err(CoerceSharedFieldPairError::FieldStyleMismatch);
}

let by_position = matches!(target_variant.ctor_kind(), Some(CtorKind::Fn));
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This is the boundary between named-field and tuple-field correspondence. CoerceShared does not allow named structs and tuple structs to be matched against each other, because the meaning of “corresponding field” is different in the two styles.

View changes since the review

let target_fields = reborrow_data_fields(tcx, target_def, target_args);

if by_position {
target_fields
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

Tuple structs match by filtered data-field ordinal, not by original field index. This lets PhantomData be ignored for correspondence while still preserving each field’s original FieldIdx inside the returned pair for later projection.

View changes since the review

if by_position {
target_fields
.into_iter()
.zip(source_fields.into_iter().map(Some).chain(std::iter::repeat(None)))
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The repeat(None) tail makes the target-driven failure mode explicit: every target data field must have a corresponding source data field. Extra source fields do not matter for constructing the shared target view, but missing source fields are rejected.

View changes since the review

Comment on lines +99 to +101
.find(|source| {
tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)
})
Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

This is the hygiene-sensitive part of named-field correspondence. Two fields can print with the same textual name under decl_macro while still being distinct identifiers, so this must use rustc’s hygienic field identity rather than raw Symbol equality. (The decl macro hygiene bug was caused by me just comparing names)

View changes since the review

tcx.hygienic_eq(target.ident, source.ident, source_variant.def_id)
})
.ok_or(CoerceSharedFieldPairError::MissingSourceField { target })?;

Copy link
Copy Markdown
Contributor Author

@P8L1 P8L1 Jun 2, 2026

Choose a reason for hiding this comment

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

The named-field branch is target-driven: each non-PhantomData target field must resolve to a hygienically corresponding source field. If no source field matches, the helper reports the target field as missing instead of silently omitting it or falling back to position.

View changes since the review

@theemathas
Copy link
Copy Markdown
Contributor

The CoerceShared impl check does not resolve associated types before checking whether the field types match. Is this intentional?

#![feature(reborrow)]

use std::marker::{CoerceShared, Reborrow};

trait Trait {
    type Assoc;
}
impl Trait for i32 {
    type Assoc = i64;
}

struct MyMut<'a> {
    x: &'a (),
    y: i64,
}

#[derive(Copy, Clone)]
struct MyRef<'a> {
    x: &'a (),
    y: <i32 as Trait>::Assoc,
}

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field
  --> src/lib.rs:20:5
   |
14 |     y: i64,
   |     ------ source field `y` has type `i64`
...
20 |     y: <i32 as Trait>::Assoc,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ target field `y` has type `<i32 as Trait>::Assoc`

@theemathas
Copy link
Copy Markdown
Contributor

The CoerceShared impl check does not verify that lifetimes match. Is this intentional?

// Compiles without errors

#![feature(reborrow)]

use std::marker::{CoerceShared, Reborrow};

struct MyMut<'a> {
    x: &'static (),
    y: &'a (),
}

#[derive(Copy, Clone)]
struct MyRef<'a> {
    x: &'a (),
    y: &'static (),
}

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 2, 2026

Ok I would like to start off by saying sorry for all the pings.
I have fixed the decl macro hygiene issues.

I have recently received valid criticism about the reviewability of this PR. Thus, I took my self-review notes, summarised them and posted them as review comments here. This was done to make it easier for reviewers to wrap their heads around the changes.

I have also updated the Design Note to reflect the new decl_macro hygiene changes

@P8L1
Copy link
Copy Markdown
Contributor Author

P8L1 commented Jun 2, 2026

The CoerceShared impl check does not resolve associated types before checking whether the field types match. Is this intentional?

#![feature(reborrow)]

use std::marker::{CoerceShared, Reborrow};

trait Trait {
    type Assoc;
}
impl Trait for i32 {
    type Assoc = i64;
}

struct MyMut<'a> {
    x: &'a (),
    y: i64,
}

#[derive(Copy, Clone)]
struct MyRef<'a> {
    x: &'a (),
    y: <i32 as Trait>::Assoc,
}

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}
error: implementing `CoerceShared` requires corresponding fields to match, be reborrowable with `CoerceShared`, or coerce a mutable reference field to a shared reference field
  --> src/lib.rs:20:5
   |
14 |     y: i64,
   |     ------ source field `y` has type `i64`
...
20 |     y: <i32 as Trait>::Assoc,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ target field `y` has type `<i32 as Trait>::Assoc`

The CoerceShared impl check does not verify that lifetimes match. Is this intentional?

// Compiles without errors

#![feature(reborrow)]

use std::marker::{CoerceShared, Reborrow};

struct MyMut<'a> {
    x: &'static (),
    y: &'a (),
}

#[derive(Copy, Clone)]
struct MyRef<'a> {
    x: &'a (),
    y: &'static (),
}

impl Reborrow for MyMut<'_> {}

impl<'a> CoerceShared<MyRef<'a>> for MyMut<'a> {}

Thanks, both of these were bugs in the impl validation rather than intentional behavior.

I pushed a fix in Validate CoerceShared field relations after normalization. CoerceShared field validation now structurally normalizes the field types before accepting equality or the built-in &mut T -> &T field relation, evaluates the generated obligations, and resolves region constraints before returning success.

That fixes the associated-type case because <i32 as Trait>::Assoc is normalized before the field equality check. It also rejects the swapped-lifetime case because field-internal lifetime constraints are now actually resolved instead of relying only on the top-level reborrow lifetime argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

F-reborrow `#![feature(reborrow)]`; see #145612 S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

10 participants