Skip to content

Commit 00876c6

Browse files
authored
Rollup merge of #104411 - lcnr:bivariance-nll, r=compiler-errors
nll: correctly deal with bivariance fixes #104409 when in a bivariant context, relating stuff should always trivially succeed. Also changes the mir validator to correctly deal with higher ranked regions. r? types cc ``@RalfJung``
2 parents aeeac5d + b2e6d08 commit 00876c6

File tree

10 files changed

+130
-85
lines changed

10 files changed

+130
-85
lines changed

compiler/rustc_const_eval/src/interpret/eval_context.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use super::{
2323
MemPlaceMeta, Memory, MemoryKind, Operand, Place, PlaceTy, PointerArithmetic, Provenance,
2424
Scalar, StackPopJump,
2525
};
26-
use crate::transform::validate::equal_up_to_regions;
26+
use crate::util;
2727

2828
pub struct InterpCx<'mir, 'tcx, M: Machine<'mir, 'tcx>> {
2929
/// Stores the `Machine` instance.
@@ -354,8 +354,8 @@ pub(super) fn mir_assign_valid_types<'tcx>(
354354
// Type-changing assignments can happen when subtyping is used. While
355355
// all normal lifetimes are erased, higher-ranked types with their
356356
// late-bound lifetimes are still around and can lead to type
357-
// differences. So we compare ignoring lifetimes.
358-
if equal_up_to_regions(tcx, param_env, src.ty, dest.ty) {
357+
// differences.
358+
if util::is_subtype(tcx, param_env, src.ty, dest.ty) {
359359
// Make sure the layout is equal, too -- just to be safe. Miri really
360360
// needs layout equality. For performance reason we skip this check when
361361
// the types are equal. Equal types *can* have different layouts when

compiler/rustc_const_eval/src/transform/validate.rs

+2-57
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
33
use rustc_data_structures::fx::FxHashSet;
44
use rustc_index::bit_set::BitSet;
5-
use rustc_infer::infer::TyCtxtInferExt;
65
use rustc_middle::mir::interpret::Scalar;
76
use rustc_middle::mir::visit::NonUseContext::VarDebugInfo;
87
use rustc_middle::mir::visit::{PlaceContext, Visitor};
@@ -12,8 +11,7 @@ use rustc_middle::mir::{
1211
ProjectionElem, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, Terminator,
1312
TerminatorKind, UnOp, START_BLOCK,
1413
};
15-
use rustc_middle::ty::fold::BottomUpFolder;
16-
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeFoldable, TypeVisitable};
14+
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitable};
1715
use rustc_mir_dataflow::impls::MaybeStorageLive;
1816
use rustc_mir_dataflow::storage::always_storage_live_locals;
1917
use rustc_mir_dataflow::{Analysis, ResultsCursor};
@@ -70,44 +68,6 @@ impl<'tcx> MirPass<'tcx> for Validator {
7068
}
7169
}
7270

73-
/// Returns whether the two types are equal up to lifetimes.
74-
/// All lifetimes, including higher-ranked ones, get ignored for this comparison.
75-
/// (This is unlike the `erasing_regions` methods, which keep higher-ranked lifetimes for soundness reasons.)
76-
///
77-
/// The point of this function is to approximate "equal up to subtyping". However,
78-
/// the approximation is incorrect as variance is ignored.
79-
pub fn equal_up_to_regions<'tcx>(
80-
tcx: TyCtxt<'tcx>,
81-
param_env: ParamEnv<'tcx>,
82-
src: Ty<'tcx>,
83-
dest: Ty<'tcx>,
84-
) -> bool {
85-
// Fast path.
86-
if src == dest {
87-
return true;
88-
}
89-
90-
// Normalize lifetimes away on both sides, then compare.
91-
let normalize = |ty: Ty<'tcx>| {
92-
tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty).fold_with(
93-
&mut BottomUpFolder {
94-
tcx,
95-
// FIXME: We erase all late-bound lifetimes, but this is not fully correct.
96-
// If you have a type like `<for<'a> fn(&'a u32) as SomeTrait>::Assoc`,
97-
// this is not necessarily equivalent to `<fn(&'static u32) as SomeTrait>::Assoc`,
98-
// since one may have an `impl SomeTrait for fn(&32)` and
99-
// `impl SomeTrait for fn(&'static u32)` at the same time which
100-
// specify distinct values for Assoc. (See also #56105)
101-
lt_op: |_| tcx.lifetimes.re_erased,
102-
// Leave consts and types unchanged.
103-
ct_op: |ct| ct,
104-
ty_op: |ty| ty,
105-
},
106-
)
107-
};
108-
tcx.infer_ctxt().build().can_eq(param_env, normalize(src), normalize(dest)).is_ok()
109-
}
110-
11171
struct TypeChecker<'a, 'tcx> {
11272
when: &'a str,
11373
body: &'a Body<'tcx>,
@@ -183,22 +143,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
183143
return true;
184144
}
185145

186-
// Normalize projections and things like that.
187-
// Type-changing assignments can happen when subtyping is used. While
188-
// all normal lifetimes are erased, higher-ranked types with their
189-
// late-bound lifetimes are still around and can lead to type
190-
// differences. So we compare ignoring lifetimes.
191-
192-
// First, try with reveal_all. This might not work in some cases, as the predicates
193-
// can be cleared in reveal_all mode. We try the reveal first anyways as it is used
194-
// by some other passes like inlining as well.
195-
let param_env = self.param_env.with_reveal_all_normalized(self.tcx);
196-
if equal_up_to_regions(self.tcx, param_env, src, dest) {
197-
return true;
198-
}
199-
200-
// If this fails, we can try it without the reveal.
201-
equal_up_to_regions(self.tcx, self.param_env, src, dest)
146+
crate::util::is_subtype(self.tcx, self.param_env, src, dest)
202147
}
203148
}
204149

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//! Routines to check for relations between fully inferred types.
2+
//!
3+
//! FIXME: Move this to a more general place. The utility of this extends to
4+
//! other areas of the compiler as well.
5+
6+
use rustc_infer::infer::{DefiningAnchor, TyCtxtInferExt};
7+
use rustc_infer::traits::ObligationCause;
8+
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
9+
use rustc_trait_selection::traits::ObligationCtxt;
10+
11+
/// Returns whether the two types are equal up to subtyping.
12+
///
13+
/// This is used in case we don't know the expected subtyping direction
14+
/// and still want to check whether anything is broken.
15+
pub fn is_equal_up_to_subtyping<'tcx>(
16+
tcx: TyCtxt<'tcx>,
17+
param_env: ParamEnv<'tcx>,
18+
src: Ty<'tcx>,
19+
dest: Ty<'tcx>,
20+
) -> bool {
21+
// Fast path.
22+
if src == dest {
23+
return true;
24+
}
25+
26+
// Check for subtyping in either direction.
27+
is_subtype(tcx, param_env, src, dest) || is_subtype(tcx, param_env, dest, src)
28+
}
29+
30+
/// Returns whether `src` is a subtype of `dest`, i.e. `src <: dest`.
31+
///
32+
/// This mostly ignores opaque types as it can be used in constraining contexts
33+
/// while still computing the final underlying type.
34+
pub fn is_subtype<'tcx>(
35+
tcx: TyCtxt<'tcx>,
36+
param_env: ParamEnv<'tcx>,
37+
src: Ty<'tcx>,
38+
dest: Ty<'tcx>,
39+
) -> bool {
40+
if src == dest {
41+
return true;
42+
}
43+
44+
let mut builder =
45+
tcx.infer_ctxt().ignoring_regions().with_opaque_type_inference(DefiningAnchor::Bubble);
46+
let infcx = builder.build();
47+
let ocx = ObligationCtxt::new(&infcx);
48+
let cause = ObligationCause::dummy();
49+
let src = ocx.normalize(cause.clone(), param_env, src);
50+
let dest = ocx.normalize(cause.clone(), param_env, dest);
51+
match ocx.sub(&cause, param_env, src, dest) {
52+
Ok(()) => {}
53+
Err(_) => return false,
54+
};
55+
let errors = ocx.select_all_or_error();
56+
// With `Reveal::All`, opaque types get normalized away, with `Reveal::UserFacing`
57+
// we would get unification errors because we're unable to look into opaque types,
58+
// even if they're constrained in our current function.
59+
//
60+
// It seems very unlikely that this hides any bugs.
61+
let _ = infcx.inner.borrow_mut().opaque_type_storage.take_opaque_types();
62+
errors.is_empty()
63+
}

compiler/rustc_const_eval/src/util/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ pub mod aggregate;
22
mod alignment;
33
mod call_kind;
44
pub mod collect_writes;
5+
mod compare_types;
56
mod find_self_call;
67
mod might_permit_raw_init;
78
mod type_name;
89

910
pub use self::aggregate::expand_aggregate;
1011
pub use self::alignment::is_disaligned;
1112
pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind};
13+
pub use self::compare_types::{is_equal_up_to_subtyping, is_subtype};
1214
pub use self::find_self_call::find_self_call;
1315
pub use self::might_permit_raw_init::might_permit_raw_init;
1416
pub use self::type_name::type_name;

compiler/rustc_hir_analysis/src/check/check.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@ fn check_opaque_meets_bounds<'tcx>(
449449

450450
let misc_cause = traits::ObligationCause::misc(span, hir_id);
451451

452-
match infcx.at(&misc_cause, param_env).eq(opaque_ty, hidden_ty) {
453-
Ok(infer_ok) => ocx.register_infer_ok_obligations(infer_ok),
452+
match ocx.eq(&misc_cause, param_env, opaque_ty, hidden_ty) {
453+
Ok(()) => {}
454454
Err(ty_err) => {
455455
tcx.sess.delay_span_bug(
456456
span,

compiler/rustc_hir_analysis/src/check/compare_method.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,8 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
402402
unnormalized_trait_sig.inputs_and_output.iter().chain(trait_sig.inputs_and_output.iter()),
403403
);
404404

405-
match infcx.at(&cause, param_env).eq(trait_return_ty, impl_return_ty) {
406-
Ok(infer::InferOk { value: (), obligations }) => {
407-
ocx.register_obligations(obligations);
408-
}
405+
match ocx.eq(&cause, param_env, trait_return_ty, impl_return_ty) {
406+
Ok(()) => {}
409407
Err(terr) => {
410408
let mut diag = struct_span_err!(
411409
tcx.sess,
@@ -442,10 +440,8 @@ pub fn collect_trait_impl_trait_tys<'tcx>(
442440
// the lifetimes of the return type, but do this after unifying just the
443441
// return types, since we want to avoid duplicating errors from
444442
// `compare_predicate_entailment`.
445-
match infcx.at(&cause, param_env).eq(trait_fty, impl_fty) {
446-
Ok(infer::InferOk { value: (), obligations }) => {
447-
ocx.register_obligations(obligations);
448-
}
443+
match ocx.eq(&cause, param_env, trait_fty, impl_fty) {
444+
Ok(()) => {}
449445
Err(terr) => {
450446
// This function gets called during `compare_predicate_entailment` when normalizing a
451447
// signature that contains RPITIT. When the method signatures don't match, we have to

compiler/rustc_infer/src/infer/nll_relate/mod.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -556,8 +556,9 @@ where
556556
self.ambient_variance_info = self.ambient_variance_info.xform(info);
557557

558558
debug!(?self.ambient_variance);
559-
560-
let r = self.relate(a, b)?;
559+
// In a bivariant context this always succeeds.
560+
let r =
561+
if self.ambient_variance == ty::Variance::Bivariant { a } else { self.relate(a, b)? };
561562

562563
self.ambient_variance = old_ambient_variance;
563564

compiler/rustc_mir_transform/src/inline.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
//! Inlining pass for MIR functions
22
use crate::deref_separator::deref_finder;
33
use rustc_attr::InlineAttr;
4-
use rustc_const_eval::transform::validate::equal_up_to_regions;
54
use rustc_index::bit_set::BitSet;
65
use rustc_index::vec::Idx;
76
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
@@ -14,7 +13,8 @@ use rustc_span::{hygiene::ExpnKind, ExpnData, LocalExpnId, Span};
1413
use rustc_target::abi::VariantIdx;
1514
use rustc_target::spec::abi::Abi;
1615

17-
use super::simplify::{remove_dead_blocks, CfgSimplifier};
16+
use crate::simplify::{remove_dead_blocks, CfgSimplifier};
17+
use crate::util;
1818
use crate::MirPass;
1919
use std::iter;
2020
use std::ops::{Range, RangeFrom};
@@ -180,7 +180,7 @@ impl<'tcx> Inliner<'tcx> {
180180
let TerminatorKind::Call { args, destination, .. } = &terminator.kind else { bug!() };
181181
let destination_ty = destination.ty(&caller_body.local_decls, self.tcx).ty;
182182
let output_type = callee_body.return_ty();
183-
if !equal_up_to_regions(self.tcx, self.param_env, output_type, destination_ty) {
183+
if !util::is_subtype(self.tcx, self.param_env, output_type, destination_ty) {
184184
trace!(?output_type, ?destination_ty);
185185
return Err("failed to normalize return type");
186186
}
@@ -200,7 +200,7 @@ impl<'tcx> Inliner<'tcx> {
200200
arg_tuple_tys.iter().zip(callee_body.args_iter().skip(skipped_args))
201201
{
202202
let input_type = callee_body.local_decls[input].ty;
203-
if !equal_up_to_regions(self.tcx, self.param_env, arg_ty, input_type) {
203+
if !util::is_subtype(self.tcx, self.param_env, input_type, arg_ty) {
204204
trace!(?arg_ty, ?input_type);
205205
return Err("failed to normalize tuple argument type");
206206
}
@@ -209,7 +209,7 @@ impl<'tcx> Inliner<'tcx> {
209209
for (arg, input) in args.iter().zip(callee_body.args_iter()) {
210210
let input_type = callee_body.local_decls[input].ty;
211211
let arg_ty = arg.ty(&caller_body.local_decls, self.tcx);
212-
if !equal_up_to_regions(self.tcx, self.param_env, arg_ty, input_type) {
212+
if !util::is_subtype(self.tcx, self.param_env, input_type, arg_ty) {
213213
trace!(?arg_ty, ?input_type);
214214
return Err("failed to normalize argument type");
215215
}
@@ -847,7 +847,7 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> {
847847
let parent = Place { local, projection: self.tcx.intern_place_elems(proj_base) };
848848
let parent_ty = parent.ty(&self.callee_body.local_decls, self.tcx);
849849
let check_equal = |this: &mut Self, f_ty| {
850-
if !equal_up_to_regions(this.tcx, this.param_env, ty, f_ty) {
850+
if !util::is_equal_up_to_subtyping(this.tcx, this.param_env, ty, f_ty) {
851851
trace!(?ty, ?f_ty);
852852
this.validation = Err("failed to normalize projection type");
853853
return;

compiler/rustc_trait_selection/src/traits/engine.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,32 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> {
125125
.map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
126126
}
127127

128+
/// Checks whether `expected` is a subtype of `actual`: `expected <: actual`.
129+
pub fn sub<T: ToTrace<'tcx>>(
130+
&self,
131+
cause: &ObligationCause<'tcx>,
132+
param_env: ty::ParamEnv<'tcx>,
133+
expected: T,
134+
actual: T,
135+
) -> Result<(), TypeError<'tcx>> {
136+
self.infcx
137+
.at(cause, param_env)
138+
.sup(expected, actual)
139+
.map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
140+
}
141+
142+
/// Checks whether `expected` is a supertype of `actual`: `expected :> actual`.
128143
pub fn sup<T: ToTrace<'tcx>>(
129144
&self,
130145
cause: &ObligationCause<'tcx>,
131146
param_env: ty::ParamEnv<'tcx>,
132147
expected: T,
133148
actual: T,
134149
) -> Result<(), TypeError<'tcx>> {
135-
match self.infcx.at(cause, param_env).sup(expected, actual) {
136-
Ok(InferOk { obligations, value: () }) => {
137-
self.register_obligations(obligations);
138-
Ok(())
139-
}
140-
Err(e) => Err(e),
141-
}
150+
self.infcx
151+
.at(cause, param_env)
152+
.sup(expected, actual)
153+
.map(|infer_ok| self.register_infer_ok_obligations(infer_ok))
142154
}
143155

144156
pub fn select_where_possible(&self) -> Vec<FulfillmentError<'tcx>> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// check-pass
2+
// compile-flags: -Zvalidate-mir
3+
4+
// This test checks that bivariant parameters are handled correctly
5+
// in the mir.
6+
#![allow(coherence_leak_check)]
7+
trait Trait {
8+
type Assoc;
9+
}
10+
11+
struct Foo<T, U>(T)
12+
where
13+
T: Trait<Assoc = U>;
14+
15+
impl Trait for for<'a> fn(&'a ()) {
16+
type Assoc = u32;
17+
}
18+
impl Trait for fn(&'static ()) {
19+
type Assoc = String;
20+
}
21+
22+
fn foo(x: Foo<for<'a> fn(&'a ()), u32>) -> Foo<fn(&'static ()), String> {
23+
x
24+
}
25+
26+
fn main() {}

0 commit comments

Comments
 (0)