Implement proper subtyping for region fn types (part of #2263)

This commit is contained in:
Niko Matsakis 2012-10-19 06:01:01 -07:00
parent f04a6fc213
commit 1a3a70760b
20 changed files with 877 additions and 417 deletions

View file

@ -136,12 +136,23 @@ pub pure fn from_elem<T: Copy>(n_elts: uint, t: T) -> @[T] {
#[cfg(notest)]
pub mod traits {
#[legacy_exports];
#[cfg(stage0)]
pub impl<T: Copy> @[T] : Add<&[const T],@[T]> {
#[inline(always)]
pure fn add(rhs: & &[const T]) -> @[T] {
append(self, (*rhs))
}
}
#[cfg(stage1)]
#[cfg(stage2)]
pub impl<T: Copy> @[T] : Add<&[const T],@[T]> {
#[inline(always)]
pure fn add(rhs: & &self/[const T]) -> @[T] {
append(self, (*rhs))
}
}
}
#[cfg(test)]

View file

@ -226,12 +226,21 @@ impl<T> *const T : Ord {
}
// Equality for region pointers
#[cfg(stage0)]
impl<T:Eq> &const T : Eq {
pure fn eq(other: & &const T) -> bool { return *self == *(*other); }
pure fn ne(other: & &const T) -> bool { return *self != *(*other); }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T:Eq> &const T : Eq {
pure fn eq(other: & &self/const T) -> bool { return *self == *(*other); }
pure fn ne(other: & &self/const T) -> bool { return *self != *(*other); }
}
// Comparison for region pointers
#[cfg(stage0)]
impl<T:Ord> &const T : Ord {
pure fn lt(other: & &const T) -> bool { *self < *(*other) }
pure fn le(other: & &const T) -> bool { *self <= *(*other) }
@ -239,6 +248,15 @@ impl<T:Ord> &const T : Ord {
pure fn gt(other: & &const T) -> bool { *self > *(*other) }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T:Ord> &const T : Ord {
pure fn lt(other: & &self/const T) -> bool { *self < *(*other) }
pure fn le(other: & &self/const T) -> bool { *self <= *(*other) }
pure fn ge(other: & &self/const T) -> bool { *self >= *(*other) }
pure fn gt(other: & &self/const T) -> bool { *self > *(*other) }
}
#[test]
pub fn test() {
unsafe {

View file

@ -735,6 +735,7 @@ pure fn gt(a: &str, b: &str) -> bool {
!le(a, b)
}
#[cfg(stage0)]
impl &str : Eq {
#[inline(always)]
pure fn eq(other: & &str) -> bool {
@ -744,6 +745,17 @@ impl &str : Eq {
pure fn ne(other: & &str) -> bool { !self.eq(other) }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl &str : Eq {
#[inline(always)]
pure fn eq(other: & &self/str) -> bool {
eq_slice(self, (*other))
}
#[inline(always)]
pure fn ne(other: & &self/str) -> bool { !self.eq(other) }
}
impl ~str : Eq {
#[inline(always)]
pure fn eq(other: &~str) -> bool {
@ -773,6 +785,7 @@ impl ~str : Ord {
pure fn gt(other: &~str) -> bool { gt(self, (*other)) }
}
#[cfg(stage0)]
impl &str : Ord {
#[inline(always)]
pure fn lt(other: & &str) -> bool { lt(self, (*other)) }
@ -784,6 +797,19 @@ impl &str : Ord {
pure fn gt(other: & &str) -> bool { gt(self, (*other)) }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl &str : Ord {
#[inline(always)]
pure fn lt(other: & &self/str) -> bool { lt(self, (*other)) }
#[inline(always)]
pure fn le(other: & &self/str) -> bool { le(self, (*other)) }
#[inline(always)]
pure fn ge(other: & &self/str) -> bool { ge(self, (*other)) }
#[inline(always)]
pure fn gt(other: & &self/str) -> bool { gt(self, (*other)) }
}
impl @str : Ord {
#[inline(always)]
pure fn lt(other: &@str) -> bool { lt(self, (*other)) }
@ -2096,12 +2122,22 @@ impl ~str: Trimmable {
#[cfg(notest)]
pub mod traits {
#[cfg(stage0)]
impl ~str : Add<&str,~str> {
#[inline(always)]
pure fn add(rhs: & &str) -> ~str {
append(copy self, (*rhs))
}
}
#[cfg(stage1)]
#[cfg(stage2)]
impl ~str : Add<&str,~str> {
#[inline(always)]
pure fn add(rhs: & &self/str) -> ~str {
append(copy self, (*rhs))
}
}
}
#[cfg(test)]
@ -2558,7 +2594,7 @@ mod tests {
assert find_str_between(data, ~"ab", 2u, 4u).is_none();
let mut data = ~"ประเทศไทย中华Việt Nam";
data += data;
data = data + data;
assert find_str_between(data, ~"", 0u, 43u) == Some(0u);
assert find_str_between(data, ~"", 6u, 43u) == Some(6u);

View file

@ -736,6 +736,27 @@ pub pure fn filter<T: Copy>(v: &[T], f: fn(t: &T) -> bool) -> ~[T] {
move result
}
/**
* Like `filter()`, but in place. Preserves order of `v`. Linear time.
*/
pub fn retain<T>(v: &mut ~[T], f: pure fn(t: &T) -> bool) {
let len = v.len();
let mut deleted: uint = 0;
for uint::range(0, len) |i| {
if !f(&v[i]) {
deleted += 1;
} else if deleted > 0 {
v[i - deleted] <-> v[i];
}
}
while deleted > 0 {
v.pop();
deleted -= 1;
}
}
/**
* Concatenate a vector of vectors.
*
@ -759,14 +780,17 @@ pub pure fn connect<T: Copy>(v: &[~[T]], sep: &T) -> ~[T] {
}
/// Reduce a vector from left to right
pub pure fn foldl<T: Copy, U>(z: T, v: &[U], p: fn(t: T, u: &U) -> T) -> T {
let mut accum = z;
for each(v) |elt| {
// it should be possible to move accum in, but the liveness analysis
// is not smart enough.
accum = p(accum, elt);
pub pure fn foldl<T, U>(z: T, v: &[U], p: fn(t: T, u: &U) -> T) -> T {
let mut accum = move z;
let mut i = 0;
let l = v.len();
while i < l {
// Use a while loop so that liveness analysis can handle moving
// the accumulator.
accum = p(move accum, &v[i]);
i += 1;
}
return accum;
return move accum;
}
/// Reduce a vector from right to left
@ -1293,6 +1317,7 @@ pure fn eq<T: Eq>(a: &[T], b: &[T]) -> bool {
return true;
}
#[cfg(stage0)]
impl<T: Eq> &[T] : Eq {
#[inline(always)]
pure fn eq(other: & &[T]) -> bool { eq(self, (*other)) }
@ -1300,6 +1325,16 @@ impl<T: Eq> &[T] : Eq {
pure fn ne(other: & &[T]) -> bool { !self.eq(other) }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T: Eq> &[T] : Eq {
#[inline(always)]
pure fn eq(other: & &self/[T]) -> bool { eq(self, (*other)) }
#[inline(always)]
pure fn ne(other: & &self/[T]) -> bool { !self.eq(other) }
}
impl<T: Eq> ~[T] : Eq {
#[inline(always)]
pure fn eq(other: &~[T]) -> bool { eq(self, (*other)) }
@ -1335,6 +1370,7 @@ pure fn le<T: Ord>(a: &[T], b: &[T]) -> bool { !lt(b, a) }
pure fn ge<T: Ord>(a: &[T], b: &[T]) -> bool { !lt(a, b) }
pure fn gt<T: Ord>(a: &[T], b: &[T]) -> bool { lt(b, a) }
#[cfg(stage0)]
impl<T: Ord> &[T] : Ord {
#[inline(always)]
pure fn lt(other: & &[T]) -> bool { lt(self, (*other)) }
@ -1346,6 +1382,19 @@ impl<T: Ord> &[T] : Ord {
pure fn gt(other: & &[T]) -> bool { gt(self, (*other)) }
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T: Ord> &[T] : Ord {
#[inline(always)]
pure fn lt(other: & &self/[T]) -> bool { lt(self, (*other)) }
#[inline(always)]
pure fn le(other: & &self/[T]) -> bool { le(self, (*other)) }
#[inline(always)]
pure fn ge(other: & &self/[T]) -> bool { ge(self, (*other)) }
#[inline(always)]
pure fn gt(other: & &self/[T]) -> bool { gt(self, (*other)) }
}
impl<T: Ord> ~[T] : Ord {
#[inline(always)]
pure fn lt(other: &~[T]) -> bool { lt(self, (*other)) }
@ -1370,6 +1419,7 @@ impl<T: Ord> @[T] : Ord {
#[cfg(notest)]
pub mod traits {
#[cfg(stage0)]
impl<T: Copy> ~[T] : Add<&[const T],~[T]> {
#[inline(always)]
pure fn add(rhs: & &[const T]) -> ~[T] {
@ -1377,12 +1427,31 @@ pub mod traits {
}
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T: Copy> ~[T] : Add<&[const T],~[T]> {
#[inline(always)]
pure fn add(rhs: & &self/[const T]) -> ~[T] {
append(copy self, (*rhs))
}
}
#[cfg(stage0)]
impl<T: Copy> ~[mut T] : Add<&[const T],~[mut T]> {
#[inline(always)]
pure fn add(rhs: & &[const T]) -> ~[mut T] {
append_mut(copy self, (*rhs))
}
}
#[cfg(stage1)]
#[cfg(stage2)]
impl<T: Copy> ~[mut T] : Add<&[const T],~[mut T]> {
#[inline(always)]
pure fn add(rhs: & &self/[const T]) -> ~[mut T] {
append_mut(copy self, (*rhs))
}
}
}
#[cfg(test)]
@ -1590,6 +1659,7 @@ pub trait MutableVector<T> {
fn unshift(&mut self, x: T);
fn swap_remove(&mut self, index: uint) -> T;
fn truncate(&mut self, newlen: uint);
fn retain(&mut self, f: pure fn(t: &T) -> bool);
}
pub trait MutableCopyableVector<T: Copy> {
@ -1631,6 +1701,10 @@ impl<T> ~[T]: MutableVector<T> {
fn truncate(&mut self, newlen: uint) {
truncate(self, newlen);
}
fn retain(&mut self, f: pure fn(t: &T) -> bool) {
retain(self, f);
}
}
impl<T: Copy> ~[T]: MutableCopyableVector<T> {

View file

@ -147,7 +147,7 @@ fn enc_region(w: io::Writer, cx: @ctxt, r: ty::Region) {
ty::re_static => {
w.write_char('t');
}
ty::re_var(_) => {
ty::re_infer(_) => {
// these should not crop up after typeck
cx.diag.handler().bug(~"Cannot encode region variables");
}

View file

@ -387,7 +387,7 @@ impl ty::Region: tr {
ty::re_bound(br) => ty::re_bound(br.tr(xcx)),
ty::re_free(id, br) => ty::re_free(xcx.tr_id(id), br.tr(xcx)),
ty::re_scope(id) => ty::re_scope(xcx.tr_id(id)),
ty::re_static | ty::re_var(*) => self,
ty::re_static | ty::re_infer(*) => self,
}
}
}

View file

@ -580,7 +580,7 @@ fn check_cast_for_escaping_regions(
match target_substs.self_r {
Some(ty::re_scope(*)) => { return; /* case (1) */ }
None | Some(ty::re_static) | Some(ty::re_free(*)) => {}
Some(ty::re_bound(*)) | Some(ty::re_var(*)) => {
Some(ty::re_bound(*)) | Some(ty::re_infer(*)) => {
cx.tcx.sess.span_bug(
source.span,
fmt!("bad region found in kind: %?", target_substs.self_r));

View file

@ -112,18 +112,18 @@ fn is_subregion_of(region_map: region_map,
super_region: ty::Region) -> bool {
sub_region == super_region ||
match (sub_region, super_region) {
(_, ty::re_static) => {
true
}
(_, ty::re_static) => {
true
}
(ty::re_scope(sub_scope), ty::re_scope(super_scope)) |
(ty::re_scope(sub_scope), ty::re_free(super_scope, _)) => {
scope_contains(region_map, super_scope, sub_scope)
}
(ty::re_scope(sub_scope), ty::re_scope(super_scope)) |
(ty::re_scope(sub_scope), ty::re_free(super_scope, _)) => {
scope_contains(region_map, super_scope, sub_scope)
}
_ => {
false
}
_ => {
false
}
}
}

View file

@ -12,7 +12,7 @@ use syntax::ast_util::{is_local, local_def};
use syntax::codemap::span;
use metadata::csearch;
use util::ppaux::{region_to_str, explain_region, vstore_to_str,
note_and_explain_region};
note_and_explain_region, bound_region_to_str};
use middle::lint;
use middle::lint::{get_lint_level, allow};
use syntax::ast::*;
@ -107,7 +107,8 @@ export InferTy, TyVar, IntVar;
export ty_self, mk_self, type_has_self;
export ty_class;
export Region, bound_region, encl_region;
export re_bound, re_free, re_scope, re_static, re_var;
export re_bound, re_free, re_scope, re_static, re_infer;
export ReVar, ReSkolemized;
export br_self, br_anon, br_named, br_cap_avoid;
export get, type_has_params, type_needs_infer, type_has_regions;
export type_is_region_ptr;
@ -179,6 +180,8 @@ export terr_in_field, terr_record_fields, terr_vstores_differ, terr_arg_count;
export terr_sorts, terr_vec, terr_str, terr_record_size, terr_tuple_size;
export terr_regions_does_not_outlive, terr_mutability, terr_purity_mismatch;
export terr_regions_not_same, terr_regions_no_overlap;
export terr_regions_insufficiently_polymorphic;
export terr_regions_overly_polymorphic;
export terr_proto_mismatch;
export terr_ret_style_mismatch;
export terr_fn, terr_trait;
@ -555,7 +558,7 @@ enum Region {
re_static,
/// A region variable. Should not exist after typeck.
re_var(RegionVid)
re_infer(InferRegion)
}
#[auto_serialize]
@ -671,6 +674,8 @@ enum type_err {
terr_regions_does_not_outlive(Region, Region),
terr_regions_not_same(Region, Region),
terr_regions_no_overlap(Region, Region),
terr_regions_insufficiently_polymorphic(bound_region, Region),
terr_regions_overly_polymorphic(bound_region, Region),
terr_vstores_differ(terr_vstore_kind, expected_found<vstore>),
terr_in_field(@type_err, ast::ident),
terr_sorts(expected_found<t>),
@ -707,6 +712,39 @@ impl InferTy : to_bytes::IterBytes {
}
}
#[auto_serialize]
#[auto_deserialize]
enum InferRegion {
ReVar(RegionVid),
ReSkolemized(uint, bound_region)
}
impl InferRegion : to_bytes::IterBytes {
pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) {
match self {
ReVar(ref rv) => to_bytes::iter_bytes_2(&0u8, rv, lsb0, f),
ReSkolemized(ref v, _) => to_bytes::iter_bytes_2(&1u8, v, lsb0, f)
}
}
}
impl InferRegion : cmp::Eq {
pure fn eq(other: &InferRegion) -> bool {
match (self, *other) {
(ReVar(rva), ReVar(rvb)) => {
rva == rvb
}
(ReSkolemized(rva, _), ReSkolemized(rvb, _)) => {
rva == rvb
}
_ => false
}
}
pure fn ne(other: &InferRegion) -> bool {
!(self == (*other))
}
}
impl param_bound : to_bytes::IterBytes {
pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) {
match self {
@ -923,7 +961,7 @@ fn mk_t_with_id(cx: ctxt, +st: sty, o_def_id: Option<ast::def_id>) -> t {
fn rflags(r: Region) -> uint {
(has_regions as uint) | {
match r {
ty::re_var(_) => needs_infer as uint,
ty::re_infer(_) => needs_infer as uint,
_ => 0u
}
}
@ -2591,7 +2629,7 @@ impl Region : to_bytes::IterBytes {
re_scope(ref id) =>
to_bytes::iter_bytes_2(&2u8, id, lsb0, f),
re_var(ref id) =>
re_infer(ref id) =>
to_bytes::iter_bytes_2(&3u8, id, lsb0, f),
re_static => 4u8.iter_bytes(lsb0, f)
@ -3253,92 +3291,103 @@ fn type_err_to_str(cx: ctxt, err: &type_err) -> ~str {
}
match *err {
terr_mismatch => ~"types differ",
terr_ret_style_mismatch(values) => {
fn to_str(s: ast::ret_style) -> ~str {
match s {
ast::noreturn => ~"non-returning",
ast::return_val => ~"return-by-value"
terr_mismatch => ~"types differ",
terr_ret_style_mismatch(values) => {
fn to_str(s: ast::ret_style) -> ~str {
match s {
ast::noreturn => ~"non-returning",
ast::return_val => ~"return-by-value"
}
}
fmt!("expected %s function, found %s function",
to_str(values.expected),
to_str(values.expected))
}
terr_purity_mismatch(values) => {
fmt!("expected %s fn but found %s fn",
purity_to_str(values.expected),
purity_to_str(values.found))
}
terr_proto_mismatch(values) => {
fmt!("expected %s closure, found %s closure",
proto_ty_to_str(cx, values.expected),
proto_ty_to_str(cx, values.found))
}
terr_mutability => ~"values differ in mutability",
terr_box_mutability => ~"boxed values differ in mutability",
terr_vec_mutability => ~"vectors differ in mutability",
terr_ptr_mutability => ~"pointers differ in mutability",
terr_ref_mutability => ~"references differ in mutability",
terr_ty_param_size(values) => {
fmt!("expected a type with %? type params \
but found one with %? type params",
values.expected, values.found)
}
terr_tuple_size(values) => {
fmt!("expected a tuple with %? elements \
but found one with %? elements",
values.expected, values.found)
}
terr_record_size(values) => {
fmt!("expected a record with %? fields \
but found one with %? fields",
values.expected, values.found)
}
terr_record_mutability => {
~"record elements differ in mutability"
}
terr_record_fields(values) => {
fmt!("expected a record with field `%s` but found one with field \
`%s`",
cx.sess.str_of(values.expected),
cx.sess.str_of(values.found))
}
terr_arg_count => ~"incorrect number of function parameters",
terr_mode_mismatch(values) => {
fmt!("expected argument mode %s, but found %s",
mode_to_str(values.expected), mode_to_str(values.found))
}
terr_regions_does_not_outlive(*) => {
fmt!("lifetime mismatch")
}
terr_regions_not_same(*) => {
fmt!("lifetimes are not the same")
}
terr_regions_no_overlap(*) => {
fmt!("lifetimes do not intersect")
}
terr_regions_insufficiently_polymorphic(br, _) => {
fmt!("expected bound lifetime parameter %s, \
but found concrete lifetime",
bound_region_to_str(cx, br))
}
terr_regions_overly_polymorphic(br, _) => {
fmt!("expected concrete lifetime, \
but found bound lifetime parameter %s",
bound_region_to_str(cx, br))
}
terr_vstores_differ(k, values) => {
fmt!("%s storage differs: expected %s but found %s",
terr_vstore_kind_to_str(k),
vstore_to_str(cx, values.expected),
vstore_to_str(cx, values.found))
}
terr_in_field(err, fname) => {
fmt!("in field `%s`, %s", cx.sess.str_of(fname),
type_err_to_str(cx, err))
}
terr_sorts(values) => {
fmt!("expected %s but found %s",
ty_sort_str(cx, values.expected),
ty_sort_str(cx, values.found))
}
terr_self_substs => {
~"inconsistent self substitution" // XXX this is more of a bug
}
terr_no_integral_type => {
~"couldn't determine an appropriate integral type for integer \
literal"
}
fmt!("expected %s function, found %s function",
to_str(values.expected),
to_str(values.expected))
}
terr_purity_mismatch(values) => {
fmt!("expected %s fn but found %s fn",
purity_to_str(values.expected),
purity_to_str(values.found))
}
terr_proto_mismatch(values) => {
fmt!("expected %s closure, found %s closure",
proto_ty_to_str(cx, values.expected),
proto_ty_to_str(cx, values.found))
}
terr_mutability => ~"values differ in mutability",
terr_box_mutability => ~"boxed values differ in mutability",
terr_vec_mutability => ~"vectors differ in mutability",
terr_ptr_mutability => ~"pointers differ in mutability",
terr_ref_mutability => ~"references differ in mutability",
terr_ty_param_size(values) => {
fmt!("expected a type with %? type params \
but found one with %? type params",
values.expected, values.found)
}
terr_tuple_size(values) => {
fmt!("expected a tuple with %? elements \
but found one with %? elements",
values.expected, values.found)
}
terr_record_size(values) => {
fmt!("expected a record with %? fields \
but found one with %? fields",
values.expected, values.found)
}
terr_record_mutability => {
~"record elements differ in mutability"
}
terr_record_fields(values) => {
fmt!("expected a record with field `%s` but found one with field \
`%s`",
cx.sess.str_of(values.expected), cx.sess.str_of(values.found))
}
terr_arg_count => ~"incorrect number of function parameters",
terr_mode_mismatch(values) => {
fmt!("expected argument mode %s, but found %s",
mode_to_str(values.expected), mode_to_str(values.found))
}
terr_regions_does_not_outlive(*) => {
fmt!("lifetime mismatch")
}
terr_regions_not_same(*) => {
fmt!("lifetimes are not the same")
}
terr_regions_no_overlap(*) => {
fmt!("lifetimes do not intersect")
}
terr_vstores_differ(k, values) => {
fmt!("%s storage differs: expected %s but found %s",
terr_vstore_kind_to_str(k),
vstore_to_str(cx, values.expected),
vstore_to_str(cx, values.found))
}
terr_in_field(err, fname) => {
fmt!("in field `%s`, %s", cx.sess.str_of(fname),
type_err_to_str(cx, err))
}
terr_sorts(values) => {
fmt!("expected %s but found %s",
ty_sort_str(cx, values.expected),
ty_sort_str(cx, values.found))
}
terr_self_substs => {
~"inconsistent self substitution" // XXX this is more of a bug
}
terr_no_integral_type => {
~"couldn't determine an appropriate integral type for integer \
literal"
}
}
}
@ -3359,6 +3408,16 @@ fn note_and_explain_type_err(cx: ctxt, err: &type_err) {
note_and_explain_region(cx, ~"...does not overlap ",
region2, ~"");
}
terr_regions_insufficiently_polymorphic(_, conc_region) => {
note_and_explain_region(cx,
~"concrete lifetime that was found is ",
conc_region, ~"");
}
terr_regions_overly_polymorphic(_, conc_region) => {
note_and_explain_region(cx,
~"expected concrete lifetime is ",
conc_region, ~"");
}
_ => {}
}
}
@ -4182,9 +4241,9 @@ impl Region : cmp::Eq {
_ => false
}
}
re_var(e0a) => {
re_infer(e0a) => {
match (*other) {
re_var(e0b) => e0a == e0b,
re_infer(e0b) => e0a == e0b,
_ => false
}
}

View file

@ -97,7 +97,7 @@ fn replace_bound_regions_in_fn_ty(
r: ty::Region) -> isr_alist {
match r {
ty::re_free(_, _) | ty::re_static | ty::re_scope(_) |
ty::re_var(_) => {
ty::re_infer(_) => {
isr
}
ty::re_bound(br) => {
@ -165,7 +165,7 @@ fn replace_bound_regions_in_fn_ty(
ty::re_static |
ty::re_scope(_) |
ty::re_free(_, _) |
ty::re_var(_) => r
ty::re_infer(_) => r
}
}
}

View file

@ -272,46 +272,60 @@ fn ensure_supertraits(ccx: @crate_ctxt,
*
* - impl_m: the method in the impl
* - impl_tps: the type params declared on the impl itself (not the method!)
* - impl_body_id: the id of the method body from the impl
* - trait_m: the method in the trait
* - trait_substs: the substitutions used on the type of the trait
* - self_ty: the self type of the impl
*/
fn compare_impl_method(tcx: ty::ctxt, sp: span,
impl_m: ty::method, impl_tps: uint,
trait_m: ty::method, trait_substs: ty::substs,
self_ty: ty::t) {
fn compare_impl_method(tcx: ty::ctxt,
impl_tps: uint,
cm: &ConvertedMethod,
trait_m: &ty::method,
trait_substs: &ty::substs,
self_ty: ty::t)
{
debug!("compare_impl_method()");
let _indenter = indenter();
let impl_m = &cm.mty;
if impl_m.fty.meta.purity != trait_m.fty.meta.purity {
tcx.sess.span_err(
sp, fmt!("method `%s`'s purity does \
not match the trait method's \
purity", tcx.sess.str_of(impl_m.ident)));
cm.span,
fmt!("method `%s`'s purity does \
not match the trait method's \
purity", tcx.sess.str_of(impl_m.ident)));
}
// is this check right?
if impl_m.self_ty != trait_m.self_ty {
tcx.sess.span_err(
sp, fmt!("method `%s`'s self type does \
not match the trait method's \
self type", tcx.sess.str_of(impl_m.ident)));
cm.span,
fmt!("method `%s`'s self type does \
not match the trait method's \
self type", tcx.sess.str_of(impl_m.ident)));
}
if impl_m.tps.len() != trait_m.tps.len() {
tcx.sess.span_err(sp, fmt!("method `%s` \
has %u type %s, but its trait declaration has %u type %s",
tcx.sess.str_of(trait_m.ident), impl_m.tps.len(),
pluralize(impl_m.tps.len(), ~"parameter"),
trait_m.tps.len(),
pluralize(trait_m.tps.len(), ~"parameter")));
tcx.sess.span_err(
cm.span,
fmt!("method `%s` has %u type %s, but its trait \
declaration has %u type %s",
tcx.sess.str_of(trait_m.ident), impl_m.tps.len(),
pluralize(impl_m.tps.len(), ~"parameter"),
trait_m.tps.len(),
pluralize(trait_m.tps.len(), ~"parameter")));
return;
}
if vec::len(impl_m.fty.sig.inputs) != vec::len(trait_m.fty.sig.inputs) {
tcx.sess.span_err(sp,fmt!("method `%s` has %u parameters \
but the trait has %u",
tcx.sess.str_of(trait_m.ident),
vec::len(impl_m.fty.sig.inputs),
vec::len(trait_m.fty.sig.inputs)));
tcx.sess.span_err(
cm.span,
fmt!("method `%s` has %u parameters \
but the trait has %u",
tcx.sess.str_of(trait_m.ident),
vec::len(impl_m.fty.sig.inputs),
vec::len(trait_m.fty.sig.inputs)));
return;
}
@ -322,14 +336,16 @@ fn compare_impl_method(tcx: ty::ctxt, sp: span,
// Would be nice to use the ty param names in the error message,
// but we don't have easy access to them here
if impl_param_bounds.len() != trait_param_bounds.len() {
tcx.sess.span_err(sp, fmt!("in method `%s`, \
type parameter %u has %u %s, but the same type \
parameter in its trait declaration has %u %s",
tcx.sess.str_of(trait_m.ident),
i, impl_param_bounds.len(),
pluralize(impl_param_bounds.len(), ~"bound"),
trait_param_bounds.len(),
pluralize(trait_param_bounds.len(), ~"bound")));
tcx.sess.span_err(
cm.span,
fmt!("in method `%s`, \
type parameter %u has %u %s, but the same type \
parameter in its trait declaration has %u %s",
tcx.sess.str_of(trait_m.ident),
i, impl_param_bounds.len(),
pluralize(impl_param_bounds.len(), ~"bound"),
trait_param_bounds.len(),
pluralize(trait_param_bounds.len(), ~"bound")));
return;
}
// tjc: I'm mildly worried that there's something I'm
@ -337,35 +353,51 @@ fn compare_impl_method(tcx: ty::ctxt, sp: span,
// but I can't figure out what.
}
// Replace any references to the self region in the self type with
// a free region. So, for example, if the impl type is
// "&self/str", then this would replace the self type with a free
// region `self`.
//
// Note: Ideal would be to use the node-id of the method body here,
// not the node id of the method itself.
let dummy_self_r = ty::re_free(cm.body_id, ty::br_self);
let self_ty = replace_bound_self(tcx, self_ty, dummy_self_r);
// Perform substitutions so that the trait/impl methods are expressed
// in terms of the same set of type/region parameters:
// - replace trait type parameters with those from `trait_substs`
// - replace trait type parameters with those from `trait_substs`,
// except with any reference to bound self replaced with `dummy_self_r`
// - replace method parameters on the trait with fresh, dummy parameters
// that correspond to the parameters we will find on the impl
// - replace self region with a fresh, dummy region
let dummy_self_r = ty::re_free(0, ty::br_self);
let impl_fty = {
let impl_fty = ty::mk_fn(tcx, impl_m.fty);
debug!("impl_fty (pre-subst): %s", ty_to_str(tcx, impl_fty));
replace_bound_self(tcx, impl_fty, dummy_self_r)
};
debug!("impl_fty: %s", ty_to_str(tcx, impl_fty));
let trait_fty = {
let dummy_tps = do vec::from_fn((*trait_m.tps).len()) |i| {
// hack: we don't know the def id of the impl tp, but it
// is not important for unification
ty::mk_param(tcx, i + impl_tps, {crate: 0, node: 0})
};
let trait_tps = trait_substs.tps.map(
|t| replace_bound_self(tcx, *t, dummy_self_r));
let substs = {
self_r: Some(dummy_self_r),
self_ty: Some(self_ty),
tps: vec::append(trait_substs.tps, dummy_tps)
tps: vec::append(trait_tps, dummy_tps)
};
let trait_fty = ty::mk_fn(tcx, trait_m.fty);
debug!("trait_fty (pre-subst): %s", ty_to_str(tcx, trait_fty));
ty::subst(tcx, &substs, trait_fty)
};
debug!("trait_fty: %s", ty_to_str(tcx, trait_fty));
require_same_types(
tcx, None, false, sp, impl_fty, trait_fty,
|| ~"method `" + tcx.sess.str_of(trait_m.ident)
+ ~"` has an incompatible type");
tcx, None, false, cm.span, impl_fty, trait_fty,
|| fmt!("method `%s` has an incompatible type",
tcx.sess.str_of(trait_m.ident)));
return;
// Replaces bound references to the self region with `with_r`.
@ -382,7 +414,7 @@ fn check_methods_against_trait(ccx: @crate_ctxt,
rp: Option<ty::region_variance>,
selfty: ty::t,
a_trait_ty: @ast::trait_ref,
impl_ms: ~[converted_method]) {
impl_ms: ~[ConvertedMethod]) {
let tcx = ccx.tcx;
let (did, tpt) = instantiate_trait_ref(ccx, a_trait_ty, rp);
@ -391,32 +423,32 @@ fn check_methods_against_trait(ccx: @crate_ctxt,
}
for vec::each(*ty::trait_methods(tcx, did)) |trait_m| {
match vec::find(impl_ms, |impl_m| trait_m.ident == impl_m.mty.ident) {
Some({mty: impl_m, span, _}) => {
compare_impl_method(
ccx.tcx, span, impl_m, vec::len(tps),
*trait_m, tpt.substs, selfty);
}
None => {
// If we couldn't find an implementation for trait_m in
// the impl, then see if there was a default
// implementation in the trait itself. If not, raise a
// "missing method" error.
Some(ref cm) => {
compare_impl_method(
ccx.tcx, vec::len(tps), cm, trait_m,
&tpt.substs, selfty);
}
None => {
// If we couldn't find an implementation for trait_m in
// the impl, then see if there was a default
// implementation in the trait itself. If not, raise a
// "missing method" error.
let provided_methods = ty::provided_trait_methods(tcx, did);
match vec::find(provided_methods, |provided_method|
*provided_method == trait_m.ident) {
Some(_) => {
// If there's a provided method with the name we
// want, then we're fine; nothing else to do.
let provided_methods = ty::provided_trait_methods(tcx, did);
match vec::find(provided_methods, |provided_method|
*provided_method == trait_m.ident) {
Some(_) => {
// If there's a provided method with the name we
// want, then we're fine; nothing else to do.
}
None => {
tcx.sess.span_err(
a_trait_ty.path.span,
fmt!("missing method `%s`",
tcx.sess.str_of(trait_m.ident)));
}
}
None => {
tcx.sess.span_err(
a_trait_ty.path.span,
fmt!("missing method `%s`",
tcx.sess.str_of(trait_m.ident)));
}
}
}
}
} // match
} // |trait_m|
} // fn
@ -434,12 +466,17 @@ fn convert_field(ccx: @crate_ctxt,
ty: tt});
}
type converted_method = {mty: ty::method, id: ast::node_id, span: span};
struct ConvertedMethod {
mty: ty::method,
id: ast::node_id,
span: span,
body_id: ast::node_id
}
fn convert_methods(ccx: @crate_ctxt,
ms: ~[@ast::method],
rp: Option<ty::region_variance>,
rcvr_bounds: @~[ty::param_bounds]) -> ~[converted_method] {
rcvr_bounds: @~[ty::param_bounds]) -> ~[ConvertedMethod] {
let tcx = ccx.tcx;
do vec::map(ms) |m| {
@ -455,7 +492,8 @@ fn convert_methods(ccx: @crate_ctxt,
region_param: rp,
ty: fty});
write_ty_to_tcx(tcx, m.id, fty);
{mty: mty, id: m.id, span: m.span}
ConvertedMethod {mty: mty, id: m.id,
span: m.span, body_id: m.body.node.id}
}
}

View file

@ -262,7 +262,7 @@ use util::common::{indent, indenter};
use ast::{unsafe_fn, impure_fn, pure_fn, extern_fn};
use ast::{m_const, m_imm, m_mutbl};
use dvec::DVec;
use region_var_bindings::{RegionVarBindings};
use region_inference::{RegionVarBindings};
use ast_util::dummy_sp;
use cmp::Eq;
@ -628,7 +628,7 @@ impl infer_ctxt {
}
fn next_region_var_nb(span: span) -> ty::Region {
ty::re_var(self.region_vars.new_region_var(span))
ty::re_infer(ty::ReVar(self.region_vars.new_region_var(span)))
}
fn next_region_var_with_lb(span: span,

View file

@ -0,0 +1,12 @@
{
macro_rules! if_ok(
($inp: expr) => (
match $inp {
Ok(move v) => { move v }
Err(move e) => { return Err(e); }
}
)
);
}

View file

@ -134,45 +134,66 @@ to read the whole thing):
http://research.microsoft.com/en-us/um/people/simonpj/papers/higher-rank/
NOTE--for the most part, we do not yet handle these cases correctly!
Although my explanation will never compete with SPJ's (for one thing,
his is approximately 100 pages), I will attempt to explain the basic
problem and also how we solve it. Note that the paper only discusses
subtyping, not the computation of LUB/GLB.
## Subtyping and bound regions
The problem we are addressing is that there is a kind of subtyping
between functions with bound region parameters. Consider, for
example, whether the following relation holds:
### Simple examples
fn(&a/int) <: fn(&b/int)? (Yes, a => b)
The situation is well-summarized by these examples (here I am omitting
the types as they are not interesting, and I am writing binding
explicitly):
The answer is that of course it does. These two types are basically
the same, except that in one we used the name `a` and one we used
the name `b`.
1. fn<a>(&a/T) <: fn<b>(&b/T)? Yes: a -> b
2. fn<a>(&a/T) <: fn(&b/T)? Yes: a -> b
3. fn(&a/T) <: fn<b>(&b/T)? No!
4. fn(&a/T) <: fn(&b/T)? No!
5. fn(&a/T) <: fn(&a)? Yes!
In the examples that follow, it becomes very important to know whether
a lifetime is bound in a function type (that is, is a lifetime
parameter) or appears free (is defined in some outer scope).
Therefore, from now on I will write the bindings explicitly, using a
notation like `fn<a>(&a/int)` to indicate that `a` is a lifetime
parameter.
In case one, the two function types are equivalent because both
reference a bound region, just with different names.
Now let's consider two more function types. Here, we assume that the
`self` lifetime is defined somewhere outside and hence is not a
lifetime parameter bound by the function type (it "appears free"):
In case two, the subtyping relationship is valid because the subtyping
function accepts a pointer in *any* region, whereas the supertype
function accepts a pointer *only in the region `b`*. Therefore, it is
safe to use the subtype wherever the supertype is expected, as the
supertype can only be passed pointers in region `b`, and the subtype
can handle `b` (but also others).
fn<a>(&a/int) <: fn(&self/int)? (Yes, a => self)
Case three is the opposite: here the subtype requires the region `a`,
but the supertype must accept pointers in any region. That means that
it is not safe to use the subtype where the supertype is expected: the
supertype can be passed pointers in any region, but the subtype can
only handle pointers in the region `a`.
This subtyping relation does in fact hold. To see why, you have to
consider what subtyping means. One way to look at `T1 <: T2` is to
say that it means that it is always ok to treat an instance of `T1` as
if it had the type `T2`. So, with our functions, it is always ok to
treat a function that can take pointers with any lifetime as if it
were a function that can only take a pointer with the specific
lifetime `&self`. After all, `&self` is a lifetime, after all, and
the function can take values of any lifetime.
Case four is fairly simple. The subtype expects region `a` but the supertype
expects region `b`. These two regions are not the same. Therefore, not
a subtype.
You can also look at subtyping as the *is a* relationship. This amounts
to the same thing: a function that accepts pointers with any lifetime
*is a* function that accepts pointers with some specific lifetime.
Case five is similar to four, except that the subtype and supertype
expect the same region, so in fact they are the same type. That's
fine.
So, what if we reverse the order of the two function types, like this:
fn(&self/int) <: fn<a>(&a/int)? (No)
Does the subtyping relationship still hold? The answer of course is
no. In this case, the function accepts *only the lifetime `&self`*,
so it is not reasonable to treat it as if it were a function that
accepted any lifetime.
What about these two examples:
fn<a,b>(&a/int, &b/int) <: fn<a>(&a/int, &a/int)? (Yes)
fn<a>(&a/int, &a/int) <: fn<a,b>(&a/int, &b/int)? (No)
Here, it is true that functions which take two pointers with any two
lifetimes can be treated as if they only accepted two pointers with
the same lifetime, but not the reverse.
## The algorithm
Here is the algorithm we use to perform the subtyping check:
@ -184,15 +205,13 @@ Here is the algorithm we use to perform the subtyping check:
4. Ensure that no skolemized regions 'leak' into region variables
visible from "the outside"
I'll walk briefly through how this works with the examples above.
I'll ignore the last step for now, it'll come up in the complex
examples below.
Let's walk through some examples and see how this algorithm plays out.
#### First example
Let's look first at the first example, which was:
We'll start with the first example, which was:
1. fn<a>(&a/T) <: fn<b>(&b/T/T)? Yes: a -> x
1. fn<a>(&a/T) <: fn<b>(&b/T)? Yes: a -> b
After steps 1 and 2 of the algorithm we will have replaced the types
like so:
@ -204,7 +223,7 @@ region whose value is being inferred by the system. I also replaced
`&b` with `&x`---I'll use letters late in the alphabet (`x`, `y`, `z`)
to indicate skolemized region names. We can assume they don't appear
elsewhere. Note that neither the sub- nor the supertype bind any
region names anymore (that is, the `<a>` and `<b>` have been removed).
region names anymore (as indicated by the absence of `<` and `>`).
The next step is to check that the parameter types match. Because
parameters are contravariant, this means that we check whether:
@ -226,25 +245,25 @@ So far we have encountered no error, so the subtype check succeeds.
Now let's look first at the third example, which was:
3. fn(&a/T) <: fn<b>(&b/T)? No!
3. fn(&self/T) <: fn<b>(&b/T)? No!
After steps 1 and 2 of the algorithm we will have replaced the types
like so:
3. fn(&a/T) <: fn(&x/T)?
3. fn(&self/T) <: fn(&x/T)?
This looks pretty much the same as before, except that on the LHS `&a`
was not bound, and hence was left as-is and not replaced with a
variable. The next step is again to check that the parameter types
match. This will ultimately require (as before) that `&a` <= `&x`
must hold: but this does not hold. `a` and `x` are both distinct free
regions. So the subtype check fails.
This looks pretty much the same as before, except that on the LHS
`&self` was not bound, and hence was left as-is and not replaced with
a variable. The next step is again to check that the parameter types
match. This will ultimately require (as before) that `&self` <= `&x`
must hold: but this does not hold. `self` and `x` are both distinct
free regions. So the subtype check fails.
#### Checking for skolemization leaks
You may be wondering about that mysterious last step. So far it has not
been relevant. The purpose of that last step is to catch something like
*this*:
You may be wondering about that mysterious last step in the algorithm.
So far it has not been relevant. The purpose of that last step is to
catch something like *this*:
fn<a>() -> fn(&a/T) <: fn() -> fn<b>(&b/T)? No.
@ -295,10 +314,11 @@ rule.
So the way we solve this is to add a fourth step that examines the
constraints that refer to skolemized names. Basically, consider a
non-directed verison of the constraint graph. The only things
reachable from a skolemized region ought to be the region variables
that were created at the same time. So this case here would fail
because `&x` was created alone, but is relatable to `&A`.
non-directed verison of the constraint graph. Let `Tainted(x)` be the
set of all things reachable from a skolemized variable `x`.
`Tainted(x)` should not contain any regions that existed before the
step at which the skolemization was performed. So this case here
would fail because `&x` was created alone, but is relatable to `&A`.
*/
@ -313,7 +333,8 @@ use std::cell::{Cell, empty_cell};
use std::list::{List, Nil, Cons};
use region::is_subregion_of;
use ty::{Region, RegionVid};
use ty::{Region, RegionVid, re_static, re_infer, re_free, re_bound,
re_scope, ReVar, ReSkolemized};
use syntax::codemap;
use to_str::ToStr;
use util::ppaux::note_and_explain_region;
@ -350,7 +371,7 @@ impl Constraint : cmp::Eq {
}
impl Constraint : to_bytes::IterBytes {
pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) {
pure fn iter_bytes(+lsb0: bool, f: to_bytes::Cb) {
match self {
ConstrainVarSubVar(ref v0, ref v1) =>
to_bytes::iter_bytes_3(&0u8, v0, v1, lsb0, f),
@ -394,10 +415,11 @@ type CombineMap = HashMap<TwoRegions, RegionVid>;
struct RegionVarBindings {
tcx: ty::ctxt,
var_spans: DVec<span>,
values: Cell<~[ty::Region]>,
values: Cell<~[Region]>,
constraints: HashMap<Constraint, span>,
lubs: CombineMap,
glbs: CombineMap,
mut skolemization_count: uint,
// The undo log records actions that might later be undone.
//
@ -407,7 +429,7 @@ struct RegionVarBindings {
// actively snapshotting. The reason for this is that otherwise
// we end up adding entries for things like the lower bound on
// a variable and so forth, which can never be rolled back.
undo_log: DVec<UndoLogEntry>
mut undo_log: ~[UndoLogEntry]
}
fn RegionVarBindings(tcx: ty::ctxt) -> RegionVarBindings {
@ -418,7 +440,8 @@ fn RegionVarBindings(tcx: ty::ctxt) -> RegionVarBindings {
constraints: HashMap(),
lubs: CombineMap(),
glbs: CombineMap(),
undo_log: DVec()
skolemization_count: 0,
undo_log: ~[]
}
}
@ -430,11 +453,11 @@ fn CombineMap() -> CombineMap {
}
impl RegionVarBindings {
fn in_snapshot() -> bool {
fn in_snapshot(&self) -> bool {
self.undo_log.len() > 0
}
fn start_snapshot() -> uint {
fn start_snapshot(&self) -> uint {
debug!("RegionVarBindings: snapshot()=%u", self.undo_log.len());
if self.in_snapshot() {
self.undo_log.len()
@ -444,14 +467,14 @@ impl RegionVarBindings {
}
}
fn commit() {
fn commit(&self) {
debug!("RegionVarBindings: commit()");
while self.undo_log.len() > 0 {
self.undo_log.pop();
}
}
fn rollback_to(snapshot: uint) {
fn rollback_to(&self, snapshot: uint) {
debug!("RegionVarBindings: rollback_to(%u)", snapshot);
while self.undo_log.len() > snapshot {
let undo_item = self.undo_log.pop();
@ -472,11 +495,11 @@ impl RegionVarBindings {
}
}
fn num_vars() -> uint {
fn num_vars(&self) -> uint {
self.var_spans.len()
}
fn new_region_var(span: span) -> RegionVid {
fn new_region_var(&self, span: span) -> RegionVid {
let id = self.num_vars();
self.var_spans.push(span);
let vid = RegionVid(id);
@ -488,7 +511,13 @@ impl RegionVarBindings {
return vid;
}
fn add_constraint(+constraint: Constraint, span: span) {
fn new_skolemized(&self, br: ty::bound_region) -> Region {
let sc = self.skolemization_count;
self.skolemization_count += 1;
re_infer(ReSkolemized(sc, br))
}
fn add_constraint(&self, +constraint: Constraint, span: span) {
// cannot add constraints once regions are resolved
assert self.values.is_empty();
@ -501,21 +530,22 @@ impl RegionVarBindings {
}
}
fn make_subregion(span: span, sub: Region, sup: Region) -> cres<()> {
fn make_subregion(&self, span: span,
sub: Region, sup: Region) -> cres<()> {
// cannot add constraints once regions are resolved
assert self.values.is_empty();
debug!("RegionVarBindings: make_subregion(%?, %?)", sub, sup);
match (sub, sup) {
(ty::re_var (sub_id), ty::re_var(sup_id)) => {
(re_infer(ReVar(sub_id)), re_infer(ReVar(sup_id))) => {
self.add_constraint(ConstrainVarSubVar(sub_id, sup_id), span);
Ok(())
}
(r, ty::re_var(sup_id)) => {
(r, re_infer(ReVar(sup_id))) => {
self.add_constraint(ConstrainRegSubVar(r, sup_id), span);
Ok(())
}
(ty::re_var(sub_id), r) => {
(re_infer(ReVar(sub_id)), r) => {
self.add_constraint(ConstrainVarSubReg(sub_id, r), span);
Ok(())
}
@ -529,17 +559,17 @@ impl RegionVarBindings {
}
}
fn lub_regions(span: span, a: Region, b: Region) -> cres<Region> {
fn lub_regions(&self, span: span, a: Region, b: Region) -> cres<Region> {
// cannot add constraints once regions are resolved
assert self.values.is_empty();
debug!("RegionVarBindings: lub_regions(%?, %?)", a, b);
match (a, b) {
(ty::re_static, _) | (_, ty::re_static) => {
Ok(ty::re_static) // nothing lives longer than static
(re_static, _) | (_, re_static) => {
Ok(re_static) // nothing lives longer than static
}
(ty::re_var(*), _) | (_, ty::re_var(*)) => {
(re_infer(ReVar(*)), _) | (_, re_infer(ReVar(*))) => {
self.combine_vars(
self.lubs, a, b, span,
|old_r, new_r| self.make_subregion(span, old_r, new_r))
@ -551,18 +581,18 @@ impl RegionVarBindings {
}
}
fn glb_regions(span: span, a: Region, b: Region) -> cres<Region> {
fn glb_regions(&self, span: span, a: Region, b: Region) -> cres<Region> {
// cannot add constraints once regions are resolved
assert self.values.is_empty();
debug!("RegionVarBindings: glb_regions(%?, %?)", a, b);
match (a, b) {
(ty::re_static, r) | (r, ty::re_static) => {
(re_static, r) | (r, re_static) => {
// static lives longer than everything else
Ok(r)
}
(ty::re_var(*), _) | (_, ty::re_var(*)) => {
(re_infer(ReVar(*)), _) | (_, re_infer(ReVar(*))) => {
self.combine_vars(
self.glbs, a, b, span,
|old_r, new_r| self.make_subregion(span, new_r, old_r))
@ -574,7 +604,7 @@ impl RegionVarBindings {
}
}
fn resolve_var(rid: RegionVid) -> ty::Region {
fn resolve_var(&self, rid: RegionVid) -> ty::Region {
debug!("RegionVarBindings: resolve_var(%?=%u)", rid, *rid);
if self.values.is_empty() {
self.tcx.sess.span_bug(
@ -586,29 +616,129 @@ impl RegionVarBindings {
self.values.with_ref(|values| values[*rid])
}
fn combine_vars(combines: CombineMap, a: Region, b: Region, span: span,
fn combine_vars(&self,
combines: CombineMap,
a: Region,
b: Region,
span: span,
relate: fn(old_r: Region, new_r: Region) -> cres<()>)
-> cres<Region> {
let vars = TwoRegions { a: a, b: b };
match combines.find(vars) {
Some(c) => Ok(ty::re_var(c)),
Some(c) => Ok(re_infer(ReVar(c))),
None => {
let c = self.new_region_var(span);
combines.insert(vars, c);
if self.in_snapshot() {
self.undo_log.push(AddCombination(combines, vars));
}
do relate(a, ty::re_var(c)).then {
do relate(b, ty::re_var(c)).then {
debug!("combine_vars() c=%?", ty::re_var(c));
Ok(ty::re_var(c))
do relate(a, re_infer(ReVar(c))).then {
do relate(b, re_infer(ReVar(c))).then {
debug!("combine_vars() c=%?", c);
Ok(re_infer(ReVar(c)))
}
}
}
}
}
fn tainted(&self, snapshot: uint, r0: Region) -> ~[Region] {
/*!
*
* Computes all regions that have been related to `r0` in any
* way since the snapshot `snapshot` was taken---excluding
* `r0` itself and any region variables added as part of the
* snapshot. This is used when checking whether skolemized
* regions are being improperly related to other regions.
*/
debug!("tainted(snapshot=%u, r0=%?)", snapshot, r0);
let _indenter = indenter();
let undo_len = self.undo_log.len();
// collect variables added since the snapshot was taken
let new_vars = do vec::build |push| {
for uint::range(snapshot, undo_len) |i| {
match self.undo_log[i] {
AddVar(vid) => push(vid),
_ => ()
}
}
};
// `result_set` acts as a worklist: we explore all outgoing
// edges and add any new regions we find to result_set. This
// is not a terribly efficient implementation.
let mut result_set = ~[r0], result_index = 0;
while result_index < result_set.len() {
// nb: can't use uint::range() here because result_set grows
let r = result_set[result_index];
debug!("result_index=%u, r=%?", result_index, r);
let mut undo_index = snapshot;
while undo_index < undo_len {
// nb: can't use uint::range() here as we move result_set
let regs = match self.undo_log[undo_index] {
AddConstraint(ConstrainVarSubVar(ref a, ref b)) => {
Some((re_infer(ReVar(*a)),
re_infer(ReVar(*b))))
}
AddConstraint(ConstrainRegSubVar(ref a, ref b)) => {
Some((*a, re_infer(ReVar(*b))))
}
AddConstraint(ConstrainVarSubReg(ref a, ref b)) => {
Some((re_infer(ReVar(*a)), *b))
}
_ => {
None
}
};
match regs {
None => {}
Some((ref r1, ref r2)) => {
result_set =
consider_adding_edge(move result_set, &r, r1, r2);
result_set =
consider_adding_edge(move result_set, &r, r2, r1);
}
}
undo_index += 1;
}
result_index += 1;
}
// Drop `r0` itself and any region variables that were created
// since the snapshot.
result_set.retain(|r| {
match *r {
re_infer(ReVar(ref vid)) => !new_vars.contains(vid),
_ => *r != r0
}
});
return result_set;
fn consider_adding_edge(+result_set: ~[Region],
r: &Region,
r1: &Region,
r2: &Region) -> ~[Region]
{
let mut result_set = move result_set;
if *r == *r1 { // Clearly, this is potentially inefficient.
if !result_set.contains(r2) {
result_set.push(*r2);
}
}
return move result_set;
}
}
/**
This function performs the actual region resolution. It must be
called after all constraints have been added. It performs a
@ -616,32 +746,32 @@ impl RegionVarBindings {
constraints, assuming such values can be found; if they cannot,
errors are reported.
*/
fn resolve_regions() {
fn resolve_regions(&self) {
debug!("RegionVarBindings: resolve_regions()");
self.values.put_back(self.infer_variable_values());
}
}
priv impl RegionVarBindings {
fn is_subregion_of(sub: Region, sup: Region) -> bool {
fn is_subregion_of(&self, sub: Region, sup: Region) -> bool {
is_subregion_of(self.tcx.region_map, sub, sup)
}
fn lub_concrete_regions(+a: Region, +b: Region) -> Region {
fn lub_concrete_regions(&self, +a: Region, +b: Region) -> Region {
match (a, b) {
(ty::re_static, _) | (_, ty::re_static) => {
ty::re_static // nothing lives longer than static
(re_static, _) | (_, re_static) => {
re_static // nothing lives longer than static
}
(ty::re_var(v_id), _) | (_, ty::re_var(v_id)) => {
(re_infer(ReVar(v_id)), _) | (_, re_infer(ReVar(v_id))) => {
self.tcx.sess.span_bug(
self.var_spans[*v_id],
fmt!("lub_concrete_regions invoked with \
non-concrete regions: %?, %?", a, b));
}
(f @ ty::re_free(f_id, _), ty::re_scope(s_id)) |
(ty::re_scope(s_id), f @ ty::re_free(f_id, _)) => {
(f @ re_free(f_id, _), re_scope(s_id)) |
(re_scope(s_id), f @ re_free(f_id, _)) => {
// A "free" region can be interpreted as "some region
// at least as big as the block f_id". So, we can
// reasonably compare free regions and scopes:
@ -654,98 +784,103 @@ priv impl RegionVarBindings {
// otherwise, we don't know what the free region is,
// so we must conservatively say the LUB is static:
_ => ty::re_static
_ => re_static
}
}
(ty::re_scope(a_id), ty::re_scope(b_id)) => {
(re_scope(a_id), re_scope(b_id)) => {
// The region corresponding to an outer block is a
// subtype of the region corresponding to an inner
// block.
let rm = self.tcx.region_map;
match region::nearest_common_ancestor(rm, a_id, b_id) {
Some(r_id) => ty::re_scope(r_id),
_ => ty::re_static
Some(r_id) => re_scope(r_id),
_ => re_static
}
}
// For these types, we cannot define any additional
// relationship:
(ty::re_free(_, _), ty::re_free(_, _)) |
(ty::re_bound(_), ty::re_bound(_)) |
(ty::re_bound(_), ty::re_free(_, _)) |
(ty::re_bound(_), ty::re_scope(_)) |
(ty::re_free(_, _), ty::re_bound(_)) |
(ty::re_scope(_), ty::re_bound(_)) => {
if a == b {a} else {ty::re_static}
(re_infer(ReSkolemized(*)), _) |
(_, re_infer(ReSkolemized(*))) |
(re_free(_, _), re_free(_, _)) |
(re_bound(_), re_bound(_)) |
(re_bound(_), re_free(_, _)) |
(re_bound(_), re_scope(_)) |
(re_free(_, _), re_bound(_)) |
(re_scope(_), re_bound(_)) => {
if a == b {a} else {re_static}
}
}
}
fn glb_concrete_regions(+a: Region, +b: Region) -> cres<Region> {
fn glb_concrete_regions(&self, +a: Region, +b: Region) -> cres<Region> {
match (a, b) {
(ty::re_static, r) | (r, ty::re_static) => {
// static lives longer than everything else
Ok(r)
}
(ty::re_var(v_id), _) | (_, ty::re_var(v_id)) => {
self.tcx.sess.span_bug(
self.var_spans[*v_id],
fmt!("glb_concrete_regions invoked with \
non-concrete regions: %?, %?", a, b));
}
(ty::re_free(f_id, _), s @ ty::re_scope(s_id)) |
(s @ ty::re_scope(s_id), ty::re_free(f_id, _)) => {
// Free region is something "at least as big as
// `f_id`." If we find that the scope `f_id` is bigger
// than the scope `s_id`, then we can say that the GLB
// is the scope `s_id`. Otherwise, as we do not know
// big the free region is precisely, the GLB is undefined.
let rm = self.tcx.region_map;
match region::nearest_common_ancestor(rm, f_id, s_id) {
Some(r_id) if r_id == f_id => Ok(s),
_ => Err(ty::terr_regions_no_overlap(b, a))
(re_static, r) | (r, re_static) => {
// static lives longer than everything else
Ok(r)
}
}
(ty::re_scope(a_id), ty::re_scope(b_id)) |
(ty::re_free(a_id, _), ty::re_free(b_id, _)) => {
if a == b {
// Same scope or same free identifier, easy case.
Ok(a)
} else {
// We want to generate the intersection of two
// scopes or two free regions. So, if one of
// these scopes is a subscope of the other, return
// it. Otherwise fail.
(re_infer(ReVar(v_id)), _) |
(_, re_infer(ReVar(v_id))) => {
self.tcx.sess.span_bug(
self.var_spans[*v_id],
fmt!("glb_concrete_regions invoked with \
non-concrete regions: %?, %?", a, b));
}
(re_free(f_id, _), s @ re_scope(s_id)) |
(s @ re_scope(s_id), re_free(f_id, _)) => {
// Free region is something "at least as big as
// `f_id`." If we find that the scope `f_id` is bigger
// than the scope `s_id`, then we can say that the GLB
// is the scope `s_id`. Otherwise, as we do not know
// big the free region is precisely, the GLB is undefined.
let rm = self.tcx.region_map;
match region::nearest_common_ancestor(rm, a_id, b_id) {
Some(r_id) if a_id == r_id => Ok(ty::re_scope(b_id)),
Some(r_id) if b_id == r_id => Ok(ty::re_scope(a_id)),
_ => Err(ty::terr_regions_no_overlap(b, a))
match region::nearest_common_ancestor(rm, f_id, s_id) {
Some(r_id) if r_id == f_id => Ok(s),
_ => Err(ty::terr_regions_no_overlap(b, a))
}
}
}
// For these types, we cannot define any additional
// relationship:
(ty::re_bound(_), ty::re_bound(_)) |
(ty::re_bound(_), ty::re_free(_, _)) |
(ty::re_bound(_), ty::re_scope(_)) |
(ty::re_free(_, _), ty::re_bound(_)) |
(ty::re_scope(_), ty::re_bound(_)) => {
if a == b {
Ok(a)
} else {
Err(ty::terr_regions_no_overlap(b, a))
(re_scope(a_id), re_scope(b_id)) |
(re_free(a_id, _), re_free(b_id, _)) => {
if a == b {
// Same scope or same free identifier, easy case.
Ok(a)
} else {
// We want to generate the intersection of two
// scopes or two free regions. So, if one of
// these scopes is a subscope of the other, return
// it. Otherwise fail.
let rm = self.tcx.region_map;
match region::nearest_common_ancestor(rm, a_id, b_id) {
Some(r_id) if a_id == r_id => Ok(re_scope(b_id)),
Some(r_id) if b_id == r_id => Ok(re_scope(a_id)),
_ => Err(ty::terr_regions_no_overlap(b, a))
}
}
}
// For these types, we cannot define any additional
// relationship:
(re_infer(ReSkolemized(*)), _) |
(_, re_infer(ReSkolemized(*))) |
(re_bound(_), re_bound(_)) |
(re_bound(_), re_free(_, _)) |
(re_bound(_), re_scope(_)) |
(re_free(_, _), re_bound(_)) |
(re_scope(_), re_bound(_)) => {
if a == b {
Ok(a)
} else {
Err(ty::terr_regions_no_overlap(b, a))
}
}
}
}
}
fn report_type_error(span: span, terr: &ty::type_err) {
fn report_type_error(&self, span: span, terr: &ty::type_err) {
let terr_str = ty::type_err_to_str(self.tcx, terr);
self.tcx.sess.span_err(span, terr_str);
}
@ -803,14 +938,14 @@ fn TwoRegionsMap() -> TwoRegionsMap {
}
impl RegionVarBindings {
fn infer_variable_values() -> ~[Region] {
fn infer_variable_values(&self) -> ~[Region] {
let graph = self.construct_graph();
self.expansion(&graph);
self.contraction(&graph);
self.extract_regions_and_report_errors(&graph)
}
fn construct_graph() -> Graph {
fn construct_graph(&self) -> Graph {
let num_vars = self.num_vars();
let num_edges = self.constraints.size();
@ -871,7 +1006,7 @@ impl RegionVarBindings {
}
}
fn expansion(graph: &Graph) {
fn expansion(&self, graph: &Graph) {
do self.iterate_until_fixed_point(~"Expansion", graph) |edge| {
match edge.constraint {
ConstrainRegSubVar(copy a_region, copy b_vid) => {
@ -895,7 +1030,8 @@ impl RegionVarBindings {
}
}
fn expand_node(a_region: Region,
fn expand_node(&self,
a_region: Region,
b_vid: RegionVid,
b_node: &GraphNode) -> bool {
debug!("expand_node(%?, %? == %?)",
@ -929,7 +1065,7 @@ impl RegionVarBindings {
}
}
fn contraction(graph: &Graph) {
fn contraction(&self, graph: &Graph) {
do self.iterate_until_fixed_point(~"Contraction", graph) |edge| {
match edge.constraint {
ConstrainRegSubVar(*) => {
@ -953,33 +1089,34 @@ impl RegionVarBindings {
}
}
fn contract_node(a_vid: RegionVid,
fn contract_node(&self,
a_vid: RegionVid,
a_node: &GraphNode,
b_region: Region) -> bool {
debug!("contract_node(%? == %?/%?, %?)",
a_vid, a_node.value, a_node.classification, b_region);
return match a_node.value {
NoValue => {
assert a_node.classification == Contracting;
a_node.value = Value(b_region);
true // changed
}
ErrorValue => {
false // no change
}
Value(copy a_region) => {
match a_node.classification {
Expanding => {
check_node(&self, a_vid, a_node, a_region, b_region)
}
Contracting => {
adjust_node(&self, a_vid, a_node, a_region, b_region)
}
NoValue => {
assert a_node.classification == Contracting;
a_node.value = Value(b_region);
true // changed
}
ErrorValue => {
false // no change
}
Value(copy a_region) => {
match a_node.classification {
Expanding => {
check_node(self, a_vid, a_node, a_region, b_region)
}
Contracting => {
adjust_node(self, a_vid, a_node, a_region, b_region)
}
}
}
}
};
fn check_node(self: &RegionVarBindings,
@ -1001,25 +1138,26 @@ impl RegionVarBindings {
a_region: Region,
b_region: Region) -> bool {
match self.glb_concrete_regions(a_region, b_region) {
Ok(glb) => {
if glb == a_region {
false
} else {
debug!("Contracting value of %? from %? to %?",
a_vid, a_region, glb);
a_node.value = Value(glb);
true
Ok(glb) => {
if glb == a_region {
false
} else {
debug!("Contracting value of %? from %? to %?",
a_vid, a_region, glb);
a_node.value = Value(glb);
true
}
}
Err(_) => {
a_node.value = ErrorValue;
false
}
}
Err(_) => {
a_node.value = ErrorValue;
false
}
}
}
}
fn iterate_until_fixed_point(
&self,
tag: ~str,
graph: &Graph,
body: fn(edge: &GraphEdge) -> bool)
@ -1040,7 +1178,7 @@ impl RegionVarBindings {
debug!("---- %s Complete after %u iteration(s)", tag, iteration);
}
fn extract_regions_and_report_errors(graph: &Graph) -> ~[Region] {
fn extract_regions_and_report_errors(&self, graph: &Graph) -> ~[Region] {
let dup_map = TwoRegionsMap();
graph.nodes.mapi(|idx, node| {
match node.value {
@ -1050,7 +1188,7 @@ impl RegionVarBindings {
self.tcx.sess.span_err(
node.span,
fmt!("Unconstrained region variable #%u", idx));
ty::re_static
re_static
}
ErrorValue => {
@ -1065,21 +1203,23 @@ impl RegionVarBindings {
graph, dup_map, node_vid);
}
}
ty::re_static
re_static
}
}
})
}
// Used to suppress reporting the same basic error over and over
fn is_reported(dup_map: TwoRegionsMap,
fn is_reported(&self,
dup_map: TwoRegionsMap,
r_a: Region,
r_b: Region) -> bool {
let key = TwoRegions { a: r_a, b: r_b };
!dup_map.insert(key, ())
}
fn report_error_for_expanding_node(graph: &Graph,
fn report_error_for_expanding_node(&self,
graph: &Graph,
dup_map: TwoRegionsMap,
node_idx: RegionVid) {
// Errors in expanding nodes result from a lower-bound that is
@ -1131,7 +1271,8 @@ impl RegionVarBindings {
}
}
fn report_error_for_contracting_node(graph: &Graph,
fn report_error_for_contracting_node(&self,
graph: &Graph,
dup_map: TwoRegionsMap,
node_idx: RegionVid) {
// Errors in contracting nodes result from two upper-bounds
@ -1184,7 +1325,8 @@ impl RegionVarBindings {
}
}
fn collect_concrete_regions(graph: &Graph,
fn collect_concrete_regions(&self,
graph: &Graph,
orig_node_idx: RegionVid,
dir: Direction) -> ~[SpannedRegion] {
let set = HashMap();
@ -1226,7 +1368,8 @@ impl RegionVarBindings {
return result;
}
fn each_edge(graph: &Graph,
fn each_edge(&self,
graph: &Graph,
node_idx: RegionVid,
dir: Direction,
op: fn(edge: &GraphEdge) -> bool) {

View file

@ -148,21 +148,21 @@ impl resolve_state {
fn resolve_region(orig: ty::Region) -> ty::Region {
debug!("Resolve_region(%s)", orig.to_str(self.infcx));
match orig {
ty::re_var(rid) => self.resolve_region_var(rid),
ty::re_infer(ty::ReVar(rid)) => self.resolve_region_var(rid),
_ => orig
}
}
fn resolve_region_var(rid: RegionVid) -> ty::Region {
if !self.should(resolve_rvar) {
return ty::re_var(rid)
return ty::re_infer(ty::ReVar(rid));
}
self.infcx.region_vars.resolve_var(rid)
}
fn assert_not_rvar(rid: RegionVid, r: ty::Region) {
match r {
ty::re_var(rid2) => {
ty::re_infer(ty::ReVar(rid2)) => {
self.err = Some(region_var_bound_by_region_var(rid, rid2));
}
_ => { }

View file

@ -1,6 +1,9 @@
use combine::*;
use unify::*;
use to_str::ToStr;
use std::list;
fn macros() { include!("macros.rs"); } // FIXME(#3114): Macro import/export.
enum Sub = combine_fields; // "subtype", "subregion" etc
@ -125,10 +128,21 @@ impl Sub: combine {
}
fn fns(a: &ty::FnTy, b: &ty::FnTy) -> cres<ty::FnTy> {
debug!("fns(a=%s, b=%s)", a.to_str(self.infcx), b.to_str(self.infcx));
let _indenter = indenter();
// Rather than checking the subtype relationship between `a` and `b`
// as-is, we need to do some extra work here in order to make sure
// that function subtyping works correctly with respect to regions
// (issue #2263).
//
// A rather detailed discussion of what's going on here can be
// found in the region_inference.rs module.
// Take a snapshot. We'll never roll this back, but in later
// phases we do want to be able to examine "all bindings that
// were created as part of this type comparison", and making a
// snapshot is a convenient way to do that.
let snapshot = self.infcx.region_vars.start_snapshot();
// First, we instantiate each bound region in the subtype with a fresh
// region variable.
@ -140,26 +154,50 @@ impl Sub: combine {
// for it. The only thing we're doing with `br` here is
// using it in the debug message.
let rvar = self.infcx.next_region_var_nb(self.span);
debug!("Bound region %s maps to %s",
debug!("Bound region %s maps to %?",
bound_region_to_str(self.infcx.tcx, br),
region_to_str(self.infcx.tcx, rvar));
rvar);
rvar
}
};
// Second, we instantiate each bound region in the supertype with a
// fresh concrete region.
let {fn_ty: b_fn_ty, _} = {
let {fn_ty: b_fn_ty, isr: skol_isr, _} = {
do replace_bound_regions_in_fn_ty(self.infcx.tcx, @Nil,
None, b) |br| {
// FIXME: eventually re_skolemized (issue #2263)
ty::re_bound(br)
let skol = self.infcx.region_vars.new_skolemized(br);
debug!("Bound region %s skolemized to %?",
bound_region_to_str(self.infcx.tcx, br),
skol);
skol
}
};
// Try to compare the supertype and subtype now that they've been
// instantiated.
super_fns(&self, &a_fn_ty, &b_fn_ty)
debug!("a_fn_ty=%s", a_fn_ty.to_str(self.infcx));
debug!("b_fn_ty=%s", b_fn_ty.to_str(self.infcx));
// Compare types now that bound regions have been replaced.
let fn_ty = if_ok!(super_fns(&self, &a_fn_ty, &b_fn_ty));
// Presuming type comparison succeeds, we need to check
// that the skolemized regions do not "leak".
for list::each(skol_isr) |pair| {
let (skol_br, skol) = *pair;
let tainted = self.infcx.region_vars.tainted(snapshot, skol);
for tainted.each |tainted_region| {
// A is not as polymorphic as B:
if self.a_is_expected {
return Err(ty::terr_regions_insufficiently_polymorphic(
skol_br, *tainted_region));
} else {
return Err(ty::terr_regions_overly_polymorphic(
skol_br, *tainted_region));
}
}
}
return Ok(fn_ty)
}
// Traits please (FIXME: #2794):

View file

@ -23,6 +23,12 @@ impl ty::Region: ToStr {
}
}
impl ty::FnTy: ToStr {
fn to_str(cx: infer_ctxt) -> ~str {
ty::mk_fn(cx.tcx, self).to_str(cx)
}
}
impl<V:Copy ToStr> bound<V>: ToStr {
fn to_str(cx: infer_ctxt) -> ~str {
match self {

View file

@ -133,7 +133,7 @@ mod middle {
#[legacy_exports]
mod lub;
#[legacy_exports]
mod region_var_bindings;
mod region_inference;
#[legacy_exports]
mod resolve;
#[legacy_exports]

View file

@ -6,7 +6,8 @@ use middle::ty::{bound_copy, bound_const, bound_owned, bound_send,
use middle::ty::{bound_region, br_anon, br_named, br_self, br_cap_avoid};
use middle::ty::{ck_block, ck_box, ck_uniq, ctxt, field, method};
use middle::ty::{mt, t, param_bound};
use middle::ty::{re_bound, re_free, re_scope, re_var, re_static, Region};
use middle::ty::{re_bound, re_free, re_scope, re_infer, re_static, Region};
use middle::ty::{ReSkolemized, ReVar};
use middle::ty::{ty_bool, ty_bot, ty_box, ty_class, ty_enum};
use middle::ty::{ty_estr, ty_evec, ty_float, ty_fn, ty_trait, ty_int};
use middle::ty::{ty_nil, ty_opaque_box, ty_opaque_closure_ptr, ty_param};
@ -95,7 +96,7 @@ fn explain_region_and_span(cx: ctxt, region: ty::Region)
// I believe these cases should not occur (except when debugging,
// perhaps)
re_var(_) | re_bound(_) => {
re_infer(_) | re_bound(_) => {
(fmt!("lifetime %?", region), None)
}
};
@ -184,8 +185,9 @@ fn region_to_str(cx: ctxt, region: Region) -> ~str {
re_scope(_) => ~"&",
re_bound(br) => bound_region_to_str(cx, br),
re_free(_, br) => bound_region_to_str(cx, br),
re_var(_) => ~"&",
re_static => ~"&static"
re_infer(ReSkolemized(_, br)) => bound_region_to_str(cx, br),
re_infer(ReVar(_)) => ~"&",
re_static => ~"&static"
}
}

View file

@ -1,24 +1,47 @@
// Here, `f` is a function that takes a pointer `x` and a function
// `g`, where `g` requires its argument `y` to be in the same region
// that `x` is in.
fn has_same_region(f: fn(x: &a/int, g: fn(y: &a/int))) {
// Somewhat counterintuitively, this fails because, in
// `wants_two_regions`, the `g` argument needs to be able to
// accept any region. That is, the type that `has_same_region`
// expects is *not* a subtype of the type that `wants_two_regions`
// expects.
wants_two_regions(f); //~ ERROR mismatched types
fn of<T>() -> @fn(T) { fail; }
fn subtype<T>(x: @fn(T)) { fail; }
fn test_fn<T>(_x: &x/T, _y: &y/T, _z: &z/T) {
// Here, x, y, and z are free. Other letters
// are bound. Note that the arrangement
// subtype::<T1>(of::<T2>()) will typecheck
// iff T1 <: T2.
subtype::<fn(&a/T)>(
of::<fn(&a/T)>());
subtype::<fn(&a/T)>(
of::<fn(&b/T)>());
subtype::<fn(&b/T)>(
of::<fn(&x/T)>());
subtype::<fn(&x/T)>(
of::<fn(&b/T)>()); //~ ERROR mismatched types
subtype::<fn(&a/T, &b/T)>(
of::<fn(&a/T, &a/T)>());
subtype::<fn(&a/T, &a/T)>(
of::<fn(&a/T, &b/T)>()); //~ ERROR mismatched types
subtype::<fn(&a/T, &b/T)>(
of::<fn(&x/T, &y/T)>());
subtype::<fn(&x/T, &y/T)>(
of::<fn(&a/T, &b/T)>()); //~ ERROR mismatched types
subtype::<fn(&x/T) -> @fn(&a/T)>(
of::<fn(&x/T) -> @fn(&a/T)>());
subtype::<fn(&a/T) -> @fn(&a/T)>(
of::<fn(&a/T) -> @fn(&b/T)>()); //~ ERROR mismatched types
subtype::<fn(&a/T) -> @fn(&a/T)>(
of::<fn(&x/T) -> @fn(&b/T)>()); //~ ERROR mismatched types
subtype::<fn(&a/T) -> @fn(&b/T)>(
of::<fn(&a/T) -> @fn(&a/T)>());
}
fn wants_two_regions(_f: fn(x: &int, g: fn(y: &int))) {
// Suppose we were to write code here that passed some arbitrary
// &int and some arbitrary fn(&int) to whatever's passed in as _f.
// This would be fine as far as the type annotation on the formal
// parameter _f goes, but if _f were `f` we'd be in trouble since
// `f` can't handle those arguments.
}
fn main() {
}
fn main() {}