Skip to content

Commit

Permalink
Reject early-bound params in the type of assoc const bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
fmease committed Mar 13, 2024
1 parent 3b85d2c commit d26c572
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 10 deletions.
18 changes: 18 additions & 0 deletions compiler/rustc_hir_analysis/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,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 @@ -431,6 +447,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
108 changes: 98 additions & 10 deletions compiler/rustc_hir_analysis/src/astconv/bounds.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use rustc_data_structures::fx::FxIndexMap;
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_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 @@ -438,14 +439,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 @@ -537,3 +532,96 @@ impl<'tcx> dyn AstConv<'tcx> + '_ {
Ok(())
}
}

/// Detect and reject early-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].
///
/// 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.

// FIXME(fmease): Reject escaping late-bound vars.
let ty = ty.skip_binder();
if !ty.has_param() {
return ty;
}

let mut collector = GenericParamCollector { params: Default::default() };
ty.visit_with(&mut collector);

let mut guar = None;
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,
}));
}

let guar = guar.unwrap_or_else(|| bug!("failed to find gen params in ty"));
Ty::new_error(tcx, guar)
}

struct GenericParamCollector {
params: FxIndexSet<u32>,
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamCollector {
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)
}
}

fn visit_region(&mut self, re: ty::Region<'tcx>) -> Self::Result {
if let ty::ReEarlyParam(param) = re.kind() {
self.params.insert(param.index);
}
}

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)
}
}
}
23 changes: 23 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,29 @@ 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(Subdiagnostic)]
#[help(hir_analysis_parenthesized_fn_trait_expansion)]
pub struct ParenthesizedFnTraitExpansion {
Expand Down
55 changes: 55 additions & 0 deletions tests/ui/associated-consts/assoc-const-eq-param-in-ty.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Regression test for issue #108271.
// Detect and reject generic params in the type of assoc consts used in an equality bound.
#![feature(associated_const_equality)]

trait Trait<'a, T: 'a, const N: usize> {
const K: &'a [T; N];
}

fn take0<'r, A: 'r, const Q: usize>(_: impl Trait<'r, A, Q, K = { loop {} }>) {}
//~^ ERROR the type of the associated constant `K` must not depend on generic parameters
//~| NOTE its type must not depend on the lifetime parameter `'r`
//~| NOTE the lifetime parameter `'r` is defined here
//~| NOTE `K` has type `&'r [A; Q]`
//~| ERROR the type of the associated constant `K` must not depend on generic parameters
//~| NOTE its type must not depend on the type parameter `A`
//~| NOTE the type parameter `A` is defined here
//~| NOTE `K` has type `&'r [A; Q]`
//~| ERROR the type of the associated constant `K` must not depend on generic parameters
//~| NOTE its type must not depend on the const parameter `Q`
//~| NOTE the const parameter `Q` is defined here
//~| NOTE `K` has type `&'r [A; Q]`

trait Project {
const SELF: Self;
}

fn take1(_: impl Project<SELF = {}>) {}
//~^ ERROR the type of the associated constant `SELF` must not depend on `impl Trait`
//~| NOTE its type must not depend on `impl Trait`
//~| NOTE the `impl Trait` is specified here

fn take2<P: Project<SELF = {}>>(_: P) {}
//~^ ERROR the type of the associated constant `SELF` must not depend on generic parameters
//~| NOTE its type must not depend on the type parameter `P`
//~| NOTE the type parameter `P` is defined here
//~| NOTE `SELF` has type `P`

trait Iface<'r> {
//~^ NOTE the lifetime parameter `'r` is defined here
type Assoc<const Q: usize>: Trait<'r, Self, Q, K = { loop {} }>
//~^ ERROR the type of the associated constant `K` must not depend on generic parameters
//~| NOTE its type must not depend on the lifetime parameter `'r`
//~| NOTE `K` has type `&'r [Self; Q]`
//~| ERROR the type of the associated constant `K` must not depend on `Self`
//~| NOTE its type must not depend on `Self`
//~| NOTE `K` has type `&'r [Self; Q]`
//~| ERROR the type of the associated constant `K` must not depend on generic parameters
//~| NOTE its type must not depend on the const parameter `Q`
//~| NOTE the const parameter `Q` is defined here
//~| NOTE `K` has type `&'r [Self; Q]`
where
Self: Sized + 'r;
}

fn main() {}
76 changes: 76 additions & 0 deletions tests/ui/associated-consts/assoc-const-eq-param-in-ty.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
error: the type of the associated constant `K` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:9:61
|
LL | fn take0<'r, A: 'r, const Q: usize>(_: impl Trait<'r, A, Q, K = { loop {} }>) {}
| -- the lifetime parameter `'r` is defined here ^ its type must not depend on the lifetime parameter `'r`
|
= note: `K` has type `&'r [A; Q]`

error: the type of the associated constant `K` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:9:61
|
LL | fn take0<'r, A: 'r, const Q: usize>(_: impl Trait<'r, A, Q, K = { loop {} }>) {}
| - the type parameter `A` is defined here ^ its type must not depend on the type parameter `A`
|
= note: `K` has type `&'r [A; Q]`

error: the type of the associated constant `K` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:9:61
|
LL | fn take0<'r, A: 'r, const Q: usize>(_: impl Trait<'r, A, Q, K = { loop {} }>) {}
| - ^ its type must not depend on the const parameter `Q`
| |
| the const parameter `Q` is defined here
|
= note: `K` has type `&'r [A; Q]`

error: the type of the associated constant `SELF` must not depend on `impl Trait`
--> $DIR/assoc-const-eq-param-in-ty.rs:27:26
|
LL | fn take1(_: impl Project<SELF = {}>) {}
| -------------^^^^------
| | |
| | its type must not depend on `impl Trait`
| the `impl Trait` is specified here

error: the type of the associated constant `SELF` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:32:21
|
LL | fn take2<P: Project<SELF = {}>>(_: P) {}
| - ^^^^ its type must not depend on the type parameter `P`
| |
| the type parameter `P` is defined here
|
= note: `SELF` has type `P`

error: the type of the associated constant `K` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:40:52
|
LL | trait Iface<'r> {
| -- the lifetime parameter `'r` is defined here
LL |
LL | type Assoc<const Q: usize>: Trait<'r, Self, Q, K = { loop {} }>
| ^ its type must not depend on the lifetime parameter `'r`
|
= note: `K` has type `&'r [Self; Q]`

error: the type of the associated constant `K` must not depend on `Self`
--> $DIR/assoc-const-eq-param-in-ty.rs:40:52
|
LL | type Assoc<const Q: usize>: Trait<'r, Self, Q, K = { loop {} }>
| ^ its type must not depend on `Self`
|
= note: `K` has type `&'r [Self; Q]`

error: the type of the associated constant `K` must not depend on generic parameters
--> $DIR/assoc-const-eq-param-in-ty.rs:40:52
|
LL | type Assoc<const Q: usize>: Trait<'r, Self, Q, K = { loop {} }>
| - ^ its type must not depend on the const parameter `Q`
| |
| the const parameter `Q` is defined here
|
= note: `K` has type `&'r [Self; Q]`

error: aborting due to 8 previous errors

0 comments on commit d26c572

Please sign in to comment.