-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Track closure signatures & kinds in freshened types #43938
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,10 +19,21 @@ | |
//! fact an unbound type variable, we want the match to be regarded as ambiguous, because depending | ||
//! on what type that type variable is ultimately assigned, the match may or may not succeed. | ||
//! | ||
//! To handle closures, freshened types also have to contain the signature and kind of any | ||
//! closure in the local inference context, as otherwise the cache key might be invalidated. | ||
//! The way this is done is somewhat hacky - the closure signature is appended to the substs, | ||
//! as well as the closure kind "encoded" as a type. Also, special handling is needed when | ||
//! the closure signature contains a reference to the original closure. | ||
//! | ||
//! Note that you should be careful not to allow the output of freshening to leak to the user in | ||
//! error messages or in any other form. Freshening is only really useful as an internal detail. | ||
//! | ||
//! __An important detail concerning regions.__ The freshener also replaces *all* regions with | ||
//! Because of the manipulation required to handle closures, doing arbitrary operations on | ||
//! freshened types is not recommended. However, in addition to doing equality/hash | ||
//! comparisons (for caching), it is possible to do a `ty::_match` operation between | ||
//! 2 freshened types - this works even with the closure encoding. | ||
//! | ||
//! __An important detail concerning regions.__ The freshener also replaces *all* free regions with | ||
//! 'erased. The reason behind this is that, in general, we do not take region relationships into | ||
//! account when making type-overloaded decisions. This is important because of the design of the | ||
//! region inferencer, which is not based on unification but rather on accumulating and then | ||
|
@@ -32,7 +43,10 @@ | |
|
||
use ty::{self, Ty, TyCtxt, TypeFoldable}; | ||
use ty::fold::TypeFolder; | ||
use ty::subst::Substs; | ||
use util::nodemap::FxHashMap; | ||
use hir::def_id::DefId; | ||
|
||
use std::collections::hash_map::Entry; | ||
|
||
use super::InferCtxt; | ||
|
@@ -42,6 +56,7 @@ pub struct TypeFreshener<'a, 'gcx: 'a+'tcx, 'tcx: 'a> { | |
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>, | ||
freshen_count: u32, | ||
freshen_map: FxHashMap<ty::InferTy, Ty<'tcx>>, | ||
closure_set: Vec<DefId>, | ||
} | ||
|
||
impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> { | ||
|
@@ -51,6 +66,7 @@ impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> { | |
infcx, | ||
freshen_count: 0, | ||
freshen_map: FxHashMap(), | ||
closure_set: vec![], | ||
} | ||
} | ||
|
||
|
@@ -76,6 +92,88 @@ impl<'a, 'gcx, 'tcx> TypeFreshener<'a, 'gcx, 'tcx> { | |
} | ||
} | ||
} | ||
|
||
fn next_fresh<F>(&mut self, | ||
freshener: F) | ||
-> Ty<'tcx> | ||
where F: FnOnce(u32) -> ty::InferTy, | ||
{ | ||
let index = self.freshen_count; | ||
self.freshen_count += 1; | ||
self.infcx.tcx.mk_infer(freshener(index)) | ||
} | ||
|
||
fn freshen_closure_like<M, C>(&mut self, | ||
def_id: DefId, | ||
substs: ty::ClosureSubsts<'tcx>, | ||
t: Ty<'tcx>, | ||
markers: M, | ||
combine: C) | ||
-> Ty<'tcx> | ||
where M: FnOnce(&mut Self) -> (Ty<'tcx>, Ty<'tcx>), | ||
C: FnOnce(&'tcx Substs<'tcx>) -> Ty<'tcx> | ||
{ | ||
let tcx = self.infcx.tcx; | ||
|
||
let closure_in_progress = self.infcx.in_progress_tables.map_or(false, |tables| { | ||
tcx.hir.as_local_node_id(def_id).map_or(false, |closure_id| { | ||
tables.borrow().local_id_root == | ||
Some(DefId::local(tcx.hir.node_to_hir_id(closure_id).owner)) | ||
}) | ||
}); | ||
|
||
if !closure_in_progress { | ||
// If this closure belongs to another infcx, its kind etc. were | ||
// fully inferred and its signature/kind are exactly what's listed | ||
// in its infcx. So we don't need to add the markers for them. | ||
return t.super_fold_with(self); | ||
} | ||
|
||
// We are encoding a closure in progress. Because we want our freshening | ||
// key to contain all inference information needed to make sense of our | ||
// value, we need to encode the closure signature and kind. The way | ||
// we do that is to add them as 2 variables to the closure substs, | ||
// basically because it's there (and nobody cares about adding extra stuff | ||
// to substs). | ||
// | ||
// This means the "freshened" closure substs ends up looking like | ||
// fresh_substs = [PARENT_SUBSTS* ; UPVARS* ; SIG_MARKER ; KIND_MARKER] | ||
let (marker_1, marker_2) = if self.closure_set.contains(&def_id) { | ||
// We found the closure def-id within its own signature. Just | ||
// leave a new freshened type - any matching operations would | ||
// have found and compared the exterior closure already to | ||
// get here. | ||
// | ||
// In that case, we already know what the signature would | ||
// be - the parent closure on the stack already contains a | ||
// "copy" of the signature, so there is no reason to encode | ||
// it again for injectivity. Just use a fresh type variable | ||
// to make everything comparable. | ||
// | ||
// For example (closure kinds omitted for clarity) | ||
// t=[closure FOO sig=[closure BAR sig=[closure FOO ..]]] | ||
// Would get encoded to | ||
// t=[closure FOO sig=[closure BAR sig=[closure FOO sig=$0]]] | ||
// | ||
// and we can decode by having | ||
// $0=[closure BAR {sig doesn't exist in decode}] | ||
// and get | ||
// t=[closure FOO] | ||
// sig[FOO] = [closure BAR] | ||
// sig[BAR] = [closure FOO] | ||
(self.next_fresh(ty::FreshTy), self.next_fresh(ty::FreshTy)) | ||
} else { | ||
self.closure_set.push(def_id); | ||
let markers = markers(self); | ||
self.closure_set.pop(); | ||
markers | ||
}; | ||
|
||
combine(tcx.mk_substs( | ||
substs.substs.iter().map(|k| k.fold_with(self)).chain( | ||
[marker_1, marker_2].iter().cloned().map(From::from) | ||
))) | ||
} | ||
} | ||
|
||
impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> { | ||
|
@@ -105,7 +203,8 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> { | |
} | ||
|
||
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { | ||
if !t.needs_infer() && !t.has_erasable_regions() { | ||
if !t.needs_infer() && !t.has_erasable_regions() && | ||
!(t.has_closure_types() && self.infcx.in_progress_tables.is_some()) { | ||
return t; | ||
} | ||
|
||
|
@@ -150,6 +249,51 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> { | |
t | ||
} | ||
|
||
ty::TyClosure(def_id, substs) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. feels like some comments laying out the strategy here would be very welcome |
||
self.freshen_closure_like( | ||
def_id, substs, t, | ||
|this| { | ||
// HACK: use a "random" integer type to mark the kind. Because | ||
// different closure kinds shouldn't get unified during | ||
// selection, the "subtyping" relationship (where any kind is | ||
// better than no kind) shouldn't matter here, just that the | ||
// types are different. | ||
let closure_kind = this.infcx.closure_kind(def_id); | ||
let closure_kind_marker = match closure_kind { | ||
None => tcx.types.i8, | ||
Some(ty::ClosureKind::Fn) => tcx.types.i16, | ||
Some(ty::ClosureKind::FnMut) => tcx.types.i32, | ||
Some(ty::ClosureKind::FnOnce) => tcx.types.i64, | ||
}; | ||
|
||
let closure_sig = this.infcx.fn_sig(def_id); | ||
(tcx.mk_fn_ptr(closure_sig.fold_with(this)), | ||
closure_kind_marker) | ||
}, | ||
|substs| tcx.mk_closure(def_id, substs) | ||
) | ||
} | ||
|
||
ty::TyGenerator(def_id, substs, interior) => { | ||
self.freshen_closure_like( | ||
def_id, substs, t, | ||
|this| { | ||
let gen_sig = this.infcx.generator_sig(def_id).unwrap(); | ||
// FIXME: want to revise this strategy when generator | ||
// signatures can actually contain LBRs. | ||
let sig = this.tcx().no_late_bound_regions(&gen_sig) | ||
.unwrap_or_else(|| { | ||
bug!("late-bound regions in signature of {:?}", | ||
def_id) | ||
}); | ||
(sig.yield_ty, sig.return_ty).fold_with(this) | ||
}, | ||
|substs| { | ||
tcx.mk_generator(def_id, ty::ClosureSubsts { substs }, interior) | ||
} | ||
) | ||
} | ||
|
||
ty::TyBool | | ||
ty::TyChar | | ||
ty::TyInt(..) | | ||
|
@@ -165,8 +309,6 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for TypeFreshener<'a, 'gcx, 'tcx> { | |
ty::TyFnDef(..) | | ||
ty::TyFnPtr(_) | | ||
ty::TyDynamic(..) | | ||
ty::TyClosure(..) | | ||
ty::TyGenerator(..) | | ||
ty::TyNever | | ||
ty::TyTuple(..) | | ||
ty::TyProjection(..) | | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice comments