detect additional uses of opaques after writeback

This commit is contained in:
lcnr
2025-05-04 17:19:54 +00:00
parent fc0ef54fd9
commit e7979eab89
12 changed files with 143 additions and 70 deletions

View File

@@ -32,6 +32,7 @@ mod gather_locals;
mod intrinsicck; mod intrinsicck;
mod method; mod method;
mod op; mod op;
mod opaque_types;
mod pat; mod pat;
mod place_op; mod place_op;
mod rvalue_scopes; mod rvalue_scopes;
@@ -245,9 +246,7 @@ fn typeck_with_inspect<'tcx>(
let typeck_results = fcx.resolve_type_vars_in_body(body); let typeck_results = fcx.resolve_type_vars_in_body(body);
// We clone the defined opaque types during writeback in the new solver fcx.detect_opaque_types_added_during_writeback();
// because we have to use them during normalization.
let _ = fcx.infcx.take_opaque_types();
// Consistency check our TypeckResults instance can hold all ItemLocalIds // Consistency check our TypeckResults instance can hold all ItemLocalIds
// it will need to hold. // it will need to hold.

View File

@@ -0,0 +1,26 @@
use super::FnCtxt;
impl<'tcx> FnCtxt<'_, 'tcx> {
/// We may in theory add further uses of an opaque after cloning the opaque
/// types storage during writeback when computing the defining uses.
///
/// Silently ignoring them is dangerous and could result in ICE or even in
/// unsoundness, so we make sure we catch such cases here. There's currently
/// no known code where this actually happens, even with the new solver which
/// does normalize types in writeback after cloning the opaque type storage.
///
/// FIXME(@lcnr): I believe this should be possible in theory and would like
/// an actual test here. After playing around with this for an hour, I wasn't
/// able to do anything which didn't already try to normalize the opaque before
/// then, either allowing compilation to succeed or causing an ambiguity error.
pub(super) fn detect_opaque_types_added_during_writeback(&self) {
let num_entries = self.checked_opaque_types_storage_entries.take().unwrap();
for (key, hidden_type) in
self.inner.borrow_mut().opaque_types().opaque_types_added_since(num_entries)
{
let opaque_type_string = self.tcx.def_path_str(key.def_id);
let msg = format!("unexpected cyclic definition of `{opaque_type_string}`");
self.dcx().span_delayed_bug(hidden_type.span, msg);
}
let _ = self.take_opaque_types();
}
}

View File

@@ -1,10 +1,10 @@
use std::cell::RefCell; use std::cell::{Cell, RefCell};
use std::ops::Deref; use std::ops::Deref;
use rustc_data_structures::unord::{UnordMap, UnordSet}; use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::LocalDefId;
use rustc_hir::{self as hir, HirId, HirIdMap, LangItem}; use rustc_hir::{self as hir, HirId, HirIdMap, LangItem};
use rustc_infer::infer::{InferCtxt, InferOk, TyCtxtInferExt}; use rustc_infer::infer::{InferCtxt, InferOk, OpaqueTypeStorageEntries, TyCtxtInferExt};
use rustc_middle::span_bug; use rustc_middle::span_bug;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypingMode}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, TypingMode};
use rustc_span::Span; use rustc_span::Span;
@@ -37,6 +37,11 @@ pub(crate) struct TypeckRootCtxt<'tcx> {
pub(super) fulfillment_cx: RefCell<Box<dyn TraitEngine<'tcx, FulfillmentError<'tcx>>>>, pub(super) fulfillment_cx: RefCell<Box<dyn TraitEngine<'tcx, FulfillmentError<'tcx>>>>,
// Used to detect opaque types uses added after we've already checked them.
//
// See [FnCtxt::detect_opaque_types_added_during_writeback] for more details.
pub(super) checked_opaque_types_storage_entries: Cell<Option<OpaqueTypeStorageEntries>>,
/// Some additional `Sized` obligations badly affect type inference. /// Some additional `Sized` obligations badly affect type inference.
/// These obligations are added in a later stage of typeck. /// These obligations are added in a later stage of typeck.
/// Removing these may also cause additional complications, see #101066. /// Removing these may also cause additional complications, see #101066.
@@ -85,12 +90,14 @@ impl<'tcx> TypeckRootCtxt<'tcx> {
let infcx = let infcx =
tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id)); tcx.infer_ctxt().ignoring_regions().build(TypingMode::typeck_for_body(tcx, def_id));
let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner)); let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
let fulfillment_cx = RefCell::new(<dyn TraitEngine<'_, _>>::new(&infcx));
TypeckRootCtxt { TypeckRootCtxt {
typeck_results,
fulfillment_cx: RefCell::new(<dyn TraitEngine<'_, _>>::new(&infcx)),
infcx, infcx,
typeck_results,
locals: RefCell::new(Default::default()), locals: RefCell::new(Default::default()),
fulfillment_cx,
checked_opaque_types_storage_entries: Cell::new(None),
deferred_sized_obligations: RefCell::new(Vec::new()), deferred_sized_obligations: RefCell::new(Vec::new()),
deferred_call_resolutions: RefCell::new(Default::default()), deferred_call_resolutions: RefCell::new(Default::default()),
deferred_cast_checks: RefCell::new(Vec::new()), deferred_cast_checks: RefCell::new(Vec::new()),

View File

@@ -535,13 +535,10 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
let tcx = self.tcx(); let tcx = self.tcx();
// We clone the opaques instead of stealing them here as they are still used for // We clone the opaques instead of stealing them here as they are still used for
// normalization in the next generation trait solver. // normalization in the next generation trait solver.
//
// FIXME(-Znext-solver): Opaque types defined after this would simply get dropped
// at the end of typeck. While this seems unlikely to happen in practice this
// should still get fixed. Either by preventing writeback from defining new opaque
// types or by using this function at the end of writeback and running it as a
// fixpoint.
let opaque_types = self.fcx.infcx.clone_opaque_types(); let opaque_types = self.fcx.infcx.clone_opaque_types();
let num_entries = self.fcx.inner.borrow_mut().opaque_types().num_entries();
let prev = self.fcx.checked_opaque_types_storage_entries.replace(Some(num_entries));
debug_assert_eq!(prev, None);
for (opaque_type_key, hidden_type) in opaque_types { for (opaque_type_key, hidden_type) in opaque_types {
let hidden_type = self.resolve(hidden_type, &hidden_type.span); let hidden_type = self.resolve(hidden_type, &hidden_type.span);
let opaque_type_key = self.resolve(opaque_type_key, &hidden_type.span); let opaque_type_key = self.resolve(opaque_type_key, &hidden_type.span);

View File

@@ -6,7 +6,10 @@ use rustc_middle::ty::relate::combine::PredicateEmittingRelation;
use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span};
use super::{BoundRegionConversionTime, InferCtxt, RegionVariableOrigin, SubregionOrigin}; use super::{
BoundRegionConversionTime, InferCtxt, OpaqueTypeStorageEntries, RegionVariableOrigin,
SubregionOrigin,
};
impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
type Interner = TyCtxt<'tcx>; type Interner = TyCtxt<'tcx>;
@@ -214,6 +217,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
self.register_region_obligation_with_cause(ty, r, &ObligationCause::dummy_with_span(span)); self.register_region_obligation_with_cause(ty, r, &ObligationCause::dummy_with_span(span));
} }
type OpaqueTypeStorageEntries = OpaqueTypeStorageEntries;
fn opaque_types_storage_num_entries(&self) -> OpaqueTypeStorageEntries {
self.inner.borrow_mut().opaque_types().num_entries()
}
fn clone_opaque_types_lookup_table(&self) -> Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)> { fn clone_opaque_types_lookup_table(&self) -> Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)> {
self.inner.borrow_mut().opaque_types().iter_lookup_table().map(|(k, h)| (k, h.ty)).collect() self.inner.borrow_mut().opaque_types().iter_lookup_table().map(|(k, h)| (k, h.ty)).collect()
} }
@@ -225,6 +232,17 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> {
.map(|(k, h)| (k, h.ty)) .map(|(k, h)| (k, h.ty))
.collect() .collect()
} }
fn clone_opaque_types_added_since(
&self,
prev_entries: OpaqueTypeStorageEntries,
) -> Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)> {
self.inner
.borrow_mut()
.opaque_types()
.opaque_types_added_since(prev_entries)
.map(|(k, h)| (k, h.ty))
.collect()
}
fn register_hidden_type_in_storage( fn register_hidden_type_in_storage(
&self, &self,

View File

@@ -9,7 +9,7 @@ use free_regions::RegionRelations;
pub use freshen::TypeFreshener; pub use freshen::TypeFreshener;
use lexical_region_resolve::LexicalRegionResolutions; use lexical_region_resolve::LexicalRegionResolutions;
pub use lexical_region_resolve::RegionResolutionError; pub use lexical_region_resolve::RegionResolutionError;
use opaque_types::OpaqueTypeStorage; pub use opaque_types::{OpaqueTypeStorage, OpaqueTypeStorageEntries, OpaqueTypeTable};
use region_constraints::{ use region_constraints::{
GenericKind, RegionConstraintCollector, RegionConstraintStorage, VarInfos, VerifyBound, GenericKind, RegionConstraintCollector, RegionConstraintStorage, VarInfos, VerifyBound,
}; };

View File

@@ -18,7 +18,7 @@ use crate::traits::{self, Obligation, PredicateObligations};
mod table; mod table;
pub(crate) use table::{OpaqueTypeStorage, OpaqueTypeTable}; pub use table::{OpaqueTypeStorage, OpaqueTypeStorageEntries, OpaqueTypeTable};
impl<'tcx> InferCtxt<'tcx> { impl<'tcx> InferCtxt<'tcx> {
/// This is a backwards compatibility hack to prevent breaking changes from /// This is a backwards compatibility hack to prevent breaking changes from

View File

@@ -14,6 +14,16 @@ pub struct OpaqueTypeStorage<'tcx> {
duplicate_entries: Vec<(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)>, duplicate_entries: Vec<(OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)>,
} }
/// The number of entries in the opaque type storage at a given point.
///
/// Used to check that we haven't added any new opaque types after checking
/// the opaque types currently in the storage.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpaqueTypeStorageEntries {
opaque_types: usize,
duplicate_entries: usize,
}
impl<'tcx> OpaqueTypeStorage<'tcx> { impl<'tcx> OpaqueTypeStorage<'tcx> {
#[instrument(level = "debug")] #[instrument(level = "debug")]
pub(crate) fn remove( pub(crate) fn remove(
@@ -49,6 +59,24 @@ impl<'tcx> OpaqueTypeStorage<'tcx> {
std::mem::take(opaque_types).into_iter().chain(std::mem::take(duplicate_entries)) std::mem::take(opaque_types).into_iter().chain(std::mem::take(duplicate_entries))
} }
pub fn num_entries(&self) -> OpaqueTypeStorageEntries {
OpaqueTypeStorageEntries {
opaque_types: self.opaque_types.len(),
duplicate_entries: self.duplicate_entries.len(),
}
}
pub fn opaque_types_added_since(
&self,
prev_entries: OpaqueTypeStorageEntries,
) -> impl Iterator<Item = (OpaqueTypeKey<'tcx>, OpaqueHiddenType<'tcx>)> {
self.opaque_types
.iter()
.skip(prev_entries.opaque_types)
.map(|(k, v)| (*k, *v))
.chain(self.duplicate_entries.iter().skip(prev_entries.duplicate_entries).copied())
}
/// Only returns the opaque types from the lookup table. These are used /// Only returns the opaque types from the lookup table. These are used
/// when normalizing opaque types and have a unique key. /// when normalizing opaque types and have a unique key.
/// ///

View File

@@ -250,13 +250,7 @@ where
// to the `var_values`. // to the `var_values`.
let opaque_types = self let opaque_types = self
.delegate .delegate
.clone_opaque_types_lookup_table() .clone_opaque_types_added_since(self.initial_opaque_types_storage_num_entries);
.into_iter()
.filter(|(a, _)| {
self.predefined_opaques_in_body.opaque_types.iter().all(|(pa, _)| pa != a)
})
.chain(self.delegate.clone_duplicate_opaque_types())
.collect();
ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals } ExternalConstraintsData { region_constraints, opaque_types, normalization_nested_goals }
} }

View File

@@ -23,8 +23,7 @@ use crate::solve::inspect::{self, ProofTreeBuilder};
use crate::solve::search_graph::SearchGraph; use crate::solve::search_graph::SearchGraph;
use crate::solve::{ use crate::solve::{
CanonicalInput, Certainty, FIXPOINT_STEP_LIMIT, Goal, GoalEvaluationKind, GoalSource, CanonicalInput, Certainty, FIXPOINT_STEP_LIMIT, Goal, GoalEvaluationKind, GoalSource,
HasChanged, NestedNormalizationGoals, NoSolution, PredefinedOpaquesData, QueryInput, HasChanged, NestedNormalizationGoals, NoSolution, QueryInput, QueryResult,
QueryResult,
}; };
pub(super) mod canonical; pub(super) mod canonical;
@@ -99,8 +98,6 @@ where
current_goal_kind: CurrentGoalKind, current_goal_kind: CurrentGoalKind,
pub(super) var_values: CanonicalVarValues<I>, pub(super) var_values: CanonicalVarValues<I>,
predefined_opaques_in_body: I::PredefinedOpaques,
/// The highest universe index nameable by the caller. /// The highest universe index nameable by the caller.
/// ///
/// When we enter a new binder inside of the query we create new universes /// When we enter a new binder inside of the query we create new universes
@@ -111,6 +108,10 @@ where
/// if we have a coinductive cycle and because that's the only way we can return /// if we have a coinductive cycle and because that's the only way we can return
/// new placeholders to the caller. /// new placeholders to the caller.
pub(super) max_input_universe: ty::UniverseIndex, pub(super) max_input_universe: ty::UniverseIndex,
/// The opaque types from the canonical input. We only need to return opaque types
/// which have been added to the storage while evaluating this goal.
pub(super) initial_opaque_types_storage_num_entries:
<D::Infcx as InferCtxtLike>::OpaqueTypeStorageEntries,
pub(super) search_graph: &'a mut SearchGraph<D>, pub(super) search_graph: &'a mut SearchGraph<D>,
@@ -305,10 +306,8 @@ where
// Only relevant when canonicalizing the response, // Only relevant when canonicalizing the response,
// which we don't do within this evaluation context. // which we don't do within this evaluation context.
predefined_opaques_in_body: delegate
.cx()
.mk_predefined_opaques_in_body(PredefinedOpaquesData::default()),
max_input_universe: ty::UniverseIndex::ROOT, max_input_universe: ty::UniverseIndex::ROOT,
initial_opaque_types_storage_num_entries: Default::default(),
variables: Default::default(), variables: Default::default(),
var_values: CanonicalVarValues::dummy(), var_values: CanonicalVarValues::dummy(),
current_goal_kind: CurrentGoalKind::Misc, current_goal_kind: CurrentGoalKind::Misc,
@@ -342,25 +341,10 @@ where
canonical_goal_evaluation: &mut ProofTreeBuilder<D>, canonical_goal_evaluation: &mut ProofTreeBuilder<D>,
f: impl FnOnce(&mut EvalCtxt<'_, D>, Goal<I, I::Predicate>) -> R, f: impl FnOnce(&mut EvalCtxt<'_, D>, Goal<I, I::Predicate>) -> R,
) -> R { ) -> R {
let (ref delegate, input, var_values) = let (ref delegate, input, var_values) = D::build_with_canonical(cx, &canonical_input);
SolverDelegate::build_with_canonical(cx, &canonical_input);
let mut ecx = EvalCtxt {
delegate,
variables: canonical_input.canonical.variables,
var_values,
current_goal_kind: CurrentGoalKind::from_query_input(cx, input),
predefined_opaques_in_body: input.predefined_opaques_in_body,
max_input_universe: canonical_input.canonical.max_universe,
search_graph,
nested_goals: Default::default(),
origin_span: I::Span::dummy(),
tainted: Ok(()),
inspect: canonical_goal_evaluation.new_goal_evaluation_step(var_values),
};
for &(key, ty) in &input.predefined_opaques_in_body.opaque_types { for &(key, ty) in &input.predefined_opaques_in_body.opaque_types {
let prev = ecx.delegate.register_hidden_type_in_storage(key, ty, ecx.origin_span); let prev = delegate.register_hidden_type_in_storage(key, ty, I::Span::dummy());
// It may be possible that two entries in the opaque type storage end up // It may be possible that two entries in the opaque type storage end up
// with the same key after resolving contained inference variables. // with the same key after resolving contained inference variables.
// //
@@ -373,13 +357,24 @@ where
// the canonical input. This is more annoying to implement and may cause a // the canonical input. This is more annoying to implement and may cause a
// perf regression, so we do it inside of the query for now. // perf regression, so we do it inside of the query for now.
if let Some(prev) = prev { if let Some(prev) = prev {
debug!(?key, ?ty, ?prev, "ignore duplicate in `opaque_type_storage`"); debug!(?key, ?ty, ?prev, "ignore duplicate in `opaque_types_storage`");
} }
} }
if !ecx.nested_goals.is_empty() { let initial_opaque_types_storage_num_entries = delegate.opaque_types_storage_num_entries();
panic!("prepopulating opaque types shouldn't add goals: {:?}", ecx.nested_goals); let mut ecx = EvalCtxt {
} delegate,
variables: canonical_input.canonical.variables,
var_values,
current_goal_kind: CurrentGoalKind::from_query_input(cx, input),
max_input_universe: canonical_input.canonical.max_universe,
initial_opaque_types_storage_num_entries,
search_graph,
nested_goals: Default::default(),
origin_span: I::Span::dummy(),
tainted: Ok(()),
inspect: canonical_goal_evaluation.new_goal_evaluation_step(var_values),
};
let result = f(&mut ecx, input.goal); let result = f(&mut ecx, input.goal);
ecx.inspect.probe_final_state(ecx.delegate, ecx.max_input_universe); ecx.inspect.probe_final_state(ecx.delegate, ecx.max_input_universe);

View File

@@ -26,32 +26,33 @@ where
I: Interner, I: Interner,
{ {
pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T) -> T { pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T) -> T {
let ProbeCtxt { ecx: outer_ecx, probe_kind, _result } = self; let ProbeCtxt { ecx: outer, probe_kind, _result } = self;
let delegate = outer_ecx.delegate; let delegate = outer.delegate;
let max_input_universe = outer_ecx.max_input_universe; let max_input_universe = outer.max_input_universe;
let mut nested_ecx = EvalCtxt { let mut nested = EvalCtxt {
delegate, delegate,
variables: outer_ecx.variables, variables: outer.variables,
var_values: outer_ecx.var_values, var_values: outer.var_values,
current_goal_kind: outer_ecx.current_goal_kind, current_goal_kind: outer.current_goal_kind,
predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
max_input_universe, max_input_universe,
search_graph: outer_ecx.search_graph, initial_opaque_types_storage_num_entries: outer
nested_goals: outer_ecx.nested_goals.clone(), .initial_opaque_types_storage_num_entries,
origin_span: outer_ecx.origin_span, search_graph: outer.search_graph,
tainted: outer_ecx.tainted, nested_goals: outer.nested_goals.clone(),
inspect: outer_ecx.inspect.take_and_enter_probe(), origin_span: outer.origin_span,
tainted: outer.tainted,
inspect: outer.inspect.take_and_enter_probe(),
}; };
let r = nested_ecx.delegate.probe(|| { let r = nested.delegate.probe(|| {
let r = f(&mut nested_ecx); let r = f(&mut nested);
nested_ecx.inspect.probe_final_state(delegate, max_input_universe); nested.inspect.probe_final_state(delegate, max_input_universe);
r r
}); });
if !nested_ecx.inspect.is_noop() { if !nested.inspect.is_noop() {
let probe_kind = probe_kind(&r); let probe_kind = probe_kind(&r);
nested_ecx.inspect.probe_kind(probe_kind); nested.inspect.probe_kind(probe_kind);
outer_ecx.inspect = nested_ecx.inspect.finish_probe(); outer.inspect = nested.inspect.finish_probe();
} }
r r
} }

View File

@@ -1,3 +1,5 @@
use std::fmt::Debug;
use derive_where::derive_where; use derive_where::derive_where;
#[cfg(feature = "nightly")] #[cfg(feature = "nightly")]
use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext}; use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext};
@@ -246,12 +248,18 @@ pub trait InferCtxtLike: Sized {
span: <Self::Interner as Interner>::Span, span: <Self::Interner as Interner>::Span,
); );
type OpaqueTypeStorageEntries: Debug + Copy + Default;
fn opaque_types_storage_num_entries(&self) -> Self::OpaqueTypeStorageEntries;
fn clone_opaque_types_lookup_table( fn clone_opaque_types_lookup_table(
&self, &self,
) -> Vec<(ty::OpaqueTypeKey<Self::Interner>, <Self::Interner as Interner>::Ty)>; ) -> Vec<(ty::OpaqueTypeKey<Self::Interner>, <Self::Interner as Interner>::Ty)>;
fn clone_duplicate_opaque_types( fn clone_duplicate_opaque_types(
&self, &self,
) -> Vec<(ty::OpaqueTypeKey<Self::Interner>, <Self::Interner as Interner>::Ty)>; ) -> Vec<(ty::OpaqueTypeKey<Self::Interner>, <Self::Interner as Interner>::Ty)>;
fn clone_opaque_types_added_since(
&self,
prev_entries: Self::OpaqueTypeStorageEntries,
) -> Vec<(ty::OpaqueTypeKey<Self::Interner>, <Self::Interner as Interner>::Ty)>;
fn register_hidden_type_in_storage( fn register_hidden_type_in_storage(
&self, &self,