Move Const::{from_anon_const,try_from_lit} to hir_ty_lowering

These operations are much more about lowering the HIR than about
`Const`s themselves. They fit better in hir_ty_lowering with
`lower_const_arg` (formerly `Const::from_const_arg`) and the rest.

To accomplish this, `const_evaluatable_predicates_of` had to be changed
to not use `from_anon_const` anymore. Instead of visiting the HIR and
lowering anon consts on the fly, it now visits the `rustc_middle::ty`
data structures instead and directly looks for `UnevaluatedConst`s. This
approach was proposed in:
https://github.com/rust-lang/rust/pull/131081#discussion_r1821189257
This commit is contained in:
Noah Lev 2024-11-28 22:04:03 -08:00 committed by Boxy
parent 3bff51ea91
commit 277e049d91
10 changed files with 244 additions and 167 deletions

View File

@ -13,6 +13,7 @@ use rustc_hir::lang_items::LangItem;
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
use rustc_infer::infer::{self, InferCtxt, TyCtxtInferExt};
use rustc_macros::LintDiagnostic;
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::query::Providers;
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_middle::ty::trait_def::TraitSpecializationKind;
@ -1170,19 +1171,13 @@ fn check_type_defn<'tcx>(
// Explicit `enum` discriminant values must const-evaluate successfully.
if let ty::VariantDiscr::Explicit(discr_def_id) = variant.discr {
let cause = traits::ObligationCause::new(
tcx.def_span(discr_def_id),
wfcx.body_def_id,
ObligationCauseCode::Misc,
);
wfcx.register_obligation(Obligation::new(
tcx,
cause,
wfcx.param_env,
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(
ty::Const::from_anon_const(tcx, discr_def_id.expect_local()),
))),
));
match tcx.const_eval_poly(discr_def_id) {
Ok(_) => {}
Err(ErrorHandled::Reported(..)) => {}
Err(ErrorHandled::TooGeneric(sp)) => {
span_bug!(sp, "enum variant discr was too generic to eval")
}
}
}
}

View File

@ -1,6 +1,6 @@
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
use rustc_hir::intravisit;
use rustc_middle::hir::nested_filter::OnlyBodies;
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::sym;
@ -42,7 +42,8 @@ pub(crate) fn predicates_and_item_bounds(tcx: TyCtxt<'_>) {
}
pub(crate) fn def_parents(tcx: TyCtxt<'_>) {
for did in tcx.hir().body_owners() {
for iid in tcx.hir().items() {
let did = iid.owner_id.def_id;
if tcx.has_attr(did, sym::rustc_dump_def_parents) {
struct AnonConstFinder<'tcx> {
tcx: TyCtxt<'tcx>,
@ -50,7 +51,7 @@ pub(crate) fn def_parents(tcx: TyCtxt<'_>) {
}
impl<'tcx> intravisit::Visitor<'tcx> for AnonConstFinder<'tcx> {
type NestedFilter = OnlyBodies;
type NestedFilter = nested_filter::All;
fn nested_visit_map(&mut self) -> Self::Map {
self.tcx.hir()
@ -62,11 +63,11 @@ pub(crate) fn def_parents(tcx: TyCtxt<'_>) {
}
}
// Look for any anon consts inside of this body owner as there is no way to apply
// Look for any anon consts inside of this item as there is no way to apply
// the `rustc_dump_def_parents` attribute to the anon const so it would not be possible
// to see what its def parent is.
let mut anon_ct_finder = AnonConstFinder { tcx, anon_consts: vec![] };
intravisit::walk_expr(&mut anon_ct_finder, tcx.hir().body_owned_by(did).value);
intravisit::walk_item(&mut anon_ct_finder, tcx.hir().item(iid));
for did in [did].into_iter().chain(anon_ct_finder.anon_consts) {
let span = tcx.def_span(did);

View File

@ -1,12 +1,13 @@
use std::assert_matches::assert_matches;
use hir::{HirId, Node};
use hir::Node;
use rustc_data_structures::fx::FxIndexSet;
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::{self, Visitor};
use rustc_middle::ty::{self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, Upcast};
use rustc_middle::ty::{
self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, TypeVisitable, TypeVisitor, Upcast,
};
use rustc_middle::{bug, span_bug};
use rustc_span::symbol::Ident;
use rustc_span::{DUMMY_SP, Span};
@ -305,7 +306,7 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
}
if tcx.features().generic_const_exprs() {
predicates.extend(const_evaluatable_predicates_of(tcx, def_id));
predicates.extend(const_evaluatable_predicates_of(tcx, def_id, &predicates));
}
let mut predicates: Vec<_> = predicates.into_iter().collect();
@ -369,32 +370,48 @@ fn compute_bidirectional_outlives_predicates<'tcx>(
}
}
fn const_evaluatable_predicates_of(
tcx: TyCtxt<'_>,
#[instrument(level = "debug", skip(tcx, predicates), ret)]
fn const_evaluatable_predicates_of<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
) -> FxIndexSet<(ty::Clause<'_>, Span)> {
predicates: &FxIndexSet<(ty::Clause<'tcx>, Span)>,
) -> FxIndexSet<(ty::Clause<'tcx>, Span)> {
struct ConstCollector<'tcx> {
tcx: TyCtxt<'tcx>,
preds: FxIndexSet<(ty::Clause<'tcx>, Span)>,
}
impl<'tcx> intravisit::Visitor<'tcx> for ConstCollector<'tcx> {
fn visit_anon_const(&mut self, c: &'tcx hir::AnonConst) {
let ct = ty::Const::from_anon_const(self.tcx, c.def_id);
if let ty::ConstKind::Unevaluated(_) = ct.kind() {
let span = self.tcx.def_span(c.def_id);
self.preds.insert((ty::ClauseKind::ConstEvaluatable(ct).upcast(self.tcx), span));
}
}
fn is_const_param_default(tcx: TyCtxt<'_>, def: LocalDefId) -> bool {
let hir_id = tcx.local_def_id_to_hir_id(def);
let (_, parent_node) = tcx
.hir()
.parent_iter(hir_id)
.skip_while(|(_, n)| matches!(n, Node::ConstArg(..)))
.next()
.unwrap();
matches!(
parent_node,
Node::GenericParam(hir::GenericParam { kind: hir::GenericParamKind::Const { .. }, .. })
)
}
fn visit_const_param_default(&mut self, _param: HirId, _ct: &'tcx hir::ConstArg<'tcx>) {
// Do not look into const param defaults,
// these get checked when they are actually instantiated.
//
// We do not want the following to error:
//
// struct Foo<const N: usize, const M: usize = { N + 1 }>;
// struct Bar<const N: usize>(Foo<N, 3>);
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ConstCollector<'tcx> {
fn visit_const(&mut self, c: ty::Const<'tcx>) {
if let ty::ConstKind::Unevaluated(uv) = c.kind() {
if is_const_param_default(self.tcx, uv.def.expect_local()) {
// Do not look into const param defaults,
// these get checked when they are actually instantiated.
//
// We do not want the following to error:
//
// struct Foo<const N: usize, const M: usize = { N + 1 }>;
// struct Bar<const N: usize>(Foo<N, 3>);
return;
}
let span = self.tcx.def_span(uv.def);
self.preds.insert((ty::ClauseKind::ConstEvaluatable(c).upcast(self.tcx), span));
}
}
}
@ -402,29 +419,32 @@ fn const_evaluatable_predicates_of(
let node = tcx.hir_node(hir_id);
let mut collector = ConstCollector { tcx, preds: FxIndexSet::default() };
for (clause, _sp) in predicates {
clause.visit_with(&mut collector);
}
if let hir::Node::Item(item) = node
&& let hir::ItemKind::Impl(impl_) = item.kind
&& let hir::ItemKind::Impl(_) = item.kind
{
if let Some(of_trait) = &impl_.of_trait {
debug!("const_evaluatable_predicates_of({:?}): visit impl trait_ref", def_id);
collector.visit_trait_ref(of_trait);
if let Some(of_trait) = tcx.impl_trait_ref(def_id) {
debug!("visit impl trait_ref");
of_trait.instantiate_identity().visit_with(&mut collector);
}
debug!("const_evaluatable_predicates_of({:?}): visit_self_ty", def_id);
collector.visit_ty(impl_.self_ty);
debug!("visit self_ty");
let self_ty = tcx.type_of(def_id);
self_ty.instantiate_identity().visit_with(&mut collector);
}
if let Some(generics) = node.generics() {
debug!("const_evaluatable_predicates_of({:?}): visit_generics", def_id);
collector.visit_generics(generics);
if let Some(_) = tcx.hir().fn_sig_by_hir_id(hir_id) {
debug!("visit fn sig");
let fn_sig = tcx.fn_sig(def_id);
let fn_sig = fn_sig.instantiate_identity();
debug!(?fn_sig);
fn_sig.visit_with(&mut collector);
}
if let Some(fn_sig) = tcx.hir().fn_sig_by_hir_id(hir_id) {
debug!("const_evaluatable_predicates_of({:?}): visit_fn_decl", def_id);
collector.visit_fn_decl(fn_sig.decl);
}
debug!("const_evaluatable_predicates_of({:?}) = {:?}", def_id, collector.preds);
collector.preds
}

View File

@ -2089,7 +2089,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
qpath.span(),
format!("Const::lower_const_arg: invalid qpath {qpath:?}"),
),
hir::ConstArgKind::Anon(anon) => Const::from_anon_const(tcx, anon.def_id),
hir::ConstArgKind::Anon(anon) => self.lower_anon_const(anon.def_id),
hir::ConstArgKind::Infer(span) => self.ct_infer(None, span),
}
}
@ -2177,6 +2177,92 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
}
}
/// Literals and const generic parameters are eagerly converted to a constant, everything else
/// becomes `Unevaluated`.
#[instrument(skip(self), level = "debug")]
fn lower_anon_const(&self, def: LocalDefId) -> Const<'tcx> {
let tcx = self.tcx();
let body_id = match tcx.hir_node_by_def_id(def) {
hir::Node::AnonConst(ac) => ac.body,
node => span_bug!(
tcx.def_span(def.to_def_id()),
"from_anon_const can only process anonymous constants, not {node:?}"
),
};
let expr = &tcx.hir().body(body_id).value;
debug!(?expr);
let ty = tcx.type_of(def).no_bound_vars().expect("const parameter types cannot be generic");
match self.try_lower_anon_const_lit(ty, expr) {
Some(v) => v,
None => ty::Const::new_unevaluated(tcx, ty::UnevaluatedConst {
def: def.to_def_id(),
args: ty::GenericArgs::identity_for_item(tcx, def.to_def_id()),
}),
}
}
#[instrument(skip(self), level = "debug")]
fn try_lower_anon_const_lit(
&self,
ty: Ty<'tcx>,
expr: &'tcx hir::Expr<'tcx>,
) -> Option<Const<'tcx>> {
let tcx = self.tcx();
// 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 {
hir::ExprKind::Block(block, _) if block.stmts.is_empty() && block.expr.is_some() => {
block.expr.as_ref().unwrap()
}
_ => expr,
};
if let hir::ExprKind::Path(hir::QPath::Resolved(
_,
&hir::Path { res: Res::Def(DefKind::ConstParam, _), .. },
)) = expr.kind
{
span_bug!(
expr.span,
"try_lower_anon_const_lit: received const param which shouldn't be possible"
);
};
let lit_input = match expr.kind {
hir::ExprKind::Lit(lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }),
hir::ExprKind::Unary(hir::UnOp::Neg, expr) => match expr.kind {
hir::ExprKind::Lit(lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: true }),
_ => None,
},
_ => None,
};
if let Some(lit_input) = lit_input {
// If an error occurred, ignore that it's a literal and leave reporting the error up to
// mir.
match tcx.at(expr.span).lit_to_const(lit_input) {
Ok(c) => return Some(c),
Err(_) if lit_input.ty.has_aliases() => {
// allow the `ty` to be an alias type, though we cannot handle it here
return None;
}
Err(e) => {
tcx.dcx().span_delayed_bug(
expr.span,
format!("try_lower_anon_const_lit: couldn't lit_to_const {e:?}"),
);
}
}
}
None
}
fn lower_delegation_ty(&self, idx: hir::InferDelegationKind) -> Ty<'tcx> {
let delegation_sig = self.tcx().inherit_sig_for_delegation_item(self.item_def_id());
match idx {

View File

@ -2,15 +2,11 @@ use std::borrow::Cow;
use rustc_data_structures::intern::Interned;
use rustc_error_messages::MultiSpan;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::{self as hir};
use rustc_macros::HashStable;
use rustc_type_ir::{self as ir, TypeFlags, WithCachedTypeInfo};
use tracing::{debug, instrument};
use crate::mir::interpret::{LitToConstInput, Scalar};
use crate::ty::{self, GenericArgs, Ty, TyCtxt, TypeVisitableExt};
use crate::mir::interpret::Scalar;
use crate::ty::{self, Ty, TyCtxt};
mod int;
mod kind;
@ -181,82 +177,6 @@ impl<'tcx> rustc_type_ir::inherent::Const<TyCtxt<'tcx>> for Const<'tcx> {
}
impl<'tcx> Const<'tcx> {
// FIXME: move this and try_from_lit to hir_ty_lowering like lower_const_arg/from_const_arg
/// 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,
node => span_bug!(
tcx.def_span(def.to_def_id()),
"from_anon_const can only process anonymous constants, not {node:?}"
),
};
let expr = &tcx.hir().body(body_id).value;
debug!(?expr);
let ty = tcx.type_of(def).no_bound_vars().expect("const parameter types cannot be generic");
match Self::try_from_lit(tcx, ty, expr) {
Some(v) => v,
None => ty::Const::new_unevaluated(tcx, ty::UnevaluatedConst {
def: def.to_def_id(),
args: GenericArgs::identity_for_item(tcx, def.to_def_id()),
}),
}
}
#[instrument(skip(tcx), level = "debug")]
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 {
hir::ExprKind::Block(block, _) if block.stmts.is_empty() && block.expr.is_some() => {
block.expr.as_ref().unwrap()
}
_ => expr,
};
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");
};
let lit_input = match expr.kind {
hir::ExprKind::Lit(lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }),
hir::ExprKind::Unary(hir::UnOp::Neg, expr) => match expr.kind {
hir::ExprKind::Lit(lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: true }),
_ => None,
},
_ => None,
};
if let Some(lit_input) = lit_input {
// If an error occurred, ignore that it's a literal and leave reporting the error up to
// mir.
match tcx.at(expr.span).lit_to_const(lit_input) {
Ok(c) => return Some(c),
Err(_) if lit_input.ty.has_aliases() => {
// allow the `ty` to be an alias type, though we cannot handle it here
return None;
}
Err(e) => {
tcx.dcx().span_delayed_bug(
expr.span,
format!("Const::try_from_lit: couldn't lit_to_const {e:?}"),
);
}
}
}
None
}
/// Creates a constant with the given integer value and interns it.
#[inline]
pub fn from_bits(

View File

@ -1,13 +1,10 @@
//@ known-bug: #121429
#![feature(generic_const_exprs)]
pub trait True {}
impl<const N: usize = { const { 3 } }> PartialEq<FixedI8<FRAC_RHS>> for FixedI8<FRAC_LHS> where
If<{}>: True
{
}
#![feature(generic_const_exprs)]
struct FixedI8<const X: usize>;
const FRAC_LHS: usize = 0;
const FRAC_RHS: usize = 1;
pub trait True {}

View File

@ -3,16 +3,17 @@
fn bar() {
fn foo() {
#[rustc_dump_def_parents]
fn baz() {
#[rustc_dump_def_parents]
//~^ ERROR: rustc_dump_def_parents: DefId
|| {
//~^ ERROR: rustc_dump_def_parents: DefId
qux::<
{
//~^ ERROR: rustc_dump_def_parents: DefId
fn inhibits_dump() {
qux::<
{
//~^ ERROR: rustc_dump_def_parents: DefId
"hi";
1
},

View File

@ -1,14 +1,9 @@
error: rustc_dump_def_parents: DefId(..)
--> $DIR/dump_def_parents.rs:8:13
|
LL | || {
| ^^
|
note: DefId(..)
--> $DIR/dump_def_parents.rs:6:9
--> $DIR/dump_def_parents.rs:7:9
|
LL | fn baz() {
| ^^^^^^^^
|
note: DefId(..)
--> $DIR/dump_def_parents.rs:5:5
|
@ -44,12 +39,12 @@ LL | | },
| |_____________________^
|
note: DefId(..)
--> $DIR/dump_def_parents.rs:8:13
--> $DIR/dump_def_parents.rs:9:13
|
LL | || {
| ^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:6:9
--> $DIR/dump_def_parents.rs:7:9
|
LL | fn baz() {
| ^^^^^^^^
@ -76,7 +71,65 @@ LL | | fn main() {}
| |____________^
error: rustc_dump_def_parents: DefId(..)
--> $DIR/dump_def_parents.rs:22:31
--> $DIR/dump_def_parents.rs:15:33
|
LL | / ... {
LL | | ...
LL | | ... "hi";
LL | | ... 1
LL | | ... },
| |_______________________^
|
note: DefId(..)
--> $DIR/dump_def_parents.rs:13:25
|
LL | fn inhibits_dump() {
| ^^^^^^^^^^^^^^^^^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:11:21
|
LL | / {
LL | |
LL | | fn inhibits_dump() {
LL | | qux::<
... |
LL | | 1
LL | | },
| |_____________________^
note: DefId(..)
--> $DIR/dump_def_parents.rs:9:13
|
LL | || {
| ^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:7:9
|
LL | fn baz() {
| ^^^^^^^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:5:5
|
LL | fn foo() {
| ^^^^^^^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:4:1
|
LL | fn bar() {
| ^^^^^^^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:2:1
|
LL | / #![feature(rustc_attrs)]
LL | |
LL | | fn bar() {
LL | | fn foo() {
... |
LL | |
LL | | fn main() {}
| |____________^
error: rustc_dump_def_parents: DefId(..)
--> $DIR/dump_def_parents.rs:23:31
|
LL | qux::<{ 1 + 1 }>();
| ^^^^^^^^^
@ -93,12 +146,12 @@ LL | | 1
LL | | },
| |_____________________^
note: DefId(..)
--> $DIR/dump_def_parents.rs:8:13
--> $DIR/dump_def_parents.rs:9:13
|
LL | || {
| ^^
note: DefId(..)
--> $DIR/dump_def_parents.rs:6:9
--> $DIR/dump_def_parents.rs:7:9
|
LL | fn baz() {
| ^^^^^^^^
@ -124,5 +177,5 @@ LL | |
LL | | fn main() {}
| |____________^
error: aborting due to 3 previous errors
error: aborting due to 4 previous errors

View File

@ -1,7 +1,11 @@
//@ known-bug: rust-lang/rust#128176
//@ check-pass
// Regression test for #128176.
#![feature(generic_const_exprs)]
#![feature(dyn_compatible_for_dispatch)]
#![allow(incomplete_features)]
trait X {
type Y<const N: i16>;
}

View File

@ -10,11 +10,11 @@ note: ...which requires computing candidate for `<LazyUpdim<'_, T, <T as TensorD
LL | trait TensorDimension {
| ^^^^^^^^^^^^^^^^^^^^^
= note: ...which again requires resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`, completing the cycle
note: cycle used when computing candidate for `<LazyUpdim<'_, T, { T::DIM }, DIM> as TensorDimension>`
--> $DIR/issue-83765.rs:4:1
note: cycle used when checking assoc item `<impl at $DIR/issue-83765.rs:50:1: 50:94>::size` is compatible with trait definition
--> $DIR/issue-83765.rs:51:5
|
LL | trait TensorDimension {
| ^^^^^^^^^^^^^^^^^^^^^
LL | fn size(&self) -> [usize; DIM] {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
error[E0391]: cycle detected when resolving instance `<LazyUpdim<'_, T, <T as TensorDimension>::DIM, DIM> as TensorDimension>::DIM`