Auto merge of #107940 - BoxyUwU:const_ty_assertion_use_semantic_equality, r=compiler-errors
use semantic equality for const param type equality assertion Fixes #107898 See added test for what caused this ICE --- The current in assertion in `relate.rs` is rather inadequate when keeping in mind future expansions to const generics: - it will ICE when there are infer vars in a projection in a const param ty - it will spurriously return false when either ty has infer vars because of using `==` instead of `infcx.at(..).eq` - i am also unsure if it would be possible with `adt_const_params` to craft a situation where the const param type is not wf causing `normalize_erasing_regions` to `bug!` when we would have emitted a diagnostic. This impl feels pretty Not Great to me although i am not sure what a better idea would be. - We have to have the logic behind a query because neither `relate.rs` or `combine.rs` have access to trait solving machinery (without evaluating nested obligations this assert will become _far_ less useful under lazy norm, which consts are already doing) - `relate.rs` does not have access to canonicalization machinery which is necessary in order to have types potentially containing infer vars in query arguments. We could possible add a method to `TypeRelation` to do this assertion rather than a query but to avoid implementing the same logic over and over we'd probably end up with the logic in a free function somewhere in `rustc_trait_selection` _anyway_ so I don't think that would be much better. We could also just remove this assertion, it should not actually be necessary for it to be present. It has caught some bugs in the past though so if possible I would like to keep it. r? `@compiler-errors`
This commit is contained in:
commit
068161ea48
@ -31,8 +31,10 @@
|
||||
use crate::traits::{Obligation, PredicateObligations};
|
||||
use rustc_data_structures::sso::SsoHashMap;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_middle::infer::canonical::OriginalQueryValues;
|
||||
use rustc_middle::infer::unify_key::{ConstVarValue, ConstVariableValue};
|
||||
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
|
||||
use rustc_middle::traits::query::NoSolution;
|
||||
use rustc_middle::traits::ObligationCause;
|
||||
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
||||
use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
|
||||
@ -152,6 +154,34 @@ pub fn super_combine_consts<R>(
|
||||
let a = self.shallow_resolve(a);
|
||||
let b = self.shallow_resolve(b);
|
||||
|
||||
// We should never have to relate the `ty` field on `Const` as it is checked elsewhere that consts have the
|
||||
// correct type for the generic param they are an argument for. However there have been a number of cases
|
||||
// historically where asserting that the types are equal has found bugs in the compiler so this is valuable
|
||||
// to check even if it is a bit nasty impl wise :(
|
||||
//
|
||||
// This probe is probably not strictly necessary but it seems better to be safe and not accidentally find
|
||||
// ourselves with a check to find bugs being required for code to compile because it made inference progress.
|
||||
self.probe(|_| {
|
||||
if a.ty() == b.ty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't have access to trait solving machinery in `rustc_infer` so the logic for determining if the
|
||||
// two const param's types are able to be equal has to go through a canonical query with the actual logic
|
||||
// in `rustc_trait_selection`.
|
||||
let canonical = self.canonicalize_query(
|
||||
(relation.param_env(), a.ty(), b.ty()),
|
||||
&mut OriginalQueryValues::default(),
|
||||
);
|
||||
|
||||
if let Err(NoSolution) = self.tcx.check_tys_might_be_eq(canonical) {
|
||||
self.tcx.sess.delay_span_bug(
|
||||
DUMMY_SP,
|
||||
&format!("cannot relate consts of different types (a={:?}, b={:?})", a, b,),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
match (a.kind(), b.kind()) {
|
||||
(
|
||||
ty::ConstKind::Infer(InferConst::Var(a_vid)),
|
||||
|
@ -2173,4 +2173,11 @@
|
||||
desc { "traits in scope for documentation links for a module" }
|
||||
separate_provide_extern
|
||||
}
|
||||
|
||||
/// Used in `super_combine_consts` to ICE if the type of the two consts are definitely not going to end up being
|
||||
/// equal to eachother. This might return `Ok` even if the types are unequal, but will never return `Err` if
|
||||
/// the types might be equal.
|
||||
query check_tys_might_be_eq(arg: Canonical<'tcx, (ty::ParamEnv<'tcx>, Ty<'tcx>, Ty<'tcx>)>) -> Result<(), NoSolution> {
|
||||
desc { "check whether two const param are definitely not equal to eachother"}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@
|
||||
use crate::ty::{GenericArg, GenericArgKind, SubstsRef};
|
||||
use rustc_hir as ast;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_target::spec::abi;
|
||||
use std::iter;
|
||||
|
||||
@ -594,25 +593,6 @@ pub fn super_relate_consts<'tcx, R: TypeRelation<'tcx>>(
|
||||
debug!("{}.super_relate_consts(a = {:?}, b = {:?})", relation.tag(), a, b);
|
||||
let tcx = relation.tcx();
|
||||
|
||||
let a_ty;
|
||||
let b_ty;
|
||||
if relation.tcx().features().adt_const_params {
|
||||
a_ty = tcx.normalize_erasing_regions(relation.param_env(), a.ty());
|
||||
b_ty = tcx.normalize_erasing_regions(relation.param_env(), b.ty());
|
||||
} else {
|
||||
a_ty = tcx.erase_regions(a.ty());
|
||||
b_ty = tcx.erase_regions(b.ty());
|
||||
}
|
||||
if a_ty != b_ty {
|
||||
relation.tcx().sess.delay_span_bug(
|
||||
DUMMY_SP,
|
||||
&format!(
|
||||
"cannot relate constants ({:?}, {:?}) of different types: {} != {}",
|
||||
a, b, a_ty, b_ty
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// HACK(const_generics): We still need to eagerly evaluate consts when
|
||||
// relating them because during `normalize_param_env_or_error`,
|
||||
// we may relate an evaluated constant in a obligation against
|
||||
|
@ -1,12 +1,15 @@
|
||||
//! Miscellaneous type-system utilities that are too small to deserve their own modules.
|
||||
|
||||
use crate::traits::{self, ObligationCause};
|
||||
use crate::traits::{self, ObligationCause, ObligationCtxt};
|
||||
|
||||
use rustc_data_structures::fx::FxIndexSet;
|
||||
use rustc_hir as hir;
|
||||
use rustc_infer::infer::canonical::Canonical;
|
||||
use rustc_infer::infer::{RegionResolutionError, TyCtxtInferExt};
|
||||
use rustc_infer::traits::query::NoSolution;
|
||||
use rustc_infer::{infer::outlives::env::OutlivesEnvironment, traits::FulfillmentError};
|
||||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
|
||||
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt, TypeVisitable};
|
||||
use rustc_span::DUMMY_SP;
|
||||
|
||||
use super::outlives_bounds::InferCtxtExt;
|
||||
|
||||
@ -131,3 +134,19 @@ pub fn type_allowed_to_implement_copy<'tcx>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_tys_might_be_eq<'tcx>(
|
||||
tcx: TyCtxt<'tcx>,
|
||||
canonical: Canonical<'tcx, (ParamEnv<'tcx>, Ty<'tcx>, Ty<'tcx>)>,
|
||||
) -> Result<(), NoSolution> {
|
||||
let (infcx, (param_env, ty_a, ty_b), _) =
|
||||
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical);
|
||||
let ocx = ObligationCtxt::new(&infcx);
|
||||
|
||||
let result = ocx.eq(&ObligationCause::dummy(), param_env, ty_a, ty_b);
|
||||
// use `select_where_possible` instead of `select_all_or_error` so that
|
||||
// we don't get errors from obligations being ambiguous.
|
||||
let errors = ocx.select_where_possible();
|
||||
|
||||
if errors.len() > 0 || result.is_err() { Err(NoSolution) } else { Ok(()) }
|
||||
}
|
||||
|
@ -554,6 +554,7 @@ pub fn provide(providers: &mut ty::query::Providers) {
|
||||
specialization_graph_of: specialize::specialization_graph_provider,
|
||||
specializes: specialize::specializes,
|
||||
subst_and_check_impossible_predicates,
|
||||
check_tys_might_be_eq: misc::check_tys_might_be_eq,
|
||||
is_impossible_method,
|
||||
..*providers
|
||||
};
|
||||
|
@ -0,0 +1,30 @@
|
||||
// check-pass
|
||||
#![feature(generic_const_exprs)]
|
||||
#![allow(incomplete_features)]
|
||||
|
||||
// issue #107899
|
||||
// We end up relating `Const(ty: size_of<?0>, kind: Value(Branch([])))` with
|
||||
// `Const(ty: size_of<T>, kind: Value(Branch([])))` which if you were to `==`
|
||||
// the `ty` fields would return `false` and ICE. This test checks that we use
|
||||
// actual semantic equality that takes into account aliases and infer vars.
|
||||
|
||||
use std::mem::size_of;
|
||||
|
||||
trait X<T> {
|
||||
fn f(self);
|
||||
fn g(self);
|
||||
}
|
||||
|
||||
struct Y;
|
||||
|
||||
impl<T> X<T> for Y
|
||||
where
|
||||
[(); size_of::<T>()]: Sized,
|
||||
{
|
||||
fn f(self) {
|
||||
self.g();
|
||||
}
|
||||
fn g(self) {}
|
||||
}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,151 @@
|
||||
// check-pass
|
||||
#![feature(inline_const, generic_const_exprs)]
|
||||
#![allow(incomplete_features)]
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct Equal<const T: usize, const R: usize>();
|
||||
pub trait True {}
|
||||
impl<const T: usize> True for Equal<T, T> {}
|
||||
|
||||
// replacement for generativity
|
||||
pub struct Id<'id>(PhantomData<fn(&'id ()) -> &'id ()>);
|
||||
pub struct Guard<'id>(Id<'id>);
|
||||
fn make_guard<'id>(i: &'id Id<'id>) -> Guard<'id> {
|
||||
Guard(Id(PhantomData))
|
||||
}
|
||||
|
||||
impl<'id> Into<Id<'id>> for Guard<'id> {
|
||||
fn into(self) -> Id<'id> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Arena<'life> {
|
||||
bytes: *mut [u8],
|
||||
//bitmap: RefCell<RoaringBitmap>,
|
||||
_token: PhantomData<Id<'life>>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Item<'life, T> {
|
||||
data: T,
|
||||
_phantom: PhantomData<Id<'life>>,
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Token<'life, 'borrow, 'compact, 'reborrow, T>
|
||||
where
|
||||
'life: 'reborrow,
|
||||
T: Tokenize<'life, 'borrow, 'compact, 'reborrow>,
|
||||
{
|
||||
//ptr: *mut <T as Tokenize>::Tokenized,
|
||||
ptr: core::ptr::NonNull<T::Tokenized>,
|
||||
_phantom: PhantomData<Id<'life>>,
|
||||
_compact: PhantomData<&'borrow Guard<'compact>>,
|
||||
_result: PhantomData<&'reborrow T::Untokenized>,
|
||||
}
|
||||
|
||||
impl<'life> Arena<'life> {
|
||||
pub fn tokenize<'before, 'compact, 'borrow, 'reborrow, T, U>(
|
||||
&self,
|
||||
guard: &'borrow Guard<'compact>,
|
||||
item: Item<'life, &'before mut T>,
|
||||
) -> Token<'life, 'borrow, 'compact, 'reborrow, U>
|
||||
where
|
||||
T: Tokenize<'life, 'borrow, 'compact, 'reborrow, Untokenized = U>,
|
||||
T::Untokenized: Tokenize<'life, 'borrow, 'compact, 'reborrow>,
|
||||
Equal<{ core::mem::size_of::<T>() }, { core::mem::size_of::<U>() }>: True,
|
||||
'compact: 'borrow,
|
||||
'life: 'reborrow,
|
||||
'life: 'compact,
|
||||
'life: 'borrow,
|
||||
// 'borrow: 'before ??
|
||||
{
|
||||
let dst = item.data as *mut T as *mut T::Tokenized;
|
||||
Token {
|
||||
ptr: core::ptr::NonNull::new(dst as *mut _).unwrap(),
|
||||
_phantom: PhantomData,
|
||||
_compact: PhantomData,
|
||||
_result: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Tokenize<'life, 'borrow, 'compact, 'reborrow>
|
||||
where
|
||||
'compact: 'borrow,
|
||||
'life: 'reborrow,
|
||||
'life: 'borrow,
|
||||
'life: 'compact,
|
||||
{
|
||||
type Tokenized;
|
||||
type Untokenized;
|
||||
const TO: fn(&Arena<'life>, &'borrow Guard<'compact>, Self) -> Self::Tokenized;
|
||||
const FROM: fn(&'reborrow Arena<'life>, Self::Tokenized) -> Self::Untokenized;
|
||||
}
|
||||
|
||||
macro_rules! tokenize {
|
||||
($to:expr, $from:expr) => {
|
||||
const TO: fn(&Arena<'life>, &'borrow Guard<'compact>, Self) -> Self::Tokenized = $to;
|
||||
const FROM: fn(&'reborrow Arena<'life>, Self::Tokenized) -> Self::Untokenized = $from;
|
||||
};
|
||||
}
|
||||
|
||||
struct Foo<'life, 'borrow>(Option<Item<'life, &'borrow mut Bar>>);
|
||||
struct TokenFoo<'life, 'borrow, 'compact, 'reborrow>(
|
||||
Option<Token<'life, 'borrow, 'compact, 'reborrow, Bar>>,
|
||||
);
|
||||
struct Bar(u8);
|
||||
|
||||
impl<'life, 'before, 'borrow, 'compact, 'reborrow> Tokenize<'life, 'borrow, 'compact, 'reborrow>
|
||||
for Foo<'life, 'before>
|
||||
where
|
||||
'compact: 'borrow,
|
||||
'life: 'reborrow,
|
||||
'life: 'borrow,
|
||||
'life: 'compact,
|
||||
{
|
||||
type Tokenized = TokenFoo<'life, 'borrow, 'compact, 'reborrow>;
|
||||
type Untokenized = Foo<'life, 'reborrow>;
|
||||
tokenize!(foo_to, foo_from);
|
||||
}
|
||||
|
||||
impl<'life, 'borrow, 'compact, 'reborrow> Tokenize<'life, 'borrow, 'compact, 'reborrow> for Bar
|
||||
where
|
||||
'compact: 'borrow,
|
||||
'life: 'reborrow,
|
||||
'life: 'borrow,
|
||||
'life: 'compact,
|
||||
{
|
||||
type Tokenized = Bar;
|
||||
type Untokenized = Bar;
|
||||
tokenize!(bar_to, bar_from);
|
||||
}
|
||||
|
||||
fn bar_to<'life, 'borrow, 'compact>(
|
||||
arena: &Arena<'life>,
|
||||
guard: &'borrow Guard<'compact>,
|
||||
s: Bar,
|
||||
) -> Bar {
|
||||
s
|
||||
}
|
||||
fn bar_from<'life, 'reborrow>(arena: &'reborrow Arena<'life>, s: Bar) -> Bar {
|
||||
s
|
||||
}
|
||||
|
||||
fn foo_to<'life, 'borrow, 'compact, 'reborrow, 'before>(
|
||||
arena: &'before Arena<'life>,
|
||||
guard: &'borrow Guard<'compact>,
|
||||
s: Foo<'life, 'before>,
|
||||
) -> TokenFoo<'life, 'borrow, 'compact, 'reborrow> {
|
||||
let Foo(bar) = s;
|
||||
TokenFoo(bar.map(|bar| arena.tokenize(guard, bar)))
|
||||
}
|
||||
fn foo_from<'life, 'borrow, 'compact, 'reborrow>(
|
||||
arena: &'reborrow Arena<'life>,
|
||||
s: TokenFoo<'life, 'borrow, 'compact, 'reborrow>,
|
||||
) -> Foo<'life, 'reborrow> {
|
||||
Foo(s.0.map(|bar| panic!()))
|
||||
}
|
||||
|
||||
fn main() {}
|
Loading…
Reference in New Issue
Block a user