Skip to content

Commit

Permalink
Rollup merge of rust-lang#121258 - fmease:assoc-const-eq-reject-overl…
Browse files Browse the repository at this point in the history
…y-generic-tys, r=compiler-errors

Reject overly generic assoc const binding types

Split off from rust-lang#119385 to make rust-lang#119385 easier to review.

---

In the *instantiated* type of assoc const bindings

1. reject **early-bound generic params**
   * Provide a rich error message instead of ICE'ing ([rust-lang#108271](rust-lang#108271)).
   * This is a temporary and semi-artificial restriction until the arrival of *generic const generics*.
   * It's quite possible that rustc could already perfectly support this subset of generic const generics if we just removed some checks (some `.no_bound_vars().expect(…)`) but even if that was the case, I'd rather gate it behind a new feature flag. Reporting an error instead of ICE'ing is a good first step towards an eventual feature gate error.
2. reject **escaping late-bound generic params**
   * They lead to ICEs before & I'm pretty sure that they remain incorrect even in a world with *generic const generics*

---

Together with rust-lang#118668 & rust-lang#119385, this supersedes rust-lang#118360.
Fixes rust-lang#108271.
  • Loading branch information
matthiaskrgr committed Mar 18, 2024
2 parents d31b6fb + 0b2fb8d commit 05f7633
Show file tree
Hide file tree
Showing 10 changed files with 463 additions and 11 deletions.
23 changes: 23 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 Expand Up @@ -316,6 +321,22 @@ hir_analysis_opaque_captures_higher_ranked_lifetime = `impl Trait` cannot captur
.label = `impl Trait` implicitly captures all lifetimes in scope
.note = lifetime declared here
hir_analysis_param_in_ty_of_assoc_const_binding =
the type of the associated constant `{$assoc_const}` must not depend on {$param_category ->
[self] `Self`
[synthetic] `impl Trait`
*[normal] generic parameters
}
.label = its type must not depend on {$param_category ->
[self] `Self`
[synthetic] `impl Trait`
*[normal] the {$param_def_kind} `{$param_name}`
}
.param_defined_here_label = {$param_category ->
[synthetic] the `impl Trait` is specified here
*[normal] the {$param_def_kind} `{$param_name}` is defined here
}
hir_analysis_paren_sugar_attribute = the `#[rustc_paren_sugar]` attribute is a temporary means of controlling which traits can use parenthetical notation
.help = add `#![feature(unboxed_closures)]` to the crate attributes to use it
Expand Down Expand Up @@ -432,6 +453,8 @@ hir_analysis_transparent_non_zero_sized_enum = the variant of a transparent {$de
.label = needs at most one field with non-trivial size or alignment, but has {$field_count}
.labels = this field has non-zero size or requires alignment
hir_analysis_ty_of_assoc_const_binding_note = `{$assoc_const}` has type `{$ty}`
hir_analysis_ty_param_first_local = type parameter `{$param_ty}` must be covered by another type when it appears before the first local type (`{$local_type}`)
.label = type parameter `{$param_ty}` must be covered by another type when it appears before the first local type (`{$local_type}`)
.note = implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
Expand Down
183 changes: 172 additions & 11 deletions compiler/rustc_hir_analysis/src/astconv/bounds.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use rustc_data_structures::fx::FxIndexMap;
use std::ops::ControlFlow;

use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_errors::{codes::*, struct_span_code_err};
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::ty::{self as ty, Ty};
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;

use crate::astconv::{AstConv, OnlySelfBounds, PredicateFilter};
Expand Down Expand Up @@ -433,14 +436,8 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
binding.kind
{
let ty = alias_ty.map_bound(|ty| tcx.type_of(ty.def_id).instantiate(tcx, ty.args));
// Since the arguments passed to the alias type above may contain early-bound
// generic parameters, the instantiated type may contain some as well.
// Therefore wrap it in `EarlyBinder`.
// FIXME(fmease): Reject escaping late-bound vars.
tcx.feed_anon_const_type(
anon_const.def_id,
ty::EarlyBinder::bind(ty.skip_binder()),
);
let ty = check_assoc_const_binding_type(tcx, assoc_ident, ty, binding.hir_id);
tcx.feed_anon_const_type(anon_const.def_id, ty::EarlyBinder::bind(ty));
}

alias_ty
Expand Down Expand Up @@ -530,3 +527,167 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
Ok(())
}
}

/// 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].
///
/// It might actually be possible that we can already support early-bound generic params
/// in such types if we just lifted some more checks in other places, too, for example
/// inside [`ty::Const::from_anon_const`]. However, even if that were the case, we should
/// probably gate this behind another feature flag.
///
/// [^1]: <https://github.com/rust-lang/project-const-generics/issues/28>.
fn check_assoc_const_binding_type<'tcx>(
tcx: TyCtxt<'tcx>,
assoc_const: Ident,
ty: ty::Binder<'tcx, Ty<'tcx>>,
hir_id: hir::HirId,
) -> 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.

let ty = ty.skip_binder();
if !ty.has_param() && !ty.has_escaping_bound_vars() {
return ty;
}

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 ty_note = ty
.make_suggestable(tcx, false)
.map(|ty| crate::errors::TyOfAssocConstBindingNote { assoc_const, ty });

let enclosing_item_owner_id = tcx
.hir()
.parent_owner_iter(hir_id)
.find_map(|(owner_id, parent)| parent.generics().map(|_| owner_id))
.unwrap();
let generics = tcx.generics_of(enclosing_item_owner_id);
for index in collector.params {
let param = generics.param_at(index as _, tcx);
let is_self_param = param.name == rustc_span::symbol::kw::SelfUpper;
guar.get_or_insert(tcx.dcx().emit_err(crate::errors::ParamInTyOfAssocConstBinding {
span: assoc_const.span,
assoc_const,
param_name: param.name,
param_def_kind: tcx.def_descr(param.def_id),
param_category: if is_self_param {
"self"
} else if param.kind.is_synthetic() {
"synthetic"
} else {
"normal"
},
param_defined_here_label:
(!is_self_param).then(|| tcx.def_ident_span(param.def_id).unwrap()),
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 or bound vars in ty"));
Ty::new_error(tcx, guar)
}

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

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 {
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 {
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 {
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(())
}
}
38 changes: 38 additions & 0 deletions compiler/rustc_hir_analysis/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,44 @@ pub struct AssocTypeBindingNotAllowed {
pub fn_trait_expansion: Option<ParenthesizedFnTraitExpansion>,
}

#[derive(Diagnostic)]
#[diag(hir_analysis_param_in_ty_of_assoc_const_binding)]
pub(crate) struct ParamInTyOfAssocConstBinding<'tcx> {
#[primary_span]
#[label]
pub span: Span,
pub assoc_const: Ident,
pub param_name: Symbol,
pub param_def_kind: &'static str,
pub param_category: &'static str,
#[label(hir_analysis_param_defined_here_label)]
pub param_defined_here_label: Option<Span>,
#[subdiagnostic]
pub ty_note: Option<TyOfAssocConstBindingNote<'tcx>>,
}

#[derive(Subdiagnostic, Clone, Copy)]
#[note(hir_analysis_ty_of_assoc_const_binding_note)]
pub(crate) struct TyOfAssocConstBindingNote<'tcx> {
pub assoc_const: Ident,
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 05f7633

Please sign in to comment.