From 1ae0e969c44743909aa9b36e9a74b33b1324ec9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 10 Jan 2024 02:11:25 +0100 Subject: [PATCH] Reject escaping bound vars in the type of assoc const bindings --- compiler/rustc_hir_analysis/messages.ftl | 5 + .../rustc_hir_analysis/src/astconv/bounds.rs | 113 ++++++++++++++---- compiler/rustc_hir_analysis/src/errors.rs | 15 +++ .../assoc-const-eq-bound-var-in-ty-not-wf.rs | 25 ++++ ...soc-const-eq-bound-var-in-ty-not-wf.stderr | 25 ++++ .../assoc-const-eq-bound-var-in-ty.rs | 22 ++++ .../assoc-const-eq-esc-bound-var-in-ty.rs | 15 +++ .../assoc-const-eq-esc-bound-var-in-ty.stderr | 12 ++ 8 files changed, 212 insertions(+), 20 deletions(-) create mode 100644 tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.rs create mode 100644 tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.stderr create mode 100644 tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs create mode 100644 tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs create mode 100644 tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.stderr diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl index e38bcdcb298e1..364e39e626bf6 100644 --- a/compiler/rustc_hir_analysis/messages.ftl +++ b/compiler/rustc_hir_analysis/messages.ftl @@ -118,6 +118,11 @@ hir_analysis_enum_discriminant_overflowed = enum discriminant overflowed .label = overflowed on value after {$discr} .note = explicitly set `{$item_name} = {$wrapped_discr}` if that is desired outcome +hir_analysis_escaping_bound_var_in_ty_of_assoc_const_binding = + the type of the associated constant `{$assoc_const}` cannot capture late-bound generic parameters + .label = its type cannot capture the late-bound {$var_def_kind} `{$var_name}` + .var_defined_here_label = the late-bound {$var_def_kind} `{$var_name}` is defined here + hir_analysis_field_already_declared = field `{$field_name}` is already declared .label = field already declared diff --git a/compiler/rustc_hir_analysis/src/astconv/bounds.rs b/compiler/rustc_hir_analysis/src/astconv/bounds.rs index e5cef88e324ae..6d415bc25d19c 100644 --- a/compiler/rustc_hir_analysis/src/astconv/bounds.rs +++ b/compiler/rustc_hir_analysis/src/astconv/bounds.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_errors::{codes::*, struct_span_code_err}; use rustc_hir as hir; @@ -5,7 +7,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::ty::{self as ty, IsSuggestable, Ty, TyCtxt}; use rustc_span::symbol::Ident; -use rustc_span::{ErrorGuaranteed, Span}; +use rustc_span::{ErrorGuaranteed, Span, Symbol}; use rustc_trait_selection::traits; use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor}; use smallvec::SmallVec; @@ -533,7 +535,7 @@ impl<'tcx> dyn AstConv<'tcx> + '_ { } } -/// Detect and reject early-bound generic params in the type of associated const bindings. +/// Detect and reject early-bound & late-bound generic params in the type of associated const bindings. /// /// FIXME(const_generics): This is a temporary and semi-artifical restriction until the /// arrival of *generic const generics*[^1]. @@ -552,17 +554,23 @@ fn check_assoc_const_binding_type<'tcx>( ) -> Ty<'tcx> { // We can't perform the checks for early-bound params during name resolution unlike E0770 // because this information depends on *type* resolution. + // We can't perform these checks in `resolve_bound_vars` either for the same reason. + // Consider the trait ref `for<'a> Trait<'a, C = { &0 }>`. We need to know the fully + // resolved type of `Trait::C` in order to know if it references `'a` or not. - // FIXME(fmease): Reject escaping late-bound vars. let ty = ty.skip_binder(); - if !ty.has_param() { + if !ty.has_param() && !ty.has_escaping_bound_vars() { return ty; } - let mut collector = GenericParamCollector { params: Default::default() }; - ty.visit_with(&mut collector); + let mut collector = GenericParamAndBoundVarCollector { + tcx, + params: Default::default(), + vars: Default::default(), + depth: ty::INNERMOST, + }; + let mut guar = ty.visit_with(&mut collector).break_value(); - let mut guar = None; let ty_note = ty .make_suggestable(tcx, false) .map(|ty| crate::errors::TyOfAssocConstBindingNote { assoc_const, ty }); @@ -593,35 +601,100 @@ fn check_assoc_const_binding_type<'tcx>( ty_note, })); } + for (var_def_id, var_name) in collector.vars { + guar.get_or_insert(tcx.dcx().emit_err( + crate::errors::EscapingBoundVarInTyOfAssocConstBinding { + span: assoc_const.span, + assoc_const, + var_name, + var_def_kind: tcx.def_descr(var_def_id), + var_defined_here_label: tcx.def_ident_span(var_def_id).unwrap(), + ty_note, + }, + )); + } - let guar = guar.unwrap_or_else(|| bug!("failed to find gen params in ty")); + let guar = guar.unwrap_or_else(|| bug!("failed to find gen params or bound vars in ty")); Ty::new_error(tcx, guar) } -struct GenericParamCollector { +struct GenericParamAndBoundVarCollector<'tcx> { + tcx: TyCtxt<'tcx>, params: FxIndexSet, + vars: FxIndexSet<(DefId, Symbol)>, + depth: ty::DebruijnIndex, } -impl<'tcx> TypeVisitor> for GenericParamCollector { +impl<'tcx> TypeVisitor> for GenericParamAndBoundVarCollector<'tcx> { + type Result = ControlFlow; + + fn visit_binder>>( + &mut self, + binder: &ty::Binder<'tcx, T>, + ) -> Self::Result { + self.depth.shift_in(1); + let result = binder.super_visit_with(self); + self.depth.shift_out(1); + result + } + fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - if let ty::Param(param) = ty.kind() { - self.params.insert(param.index); - } else if ty.has_param() { - ty.super_visit_with(self) + match ty.kind() { + ty::Param(param) => { + self.params.insert(param.index); + } + ty::Bound(db, bt) if *db >= self.depth => { + self.vars.insert(match bt.kind { + ty::BoundTyKind::Param(def_id, name) => (def_id, name), + ty::BoundTyKind::Anon => { + let reported = self + .tcx + .dcx() + .delayed_bug(format!("unexpected anon bound ty: {:?}", bt.var)); + return ControlFlow::Break(reported); + } + }); + } + _ if ty.has_param() || ty.has_bound_vars() => return ty.super_visit_with(self), + _ => {} } + ControlFlow::Continue(()) } fn visit_region(&mut self, re: ty::Region<'tcx>) -> Self::Result { - if let ty::ReEarlyParam(param) = re.kind() { - self.params.insert(param.index); + match re.kind() { + ty::ReEarlyParam(param) => { + self.params.insert(param.index); + } + ty::ReBound(db, br) if db >= self.depth => { + self.vars.insert(match br.kind { + ty::BrNamed(def_id, name) => (def_id, name), + ty::BrAnon | ty::BrEnv => { + let guar = self + .tcx + .dcx() + .delayed_bug(format!("unexpected bound region kind: {:?}", br.kind)); + return ControlFlow::Break(guar); + } + }); + } + _ => {} } + ControlFlow::Continue(()) } fn visit_const(&mut self, ct: ty::Const<'tcx>) -> Self::Result { - if let ty::ConstKind::Param(param) = ct.kind() { - self.params.insert(param.index); - } else if ct.has_param() { - ct.super_visit_with(self) + match ct.kind() { + ty::ConstKind::Param(param) => { + self.params.insert(param.index); + } + ty::ConstKind::Bound(db, ty::BoundVar { .. }) if db >= self.depth => { + let guar = self.tcx.dcx().delayed_bug("unexpected escaping late-bound const var"); + return ControlFlow::Break(guar); + } + _ if ct.has_param() || ct.has_bound_vars() => return ct.super_visit_with(self), + _ => {} } + ControlFlow::Continue(()) } } diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs index beec345109ed8..dc0e1ae6d87c5 100644 --- a/compiler/rustc_hir_analysis/src/errors.rs +++ b/compiler/rustc_hir_analysis/src/errors.rs @@ -318,6 +318,21 @@ pub(crate) struct TyOfAssocConstBindingNote<'tcx> { pub ty: Ty<'tcx>, } +#[derive(Diagnostic)] +#[diag(hir_analysis_escaping_bound_var_in_ty_of_assoc_const_binding)] +pub(crate) struct EscapingBoundVarInTyOfAssocConstBinding<'tcx> { + #[primary_span] + #[label] + pub span: Span, + pub assoc_const: Ident, + pub var_name: Symbol, + pub var_def_kind: &'static str, + #[label(hir_analysis_var_defined_here_label)] + pub var_defined_here_label: Span, + #[subdiagnostic] + pub ty_note: Option>, +} + #[derive(Subdiagnostic)] #[help(hir_analysis_parenthesized_fn_trait_expansion)] pub struct ParenthesizedFnTraitExpansion { diff --git a/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.rs b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.rs new file mode 100644 index 0000000000000..a718eb23bed59 --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.rs @@ -0,0 +1,25 @@ +// Check that we eventually catch types of assoc const bounds +// (containing late-bound vars) that are ill-formed. +#![feature(associated_const_equality)] + +trait Trait { + const K: T; +} + +fn take( + _: impl Trait< + < fn(&'a str) -> &'a str as Project>::Out as Discard>::Out, + K = { () } + >, +) {} +//~^^^^^^ ERROR implementation of `Project` is not general enough +//~^^^^ ERROR higher-ranked subtype error +//~| ERROR higher-ranked subtype error + +trait Project { type Out; } +impl Project for fn(T) -> T { type Out = T; } + +trait Discard { type Out; } +impl Discard for T { type Out = (); } + +fn main() {} diff --git a/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.stderr b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.stderr new file mode 100644 index 0000000000000..967814c9c3d9d --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.stderr @@ -0,0 +1,25 @@ +error: higher-ranked subtype error + --> $DIR/assoc-const-eq-bound-var-in-ty-not-wf.rs:12:13 + | +LL | K = { () } + | ^^^^^^ + +error: higher-ranked subtype error + --> $DIR/assoc-const-eq-bound-var-in-ty-not-wf.rs:12:13 + | +LL | K = { () } + | ^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: implementation of `Project` is not general enough + --> $DIR/assoc-const-eq-bound-var-in-ty-not-wf.rs:9:4 + | +LL | fn take( + | ^^^^ implementation of `Project` is not general enough + | + = note: `Project` would have to be implemented for the type `for<'a> fn(&'a str) -> &'a str` + = note: ...but `Project` is actually implemented for the type `fn(&'0 str) -> &'0 str`, for some specific lifetime `'0` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs new file mode 100644 index 0000000000000..7fc6d564ca444 --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs @@ -0,0 +1,22 @@ +// Check that we don't reject non-escaping late-bound vars in the type of assoc const bindings. +// There's no reason why we should disallow them. +// +//@ check-pass + +#![feature(associated_const_equality)] + +trait Trait { + const K: T; +} + +fn take( + _: impl Trait< + fn(&'a str) -> &'a str as Discard>::Out, + K = { () } + >, +) {} + +trait Discard { type Out; } +impl Discard for T { type Out = (); } + +fn main() {} diff --git a/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs b/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs new file mode 100644 index 0000000000000..6db1e85ccfa6a --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs @@ -0,0 +1,15 @@ +// Detect and reject escaping late-bound generic params in +// the type of assoc consts used in an equality bound. +#![feature(associated_const_equality)] + +trait Trait<'a> { + const K: &'a (); +} + +fn take(_: impl for<'r> Trait<'r, K = { &() }>) {} +//~^ ERROR the type of the associated constant `K` cannot capture late-bound generic parameters +//~| NOTE its type cannot capture the late-bound lifetime parameter `'r` +//~| NOTE the late-bound lifetime parameter `'r` is defined here +//~| NOTE `K` has type `&'r ()` + +fn main() {} diff --git a/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.stderr b/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.stderr new file mode 100644 index 0000000000000..349fddcafe8b7 --- /dev/null +++ b/tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.stderr @@ -0,0 +1,12 @@ +error: the type of the associated constant `K` cannot capture late-bound generic parameters + --> $DIR/assoc-const-eq-esc-bound-var-in-ty.rs:9:35 + | +LL | fn take(_: impl for<'r> Trait<'r, K = { &() }>) {} + | -- ^ its type cannot capture the late-bound lifetime parameter `'r` + | | + | the late-bound lifetime parameter `'r` is defined here + | + = note: `K` has type `&'r ()` + +error: aborting due to 1 previous error +