diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 56e272b14bd61..7be8ac408488f 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -356,7 +356,7 @@ fn check_opaque_meets_bounds<'tcx>( // Can have different predicates to their defining use hir::OpaqueTyOrigin::TyAlias { .. } => { let wf_tys = ocx.assumed_wf_types_and_report_errors(param_env, def_id)?; - let implied_bounds = infcx.implied_bounds_tys(param_env, def_id, wf_tys); + let implied_bounds = infcx.implied_bounds_tys_compat(param_env, def_id, &wf_tys); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); ocx.resolve_regions_and_report_errors(defining_use_anchor, &outlives_env)?; } diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs index 1412fd1a987cb..f1ad18b47c526 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs @@ -404,7 +404,7 @@ fn compare_method_predicate_entailment<'tcx>( // lifetime parameters. let outlives_env = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys(param_env, impl_m_def_id, wf_tys), + infcx.implied_bounds_tys_compat(param_env, impl_m_def_id, &wf_tys), ); let errors = infcx.resolve_regions(&outlives_env); if !errors.is_empty() { @@ -880,7 +880,7 @@ pub(super) fn collect_return_position_impl_trait_in_trait_tys<'tcx>( // lifetime parameters. let outlives_env = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys(param_env, impl_m_def_id, wf_tys), + infcx.implied_bounds_tys_compat(param_env, impl_m_def_id, &wf_tys), ); ocx.resolve_regions_and_report_errors(impl_m_def_id, &outlives_env)?; @@ -2242,7 +2242,8 @@ pub(super) fn check_type_bounds<'tcx>( // Finally, resolve all regions. This catches wily misuses of // lifetime parameters. - let implied_bounds = infcx.implied_bounds_tys(param_env, impl_ty_def_id, assumed_wf_types); + let implied_bounds = + infcx.implied_bounds_tys_compat(param_env, impl_ty_def_id, &assumed_wf_types); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); ocx.resolve_regions_and_report_errors(impl_ty_def_id, &outlives_env) } diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs index 86c38480d57c4..24bff188b1e2d 100644 --- a/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs +++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item/refine.rs @@ -161,7 +161,7 @@ pub(super) fn check_refining_return_position_impl_trait_in_trait<'tcx>( } let outlives_env = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys(param_env, impl_m.def_id.expect_local(), implied_wf_types), + infcx.implied_bounds_tys_compat(param_env, impl_m.def_id.expect_local(), &implied_wf_types), ); let errors = infcx.resolve_regions(&outlives_env); if !errors.is_empty() { diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 3e805ab00b9c7..54262c6ad8b1b 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -13,6 +13,7 @@ use rustc_infer::infer::outlives::obligations::TypeOutlives; use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt}; use rustc_middle::mir::ConstraintCategory; use rustc_middle::query::Providers; +use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{ self, AdtKind, GenericParamDefKind, ToPredicate, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, @@ -110,8 +111,6 @@ where let assumed_wf_types = wfcx.ocx.assumed_wf_types_and_report_errors(param_env, body_def_id)?; - let implied_bounds = infcx.implied_bounds_tys(param_env, body_def_id, assumed_wf_types); - let errors = wfcx.select_all_or_error(); if !errors.is_empty() { let err = infcx.err_ctxt().report_fulfillment_errors(errors); @@ -126,9 +125,60 @@ where } } + let infcx_compat = infcx.fork(); + + debug!(?assumed_wf_types); + let implied_bounds = infcx.implied_bounds_tys(param_env, body_def_id, &assumed_wf_types); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); - wfcx.ocx.resolve_regions_and_report_errors(body_def_id, &outlives_env)?; + let errors = infcx.resolve_regions(&outlives_env); + if errors.is_empty() { + return Ok(()); + } + + let implied_bounds = + infcx_compat.implied_bounds_tys_compat(param_env, body_def_id, &assumed_wf_types); + let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); + let errors_compat = infcx_compat.resolve_regions(&outlives_env); + if !errors_compat.is_empty() { + return Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat)); + } + + // We don't want to emit this for dependents of Bevy, for now. + for ty in assumed_wf_types.iter() { + match ty.kind() { + ty::Adt(def, _) => { + let adt_did = with_no_trimmed_paths!(infcx.tcx.def_path_str(def.0.did)); + if adt_did.contains("ParamSet") { + return Ok(()); + } + } + _ => {} + } + } + + let hir_id = tcx.local_def_id_to_hir_id(body_def_id); + let (lint_level, _) = tcx + .lint_level_at_node(rustc_session::lint::builtin::IMPLIED_BOUNDS_FROM_TRAIT_IMPL, hir_id); + tcx.struct_span_lint_hir( + rustc_session::lint::builtin::IMPLIED_BOUNDS_FROM_TRAIT_IMPL, + hir_id, + tcx.def_span(body_def_id), + format!("{} is missing necessary lifetime bounds", tcx.def_descr(body_def_id.into())), + |lint| { + if !lint_level.is_error() { + lint.note( + "to get more detailed errors, use `#[deny(implied_bounds_from_trait_impl)]`", + ) + } else { + lint.note("more concrete lifetime errors are emitted below") + } + }, + ); + if true || lint_level.is_error() { + infcx.err_ctxt().report_region_errors(body_def_id, &errors); + } + infcx.tainted_by_errors().error_reported() } @@ -717,7 +767,7 @@ fn resolve_regions_with_wf_tys<'tcx>( let infcx = tcx.infer_ctxt().build(); let outlives_environment = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys(param_env, id, wf_tys.clone()), + infcx.implied_bounds_tys_compat(param_env, id, wf_tys), ); let region_bound_pairs = outlives_environment.region_bound_pairs(); diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index 7941861fd2f7d..768c42c67b8df 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -202,7 +202,8 @@ fn get_impl_args( return Err(guar); } - let implied_bounds = infcx.implied_bounds_tys(param_env, impl1_def_id, assumed_wf_types); + let implied_bounds = + infcx.implied_bounds_tys_compat(param_env, impl1_def_id, &assumed_wf_types); let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds); let _ = ocx.resolve_regions_and_report_errors(impl1_def_id, &outlives_env); let Ok(impl2_args) = infcx.fully_resolve(impl2_args) else { diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index fc2b2192febb6..75848d3cbd5a3 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3389,6 +3389,7 @@ declare_lint_pass! { ILL_FORMED_ATTRIBUTE_INPUT, ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, IMPLIED_BOUNDS_ENTAILMENT, + IMPLIED_BOUNDS_FROM_TRAIT_IMPL, INCOMPLETE_INCLUDE, INDIRECT_STRUCTURAL_MATCH, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, @@ -4621,3 +4622,44 @@ declare_lint! { reference: "issue #115010 ", }; } + +declare_lint! { + /// The `implied_bounds_from_trait_impl` lint detects + /// a compiler bug allowed some code to assume invalid implied lifetime bounds. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(implied_bounds_from_trait_impl)] + /// + /// trait Trait { + /// type Assoc; + /// } + /// + /// impl Trait for (X,) { + /// type Assoc = (); + /// } + /// + /// struct Foo(T) + /// where + /// T::Assoc: Clone; // any bound using `T::Assoc` + /// + /// fn func(foo: Foo<(&str,)>) { + /// let _: &'static str = foo.0.0; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// FIXME: explanataion + /// + pub IMPLIED_BOUNDS_FROM_TRAIT_IMPL, + Warn, + "item uses illegal implied bounds derived from a trait impl", + @future_incompatible = FutureIncompatibleInfo { + reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps, + reference: "issue #109628 ", + }; +} diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index cdde6a596a80e..6bb2e36c1d553 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -74,6 +74,10 @@ impl EraseType for Result<&'_ T, traits::query::NoSolution> { type Result = [u8; size_of::>()]; } +impl EraseType for Result<&'_ [T], traits::query::NoSolution> { + type Result = [u8; size_of::>()]; +} + impl EraseType for Result<&'_ T, rustc_errors::ErrorGuaranteed> { type Result = [u8; size_of::>()]; } diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 03f3ceb8d1733..cbe30f06e4bb9 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1953,7 +1953,7 @@ rustc_queries! { desc { "normalizing `{}`", goal.value } } - query implied_outlives_bounds( + query implied_outlives_bounds_compat( goal: CanonicalTyGoal<'tcx> ) -> Result< &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec>>>, @@ -1962,6 +1962,15 @@ rustc_queries! { desc { "computing implied outlives bounds for `{}`", goal.value.value } } + query implied_outlives_bounds( + goal: CanonicalTyGoal<'tcx> + ) -> Result< + &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec>>>, + NoSolution, + > { + desc { "computing implied outlives bounds v2 for `{}`", goal.value.value } + } + /// Do not call this query directly: /// invoke `DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx)` instead. query dropck_outlives( diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs index 34c56eb0d7b1e..f7ceec7546951 100644 --- a/compiler/rustc_middle/src/traits/query.rs +++ b/compiler/rustc_middle/src/traits/query.rs @@ -191,7 +191,7 @@ pub struct NormalizationResult<'tcx> { /// case they are called implied bounds). They are fed to the /// `OutlivesEnv` which in turn is supplied to the region checker and /// other parts of the inference system. -#[derive(Clone, Debug, TypeFoldable, TypeVisitable, HashStable)] +#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, HashStable)] pub enum OutlivesBound<'tcx> { RegionSubRegion(ty::Region<'tcx>, ty::Region<'tcx>), RegionSubParam(ty::Region<'tcx>, ty::ParamTy), diff --git a/compiler/rustc_trait_selection/src/traits/misc.rs b/compiler/rustc_trait_selection/src/traits/misc.rs index 2f2411310a9a0..90d03cd1c354a 100644 --- a/compiler/rustc_trait_selection/src/traits/misc.rs +++ b/compiler/rustc_trait_selection/src/traits/misc.rs @@ -191,10 +191,10 @@ pub fn all_fields_implement_trait<'tcx>( // Check regions assuming the self type of the impl is WF let outlives_env = OutlivesEnvironment::with_bounds( param_env, - infcx.implied_bounds_tys( + infcx.implied_bounds_tys_compat( param_env, parent_cause.body_id, - FxIndexSet::from_iter([self_type]), + &FxIndexSet::from_iter([self_type]), ), ); let errors = infcx.resolve_regions(&outlives_env); diff --git a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs index 7513f88cfc3cf..b6a80dfe6aefa 100644 --- a/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/outlives_bounds.rs @@ -9,124 +9,140 @@ use rustc_span::def_id::LocalDefId; pub use rustc_middle::traits::query::OutlivesBound; +pub type BoundsCompat<'a, 'tcx: 'a> = impl Iterator> + 'a; pub type Bounds<'a, 'tcx: 'a> = impl Iterator> + 'a; pub trait InferCtxtExt<'a, 'tcx> { - fn implied_outlives_bounds( - &self, + fn implied_bounds_tys_compat( + &'a self, param_env: ty::ParamEnv<'tcx>, body_id: LocalDefId, - ty: Ty<'tcx>, - ) -> Vec>; + tys: &'a FxIndexSet>, + ) -> BoundsCompat<'a, 'tcx>; fn implied_bounds_tys( &'a self, param_env: ty::ParamEnv<'tcx>, body_id: LocalDefId, - tys: FxIndexSet>, + tys: &'a FxIndexSet>, ) -> Bounds<'a, 'tcx>; } -impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> { - /// Implied bounds are region relationships that we deduce - /// automatically. The idea is that (e.g.) a caller must check that a - /// function's argument types are well-formed immediately before - /// calling that fn, and hence the *callee* can assume that its - /// argument types are well-formed. This may imply certain relationships - /// between generic parameters. For example: - /// ``` - /// fn foo(x: &T) {} - /// ``` - /// can only be called with a `'a` and `T` such that `&'a T` is WF. - /// For `&'a T` to be WF, `T: 'a` must hold. So we can assume `T: 'a`. - /// - /// # Parameters - /// - /// - `param_env`, the where-clauses in scope - /// - `body_id`, the body-id to use when normalizing assoc types. - /// Note that this may cause outlives obligations to be injected - /// into the inference context with this body-id. - /// - `ty`, the type that we are supposed to assume is WF. - #[instrument(level = "debug", skip(self, param_env, body_id), ret)] - fn implied_outlives_bounds( - &self, - param_env: ty::ParamEnv<'tcx>, - body_id: LocalDefId, - ty: Ty<'tcx>, - ) -> Vec> { - let ty = self.resolve_vars_if_possible(ty); - let ty = OpportunisticRegionResolver::new(self).fold_ty(ty); +/// Implied bounds are region relationships that we deduce +/// automatically. The idea is that (e.g.) a caller must check that a +/// function's argument types are well-formed immediately before +/// calling that fn, and hence the *callee* can assume that its +/// argument types are well-formed. This may imply certain relationships +/// between generic parameters. For example: +/// ``` +/// fn foo(x: &T) {} +/// ``` +/// can only be called with a `'a` and `T` such that `&'a T` is WF. +/// For `&'a T` to be WF, `T: 'a` must hold. So we can assume `T: 'a`. +/// +/// # Parameters +/// +/// - `param_env`, the where-clauses in scope +/// - `body_id`, the body-id to use when normalizing assoc types. +/// Note that this may cause outlives obligations to be injected +/// into the inference context with this body-id. +/// - `ty`, the type that we are supposed to assume is WF. +#[instrument(level = "debug", skip(infcx, param_env, body_id), ret)] +fn implied_outlives_bounds<'a, 'tcx>( + infcx: &'a InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + body_id: LocalDefId, + ty: Ty<'tcx>, + compat: bool, +) -> Vec> { + let ty = infcx.resolve_vars_if_possible(ty); + let ty = OpportunisticRegionResolver::new(infcx).fold_ty(ty); - // We do not expect existential variables in implied bounds. - // We may however encounter unconstrained lifetime variables - // in very rare cases. - // - // See `ui/implied-bounds/implied-bounds-unconstrained-2.rs` for - // an example. - assert!(!ty.has_non_region_infer()); + // We do not expect existential variables in implied bounds. + // We may however encounter unconstrained lifetime variables + // in very rare cases. + // + // See `ui/implied-bounds/implied-bounds-unconstrained-2.rs` for + // an example. + assert!(!ty.has_non_region_infer()); - let mut canonical_var_values = OriginalQueryValues::default(); - let canonical_ty = - self.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values); - let Ok(canonical_result) = self.tcx.implied_outlives_bounds(canonical_ty) else { - return vec![]; - }; + let mut canonical_var_values = OriginalQueryValues::default(); + let canonical_ty = + infcx.canonicalize_query_keep_static(param_env.and(ty), &mut canonical_var_values); + let implied_bounds_result = if compat { + infcx.tcx.implied_outlives_bounds_compat(canonical_ty) + } else { + infcx.tcx.implied_outlives_bounds(canonical_ty) + }; + let Ok(canonical_result) = implied_bounds_result else { + return vec![]; + }; - let mut constraints = QueryRegionConstraints::default(); - let Ok(InferOk { value: mut bounds, obligations }) = self - .instantiate_nll_query_response_and_region_obligations( - &ObligationCause::dummy(), - param_env, - &canonical_var_values, - canonical_result, - &mut constraints, - ) - else { - return vec![]; - }; - assert_eq!(&obligations, &[]); + let mut constraints = QueryRegionConstraints::default(); + let Ok(InferOk { value: mut bounds, obligations }) = infcx + .instantiate_nll_query_response_and_region_obligations( + &ObligationCause::dummy(), + param_env, + &canonical_var_values, + canonical_result, + &mut constraints, + ) + else { + return vec![]; + }; + assert_eq!(&obligations, &[]); + + // Because of #109628, we may have unexpected placeholders. Ignore them! + // FIXME(#109628): panic in this case once the issue is fixed. + bounds.retain(|bound| !bound.has_placeholders()); - // Because of #109628, we may have unexpected placeholders. Ignore them! - // FIXME(#109628): panic in this case once the issue is fixed. - bounds.retain(|bound| !bound.has_placeholders()); + if !constraints.is_empty() { + let span = infcx.tcx.def_span(body_id); - if !constraints.is_empty() { - let span = self.tcx.def_span(body_id); + debug!(?constraints); + if !constraints.member_constraints.is_empty() { + span_bug!(span, "{:#?}", constraints.member_constraints); + } - debug!(?constraints); - if !constraints.member_constraints.is_empty() { - span_bug!(span, "{:#?}", constraints.member_constraints); - } + // Instantiation may have produced new inference variables and constraints on those + // variables. Process these constraints. + let ocx = ObligationCtxt::new(infcx); + let cause = ObligationCause::misc(span, body_id); + for &constraint in &constraints.outlives { + ocx.register_obligation(infcx.query_outlives_constraint_to_obligation( + constraint, + cause.clone(), + param_env, + )); + } - // Instantiation may have produced new inference variables and constraints on those - // variables. Process these constraints. - let ocx = ObligationCtxt::new(self); - let cause = ObligationCause::misc(span, body_id); - for &constraint in &constraints.outlives { - ocx.register_obligation(self.query_outlives_constraint_to_obligation( - constraint, - cause.clone(), - param_env, - )); - } + let errors = ocx.select_all_or_error(); + if !errors.is_empty() { + infcx.tcx.sess.span_delayed_bug( + span, + "implied_outlives_bounds_compat failed to solve obligations from instantiation", + ); + } + }; - let errors = ocx.select_all_or_error(); - if !errors.is_empty() { - self.tcx.sess.span_delayed_bug( - span, - "implied_outlives_bounds failed to solve obligations from instantiation", - ); - } - }; + bounds +} - bounds +impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> { + fn implied_bounds_tys_compat( + &'a self, + param_env: ParamEnv<'tcx>, + body_id: LocalDefId, + tys: &'a FxIndexSet>, + ) -> BoundsCompat<'a, 'tcx> { + tys.iter().flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, *ty, true)) } fn implied_bounds_tys( &'a self, param_env: ParamEnv<'tcx>, body_id: LocalDefId, - tys: FxIndexSet>, + tys: &'a FxIndexSet>, ) -> Bounds<'a, 'tcx> { - tys.into_iter().flat_map(move |ty| self.implied_outlives_bounds(param_env, body_id, ty)) + tys.iter().flat_map(move |ty| implied_outlives_bounds(self, param_env, body_id, *ty, false)) } } diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs index 56844f39478fb..1312aca2835aa 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs @@ -5,10 +5,11 @@ use crate::traits::ObligationCtxt; use rustc_infer::infer::canonical::Canonical; use rustc_infer::infer::outlives::components::{push_outlives_components, Component}; +use rustc_infer::infer::resolve::OpportunisticRegionResolver; use rustc_infer::traits::query::OutlivesBound; use rustc_middle::infer::canonical::CanonicalQueryResponse; use rustc_middle::traits::ObligationCause; -use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFolder, TypeVisitableExt}; use rustc_span::def_id::CRATE_DEF_ID; use rustc_span::DUMMY_SP; use smallvec::{smallvec, SmallVec}; @@ -47,21 +48,103 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> { param_env.and(ty) }); - tcx.implied_outlives_bounds(canonicalized) + tcx.implied_outlives_bounds_compat(canonicalized) } fn perform_locally_in_new_solver( ocx: &ObligationCtxt<'_, 'tcx>, key: ParamEnvAnd<'tcx, Self>, ) -> Result { - compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty) + compute_implied_outlives_bounds_compat_inner(ocx, key.param_env, key.value.ty) } } +/// For the sake of completeness, we should be careful when dealing with inference artifacts: +/// - `ty` must be fully resolved. +/// - `normalize_op` must return a fully resolved type. pub fn compute_implied_outlives_bounds_inner<'tcx>( ocx: &ObligationCtxt<'_, 'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>, +) -> Result>, NoSolution> { + let normalize_op = |ty| { + let ty = ocx.normalize(&ObligationCause::dummy(), param_env, ty); + if !ocx.select_all_or_error().is_empty() { + return Err(NoSolution); + } + let ty = ocx.infcx.resolve_vars_if_possible(ty); + let ty = OpportunisticRegionResolver::new(&ocx.infcx).fold_ty(ty); + Ok(ty) + }; + + // Sometimes when we ask what it takes for T: WF, we get back that + // U: WF is required; in that case, we push U onto this stack and + // process it next. Because the resulting predicates aren't always + // guaranteed to be a subset of the original type, so we need to store the + // WF args we've computed in a set. + let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default(); + let mut wf_args = vec![ty.into(), normalize_op(ty)?.into()]; + + let mut outlives_bounds: Vec> = vec![]; + + while let Some(arg) = wf_args.pop() { + if !checked_wf_args.insert(arg) { + continue; + } + + // From the full set of obligations, just filter down to the region relationships. + for obligation in + wf::unnormalized_obligations(ocx.infcx, param_env, arg).into_iter().flatten() + { + assert!(!obligation.has_escaping_bound_vars()); + let Some(pred) = obligation.predicate.kind().no_bound_vars() else { + continue; + }; + match pred { + ty::PredicateKind::Clause(ty::ClauseKind::Trait(..)) + // FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound + // if we ever support that + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..)) + | ty::PredicateKind::Subtype(..) + | ty::PredicateKind::Coerce(..) + | ty::PredicateKind::Clause(ty::ClauseKind::Projection(..)) + | ty::PredicateKind::ObjectSafe(..) + | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) + | ty::PredicateKind::ConstEquate(..) + | ty::PredicateKind::Ambiguous + | ty::PredicateKind::AliasRelate(..) + => {} + + // We need to search through *all* WellFormed predicates + ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => { + wf_args.push(arg); + } + + // We need to register region relationships + ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives( + ty::OutlivesPredicate(r_a, r_b), + )) => outlives_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a)), + + ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( + ty_a, + r_b, + ))) => { + let ty_a = normalize_op(ty_a)?; + let mut components = smallvec![]; + push_outlives_components(ocx.infcx.tcx, ty_a, &mut components); + outlives_bounds.extend(implied_bounds_from_components(r_b, components)) + } + } + } + } + + Ok(outlives_bounds) +} + +pub fn compute_implied_outlives_bounds_compat_inner<'tcx>( + ocx: &ObligationCtxt<'_, 'tcx>, + param_env: ty::ParamEnv<'tcx>, + ty: Ty<'tcx>, ) -> Result>, NoSolution> { let tcx = ocx.infcx.tcx; @@ -196,7 +279,7 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( /// this down to determine what relationships would have to hold for /// `T: 'a` to hold. We get to assume that the caller has validated /// those relationships. -fn implied_bounds_from_components<'tcx>( +pub fn implied_bounds_from_components<'tcx>( sub_region: ty::Region<'tcx>, sup_components: SmallVec<[Component<'tcx>; 4]>, ) -> Vec> { diff --git a/compiler/rustc_traits/src/implied_outlives_bounds.rs b/compiler/rustc_traits/src/implied_outlives_bounds.rs index 959838ab348fd..41f12c2658202 100644 --- a/compiler/rustc_traits/src/implied_outlives_bounds.rs +++ b/compiler/rustc_traits/src/implied_outlives_bounds.rs @@ -8,13 +8,29 @@ use rustc_infer::traits::query::OutlivesBound; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; use rustc_trait_selection::infer::InferCtxtBuilderExt; -use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::compute_implied_outlives_bounds_inner; +use rustc_trait_selection::traits::query::type_op::implied_outlives_bounds::{ + compute_implied_outlives_bounds_compat_inner, compute_implied_outlives_bounds_inner, +}; use rustc_trait_selection::traits::query::{CanonicalTyGoal, NoSolution}; pub(crate) fn provide(p: &mut Providers) { + *p = Providers { implied_outlives_bounds_compat, ..*p }; *p = Providers { implied_outlives_bounds, ..*p }; } +fn implied_outlives_bounds_compat<'tcx>( + tcx: TyCtxt<'tcx>, + goal: CanonicalTyGoal<'tcx>, +) -> Result< + &'tcx Canonical<'tcx, canonical::QueryResponse<'tcx, Vec>>>, + NoSolution, +> { + tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| { + let (param_env, ty) = key.into_parts(); + compute_implied_outlives_bounds_compat_inner(ocx, param_env, ty) + }) +} + fn implied_outlives_bounds<'tcx>( tcx: TyCtxt<'tcx>, goal: CanonicalTyGoal<'tcx>, @@ -24,6 +40,6 @@ fn implied_outlives_bounds<'tcx>( > { tcx.infer_ctxt().enter_canonical_trait_query(&goal, |ocx, key| { let (param_env, ty) = key.into_parts(); - compute_implied_outlives_bounds_inner(ocx, param_env, ty) + compute_implied_outlives_bounds_inner(&ocx, param_env, ty) }) } diff --git a/tests/ui/implied-bounds/auxiliary/bevy_ecs.rs b/tests/ui/implied-bounds/auxiliary/bevy_ecs.rs new file mode 100644 index 0000000000000..809cfa3bb6b5a --- /dev/null +++ b/tests/ui/implied-bounds/auxiliary/bevy_ecs.rs @@ -0,0 +1,16 @@ +pub mod system { + pub trait WorldQuery {} + impl WorldQuery for &u8 {} + + pub struct Query(Q); + + pub trait SystemParam { + type State; + } + impl SystemParam for Query { + type State = (); + // `Q: 'static` is required because we need the TypeId of Q ... + } + + pub struct ParamSet(T) where T::State: Sized; +} diff --git a/tests/ui/implied-bounds/bevy_world_query.rs b/tests/ui/implied-bounds/bevy_world_query.rs new file mode 100644 index 0000000000000..0aa73341f894a --- /dev/null +++ b/tests/ui/implied-bounds/bevy_world_query.rs @@ -0,0 +1,13 @@ +// aux-crate:bevy_ecs=bevy_ecs.rs +// check-pass + +// We currently special case bevy from emitting the `IMPLIED_BOUNDS_FROM_TRAIT_IMPL` lint. +// Otherwise, we would expect this to hit the lint. + +extern crate bevy_ecs; + +use bevy_ecs::system::*; + +fn handler<'a>(_: ParamSet>) {} + +fn main() {} diff --git a/tests/ui/implied-bounds/from-trait-impl.rs b/tests/ui/implied-bounds/from-trait-impl.rs new file mode 100644 index 0000000000000..f18bd5099b1c6 --- /dev/null +++ b/tests/ui/implied-bounds/from-trait-impl.rs @@ -0,0 +1,39 @@ +trait Trait { + type Assoc; +} + +impl Trait for (X,) { + type Assoc = (); +} + +struct Foo(T) +where + T::Assoc: Clone; // any predicate using `T::Assoc` works here + +fn func1(foo: Foo<(&str,)>) { + //~^ WARN function is missing necessary lifetime bounds + //~| WARN this was previously accepted + let _: &'static str = foo.0.0; +} + +#[deny(implied_bounds_from_trait_impl)] +fn func2(foo: Foo<(&str,)>) { + //~^ ERROR function is missing necessary lifetime bounds + //~| WARN this was previously accepted + //~| ERROR `&str` does not fulfill the required lifetime + let _: &'static str = foo.0.0; +} + +trait TestTrait {} + +impl TestTrait for [Foo<(X,)>; 1] {} +//~^ WARN implementation is missing necessary lifetime bounds +//~| WARN this was previously accepted + +#[deny(implied_bounds_from_trait_impl)] +impl TestTrait for [Foo<(X,)>; 2] {} +//~^ ERROR implementation is missing necessary lifetime bounds +//~| WARN this was previously accepted +//~| ERROR `X` may not live long enough + +fn main() {} diff --git a/tests/ui/implied-bounds/from-trait-impl.stderr b/tests/ui/implied-bounds/from-trait-impl.stderr new file mode 100644 index 0000000000000..a8543aba7dfa9 --- /dev/null +++ b/tests/ui/implied-bounds/from-trait-impl.stderr @@ -0,0 +1,77 @@ +warning: function is missing necessary lifetime bounds + --> $DIR/from-trait-impl.rs:13:1 + | +LL | fn func1(foo: Foo<(&str,)>) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #109628 + = note: to get more detailed errors, use `#[deny(implied_bounds_from_trait_impl)]` + = note: `#[warn(implied_bounds_from_trait_impl)]` on by default + +error: function is missing necessary lifetime bounds + --> $DIR/from-trait-impl.rs:20:1 + | +LL | fn func2(foo: Foo<(&str,)>) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #109628 + = note: more concrete lifetime errors are emitted below +note: the lint level is defined here + --> $DIR/from-trait-impl.rs:19:8 + | +LL | #[deny(implied_bounds_from_trait_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0477]: the type `&str` does not fulfill the required lifetime + --> $DIR/from-trait-impl.rs:20:15 + | +LL | fn func2(foo: Foo<(&str,)>) { + | ^^^^^^^^^^^^ + | + = note: type must satisfy the static lifetime + +warning: implementation is missing necessary lifetime bounds + --> $DIR/from-trait-impl.rs:29:1 + | +LL | impl TestTrait for [Foo<(X,)>; 1] {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #109628 + = note: to get more detailed errors, use `#[deny(implied_bounds_from_trait_impl)]` + +error: implementation is missing necessary lifetime bounds + --> $DIR/from-trait-impl.rs:34:1 + | +LL | impl TestTrait for [Foo<(X,)>; 2] {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #109628 + = note: more concrete lifetime errors are emitted below +note: the lint level is defined here + --> $DIR/from-trait-impl.rs:33:8 + | +LL | #[deny(implied_bounds_from_trait_impl)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0310]: the parameter type `X` may not live long enough + --> $DIR/from-trait-impl.rs:34:23 + | +LL | impl TestTrait for [Foo<(X,)>; 2] {} + | ^^^^^^^^^^^^^^ + | | + | the parameter type `X` must be valid for the static lifetime... + | ...so that the type `X` will meet its required lifetime bounds + | +help: consider adding an explicit lifetime bound + | +LL | impl TestTrait for [Foo<(X,)>; 2] {} + | +++++++++ + +error: aborting due to 4 previous errors; 2 warnings emitted + +Some errors have detailed explanations: E0310, E0477. +For more information about an error, try `rustc --explain E0310`. diff --git a/tests/ui/implied-bounds/gluon_salsa.rs b/tests/ui/implied-bounds/gluon_salsa.rs new file mode 100644 index 0000000000000..a9353fbfb159f --- /dev/null +++ b/tests/ui/implied-bounds/gluon_salsa.rs @@ -0,0 +1,29 @@ +// check-pass + +pub trait QueryBase { + type Db; +} + +pub trait AsyncQueryFunction<'f>: // 'f is important + QueryBase>::SendDb> // bound is important +{ + type SendDb; +} + +pub struct QueryTable<'me, Q, DB> { + _q: Option, + _db: Option, + _marker: Option<&'me ()>, +} + +impl<'me, Q> QueryTable<'me, Q, ::Db> // projection is important +// ^^^ removing 'me (and in QueryTable) gives a different error +where + Q: for<'f> AsyncQueryFunction<'f>, +{ + pub fn get_async<'a>(&'a mut self) { + panic!(); + } +} + +fn main() {} diff --git a/tests/ui/implied-bounds/normalization-nested.lifetime.stderr b/tests/ui/implied-bounds/normalization-nested.lifetime.stderr index abffee57a0f09..dc69ebc9bca05 100644 --- a/tests/ui/implied-bounds/normalization-nested.lifetime.stderr +++ b/tests/ui/implied-bounds/normalization-nested.lifetime.stderr @@ -1,18 +1,10 @@ -error[E0759]: `fn` parameter has lifetime `'x` but it needs to satisfy a `'static` lifetime requirement - --> $DIR/normalization-nested.rs:35:20 +error: lifetime may not live long enough + --> $DIR/normalization-nested.rs:37:5 | LL | pub fn test<'x>(_: Map>, s: &'x str) -> &'static str { - | ^^^^^^^^^^^^^^^^ - | | - | this data with lifetime `'x`... - | ...is used and required to live as long as `'static` here - | -note: `'static` lifetime requirement introduced by this bound - --> $DIR/normalization-nested.rs:33:14 - | -LL | I::Item: 'static; - | ^^^^^^^ + | -- lifetime `'x` defined here +LL | s + | ^ returning this value requires that `'x` must outlive `'static` error: aborting due to 1 previous error -For more information about this error, try `rustc --explain E0759`. diff --git a/tests/ui/implied-bounds/sod_service_chain.rs b/tests/ui/implied-bounds/sod_service_chain.rs new file mode 100644 index 0000000000000..33b0835880efc --- /dev/null +++ b/tests/ui/implied-bounds/sod_service_chain.rs @@ -0,0 +1,34 @@ +pub trait Debug {} + +pub trait Service { + type Input; + type Output; + type Error; +} + +pub struct ServiceChain { + prev: P, + service: S, +} +impl> Service for ServiceChain +where + P::Error: 'static, + S::Error: 'static, +{ + type Input = P::Input; + type Output = S::Output; + type Error = (); +} + +pub struct ServiceChainBuilder> { + chain: ServiceChain, +} +impl> ServiceChainBuilder { + pub fn next>( + self, + ) -> ServiceChainBuilder, NS> { + panic!(); + } +} + +fn main() {}