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
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ where
| ty::Placeholder(..)
| ty::Alias(ty::IsRigid::No, _)
| ty::Bound(..)
| ty::Infer(_) => {
| ty::Infer(_)
| ty::Alias(ty::IsRigid::Yes, ty::AliasTy { kind: ty::Opaque { .. }, .. }) => {
panic!("unexpected type `{ty:?}`")
}

Expand Down Expand Up @@ -106,15 +107,6 @@ where
.map(Unnormalized::skip_norm_wip)
.collect(),
)),

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(),
]))
}
}
}

Expand Down
85 changes: 74 additions & 11 deletions compiler/rustc_next_trait_solver/src/solve/trait_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::SolverTraitLangItem;
use rustc_type_ir::solve::{
AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, MaybeInfo,
AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, ExternalConstraintsData, MaybeInfo,
NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunNonErased,
RerunReason, RerunResultExt, SizedTraitKind,
};
Expand Down Expand Up @@ -236,19 +236,11 @@ 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 }, .. }) =
if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) =
Comment thread
bit-aloo marked this conversation as resolved.
Resolved
goal.predicate.self_ty().kind()
{
debug_assert!(is_rigid == ty::IsRigid::Yes);

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());
}
}
return ecx.consider_auto_trait_candidate_for_opaque_ty(goal, def_id, args);
}

// We need to make sure to stall any coroutines we are inferring to avoid query cycles.
Expand Down Expand Up @@ -1286,6 +1278,77 @@ where
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
}

fn consider_auto_trait_candidate_for_opaque_ty(
&mut self,
goal: Goal<I, TraitClause<I>>,
def_id: I::OpaqueTyId,
args: I::GenericArgs,
) -> Result<Candidate<I>, NoSolutionOrRerunNonErased> {
let cx = self.cx();
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);

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());
}
}

let candidate = self.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)
})?;

// Proving an auto trait for the hidden type must not constrain inference
// variables, as that would leak the hidden type itself.
if !candidate.result.value.var_values.is_identity_modulo_regions() {
return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
}

let ExternalConstraintsData {
region_constraints: _,
ref opaque_types,
ref normalization_nested_goals,
} = *candidate.result.value.external_constraints;
debug_assert!(normalization_nested_goals.is_empty());

// New defining uses may leak an opaque's hidden type into the caller's
// inference state. This is safe after typeck, where hidden types are already
// fixed and only regions are inferred, but not during typeck while hidden
// types may still contain inference variables.
if !opaque_types.is_empty() {
let typing_mode = self.typing_mode();

match typing_mode {
// We're inferring regions of opaque types, but
// the type itself is already fully known, no way
// to leak the hidden type.
TypingMode::PostTypeckUntilBorrowck { .. } => {}
// We're inferring the hidden type of opaques, could
// leak types through it.
TypingMode::Typeck { .. } => {
return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
}
// we never add new uses to the opaque type storage
TypingMode::Coherence
| TypingMode::PostBorrowck { .. }
| TypingMode::Reflection
| TypingMode::PostAnalysis
| TypingMode::Codegen
| TypingMode::ErasedNotCoherence(MayBeErased) => {
unreachable!(
"we never add new uses to opaque types in typing mode {typing_mode:?}"
);
}
}
}

@lcnr lcnr Sep 21, 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.

feel like this could be written in a slightly cleaner way 🤔

we could definitely just ? the probe_trait_candidate.

And then maybe explicitly destructure candidate.result/ do

if !candidate.result.var_values.is_identity_modulo_regions() {
    return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
}

let ExternalConstraintData {
    region_constraints: _,
    opaque_types,
    normalization_nested_goals,
 } = candidate.result.external_constraints;
debug_assert!(normalization_nested_goals.is_empty());
if !opaque_types.is_empty() {
   blub
}

I feel like the way has_only_region_constraints and if allow_new_defining_uses && candidate.result.value.var_values.is_identity_modulo_regions() relate isn't very obvious

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is much better. Added


Ok(candidate)
}

// Return `Some` if there is an impl (built-in or user provided) that may
// hold for the self type of the goal, which for coherence and soundness
// purposes must disqualify the built-in auto impl assembled by considering
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// External crate for `opaque-hidden-ty-inference.rs`.
//
// `define` returns `WaddupGamers<T, closure>`. Its `Unpin` impl only holds if
// `<T as Leak>::Assoc` is that closure. The test's caller says `Assoc` is its
// own opaque type instead, so checking `Unpin` would try to set that opaque
// type to this closure.
pub struct WaddupGamers<T, U>(Option<T>, U);

impl<T: Leak<Assoc = U>, U> Unpin for WaddupGamers<T, U> {}

pub trait Leak {
type Assoc;
}

impl<T> Leak for T {
type Assoc = T;
}

pub fn define<T>() -> impl Sized {
WaddupGamers(None::<T>, || ())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//@ ignore-compare-mode-next-solver
//@ compile-flags: -Znext-solver
//@ aux-build:opaque-auto-trait-leakage.rs

//! Regression test for https://github.com/rust-lang/rust/issues/134578.
//! Leaking the auto traits of a foreign opaque must not constrain inference
//! variables in the caller, as that would leak the hidden type itself. Here
//! that would constrain `NameMe<T>` to a closure from the auxiliary crate and
//! ICE in typeck. The hidden type may still show up in the diagnostic.

#![feature(type_alias_impl_trait)]
#![allow(unused)]

extern crate opaque_auto_trait_leakage as dep;

use dep::*;

fn require_auto<T: Unpin>(x: T) -> T {
x
}

type NameMe<T> = impl Sized;

#[define_opaque(NameMe)]
fn leak<T>() -> NameMe<T>
where
T: Leak<Assoc = NameMe<T>>,
{
// Proving `impl Sized: Unpin` must not constrain `NameMe<T>`
// to the foreign closure hidden inside `define`.
let opaque = require_auto(define::<T>());
//~^ ERROR type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}`
let closure;
loop {}
return closure;
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
error[E0271]: type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}`
--> $DIR/opaque-hidden-ty-inference.rs:31:31
|
LL | let opaque = require_auto(define::<T>());
| ------------ ^^^^^^^^^^^^^ expected closure, found `!`
| |
| required by a bound introduced by this call
|
::: $DIR/auxiliary/opaque-auto-trait-leakage.rs:20:29
|
LL | WaddupGamers(None::<T>, || ())
| -- the expected closure
|
= note: expected closure `{closure@dep::define<T>::{closure#0}}`
found type `!`
= note: required for `WaddupGamers<T, {closure@dep::define<T>::{closure#0}}>` to implement `Unpin`
note: required because it appears within the type `impl Sized`
--> $DIR/auxiliary/opaque-auto-trait-leakage.rs:19:23
|
LL | pub fn define<T>() -> impl Sized {
| ^^^^^^^^^^
note: required by a bound in `require_auto`
--> $DIR/opaque-hidden-ty-inference.rs:18:20
|
LL | fn require_auto<T: Unpin>(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 E0271`.
Loading