Skip to content

Commit 0b13deb

Browse files
authored
Rollup merge of #113661 - oli-obk:tait_wtf, r=lcnr
Double check that hidden types match the expected hidden type Fixes #113278 specifically, but I left a TODO for where we should also add some hardening. It feels a bit like papering over the issue, but at least this way we don't get unsoundness, but just surprising errors. Errors will be improved and given spans before this PR lands. r? `@compiler-errors` `@lcnr`
2 parents a6bf68d + df4bfd9 commit 0b13deb

File tree

8 files changed

+260
-12
lines changed

8 files changed

+260
-12
lines changed

compiler/rustc_borrowck/src/region_infer/opaque_types.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,8 @@ fn check_opaque_type_well_formed<'tcx>(
339339
// version.
340340
let errors = ocx.select_all_or_error();
341341

342-
// This is still required for many(half of the tests in ui/type-alias-impl-trait)
343-
// tests to pass
342+
// This is fishy, but we check it again in `check_opaque_meets_bounds`.
343+
// Remove once we can prepopulate with known hidden types.
344344
let _ = infcx.take_opaque_types();
345345

346346
if errors.is_empty() {

compiler/rustc_const_eval/src/util/compare_types.rs

+8-3
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,13 @@ pub fn is_subtype<'tcx>(
5656
// With `Reveal::All`, opaque types get normalized away, with `Reveal::UserFacing`
5757
// we would get unification errors because we're unable to look into opaque types,
5858
// even if they're constrained in our current function.
59-
//
60-
// It seems very unlikely that this hides any bugs.
61-
let _ = infcx.take_opaque_types();
59+
for (key, ty) in infcx.take_opaque_types() {
60+
span_bug!(
61+
ty.hidden_type.span,
62+
"{}, {}",
63+
tcx.type_of(key.def_id).instantiate(tcx, key.args),
64+
ty.hidden_type.ty
65+
);
66+
}
6267
errors.is_empty()
6368
}

compiler/rustc_hir_analysis/src/check/check.rs

+175-4
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ use rustc_lint_defs::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS;
1919
use rustc_middle::hir::nested_filter;
2020
use rustc_middle::middle::stability::EvalResult;
2121
use rustc_middle::traits::DefiningAnchor;
22+
use rustc_middle::ty::fold::BottomUpFolder;
2223
use rustc_middle::ty::layout::{LayoutError, MAX_SIMD_LANES};
2324
use rustc_middle::ty::util::{Discr, IntTypeExt};
2425
use rustc_middle::ty::GenericArgKind;
2526
use rustc_middle::ty::{
26-
self, AdtDef, ParamEnv, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
27+
self, AdtDef, ParamEnv, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
28+
TypeVisitableExt,
2729
};
2830
use rustc_session::lint::builtin::{UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS};
2931
use rustc_span::symbol::sym;
@@ -34,6 +36,7 @@ use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplem
3436
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
3537
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
3638
use rustc_trait_selection::traits::{self, ObligationCtxt, TraitEngine, TraitEngineExt as _};
39+
use rustc_type_ir::fold::TypeFoldable;
3740

3841
use std::ops::ControlFlow;
3942

@@ -437,7 +440,7 @@ fn check_opaque_meets_bounds<'tcx>(
437440
// hidden type is well formed even without those bounds.
438441
let predicate =
439442
ty::Binder::dummy(ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(hidden_ty.into())));
440-
ocx.register_obligation(Obligation::new(tcx, misc_cause, param_env, predicate));
443+
ocx.register_obligation(Obligation::new(tcx, misc_cause.clone(), param_env, predicate));
441444

442445
// Check that all obligations are satisfied by the implementation's
443446
// version.
@@ -464,11 +467,179 @@ fn check_opaque_meets_bounds<'tcx>(
464467
ocx.resolve_regions_and_report_errors(defining_use_anchor, &outlives_env)?;
465468
}
466469
}
467-
// Clean up after ourselves
468-
let _ = infcx.take_opaque_types();
470+
// Check that any hidden types found during wf checking match the hidden types that `type_of` sees.
471+
for (key, mut ty) in infcx.take_opaque_types() {
472+
ty.hidden_type.ty = infcx.resolve_vars_if_possible(ty.hidden_type.ty);
473+
sanity_check_found_hidden_type(tcx, key, ty.hidden_type, defining_use_anchor, origin)?;
474+
}
469475
Ok(())
470476
}
471477

478+
fn sanity_check_found_hidden_type<'tcx>(
479+
tcx: TyCtxt<'tcx>,
480+
key: ty::OpaqueTypeKey<'tcx>,
481+
mut ty: ty::OpaqueHiddenType<'tcx>,
482+
defining_use_anchor: LocalDefId,
483+
origin: &hir::OpaqueTyOrigin,
484+
) -> Result<(), ErrorGuaranteed> {
485+
if ty.ty.is_ty_var() {
486+
// Nothing was actually constrained.
487+
return Ok(());
488+
}
489+
if let ty::Alias(ty::Opaque, alias) = ty.ty.kind() {
490+
if alias.def_id == key.def_id.to_def_id() && alias.args == key.args {
491+
// Nothing was actually constrained, this is an opaque usage that was
492+
// only discovered to be opaque after inference vars resolved.
493+
return Ok(());
494+
}
495+
}
496+
// Closures frequently end up containing erased lifetimes in their final representation.
497+
// These correspond to lifetime variables that never got resolved, so we patch this up here.
498+
ty.ty = ty.ty.fold_with(&mut BottomUpFolder {
499+
tcx,
500+
ty_op: |t| t,
501+
ct_op: |c| c,
502+
lt_op: |l| match l.kind() {
503+
RegionKind::ReVar(_) => tcx.lifetimes.re_erased,
504+
_ => l,
505+
},
506+
});
507+
// Get the hidden type.
508+
let mut hidden_ty = tcx.type_of(key.def_id).instantiate(tcx, key.args);
509+
if let hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) = origin {
510+
if hidden_ty != ty.ty {
511+
hidden_ty = find_and_apply_rpit_args(
512+
tcx,
513+
hidden_ty,
514+
defining_use_anchor.to_def_id(),
515+
key.def_id.to_def_id(),
516+
)?;
517+
}
518+
}
519+
520+
// If the hidden types differ, emit a type mismatch diagnostic.
521+
if hidden_ty == ty.ty {
522+
Ok(())
523+
} else {
524+
let span = tcx.def_span(key.def_id);
525+
let other = ty::OpaqueHiddenType { ty: hidden_ty, span };
526+
Err(ty.report_mismatch(&other, key.def_id, tcx).emit())
527+
}
528+
}
529+
530+
/// In case it is in a nested opaque type, find that opaque type's
531+
/// usage in the function signature and use the generic arguments from the usage site.
532+
/// We need to do because RPITs ignore the lifetimes of the function,
533+
/// as they have their own copies of all the lifetimes they capture.
534+
/// So the only way to get the lifetimes represented in terms of the function,
535+
/// is to look how they are used in the function signature (or do some other fancy
536+
/// recording of this mapping at ast -> hir lowering time).
537+
///
538+
/// As an example:
539+
/// ```text
540+
/// trait Id {
541+
/// type Assoc;
542+
/// }
543+
/// impl<'a> Id for &'a () {
544+
/// type Assoc = &'a ();
545+
/// }
546+
/// fn func<'a>(x: &'a ()) -> impl Id<Assoc = impl Sized + 'a> { x }
547+
/// // desugared to
548+
/// fn func<'a>(x: &'a () -> Outer<'a> where <Outer<'a> as Id>::Assoc = Inner<'a> {
549+
/// // Note that in contrast to other nested items, RPIT type aliases can
550+
/// // access their parents' generics.
551+
///
552+
/// // hidden type is `&'aDupOuter ()`
553+
/// // During wfcheck the hidden type of `Inner<'aDupOuter>` is `&'a ()`, but
554+
/// // `typeof(Inner<'aDupOuter>) = &'aDupOuter ()`.
555+
/// // So we walk the signature of `func` to find the use of `Inner<'a>`
556+
/// // and then use that to replace the lifetimes in the hidden type, obtaining
557+
/// // `&'a ()`.
558+
/// type Outer<'aDupOuter> = impl Id<Assoc = Inner<'aDupOuter>>;
559+
///
560+
/// // hidden type is `&'aDupInner ()`
561+
/// type Inner<'aDupInner> = impl Sized + 'aDupInner;
562+
///
563+
/// x
564+
/// }
565+
/// ```
566+
fn find_and_apply_rpit_args<'tcx>(
567+
tcx: TyCtxt<'tcx>,
568+
mut hidden_ty: Ty<'tcx>,
569+
function: DefId,
570+
opaque: DefId,
571+
) -> Result<Ty<'tcx>, ErrorGuaranteed> {
572+
// Find use of the RPIT in the function signature and thus find the right args to
573+
// convert it into the parameter space of the function signature. This is needed,
574+
// because that's what `type_of` returns, against which we compare later.
575+
let ret = tcx.fn_sig(function).instantiate_identity().output();
576+
struct Visitor<'tcx> {
577+
tcx: TyCtxt<'tcx>,
578+
opaque: DefId,
579+
function: DefId,
580+
seen: FxHashSet<DefId>,
581+
}
582+
impl<'tcx> ty::TypeVisitor<TyCtxt<'tcx>> for Visitor<'tcx> {
583+
type BreakTy = GenericArgsRef<'tcx>;
584+
585+
#[instrument(level = "trace", skip(self), ret)]
586+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
587+
trace!("{:#?}", t.kind());
588+
match t.kind() {
589+
ty::Alias(ty::Opaque, alias) => {
590+
trace!(?alias.def_id);
591+
if alias.def_id == self.opaque {
592+
return ControlFlow::Break(alias.args);
593+
} else if self.seen.insert(alias.def_id) {
594+
for clause in self
595+
.tcx
596+
.explicit_item_bounds(alias.def_id)
597+
.iter_instantiated_copied(self.tcx, alias.args)
598+
{
599+
trace!(?clause);
600+
clause.visit_with(self)?;
601+
}
602+
}
603+
}
604+
ty::Alias(ty::Projection, alias) => {
605+
if self.tcx.is_impl_trait_in_trait(alias.def_id)
606+
&& self.tcx.impl_trait_in_trait_parent_fn(alias.def_id) == self.function
607+
{
608+
// If we're lowering to associated item, install the opaque type which is just
609+
// the `type_of` of the trait's associated item. If we're using the old lowering
610+
// strategy, then just reinterpret the associated type like an opaque :^)
611+
self.tcx
612+
.type_of(alias.def_id)
613+
.instantiate(self.tcx, alias.args)
614+
.visit_with(self)?;
615+
}
616+
}
617+
ty::Alias(ty::Weak, alias) => {
618+
self.tcx
619+
.type_of(alias.def_id)
620+
.instantiate(self.tcx, alias.args)
621+
.visit_with(self)?;
622+
}
623+
_ => (),
624+
}
625+
626+
t.super_visit_with(self)
627+
}
628+
}
629+
if let ControlFlow::Break(args) =
630+
ret.visit_with(&mut Visitor { tcx, function, opaque, seen: Default::default() })
631+
{
632+
trace!(?args);
633+
trace!("expected: {hidden_ty:#?}");
634+
hidden_ty = ty::EarlyBinder::bind(hidden_ty).instantiate(tcx, args);
635+
trace!("expected: {hidden_ty:#?}");
636+
} else {
637+
tcx.sess
638+
.delay_span_bug(tcx.def_span(function), format!("{ret:?} does not contain {opaque:?}"));
639+
}
640+
Ok(hidden_ty)
641+
}
642+
472643
fn is_enum_of_nonnullable_ptr<'tcx>(
473644
tcx: TyCtxt<'tcx>,
474645
adt_def: AdtDef<'tcx>,

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
272272
// assertions against dropping an `InferCtxt` without taking opaques.
273273
// FIXME: Once we remove support for the old impl we can remove this.
274274
if input.anchor != DefiningAnchor::Error {
275+
// This seems ok, but fragile.
275276
let _ = infcx.take_opaque_types();
276277
}
277278

src/tools/tidy/src/ui_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::path::{Path, PathBuf};
1010

1111
const ENTRY_LIMIT: usize = 900;
1212
// FIXME: The following limits should be reduced eventually.
13-
const ISSUES_ENTRY_LIMIT: usize = 1894;
13+
const ISSUES_ENTRY_LIMIT: usize = 1893;
1414
const ROOT_ENTRY_LIMIT: usize = 870;
1515

1616
const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! This test checks that we don't lose hidden types
2+
//! for *other* opaque types that we register and use
3+
//! to prove bounds while checking that a hidden type
4+
//! satisfies its opaque type's bounds.
5+
6+
#![feature(trivial_bounds, type_alias_impl_trait)]
7+
#![allow(trivial_bounds)]
8+
9+
mod sus {
10+
use super::*;
11+
pub type Sep = impl Sized + std::fmt::Display;
12+
//~^ ERROR: concrete type differs from previous defining opaque type use
13+
pub fn mk_sep() -> Sep {
14+
String::from("hello")
15+
}
16+
17+
pub trait Proj {
18+
type Assoc;
19+
}
20+
impl Proj for () {
21+
type Assoc = sus::Sep;
22+
}
23+
24+
pub struct Bar<T: Proj> {
25+
pub inner: <T as Proj>::Assoc,
26+
pub _marker: T,
27+
}
28+
impl<T: Proj> Clone for Bar<T> {
29+
fn clone(&self) -> Self {
30+
todo!()
31+
}
32+
}
33+
impl<T: Proj<Assoc = i32> + Copy> Copy for Bar<T> {}
34+
// This allows producing `Tait`s via `From`, even though
35+
// `define_tait` is not actually callable, and thus assumed
36+
// `Bar<()>: Copy` even though it isn't.
37+
pub type Tait = impl Copy + From<Bar<()>> + Into<Bar<()>>;
38+
pub fn define_tait() -> Tait
39+
where
40+
// this proves `Bar<()>: Copy`, but `define_tait` is
41+
// now uncallable
42+
(): Proj<Assoc = i32>,
43+
{
44+
Bar { inner: 1i32, _marker: () }
45+
}
46+
}
47+
48+
fn copy_tait(x: sus::Tait) -> (sus::Tait, sus::Tait) {
49+
(x, x)
50+
}
51+
52+
fn main() {
53+
let bar = sus::Bar { inner: sus::mk_sep(), _marker: () };
54+
let (y, z) = copy_tait(bar.into()); // copy a string
55+
drop(y.into()); // drop one instance
56+
println!("{}", z.into().inner); // print the other
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: concrete type differs from previous defining opaque type use
2+
--> $DIR/hidden_type_mismatch.rs:11:20
3+
|
4+
LL | pub type Sep = impl Sized + std::fmt::Display;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, got `String`
6+
|
7+
note: previous use here
8+
--> $DIR/hidden_type_mismatch.rs:37:21
9+
|
10+
LL | pub type Tait = impl Copy + From<Bar<()>> + Into<Bar<()>>;
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
13+
error: aborting due to previous error
14+

tests/ui/issues/issue-83190.rs renamed to tests/ui/type-alias-impl-trait/nested-rpit-with-lifetimes.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// check-pass
2-
31
// Regression test for issue #83190, triggering an ICE in borrowck.
42

3+
// check-pass
4+
55
pub trait Any {}
66
impl<T> Any for T {}
77

0 commit comments

Comments
 (0)