From 690f3339811e6635ef23b52031444e22baaadc6a Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Sat, 18 Jul 2026 13:03:55 +0000 Subject: [PATCH 1/3] add opaque test --- .../auxiliary/opaque-auto-trait-leakage.rs | 15 +++++++++ .../opaque-hidden-ty-inference.rs | 32 +++++++++++++++++++ .../opaque-hidden-ty-inference.stderr | 18 +++++++++++ 3 files changed, 65 insertions(+) create mode 100644 tests/ui/impl-trait/auto-trait-leakage/auxiliary/opaque-auto-trait-leakage.rs create mode 100644 tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.rs create mode 100644 tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.stderr diff --git a/tests/ui/impl-trait/auto-trait-leakage/auxiliary/opaque-auto-trait-leakage.rs b/tests/ui/impl-trait/auto-trait-leakage/auxiliary/opaque-auto-trait-leakage.rs new file mode 100644 index 0000000000000..d7a2c4a8642eb --- /dev/null +++ b/tests/ui/impl-trait/auto-trait-leakage/auxiliary/opaque-auto-trait-leakage.rs @@ -0,0 +1,15 @@ +pub struct WaddupGamers(Option, U); + +impl, U> Unpin for WaddupGamers {} + +pub trait Leak { + type Assoc; +} + +impl Leak for T { + type Assoc = T; +} + +pub fn define() -> impl Sized { + WaddupGamers(None::, || ()) +} diff --git a/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.rs b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.rs new file mode 100644 index 0000000000000..97a9e4d393356 --- /dev/null +++ b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.rs @@ -0,0 +1,32 @@ +//@ ignore-compare-mode-next-solver +//@ compile-flags: -Znext-solver +//@ aux-build:opaque-auto-trait-leakage.rs + +#![feature(type_alias_impl_trait)] +#![allow(unused)] + +extern crate opaque_auto_trait_leakage as dep; + +use dep::*; + +fn require_auto(x: T) -> T { + x +} + +type NameMe = impl Sized; + +#[define_opaque(NameMe)] +fn leak() -> NameMe +where + T: Leak>, +{ + // Proving `impl Sized: Unpin` must not constrain `NameMe` + // to the foreign closure hidden inside `define`. + let opaque = require_auto(define::()); + //~^ ERROR type annotations needed + let closure; + loop {} + return closure; +} + +fn main() {} diff --git a/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.stderr b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.stderr new file mode 100644 index 0000000000000..d42546ed9ed14 --- /dev/null +++ b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.stderr @@ -0,0 +1,18 @@ +error[E0283]: type annotations needed: cannot satisfy `impl Sized: Unpin` + --> $DIR/opaque-hidden-ty-inference.rs:25:31 + | +LL | let opaque = require_auto(define::()); + | ------------ ^^^^^^^^^^^^^ + | | + | required by a bound introduced by this call + | + = note: cannot satisfy `impl Sized: Unpin` +note: required by a bound in `require_auto` + --> $DIR/opaque-hidden-ty-inference.rs:12:20 + | +LL | fn require_auto(x: T) -> T { + | ^^^^^ required by this bound in `require_auto` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0283`. From c868c7d1fc86eb17a591db9bbad6701f708915ba Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Mon, 20 Jul 2026 07:00:56 +0000 Subject: [PATCH 2/3] next-solver: avoid leaking opaque hidden types via auto trait probes --- .../src/solve/trait_goals.rs | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 09d96e3b3a143..20c700dc54496 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -235,7 +235,8 @@ where // when merging candidates anyways. // // See tests/ui/impl-trait/auto-trait-leakage/avoid-query-cycle-via-item-bound.rs. - if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, .. }) = + let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc); + if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) = goal.predicate.self_ty().kind() { debug_assert!(is_rigid == ty::IsRigid::Yes); @@ -252,6 +253,29 @@ where return Err(NoSolution.into()); } } + + let candidate = ecx.probe_trait_candidate(source).enter(|ecx| { + let hidden_ty = cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(); + ecx.add_goal( + GoalSource::ImplWhereBound, + goal.with(cx, goal.predicate.with_replaced_self_ty(cx, hidden_ty)), + )?; + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }); + + // If this hidden-type proof would constrain non-region inference, + // treating it as a hard success can reveal the hidden type to the + // caller. A hard failure can do the same when it depends on caller + // bounds. For now, we are very conservative with caller bounds. + let param_env_may_leak_hidden_ty = !goal.param_env.caller_bounds().is_empty(); + return match candidate { + Ok(candidate) if has_only_region_constraints(candidate.result) => Ok(candidate), + Ok(_) => ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS), + Err(NoSolutionOrRerunNonErased::NoSolution(_)) if param_env_may_leak_hidden_ty => { + ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS) + } + Err(err) => Err(err), + }; } // We need to make sure to stall any coroutines we are inferring to avoid query cycles. @@ -260,7 +284,7 @@ where } ecx.probe_and_evaluate_goal_for_constituent_tys( - CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + source, goal, structural_traits::instantiate_constituent_tys_for_auto_trait, ) From 2e546e089b5f8a767e0e0e8379e5a558592b6ec7 Mon Sep 17 00:00:00 2001 From: bit-aloo Date: Tue, 21 Jul 2026 06:55:05 +0000 Subject: [PATCH 3/3] define consider_auto_trait_candidate_for_opaque_ty and call it from consider_auto_trait_candidate --- .../src/solve/assembly/structural_traits.rs | 67 ++++++++++++++++--- .../src/solve/trait_goals.rs | 56 +++------------- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 2703848162b3d..88b88e85dd4d8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -5,17 +5,21 @@ use derive_where::derive_where; use rustc_type_ir::data_structures::HashMap; use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::{SolverProjectionLangItem, SolverTraitLangItem}; -use rustc_type_ir::solve::SizedTraitKind; use rustc_type_ir::solve::inspect::ProbeKind; +use rustc_type_ir::solve::{MaybeInfo, NoSolutionOrRerunNonErased, RerunReason, SizedTraitKind}; use rustc_type_ir::{ - self as ty, Binder, FallibleTypeFolder, Interner, Movability, Mutability, TypeFoldable, - TypeSuperFoldable, Unnormalized, Upcast as _, elaborate, + self as ty, Binder, FallibleTypeFolder, Interner, Movability, Mutability, TraitPredicate, + TypeFoldable, TypeSuperFoldable, Unnormalized, Upcast as _, elaborate, }; use rustc_type_ir_macros::{TypeFoldable_Generic, TypeVisitable_Generic}; use tracing::instrument; +use super::Candidate; use crate::delegate::SolverDelegate; -use crate::solve::{AdtDestructorKind, EvalCtxt, Goal, NoSolution}; +use crate::solve::{ + AdtDestructorKind, BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, + NoSolution, has_only_region_constraints, +}; // Calculates the constituent types of a type for `auto trait` purposes. #[instrument(level = "trace", skip(ecx), ret)] @@ -104,15 +108,56 @@ where .map(Unnormalized::skip_norm_wip) .collect(), )), + // Opaque types are already handled earlier + _ => unreachable!(), + } +} + +pub(in crate::solve) fn consider_auto_trait_candidate_for_opaque_ty( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal>, + def_id: I::OpaqueTyId, + args: I::GenericArgs, +) -> Result, NoSolutionOrRerunNonErased> +where + D: SolverDelegate, + I: Interner, +{ + let cx = ecx.cx(); + let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc); + if ecx.opaque_accesses.might_rerun() { + return match ecx.opaque_accesses.rerun_always(RerunReason::AutoTraitLeakage) { + Err(e) => Err(e.into()), + }; + } + + for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() { + if item_bound.as_trait_clause().is_some_and(|b| b.def_id() == goal.predicate.def_id()) { + return Err(NoSolution.into()); + } + } - ty::Alias(ty::IsRigid::Yes, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) => { - // We can resolve the `impl Trait` to its concrete type, - // which enforces a DAG between the functions requiring - // the auto trait bounds in question. - Ok(ty::Binder::dummy(vec![ - cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(), - ])) + let candidate = ecx.probe_trait_candidate(source).enter(|ecx| { + let hidden_ty = cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(); + ecx.add_goal( + GoalSource::ImplWhereBound, + goal.with(cx, goal.predicate.with_replaced_self_ty(cx, hidden_ty)), + )?; + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }); + + // If this hidden-type proof would constrain non-region inference, + // treating it as a hard success can reveal the hidden type to the + // caller. A hard failure can do the same when it depends on caller + // bounds. For now, we are very conservative with caller bounds. + let param_env_may_leak_hidden_ty = !goal.param_env.caller_bounds().is_empty(); + match candidate { + Ok(candidate) if has_only_region_constraints(candidate.result) => Ok(candidate), + Ok(_) => ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS), + Err(NoSolutionOrRerunNonErased::NoSolution(_)) if param_env_may_leak_hidden_ty => { + ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS) } + Err(err) => Err(err), } } diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 20c700dc54496..cb70e7f426037 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -235,59 +235,23 @@ where // when merging candidates anyways. // // See tests/ui/impl-trait/auto-trait-leakage/avoid-query-cycle-via-item-bound.rs. - let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc); if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) = goal.predicate.self_ty().kind() { debug_assert!(is_rigid == ty::IsRigid::Yes); - if ecx.opaque_accesses.might_rerun() { - ecx.opaque_accesses.rerun_always(RerunReason::AutoTraitLeakage)?; - return Err(NoSolution.into()); - } - - for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() { - if item_bound - .as_trait_clause() - .is_some_and(|b| b.def_id() == goal.predicate.def_id()) - { - return Err(NoSolution.into()); - } + structural_traits::consider_auto_trait_candidate_for_opaque_ty(ecx, goal, def_id, args) + } else { + // We need to make sure to stall any coroutines we are inferring to avoid query cycles. + if let Some(cand) = ecx.try_stall_coroutine(goal.predicate.self_ty()) { + return cand; } - let candidate = ecx.probe_trait_candidate(source).enter(|ecx| { - let hidden_ty = cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(); - ecx.add_goal( - GoalSource::ImplWhereBound, - goal.with(cx, goal.predicate.with_replaced_self_ty(cx, hidden_ty)), - )?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }); - - // If this hidden-type proof would constrain non-region inference, - // treating it as a hard success can reveal the hidden type to the - // caller. A hard failure can do the same when it depends on caller - // bounds. For now, we are very conservative with caller bounds. - let param_env_may_leak_hidden_ty = !goal.param_env.caller_bounds().is_empty(); - return match candidate { - Ok(candidate) if has_only_region_constraints(candidate.result) => Ok(candidate), - Ok(_) => ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS), - Err(NoSolutionOrRerunNonErased::NoSolution(_)) if param_env_may_leak_hidden_ty => { - ecx.forced_ambiguity(MaybeInfo::AMBIGUOUS) - } - Err(err) => Err(err), - }; - } - - // We need to make sure to stall any coroutines we are inferring to avoid query cycles. - if let Some(cand) = ecx.try_stall_coroutine(goal.predicate.self_ty()) { - return cand; + ecx.probe_and_evaluate_goal_for_constituent_tys( + CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + goal, + structural_traits::instantiate_constituent_tys_for_auto_trait, + ) } - - ecx.probe_and_evaluate_goal_for_constituent_tys( - source, - goal, - structural_traits::instantiate_constituent_tys_for_auto_trait, - ) } fn consider_trait_alias_candidate(