Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint against nested opaque types that don't satisfy associated type bounds #102568

Merged
merged 3 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_error_messages/locales/en-US/lint.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::*;
Expand Down Expand Up @@ -223,6 +225,7 @@ macro_rules! late_lint_mod_passes {
EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums,
InvalidAtomicOrdering: InvalidAtomicOrdering,
NamedAsmLabels: NamedAsmLabels,
OpaqueHiddenInferredBound: OpaqueHiddenInferredBound,
]
);
};
Expand Down
156 changes: 156 additions & 0 deletions compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs
Original file line number Diff line number Diff line change
@@ -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<Assoc = impl Sized> {
/// 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<Assoc = impl Send>`, for all of the bounds on `Assoc`,
// e.g. `type Assoc: OtherTrait`, replace `<impl Trait as Trait>::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<Span>,
suggestion: String,
}
4 changes: 3 additions & 1 deletion compiler/rustc_middle/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type2-tait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Assoc = Sendable> {
//~^ WARN opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
|| 42
}

Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type2-tait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
warning: opaque type `impl Trait<Assoc = Sendable>` 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<Assoc = Sendable> {
| ^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | type Sendable = impl Send + Duh;
| +++++

warning: 1 warning emitted

1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ impl<R: Duh, F: FnMut() -> 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<Assoc = impl Send> {
//~^ WARN opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
|| 42
}

Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type2.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
warning: opaque type `impl Trait<Assoc = impl Send>` 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<Assoc = impl Send> {
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | fn foo() -> impl Trait<Assoc = impl Send + Duh> {
| +++++

warning: 1 warning emitted

1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl<F: Duh> Trait for F {
type Sendable = impl Send;

fn foo() -> impl Trait<Assoc = Sendable> {
//~^ WARN opaque type `impl Trait<Assoc = Sendable>` does not satisfy its associated type bounds
42
}

Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
warning: opaque type `impl Trait<Assoc = Sendable>` 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<Assoc = Sendable> {
| ^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | type Sendable = impl Send + Duh;
| +++++

warning: 1 warning emitted

1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ impl<F: Duh> Trait for F {

type Sendable = impl Send;
type Traitable = impl Trait<Assoc = Sendable>;
//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds

fn foo() -> Traitable {
42
Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait2.stderr
Original file line number Diff line number Diff line change
@@ -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<Assoc = Sendable>;
| ^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | type Sendable = impl Send + Duh;
| +++++

warning: 1 warning emitted

1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl<F: Duh> Trait for F {
}

type Traitable = impl Trait<Assoc = impl Send>;
//~^ WARN opaque type `Traitable` does not satisfy its associated type bounds

fn foo() -> Traitable {
42
Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type3-tait3.stderr
Original file line number Diff line number Diff line change
@@ -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<Assoc = impl Send>;
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | type Traitable = impl Trait<Assoc = impl Send + Duh>;
| +++++

warning: 1 warning emitted

1 change: 1 addition & 0 deletions src/test/ui/impl-trait/nested-return-type3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ impl<F: Duh> Trait for F {
}

fn foo() -> impl Trait<Assoc = impl Send> {
//~^ WARN opaque type `impl Trait<Assoc = impl Send>` does not satisfy its associated type bounds
42
}

Expand Down
17 changes: 17 additions & 0 deletions src/test/ui/impl-trait/nested-return-type3.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
warning: opaque type `impl Trait<Assoc = impl Send>` 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<Assoc = impl Send> {
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(opaque_hidden_inferred_bound)]` on by default
help: add this bound
|
LL | fn foo() -> impl Trait<Assoc = impl Send + Duh> {
| +++++

warning: 1 warning emitted