Skip to content

Commit 551abd6

Browse files
committed
Auto merge of #116891 - aliemjay:opaque-region-infer-rework-2, r=compiler-errors,oli-obk
rework opaque type region inference User-facing changes are documented in [this comment](#116891 (comment)). The design document is in [this comment](#116891 (comment)). --- \- Fix Ice in check_unique; ICE -> Error; fixes #122782. \- Ignore uncaptured lifetime args; ICE -> Pass; fixes #111906, fixes #110623, fixes #109059, fixes #122307 \- Except equal parameters from the uniqueness check; Pass -> Error; fixes #113916. \- Check RPITs for invalid args; Pass -> Error; fixes #111935; ICE -> Error; fixes #110726. \- Rework opaque types region inference; Pass -> Error; fixes #113971, fixes #112841. \- Reject external lifetimes as invalid args; Pass -> Error; fixes #105498. r? `@ghost`
2 parents d0e8cbb + 59c217f commit 551abd6

40 files changed

+879
-345
lines changed

compiler/rustc_borrowck/src/region_infer/mod.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,11 @@ pub struct RegionInferenceContext<'tcx> {
9595
/// visible from this index.
9696
scc_universes: IndexVec<ConstraintSccIndex, ty::UniverseIndex>,
9797

98-
/// Contains a "representative" from each SCC. This will be the
99-
/// minimal RegionVid belonging to that universe. It is used as a
100-
/// kind of hacky way to manage checking outlives relationships,
98+
/// Contains the "representative" region of each SCC.
99+
/// It is defined as the one with the minimal RegionVid, favoring
100+
/// free regions, then placeholders, then existential regions.
101+
///
102+
/// It is a hacky way to manage checking regions for equality,
101103
/// since we can 'canonicalize' each region to the representative
102104
/// of its SCC and be sure that -- if they have the same repr --
103105
/// they *must* be equal (though not having the same repr does not
@@ -481,22 +483,28 @@ impl<'tcx> RegionInferenceContext<'tcx> {
481483
scc_universes
482484
}
483485

484-
/// For each SCC, we compute a unique `RegionVid` (in fact, the
485-
/// minimal one that belongs to the SCC). See
486+
/// For each SCC, we compute a unique `RegionVid`. See the
486487
/// `scc_representatives` field of `RegionInferenceContext` for
487488
/// more details.
488489
fn compute_scc_representatives(
489490
constraints_scc: &Sccs<RegionVid, ConstraintSccIndex>,
490491
definitions: &IndexSlice<RegionVid, RegionDefinition<'tcx>>,
491492
) -> IndexVec<ConstraintSccIndex, ty::RegionVid> {
492493
let num_sccs = constraints_scc.num_sccs();
493-
let next_region_vid = definitions.next_index();
494-
let mut scc_representatives = IndexVec::from_elem_n(next_region_vid, num_sccs);
495-
496-
for region_vid in definitions.indices() {
497-
let scc = constraints_scc.scc(region_vid);
498-
let prev_min = scc_representatives[scc];
499-
scc_representatives[scc] = region_vid.min(prev_min);
494+
let mut scc_representatives = IndexVec::from_elem_n(RegionVid::MAX, num_sccs);
495+
496+
// Iterate over all RegionVids *in-order* and pick the least RegionVid as the
497+
// representative of its SCC. This naturally prefers free regions over others.
498+
for (vid, def) in definitions.iter_enumerated() {
499+
let repr = &mut scc_representatives[constraints_scc.scc(vid)];
500+
if *repr == ty::RegionVid::MAX {
501+
*repr = vid;
502+
} else if matches!(def.origin, NllRegionVariableOrigin::Placeholder(_))
503+
&& matches!(definitions[*repr].origin, NllRegionVariableOrigin::Existential { .. })
504+
{
505+
// Pick placeholders over existentials even if they have a greater RegionVid.
506+
*repr = vid;
507+
}
500508
}
501509

502510
scc_representatives

compiler/rustc_borrowck/src/region_infer/opaque_types.rs

+210-169
Large diffs are not rendered by default.

compiler/rustc_borrowck/src/type_check/free_region_relations.rs

+7
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ impl UniversalRegionRelations<'_> {
164164
self.outlives.contains(fr1, fr2)
165165
}
166166

167+
/// Returns `true` if fr1 is known to equal fr2.
168+
///
169+
/// This will only ever be true for universally quantified regions.
170+
pub(crate) fn equal(&self, fr1: RegionVid, fr2: RegionVid) -> bool {
171+
self.outlives.contains(fr1, fr2) && self.outlives.contains(fr2, fr1)
172+
}
173+
167174
/// Returns a vector of free regions `x` such that `fr1: x` is
168175
/// known to hold.
169176
pub(crate) fn regions_outlived_by(&self, fr1: RegionVid) -> Vec<RegionVid> {

compiler/rustc_borrowck/src/type_check/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@ pub(crate) fn type_check<'mir, 'tcx>(
229229
);
230230
}
231231

232+
// Convert all regions to nll vars.
233+
let (opaque_type_key, hidden_type) =
234+
infcx.tcx.fold_regions((opaque_type_key, hidden_type), |region, _| {
235+
match region.kind() {
236+
ty::ReVar(_) => region,
237+
ty::RePlaceholder(placeholder) => checker
238+
.borrowck_context
239+
.constraints
240+
.placeholder_region(infcx, placeholder),
241+
_ => ty::Region::new_var(
242+
infcx.tcx,
243+
checker.borrowck_context.universal_regions.to_region_vid(region),
244+
),
245+
}
246+
});
247+
232248
(opaque_type_key, hidden_type)
233249
})
234250
.collect();

compiler/rustc_middle/src/ty/mod.rs

+32
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,38 @@ pub struct OpaqueTypeKey<'tcx> {
833833
pub args: GenericArgsRef<'tcx>,
834834
}
835835

836+
impl<'tcx> OpaqueTypeKey<'tcx> {
837+
pub fn iter_captured_args(
838+
self,
839+
tcx: TyCtxt<'tcx>,
840+
) -> impl Iterator<Item = (usize, GenericArg<'tcx>)> {
841+
std::iter::zip(self.args, tcx.variances_of(self.def_id)).enumerate().filter_map(
842+
|(i, (arg, v))| match (arg.unpack(), v) {
843+
(_, ty::Invariant) => Some((i, arg)),
844+
(ty::GenericArgKind::Lifetime(_), ty::Bivariant) => None,
845+
_ => bug!("unexpected opaque type arg variance"),
846+
},
847+
)
848+
}
849+
850+
pub fn fold_captured_lifetime_args(
851+
self,
852+
tcx: TyCtxt<'tcx>,
853+
mut f: impl FnMut(Region<'tcx>) -> Region<'tcx>,
854+
) -> Self {
855+
let Self { def_id, args } = self;
856+
let args = std::iter::zip(args, tcx.variances_of(def_id)).map(|(arg, v)| {
857+
match (arg.unpack(), v) {
858+
(ty::GenericArgKind::Lifetime(_), ty::Bivariant) => arg,
859+
(ty::GenericArgKind::Lifetime(lt), _) => f(lt).into(),
860+
_ => arg,
861+
}
862+
});
863+
let args = tcx.mk_args_from_iter(args);
864+
Self { def_id, args }
865+
}
866+
}
867+
836868
#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, HashStable, TyEncodable, TyDecodable)]
837869
pub struct OpaqueHiddenType<'tcx> {
838870
/// The span of this particular definition of the opaque type. So
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0792]: expected generic lifetime parameter, found `'_`
2+
--> $DIR/defining-use-captured-non-universal-region.rs:13:18
3+
|
4+
LL | fn foo<'a>() -> impl Sized + 'a {
5+
| -- this generic parameter must be used with a generic lifetime parameter
6+
...
7+
LL | let i: i32 = foo::<'_>();
8+
| ^^^^^^^^^^^
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0792`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This was an ICE. See #110726.
2+
3+
//@ revisions: statik infer fixed
4+
//@ [fixed] check-pass
5+
#![allow(unconditional_recursion)]
6+
7+
fn foo<'a>() -> impl Sized + 'a {
8+
#[cfg(statik)]
9+
let i: i32 = foo::<'static>();
10+
//[statik]~^ ERROR expected generic lifetime parameter, found `'static`
11+
12+
#[cfg(infer)]
13+
let i: i32 = foo::<'_>();
14+
//[infer]~^ ERROR expected generic lifetime parameter, found `'_`
15+
16+
#[cfg(fixed)]
17+
let i: i32 = foo::<'a>();
18+
19+
i
20+
}
21+
22+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0792]: expected generic lifetime parameter, found `'static`
2+
--> $DIR/defining-use-captured-non-universal-region.rs:9:18
3+
|
4+
LL | fn foo<'a>() -> impl Sized + 'a {
5+
| -- cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type
6+
LL | #[cfg(statik)]
7+
LL | let i: i32 = foo::<'static>();
8+
| ^^^^^^^^^^^^^^^^
9+
10+
error: aborting due to 1 previous error
11+
12+
For more information about this error, try `rustc --explain E0792`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// issue: #110623
2+
//@ check-pass
3+
4+
use std::{collections::BTreeMap, num::ParseIntError, str::FromStr};
5+
6+
enum FileSystem {
7+
File(usize),
8+
Directory(BTreeMap<String, FileSystem>),
9+
}
10+
11+
impl FromStr for FileSystem {
12+
type Err = ParseIntError;
13+
14+
fn from_str(s: &str) -> Result<Self, Self::Err> {
15+
if s.starts_with("dir") {
16+
Ok(Self::new_dir())
17+
} else {
18+
Ok(Self::File(s.split_whitespace().next().unwrap().parse()?))
19+
}
20+
}
21+
}
22+
23+
impl FileSystem {
24+
fn new_dir() -> FileSystem {
25+
FileSystem::Directory(BTreeMap::new())
26+
}
27+
28+
fn insert(&mut self, name: String, other: FileSystem) -> Option<FileSystem> {
29+
match self {
30+
FileSystem::File(_) => panic!("can only insert into directory!"),
31+
FileSystem::Directory(tree) => tree.insert(name, other),
32+
}
33+
}
34+
35+
// Recursively build a tree from commands. This uses (abuses?)
36+
// the fact that `cd /` only appears at the start and that
37+
// subsequent `cd`s can only move ONE level to use the recursion
38+
// stack as the filesystem stack
39+
fn build<'a>(
40+
&mut self,
41+
mut commands: impl Iterator<Item = &'a str> + 'a,
42+
) -> Option<impl Iterator<Item = &'a str> + 'a> {
43+
let cmd = commands.next()?;
44+
let mut elements = cmd.lines();
45+
match elements.next().map(str::trim) {
46+
Some("cd /") | None => self.build(commands),
47+
Some("cd ..") => {
48+
// return to higher scope
49+
Some(commands)
50+
}
51+
Some("ls") => {
52+
for item in elements {
53+
let name = item.split_whitespace().last().unwrap();
54+
let prior = self.insert(name.to_string(), item.parse().unwrap());
55+
debug_assert!(prior.is_none());
56+
}
57+
// continue on
58+
self.build(commands)
59+
}
60+
Some(other_cd) => {
61+
let name = other_cd
62+
.trim()
63+
.strip_prefix("cd ")
64+
.expect("expected a cd command");
65+
let mut directory = FileSystem::new_dir();
66+
let further_commands = directory.build(commands);
67+
self.insert(name.to_string(), directory);
68+
self.build(further_commands?) // THIS LINE FAILS TO COMPILE
69+
}
70+
}
71+
}
72+
}
73+
74+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ check-pass
2+
3+
#![feature(adt_const_params)]
4+
#![allow(incomplete_features)]
5+
6+
trait Bar<const FOO: &'static str> {}
7+
impl Bar<"asdf"> for () {}
8+
9+
fn foo<const FOO: &'static str>() -> impl Bar<"asdf"> {
10+
()
11+
}
12+
13+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// issue: #111906
2+
//@ check-pass
3+
4+
#![allow(unconditional_recursion)]
5+
6+
fn foo<'a: 'a>() -> impl Sized {
7+
let _: () = foo::<'a>();
8+
loop {}
9+
}
10+
11+
fn bar<'a: 'a>() -> impl Sized + 'a {
12+
let _: *mut &'a () = bar::<'a>();
13+
loop {}
14+
}
15+
16+
fn main() {}

tests/ui/impl-trait/erased-regions-in-hidden-ty.current.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: {foo<DefId(..)_'a/#0>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
1+
error: {foo<'{erased}>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
22
--> $DIR/erased-regions-in-hidden-ty.rs:12:36
33
|
44
LL | fn foo<'a: 'a>(x: &'a Vec<i32>) -> impl Fn() + 'static {

tests/ui/impl-trait/erased-regions-in-hidden-ty.next.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: {foo<DefId(..)_'a/#0>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
1+
error: {foo<'{erased}>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
22
--> $DIR/erased-regions-in-hidden-ty.rs:12:36
33
|
44
LL | fn foo<'a: 'a>(x: &'a Vec<i32>) -> impl Fn() + 'static {

tests/ui/impl-trait/erased-regions-in-hidden-ty.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// Make sure that the compiler can handle `ReErased` in the hidden type of an opaque.
1111

1212
fn foo<'a: 'a>(x: &'a Vec<i32>) -> impl Fn() + 'static {
13-
//~^ ERROR 'a/#0>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
13+
//~^ ERROR '{erased}>::{closure#0} closure_kind_ty=i8 closure_sig_as_fn_ptr_ty=extern "rust-call" fn(()) upvar_tys=()}
1414
// Can't write whole type because of lack of path sanitization
1515
|| ()
1616
}

tests/ui/impl-trait/impl-fn-predefined-lifetimes.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@
22
use std::fmt::Debug;
33

44
fn a<'a>() -> impl Fn(&'a u8) -> (impl Debug + '_) {
5-
//~^ ERROR cannot resolve opaque type
6-
75
|x| x
8-
//~^ ERROR concrete type differs from previous defining opaque type use
6+
//~^ ERROR expected generic lifetime parameter, found `'_`
97
}
108

119
fn _b<'a>() -> impl Fn(&'a u8) -> (impl Debug + 'a) {
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
1-
error: concrete type differs from previous defining opaque type use
2-
--> $DIR/impl-fn-predefined-lifetimes.rs:7:9
3-
|
4-
LL | |x| x
5-
| ^ expected `impl Debug + '_`, got `&u8`
6-
|
7-
note: previous use here
8-
--> $DIR/impl-fn-predefined-lifetimes.rs:7:5
9-
|
10-
LL | |x| x
11-
| ^^^^^
12-
13-
error[E0720]: cannot resolve opaque type
14-
--> $DIR/impl-fn-predefined-lifetimes.rs:4:35
1+
error[E0792]: expected generic lifetime parameter, found `'_`
2+
--> $DIR/impl-fn-predefined-lifetimes.rs:5:9
153
|
164
LL | fn a<'a>() -> impl Fn(&'a u8) -> (impl Debug + '_) {
17-
| ^^^^^^^^^^^^^^^ cannot resolve opaque type
5+
| -- this generic parameter must be used with a generic lifetime parameter
6+
LL | |x| x
7+
| ^
188

19-
error: aborting due to 2 previous errors
9+
error: aborting due to 1 previous error
2010

21-
For more information about this error, try `rustc --explain E0720`.
11+
For more information about this error, try `rustc --explain E0792`.

tests/ui/impl-trait/in-trait/bad-item-bound-within-rpitit-2.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ trait Foo {
55
fn bar<'other: 'a>() -> impl Sized + 'a {}
66
//~^ ERROR use of undeclared lifetime name `'a`
77
//~| ERROR use of undeclared lifetime name `'a`
8+
//~| ERROR expected generic lifetime parameter, found `'static`
89
}
910

1011
fn main() {}

tests/ui/impl-trait/in-trait/bad-item-bound-within-rpitit-2.stderr

+11-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ help: consider introducing lifetime `'a` here
2828
LL | trait Foo<'a> {
2929
| ++++
3030

31-
error: aborting due to 2 previous errors
31+
error[E0792]: expected generic lifetime parameter, found `'static`
32+
--> $DIR/bad-item-bound-within-rpitit-2.rs:5:45
33+
|
34+
LL | fn bar<'other: 'a>() -> impl Sized + 'a {}
35+
| ------ ^^
36+
| |
37+
| cannot use static lifetime; use a bound lifetime instead or remove the lifetime parameter from the opaque type
38+
39+
error: aborting due to 3 previous errors
3240

33-
For more information about this error, try `rustc --explain E0261`.
41+
Some errors have detailed explanations: E0261, E0792.
42+
For more information about an error, try `rustc --explain E0261`.

tests/ui/impl-trait/rpit/early_bound.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ fn test<'a: 'a>(n: bool) -> impl Sized + 'a {
55
let true = n else { loop {} };
66
let _ = || {
77
let _ = identity::<&'a ()>(test(false));
8+
//~^ ERROR expected generic lifetime parameter, found `'_`
89
};
910
loop {}
1011
}

0 commit comments

Comments
 (0)