about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLeón Orell Valerian Liehr <me@fmease.dev>2024-01-10 02:11:25 +0100
committerLeón Orell Valerian Liehr <me@fmease.dev>2024-03-13 14:29:27 +0100
commit0b2fb8db6540b0548230cd335e7b2a845686f7ea (patch)
tree12466dcca436dbc9af604e384683c45e47bcff44
parentd26c5723e793c793c152fffaadc3dc7a45eac29e (diff)
downloadrust-0b2fb8db6540b0548230cd335e7b2a845686f7ea.tar.gz
rust-0b2fb8db6540b0548230cd335e7b2a845686f7ea.zip
Reject escaping bound vars in the type of assoc const bindings
-rw-r--r--compiler/rustc_hir_analysis/messages.ftl5
-rw-r--r--compiler/rustc_hir_analysis/src/astconv/bounds.rs113
-rw-r--r--compiler/rustc_hir_analysis/src/errors.rs15
-rw-r--r--tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.rs25
-rw-r--r--tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty-not-wf.stderr25
-rw-r--r--tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs22
-rw-r--r--tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs15
-rw-r--r--tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.stderr12
8 files changed, 212 insertions, 20 deletions
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index e38bcdcb298..364e39e626b 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 e5cef88e324..ff365d6275a 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 & escaping late-bound generic params in the type of assoc 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<u32>,
+    vars: FxIndexSet<(DefId, Symbol)>,
+    depth: ty::DebruijnIndex,
 }
 
-impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamCollector {
+impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamAndBoundVarCollector<'tcx> {
+    type Result = ControlFlow<ErrorGuaranteed>;
+
+    fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
+        &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 beec345109e..dc0e1ae6d87 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<TyOfAssocConstBindingNote<'tcx>>,
+}
+
 #[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 00000000000..a718eb23bed
--- /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<T> {
+    const K: T;
+}
+
+fn take(
+    _: impl Trait<
+        <<for<'a> 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<T> Project for fn(T) -> T { type Out = T; }
+
+trait Discard { type Out; }
+impl<T: ?Sized> 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 00000000000..967814c9c3d
--- /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 00000000000..7fc6d564ca4
--- /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<T> {
+    const K: T;
+}
+
+fn take(
+    _: impl Trait<
+        <for<'a> fn(&'a str) -> &'a str as Discard>::Out,
+        K = { () }
+    >,
+) {}
+
+trait Discard { type Out; }
+impl<T: ?Sized> 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 00000000000..6db1e85ccfa
--- /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 00000000000..349fddcafe8
--- /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
+