Skip to content

Commit cd992b4

Browse files
Rollup merge of #159589 - bit-aloo:2026-07-29-opaque-type, r=lcnr
Avoid leaking opaque hidden types via auto trait candidates closes: #134578
2 parents ba87438 + bae45db commit cd992b4

5 files changed

Lines changed: 165 additions & 21 deletions

File tree

‎compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ where
6060
| ty::Placeholder(..)
6161
| ty::Alias(ty::IsRigid::No, _)
6262
| ty::Bound(..)
63-
| ty::Infer(_) => {
63+
| ty::Infer(_)
64+
| ty::Alias(ty::IsRigid::Yes, ty::AliasTy { kind: ty::Opaque { .. }, .. }) => {
6465
panic!("unexpected type `{ty:?}`")
6566
}
6667

@@ -106,15 +107,6 @@ where
106107
.map(Unnormalized::skip_norm_wip)
107108
.collect(),
108109
)),
109-
110-
ty::Alias(ty::IsRigid::Yes, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) => {
111-
// We can resolve the `impl Trait` to its concrete type,
112-
// which enforces a DAG between the functions requiring
113-
// the auto trait bounds in question.
114-
Ok(ty::Binder::dummy(vec![
115-
cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip(),
116-
]))
117-
}
118110
}
119111
}
120112

‎compiler/rustc_next_trait_solver/src/solve/trait_goals.rs‎

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt;
55
use rustc_type_ir::inherent::*;
66
use rustc_type_ir::lang_items::SolverTraitLangItem;
77
use rustc_type_ir::solve::{
8-
AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, MaybeInfo,
8+
AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, ExternalConstraintsData, MaybeInfo,
99
NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunNonErased,
1010
RerunReason, RerunResultExt, SizedTraitKind,
1111
};
@@ -236,19 +236,11 @@ where
236236
// when merging candidates anyways.
237237
//
238238
// See tests/ui/impl-trait/auto-trait-leakage/avoid-query-cycle-via-item-bound.rs.
239-
if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, .. }) =
239+
if let ty::Alias(is_rigid, ty::AliasTy { kind: ty::Opaque { def_id }, args, .. }) =
240240
goal.predicate.self_ty().kind()
241241
{
242242
debug_assert!(is_rigid == ty::IsRigid::Yes);
243-
244-
for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() {
245-
if item_bound
246-
.as_trait_clause()
247-
.is_some_and(|b| b.def_id() == goal.predicate.def_id())
248-
{
249-
return Err(NoSolution.into());
250-
}
251-
}
243+
return ecx.consider_auto_trait_candidate_for_opaque_ty(goal, def_id, args);
252244
}
253245

254246
// We need to make sure to stall any coroutines we are inferring to avoid query cycles.
@@ -1289,6 +1281,77 @@ where
12891281
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
12901282
}
12911283

1284+
fn consider_auto_trait_candidate_for_opaque_ty(
1285+
&mut self,
1286+
goal: Goal<I, TraitClause<I>>,
1287+
def_id: I::OpaqueTyId,
1288+
args: I::GenericArgs,
1289+
) -> Result<Candidate<I>, NoSolutionOrRerunNonErased> {
1290+
let cx = self.cx();
1291+
let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc);
1292+
1293+
for item_bound in cx.item_self_bounds(def_id.into()).skip_binder() {
1294+
if item_bound.as_trait_clause().is_some_and(|b| b.def_id() == goal.predicate.def_id()) {
1295+
return Err(NoSolution.into());
1296+
}
1297+
}
1298+
1299+
let candidate = self.probe_trait_candidate(source).enter(|ecx| {
1300+
let hidden_ty = cx.type_of(def_id.into()).instantiate(cx, args).skip_norm_wip();
1301+
ecx.add_goal(
1302+
GoalSource::ImplWhereBound,
1303+
goal.with(cx, goal.predicate.with_replaced_self_ty(cx, hidden_ty)),
1304+
)?;
1305+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
1306+
})?;
1307+
1308+
// Proving an auto trait for the hidden type must not constrain inference
1309+
// variables, as that would leak the hidden type itself.
1310+
if !candidate.result.value.var_values.is_identity_modulo_regions() {
1311+
return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
1312+
}
1313+
1314+
let ExternalConstraintsData {
1315+
region_constraints: _,
1316+
ref opaque_types,
1317+
ref normalization_nested_goals,
1318+
} = *candidate.result.value.external_constraints;
1319+
debug_assert!(normalization_nested_goals.is_empty());
1320+
1321+
// New defining uses may leak an opaque's hidden type into the caller's
1322+
// inference state. This is safe after typeck, where hidden types are already
1323+
// fixed and only regions are inferred, but not during typeck while hidden
1324+
// types may still contain inference variables.
1325+
if !opaque_types.is_empty() {
1326+
let typing_mode = self.typing_mode();
1327+
1328+
match typing_mode {
1329+
// We're inferring regions of opaque types, but
1330+
// the type itself is already fully known, no way
1331+
// to leak the hidden type.
1332+
TypingMode::PostTypeckUntilBorrowck { .. } => {}
1333+
// We're inferring the hidden type of opaques, could
1334+
// leak types through it.
1335+
TypingMode::Typeck { .. } => {
1336+
return self.forced_ambiguity(MaybeInfo::AMBIGUOUS);
1337+
}
1338+
// we never add new uses to the opaque type storage
1339+
TypingMode::Coherence
1340+
| TypingMode::PostBorrowck { .. }
1341+
| TypingMode::Reflection
1342+
| TypingMode::PostAnalysis
1343+
| TypingMode::Codegen
1344+
| TypingMode::ErasedNotCoherence(MayBeErased) => {
1345+
unreachable!(
1346+
"we never add new uses to opaque types in typing mode {typing_mode:?}"
1347+
);
1348+
}
1349+
}
1350+
}
1351+
1352+
Ok(candidate)
1353+
}
1354+
12921355
// Return `Some` if there is an impl (built-in or user provided) that may
12931356
// hold for the self type of the goal, which for coherence and soundness
12941357
// purposes must disqualify the built-in auto impl assembled by considering
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// External crate for `opaque-hidden-ty-inference.rs`.
2+
//
3+
// `define` returns `WaddupGamers<T, closure>`. Its `Unpin` impl only holds if
4+
// `<T as Leak>::Assoc` is that closure. The test's caller says `Assoc` is its
5+
// own opaque type instead, so checking `Unpin` would try to set that opaque
6+
// type to this closure.
7+
pub struct WaddupGamers<T, U>(Option<T>, U);
8+
9+
impl<T: Leak<Assoc = U>, U> Unpin for WaddupGamers<T, U> {}
10+
11+
pub trait Leak {
12+
type Assoc;
13+
}
14+
15+
impl<T> Leak for T {
16+
type Assoc = T;
17+
}
18+
19+
pub fn define<T>() -> impl Sized {
20+
WaddupGamers(None::<T>, || ())
21+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//@ ignore-compare-mode-next-solver
2+
//@ compile-flags: -Znext-solver
3+
//@ aux-build:opaque-auto-trait-leakage.rs
4+
5+
//! Regression test for https://github.com/rust-lang/rust/issues/134578.
6+
//! Leaking the auto traits of a foreign opaque must not constrain inference
7+
//! variables in the caller, as that would leak the hidden type itself. Here
8+
//! that would constrain `NameMe<T>` to a closure from the auxiliary crate and
9+
//! ICE in typeck. The hidden type may still show up in the diagnostic.
10+
11+
#![feature(type_alias_impl_trait)]
12+
#![allow(unused)]
13+
14+
extern crate opaque_auto_trait_leakage as dep;
15+
16+
use dep::*;
17+
18+
fn require_auto<T: Unpin>(x: T) -> T {
19+
x
20+
}
21+
22+
type NameMe<T> = impl Sized;
23+
24+
#[define_opaque(NameMe)]
25+
fn leak<T>() -> NameMe<T>
26+
where
27+
T: Leak<Assoc = NameMe<T>>,
28+
{
29+
// Proving `impl Sized: Unpin` must not constrain `NameMe<T>`
30+
// to the foreign closure hidden inside `define`.
31+
let opaque = require_auto(define::<T>());
32+
//~^ ERROR type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}`
33+
let closure;
34+
loop {}
35+
return closure;
36+
}
37+
38+
fn main() {}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error[E0271]: type mismatch resolving `<T as Leak>::Assoc == {closure@define<T>::{closure#0}}`
2+
--> $DIR/opaque-hidden-ty-inference.rs:31:31
3+
|
4+
LL | let opaque = require_auto(define::<T>());
5+
| ------------ ^^^^^^^^^^^^^ expected closure, found `!`
6+
| |
7+
| required by a bound introduced by this call
8+
|
9+
::: $DIR/auxiliary/opaque-auto-trait-leakage.rs:20:29
10+
|
11+
LL | WaddupGamers(None::<T>, || ())
12+
| -- the expected closure
13+
|
14+
= note: expected closure `{closure@dep::define<T>::{closure#0}}`
15+
found type `!`
16+
= note: required for `WaddupGamers<T, {closure@dep::define<T>::{closure#0}}>` to implement `Unpin`
17+
note: required because it appears within the type `impl Sized`
18+
--> $DIR/auxiliary/opaque-auto-trait-leakage.rs:19:23
19+
|
20+
LL | pub fn define<T>() -> impl Sized {
21+
| ^^^^^^^^^^
22+
note: required by a bound in `require_auto`
23+
--> $DIR/opaque-hidden-ty-inference.rs:18:20
24+
|
25+
LL | fn require_auto<T: Unpin>(x: T) -> T {
26+
| ^^^^^ required by this bound in `require_auto`
27+
28+
error: aborting due to 1 previous error
29+
30+
For more information about this error, try `rustc --explain E0271`.

0 commit comments

Comments
 (0)