diff options
| -rw-r--r-- | compiler/rustc_error_messages/locales/en-US/lint.ftl | 2 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/lib.rs | 6 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs | 125 | ||||
| -rw-r--r-- | compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs | 143 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type2-tait.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type2-tait.stderr | 17 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type2.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type2.stderr | 17 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait.stderr | 17 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait2.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait2.stderr | 17 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait3.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3-tait3.stderr | 17 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3.rs | 1 | ||||
| -rw-r--r-- | src/test/ui/impl-trait/nested-return-type3.stderr | 17 |
16 files changed, 237 insertions, 147 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/lint.ftl b/compiler/rustc_error_messages/locales/en-US/lint.ftl index 31c9a845e7e..0fd9b0ead16 100644 --- a/compiler/rustc_error_messages/locales/en-US/lint.ftl +++ b/compiler/rustc_error_messages/locales/en-US/lint.ftl @@ -434,6 +434,6 @@ 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_rpit_hidden_inferred_bound = return-position `{$ty}` does not satisfy its associated type bounds +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 fd7ea5a4bb5..9148c42195f 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -62,10 +62,10 @@ 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; -mod rpit_hidden_inferred_bound; mod traits; mod types; mod unused; @@ -94,9 +94,9 @@ 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 rpit_hidden_inferred_bound::*; use traits::*; use types::*; use unused::*; @@ -225,7 +225,7 @@ macro_rules! late_lint_mod_passes { EnumIntrinsicsNonEnums: EnumIntrinsicsNonEnums, InvalidAtomicOrdering: InvalidAtomicOrdering, NamedAsmLabels: NamedAsmLabels, - RpitHiddenInferredBound: RpitHiddenInferredBound, + 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 00000000000..7f18bf0018f --- /dev/null +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -0,0 +1,125 @@ +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. This functionality was + /// removed in #97346, but then rolled back in #99860 because it was made + /// into a hard error too quickly. + /// + /// 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. + 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, +} diff --git a/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs b/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs deleted file mode 100644 index fd9872c80a9..00000000000 --- a/compiler/rustc_lint/src/rpit_hidden_inferred_bound.rs +++ /dev/null @@ -1,143 +0,0 @@ -use hir::def_id::LocalDefId; -use rustc_hir as hir; -use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; -use rustc_macros::LintDiagnostic; -use rustc_middle::ty::{ - self, fold::BottomUpFolder, Ty, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitor, -}; -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 `rpit_hidden_inferred_bound` lint detects cases in which nested RPITs - /// in associated type bounds are not written generally enough to satisfy the - /// bounds of the associated type. This functionality was removed in #97346, - /// but then rolled back in #99860 because it was made into a hard error too - /// quickly. - /// - /// 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. - pub RPIT_HIDDEN_INFERRED_BOUND, - Warn, - "detects the use of nested RPITs in associated type bounds that are not general enough" -} - -declare_lint_pass!(RpitHiddenInferredBound => [RPIT_HIDDEN_INFERRED_BOUND]); - -impl<'tcx> LateLintPass<'tcx> for RpitHiddenInferredBound { - fn check_fn( - &mut self, - cx: &LateContext<'tcx>, - kind: hir::intravisit::FnKind<'tcx>, - _: &'tcx hir::FnDecl<'tcx>, - _: &'tcx hir::Body<'tcx>, - _: rustc_span::Span, - id: hir::HirId, - ) { - if matches!(kind, hir::intravisit::FnKind::Closure) { - return; - } - - let fn_def_id = cx.tcx.hir().local_def_id(id); - let sig: ty::FnSig<'tcx> = - cx.tcx.liberate_late_bound_regions(fn_def_id.to_def_id(), cx.tcx.fn_sig(fn_def_id)); - cx.tcx.infer_ctxt().enter(|ref infcx| { - sig.output().visit_with(&mut VisitOpaqueBounds { infcx, cx, fn_def_id }); - }); - } -} - -struct VisitOpaqueBounds<'a, 'cx, 'tcx> { - infcx: &'a InferCtxt<'a, 'tcx>, - cx: &'cx LateContext<'tcx>, - fn_def_id: LocalDefId, -} - -impl<'tcx> TypeVisitor<'tcx> for VisitOpaqueBounds<'_, '_, 'tcx> { - fn visit_ty(&mut self, ty: Ty<'tcx>) -> std::ops::ControlFlow<Self::BreakTy> { - if let ty::Opaque(def_id, substs) = *ty.kind() - && let Some(hir::Node::Item(item)) = self.cx.tcx.hir().get_if_local(def_id) - && let hir::ItemKind::OpaqueTy(opaque) = &item.kind - && let hir::OpaqueTyOrigin::FnReturn(origin_def_id) = opaque.origin - && origin_def_id == self.fn_def_id - { - for pred_and_span in self.cx.tcx.bound_explicit_item_bounds(def_id).transpose_iter() { - let pred_span = pred_and_span.0.1; - let predicate = self.cx.tcx.liberate_late_bound_regions( - def_id, - pred_and_span.map_bound(|(pred, _)| *pred).subst(self.cx.tcx, substs).kind(), - ); - let ty::PredicateKind::Projection(proj) = predicate else { - continue; - }; - let Some(proj_term) = proj.term.ty() else { continue }; - - let proj_ty = self - .cx - .tcx - .mk_projection(proj.projection_ty.item_def_id, proj.projection_ty.substs); - let proj_replacer = &mut BottomUpFolder { - tcx: self.cx.tcx, - ty_op: |ty| if ty == proj_ty { proj_term } else { ty }, - lt_op: |lt| lt, - ct_op: |ct| ct, - }; - for assoc_pred_and_span in self - .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(self.cx.tcx, &proj.projection_ty.substs) - .fold_with(proj_replacer); - if !self.infcx.predicate_must_hold_modulo_regions(&traits::Obligation::new( - traits::ObligationCause::dummy(), - self.cx.param_env, - assoc_pred, - )) { - 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(self.cx.tcx.def_span(def_id).shrink_to_hi()), - ), - _ => (String::new(), None), - }; - self.cx.emit_spanned_lint( - RPIT_HIDDEN_INFERRED_BOUND, - pred_span, - RpitHiddenInferredBoundLint { - ty, - proj_ty: proj_term, - assoc_pred_span, - suggestion, - suggest_span, - }, - ); - } - } - } - } - - ty.super_visit_with(self) - } -} - -#[derive(LintDiagnostic)] -#[diag(lint::rpit_hidden_inferred_bound)] -struct RpitHiddenInferredBoundLint<'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, -} 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 42613d5ccd9..089018a1cdf 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<Assoc = Sendable> { + //~^ WARN opaque type `impl Trait<Assoc = Sendable>` 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 00000000000..a8eb69cfcb7 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type2-tait.stderr @@ -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 + diff --git a/src/test/ui/impl-trait/nested-return-type2.rs b/src/test/ui/impl-trait/nested-return-type2.rs index 39928d543e1..cc1f1f4ec44 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: 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 } 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 00000000000..3aed05ca132 --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type2.stderr @@ -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 + 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 3936f4dbbb4..3a97e35b4c4 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<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 } 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 00000000000..5f58c8dca4a --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3-tait.stderr @@ -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 + 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 56778ed90dc..5b6f78a9896 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<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 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 00000000000..c07f6ead750 --- /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<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 + 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 04c6c92b1a3..394d8f58110 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<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 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 00000000000..d98ad89222f --- /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<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 + diff --git a/src/test/ui/impl-trait/nested-return-type3.rs b/src/test/ui/impl-trait/nested-return-type3.rs index 74b4dae22eb..5a764fc4c28 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<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 } 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 00000000000..632de71aa4c --- /dev/null +++ b/src/test/ui/impl-trait/nested-return-type3.stderr @@ -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 + |
