From f026f7d99d8762f84cccc48de4a10ea3f0f8221e Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 16 Dec 2025 20:25:58 +0000 Subject: [PATCH 1/3] Remove 'static requirement on try_as_dyn --- .../rustc_const_eval/src/interpret/util.rs | 5 +- compiler/rustc_hir_analysis/src/collect.rs | 6 ++ compiler/rustc_infer/src/infer/mod.rs | 2 + .../rustc_infer/src/infer/opaque_types/mod.rs | 4 +- .../src/rmeta/decoder/cstore_impl.rs | 1 + compiler/rustc_metadata/src/rmeta/encoder.rs | 6 ++ compiler/rustc_metadata/src/rmeta/mod.rs | 1 + compiler/rustc_middle/src/queries.rs | 9 ++ .../src/ty/context/impl_interner.rs | 4 + compiler/rustc_middle/src/ty/generics.rs | 70 ++++++++++++++- compiler/rustc_middle/src/ty/mod.rs | 52 +++++++++++ .../src/solve/assembly/mod.rs | 2 + .../src/solve/effect_goals.rs | 1 + .../rustc_next_trait_solver/src/solve/mod.rs | 2 +- .../src/solve/normalizes_to/mod.rs | 2 + .../src/solve/normalizes_to/opaque_types.rs | 2 +- .../src/solve/search_graph.rs | 1 + .../src/solve/trait_goals.rs | 13 ++- .../src/solve/delegate.rs | 1 + .../src/solve/fulfill.rs | 1 + .../src/traits/fulfill.rs | 1 + .../src/traits/normalize.rs | 4 +- .../src/traits/project.rs | 1 + .../src/traits/query/normalize.rs | 2 +- .../src/traits/select/mod.rs | 23 ++++- compiler/rustc_ty_utils/src/instance.rs | 1 + compiler/rustc_type_ir/src/infer_ctxt.rs | 4 + compiler/rustc_type_ir/src/interner.rs | 2 + compiler/rustc_type_ir/src/relate/combine.rs | 1 + library/core/src/any.rs | 22 ++--- tests/ui/any/non_static.rs | 88 +++++++++++++++++++ tests/ui/any/static_method_bound.rs | 34 +++++++ 32 files changed, 342 insertions(+), 26 deletions(-) create mode 100644 tests/ui/any/non_static.rs create mode 100644 tests/ui/any/static_method_bound.rs diff --git a/compiler/rustc_const_eval/src/interpret/util.rs b/compiler/rustc_const_eval/src/interpret/util.rs index 57a02769643b7..93e9ff9706e3e 100644 --- a/compiler/rustc_const_eval/src/interpret/util.rs +++ b/compiler/rustc_const_eval/src/interpret/util.rs @@ -29,7 +29,10 @@ pub(crate) fn type_implements_dyn_trait<'tcx, M: Machine<'tcx>>( ); }; - let (infcx, param_env) = ecx.tcx.infer_ctxt().build_with_typing_env(ecx.typing_env); + let (infcx, param_env) = ecx.tcx.infer_ctxt().build_with_typing_env(ty::TypingEnv { + typing_mode: ty::TypingMode::Reflection, + ..ecx.typing_env + }); let ocx = ObligationCtxt::new(&infcx); ocx.register_obligations(preds.iter().map(|pred: PolyExistentialPredicate<'_>| { diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 80ef2001cc72e..95d5e2acd8a1e 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -86,6 +86,7 @@ pub(crate) fn provide(providers: &mut Providers) { adt_def, fn_sig, impl_trait_header, + impl_is_fully_generic_for_reflection, coroutine_kind, coroutine_for_closure, opaque_ty_origin, @@ -1353,6 +1354,11 @@ pub fn suggest_impl_trait<'tcx>( None } +fn impl_is_fully_generic_for_reflection(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { + tcx.impl_trait_header(def_id).is_fully_generic_for_reflection() + && tcx.explicit_predicates_of(def_id).is_fully_generic_for_reflection() +} + fn impl_trait_header(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::ImplTraitHeader<'_> { let icx = ItemCtxt::new(tcx, def_id); let item = tcx.hir_expect_item(def_id); diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index b573065362601..d344f157eacf7 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1047,6 +1047,7 @@ impl<'tcx> InferCtxt<'tcx> { // and post-borrowck analysis mode. We may need to modify its uses // to support PostBorrowckAnalysis in the old solver as well. TypingMode::Coherence + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => false, } @@ -1404,6 +1405,7 @@ impl<'tcx> InferCtxt<'tcx> { } mode @ (ty::TypingMode::Coherence | ty::TypingMode::PostBorrowckAnalysis { .. } + | ty::TypingMode::Reflection | ty::TypingMode::PostAnalysis) => mode, }; ty::TypingEnv { typing_mode, param_env } diff --git a/compiler/rustc_infer/src/infer/opaque_types/mod.rs b/compiler/rustc_infer/src/infer/opaque_types/mod.rs index 9579abf7ec53c..d533876dbe8a7 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/mod.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/mod.rs @@ -276,7 +276,9 @@ impl<'tcx> InferCtxt<'tcx> { .map(|obligation| obligation.as_goal()), ); } - mode @ (ty::TypingMode::PostBorrowckAnalysis { .. } | ty::TypingMode::PostAnalysis) => { + mode @ (ty::TypingMode::PostBorrowckAnalysis { .. } + | ty::TypingMode::PostAnalysis + | ty::TypingMode::Reflection) => { bug!("insert hidden type in {mode:?}") } } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 01e6d49a51c72..d339983630c01 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -237,6 +237,7 @@ provide! { tcx, def_id, other, cdata, fn_sig => { table } codegen_fn_attrs => { table } impl_trait_header => { table } + impl_is_fully_generic_for_reflection => { table_direct } const_param_default => { table } object_lifetime_default => { table } thir_abstract_const => { table } diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index fbc7232f3a27d..81699da0e4efb 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -2172,6 +2172,12 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { let header = tcx.impl_trait_header(def_id); record!(self.tables.impl_trait_header[def_id] <- header); + let impl_is_fully_generic_for_reflection = + tcx.impl_is_fully_generic_for_reflection(def_id); + self.tables + .impl_is_fully_generic_for_reflection + .set(def_id.index, impl_is_fully_generic_for_reflection); + self.tables.defaultness.set(def_id.index, tcx.defaultness(def_id)); let trait_ref = header.trait_ref.instantiate_identity(); diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 80d7ae4e9cb38..c96d8cda95941 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -408,6 +408,7 @@ define_tables! { constness: Table, safety: Table, defaultness: Table, + impl_is_fully_generic_for_reflection: Table, - optional: attributes: Table>, diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 2636fc7024ca3..0b0aad5f7f00f 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1130,6 +1130,15 @@ rustc_queries! { separate_provide_extern } + /// Whether all generic parameters of the type are unique unconstrained generic parameters + /// of the impl. `Bar<'static>` or `Foo<'a, 'a>` or outlives bounds on the lifetimes cause + /// this boolean to be false and `try_as_dyn` to return `None`. + query impl_is_fully_generic_for_reflection(impl_id: DefId) -> bool { + desc { "computing trait implemented by `{}`", tcx.def_path_str(impl_id) } + cache_on_disk_if { impl_id.is_local() } + separate_provide_extern + } + /// Given an `impl_def_id`, return true if the self type is guaranteed to be unsized due /// to either being one of the built-in unsized types (str/slice/dyn) or to be a struct /// whose tail is one of those types. diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index e59573976af52..17e04069be361 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -626,6 +626,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.impl_polarity(impl_def_id) } + fn is_fully_generic_for_reflection(self, impl_def_id: Self::ImplId) -> bool { + self.impl_is_fully_generic_for_reflection(impl_def_id) + } + fn trait_is_auto(self, trait_def_id: DefId) -> bool { self.trait_is_auto(trait_def_id) } diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index ed587cbc3c285..65dcb73d6c0da 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -1,13 +1,15 @@ +use std::ops::ControlFlow; + use rustc_ast as ast; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_macros::{HashStable, TyDecodable, TyEncodable}; use rustc_span::{Span, Symbol, kw}; +use rustc_type_ir::{TypeSuperVisitable as _, TypeVisitable, TypeVisitor}; use tracing::instrument; use super::{Clause, InstantiatedPredicates, ParamConst, ParamTy, Ty, TyCtxt}; -use crate::ty; -use crate::ty::{EarlyBinder, GenericArgsRef}; +use crate::ty::{self, ClauseKind, EarlyBinder, GenericArgsRef, Region, RegionKind, TyKind}; #[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable)] pub enum GenericParamDefKind { @@ -421,6 +423,70 @@ impl<'tcx> GenericPredicates<'tcx> { instantiated.predicates.extend(self.predicates.iter().map(|(p, _)| p)); instantiated.spans.extend(self.predicates.iter().map(|(_, s)| s)); } + + /// Allow simple where bounds like `T: Debug`, but prevent any kind of + /// outlives bounds or uses of generic parameters on the right hand side. + pub fn is_fully_generic_for_reflection(self) -> bool { + struct ParamChecker; + impl<'tcx> TypeVisitor> for ParamChecker { + type Result = ControlFlow<()>; + fn visit_region(&mut self, r: Region<'tcx>) -> Self::Result { + match r.kind() { + RegionKind::ReEarlyParam(_) | RegionKind::ReStatic | RegionKind::ReError(_) => { + ControlFlow::Break(()) + } + RegionKind::ReVar(_) + | RegionKind::RePlaceholder(_) + | RegionKind::ReErased + | RegionKind::ReLateParam(_) => { + bug!("unexpected lifetime in impl: {r:?}") + } + RegionKind::ReBound(..) => ControlFlow::Continue(()), + } + } + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + match t.kind() { + TyKind::Param(_p) => { + // Reject using parameters used in the type in where bounds + return ControlFlow::Break(()); + } + TyKind::Alias(..) => return ControlFlow::Break(()), + _ => (), + } + t.super_visit_with(self) + } + } + + // Pessimistic: if any of the parameters have where bounds + // don't allow this impl to be used. + self.predicates.iter().all(|(clause, _)| { + match clause.kind().skip_binder() { + ClauseKind::Trait(trait_predicate) => { + // In a `T: Trait`, if the rhs bound does not contain any generic params + // or 'static lifetimes, then it cannot transitively cause such requirements, + // considering we apply the fully-generic-for-reflection rules to any impls for + // that trait, too. + if matches!(trait_predicate.self_ty().kind(), ty::Param(_)) + && trait_predicate.trait_ref.args[1..] + .iter() + .all(|arg| arg.visit_with(&mut ParamChecker).is_continue()) + { + return true; + } + } + ClauseKind::RegionOutlives(_) + | ClauseKind::TypeOutlives(_) + | ClauseKind::Projection(_) + | ClauseKind::ConstArgHasType(_, _) + | ClauseKind::WellFormed(_) + | ClauseKind::ConstEvaluatable(_) + | ClauseKind::HostEffect(_) + | ClauseKind::UnstableFeature(_) => {} + } + clause.visit_with(&mut ParamChecker).is_continue() + }) + } } /// `[const]` bounds for a given item. This is represented using a struct much like diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 6abe7d1466990..4b2b32012df03 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -15,6 +15,7 @@ use std::fmt::Debug; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::num::NonZero; +use std::ops::ControlFlow; use std::ptr::NonNull; use std::{assert_matches, fmt, iter, str}; @@ -281,6 +282,57 @@ pub struct ImplTraitHeader<'tcx> { pub constness: hir::Constness, } +impl<'tcx> ImplTraitHeader<'tcx> { + /// For trait impls, checks whether the type and trait only have generic parameters in their + /// arguments and only uses each generic param once, too. + /// Pessimistic analysis, so it will reject alias types + /// and other types that may be actually ok. We can allow more in the future. + pub fn is_fully_generic_for_reflection(self) -> bool { + #[derive(Default)] + struct ParamFinder { + seen: FxHashSet, + } + + impl<'tcx> TypeVisitor> for ParamFinder { + type Result = ControlFlow<()>; + fn visit_region(&mut self, r: Region<'tcx>) -> Self::Result { + match r.kind() { + RegionKind::ReEarlyParam(param) => { + if self.seen.insert(param.index) { + ControlFlow::Continue(()) + } else { + ControlFlow::Break(()) + } + } + RegionKind::ReBound(..) | RegionKind::ReLateParam(_) => { + ControlFlow::Continue(()) + } + RegionKind::ReStatic + | RegionKind::ReVar(_) + | RegionKind::RePlaceholder(_) + | RegionKind::ReErased + | RegionKind::ReError(_) => ControlFlow::Break(()), + } + } + + fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { + match t.kind() { + TyKind::Param(p) => { + // Reject using a parameter twice (e.g. in `Foo`) + if !self.seen.insert(p.index) { + return ControlFlow::Break(()); + } + } + TyKind::Alias(..) => return ControlFlow::Break(()), + _ => (), + } + t.super_visit_with(self) + } + } + self.trait_ref.instantiate_identity().visit_with(&mut ParamFinder::default()).is_continue() + } +} + #[derive(Copy, Clone, PartialEq, Eq, Hash, TyEncodable, TyDecodable, HashStable, Debug)] #[derive(TypeFoldable, TypeVisitable, Default)] pub enum Asyncness { diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 038168ca638b4..c6f62306d8031 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -936,6 +936,7 @@ where TypingMode::Coherence => return, TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => {} } @@ -999,6 +1000,7 @@ where TypingMode::Analysis { .. } => self.opaques_with_sub_unified_hidden_type(self_ty), TypingMode::Coherence | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => vec![], }; diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 4da76b88b5de0..1ce262460722a 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -142,6 +142,7 @@ where TypingMode::Coherence => Certainty::AMBIGUOUS, TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => return Err(NoSolution), }, diff --git a/compiler/rustc_next_trait_solver/src/solve/mod.rs b/compiler/rustc_next_trait_solver/src/solve/mod.rs index 58bd7cf663d98..f8e1bd6d3d176 100644 --- a/compiler/rustc_next_trait_solver/src/solve/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/mod.rs @@ -368,7 +368,7 @@ where fn opaque_type_is_rigid(&self, def_id: I::DefId) -> bool { match self.typing_mode() { // Opaques are never rigid outside of analysis mode. - TypingMode::Coherence | TypingMode::PostAnalysis => false, + TypingMode::Reflection | TypingMode::Coherence | TypingMode::PostAnalysis => false, // During analysis, opaques are rigid unless they may be defined by // the current body. TypingMode::Analysis { defining_opaque_types_and_generators: non_rigid_opaques } diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index 13f2ad8f82eba..a9df3d92ad7a0 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -298,6 +298,7 @@ where // Outside of coherence, we treat the associated item as rigid instead. ty::TypingMode::Analysis { .. } | ty::TypingMode::Borrowck { .. } + | ty::TypingMode::Reflection | ty::TypingMode::PostBorrowckAnalysis { .. } | ty::TypingMode::PostAnalysis => { ecx.structurally_instantiate_normalizes_to_term( @@ -336,6 +337,7 @@ where } ty::TypingMode::Analysis { .. } | ty::TypingMode::Borrowck { .. } + | ty::TypingMode::Reflection | ty::TypingMode::PostBorrowckAnalysis { .. } | ty::TypingMode::PostAnalysis => { ecx.structurally_instantiate_normalizes_to_term( diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs index a5f857a1dd85b..0fe17eedf89da 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/opaque_types.rs @@ -126,7 +126,7 @@ where self.eq(goal.param_env, expected, actual)?; self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } - TypingMode::PostAnalysis => { + TypingMode::Reflection | TypingMode::PostAnalysis => { // FIXME: Add an assertion that opaque type storage is empty. let actual = cx.type_of(opaque_ty.def_id).instantiate(cx, opaque_ty.args); self.eq(goal.param_env, expected, actual)?; diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index 73044b7943aeb..977cbcd56a449 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -68,6 +68,7 @@ where } TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => Err(NoSolution), }, 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 6589a12c4cc2d..b9cf8ca919e66 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -78,13 +78,22 @@ where TypingMode::Coherence => Certainty::AMBIGUOUS, TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => return Err(NoSolution), }, // Impl matches polarity (ty::ImplPolarity::Positive, ty::PredicatePolarity::Positive) - | (ty::ImplPolarity::Negative, ty::PredicatePolarity::Negative) => Certainty::Yes, + | (ty::ImplPolarity::Negative, ty::PredicatePolarity::Negative) => { + if let TypingMode::Reflection = ecx.typing_mode() + && !cx.is_fully_generic_for_reflection(impl_def_id) + { + return Err(NoSolution); + } else { + Certainty::Yes + } + } // Impl doesn't match polarity (ty::ImplPolarity::Positive, ty::PredicatePolarity::Negative) @@ -1381,6 +1390,7 @@ where TypingMode::Coherence => return, TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => {} } @@ -1555,6 +1565,7 @@ where } TypingMode::Coherence | TypingMode::PostAnalysis + | TypingMode::Reflection | TypingMode::Borrowck { defining_opaque_types: _ } | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => {} } diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 62572694de326..3fa1b60c33659 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -278,6 +278,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< TypingMode::Coherence | TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } => false, TypingMode::PostAnalysis => { let poly_trait_ref = self.resolve_vars_if_possible(goal_trait_ref); diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 7b61a653ae31e..97b167a82bad0 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -289,6 +289,7 @@ where TypingMode::Coherence | TypingMode::Borrowck { defining_opaque_types: _ } | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } + | TypingMode::Reflection | TypingMode::PostAnalysis => return Default::default(), }; diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 71a8e081146e9..3ef4ff42f5f5e 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -179,6 +179,7 @@ where TypingMode::Coherence | TypingMode::Borrowck { defining_opaque_types: _ } | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } + | TypingMode::Reflection | TypingMode::PostAnalysis => return Default::default(), }; diff --git a/compiler/rustc_trait_selection/src/traits/normalize.rs b/compiler/rustc_trait_selection/src/traits/normalize.rs index 19a80893e898d..cdf337e8d1db9 100644 --- a/compiler/rustc_trait_selection/src/traits/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/normalize.rs @@ -139,7 +139,7 @@ pub(super) fn needs_normalization<'tcx, T: TypeVisitable>>( | TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } | TypingMode::PostBorrowckAnalysis { .. } => flags.remove(ty::TypeFlags::HAS_TY_OPAQUE), - TypingMode::PostAnalysis => {} + TypingMode::Reflection | TypingMode::PostAnalysis => {} } value.has_type_flags(flags) @@ -403,7 +403,7 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx | TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } | TypingMode::PostBorrowckAnalysis { .. } => ty.super_fold_with(self), - TypingMode::PostAnalysis => { + TypingMode::Reflection | TypingMode::PostAnalysis => { let recursion_limit = self.cx().recursion_limit(); if !recursion_limit.value_within_limit(self.depth) { self.selcx.infcx.err_ctxt().report_overflow_error( diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 3df5c9e33438a..d235ffb634b78 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -950,6 +950,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( TypingMode::Coherence | TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } => { debug!( assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id), diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs index 2f83ee046498a..a83f205968a25 100644 --- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs @@ -222,7 +222,7 @@ impl<'a, 'tcx> FallibleTypeFolder> for QueryNormalizer<'a, 'tcx> { | TypingMode::Borrowck { .. } | TypingMode::PostBorrowckAnalysis { .. } => ty.try_super_fold_with(self)?, - TypingMode::PostAnalysis => { + TypingMode::Reflection | TypingMode::PostAnalysis => { let args = data.args.try_fold_with(self)?; let recursion_limit = self.cx().recursion_limit(); diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index a7f47a8615c24..0e3a0cb5f9894 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -17,7 +17,7 @@ use rustc_infer::infer::BoundRegionConversionTime::{self, HigherRankedType}; use rustc_infer::infer::DefineOpaqueTypes; use rustc_infer::infer::at::ToTrace; use rustc_infer::infer::relate::TypeRelation; -use rustc_infer::traits::{PredicateObligations, TraitObligation}; +use rustc_infer::traits::{ImplSource, PredicateObligations, TraitObligation}; use rustc_macros::{TypeFoldable, TypeVisitable}; use rustc_middle::bug; use rustc_middle::dep_graph::{DepKind, DepNodeIndex}; @@ -276,6 +276,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { Err(SelectionError::Overflow(OverflowError::Canonical)) } Err(e) => Err(e), + Ok(ImplSource::Builtin(..)) + if matches!(self.infcx.typing_mode(), TypingMode::Reflection) => + { + Err(SelectionError::Unimplemented) + } Ok(candidate) => Ok(Some(candidate)), } } @@ -1283,6 +1288,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { match this.confirm_candidate(stack.obligation, candidate.clone()) { Ok(selection) => { debug!(?selection); + if let ImplSource::Builtin(..) = selection + && matches!(this.infcx.typing_mode(), TypingMode::Reflection) + { + return Ok(EvaluatedToErr); + } this.evaluate_predicates_recursively( stack.list(), selection.nested_obligations().into_iter(), @@ -1469,6 +1479,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { TypingMode::Coherence => {} TypingMode::Analysis { .. } | TypingMode::Borrowck { .. } + | TypingMode::Reflection | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => return Ok(()), } @@ -1521,6 +1532,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { defining_opaque_types.is_empty() || (!pred.has_opaque_types() && !pred.has_coroutines()) } + // Impls that are not fully generic are completely ignored as "nonexistent" + // in this mode, so the results wildly differ from normal trait solving. + TypingMode::Reflection => false, // The hidden types of `defined_opaque_types` is not local to the current // inference context, so we can freely move this to the global cache. TypingMode::PostBorrowckAnalysis { .. } => true, @@ -2550,6 +2564,12 @@ impl<'tcx> SelectionContext<'_, 'tcx> { debug!("reservation impls only apply in intercrate mode"); return Err(()); } + if matches!(self.infcx.typing_mode(), TypingMode::Reflection) + && !self.tcx().impl_is_fully_generic_for_reflection(impl_def_id) + { + debug!("reflection mode only allows fully generic impls"); + return Err(()); + } Ok(Normalized { value: impl_args, obligations: nested_obligations }) } @@ -2886,6 +2906,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { } TypingMode::Coherence | TypingMode::PostAnalysis + | TypingMode::Reflection | TypingMode::Borrowck { defining_opaque_types: _ } | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ } => false, } diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index cf881961b3ce3..0aa9059daf94f 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -158,6 +158,7 @@ fn resolve_associated_item<'tcx>( ty::TypingMode::Coherence | ty::TypingMode::Analysis { .. } | ty::TypingMode::Borrowck { .. } + | ty::TypingMode::Reflection | ty::TypingMode::PostBorrowckAnalysis { .. } => false, ty::TypingMode::PostAnalysis => !trait_ref.still_further_specializable(), } diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index feafcee7bad9e..912df806b4d7e 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -79,6 +79,10 @@ pub enum TypingMode { /// This is currently only used by the new solver, but should be implemented in /// the old solver as well. PostBorrowckAnalysis { defined_opaque_types: I::LocalDefIds }, + /// During the evaluation of reflection logic that ignores lifetimes, we can only + /// handle impls that are fully generic over all lifetimes without constraints on + /// those lifetimes (other than implied bounds). + Reflection, /// After analysis, mostly during codegen and MIR optimizations, we're able to /// reveal all opaque types. As the hidden type should *never* be observable /// directly by the user, this should not be used by checks which may expose diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index e77e7af071b90..25d03fff0f6da 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -368,6 +368,8 @@ pub trait Interner: fn impl_polarity(self, impl_def_id: Self::ImplId) -> ty::ImplPolarity; + fn is_fully_generic_for_reflection(self, impl_def_id: Self::ImplId) -> bool; + fn trait_is_auto(self, trait_def_id: Self::TraitId) -> bool; fn trait_is_coinductive(self, trait_def_id: Self::TraitId) -> bool; diff --git a/compiler/rustc_type_ir/src/relate/combine.rs b/compiler/rustc_type_ir/src/relate/combine.rs index 64b87fac77f94..f64faf1a9d5a3 100644 --- a/compiler/rustc_type_ir/src/relate/combine.rs +++ b/compiler/rustc_type_ir/src/relate/combine.rs @@ -140,6 +140,7 @@ where Ok(a) } TypingMode::Analysis { .. } + | TypingMode::Reflection | TypingMode::Borrowck { .. } | TypingMode::PostBorrowckAnalysis { .. } | TypingMode::PostAnalysis => structurally_relate_tys(relation, a, b), diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 71a529400511c..8580a20ab1140 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -86,7 +86,7 @@ #![stable(feature = "rust1", since = "1.0.0")] -use crate::intrinsics::{self, type_id_vtable}; +use crate::intrinsics::{self, type_id, type_id_vtable}; use crate::mem::transmute; use crate::mem::type_info::{TraitImpl, TypeKind}; use crate::{fmt, hash, ptr}; @@ -808,14 +808,12 @@ impl TypeId { /// ``` #[unstable(feature = "type_info", issue = "146922")] #[rustc_const_unstable(feature = "type_info", issue = "146922")] - pub const fn trait_info_of< - T: ptr::Pointee> + ?Sized + 'static, - >( + pub const fn trait_info_of> + ?Sized>( self, ) -> Option> { // SAFETY: The vtable was obtained for `T`, so it is guaranteed to be `DynMetadata`. // The intrinsic can't infer this because it is designed to work with arbitrary TypeIds. - unsafe { transmute(self.trait_info_of_trait_type_id(const { TypeId::of::() })) } + unsafe { transmute(self.trait_info_of_trait_type_id(const { type_id::() })) } } /// Checks if the [TypeId] implements the trait of `trait_represented_by_type_id`. If it does it returns [TraitImpl] which can be used to build a fat pointer. @@ -1006,14 +1004,11 @@ pub const fn type_name_of_val(_val: &T) -> &'static str { /// ``` #[must_use] #[unstable(feature = "try_as_dyn", issue = "144361")] -pub const fn try_as_dyn< - T: Any + 'static, - U: ptr::Pointee> + ?Sized + 'static, ->( +pub const fn try_as_dyn> + ?Sized>( t: &T, ) -> Option<&U> { let vtable: Option> = - const { TypeId::of::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; + const { type_id::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts(t, dyn_metadata); @@ -1060,14 +1055,11 @@ pub const fn try_as_dyn< /// ``` #[must_use] #[unstable(feature = "try_as_dyn", issue = "144361")] -pub const fn try_as_dyn_mut< - T: Any + 'static, - U: ptr::Pointee> + ?Sized + 'static, ->( +pub const fn try_as_dyn_mut> + ?Sized>( t: &mut T, ) -> Option<&mut U> { let vtable: Option> = - const { TypeId::of::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; + const { type_id::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { Some(dyn_metadata) => { let pointer = ptr::from_raw_parts_mut(t, dyn_metadata); diff --git a/tests/ui/any/non_static.rs b/tests/ui/any/non_static.rs new file mode 100644 index 0000000000000..ad04b7f475e0b --- /dev/null +++ b/tests/ui/any/non_static.rs @@ -0,0 +1,88 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +//@check-pass +#![feature(try_as_dyn)] + +trait Trait {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&&42_i32).is_none()); +}; + +impl<'a> Trait for &'a [(); 1] {} +const _: () = { + let x = (); + assert!(std::any::try_as_dyn::<_, dyn Trait>(&&[x]).is_some()); +}; + +type Foo = &'static [(); 2]; + +// Ensure type aliases don't skip these checks +impl Trait for Foo {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&&[(), ()]).is_none()); +}; + +impl Trait for &() {} +const _: () = { + let x = (); + assert!(std::any::try_as_dyn::<_, dyn Trait>(&&x).is_some()); +}; + +impl Trait for () {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&()).is_some()); +}; + +// Not fully generic impl -> returns None even tho +// implemented for *some* lifetimes +impl<'a> Trait for (&'a (), &'a ()) {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&(&(), &())).is_none()); +}; + +// Not fully generic impl -> returns None even tho +// implemented for *some* lifetimes +impl<'a, 'b: 'a> Trait for (&'a (), &'b (), ()) {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&(&(), &(), ())).is_none()); +}; + +// Only valid for 'static lifetimes -> returns None +// even though we are actually using a `'static` lifetime. +// We can't know what lifetimes are there during codegen, so +// we pessimistically assume it could be a shorter one +impl Trait for &'static u32 {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&&42_u32).is_none()); +}; + +trait Trait2 {} + +struct Struct(T); + +// While this is the impl for `Trait`, in `Reflection` solver mode +// we reject the impl for `Trait2` below, and thus this impl also +// doesn't match. +impl Trait for Struct {} + +impl Trait2 for &'static u32 {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait>(&Struct(&42_u32)).is_none()); +}; + +const _: () = { + trait Homo {} + impl Homo for (T, T) {} + + // Let's pick `T = &'_ i32`. + assert!(std::any::try_as_dyn::<_, dyn Homo>(&(&42_i32, &27_i32)).is_none()); +}; + +trait Trait3<'a> {} + +impl Trait3<'static> for () {} +const _: () = { + assert!(std::any::try_as_dyn::<_, dyn Trait3<'_>>(&()).is_none()); +}; + +fn main() {} diff --git a/tests/ui/any/static_method_bound.rs b/tests/ui/any/static_method_bound.rs new file mode 100644 index 0000000000000..aaab8623868f1 --- /dev/null +++ b/tests/ui/any/static_method_bound.rs @@ -0,0 +1,34 @@ +//@run-fail +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +type Payload = Box; + +trait Trait { + fn as_static(&self) -> &'static Payload + where + Self: 'static; +} + +impl<'a> Trait for &'a Payload { + fn as_static(&self) -> &'static Payload + where + Self: 'static, + { + *self + } +} + +fn main() { + let storage: Box = Box::new(Box::new(1i32)); + let wrong: &'static Payload = extend(&*storage); + drop(storage); + println!("{wrong}"); +} + +fn extend(a: &Payload) -> &'static Payload { + // TODO: should panic at the `unwrap` here + let b: &(dyn Trait + 'static) = try_as_dyn::<&Payload, dyn Trait + 'static>(&a).unwrap(); + b.as_static() +} From c6abcad37eb85ab1498ca0c455e2e0be208a2877 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 13 Jan 2026 13:43:04 +0000 Subject: [PATCH 2/3] Add helper trait that restricts lifetimes and extra predicates to ensure soundness --- compiler/rustc_hir/src/lang_items.rs | 2 + compiler/rustc_middle/src/traits/select.rs | 2 + .../src/ty/context/impl_interner.rs | 1 + compiler/rustc_middle/src/ty/mod.rs | 16 ++- compiler/rustc_middle/src/ty/predicate.rs | 6 ++ .../src/solve/assembly/mod.rs | 24 +++++ .../src/solve/effect_goals.rs | 7 ++ .../src/solve/normalizes_to/mod.rs | 7 ++ .../src/solve/trait_goals.rs | 33 +++++++ compiler/rustc_span/src/symbol.rs | 1 + .../src/traits/select/candidate_assembly.rs | 34 ++++++- .../src/traits/select/confirmation.rs | 29 ++++++ .../src/traits/select/mod.rs | 1 + compiler/rustc_type_ir/src/lang_items.rs | 1 + library/core/src/any.rs | 99 +++++++++++-------- tests/ui/any/any_static.next.stderr | 16 +++ tests/ui/any/any_static.old.stderr | 16 +++ tests/ui/any/any_static.rs | 21 ++++ tests/ui/any/hrtb.next.stderr | 9 ++ tests/ui/any/hrtb.old.stderr | 9 ++ tests/ui/any/hrtb.rs | 18 ++++ tests/ui/any/hrtb2.next.stderr | 9 ++ tests/ui/any/hrtb2.old.stderr | 9 ++ tests/ui/any/hrtb2.rs | 18 ++++ tests/ui/any/reject_manual_impl.rs | 29 ++++++ tests/ui/any/reject_manual_impl.stderr | 47 +++++++++ tests/ui/any/static_method_bound.rs | 3 +- tests/ui/any/static_method_bound.stderr | 16 +++ tests/ui/any/try_as_dyn.rs | 6 +- tests/ui/any/try_as_dyn_assoc_ty_lifetime.rs | 26 +++++ tests/ui/any/try_as_dyn_builtin_impl.rs | 47 +++++++++ tests/ui/any/try_as_dyn_elaborated_bounds.rs | 28 ++++++ tests/ui/any/try_as_dyn_generic_impl.rs | 39 ++++++++ .../any/try_as_dyn_generic_trait.next.stderr | 9 ++ .../any/try_as_dyn_generic_trait.old.stderr | 9 ++ tests/ui/any/try_as_dyn_generic_trait.rs | 25 +++++ tests/ui/any/try_as_dyn_mut.rs | 4 +- tests/ui/any/try_as_dyn_soundness_test1.rs | 14 +-- tests/ui/any/try_as_dyn_soundness_test2.rs | 10 +- 39 files changed, 631 insertions(+), 69 deletions(-) create mode 100644 tests/ui/any/any_static.next.stderr create mode 100644 tests/ui/any/any_static.old.stderr create mode 100644 tests/ui/any/any_static.rs create mode 100644 tests/ui/any/hrtb.next.stderr create mode 100644 tests/ui/any/hrtb.old.stderr create mode 100644 tests/ui/any/hrtb.rs create mode 100644 tests/ui/any/hrtb2.next.stderr create mode 100644 tests/ui/any/hrtb2.old.stderr create mode 100644 tests/ui/any/hrtb2.rs create mode 100644 tests/ui/any/reject_manual_impl.rs create mode 100644 tests/ui/any/reject_manual_impl.stderr create mode 100644 tests/ui/any/static_method_bound.stderr create mode 100644 tests/ui/any/try_as_dyn_assoc_ty_lifetime.rs create mode 100644 tests/ui/any/try_as_dyn_builtin_impl.rs create mode 100644 tests/ui/any/try_as_dyn_elaborated_bounds.rs create mode 100644 tests/ui/any/try_as_dyn_generic_impl.rs create mode 100644 tests/ui/any/try_as_dyn_generic_trait.next.stderr create mode 100644 tests/ui/any/try_as_dyn_generic_trait.old.stderr create mode 100644 tests/ui/any/try_as_dyn_generic_trait.rs diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 80f0af9d663cb..20e29ceeccd6f 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -189,6 +189,8 @@ language_item_table! { CoerceUnsized, sym::coerce_unsized, coerce_unsized_trait, Target::Trait, GenericRequirement::Minimum(1); DispatchFromDyn, sym::dispatch_from_dyn, dispatch_from_dyn_trait, Target::Trait, GenericRequirement::Minimum(1); + TryAsDyn, sym::try_as_dyn, try_as_dyn, Target::Trait, GenericRequirement::Exact(1); + // lang items relating to transmutability TransmuteOpts, sym::transmute_opts, transmute_opts, Target::Struct, GenericRequirement::Exact(0); TransmuteTrait, sym::transmute_trait, transmute_trait, Target::Trait, GenericRequirement::Exact(2); diff --git a/compiler/rustc_middle/src/traits/select.rs b/compiler/rustc_middle/src/traits/select.rs index 3a32029c60bd0..444a47df4c7e7 100644 --- a/compiler/rustc_middle/src/traits/select.rs +++ b/compiler/rustc_middle/src/traits/select.rs @@ -183,6 +183,8 @@ pub enum SelectionCandidate<'tcx> { BuiltinUnsizeCandidate, BikeshedGuaranteedNoDropCandidate, + + TryAsDynCandidate, } /// The result of trait evaluation. The order is important diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 17e04069be361..bb7e720a7c585 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -806,6 +806,7 @@ bidirectional_lang_item_map! { Sized, TransmuteTrait, TrivialClone, + TryAsDyn, Tuple, Unpin, Unsize, diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 4b2b32012df03..5676c1fb37d3d 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -283,9 +283,9 @@ pub struct ImplTraitHeader<'tcx> { } impl<'tcx> ImplTraitHeader<'tcx> { - /// For trait impls, checks whether the type and trait only have generic parameters in their - /// arguments and only uses each generic param once, too. - /// Pessimistic analysis, so it will reject alias types + /// For trait impls, checks whether the type and trait only have generic lifetime parameters in their + /// arguments and only use any generic param once. + /// This is a pessimistic analysis, so it will reject alias types /// and other types that may be actually ok. We can allow more in the future. pub fn is_fully_generic_for_reflection(self) -> bool { #[derive(Default)] @@ -304,14 +304,12 @@ impl<'tcx> ImplTraitHeader<'tcx> { ControlFlow::Break(()) } } - RegionKind::ReBound(..) | RegionKind::ReLateParam(_) => { - ControlFlow::Continue(()) - } - RegionKind::ReStatic - | RegionKind::ReVar(_) + RegionKind::ReBound(..) => ControlFlow::Continue(()), + RegionKind::ReStatic | RegionKind::ReError(_) => ControlFlow::Break(()), + RegionKind::ReVar(_) | RegionKind::RePlaceholder(_) | RegionKind::ReErased - | RegionKind::ReError(_) => ControlFlow::Break(()), + | RegionKind::ReLateParam(_) => bug!("unexpected lifetime in impl: {r:?}"), } } diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index 3baeb7141de50..6f5fe7aefd16e 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -557,6 +557,12 @@ impl<'tcx> UpcastFrom, TypeOutlivesPredicate<'tcx>> for Predicate<' } } +impl<'tcx> UpcastFrom, PolyTypeOutlivesPredicate<'tcx>> for Predicate<'tcx> { + fn upcast_from(from: PolyTypeOutlivesPredicate<'tcx>, tcx: TyCtxt<'tcx>) -> Self { + from.map_bound(|p| PredicateKind::Clause(ClauseKind::TypeOutlives(p))).upcast(tcx) + } +} + impl<'tcx> UpcastFrom, ProjectionPredicate<'tcx>> for Predicate<'tcx> { fn upcast_from(from: ProjectionPredicate<'tcx>, tcx: TyCtxt<'tcx>) -> Self { ty::Binder::dummy(PredicateKind::Clause(ClauseKind::Projection(from))).upcast(tcx) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index c6f62306d8031..c0f14cf2e2419 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -337,6 +337,11 @@ where goal: Goal, ) -> Result, NoSolution>; + fn consider_builtin_try_as_dyn_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution>; + /// Consider (possibly several) candidates to upcast or unsize a type to another /// type, excluding the coercion of a sized type into a `dyn Trait`. /// @@ -530,6 +535,14 @@ where let cx = self.cx(); let trait_def_id = goal.predicate.trait_def_id(cx); + // Builtin impls regularly are not `is_fully_generic_for_reflection`, so instead + // of trying to handle these manually, we just reject all builtin impls in reflection + // mode. We can probably lift this restriction for specific cases, but this is safer. + // See `try_as_dyn_builtin_impl` for how just allowing all builtin impls is unsound. + if let TypingMode::Reflection = self.typing_mode() { + return; + } + // N.B. When assembling built-in candidates for lang items that are also // `auto` traits, then the auto trait candidate that is assembled in // `consider_auto_trait_candidate` MUST be disqualified to remain sound. @@ -622,6 +635,9 @@ where Some(SolverTraitLangItem::BikeshedGuaranteedNoDrop) => { G::consider_builtin_bikeshed_guaranteed_no_drop_candidate(self, goal) } + Some(SolverTraitLangItem::TryAsDyn) => { + G::consider_builtin_try_as_dyn_candidate(self, goal) + } Some(SolverTraitLangItem::Field) => G::consider_builtin_field_candidate(self, goal), _ => Err(NoSolution), } @@ -804,6 +820,14 @@ where return; } + // Builtin impls regularly are not `is_fully_generic_for_reflection`, so instead + // of trying to handle these manually, we just reject all builtin impls in reflection + // mode. We can probably lift this restriction for specific cases, but this is safer. + // See `try_as_dyn_builtin_impl` for how just allowing all builtin impls is unsound. + if let TypingMode::Reflection = self.typing_mode() { + return; + } + let self_ty = goal.predicate.self_ty(); let bounds = match self_ty.kind() { ty::Bool diff --git a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs index 1ce262460722a..3e44966939f1c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/effect_goals.rs @@ -425,6 +425,13 @@ where unreachable!("BikeshedGuaranteedNoDrop is not const"); } + fn consider_builtin_try_as_dyn_candidate( + _ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution> { + unreachable!("`TryAsDynCompat` is not const: {:?}", goal) + } + fn consider_structural_builtin_unsize_candidates( _ecx: &mut EvalCtxt<'_, D>, _goal: Goal, diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs index a9df3d92ad7a0..b74ea63d5d90b 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs @@ -953,6 +953,13 @@ where unreachable!("`BikeshedGuaranteedNoDrop` does not have an associated type: {:?}", goal) } + fn consider_builtin_try_as_dyn_candidate( + _ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution> { + unreachable!("`TryAsDynCompat` does not have an associated type: {:?}", goal) + } + fn consider_builtin_field_candidate( ecx: &mut EvalCtxt<'_, D>, goal: Goal, 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 b9cf8ca919e66..aa25add794310 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -854,6 +854,39 @@ where }) } + fn consider_builtin_try_as_dyn_candidate( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Result, NoSolution> { + if goal.predicate.polarity != ty::PredicatePolarity::Positive { + return Err(NoSolution); + } + let cx = ecx.cx(); + + ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc).enter(|ecx| { + let self_ty = goal.predicate.self_ty(); + let ty = goal.predicate.trait_ref.args.type_at(1); + match self_ty.kind() { + ty::Dynamic(_bounds, lifetime) => { + ecx.add_goal( + GoalSource::Misc, + goal.with(cx, ty::OutlivesPredicate(ty, lifetime)), + ); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } + + ty::Bound(..) + | ty::Infer( + ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_), + ) => { + panic!("unexpected type `{ty:?}`") + } + + _ => Err(NoSolution), + } + }) + } + fn consider_builtin_field_candidate( ecx: &mut EvalCtxt<'_, D>, goal: Goal, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 257ac3f51c2c1..409989cccff8a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2037,6 +2037,7 @@ symbols! { truncf32, truncf64, truncf128, + try_as_dyn, try_blocks, try_blocks_heterogeneous, try_capture, diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index b0af60d2aecfc..57de86f8df661 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -15,7 +15,8 @@ use rustc_hir::{self as hir, CoroutineDesugaring, CoroutineKind}; use rustc_infer::traits::{Obligation, PolyTraitObligation, PredicateObligation, SelectionError}; use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::{ - self, FieldInfo, SizedTraitKind, TraitRef, Ty, TypeVisitableExt, TypingMode, elaborate, + self, ExistentialPredicate, FieldInfo, SizedTraitKind, TraitRef, Ty, TypeVisitableExt, + TypingMode, elaborate, }; use rustc_middle::{bug, span_bug}; use rustc_span::DUMMY_SP; @@ -132,6 +133,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { &mut candidates, ); } + Some(LangItem::TryAsDyn) => { + self.assemble_candidates_for_try_as_dyn(obligation, &mut candidates); + } Some(LangItem::Field) => { self.assemble_candidates_for_field_trait(obligation, &mut candidates); } @@ -1447,6 +1451,34 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { } } + fn assemble_candidates_for_try_as_dyn( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + candidates: &mut SelectionCandidateSet<'tcx>, + ) { + match *obligation.predicate.self_ty().skip_binder().kind() { + ty::Dynamic(bounds, _lifetime) => { + for bound in bounds { + match bound.skip_binder() { + ExistentialPredicate::Trait(_) => {} + // FIXME(try_as_dyn): check what kind of projections we can allow + ExistentialPredicate::Projection(_) => return, + // Auto traits do not affect lifetimes outside of specialization, + // which is disabled in reflection. + ExistentialPredicate::AutoTrait(_) => {} + } + } + candidates.vec.push(TryAsDynCandidate); + } + + ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + candidates.ambiguous = true; + } + + _ => {} + } + } + fn assemble_candidates_for_field_trait( &mut self, obligation: &PolyTraitObligation<'tcx>, diff --git a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs index 9e7637df26490..5e1f01234c972 100644 --- a/compiler/rustc_trait_selection/src/traits/select/confirmation.rs +++ b/compiler/rustc_trait_selection/src/traits/select/confirmation.rs @@ -141,6 +141,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { BikeshedGuaranteedNoDropCandidate => { self.confirm_bikeshed_guaranteed_no_drop_candidate(obligation) } + + TryAsDynCandidate => self.confirm_try_as_dyn_candidate(obligation), }) } @@ -1335,4 +1337,31 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ImplSource::Builtin(BuiltinImplSource::Misc, obligations) } + + fn confirm_try_as_dyn_candidate( + &mut self, + obligation: &PolyTraitObligation<'tcx>, + ) -> ImplSource<'tcx, PredicateObligation<'tcx>> { + let tcx = self.tcx(); + + let mut obligations = PredicateObligations::new(); + + let self_ty = obligation.predicate.self_ty(); + let ty = obligation.predicate.map_bound(|p| p.trait_ref.args.type_at(1)); + + match *self_ty.skip_binder().kind() { + ty::Dynamic(_bounds, lifetime) => { + obligations.push( + obligation.with(tcx, ty.map_bound(|ty| ty::OutlivesPredicate(ty, lifetime))), + ); + } + + ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) => { + panic!("unexpected type `{self_ty:?}`") + } + + _ => {} + } + ImplSource::Builtin(BuiltinImplSource::Misc, obligations) + } } diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 0e3a0cb5f9894..cc76043de36c1 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -2053,6 +2053,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { | BuiltinObjectCandidate | BuiltinUnsizeCandidate | PointerLikeCandidate + | TryAsDynCandidate | BikeshedGuaranteedNoDropCandidate => false, // Non-global param candidates have already been handled, global // where-bounds get ignored. diff --git a/compiler/rustc_type_ir/src/lang_items.rs b/compiler/rustc_type_ir/src/lang_items.rs index f1c45a4d98b5e..fed5fe6d10c6f 100644 --- a/compiler/rustc_type_ir/src/lang_items.rs +++ b/compiler/rustc_type_ir/src/lang_items.rs @@ -52,6 +52,7 @@ pub enum SolverTraitLangItem { Sized, TransmuteTrait, TrivialClone, + TryAsDyn, Tuple, Unpin, Unsize, diff --git a/library/core/src/any.rs b/library/core/src/any.rs index 8580a20ab1140..a1471892ec67e 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -969,12 +969,67 @@ pub const fn type_name_of_val(_val: &T) -> &'static str { type_name::() } -/// Returns `Some(&U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`. +/// Trait that is automatically implemented for all `dyn Trait<'b, C> + 'a` without assoc type bounds. +/// It implicitly adds `T: 'a` requirements, which is otherwise not representable on `Pointee`. +/// +/// This is required for `try_as_dyn` to be able to soundly convert non-static +/// types to `dyn Trait`. +/// +/// Note: these requirements are sufficient for soundness, but it is unclear +/// if they are all necessary. We may be able to lift some requirements in favor +/// of more precise ones. +/// +#[unstable(feature = "try_as_dyn", issue = "144361")] +#[lang = "try_as_dyn"] +#[rustc_deny_explicit_impl] +pub trait TryAsDynCompatible: ptr::Pointee> {} + +/// Returns `Some(&U)` if `T` can be coerced to the dyn trait type `U`. Otherwise, it returns `None`. +/// +/// # Run-time failures +/// +/// There are multiple ways to get a `None`, and you need to manually analyze which one it is, as the +/// compiler does not provide any help here. +/// +/// * `T` does not implement `Trait` at all, +/// * `T`'s impl for `Trait` is not fully generic, +/// * `T`'s impl for `Trait` is a builtin impl (e.g. `dyn Debug` implements `Debug`) +/// +/// ## Fully generic impls +/// +/// `try_as_dyn` does not have access to lifetime information, thus it cannot differentiate between +/// `'static`, other lifetimes, and can't reason about outlives bounds on impls. Thus we can only accept +/// impls that do not have `'static` lifetimes, or outlives bounds of any kind. You can have simple +/// trait bounds, and the compiler will transitively only use impls of those simple trait bounds that satisfy +/// the same rules as the main trait you're converting to. +/// +/// An example of a legal impl is: +/// +/// ```rust +/// # trait Trait<'a, T> {} +/// # struct Type<'b, U>(&'b U); +/// # use std::fmt::{Debug, Display}; +/// impl<'a, 'b, T: Debug, U: Display> Trait<'a, T> for Type<'b, U> {} +/// ``` +/// +/// Impls without generic parameters at all are also legal, as long as they contain no `'static` lifetimes. +/// +/// ## Builtin impls +/// +/// Builtin impls (like `impl Debug for dyn Debug`) have various obscure rules and often are not fully generic. +/// To simplify reasoning about what is allowed and what not, all builtin impls are rejected and will neither +/// directly nor indirectly contribute to a `Some` result. /// /// # Compile-time failures -/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution. +/// Determining whether `T` can be coerced to the dyn trait type `U` requires compiler trait resolution. /// In some cases, that resolution can exceed the recursion limit, /// and compilation will fail instead of this function returning `None`. +/// +/// The input type `T` must outlive the lifetime `'a` on the `dyn Trait + 'a`. +/// This is basically the same rule that forbids `let x: &dyn Trait + 'static = &&some_local_variable;` +/// So if you see borrow check errors around `try_as_dyn`, think about whether a normal unsizing +/// coercion would be possible at all if you were using concrete types or had bounds on the input type. +/// /// # Examples /// /// ```rust @@ -1004,9 +1059,7 @@ pub const fn type_name_of_val(_val: &T) -> &'static str { /// ``` #[must_use] #[unstable(feature = "try_as_dyn", issue = "144361")] -pub const fn try_as_dyn> + ?Sized>( - t: &T, -) -> Option<&U> { +pub const fn try_as_dyn + ?Sized>(t: &T) -> Option<&U> { let vtable: Option> = const { type_id::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { @@ -1022,42 +1075,10 @@ pub const fn try_as_dyn> + ?Si /// Returns `Some(&mut U)` if `T` can be coerced to the trait object type `U`. Otherwise, it returns `None`. /// -/// # Compile-time failures -/// Determining whether `T` can be coerced to the trait object type `U` requires compiler trait resolution. -/// In some cases, that resolution can exceed the recursion limit, -/// and compilation will fail instead of this function returning `None`. -/// # Examples -/// -/// ```rust -/// #![feature(try_as_dyn)] -/// -/// use core::any::try_as_dyn_mut; -/// -/// trait Animal { -/// fn speak(&self) -> &'static str; -/// } -/// -/// struct Dog; -/// impl Animal for Dog { -/// fn speak(&self) -> &'static str { "woof" } -/// } -/// -/// struct Rock; // does not implement Animal -/// -/// let mut dog = Dog; -/// let mut rock = Rock; -/// -/// let as_animal: Option<&mut dyn Animal> = try_as_dyn_mut::(&mut dog); -/// assert_eq!(as_animal.unwrap().speak(), "woof"); -/// -/// let not_an_animal: Option<&mut dyn Animal> = try_as_dyn_mut::(&mut rock); -/// assert!(not_an_animal.is_none()); -/// ``` +/// See documentation of [try_as_dyn] for details about the behaviour and limitations. #[must_use] #[unstable(feature = "try_as_dyn", issue = "144361")] -pub const fn try_as_dyn_mut> + ?Sized>( - t: &mut T, -) -> Option<&mut U> { +pub const fn try_as_dyn_mut + ?Sized>(t: &mut T) -> Option<&mut U> { let vtable: Option> = const { type_id::().trait_info_of::().as_ref().map(TraitImpl::get_vtable) }; match vtable { diff --git a/tests/ui/any/any_static.next.stderr b/tests/ui/any/any_static.next.stderr new file mode 100644 index 0000000000000..9765ab43691f6 --- /dev/null +++ b/tests/ui/any/any_static.next.stderr @@ -0,0 +1,16 @@ +error[E0521]: borrowed data escapes outside of function + --> $DIR/any_static.rs:17:35 + | +LL | fn extend(a: &Payload) -> &'static Payload { + | - - let's call the lifetime of this reference `'1` + | | + | `a` is a reference that is only valid in the function body +LL | let b: &(dyn Any + 'static) = try_as_dyn::<&Payload, dyn Any + 'static>(&a).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `a` escapes the function body here + | argument requires that `'1` must outlive `'static` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0521`. diff --git a/tests/ui/any/any_static.old.stderr b/tests/ui/any/any_static.old.stderr new file mode 100644 index 0000000000000..9765ab43691f6 --- /dev/null +++ b/tests/ui/any/any_static.old.stderr @@ -0,0 +1,16 @@ +error[E0521]: borrowed data escapes outside of function + --> $DIR/any_static.rs:17:35 + | +LL | fn extend(a: &Payload) -> &'static Payload { + | - - let's call the lifetime of this reference `'1` + | | + | `a` is a reference that is only valid in the function body +LL | let b: &(dyn Any + 'static) = try_as_dyn::<&Payload, dyn Any + 'static>(&a).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `a` escapes the function body here + | argument requires that `'1` must outlive `'static` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0521`. diff --git a/tests/ui/any/any_static.rs b/tests/ui/any/any_static.rs new file mode 100644 index 0000000000000..14ecfe3b07ce5 --- /dev/null +++ b/tests/ui/any/any_static.rs @@ -0,0 +1,21 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +#![feature(try_as_dyn)] + +use std::any::{Any, try_as_dyn}; + +type Payload = Box; + +fn main() { + let storage: Box = Box::new(Box::new(1i32)); + let wrong: &'static Payload = extend(&*storage); + drop(storage); + println!("{wrong}"); +} + +fn extend(a: &Payload) -> &'static Payload { + let b: &(dyn Any + 'static) = try_as_dyn::<&Payload, dyn Any + 'static>(&a).unwrap(); + //~^ ERROR: borrowed data escapes outside of function + let c: &&'static Payload = b.downcast_ref::<&'static Payload>().unwrap(); + *c +} diff --git a/tests/ui/any/hrtb.next.stderr b/tests/ui/any/hrtb.next.stderr new file mode 100644 index 0000000000000..5ba8cc04817a7 --- /dev/null +++ b/tests/ui/any/hrtb.next.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/hrtb.rs:14:25 + | +LL | let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/hrtb.old.stderr b/tests/ui/any/hrtb.old.stderr new file mode 100644 index 0000000000000..5ba8cc04817a7 --- /dev/null +++ b/tests/ui/any/hrtb.old.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/hrtb.rs:14:25 + | +LL | let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/hrtb.rs b/tests/ui/any/hrtb.rs new file mode 100644 index 0000000000000..20b19a1533b89 --- /dev/null +++ b/tests/ui/any/hrtb.rs @@ -0,0 +1,18 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +trait Foo<'a, 'b> {} + +trait Bar {} +impl Foo<'a, 'b> + ?Sized> Bar for Option<*const T> {} + +const _: () = { + let x: Option<*const dyn for<'a> Foo<'a, 'a>> = None; + let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + //~^ ERROR: `Option::unwrap()` on a `None` value +}; + +fn main() {} diff --git a/tests/ui/any/hrtb2.next.stderr b/tests/ui/any/hrtb2.next.stderr new file mode 100644 index 0000000000000..0b22387de490b --- /dev/null +++ b/tests/ui/any/hrtb2.next.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/hrtb2.rs:14:25 + | +LL | let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/hrtb2.old.stderr b/tests/ui/any/hrtb2.old.stderr new file mode 100644 index 0000000000000..0b22387de490b --- /dev/null +++ b/tests/ui/any/hrtb2.old.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/hrtb2.rs:14:25 + | +LL | let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/hrtb2.rs b/tests/ui/any/hrtb2.rs new file mode 100644 index 0000000000000..fefe88aa7cdb8 --- /dev/null +++ b/tests/ui/any/hrtb2.rs @@ -0,0 +1,18 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +trait Foo<'a> {} + +trait Bar {} +impl Bar for Option<*const T> where T: for<'a> Foo<'a> {} + +const _: () = { + let x: Option<*const dyn Foo<'_>> = None; + let _dy: &dyn Bar = try_as_dyn(&x).unwrap(); + //~^ ERROR: `Option::unwrap()` on a `None` value +}; + +fn main() {} diff --git a/tests/ui/any/reject_manual_impl.rs b/tests/ui/any/reject_manual_impl.rs new file mode 100644 index 0000000000000..538a8ff456129 --- /dev/null +++ b/tests/ui/any/reject_manual_impl.rs @@ -0,0 +1,29 @@ +#![feature(try_as_dyn)] + +use std::any::TryAsDynCompatible; + +struct Foo(dyn Iterator); + +impl TryAsDynCompatible for Foo {} +//~^ ERROR: explicit impls for the `TryAsDynCompatible` trait are not permitted + +struct Bar(dyn Iterator); + +impl TryAsDynCompatible for Bar {} +//~^ ERROR: explicit impls for the `TryAsDynCompatible` trait are not permitted + +struct Baz; + +impl TryAsDynCompatible for Baz {} +//~^ ERROR: explicit impls for the `TryAsDynCompatible` trait are not permitted + +trait Trait {} + +impl TryAsDynCompatible for dyn Trait {} +//~^ ERROR: explicit impls for the `TryAsDynCompatible` trait are not permitted + +impl TryAsDynCompatible for dyn Iterator {} +//~^ ERROR: explicit impls for the `TryAsDynCompatible` trait are not permitted +//~| ERROR: only traits defined in the current crate can be implemented for arbitrary types + +fn main() {} diff --git a/tests/ui/any/reject_manual_impl.stderr b/tests/ui/any/reject_manual_impl.stderr new file mode 100644 index 0000000000000..b4be5cbf9d93c --- /dev/null +++ b/tests/ui/any/reject_manual_impl.stderr @@ -0,0 +1,47 @@ +error[E0322]: explicit impls for the `TryAsDynCompatible` trait are not permitted + --> $DIR/reject_manual_impl.rs:7:1 + | +LL | impl TryAsDynCompatible for Foo {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `TryAsDynCompatible` not allowed + +error[E0322]: explicit impls for the `TryAsDynCompatible` trait are not permitted + --> $DIR/reject_manual_impl.rs:12:1 + | +LL | impl TryAsDynCompatible for Bar {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `TryAsDynCompatible` not allowed + +error[E0322]: explicit impls for the `TryAsDynCompatible` trait are not permitted + --> $DIR/reject_manual_impl.rs:17:1 + | +LL | impl TryAsDynCompatible for Baz {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `TryAsDynCompatible` not allowed + +error[E0322]: explicit impls for the `TryAsDynCompatible` trait are not permitted + --> $DIR/reject_manual_impl.rs:22:1 + | +LL | impl TryAsDynCompatible for dyn Trait {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `TryAsDynCompatible` not allowed + +error[E0322]: explicit impls for the `TryAsDynCompatible` trait are not permitted + --> $DIR/reject_manual_impl.rs:25:1 + | +LL | impl TryAsDynCompatible for dyn Iterator {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl of `TryAsDynCompatible` not allowed + +error[E0117]: only traits defined in the current crate can be implemented for arbitrary types + --> $DIR/reject_manual_impl.rs:25:1 + | +LL | impl TryAsDynCompatible for dyn Iterator {} + | ^^^^^-----------------------^^^^^------------------------ + | | | + | | `dyn Iterator` is not defined in the current crate + | `u32` is not defined in the current crate + | + = note: impl doesn't have any local type before any uncovered type parameters + = note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules + = note: define and implement a trait or new type instead + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0117, E0322. +For more information about an error, try `rustc --explain E0117`. diff --git a/tests/ui/any/static_method_bound.rs b/tests/ui/any/static_method_bound.rs index aaab8623868f1..b2cc77e4d9cfe 100644 --- a/tests/ui/any/static_method_bound.rs +++ b/tests/ui/any/static_method_bound.rs @@ -1,4 +1,3 @@ -//@run-fail #![feature(try_as_dyn)] use std::any::try_as_dyn; @@ -28,7 +27,7 @@ fn main() { } fn extend(a: &Payload) -> &'static Payload { - // TODO: should panic at the `unwrap` here let b: &(dyn Trait + 'static) = try_as_dyn::<&Payload, dyn Trait + 'static>(&a).unwrap(); + //~^ ERROR: borrowed data escapes outside of function b.as_static() } diff --git a/tests/ui/any/static_method_bound.stderr b/tests/ui/any/static_method_bound.stderr new file mode 100644 index 0000000000000..152672d024ab1 --- /dev/null +++ b/tests/ui/any/static_method_bound.stderr @@ -0,0 +1,16 @@ +error[E0521]: borrowed data escapes outside of function + --> $DIR/static_method_bound.rs:30:37 + | +LL | fn extend(a: &Payload) -> &'static Payload { + | - - let's call the lifetime of this reference `'1` + | | + | `a` is a reference that is only valid in the function body +LL | let b: &(dyn Trait + 'static) = try_as_dyn::<&Payload, dyn Trait + 'static>(&a).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | `a` escapes the function body here + | argument requires that `'1` must outlive `'static` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0521`. diff --git a/tests/ui/any/try_as_dyn.rs b/tests/ui/any/try_as_dyn.rs index ee220f797ced9..17a5fb45bcb6e 100644 --- a/tests/ui/any/try_as_dyn.rs +++ b/tests/ui/any/try_as_dyn.rs @@ -1,3 +1,5 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver //@ run-pass #![feature(try_as_dyn)] @@ -7,7 +9,7 @@ use std::fmt::Debug; fn debug_format_with_try_as_dyn(t: &T) -> String { match std::any::try_as_dyn::<_, dyn Debug>(t) { Some(d) => format!("{d:?}"), - None => "default".to_string() + None => "default".to_string(), } } @@ -16,7 +18,7 @@ fn main() { #[allow(dead_code)] #[derive(Debug)] struct A { - index: usize + index: usize, } let a = A { index: 42 }; let result = debug_format_with_try_as_dyn(&a); diff --git a/tests/ui/any/try_as_dyn_assoc_ty_lifetime.rs b/tests/ui/any/try_as_dyn_assoc_ty_lifetime.rs new file mode 100644 index 0000000000000..0e392ad38868b --- /dev/null +++ b/tests/ui/any/try_as_dyn_assoc_ty_lifetime.rs @@ -0,0 +1,26 @@ +//@check-pass +//@ revisions: next old +//@[next] compile-flags: -Znext-solver + +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +trait HasAssoc<'a> { + type Assoc; +} +struct Dummy; +impl<'a> HasAssoc<'a> for Dummy { + // Changing this to &'a i64 makes try_as_dyn succeed + type Assoc = &'static i64; +} + +trait Trait {} +impl Trait for i32 where for<'a> Dummy: HasAssoc<'a, Assoc = &'a i64> {} + +const _: () = { + let x = 1i32; + assert!(try_as_dyn::<_, dyn Trait>(&x).is_none()); +}; + +fn main() {} diff --git a/tests/ui/any/try_as_dyn_builtin_impl.rs b/tests/ui/any/try_as_dyn_builtin_impl.rs new file mode 100644 index 0000000000000..d1410eb847e90 --- /dev/null +++ b/tests/ui/any/try_as_dyn_builtin_impl.rs @@ -0,0 +1,47 @@ +#![feature(try_as_dyn)] +//@ run-fail +//@ revisions: next old +//@[next] compile-flags: -Znext-solver + +use std::any::{Any, try_as_dyn}; + +type Payload = Box; + +trait Outlives<'b>: 'b {} + +trait WithLt { + type Ref; +} +impl<'a> WithLt for dyn for<'b> Outlives<'b> + 'a { + type Ref = &'a Payload; +} + +struct Thing(T::Ref); + +trait Trait { + fn get(&self) -> &'static Payload; +} +impl Trait for Thing +where + T: WithLt + for<'b> Outlives<'b> + ?Sized, +{ + fn get(&self) -> &'static Payload { + let x: &::Ref = &self.0; + let y: &(dyn Any + 'static) = x; + let z: &&'static Payload = y.downcast_ref().unwrap(); + *z + } +} + +fn extend<'a>(payload: &'a Payload) -> &'static Payload { + let thing: Thing Outlives<'b> + 'a> = Thing(payload); + let dy: &dyn Trait = try_as_dyn(&thing).unwrap(); + dy.get() +} + +fn main() { + let payload: Box = Box::new(Box::new(1)); + let wrong: &'static Payload = extend(&*payload); + drop(payload); + println!("{wrong}"); +} diff --git a/tests/ui/any/try_as_dyn_elaborated_bounds.rs b/tests/ui/any/try_as_dyn_elaborated_bounds.rs new file mode 100644 index 0000000000000..a934b18773920 --- /dev/null +++ b/tests/ui/any/try_as_dyn_elaborated_bounds.rs @@ -0,0 +1,28 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +//@check-pass + +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +trait Trait: 'static {} +trait Other {} +struct Foo(T); + +impl Trait for () {} +impl Trait for &'static () {} + +// This impl has an implied `T: 'static` bound, but that's +// not an issue, as we just ignore all `Trait` impls where +// that would be a relevant distinguisher. +impl Other for Foo {} + +const _: () = { + let foo = Foo(()); + assert!(try_as_dyn::, dyn Other>(&foo).is_some()); + let foo = Foo(&()); + assert!(try_as_dyn::, dyn Other>(&foo).is_none()); +}; + +fn main() {} diff --git a/tests/ui/any/try_as_dyn_generic_impl.rs b/tests/ui/any/try_as_dyn_generic_impl.rs new file mode 100644 index 0000000000000..da88a3fe785bc --- /dev/null +++ b/tests/ui/any/try_as_dyn_generic_impl.rs @@ -0,0 +1,39 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +#![feature(try_as_dyn)] +//@ check-pass + +use std::any::try_as_dyn; + +struct Thing(T); +trait Trait {} +impl Trait for Thing {} + +const _: () = { + let thing = Thing(1); + assert!(try_as_dyn::<_, dyn Trait>(&thing).is_some()); +}; + +struct Thing2(T); +impl Trait for Thing2 {} +struct NoDebug; + +const _: () = { + let thing = Thing2(1); + assert!(try_as_dyn::<_, dyn Trait>(&thing).is_some()); + let thing = Thing2(NoDebug); + assert!(try_as_dyn::<_, dyn Trait>(&thing).is_none()); +}; + +trait Trait2 {} +impl<'a, 'b> Trait2 for &'a &'b () {} + +struct Thing3(T); +impl Trait for Thing3 {} + +const _: () = { + let thing = Thing3(&&()); + assert!(try_as_dyn::<_, dyn Trait2>(&thing).is_none()); +}; + +fn main() {} diff --git a/tests/ui/any/try_as_dyn_generic_trait.next.stderr b/tests/ui/any/try_as_dyn_generic_trait.next.stderr new file mode 100644 index 0000000000000..55c9b004862f6 --- /dev/null +++ b/tests/ui/any/try_as_dyn_generic_trait.next.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/try_as_dyn_generic_trait.rs:21:51 + | +LL | let convert: &dyn Convert<&'static Payload> = try_as_dyn(&payload).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/try_as_dyn_generic_trait.old.stderr b/tests/ui/any/try_as_dyn_generic_trait.old.stderr new file mode 100644 index 0000000000000..55c9b004862f6 --- /dev/null +++ b/tests/ui/any/try_as_dyn_generic_trait.old.stderr @@ -0,0 +1,9 @@ +error[E0080]: evaluation panicked: called `Option::unwrap()` on a `None` value + --> $DIR/try_as_dyn_generic_trait.rs:21:51 + | +LL | let convert: &dyn Convert<&'static Payload> = try_as_dyn(&payload).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/any/try_as_dyn_generic_trait.rs b/tests/ui/any/try_as_dyn_generic_trait.rs new file mode 100644 index 0000000000000..47fdb49c2cf34 --- /dev/null +++ b/tests/ui/any/try_as_dyn_generic_trait.rs @@ -0,0 +1,25 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver +#![feature(try_as_dyn)] + +use std::any::try_as_dyn; + +type Payload = *const i32; + +trait Convert { + fn convert(&self) -> &T; +} + +impl Convert for T { + fn convert(&self) -> &T { + self + } +} + +const _: () = { + let payload: Payload = std::ptr::null(); + let convert: &dyn Convert<&'static Payload> = try_as_dyn(&payload).unwrap(); + //~^ ERROR: `Option::unwrap()` on a `None` value +}; + +fn main() {} diff --git a/tests/ui/any/try_as_dyn_mut.rs b/tests/ui/any/try_as_dyn_mut.rs index ff7baa32ea874..464221fef20f7 100644 --- a/tests/ui/any/try_as_dyn_mut.rs +++ b/tests/ui/any/try_as_dyn_mut.rs @@ -1,3 +1,5 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver //@ run-pass #![feature(try_as_dyn)] @@ -7,7 +9,7 @@ use std::fmt::{Error, Write}; fn try_as_dyn_mut_write(t: &mut T, s: &str) -> Result<(), Error> { match std::any::try_as_dyn_mut::<_, dyn Write>(t) { Some(w) => w.write_str(s), - None => Ok(()) + None => Ok(()), } } diff --git a/tests/ui/any/try_as_dyn_soundness_test1.rs b/tests/ui/any/try_as_dyn_soundness_test1.rs index 0772e9a2d77ee..6bc6bc5a66b97 100644 --- a/tests/ui/any/try_as_dyn_soundness_test1.rs +++ b/tests/ui/any/try_as_dyn_soundness_test1.rs @@ -1,19 +1,15 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver //@ run-pass #![feature(try_as_dyn)] use std::any::try_as_dyn; -trait Trait { +trait Trait {} -} - -impl Trait for for<'a> fn(&'a Box) { - -} - -fn store(_: &'static Box) { +impl Trait for for<'a> fn(&'a Box) {} -} +fn store(_: &'static Box) {} fn main() { let fn_ptr: fn(&'static Box) = store; diff --git a/tests/ui/any/try_as_dyn_soundness_test2.rs b/tests/ui/any/try_as_dyn_soundness_test2.rs index c16b50d0261ed..a65fe2955374f 100644 --- a/tests/ui/any/try_as_dyn_soundness_test2.rs +++ b/tests/ui/any/try_as_dyn_soundness_test2.rs @@ -1,14 +1,12 @@ +//@ revisions: next old +//@[next] compile-flags: -Znext-solver //@ run-pass #![feature(try_as_dyn)] use std::any::try_as_dyn; -trait Trait { +trait Trait {} -} - -impl Trait fn(&'a Box)> for () { - -} +impl Trait fn(&'a Box)> for () {} fn main() { let dt = try_as_dyn::<_, dyn Trait)>>(&()); From ae17524e8874b6541df442767edcaf57aac25dbb Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Tue, 10 Mar 2026 11:32:31 +0000 Subject: [PATCH 3/3] Add unstable book entry --- library/core/src/any.rs | 6 +- .../src/language-features/try_as_dyn.md | 77 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/doc/unstable-book/src/language-features/try_as_dyn.md diff --git a/library/core/src/any.rs b/library/core/src/any.rs index a1471892ec67e..b6a4d353e3b46 100644 --- a/library/core/src/any.rs +++ b/library/core/src/any.rs @@ -995,7 +995,11 @@ pub trait TryAsDynCompatible: ptr::Pointee> /// * `T`'s impl for `Trait` is not fully generic, /// * `T`'s impl for `Trait` is a builtin impl (e.g. `dyn Debug` implements `Debug`) /// -/// ## Fully generic impls +/// There is some detailed documentation about this feature at +/// https://doc.rust-lang.org/unstable-book/language-features/try_as_dyn.html +/// But the gist is summarized below: +/// +/// ## Lifetime-independent impls /// /// `try_as_dyn` does not have access to lifetime information, thus it cannot differentiate between /// `'static`, other lifetimes, and can't reason about outlives bounds on impls. Thus we can only accept diff --git a/src/doc/unstable-book/src/language-features/try_as_dyn.md b/src/doc/unstable-book/src/language-features/try_as_dyn.md new file mode 100644 index 0000000000000..be724ae35bbd5 --- /dev/null +++ b/src/doc/unstable-book/src/language-features/try_as_dyn.md @@ -0,0 +1,77 @@ +# `try_as_dyn` + +The tracking issue for this feature is: [#144361] + +[#144361]: https://github.com/rust-lang/rust/issues/144361 + +------------------------ + +The `try_as_dyn` feature allows going from a generic `T` with no bounds +to a `dyn Trait`, if `T: Trait` and various conditions are upheld. It is +very related to specialization, as it allows you to specialize within +function bodies, but in a more general manner than `Any::downcast`. + +```rust +#![feature(try_as_dyn)] + +fn downcast_debug_format(t: &T) -> String { + match std::any::try_as_dyn::<_, dyn Debug>(t) { + Some(d) => format!("{d:?}"), + None => "default".to_string() + } +} +``` + + +## Rules and reasons for them + +> [!IMPORTANT] +> The main problem of **`try_as_dyn` and specialization is the compiler's inability, while trait-checking, to distinguish/_discriminate_ between any two given lifetimes**[^1]. + +[^1]: the compiler cannot _branch_ on whether "`'a : 'b` holds": for soundness, it can either choose not to know the answer, or _assume_ that it holds and produce an obligation for the borrow-checker which shall "assert this" (making compilation fail in a fatal manner if not). Most usages of Rust lie in the latter category (typical `where` clauses anywhere), whilst specialization/`try_as_dyn()` wants to support fallibility of the operation (_i.e._, being queried on a type not fulfilling the predicate without causing a compilation error). This rules out the latter, resulting in the need for the former, _i.e._, for the `try_as_dyn()` attempt to unconditionally "fail" with `None`. + +### `'static` is not mentioned anywhere in the `impl` block header. + +The most obvious one: if you have `impl IsStatic for &'static str`, then determining whether `&'? str : IsStatic` does hold amounts to discriminating `'? : 'static`. + +### Each outlives `where` bound (`Type: 'a` and `'a: 'b`) does not mention lifetime-infected parameters. + +We can create lifetime discrimination this way. For instance, given `impl<'a, 'b> Outlives<'a> for &'b str where 'b : 'a {}`, `Outlives<'static>` amounts to `IsStatic` from previous bullet. + +### Each lifetime-infected parameter is mentioned at most once in the `Self` type and the implemented trait's generic parameters, combined. + +Repetition of a parameter entails equality of those two use-sites; in lifetime-terms, this would be a double `'a : 'b` / `'b : 'a` clause, for instance. +Follow-up from the previous example: `impl<'a> Uses<'a> for &'a str {}`, and check whether `&'? str : Uses<'static>`. + +### Each individual trait where bound (`Type: Trait`) mentions each lifetime-infected parameter at most once. + +Mentioning a lifetime-infected parameter in multiple `where` bounds is allowed. + +Looking at the previous rules, which focuses on `Self : …`, this is just observing that shifting the requirements to other parameters within `where` clauses \[ought to\] boil down to the same set of issues. + +This is _unnecessarily restrictive_: we should be able to loosen it up somehow. Repetition only in `where` clauses seems fine. + + +### The `impl` block is a handwritten impl + +as opposed to a type implementing a trait automatically by the compiler (such as auto-traits, `dyn Bounds… : Bounds…`, and closures) + + +The reason for this is that some such auto-generated impls _come with hidden bounds or whatnot_, which run afoul of the previous rules, whilst also being _extremely challenging for the current compiler logic to know of such bounds_. +IIUC, this restriction could be lifted in the future should the compiler logic be better at spotting these hidden bounds, when present. + +One contrived such example being the case of `dyn 'u + for<'a> Outlives<'a>`, where the compiler-generated `impl` for it of `Outlives` is: `impl<'b, 'u> Outlives<'b> for dyn 'u + for<'a> Outlives<'a> where 'b : 'u {}` which violates the "`'a: 'b` not to mention lt-infected params" rule, whilst also being hard to detect in current compiler logic. + +### Associated type projections (`::Assoc`) are not mentioned anywhere in the `impl` block header. + +Checking whether `&'? str: Trait` discriminates `'?` against `'static` in the following scenario, for instance: + +```rust +trait Assoc { type StaticStr; } +impl Assoc for () { type StaticStr = &'static str; } +impl Trait for <() as Assoc>::StaticStr {} +``` + +### Each trait `where` bound with an associated type equality (`Type: Trait`) does not mention lifetime-infected parameters. + +Checking whether `Option<&'? str>: IntoIterator` holds discriminates `'?` against `'static`.