Skip to content

Commit

Permalink
Move Const::{from_anon_const,try_from_lit} to hir_ty_lowering
Browse files Browse the repository at this point in the history
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:
#131081 (comment)
  • Loading branch information
camelid committed Nov 29, 2024
1 parent d53f0b1 commit 0c762b6
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 163 deletions.
21 changes: 8 additions & 13 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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")
}
}
}
}

Expand Down
11 changes: 6 additions & 5 deletions compiler/rustc_hir_analysis/src/collect/dump.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -42,15 +42,16 @@ 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>,
anon_consts: Vec<LocalDefId>,
}

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()
Expand All @@ -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);
Expand Down
96 changes: 58 additions & 38 deletions compiler/rustc_hir_analysis/src/collect/predicates_of.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -369,61 +370,80 @@ 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));
}
}
}

let hir_id = tcx.local_def_id_to_hir_id(def_id);
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);
}

if let Some(generics) = node.generics() {
debug!("const_evaluatable_predicates_of({:?}): visit_generics", def_id);
collector.visit_generics(generics);
debug!("visit self_ty");
let self_ty = tcx.type_of(def_id);
self_ty.instantiate_identity().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);
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);
}
debug!("const_evaluatable_predicates_of({:?}) = {:?}", def_id, collector.preds);

collector.preds
}
Expand Down
88 changes: 87 additions & 1 deletion compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2088,7 +2088,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),
}
}

Expand Down Expand Up @@ -2175,6 +2175,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 {
Expand Down
Loading

0 comments on commit 0c762b6

Please sign in to comment.