1
Fork 0

Auto merge of #125915 - camelid:const-arg-refactor, r=BoxyUwU

Represent type-level consts with new-and-improved `hir::ConstArg`

### Summary

This is a step toward `min_generic_const_exprs`. We now represent all const
generic arguments using an enum that differentiates between const *paths*
(temporarily just bare const params) and arbitrary anon consts that may perform
computations. This will enable us to cleanly implement the `min_generic_const_args`
plan of allowing the use of generics in paths used as const args, while
disallowing their use in arbitrary anon consts. Here is a summary of the salient
aspects of this change:

- Add `current_def_id_parent` to `LoweringContext`

  This is needed to track anon const parents properly once we implement
  `ConstArgKind::Path` (which requires moving anon const def-creation
  outside of `DefCollector`).

- Create `hir::ConstArgKind` enum with `Path` and `Anon` variants. Use it in the
  existing `hir::ConstArg` struct, replacing the previous `hir::AnonConst` field.

- Use `ConstArg` for all instances of const args. Specifically, use it instead
  of `AnonConst` for assoc item constraints, array lengths, and const param
  defaults.

- Some `ast::AnonConst`s now have their `DefId`s created in
  rustc_ast_lowering rather than `DefCollector`. This is because in some
  cases they will end up becoming a `ConstArgKind::Path` instead, which
  has no `DefId`. We have to solve this in a hacky way where we guess
  whether the `AnonConst` could end up as a path const since we can't
  know for sure until after name resolution (`N` could refer to a free
  const or a nullary struct). If it has no chance as being a const
  param, then we create a `DefId` in `DefCollector` -- otherwise we
  decide during ast_lowering. This will have to be updated once all path
  consts use `ConstArgKind::Path`.

- We explicitly use `ConstArgHasType` for array lengths, rather than
  implicitly relying on anon const type feeding -- this is due to the
  addition of `ConstArgKind::Path`.

- Some tests have their outputs changed, but the changes are for the
  most part minor (including removing duplicate or almost-duplicate
  errors). One test now ICEs, but it is for an incomplete, unstable
  feature and is now tracked at https://github.com/rust-lang/rust/issues/127009.

### Followup items post-merge

- Use `ConstArgKind::Path` for all const paths, not just const params.
- Fix (no github dont close this issue) #127009
- If a path in generic args doesn't resolve as a type, try to resolve as a const
  instead (do this in rustc_resolve). Then remove the special-casing from
  `rustc_ast_lowering`, so that all params will automatically be lowered as
  `ConstArgKind::Path`.
- (?) Consider making `const_evaluatable_unchecked` a hard error, or at least
  trying it in crater

r? `@BoxyUwU`
This commit is contained in:
bors 2024-07-19 08:44:51 +00:00
commit 8c3a94a1c7
72 changed files with 853 additions and 543 deletions

View file

@ -912,6 +912,7 @@ impl<'hir> Map<'hir> {
Node::Field(field) => field.span,
Node::AnonConst(constant) => constant.span,
Node::ConstBlock(constant) => self.body(constant.body).value.span,
Node::ConstArg(const_arg) => const_arg.span(),
Node::Expr(expr) => expr.span,
Node::ExprField(field) => field.span,
Node::Stmt(stmt) => stmt.span,
@ -962,7 +963,8 @@ impl<'hir> Map<'hir> {
/// Returns the HirId of `N` in `struct Foo<const N: usize = { ... }>` when
/// called with the HirId for the `{ ... }` anon const
pub fn opt_const_param_default_param_def_id(self, anon_const: HirId) -> Option<LocalDefId> {
match self.tcx.parent_hir_node(anon_const) {
let const_arg = self.tcx.parent_hir_id(anon_const);
match self.tcx.parent_hir_node(const_arg) {
Node::GenericParam(GenericParam {
def_id: param_id,
kind: GenericParamKind::Const { .. },
@ -1182,6 +1184,7 @@ fn hir_id_to_string(map: Map<'_>, id: HirId) -> String {
}
Node::AnonConst(_) => node_str("const"),
Node::ConstBlock(_) => node_str("const"),
Node::ConstArg(_) => node_str("const"),
Node::Expr(_) => node_str("expr"),
Node::ExprField(_) => node_str("expr field"),
Node::Stmt(_) => node_str("stmt"),

View file

@ -4,9 +4,9 @@ use crate::ty::{self, GenericArgs, ParamEnv, ParamEnvAnd, Ty, TyCtxt, TypeVisita
use either::Either;
use rustc_data_structures::intern::Interned;
use rustc_error_messages::MultiSpan;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, HirId};
use rustc_macros::HashStable;
use rustc_type_ir::{self as ir, TypeFlags, WithCachedTypeInfo};
use tracing::{debug, instrument};
@ -183,16 +183,55 @@ impl<'tcx> rustc_type_ir::inherent::Const<TyCtxt<'tcx>> for Const<'tcx> {
}
}
/// In some cases, [`hir::ConstArg`]s that are being used in the type system
/// through const generics need to have their type "fed" to them
/// using the query system.
///
/// Use this enum with [`Const::from_const_arg`] to instruct it with the
/// desired behavior.
#[derive(Debug, Clone, Copy)]
pub enum FeedConstTy {
/// Feed the type.
///
/// The `DefId` belongs to the const param that we are supplying
/// this (anon) const arg to.
Param(DefId),
/// Don't feed the type.
No,
}
impl<'tcx> Const<'tcx> {
/// Convert a [`hir::ConstArg`] to a [`ty::Const`](Self).
#[instrument(skip(tcx), level = "debug")]
pub fn from_const_arg(
tcx: TyCtxt<'tcx>,
const_arg: &'tcx hir::ConstArg<'tcx>,
feed: FeedConstTy,
) -> Self {
if let FeedConstTy::Param(param_def_id) = feed
&& let hir::ConstArgKind::Anon(anon) = &const_arg.kind
{
tcx.feed_anon_const_type(anon.def_id, tcx.type_of(param_def_id));
}
match const_arg.kind {
hir::ConstArgKind::Path(qpath) => {
// FIXME(min_generic_const_args): for now only params are lowered to ConstArgKind::Path
Self::from_param(tcx, qpath, const_arg.hir_id)
}
hir::ConstArgKind::Anon(anon) => Self::from_anon_const(tcx, anon.def_id),
}
}
/// Literals and const generic parameters are eagerly converted to a constant, everything else
/// becomes `Unevaluated`.
#[instrument(skip(tcx), level = "debug")]
pub fn from_anon_const(tcx: TyCtxt<'tcx>, def: LocalDefId) -> Self {
let body_id = match tcx.hir_node_by_def_id(def) {
hir::Node::AnonConst(ac) => ac.body,
_ => span_bug!(
node => span_bug!(
tcx.def_span(def.to_def_id()),
"from_anon_const can only process anonymous constants"
"from_anon_const can only process anonymous constants, not {node:?}"
),
};
@ -201,7 +240,7 @@ impl<'tcx> Const<'tcx> {
let ty = tcx.type_of(def).no_bound_vars().expect("const parameter types cannot be generic");
match Self::try_from_lit_or_param(tcx, ty, expr) {
match Self::try_from_lit(tcx, ty, expr) {
Some(v) => v,
None => ty::Const::new_unevaluated(
tcx,
@ -213,12 +252,36 @@ impl<'tcx> Const<'tcx> {
}
}
/// Lower a const param to a [`Const`].
///
/// IMPORTANT: `qpath` must be a const param, otherwise this will panic
fn from_param(tcx: TyCtxt<'tcx>, qpath: hir::QPath<'tcx>, hir_id: HirId) -> Self {
let hir::QPath::Resolved(_, &hir::Path { res: Res::Def(DefKind::ConstParam, def_id), .. }) =
qpath
else {
span_bug!(qpath.span(), "non-param {qpath:?} passed to Const::from_param")
};
match tcx.named_bound_var(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`.
let item_def_id = tcx.parent(def_id);
let generics = tcx.generics_of(item_def_id);
let index = generics.param_def_id_to_index[&def_id];
let name = tcx.item_name(def_id);
ty::Const::new_param(tcx, ty::ParamConst::new(index, name))
}
Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => {
ty::Const::new_bound(tcx, debruijn, ty::BoundVar::from_u32(index))
}
Some(rbv::ResolvedArg::Error(guar)) => ty::Const::new_error(tcx, guar),
arg => bug!("unexpected bound var resolution for {:?}: {arg:?}", hir_id),
}
}
#[instrument(skip(tcx), level = "debug")]
fn try_from_lit_or_param(
tcx: TyCtxt<'tcx>,
ty: Ty<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Option<Self> {
fn try_from_lit(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, expr: &'tcx hir::Expr<'tcx>) -> Option<Self> {
// Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments
// currently have to be wrapped in curly brackets, so it's necessary to special-case.
let expr = match &expr.kind {
@ -251,34 +314,15 @@ impl<'tcx> Const<'tcx> {
}
}
// FIXME(const_generics): We currently have to special case parameters because `min_const_generics`
// does not provide the parents generics to anonymous constants. We still allow generic const
// parameters by themselves however, e.g. `N`. These constants would cause an ICE if we were to
// ever try to instantiate the generic parameters in their bodies.
match expr.kind {
hir::ExprKind::Path(hir::QPath::Resolved(
_,
&hir::Path { res: Res::Def(DefKind::ConstParam, def_id), .. },
)) => {
match tcx.named_bound_var(expr.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`.
let item_def_id = tcx.parent(def_id);
let generics = tcx.generics_of(item_def_id);
let index = generics.param_def_id_to_index[&def_id];
let name = tcx.item_name(def_id);
Some(ty::Const::new_param(tcx, ty::ParamConst::new(index, name)))
}
Some(rbv::ResolvedArg::LateBound(debruijn, index, _)) => {
Some(ty::Const::new_bound(tcx, debruijn, ty::BoundVar::from_u32(index)))
}
Some(rbv::ResolvedArg::Error(guar)) => Some(ty::Const::new_error(tcx, guar)),
arg => bug!("unexpected bound var resolution for {:?}: {arg:?}", expr.hir_id),
}
}
_ => None,
if let hir::ExprKind::Path(hir::QPath::Resolved(
_,
&hir::Path { res: Res::Def(DefKind::ConstParam, _), .. },
)) = expr.kind
{
span_bug!(expr.span, "try_from_lit: received const param which shouldn't be possible")
}
None
}
#[inline]
@ -483,15 +527,15 @@ pub fn const_param_default<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
) -> ty::EarlyBinder<'tcx, Const<'tcx>> {
let default_def_id = match tcx.hir_node_by_def_id(def_id) {
let default_ct = match tcx.hir_node_by_def_id(def_id) {
hir::Node::GenericParam(hir::GenericParam {
kind: hir::GenericParamKind::Const { default: Some(ac), .. },
kind: hir::GenericParamKind::Const { default: Some(ct), .. },
..
}) => ac.def_id,
}) => ct,
_ => span_bug!(
tcx.def_span(def_id),
"`const_param_default` expected a generic parameter with a constant"
),
};
ty::EarlyBinder::bind(Const::from_anon_const(tcx, default_def_id))
ty::EarlyBinder::bind(Const::from_const_arg(tcx, default_ct, FeedConstTy::No))
}

View file

@ -86,7 +86,7 @@ pub use self::closure::{
CAPTURE_STRUCT_LOCAL,
};
pub use self::consts::{
Const, ConstInt, ConstKind, Expr, ExprKind, ScalarInt, UnevaluatedConst, ValTree,
Const, ConstInt, ConstKind, Expr, ExprKind, FeedConstTy, ScalarInt, UnevaluatedConst, ValTree,
};
pub use self::context::{
tls, CtxtInterners, CurrentGcx, DeducedParamAttrs, Feed, FreeRegionInfo, GlobalCtxt, Lift,

View file

@ -607,7 +607,9 @@ impl<'a, V> ::std::ops::Index<HirId> for LocalTableInContext<'a, V> {
type Output = V;
fn index(&self, key: HirId) -> &V {
self.get(key).expect("LocalTableInContext: key not found")
self.get(key).unwrap_or_else(|| {
bug!("LocalTableInContext({:?}): key {:?} not found", self.hir_owner, key)
})
}
}