diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl index 80b0b1a8904a1..0fd9b0ead167c 100644 --- a/compiler/rustc_error_messages/locales/en-US/lint.ftl +++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl @@ -433,3 +433,7 @@ lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}` lint_check_name_warning = {$msg} lint_check_name_deprecated = lint name `{$lint_name}` is deprecated and does not have an effect anymore. Use: {$new_name} + +lint_opaque_hidden_inferred_bound = opaque type `{$ty}` does not satisfy its associated type bounds + .specifically = this associated type bound is unsatisfied for `{$proj_ty}` + .suggestion = add this bound diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 4408f68dd63f3..9148c42195fbe 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -62,6 +62,7 @@ mod non_ascii_idents; mod non_fmt_panic; mod nonstandard_style; mod noop_method_call; +mod opaque_hidden_inferred_bound; mod pass_by_value; mod passes; mod redundant_semicolon; @@ -93,6 +94,7 @@ use non_ascii_idents::*; use non_fmt_panic::NonPanicFmt; use nonstandard_style::*; use noop_method_call::*; +use opaque_hidden_inferred_bound::*; use pass_by_value::*; use redundant_semicolon::*; use traits::*; @@ -223,6 +225,7 @@ macro_rules! late_lint_mod_passes { EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums, InvalidAtomicOrdering: InvalidAtomicOrdering, NamedAsmLabels: NamedAsmLabels, + OpaqueHiddenInferredBound: OpaqueHiddenInferredBound, ] ); }; diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs new file mode 100644 index 0000000000000..d8ce20db37ce9 --- /dev/null +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -0,0 +1,156 @@ +use rustc_hir as hir; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_macros::LintDiagnostic; +use rustc_middle::ty::{self, fold::BottomUpFolder, Ty, TypeFoldable}; +use rustc_span::Span; +use rustc_trait_selection::traits; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; + +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `opaque_hidden_inferred_bound` lint detects cases in which nested + /// `impl Trait` in associated type bounds are not written generally enough + /// to satisfy the bounds of the associated type. + /// + /// ### Explanation + /// + /// This functionality was removed in #97346, but then rolled back in #99860 + /// because it caused regressions. + /// + /// We plan on reintroducing this as a hard error, but in the mean time, + /// this lint serves to warn and suggest fixes for any use-cases which rely + /// on this behavior. + /// + /// ### Example + /// + /// ``` + /// trait Trait { + /// type Assoc: Send; + /// } + /// + /// struct Struct; + /// + /// impl Trait for Struct { + /// type Assoc = i32; + /// } + /// + /// fn test() -> impl Trait { + /// Struct + /// } + /// ``` + /// + /// {{produces}} + /// + /// In this example, `test` declares that the associated type `Assoc` for + /// `impl Trait` is `impl Sized`, which does not satisfy the `Send` bound + /// on the associated type. + /// + /// Although the hidden type, `i32` does satisfy this bound, we do not + /// consider the return type to be well-formed with this lint. It can be + /// fixed by changing `impl Sized` into `impl Sized + Send`. + pub OPAQUE_HIDDEN_INFERRED_BOUND, + Warn, + "detects the use of nested `impl Trait` types in associated type bounds that are not general enough" +} + +declare_lint_pass!(OpaqueHiddenInferredBound => [OPAQUE_HIDDEN_INFERRED_BOUND]); + +impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) { + let hir::ItemKind::OpaqueTy(_) = &item.kind else { return; }; + let def_id = item.def_id.def_id.to_def_id(); + cx.tcx.infer_ctxt().enter(|ref infcx| { + // For every projection predicate in the opaque type's explicit bounds, + // check that the type that we're assigning actually satisfies the bounds + // of the associated type. + for &(pred, pred_span) in cx.tcx.explicit_item_bounds(def_id) { + // Liberate bound regions in the predicate since we + // don't actually care about lifetimes in this check. + let predicate = cx.tcx.liberate_late_bound_regions( + def_id, + pred.kind(), + ); + let ty::PredicateKind::Projection(proj) = predicate else { + continue; + }; + // Only check types, since those are the only things that may + // have opaques in them anyways. + let Some(proj_term) = proj.term.ty() else { continue }; + + let proj_ty = + cx + .tcx + .mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs); + // For every instance of the projection type in the bounds, + // replace them with the term we're assigning to the associated + // type in our opaque type. + let proj_replacer = &mut BottomUpFolder { + tcx: cx.tcx, + ty_op: |ty| if ty == proj_ty { proj_term } else { ty }, + lt_op: |lt| lt, + ct_op: |ct| ct, + }; + // For example, in `impl Trait`, for all of the bounds on `Assoc`, + // e.g. `type Assoc: OtherTrait`, replace `::Assoc: OtherTrait` + // with `impl Send: OtherTrait`. + for assoc_pred_and_span in cx + .tcx + .bound_explicit_item_bounds(proj.projection_ty.item_def_id) + .transpose_iter() + { + let assoc_pred_span = assoc_pred_and_span.0.1; + let assoc_pred = assoc_pred_and_span + .map_bound(|(pred, _)| *pred) + .subst(cx.tcx, &proj.projection_ty.substs) + .fold_with(proj_replacer); + let Ok(assoc_pred) = traits::fully_normalize(infcx, traits::ObligationCause::dummy(), cx.param_env, assoc_pred) else { + continue; + }; + // If that predicate doesn't hold modulo regions (but passed during type-check), + // then we must've taken advantage of the hack in `project_and_unify_types` where + // we replace opaques with inference vars. Emit a warning! + if !infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new( + traits::ObligationCause::dummy(), + cx.param_env, + assoc_pred, + )) { + // If it's a trait bound and an opaque that doesn't satisfy it, + // then we can emit a suggestion to add the bound. + let (suggestion, suggest_span) = + match (proj_term.kind(), assoc_pred.kind().skip_binder()) { + (ty::Opaque(def_id, _), ty::PredicateKind::Trait(trait_pred)) => ( + format!(" + {}", trait_pred.print_modifiers_and_trait_path()), + Some(cx.tcx.def_span(def_id).shrink_to_hi()), + ), + _ => (String::new(), None), + }; + cx.emit_spanned_lint( + OPAQUE_HIDDEN_INFERRED_BOUND, + pred_span, + OpaqueHiddenInferredBoundLint { + ty: cx.tcx.mk_opaque(def_id, ty::InternalSubsts::identity_for_item(cx.tcx, def_id)), + proj_ty: proj_term, + assoc_pred_span, + suggestion, + suggest_span, + }, + ); + } + } + } + }); + } +} + +#[derive(LintDiagnostic)] +#[diag(lint::opaque_hidden_inferred_bound)] +struct OpaqueHiddenInferredBoundLint<'tcx> { + ty: Ty<'tcx>, + proj_ty: Ty<'tcx>, + #[label(lint::specifically)] + assoc_pred_span: Span, + #[suggestion_verbose(applicability = "machine-applicable", code = "{suggestion}")] + suggest_span: Option, + suggestion: String, +} diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs index f3e4f1faeb05c..1fb27fd87e87e 100644 --- a/compiler/rustc_middle/src/lint.rs +++ b/compiler/rustc_middle/src/lint.rs @@ -444,7 +444,9 @@ pub fn in_external_macro(sess: &Session, span: Span) -> bool { match expn_data.kind { ExpnKind::Inlined | ExpnKind::Root - | ExpnKind::Desugaring(DesugaringKind::ForLoop | DesugaringKind::WhileLoop) => false, + | ExpnKind::Desugaring( + DesugaringKind::ForLoop | DesugaringKind::WhileLoop | DesugaringKind::OpaqueTy, + ) => false, ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => true, // well, it's "external" ExpnKind::Macro(MacroKind::Bang, _) => { // Dummy span for the `def_site` means it's an external macro. diff --git a/src/test/ui/impl-trait/nested-return-type2-tait.rs b/src/test/ui/impl-trait/nested-return-type2-tait.rs index 42613d5ccd9a4..089018a1cdf01 100644 --- a/src/test/ui/impl-trait/nested-return-type2-tait.rs +++ b/src/test/ui/impl-trait/nested-return-type2-tait.rs @@ -26,6 +26,7 @@ type Sendable = impl Send; // var to make it uphold the `: Duh` bound on `Trait::Assoc`. The opaque // type does not implement `Duh`, but if its hidden type does. fn foo() -> impl Trait { + //~^ WARN opaque type `impl Trait` does not satisfy its associated type bounds || 42 } diff --git a/src/test/ui/impl-trait/nested-return-type2-tait.stderr b/src/test/ui/impl-trait/nested-return-type2-tait.stderr new file mode 100644 index 0000000000000..a8eb69cfcb736 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type2-tait.stderr @@ -0,0 +1,17 @@ +warning: opaque type `impl Trait` does not satisfy its associated type bounds + --> $DIR/nested-return-type2-tait.rs:28:24 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `Sendable` +... +LL | fn foo() -> impl Trait { + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | type Sendable = impl Send + Duh; + | +++++ + +warning: 1 warning emitted + diff --git a/src/test/ui/impl-trait/nested-return-type2.rs b/src/test/ui/impl-trait/nested-return-type2.rs index 39928d543e15d..cc1f1f4ec44c8 100644 --- a/src/test/ui/impl-trait/nested-return-type2.rs +++ b/src/test/ui/impl-trait/nested-return-type2.rs @@ -23,6 +23,7 @@ impl R> Trait for F { // Lazy TAIT would error out, but we inserted a hack to make it work again, // keeping backwards compatibility. fn foo() -> impl Trait { + //~^ WARN opaque type `impl Trait` does not satisfy its associated type bounds || 42 } diff --git a/src/test/ui/impl-trait/nested-return-type2.stderr b/src/test/ui/impl-trait/nested-return-type2.stderr new file mode 100644 index 0000000000000..3aed05ca13298 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type2.stderr @@ -0,0 +1,17 @@ +warning: opaque type `impl Trait` does not satisfy its associated type bounds + --> $DIR/nested-return-type2.rs:25:24 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `impl Send` +... +LL | fn foo() -> impl Trait { + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | fn foo() -> impl Trait { + | +++++ + +warning: 1 warning emitted + diff --git a/src/test/ui/impl-trait/nested-return-type3-tait.rs b/src/test/ui/impl-trait/nested-return-type3-tait.rs index 3936f4dbbb426..3a97e35b4c400 100644 --- a/src/test/ui/impl-trait/nested-return-type3-tait.rs +++ b/src/test/ui/impl-trait/nested-return-type3-tait.rs @@ -17,6 +17,7 @@ impl Trait for F { type Sendable = impl Send; fn foo() -> impl Trait { + //~^ WARN opaque type `impl Trait` does not satisfy its associated type bounds 42 } diff --git a/src/test/ui/impl-trait/nested-return-type3-tait.stderr b/src/test/ui/impl-trait/nested-return-type3-tait.stderr new file mode 100644 index 0000000000000..5f58c8dca4ad4 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3-tait.stderr @@ -0,0 +1,17 @@ +warning: opaque type `impl Trait` does not satisfy its associated type bounds + --> $DIR/nested-return-type3-tait.rs:19:24 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `Sendable` +... +LL | fn foo() -> impl Trait { + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | type Sendable = impl Send + Duh; + | +++++ + +warning: 1 warning emitted + diff --git a/src/test/ui/impl-trait/nested-return-type3-tait2.rs b/src/test/ui/impl-trait/nested-return-type3-tait2.rs index 56778ed90dc7b..5b6f78a989687 100644 --- a/src/test/ui/impl-trait/nested-return-type3-tait2.rs +++ b/src/test/ui/impl-trait/nested-return-type3-tait2.rs @@ -16,6 +16,7 @@ impl Trait for F { type Sendable = impl Send; type Traitable = impl Trait; +//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds fn foo() -> Traitable { 42 diff --git a/src/test/ui/impl-trait/nested-return-type3-tait2.stderr b/src/test/ui/impl-trait/nested-return-type3-tait2.stderr new file mode 100644 index 0000000000000..c07f6ead75028 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3-tait2.stderr @@ -0,0 +1,17 @@ +warning: opaque type `Traitable` does not satisfy its associated type bounds + --> $DIR/nested-return-type3-tait2.rs:18:29 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `Sendable` +... +LL | type Traitable = impl Trait; + | ^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | type Sendable = impl Send + Duh; + | +++++ + +warning: 1 warning emitted + diff --git a/src/test/ui/impl-trait/nested-return-type3-tait3.rs b/src/test/ui/impl-trait/nested-return-type3-tait3.rs index 04c6c92b1a3c5..394d8f581102f 100644 --- a/src/test/ui/impl-trait/nested-return-type3-tait3.rs +++ b/src/test/ui/impl-trait/nested-return-type3-tait3.rs @@ -15,6 +15,7 @@ impl Trait for F { } type Traitable = impl Trait; +//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds fn foo() -> Traitable { 42 diff --git a/src/test/ui/impl-trait/nested-return-type3-tait3.stderr b/src/test/ui/impl-trait/nested-return-type3-tait3.stderr new file mode 100644 index 0000000000000..d98ad89222fa7 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3-tait3.stderr @@ -0,0 +1,17 @@ +warning: opaque type `Traitable` does not satisfy its associated type bounds + --> $DIR/nested-return-type3-tait3.rs:17:29 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `impl Send` +... +LL | type Traitable = impl Trait; + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | type Traitable = impl Trait; + | +++++ + +warning: 1 warning emitted + diff --git a/src/test/ui/impl-trait/nested-return-type3.rs b/src/test/ui/impl-trait/nested-return-type3.rs index 74b4dae22ebfd..5a764fc4c285a 100644 --- a/src/test/ui/impl-trait/nested-return-type3.rs +++ b/src/test/ui/impl-trait/nested-return-type3.rs @@ -13,6 +13,7 @@ impl Trait for F { } fn foo() -> impl Trait { + //~^ WARN opaque type `impl Trait` does not satisfy its associated type bounds 42 } diff --git a/src/test/ui/impl-trait/nested-return-type3.stderr b/src/test/ui/impl-trait/nested-return-type3.stderr new file mode 100644 index 0000000000000..632de71aa4c88 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3.stderr @@ -0,0 +1,17 @@ +warning: opaque type `impl Trait` does not satisfy its associated type bounds + --> $DIR/nested-return-type3.rs:15:24 + | +LL | type Assoc: Duh; + | --- this associated type bound is unsatisfied for `impl Send` +... +LL | fn foo() -> impl Trait { + | ^^^^^^^^^^^^^^^^^ + | + = note: `#[warn(opaque_hidden_inferred_bound)]` on by default +help: add this bound + | +LL | fn foo() -> impl Trait { + | +++++ + +warning: 1 warning emitted +