Use more principled check for generics in const ops

Instead of using a visitor in typeck, we just check, whenever lowering a
use of a param, whether the parent item is an MCG anon const during hir
ty lowering (and instantiate_value_path). If so, we report an error
since MCG anon consts should never be able to use generics. All other
kinds of anon consts are at least syntactically allowed to use generic
params.

We use a `TypeFolder` to accomplish this; this way, we look at the
fully explicit semantic representation of the type/const/whatever and
don't miss subtle cases like `Self` type aliases.
This commit is contained in:
Noah Lev 2025-12-29 23:31:46 -08:00
parent 319735d8c9
commit c7e368543c
26 changed files with 338 additions and 124 deletions

View file

@ -38,8 +38,8 @@ use rustc_middle::middle::stability::AllowUnstable;
use rustc_middle::mir::interpret::LitToConstInput;
use rustc_middle::ty::print::PrintPolyTraitRefExt as _;
use rustc_middle::ty::{
self, Const, GenericArgKind, GenericArgsRef, GenericParamDefKind, Ty, TyCtxt, TypeVisitableExt,
TypingMode, Upcast, fold_regions,
self, Const, GenericArgKind, GenericArgsRef, GenericParamDefKind, Ty, TyCtxt,
TypeSuperFoldable, TypeVisitableExt, TypingMode, Upcast, fold_regions,
};
use rustc_middle::{bug, span_bug};
use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS;
@ -394,7 +394,118 @@ pub trait GenericArgsLowerer<'a, 'tcx> {
) -> ty::GenericArg<'tcx>;
}
struct ForbidMCGParamUsesFolder<'tcx> {
tcx: TyCtxt<'tcx>,
anon_const_def_id: LocalDefId,
span: Span,
is_self_alias: bool,
}
impl<'tcx> ForbidMCGParamUsesFolder<'tcx> {
fn error(&self) -> ErrorGuaranteed {
let msg = if self.is_self_alias {
"generic `Self` types are currently not permitted in anonymous constants"
} else {
"generic parameters may not be used in const operations"
};
let mut diag = self.tcx.dcx().struct_span_err(self.span, msg);
if self.is_self_alias {
let anon_const_hir_id: HirId = HirId::make_owner(self.anon_const_def_id);
let parent_impl = self.tcx.hir_parent_owner_iter(anon_const_hir_id).find_map(
|(_, node)| match node {
hir::OwnerNode::Item(hir::Item {
kind: hir::ItemKind::Impl(impl_), ..
}) => Some(impl_),
_ => None,
},
);
if let Some(impl_) = parent_impl {
diag.span_note(impl_.self_ty.span, "not a concrete type");
}
}
diag.emit()
}
}
impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for ForbidMCGParamUsesFolder<'tcx> {
fn cx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
if matches!(t.kind(), ty::Param(..)) {
return Ty::new_error(self.tcx, self.error());
}
t.super_fold_with(self)
}
fn fold_const(&mut self, c: Const<'tcx>) -> Const<'tcx> {
if matches!(c.kind(), ty::ConstKind::Param(..)) {
return Const::new_error(self.tcx, self.error());
}
c.super_fold_with(self)
}
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
if matches!(r.kind(), ty::RegionKind::ReEarlyParam(..) | ty::RegionKind::ReLateParam(..)) {
return ty::Region::new_error(self.tcx, self.error());
}
r
}
}
impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// See `check_param_uses_if_mcg`.
///
/// FIXME(mgca): this is pub only for instantiate_value_path and would be nice to avoid altogether
pub fn check_param_res_if_mcg_for_instantiate_value_path(
&self,
res: Res,
span: Span,
) -> Result<(), ErrorGuaranteed> {
let tcx = self.tcx();
let parent_def_id = self.item_def_id();
if let Res::Def(DefKind::ConstParam, _) = res
&& tcx.def_kind(parent_def_id) == DefKind::AnonConst
&& let ty::AnonConstKind::MCG = tcx.anon_const_kind(parent_def_id)
{
let folder = ForbidMCGParamUsesFolder {
tcx,
anon_const_def_id: parent_def_id,
span,
is_self_alias: false,
};
return Err(folder.error());
}
Ok(())
}
/// Check for uses of generic parameters that are not in scope due to this being
/// in a non-generic anon const context.
#[must_use = "need to use transformed output"]
fn check_param_uses_if_mcg<T>(&self, term: T, span: Span, is_self_alias: bool) -> T
where
T: ty::TypeFoldable<TyCtxt<'tcx>>,
{
let tcx = self.tcx();
let parent_def_id = self.item_def_id();
if tcx.def_kind(parent_def_id) == DefKind::AnonConst
&& let ty::AnonConstKind::MCG = tcx.anon_const_kind(parent_def_id)
// Fast path if contains no params/escaping bound vars.
&& (term.has_param() || term.has_escaping_bound_vars())
{
let mut folder = ForbidMCGParamUsesFolder {
tcx,
anon_const_def_id: parent_def_id,
span,
is_self_alias,
};
term.fold_with(&mut folder)
} else {
term
}
}
/// Lower a lifetime from the HIR to our internal notion of a lifetime called a *region*.
#[instrument(level = "debug", skip(self), ret)]
pub fn lower_lifetime(
@ -403,7 +514,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
reason: RegionInferReason<'_>,
) -> ty::Region<'tcx> {
if let Some(resolved) = self.tcx().named_bound_var(lifetime.hir_id) {
self.lower_resolved_lifetime(resolved)
let region = self.lower_resolved_lifetime(resolved);
self.check_param_uses_if_mcg(region, lifetime.ident.span, false)
} else {
self.re_infer(lifetime.ident.span, reason)
}
@ -411,7 +523,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// Lower a lifetime from the HIR to our internal notion of a lifetime called a *region*.
#[instrument(level = "debug", skip(self), ret)]
pub fn lower_resolved_lifetime(&self, resolved: rbv::ResolvedArg) -> ty::Region<'tcx> {
fn lower_resolved_lifetime(&self, resolved: rbv::ResolvedArg) -> ty::Region<'tcx> {
let tcx = self.tcx();
match resolved {
@ -1256,9 +1368,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
TypeRelativePath::AssocItem(def_id, args) => {
let alias_ty = ty::AliasTy::new_from_args(tcx, def_id, args);
let ty = Ty::new_alias(tcx, alias_ty.kind(tcx), alias_ty);
let ty = self.check_param_uses_if_mcg(ty, span, false);
Ok((ty, tcx.def_kind(def_id), def_id))
}
TypeRelativePath::Variant { adt, variant_did } => {
let adt = self.check_param_uses_if_mcg(adt, span, false);
Ok((adt, DefKind::Variant, variant_did))
}
}
@ -1275,7 +1389,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
span: Span,
) -> Result<Const<'tcx>, ErrorGuaranteed> {
let tcx = self.tcx();
let (def_id, args) = match self.lower_type_relative_path(
match self.lower_type_relative_path(
self_ty,
hir_self_ty,
segment,
@ -1292,15 +1406,16 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
err.note("the declaration in the trait must be marked with `#[type_const]`");
return Err(err.emit());
}
(def_id, args)
let ct = Const::new_unevaluated(tcx, ty::UnevaluatedConst::new(def_id, args));
let ct = self.check_param_uses_if_mcg(ct, span, false);
Ok(ct)
}
// FIXME(mgca): implement support for this once ready to support all adt ctor expressions,
// not just const ctors
TypeRelativePath::Variant { .. } => {
span_bug!(span, "unexpected variant res for type associated const path")
}
};
Ok(Const::new_unevaluated(tcx, ty::UnevaluatedConst::new(def_id, args)))
}
}
/// Lower a [type-relative][hir::QPath::TypeRelative] (and type-level) path.
@ -2032,9 +2147,9 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
GenericsArgsErrExtend::None
},
);
tcx.types.self_param
self.check_param_uses_if_mcg(tcx.types.self_param, span, false)
}
Res::SelfTyAlias { alias_to: def_id, forbid_generic, .. } => {
Res::SelfTyAlias { alias_to: def_id, forbid_generic: _, .. } => {
// `Self` in impl (we know the concrete type).
assert_eq!(opt_self_ty, None);
// Try to evaluate any array length constants.
@ -2043,42 +2158,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
path.segments.iter(),
GenericsArgsErrExtend::SelfTyAlias { def_id, span },
);
// HACK(min_const_generics): Forbid generic `Self` types
// here as we can't easily do that during nameres.
//
// We do this before normalization as we otherwise allow
// ```rust
// trait AlwaysApplicable { type Assoc; }
// impl<T: ?Sized> AlwaysApplicable for T { type Assoc = usize; }
//
// trait BindsParam<T> {
// type ArrayTy;
// }
// impl<T> BindsParam<T> for <T as AlwaysApplicable>::Assoc {
// type ArrayTy = [u8; Self::MAX];
// }
// ```
// Note that the normalization happens in the param env of
// the anon const, which is empty. This is why the
// `AlwaysApplicable` impl needs a `T: ?Sized` bound for
// this to compile if we were to normalize here.
if forbid_generic && ty.has_param() {
let mut err = self.dcx().struct_span_err(
path.span,
"generic `Self` types are currently not permitted in anonymous constants",
);
if let Some(hir::Node::Item(&hir::Item {
kind: hir::ItemKind::Impl(impl_),
..
})) = tcx.hir_get_if_local(def_id)
{
err.span_note(impl_.self_ty.span, "not a concrete type");
}
let reported = err.emit();
Ty::new_error(tcx, reported)
} else {
ty
}
self.check_param_uses_if_mcg(ty, span, true)
}
Res::Def(DefKind::AssocTy, def_id) => {
let trait_segment = if let [modules @ .., trait_, _item] = path.segments {
@ -2138,7 +2218,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
/// and late-bound ones to [`ty::Bound`].
pub(crate) fn lower_ty_param(&self, hir_id: HirId) -> Ty<'tcx> {
let tcx = self.tcx();
match tcx.named_bound_var(hir_id) {
let ty = match tcx.named_bound_var(hir_id) {
Some(rbv::ResolvedArg::LateBound(debruijn, index, def_id)) => {
let br = ty::BoundTy {
var: ty::BoundVar::from_u32(index),
@ -2154,7 +2235,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
}
Some(rbv::ResolvedArg::Error(guar)) => Ty::new_error(tcx, guar),
arg => bug!("unexpected bound var resolution for {hir_id:?}: {arg:?}"),
}
};
self.check_param_uses_if_mcg(ty, tcx.hir_span(hir_id), false)
}
/// Lower a const parameter from the HIR to our internal notion of a constant.
@ -2164,7 +2246,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
pub(crate) fn lower_const_param(&self, param_def_id: DefId, path_hir_id: HirId) -> Const<'tcx> {
let tcx = self.tcx();
match tcx.named_bound_var(path_hir_id) {
let ct = match tcx.named_bound_var(path_hir_id) {
Some(rbv::ResolvedArg::EarlyBound(_)) => {
// Find the name and index of the const parameter by indexing the generics of
// the parent item and construct a `ParamConst`.
@ -2181,7 +2263,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
),
Some(rbv::ResolvedArg::Error(guar)) => ty::Const::new_error(tcx, guar),
arg => bug!("unexpected bound var resolution for {:?}: {arg:?}", path_hir_id),
}
};
self.check_param_uses_if_mcg(ct, tcx.hir_span(path_hir_id), false)
}
/// Lower a [`hir::ConstArg`] to a (type-level) [`ty::Const`](Const).
@ -2386,7 +2469,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
) -> Const<'tcx> {
let tcx = self.tcx();
let span = path.span;
match path.res {
let ct = match path.res {
Res::Def(DefKind::ConstParam, def_id) => {
assert_eq!(opt_self_ty, None);
let _ = self.prohibit_generic_args(
@ -2475,7 +2558,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
span,
format!("invalid Res {res:?} for const path"),
),
}
};
self.check_param_uses_if_mcg(ct, span, false)
}
/// Literals are eagerly converted to a constant, everything else becomes `Unevaluated`.
@ -2787,6 +2871,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let args = ty::GenericArgs::for_item(tcx, def_id, |param, _| {
if let Some(i) = (param.index as usize).checked_sub(offset) {
let (lifetime, _) = lifetimes[i];
// FIXME(mgca): should we be calling self.check_params_use_if_mcg here too?
self.lower_resolved_lifetime(lifetime).into()
} else {
tcx.mk_param_from_def(param)

View file

@ -1004,6 +1004,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err_extend,
);
if let Err(e) = self.lowerer().check_param_res_if_mcg_for_instantiate_value_path(res, span)
{
return (Ty::new_error(self.tcx, e), res);
}
if let Res::Local(hid) = res {
let ty = self.local_ty(span, hid);
let ty = self.normalize(span, ty);

View file

@ -115,18 +115,6 @@ fn typeck_with_inspect<'tcx>(
return tcx.typeck(typeck_root_def_id);
}
// We can't handle bodies containing generic parameters even though
// these generic parameters aren't part of its `generics_of` right now.
//
// See the FIXME on `check_anon_const_invalid_param_uses`.
if tcx.features().min_generic_const_args()
&& let DefKind::AnonConst = tcx.def_kind(def_id)
&& let ty::AnonConstKind::MCG = tcx.anon_const_kind(def_id)
&& let Err(e) = tcx.check_anon_const_invalid_param_uses(def_id)
{
e.raise_fatal();
}
let id = tcx.local_def_id_to_hir_id(def_id);
let node = tcx.hir_node(id);
let span = tcx.def_span(def_id);

View file

@ -2,8 +2,6 @@
//! eliminated, and all its methods are now on `TyCtxt`. But the module name
//! stays as `map` because there isn't an obviously better name for it.
use std::ops::ControlFlow;
use rustc_abi::ExternAbi;
use rustc_ast::visit::{VisitorResult, walk_list};
use rustc_data_structures::fingerprint::Fingerprint;
@ -1090,52 +1088,6 @@ impl<'tcx> TyCtxt<'tcx> {
None
}
// FIXME(mgca): this is pretty iffy. In the long term we should make
// HIR ty lowering able to return `Error` versions of types/consts when
// lowering them in contexts that aren't supposed to use generic parameters.
//
// This current impl strategy is incomplete and doesn't handle `Self` ty aliases.
pub fn check_anon_const_invalid_param_uses(
self,
anon: LocalDefId,
) -> Result<(), ErrorGuaranteed> {
struct GenericParamVisitor<'tcx>(TyCtxt<'tcx>);
impl<'tcx> Visitor<'tcx> for GenericParamVisitor<'tcx> {
type NestedFilter = nested_filter::OnlyBodies;
type Result = ControlFlow<ErrorGuaranteed>;
fn maybe_tcx(&mut self) -> TyCtxt<'tcx> {
self.0
}
fn visit_path(
&mut self,
path: &crate::hir::Path<'tcx>,
_id: HirId,
) -> ControlFlow<ErrorGuaranteed> {
if let Res::Def(
DefKind::TyParam | DefKind::ConstParam | DefKind::LifetimeParam,
_,
) = path.res
{
let e = self.0.dcx().struct_span_err(
path.span,
"generic parameters may not be used in const operations",
);
return ControlFlow::Break(e.emit());
}
intravisit::walk_path(self, path)
}
}
let body = self.hir_maybe_body_owned_by(anon).unwrap();
match GenericParamVisitor(self).visit_expr(&body.value) {
ControlFlow::Break(e) => Err(e),
ControlFlow::Continue(()) => Ok(()),
}
}
}
impl<'tcx> intravisit::HirTyCtxt<'tcx> for TyCtxt<'tcx> {

View file

@ -1,6 +0,0 @@
//@ known-bug: #140891
struct A<const N: usize> {}
impl<const N: usize> Iterator for A<N> {
fn next() -> [(); std::mem::size_of::<Option<Self::Item>>] {}
}
fn main() {}

View file

@ -11,7 +11,7 @@ struct Wrapper<const F: usize>(i64);
impl<const F: usize> aux::FromSlice for Wrapper<F> {
fn validate_slice(_: &[[u8; Self::SIZE]]) -> Result<(), aux::Error> {
//~^ ERROR generic `Self` types are currently not permitted in anonymous constants
//~^ ERROR generic `Self`
Ok(())
}
}

View file

@ -1,7 +1,7 @@
//@ check-fail
struct DataWrapper<'a> {
data: &'a [u8; Self::SIZE], //~ ERROR generic `Self` types are currently not permitted in anonymous constants
data: &'a [u8; Self::SIZE], //~ ERROR generic `Self`
}
impl DataWrapper<'_> {

View file

@ -5,7 +5,7 @@ impl<'a> OnDiskDirEntry<'a> {
const LFN_FRAGMENT_LEN: usize = 2;
fn lfn_contents(&self) -> [char; Self::LFN_FRAGMENT_LEN] { loop { } }
//~^ ERROR: generic `Self` types are currently not permitted in anonymous constants
//~^ ERROR: generic `Self`
}
fn main() {}

View file

@ -2,7 +2,7 @@
pub struct Memory<'rom> {
rom: &'rom [u8],
ram: [u8; Self::SIZE],
//~^ ERROR: generic `Self` types are currently not permitted in anonymous constants
//~^ ERROR: generic `Self`
}
impl<'rom> Memory<'rom> {

View file

@ -7,7 +7,7 @@ struct Bug<S: ?Sized> {
A: [(); {
//[full]~^ ERROR overly complex generic constant
let x: Option<Box<Self>> = None;
//[min]~^ ERROR generic `Self` types are currently not permitted in anonymous constants
//[min]~^ ERROR generic `Self`
0
}],
B: S

View file

@ -0,0 +1,13 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait<const N: usize> {}
fn foo<'a, T>()
where
'a: 'a,
T: Trait<const { let a: &'a (); 1 }>
//~^ ERROR generic parameters may not be used in const operations
{}
fn main() {}

View file

@ -0,0 +1,8 @@
error: generic parameters may not be used in const operations
--> $DIR/early-bound-param-lt-bad.rs:9:30
|
LL | T: Trait<const { let a: &'a (); 1 }>
| ^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,10 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
// library crates exercise weirder code paths around
// DefIds which were created for const args.
#![crate_type = "lib"]
fn foo<const N: usize>() -> [(); N] {
let a: [(); const { N }] = todo!();
//~^ ERROR: generic parameters may not be used in const operations
}

View file

@ -0,0 +1,8 @@
error: generic parameters may not be used in const operations
--> $DIR/explicit_anon_consts-7.rs:8:25
|
LL | let a: [(); const { N }] = todo!();
| ^
error: aborting due to 1 previous error

View file

@ -0,0 +1,12 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait<const N: usize> {}
fn foo<T>()
where
for<'a> T: Trait<const { let a: &'a (); 1 }>
//~^ ERROR cannot capture late-bound lifetime in constant
{}
fn main() {}

View file

@ -0,0 +1,8 @@
error: cannot capture late-bound lifetime in constant
--> $DIR/higher-ranked-lts-bad.rs:8:38
|
LL | for<'a> T: Trait<const { let a: &'a (); 1 }>
| -- lifetime defined here ^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,13 @@
//@ check-pass
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait<const N: usize> {}
fn foo<T>()
where
T: Trait<const { let a: for<'a> fn(&'a ()); 1 }>
{}
fn main() {}

View file

@ -0,0 +1,12 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Trait<const N: usize> {}
fn foo<'a, T>()
where
T: Trait<const { let a: &'a (); 1 }>
//~^ ERROR cannot capture late-bound lifetime in constant
{}
fn main() {}

View file

@ -0,0 +1,11 @@
error: cannot capture late-bound lifetime in constant
--> $DIR/late-bound-param-lt-bad.rs:8:30
|
LL | fn foo<'a, T>()
| -- lifetime defined here
LL | where
LL | T: Trait<const { let a: &'a (); 1 }>
| ^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,13 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
struct S<const N: usize>([(); N]);
impl<const N: usize> S<N> {
fn foo() -> [(); const { let _: Self = loop {}; 1 }] {
//~^ ERROR generic `Self`
todo!()
}
}
fn main() {}

View file

@ -0,0 +1,14 @@
error: generic `Self` types are currently not permitted in anonymous constants
--> $DIR/selftyalias-containing-param.rs:7:37
|
LL | fn foo() -> [(); const { let _: Self = loop {}; 1 }] {
| ^^^^
|
note: not a concrete type
--> $DIR/selftyalias-containing-param.rs:6:22
|
LL | impl<const N: usize> S<N> {
| ^^^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,9 @@
#![feature(min_generic_const_args)]
#![expect(incomplete_features)]
trait Tr<const N: usize> {
fn foo() -> [(); const { let _: Self; 1 }];
//~^ ERROR generic parameters
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: generic parameters may not be used in const operations
--> $DIR/selftyparam.rs:5:37
|
LL | fn foo() -> [(); const { let _: Self; 1 }];
| ^^^^
error: aborting due to 1 previous error

View file

@ -9,7 +9,7 @@ trait BindsParam<T> {
type ArrayTy;
}
impl<T> BindsParam<T> for <T as AlwaysApplicable>::Assoc {
type ArrayTy = [u8; Self::MAX]; //~ ERROR generic `Self` types
type ArrayTy = [u8; Self::MAX]; //~ ERROR generic `Self`
}
fn main() {}

View file

@ -0,0 +1,8 @@
error: generic parameters may not be used in const operations
--> $DIR/type-relative-path-144547.rs:39:35
|
LL | type SupportedArray<T> = [T; <Self::InfoType as LevelInfo>::SUPPORTED_SLOTS];
| ^^^^^^^^^^^^^^
error: aborting due to 1 previous error

View file

@ -0,0 +1,43 @@
// Regression test for #144547.
//@ revisions: min mgca
//@[mgca] check-pass
#![cfg_attr(mgca, feature(min_generic_const_args))]
#![cfg_attr(mgca, expect(incomplete_features))]
trait UnderlyingImpl<const MAX_SIZE: usize> {
type InfoType: LevelInfo;
type SupportedArray<T>;
}
// FIXME: cfg_attr(..., type_const) is broken (search for sym::type_const in compiler/)
trait LevelInfo {
#[cfg(mgca)]
#[type_const]
const SUPPORTED_SLOTS: usize;
#[cfg(not(mgca))]
const SUPPORTED_SLOTS: usize;
}
struct Info;
impl LevelInfo for Info {
#[cfg(mgca)]
#[type_const]
const SUPPORTED_SLOTS: usize = 1;
#[cfg(not(mgca))]
const SUPPORTED_SLOTS: usize = 1;
}
struct SomeImpl;
impl<const MAX_SIZE: usize> UnderlyingImpl<MAX_SIZE> for SomeImpl {
type InfoType = Info;
type SupportedArray<T> = [T; <Self::InfoType as LevelInfo>::SUPPORTED_SLOTS];
//[min]~^ ERROR generic parameters
}
fn main() {}