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 ae78d68865de3..2f9228ef7e9e8 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 @@ -58,7 +58,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:?}`") } @@ -104,15 +105,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(), - ])) - } } } 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 924406b7b18d4..6be05c185bd0a 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -245,35 +245,23 @@ 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, .. }) = 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()); - } + ecx.consider_auto_trait_candidate_for_opaque_ty(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; } - } - // 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( - CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), - goal, - structural_traits::instantiate_constituent_tys_for_auto_trait, - ) } fn consider_trait_alias_candidate( @@ -1288,6 +1276,42 @@ 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>, + def_id: I::OpaqueTyId, + args: I::GenericArgs, + ) -> Result, NoSolutionOrRerunNonErased> { + let cx = self.cx(); + let source = CandidateSource::BuiltinImpl(BuiltinImplSource::Misc); + if self.opaque_accesses.might_rerun() { + return match self.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()); + } + } + + 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) + }); + + match candidate { + Ok(candidate) if has_only_region_constraints(candidate.result) => Ok(candidate), + Ok(_) => self.forced_ambiguity(MaybeInfo::AMBIGUOUS), + Err(err) => Err(err), + } + } + // 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 diff --git a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs index bb75ae6247e38..6a1f32eb5c7f8 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill/derive_errors.rs @@ -2,7 +2,7 @@ use std::ops::ControlFlow; use rustc_hir::attrs::lang_items::LangItem; use rustc_infer::infer::InferCtxt; -use rustc_infer::traits::solve::{CandidateSource, GoalSource, MaybeCause}; +use rustc_infer::traits::solve::{BuiltinImplSource, CandidateSource, GoalSource, MaybeCause}; use rustc_infer::traits::{ self, MismatchedProjectionTypes, Obligation, ObligationCause, ObligationCauseCode, PredicateObligation, SelectionError, @@ -248,6 +248,55 @@ impl<'tcx> BestObligation<'tcx> { candidates } + fn is_opaque_auto_trait_candidate( + &self, + tcx: TyCtxt<'tcx>, + candidate: &inspect::InspectCandidate<'_, 'tcx>, + pred: ty::Predicate<'tcx>, + ) -> bool { + self.is_builtin_misc_trait_candidate(candidate) + && self.is_positive_auto_trait_predicate_with_opaque_self(tcx, pred) + } + + fn is_builtin_misc_trait_candidate( + &self, + candidate: &inspect::InspectCandidate<'_, 'tcx>, + ) -> bool { + matches!( + candidate.kind(), + inspect::ProbeKind::TraitCandidate { + source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), + result: _, + } + ) + } + + fn is_positive_auto_trait_predicate_with_opaque_self( + &self, + tcx: TyCtxt<'tcx>, + pred: ty::Predicate<'tcx>, + ) -> bool { + let ty::PredicateKind::Clause(ty::ClauseKind::Trait(trait_pred)) = + pred.kind().skip_binder() + else { + return false; + }; + + if trait_pred.polarity != ty::PredicatePolarity::Positive + || !tcx.trait_is_auto(trait_pred.def_id()) + { + return false; + } + + let ty::Alias(ty::IsRigid::Yes, ty::AliasTy { kind: ty::Opaque { def_id, .. }, .. }) = + trait_pred.self_ty().kind() + else { + return false; + }; + + !matches!(tcx.opaque_ty_origin(*def_id), rustc_hir::OpaqueTyOrigin::AsyncFn { .. }) + } + /// HACK: We walk the nested obligations for a well-formed arg manually, /// since there's nontrivial logic in `wf.rs` to set up an obligation cause. /// Ideally we'd be able to track this better. @@ -447,6 +496,13 @@ impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> { return ControlFlow::Break(self.obligation.clone()); } + // Don't walk into opaque auto trait candidates, as doing so would expose + // the opaque's hidden type in diagnostics outside of its defining scope. + if self.is_opaque_auto_trait_candidate(tcx, candidate, pred) { + trace!("opaque auto trait candidate -> exit"); + return ControlFlow::Break(self.obligation.clone()); + } + // FIXME: Also, what about considering >1 layer up the stack? May be necessary // for normalizes-to. let child_mode = match pred.kind().skip_binder() { 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..c5bc085efe2a1 --- /dev/null +++ b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.rs @@ -0,0 +1,37 @@ +//@ 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. +//! When reporting a failed auto-trait obligation for an opaque type, diagnostics +//! must not reveal the opaque's hidden type. In this test, the error should refer +//! only to `impl Sized`, without exposing the concrete type from the auxiliary crate. + +#![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 `impl Sized` cannot be unpinned + 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..b7b4bba931e26 --- /dev/null +++ b/tests/ui/impl-trait/auto-trait-leakage/opaque-hidden-ty-inference.stderr @@ -0,0 +1,19 @@ +error[E0277]: `impl Sized` cannot be unpinned + --> $DIR/opaque-hidden-ty-inference.rs:30:31 + | +LL | let opaque = require_auto(define::()); + | ------------ ^^^^^^^^^^^^^ the trait `Unpin` is not implemented for `impl Sized` + | | + | required by a bound introduced by this call + | + = note: consider using the `pin!` macro + consider using `Box::pin` if you need to access the pinned value outside of the current scope +note: required by a bound in `require_auto` + --> $DIR/opaque-hidden-ty-inference.rs:17: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 E0277`.