Skip to content
Draft
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
6 changes: 6 additions & 0 deletions compiler/rustc_attr_ir/src/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,12 @@ pub enum AttributeKind {
/// Represents `#[rustc_allow_lifetime_dependent_specialization]`.
RustcAllowLifetimeDependentSpecialization,

/// Represents `#[rustc_anti_fundamental]`. This marks a trait so that it
/// cannot be implemented for non-local `#[fundamental]` types. This in particular
/// prevents the implementation of `Deref`, `DerefMut`, and `DispatchFromDyn` on
/// fundamental wrappers like `Pin` and `Box`.
RustcAntiFundamental,

/// Represents `#[rustc_as_ptr]` (used by the `dangling_pointers_from_temporaries` lint).
RustcAsPtr,

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_ir/src/encode_cross_crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ impl AttributeKind {
RustcAllowConstFnUnstable(..) => No,
RustcAllowIncoherentImpl(..) => No,
RustcAllowLifetimeDependentSpecialization => No,
RustcAntiFundamental => No,
RustcAsPtr => Yes,
RustcAutodiff(..) => Yes,
RustcBodyStability { .. } => No,
Expand Down
8 changes: 8 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ impl NoArgsAttributeParser for RustcCoinductiveParser {
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcCoinductive;
}

pub(crate) struct RustcAntiFundamentalParser;
impl NoArgsAttributeParser for RustcAntiFundamentalParser {
const PATH: &[Symbol] = &[sym::rustc_anti_fundamental];
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Trait)]);
const STABILITY: AttributeStability = unstable!(rustc_attrs);
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcAntiFundamental;
}

pub(crate) struct RustcAllowIncoherentImplParser;
impl NoArgsAttributeParser for RustcAllowIncoherentImplParser {
const PATH: &[Symbol] = &[sym::rustc_allow_incoherent_impl];
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ attribute_parsers!(
Single<WithoutArgs<RustcAllocatorZeroedParser>>,
Single<WithoutArgs<RustcAllowIncoherentImplParser>>,
Single<WithoutArgs<RustcAllowLifetimeDependentSpecializationParser>>,
Single<WithoutArgs<RustcAntiFundamentalParser>>,
Single<WithoutArgs<RustcAsPtrParser>>,
Single<WithoutArgs<RustcCanonicalSymbolParser>>,
Single<WithoutArgs<RustcCaptureAnalysisParser>>,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[
sym::rustc_never_returns_null_ptr,
sym::rustc_no_implicit_autorefs,
sym::rustc_coherence_is_core,
sym::rustc_anti_fundamental,
sym::rustc_coinductive,
sym::rustc_comptime,
sym::rustc_allow_incoherent_impl,
Expand Down
86 changes: 62 additions & 24 deletions compiler/rustc_hir_analysis/src/coherence/orphan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,41 @@ pub(crate) fn orphan_check_impl(

match orphan_check(tcx, impl_def_id, OrphanCheckMode::Proper) {
Ok(()) => {}
Err(err) => match orphan_check(tcx, impl_def_id, OrphanCheckMode::Compat) {
Ok(()) => match err {
OrphanCheckErr::UncoveredTyParams(uncovered_ty_params) => {
let hir_id = tcx.local_def_id_to_hir_id(impl_def_id);

for param_def_id in uncovered_ty_params.uncovered {
let ident = tcx.item_ident(param_def_id);

tcx.emit_node_span_lint(
UNCOVERED_PARAM_IN_PROJECTION,
hir_id,
ident.span,
diagnostics::UncoveredTyParam {
param: ident,
local_ty: uncovered_ty_params.local_ty,
},
);
Err(err) => {
if tcx.trait_def(trait_ref.def_id).is_anti_fundamental {
return Err(emit_orphan_check_error(tcx, trait_ref, impl_def_id, err));
}

match orphan_check(tcx, impl_def_id, OrphanCheckMode::Compat) {
Ok(()) => match err {
OrphanCheckErr::UncoveredTyParams(uncovered_ty_params) => {
let hir_id = tcx.local_def_id_to_hir_id(impl_def_id);

for param_def_id in uncovered_ty_params.uncovered {
let ident = tcx.item_ident(param_def_id);

tcx.emit_node_span_lint(
UNCOVERED_PARAM_IN_PROJECTION,
hir_id,
ident.span,
diagnostics::UncoveredTyParam {
param: ident,
local_ty: uncovered_ty_params.local_ty,
},
);
}
}
}
OrphanCheckErr::NonLocalInputType(_) => {
bug!("orphanck: shouldn't've gotten non-local input tys in compat mode")
}
},
Err(err) => return Err(emit_orphan_check_error(tcx, trait_ref, impl_def_id, err)),
},
OrphanCheckErr::NonLocalInputType(_) => {
bug!("orphanck: shouldn't've gotten non-local input tys in compat mode")
}
OrphanCheckErr::AntiFundamentalForeignType { .. } => {
// An anti-fundamental trait should return early above and never enter compat mode.
bug!("anti-fundamental traits never enter compat mode")
}
},
Err(err) => return Err(emit_orphan_check_error(tcx, trait_ref, impl_def_id, err)),
}
}
}

let trait_def_id = trait_ref.def_id;
Expand Down Expand Up @@ -381,6 +391,24 @@ fn orphan_check<'tcx>(
});
OrphanCheckErr::NonLocalInputType(tys)
}
OrphanCheckErr::AntiFundamentalForeignType { self_ty, fundamental_ty } => {
let (self_ty, fundamental_ty) = infcx.probe(|_| {
for (arg, id_arg) in
std::iter::zip(args, ty::GenericArgs::identity_for_item(tcx, impl_def_id))
{
let _ = infcx.at(&cause, ty::ParamEnv::empty()).eq(
DefineOpaqueTypes::No,
arg,
id_arg,
);
}
(
infcx.resolve_vars_if_possible(self_ty),
infcx.resolve_vars_if_possible(fundamental_ty),
)
});
OrphanCheckErr::AntiFundamentalForeignType { self_ty, fundamental_ty }
}
})
}

Expand Down Expand Up @@ -499,6 +527,16 @@ fn emit_orphan_check_error<'tcx>(
}
guar.unwrap()
}
traits::OrphanCheckErr::AntiFundamentalForeignType { self_ty, fundamental_ty } => {
let item = tcx.hir_expect_item(impl_def_id);
let impl_ = item.expect_impl();
tcx.dcx().emit_err(diagnostics::AntiFundamentalForeignImpl {
span: impl_.self_ty.span,
trait_name: tcx.def_path_str(trait_ref.def_id),
self_ty,
fundamental_ty,
})
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {

let deny_explicit_impl = find_attr!(attrs, RustcDenyExplicitImpl);
let force_dyn_incompatible = find_attr!(attrs, RustcDynIncompatibleTrait(span) => *span);
let is_anti_fundamental = find_attr!(attrs, RustcAntiFundamental);

ty::TraitDef {
def_id: def_id.to_def_id(),
Expand All @@ -1139,6 +1140,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef {
must_implement_one_of,
force_dyn_incompatible,
deny_explicit_impl,
is_anti_fundamental,
}
}

Expand Down
16 changes: 15 additions & 1 deletion compiler/rustc_hir_analysis/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2144,7 +2144,6 @@ pub(crate) struct OnlyStructsCanBeViewedAdt<'tcx> {
pub article: &'static str,
pub kind: &'static str,
}

#[derive(Diagnostic)]
#[diag("the type of const parameters must not depend on other generic parameters", code = E0770)]
pub(crate) struct ParamInTyOfConstParam<'tcx> {
Expand All @@ -2153,3 +2152,18 @@ pub(crate) struct ParamInTyOfConstParam<'tcx> {
pub(crate) span: Span,
pub(crate) ty: Ty<'tcx>,
}

#[derive(Diagnostic)]
#[diag("cannot implement `{$trait_name}` for the fundamental type `{$fundamental_ty}`")]
#[note(
"`{$trait_name}` is `#[rustc_anti_fundamental]` and \
cannot be implemented for `#[fundamental]` types from another crate"
)]
pub(crate) struct AntiFundamentalForeignImpl<'tcx> {
#[primary_span]
#[label("impl of `{$trait_name}` not allowed for `{$self_ty}`")]
pub(crate) span: Span,
pub(crate) trait_name: String,
pub(crate) self_ty: Ty<'tcx>,
pub(crate) fundamental_ty: Ty<'tcx>,
}
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/ty/context/impl_interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
self.trait_def(def_id).is_fundamental
}

fn trait_is_anti_fundamental(self, def_id: DefId) -> bool {
self.trait_def(def_id).is_anti_fundamental
}

fn trait_is_unsafe(self, trait_def_id: Self::DefId) -> bool {
self.trait_def(trait_def_id).safety.is_unsafe()
}
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_middle/src/ty/trait_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ pub struct TraitDef {
/// This only applies to built-in traits, and is marked via
/// `#[rustc_deny_explicit_impl]`.
pub deny_explicit_impl: bool,

/// If `true`, then this trait has the `#[rustc_anti_fundamental]` attribute
/// and cannot be implemented for `#[fundamental]` types from another crate.
/// Used for `Deref`, `DerefMut`, `DispatchFromDyn`, `CoerceUnsized`, etc.
pub is_anti_fundamental: bool,
}

/// Whether this trait is treated specially by the standard library
Expand Down
77 changes: 71 additions & 6 deletions compiler/rustc_next_trait_solver/src/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ pub enum InCrate {
Remote,
}

impl InCrate {
pub fn def_id_is_local<I: Interner>(self, def_id: impl DefId<I>) -> bool {
matches!(self, InCrate::Local { .. }) && def_id.is_local()
}
}

#[derive(Copy, Clone, Debug)]
pub enum OrphanCheckMode {
/// Proper orphan check.
Expand Down Expand Up @@ -118,6 +124,7 @@ impl From<bool> for IsFirstInputType {
pub enum OrphanCheckErr<I: Interner, T> {
NonLocalInputType(Vec<(I::Ty, IsFirstInputType)>),
UncoveredTyParams(UncoveredTyParams<I, T>),
AntiFundamentalForeignType { self_ty: I::Ty, fundamental_ty: I::Ty },
}

#[derive_where(Debug; I: Interner, T: Debug)]
Expand Down Expand Up @@ -160,6 +167,13 @@ pub struct UncoveredTyParams<I: Interner, T> {
/// - however, `LocalType<Vec<T>>` is OK, because `T` is a subtree of
/// `LocalType<Vec<T>>`, which is local and has no types between it and
/// the type parameter.
/// 5. If the trait is marked `#[rustc_anti_fundamental]`, the `Self` type
/// must not have a non-local `#[fundamental]` type at its head (even if
/// it wraps a local type as in (2)).
/// - e.g., `Box<LocalType>` or `&Pin<LocalType>` is rejected if the trait
/// is `#[rustc_anti_fundamental]`.
/// - This lets the standard library reserve control over traits like `Deref`

@dingxiangfei2009 dingxiangfei2009 Sep 3, 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 feel that we don't need bullet points to structure the motivation, it would be more readable if this is just a prose.

View changes since the review

/// and `DispatchFromDyn` on fundamental wrappers such as `Box` and `Pin`.
///
/// The orphan rules actually serve several different purposes:
///
Expand Down Expand Up @@ -223,7 +237,7 @@ pub fn orphan_check_trait_ref<Infcx, I, E: Debug>(
infcx: &Infcx,
trait_ref: ty::TraitRef<I>,
in_crate: InCrate,
lazily_normalize_ty: impl FnMut(I::Ty) -> Result<I::Ty, E>,
mut lazily_normalize_ty: impl FnMut(I::Ty) -> Result<I::Ty, E>,
) -> Result<Result<(), OrphanCheckErr<I, I::Ty>>, E>
where
Infcx: InferCtxtLike<Interner = I>,
Expand All @@ -234,6 +248,20 @@ where
panic!("orphan check only expects inference variables: {trait_ref:?}");
}

// Anti-fundamental check: if the trait is marked `#[rustc_anti_fundamental]`,
// we do not allow impls where the head of the Self type is a non-local fundamental
// type. This prevents downstream crates from implementing traits like `Deref` on
// fundamental wrappers like `Box` or `Pin`.
let cx = infcx.cx();
if cx.trait_is_anti_fundamental(trait_ref.def_id) {
let self_ty = trait_ref.self_ty();
if let Some(fundamental_ty) =
check_anti_fundamental_head(infcx, in_crate, &mut lazily_normalize_ty, self_ty)?
{
return Ok(Err(OrphanCheckErr::AntiFundamentalForeignType { self_ty, fundamental_ty }));
}
}

let mut checker = OrphanChecker::new(infcx, in_crate, lazily_normalize_ty);
Ok(match trait_ref.visit_with(&mut checker) {
ControlFlow::Continue(()) => Err(OrphanCheckErr::NonLocalInputType(checker.non_local_tys)),
Expand All @@ -256,6 +284,46 @@ where
})
}

/// Checks the head of the Self type for a non-local fundamental type.
///
/// If the head is a reference (`&` / `&mut`), we unwrap it and inspect the pointee type.
/// This ensures that wrapping a fundamental type in a reference (such as `&Pin<LocalType>`)

@dingxiangfei2009 dingxiangfei2009 Sep 3, 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.

Suggested change
/// This ensures that wrapping a fundamental type in a reference (such as `&Pin<LocalType>`)
/// This ensures that wrapping a fundamental type in a reference, say a `&Pin<LocalType>`,

View changes since the review

/// cannot be used to bypass the anti-fundamental restriction.
///
/// Returns `Some(ty)` with the offending fundamental type if the check fails.
fn check_anti_fundamental_head<Infcx, I, E: Debug>(
infcx: &Infcx,
in_crate: InCrate,
mut lazily_normalize_ty: impl FnMut(I::Ty) -> Result<I::Ty, E>,
mut ty: I::Ty,
) -> Result<Option<I::Ty>, E>
where
Infcx: InferCtxtLike<Interner = I>,
I: Interner,
{
loop {
ty = infcx.shallow_resolve(ty);
let norm_ty = match lazily_normalize_ty(ty)? {
norm if norm.is_ty_var() => ty,

@dingxiangfei2009 dingxiangfei2009 Sep 3, 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.

Huh, if norm is a type var, ty can't be normalized to the slightest bit I suppose. So it reads a bit off to me if we call it norm_ty... Wdyt?

View changes since the review

norm => norm,
};

if let ty::Ref(_, inner, _) = norm_ty.kind() {
ty = inner;
continue;
}

ty = norm_ty;
break;
}

Ok(matches!(
ty.kind(),
ty::Adt(def, _) if def.is_fundamental() && !in_crate.def_id_is_local(def.def_id())
)
.then_some(ty))
}

struct OrphanChecker<'a, Infcx, I: Interner, F> {
infcx: &'a Infcx,
in_crate: InCrate,
Expand Down Expand Up @@ -296,11 +364,8 @@ where
ControlFlow::Break(OrphanCheckEarlyExit::UncoveredTyParam(ty))
}

fn def_id_is_local(&mut self, def_id: impl DefId<I>) -> bool {
match self.in_crate {
InCrate::Local { .. } => def_id.is_local(),
InCrate::Remote => false,
}
fn def_id_is_local(&self, def_id: impl DefId<I>) -> bool {
self.in_crate.def_id_is_local(def_id)
}
}

Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
AttributeKind::RustcAllocatorZeroedVariant { .. } => (),
AttributeKind::RustcAllowIncoherentImpl(..) => (),
AttributeKind::RustcAllowLifetimeDependentSpecialization => (),
AttributeKind::RustcAntiFundamental => (),
AttributeKind::RustcAsPtr => (),
AttributeKind::RustcAutodiff(..) => (),
AttributeKind::RustcBodyStability { .. } => (),
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ symbols! {
PartialEq,
PartialOrd,
Pending,
PinDerefMutHelper,
PinMacroHelper,
Pointer,
Poll,
Expand Down Expand Up @@ -1782,6 +1781,7 @@ symbols! {
rustc_allow_incoherent_impl,
rustc_allow_lifetime_dependent_specialization,
rustc_allowed_through_unstable_modules,
rustc_anti_fundamental,
rustc_as_ptr,
rustc_attrs,
rustc_autodiff,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4432,23 +4432,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
// can do about it. As far as they are concerned, `?` is compiler magic.
return;
}
if tcx.is_diagnostic_item(sym::PinDerefMutHelper, parent_def_id) {
let parent_predicate =
self.resolve_vars_if_possible(data.derived.parent_trait_pred);

// Skip PinDerefMutHelper in suggestions, but still show downstream suggestions.

self.note_obligation_cause_code(
body_def_id,
err,
parent_predicate,
param_env,
&data.derived.parent_code,
obligated_types,
seen_requirements,
);
return;
}
let self_ty_str =
tcx.short_string(parent_trait_pred.skip_binder().self_ty(), err.long_ty_path());
let trait_name = tcx.short_string(
Expand Down
Loading
Loading