Skip to content

Commit

Permalink
Reject escaping bound vars in the type of assoc const bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
fmease committed Feb 18, 2024
1 parent 4b7db5a commit 76e4484
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 29 deletions.
5 changes: 5 additions & 0 deletions compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
116 changes: 87 additions & 29 deletions compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,17 @@ fn anon_const_type_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Ty<'tcx> {
}

Node::TypeBinding(&TypeBinding { hir_id, ident, .. }) => {
let ty = tcx.type_of_assoc_const_binding(hir_id);
let ty = tcx.type_of_assoc_const_binding(hir_id).skip_binder().skip_binder();

// We can't possibly catch this in the resolver, therefore we need to handle it here.
// FIXME(const_generics): Support generic const generics.
let Some(ty) = ty.no_bound_vars() else {
let reported = report_overly_generic_assoc_const_binding_type(
tcx,
ident,
ty.skip_binder().skip_binder(),
hir_id,
);
if ty.has_param() || ty.has_escaping_bound_vars() {
let reported =
report_overly_generic_assoc_const_binding_type(tcx, ident, ty, hir_id);
return Ty::new_error(tcx, reported);
};

// FIXME(fmease): Reject escaping late-bound vars.
return ty.skip_binder();
return ty;
}

// This match arm is for when the def_id appears in a GAT whose
Expand Down Expand Up @@ -325,10 +320,13 @@ fn report_overly_generic_assoc_const_binding_type<'tcx>(
ty: Ty<'tcx>,
hir_id: HirId,
) -> ErrorGuaranteed {
let mut collector = GenericParamCollector { params: Default::default() };
ty.visit_with(&mut collector);

let mut reported = None;
let mut collector = GenericParamAndBoundVarCollector {
tcx,
params: Default::default(),
vars: Default::default(),
depth: ty::INNERMOST,
};
let mut reported = ty.visit_with(&mut collector).break_value();

let ty_note = ty
.make_suggestable(tcx, false)
Expand Down Expand Up @@ -363,40 +361,100 @@ fn report_overly_generic_assoc_const_binding_type<'tcx>(
ty_note,
}));
}
for (var_def_id, var_name) in collector.vars {
reported.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,
},
));
}

struct GenericParamCollector {
struct GenericParamAndBoundVarCollector<'tcx> {
tcx: TyCtxt<'tcx>,
params: FxIndexSet<(u32, Symbol)>,
vars: FxIndexSet<(DefId, Symbol)>,
depth: ty::DebruijnIndex,
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamCollector {
type BreakTy = !;
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamAndBoundVarCollector<'tcx> {
type BreakTy = ErrorGuaranteed;

fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
&mut self,
binder: &ty::Binder<'tcx, T>,
) -> ControlFlow<Self::BreakTy> {
self.depth.shift_in(1);
let binder = binder.super_visit_with(self);
self.depth.shift_out(1);
binder
}

fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::Param(param) = ty.kind() {
self.params.insert((param.index, param.name));
return ControlFlow::Continue(());
match ty.kind() {
ty::Param(param) => {
self.params.insert((param.index, param.name));
}
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);
}
});
}
_ => return ty.super_visit_with(self),
}
ty.super_visit_with(self)
ControlFlow::Continue(())
}

fn visit_region(&mut self, re: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::ReEarlyParam(param) = re.kind() {
self.params.insert((param.index, param.name));
return ControlFlow::Continue(());
match re.kind() {
ty::ReEarlyParam(param) => {
self.params.insert((param.index, param.name));
}
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 reported = self.tcx.dcx().delayed_bug(format!(
"unexpected bound region kind: {:?}",
br.kind
));
return ControlFlow::Break(reported);
}
});
}
_ => {}
}
ControlFlow::Continue(())
}

fn visit_const(&mut self, ct: ty::Const<'tcx>) -> ControlFlow<Self::BreakTy> {
if let ty::ConstKind::Param(param) = ct.kind() {
self.params.insert((param.index, param.name));
return ControlFlow::Continue(());
match ct.kind() {
ty::ConstKind::Param(param) => {
self.params.insert((param.index, param.name));
ControlFlow::Continue(())
}
ty::ConstKind::Bound(db, ty::BoundVar { .. }) if db >= self.depth => {
let reported =
self.tcx.dcx().delayed_bug("unexpected escaping late-bound const var");
ControlFlow::Break(reported)
}
_ => ct.super_visit_with(self),
}
ct.super_visit_with(self)
}
}

reported.unwrap_or_else(|| bug!("failed to find gen params in ty"))
reported.unwrap_or_else(|| bug!("failed to find gen params or bound vars in ty"))
}

fn get_path_containing_arg_in_pat<'hir>(
Expand Down
15 changes: 15 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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

22 changes: 22 additions & 0 deletions tests/ui/associated-consts/assoc-const-eq-bound-var-in-ty.rs
Original file line number Diff line number Diff line change
@@ -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() {}
15 changes: 15 additions & 0 deletions tests/ui/associated-consts/assoc-const-eq-esc-bound-var-in-ty.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 76e4484

Please sign in to comment.