From 117e5e35831ded02e71554d2e13d6300215119b0 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Thu, 1 Nov 2012 15:16:46 -0700 Subject: [PATCH] Implement LUB algorithm and add new unit-testing infrastructure for infer. r=brson --- src/librustc/middle/ty.rs | 25 ++ src/librustc/middle/typeck.rs | 23 ++ src/librustc/middle/typeck/check.rs | 23 -- src/librustc/middle/typeck/infer.rs | 19 ++ src/librustc/middle/typeck/infer/combine.rs | 5 +- src/librustc/middle/typeck/infer/lub.rs | 108 ++++++- .../middle/typeck/infer/region_inference.rs | 272 +++++++++++++--- src/librustc/middle/typeck/infer/sub.rs | 34 +- src/librustc/middle/typeck/infer/test.rs | 301 ++++++++++++++++++ src/librustc/rustc.rc | 3 + src/test/compile-fail/regions-fns.rs | 20 +- src/test/run-pass/regions-equiv-fns.rs | 13 + 12 files changed, 727 insertions(+), 119 deletions(-) create mode 100644 src/librustc/middle/typeck/infer/test.rs create mode 100644 src/test/run-pass/regions-equiv-fns.rs diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index cb567028ac9..864619f2759 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -41,6 +41,7 @@ export expr_is_lval, expr_kind; export ExprKind, LvalueExpr, RvalueDatumExpr, RvalueDpsExpr, RvalueStmtExpr; export field_ty; export fold_ty, fold_sty_to_ty, fold_region, fold_regions; +export apply_op_on_t_to_ty_fn; export fold_regions_and_ty, walk_regions_and_ty; export field; export field_idx, field_idx_strict; @@ -1482,6 +1483,30 @@ fn fold_regions_and_ty( } } +/* A little utility: it often happens that I have a `fn_ty`, + * but I want to use some function like `fold_regions_and_ty()` + * that is defined over all types. This utility converts to + * a full type and back. It's not the best way to do this (somewhat + * inefficient to do the conversion), it would be better to refactor + * all this folding business. However, I've been waiting on that + * until trait support is improved. */ +fn apply_op_on_t_to_ty_fn( + cx: ctxt, + f: &FnTy, + t_op: fn(t) -> t) -> FnTy +{ + let t0 = ty::mk_fn(cx, *f); + let t1 = t_op(t0); + match ty::get(t1).sty { + ty::ty_fn(copy f) => { + move f + } + _ => { + cx.sess.bug(~"`t_op` did not return a function type"); + } + } +} + // n.b. this function is intended to eventually replace fold_region() below, // that is why its name is so similar. fn fold_regions( diff --git a/src/librustc/middle/typeck.rs b/src/librustc/middle/typeck.rs index 977fc06ce86..421c68fa9a2 100644 --- a/src/librustc/middle/typeck.rs +++ b/src/librustc/middle/typeck.rs @@ -251,6 +251,29 @@ fn require_same_types( } } +// a list of mapping from in-scope-region-names ("isr") to the +// corresponding ty::Region +type isr_alist = @List<(ty::bound_region, ty::Region)>; + +trait get_and_find_region { + fn get(br: ty::bound_region) -> ty::Region; + fn find(br: ty::bound_region) -> Option; +} + +impl isr_alist: get_and_find_region { + fn get(br: ty::bound_region) -> ty::Region { + self.find(br).get() + } + + fn find(br: ty::bound_region) -> Option { + for list::each(self) |isr| { + let (isr_br, isr_r) = *isr; + if isr_br == br { return Some(isr_r); } + } + return None; + } +} + fn arg_is_argv_ty(tcx: ty::ctxt, a: ty::arg) -> bool { match ty::resolved_mode(tcx, a.mode) { ast::by_val => { /*ok*/ } diff --git a/src/librustc/middle/typeck/check.rs b/src/librustc/middle/typeck/check.rs index ef445106cf8..ee93c7fccef 100644 --- a/src/librustc/middle/typeck/check.rs +++ b/src/librustc/middle/typeck/check.rs @@ -166,29 +166,6 @@ fn blank_fn_ctxt(ccx: @crate_ctxt, rty: ty::t, } } -// a list of mapping from in-scope-region-names ("isr") to the -// corresponding ty::Region -type isr_alist = @List<(ty::bound_region, ty::Region)>; - -trait get_and_find_region { - fn get(br: ty::bound_region) -> ty::Region; - fn find(br: ty::bound_region) -> Option; -} - -impl isr_alist: get_and_find_region { - fn get(br: ty::bound_region) -> ty::Region { - self.find(br).get() - } - - fn find(br: ty::bound_region) -> Option { - for list::each(self) |isr| { - let (isr_br, isr_r) = *isr; - if isr_br == br { return Some(isr_r); } - } - return None; - } -} - fn check_item_types(ccx: @crate_ctxt, crate: @ast::crate) { let visit = visit::mk_simple_visitor(@{ visit_item: |a| check_item(ccx, a), diff --git a/src/librustc/middle/typeck/infer.rs b/src/librustc/middle/typeck/infer.rs index 56befd97342..b4ffd92c0a8 100644 --- a/src/librustc/middle/typeck/infer.rs +++ b/src/librustc/middle/typeck/infer.rs @@ -722,5 +722,24 @@ impl infer_ctxt { self.type_error_message(sp, mk_msg, a, Some(err)); } + fn replace_bound_regions_with_fresh_regions( + &self, span: span, + fty: &ty::FnTy) -> (ty::FnTy, isr_alist) + { + let {fn_ty, isr, _} = + replace_bound_regions_in_fn_ty(self.tcx, @Nil, None, fty, |br| { + // N.B.: The name of the bound region doesn't have anything to + // do with the region variable that's created for it. The + // only thing we're doing with `br` here is using it in the + // debug message. + let rvar = self.next_region_var_nb(span); + debug!("Bound region %s maps to %?", + bound_region_to_str(self.tcx, br), + rvar); + rvar + }); + (fn_ty, isr) + } + } diff --git a/src/librustc/middle/typeck/infer/combine.rs b/src/librustc/middle/typeck/infer/combine.rs index 099b0469bcf..10fc0db0f7b 100644 --- a/src/librustc/middle/typeck/infer/combine.rs +++ b/src/librustc/middle/typeck/infer/combine.rs @@ -290,7 +290,9 @@ fn super_args( fn super_vstores( self: &C, vk: ty::terr_vstore_kind, - a: ty::vstore, b: ty::vstore) -> cres { + a: ty::vstore, b: ty::vstore) -> cres +{ + debug!("%s.super_vstores(a=%?, b=%?)", self.tag(), a, b); match (a, b) { (ty::vstore_slice(a_r), ty::vstore_slice(b_r)) => { @@ -517,4 +519,3 @@ fn super_tys( _ => Err(ty::terr_sorts(expected_found(self, a, b))) } } - diff --git a/src/librustc/middle/typeck/infer/lub.rs b/src/librustc/middle/typeck/infer/lub.rs index a323ae720b2..8c3357190f7 100644 --- a/src/librustc/middle/typeck/infer/lub.rs +++ b/src/librustc/middle/typeck/infer/lub.rs @@ -3,6 +3,8 @@ use lattice::*; use to_str::ToStr; use syntax::ast::{Many, Once}; +fn macros() { include!("macros.rs"); } // FIXME(#3114): Macro import/export. + enum Lub = combine_fields; // "subtype", "subregion" etc impl Lub: combine { @@ -102,6 +104,100 @@ impl Lub: combine { } } + fn fns(a: &ty::FnTy, b: &ty::FnTy) -> cres { + // Note: this is a subtle algorithm. For a full explanation, + // please see the large comment in `region_inference.rs`. + + // 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(); + + // Instantiate each bound region with a fresh region variable. + let (a_with_fresh, a_isr) = + self.infcx.replace_bound_regions_with_fresh_regions( + self.span, a); + let (b_with_fresh, _) = + self.infcx.replace_bound_regions_with_fresh_regions( + self.span, b); + + // Collect constraints. + let fn_ty0 = if_ok!(super_fns(&self, &a_with_fresh, &b_with_fresh)); + debug!("fn_ty0 = %s", fn_ty0.to_str(self.infcx)); + + // Generalize the regions appearing in fn_ty0 if possible + let new_vars = + self.infcx.region_vars.vars_created_since_snapshot(snapshot); + let fn_ty1 = + ty::apply_op_on_t_to_ty_fn( + self.infcx.tcx, &fn_ty0, + |t| ty::fold_regions( + self.infcx.tcx, t, + |r, _in_fn| generalize_region(&self, snapshot, + new_vars, a_isr, r))); + return Ok(move fn_ty1); + + fn generalize_region(self: &Lub, + snapshot: uint, + new_vars: &[RegionVid], + a_isr: isr_alist, + r0: ty::Region) -> ty::Region { + // Regions that pre-dated the LUB computation stay as they are. + if !is_new_var(new_vars, r0) { + debug!("generalize_region(r0=%?): not new variable", r0); + return r0; + } + + let tainted = self.infcx.region_vars.tainted(snapshot, r0); + + // Variables created during LUB computation which are + // *related* to regions that pre-date the LUB computation + // stay as they are. + if !tainted.all(|r| is_new_var(new_vars, *r)) { + debug!("generalize_region(r0=%?): \ + non-new-variables found in %?", + r0, tainted); + return r0; + } + + // Otherwise, the variable must be associated with at + // least one of the variables representing bound regions + // in both A and B. Replace the variable with the "first" + // bound region from A that we find it to be associated + // with. + for list::each(a_isr) |pair| { + let (a_br, a_r) = *pair; + if tainted.contains(&a_r) { + debug!("generalize_region(r0=%?): \ + replacing with %?, tainted=%?", + r0, a_br, tainted); + return ty::re_bound(a_br); + } + } + + self.infcx.tcx.sess.span_bug( + self.span, + fmt!("Region %? is not associated with \ + any bound region from A!", r0)); + } + + fn is_new_var(new_vars: &[RegionVid], r: ty::Region) -> bool { + match r { + ty::re_infer(ty::ReVar(ref v)) => new_vars.contains(v), + _ => false + } + } + } + + fn fn_metas(a: &ty::FnMeta, b: &ty::FnMeta) -> cres { + super_fn_metas(&self, a, b) + } + + fn fn_sigs(a: &ty::FnSig, b: &ty::FnSig) -> cres { + super_fn_sigs(&self, a, b) + } + // Traits please (FIXME: #2794): fn tys(a: ty::t, b: ty::t) -> cres { @@ -125,18 +221,6 @@ impl Lub: combine { super_args(&self, a, b) } - fn fns(a: &ty::FnTy, b: &ty::FnTy) -> cres { - super_fns(&self, a, b) - } - - fn fn_metas(a: &ty::FnMeta, b: &ty::FnMeta) -> cres { - super_fn_metas(&self, a, b) - } - - fn fn_sigs(a: &ty::FnSig, b: &ty::FnSig) -> cres { - super_fn_sigs(&self, a, b) - } - fn substs(did: ast::def_id, as_: &ty::substs, bs: &ty::substs) -> cres { diff --git a/src/librustc/middle/typeck/infer/region_inference.rs b/src/librustc/middle/typeck/infer/region_inference.rs index f0f9c97720e..923e29cf863 100644 --- a/src/librustc/middle/typeck/infer/region_inference.rs +++ b/src/librustc/middle/typeck/infer/region_inference.rs @@ -320,6 +320,124 @@ set of all things reachable from a skolemized variable `x`. step at which the skolemization was performed. So this case here would fail because `&x` was created alone, but is relatable to `&A`. +## Computing the LUB and GLB + +The paper I pointed you at is written for Haskell. It does not +therefore considering subtyping and in particular does not consider +LUB or GLB computation. We have to consider this. Here is the +algorithm I implemented. + +### LUB + +The LUB algorithm proceeds in three steps: + +1. Replace all bound regions (on both sides) with fresh region + inference variables. +2. Compute the LUB "as normal", meaning compute the GLB of each + pair of argument types and the LUB of the return types and + so forth. Combine those to a new function type F. +3. Map the regions appearing in `F` using the procedure described below. + +For each region `R` that appears in `F`, we may need to replace it +with a bound region. Let `V` be the set of fresh variables created as +part of the LUB procedure (either in step 1 or step 2). You may be +wondering how variables can be created in step 2. The answer is that +when we are asked to compute the LUB or GLB of two region variables, +we do so by producing a new region variable that is related to those +two variables. i.e., The LUB of two variables `$x` and `$y` is a +fresh variable `$z` that is constrained such that `$x <= $z` and `$y +<= $z`. + +To decide how to replace a region `R`, we must examine `Tainted(R)`. +This function searches through the constraints which were generated +when computing the bounds of all the argument and return types and +produces a list of all regions to which `R` is related, directly or +indirectly. + +If `R` is not in `V` or `Tainted(R)` contains any region that is not +in `V`, then `R` is not replaced (that is, `R` is mapped to itself). +Otherwise, if `Tainted(R)` is a subset of `V`, then we select the +earliest variable in `Tainted(R)` that originates from the left-hand +side and replace `R` with a bound version of that variable. + +So, let's work through the simplest example: `fn(&A)` and `fn(&a)`. +In this case, `&a` will be replaced with `$a` (the $ indicates an +inference variable) which will be linked to the free region `&A`, and +hence `V = { $a }` and `Tainted($a) = { &A }`. Since `$a` is not a +member of `V`, we leave `$a` as is. When region inference happens, +`$a` will be resolved to `&A`, as we wanted. + +So, let's work through the simplest example: `fn(&A)` and `fn(&a)`. +In this case, `&a` will be replaced with `$a` (the $ indicates an +inference variable) which will be linked to the free region `&A`, and +hence `V = { $a }` and `Tainted($a) = { $a, &A }`. Since `&A` is not a +member of `V`, we leave `$a` as is. When region inference happens, +`$a` will be resolved to `&A`, as we wanted. + +Let's look at a more complex one: `fn(&a, &b)` and `fn(&x, &x)`. +In this case, we'll end up with a graph that looks like: + +``` + $a $b *--$x + \ \ / / + \ $h-* / + $g-----------* +``` + +Here `$g` and `$h` are fresh variables that are created to represent +the LUB/GLB of things requiring inference. This means that `V` and +`Tainted` will look like: + +``` +V = {$a, $b, $x} +Tainted($g) = Tainted($h) = { $a, $b, $h, $x } +``` + +Therefore we replace both `$g` and `$h` with `$a`, and end up +with the type `fn(&a, &a)`. + +### GLB + +The procedure for computing the GLB is similar. The difference lies +in computing the replacements for the various variables. For each +region `R` that appears in the type `F`, we again compute `Tainted(R)` +and examine the results: + +1. If `Tainted(R) = {R}` is a singleton set, replace `R` with itself. +2. Else, if `Tainted(R)` contains only variables in `V`, and it + contains exactly one variable from the LHS and one variable from + the RHS, then `R` can be mapped to the bound version of the + variable from the LHS. +3. Else, `R` is mapped to a fresh bound variable. + +These rules are pretty complex. Let's look at some examples to see +how they play out. + +Out first example was `fn(&a)` and `fn(&X)`---in +this case, the LUB will be a variable `$g`, and `Tainted($g) = +{$g,$a,$x}`. By these rules, we'll replace `$g` with a fresh bound +variable, so the result is `fn(&z)`, which is fine. + +The next example is `fn(&A)` and `fn(&Z)`. XXX + +The next example is `fn(&a, &b)` and `fn(&x, &x)`. In this case, as +before, we'll end up with `F=fn(&g, &h)` where `Tainted($g) = +Tainted($h) = {$g, $a, $b, $x}`. This means that we'll select fresh +bound varibales `g` and `h` and wind up with `fn(&g, &h)`. + +For the last example, let's consider what may seem trivial, but is +not: `fn(&a, &a)` and `fn(&x, &x)`. In this case, we'll get `F=fn(&g, +&h)` where `Tainted($g) = {$g, $a, $x}` and `Tainted($h) = {$h, $a, +$x}`. Both of these sets contain exactly one bound variable from each +side, so we'll map them both to `&a`, resulting in `fn(&a, &a)`. +Horray! + +### Why are these correct? + +You may be wondering whether this algorithm is correct. So am I. But +I believe it is. (Justification forthcoming, haven't had time to +write it) + */ #[warn(deprecated_mode)]; @@ -448,7 +566,6 @@ type CombineMap = HashMap; struct RegionVarBindings { tcx: ty::ctxt, var_spans: DVec, - values: Cell<~[Region]>, constraints: HashMap, lubs: CombineMap, glbs: CombineMap, @@ -462,7 +579,12 @@ 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. - mut undo_log: ~[UndoLogEntry] + mut undo_log: ~[UndoLogEntry], + + // This contains the results of inference. It begins as an empty + // cell and only acquires a value after inference is complete. + // We use a cell vs a mutable option to circumvent borrowck errors. + values: Cell<~[GraphNodeValue]>, } fn RegionVarBindings(tcx: ty::ctxt) -> RegionVarBindings { @@ -646,7 +768,37 @@ impl RegionVarBindings { been computed!")); } - self.values.with_ref(|values| values[*rid]) + let v = self.values.with_ref(|values| values[*rid]); + match v { + Value(r) => r, + + NoValue => { + // No constraints, report an error. It is plausible + // that we could select an arbitrary region here + // instead. At the moment I am not doing this because + // this generally masks bugs in the inference + // algorithm, and given our syntax one cannot create + // generally create a lifetime variable that isn't + // used in some type, and hence all lifetime variables + // should ultimately have some bounds. + + self.tcx.sess.span_err( + self.var_spans[*rid], + fmt!("Unconstrained region variable #%u", *rid)); + + // Touch of a hack: to suppress duplicate messages, + // replace the NoValue entry with ErrorValue. + let mut values = self.values.take(); + values[*rid] = ErrorValue; + self.values.put_back(move values); + re_static + } + + ErrorValue => { + // An error that has previously been reported. + re_static + } + } } fn combine_vars(&self, @@ -676,14 +828,25 @@ impl RegionVarBindings { } } + fn vars_created_since_snapshot(&self, snapshot: uint) -> ~[RegionVid] { + do vec::build |push| { + for uint::range(snapshot, self.undo_log.len()) |i| { + match self.undo_log[i] { + AddVar(vid) => push(vid), + _ => () + } + } + } + } + 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. + * way since the snapshot `snapshot` was taken---`r0` itself + * will be the first entry. This is used when checking whether + * skolemized regions are being improperly related to other + * regions. */ debug!("tainted(snapshot=%u, r0=%?)", snapshot, r0); @@ -691,16 +854,6 @@ impl RegionVarBindings { 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. @@ -746,15 +899,6 @@ impl RegionVarBindings { 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], @@ -991,11 +1135,11 @@ fn TwoRegionsMap() -> TwoRegionsMap { } impl RegionVarBindings { - fn infer_variable_values(&self) -> ~[Region] { + fn infer_variable_values(&self) -> ~[GraphNodeValue] { let graph = self.construct_graph(); self.expansion(&graph); self.contraction(&graph); - self.extract_regions_and_report_errors(&graph) + self.extract_values_and_report_conflicts(&graph) } fn construct_graph(&self) -> Graph { @@ -1231,34 +1375,60 @@ impl RegionVarBindings { debug!("---- %s Complete after %u iteration(s)", tag, iteration); } - fn extract_regions_and_report_errors(&self, graph: &Graph) -> ~[Region] { + fn extract_values_and_report_conflicts( + &self, + graph: &Graph) -> ~[GraphNodeValue] + { let dup_map = TwoRegionsMap(); graph.nodes.mapi(|idx, node| { match node.value { - Value(v) => v, - - NoValue => { - self.tcx.sess.span_err( - node.span, - fmt!("Unconstrained region variable #%u", idx)); - re_static - } - - ErrorValue => { - let node_vid = RegionVid(idx); - match node.classification { - Expanding => { - self.report_error_for_expanding_node( - graph, dup_map, node_vid); - } - Contracting => { - self.report_error_for_contracting_node( - graph, dup_map, node_vid); - } + Value(_) => { + /* Inference successful */ + } + NoValue => { + /* Unconstrained inference: do not report an error + until the value of this variable is requested. + After all, sometimes we make region variables but never + really use their values. */ + } + ErrorValue => { + /* Inference impossible, this value contains + inconsistent constraints. + + I think that in this case we should report an + error now---unlike the case above, we can't + wait to see whether the user needs the result + of this variable. The reason is that the mere + existence of this variable implies that the + region graph is inconsistent, whether or not it + is used. + + For example, we may have created a region + variable that is the GLB of two other regions + which do not have a GLB. Even if that variable + is not used, it implies that those two regions + *should* have a GLB. + + At least I think this is true. It may be that + the mere existence of a conflict in a region variable + that is not used is not a problem, so if this rule + starts to create problems we'll have to revisit + this portion of the code and think hard about it. =) */ + let node_vid = RegionVid(idx); + match node.classification { + Expanding => { + self.report_error_for_expanding_node( + graph, dup_map, node_vid); + } + Contracting => { + self.report_error_for_contracting_node( + graph, dup_map, node_vid); + } + } } - re_static - } } + + node.value }) } diff --git a/src/librustc/middle/typeck/infer/sub.rs b/src/librustc/middle/typeck/infer/sub.rs index 80f9eb85aab..0bd53a3e800 100644 --- a/src/librustc/middle/typeck/infer/sub.rs +++ b/src/librustc/middle/typeck/infer/sub.rs @@ -125,8 +125,8 @@ impl Sub: combine { // as-is, we need to do some extra work here in order to make sure // that function subtyping works correctly with respect to regions // - // A rather detailed discussion of what's going on here can be - // found in the region_inference.rs module. + // Note: this is a subtle algorithm. For a full explanation, + // please see the large comment in `region_inference.rs`. // Take a snapshot. We'll never roll this back, but in later // phases we do want to be able to examine "all bindings that @@ -136,20 +136,9 @@ impl Sub: combine { // First, we instantiate each bound region in the subtype with a fresh // region variable. - let {fn_ty: a_fn_ty, _} = { - do replace_bound_regions_in_fn_ty(self.infcx.tcx, @Nil, - None, a) |br| { - // N.B.: The name of the bound region doesn't have - // anything to do with the region variable that's created - // 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 %?", - bound_region_to_str(self.infcx.tcx, br), - rvar); - rvar - } - }; + let (a_fn_ty, _) = + self.infcx.replace_bound_regions_with_fresh_regions( + self.span, a); // Second, we instantiate each bound region in the supertype with a // fresh concrete region. @@ -172,10 +161,23 @@ impl Sub: combine { // Presuming type comparison succeeds, we need to check // that the skolemized regions do not "leak". + let new_vars = + self.infcx.region_vars.vars_created_since_snapshot(snapshot); 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| { + // Each skolemized should only be relatable to itself + // or new variables: + match *tainted_region { + ty::re_infer(ty::ReVar(ref vid)) => { + if new_vars.contains(vid) { loop; } + } + _ => { + if *tainted_region == skol { loop; } + } + }; + // A is not as polymorphic as B: if self.a_is_expected { return Err(ty::terr_regions_insufficiently_polymorphic( diff --git a/src/librustc/middle/typeck/infer/test.rs b/src/librustc/middle/typeck/infer/test.rs new file mode 100644 index 00000000000..53cc59fc1b0 --- /dev/null +++ b/src/librustc/middle/typeck/infer/test.rs @@ -0,0 +1,301 @@ +/** + +# Standalone Tests for the Inference Module + +Note: This module is only compiled when doing unit testing. + +*/ + +use std::getopts; +use std::map::HashMap; +use std::getopts; +use std::getopts::{opt_present}; +use std::getopts::groups; +use std::getopts::groups::{optopt, optmulti, optflag, optflagopt, getopts}; +use driver::driver::{optgroups, build_session_options, build_session, + str_input, build_configuration}; +use driver::diagnostic; +use syntax::{ast, attr, parse}; +use syntax::parse::parse_crate_from_source_str; +use middle::lang_items::{LanguageItems, language_items}; +use util::ppaux::ty_to_str; +use syntax::ast_util::dummy_sp; +use middle::ty::{FnTyBase, FnMeta, FnSig}; + +struct Env { + crate: @ast::crate, + tcx: ty::ctxt, + infcx: infer::infer_ctxt +} + +struct RH { + id: ast::node_id, + sub: &[RH] +} + +fn setup_env(test_name: &str, source_string: &str) -> Env { + let matches = getopts(~[~"-Z", ~"verbose"], optgroups()).get(); + let sessopts = build_session_options(~"rustc", matches, diagnostic::emit); + let sess = build_session(sessopts, diagnostic::emit); + let cfg = build_configuration(sess, ~"whatever", str_input(~"")); + let dm = HashMap(); + let amap = HashMap(); + let freevars = HashMap(); + let region_paramd_items = HashMap(); + let region_map = HashMap(); + let lang_items = language_items::make(); + + let parse_sess = parse::new_parse_sess(None); + let crate = parse_crate_from_source_str( + test_name.to_str(), @source_string.to_str(), + cfg, parse_sess); + + let tcx = ty::mk_ctxt(sess, dm, amap, freevars, region_map, + region_paramd_items, move lang_items, crate); + + let infcx = infer::new_infer_ctxt(tcx); + + return Env { crate: crate, tcx: tcx, infcx: infcx }; +} + +impl Env { + fn create_region_hierarchy(&self, rh: &RH) { + for rh.sub.each |child_rh| { + self.create_region_hierarchy(child_rh); + self.tcx.region_map.insert(child_rh.id, rh.id); + } + } + + fn create_simple_region_hierarchy(&self) { + // creates a region hierarchy where 1 is root, 10 and 11 are + // children of 1, etc + self.create_region_hierarchy( + &RH {id: 1, + sub: &[RH {id: 10, + sub: &[]}, + RH {id: 11, + sub: &[]}]}); + } + + fn lookup_item(&self, names: &[~str]) -> ast::node_id { + return match search_mod(self, &self.crate.node.module, 0, names) { + Some(id) => id, + None => { + fail fmt!("No item found: `%s`", str::connect(names, "::")); + } + }; + + fn search_mod(self: &Env, + m: &ast::_mod, + idx: uint, + names: &[~str]) -> Option { + assert idx < names.len(); + for m.items.each |item| { + if self.tcx.sess.str_of(item.ident) == names[idx] { + return search(self, *item, idx+1, names); + } + } + return None; + } + + fn search(self: &Env, + it: @ast::item, + idx: uint, + names: &[~str]) -> Option { + if idx == names.len() { + return Some(it.id); + } + + return match it.node { + ast::item_const(*) | ast::item_fn(*) | + ast::item_foreign_mod(*) | ast::item_ty(*) => { + None + } + + ast::item_enum(*) | ast::item_class(*) | + ast::item_trait(*) | ast::item_impl(*) | + ast::item_mac(*) => { + None + } + + ast::item_mod(ref m) => { + search_mod(self, m, idx, names) + } + }; + } + } + + fn is_subtype(&self, a: ty::t, b: ty::t) -> bool { + match infer::can_mk_subty(self.infcx, a, b) { + Ok(_) => true, + Err(_) => false + } + } + + fn assert_subtype(&self, a: ty::t, b: ty::t) { + if !self.is_subtype(a, b) { + fail fmt!("%s is not a subtype of %s, but it should be", + self.ty_to_str(a), + self.ty_to_str(b)); + } + } + + fn assert_not_subtype(&self, a: ty::t, b: ty::t) { + if self.is_subtype(a, b) { + fail fmt!("%s is a subtype of %s, but it shouldn't be", + self.ty_to_str(a), + self.ty_to_str(b)); + } + } + + fn assert_strict_subtype(&self, a: ty::t, b: ty::t) { + self.assert_subtype(a, b); + self.assert_not_subtype(b, a); + } + + fn assert_eq(&self, a: ty::t, b: ty::t) { + self.assert_subtype(a, b); + self.assert_subtype(b, a); + } + + fn ty_to_str(&self, a: ty::t) -> ~str { + ty_to_str(self.tcx, a) + } + + fn t_fn(&self, input_tys: &[ty::t], output_ty: ty::t) -> ty::t { + let inputs = input_tys.map(|t| {mode: ast::expl(ast::by_copy), + ty: *t}); + ty::mk_fn(self.tcx, FnTyBase { + meta: FnMeta {purity: ast::impure_fn, + proto: ast::ProtoBare, + onceness: ast::Many, + region: ty::re_static, + bounds: @~[], + ret_style: ast::return_val}, + sig: FnSig {inputs: move inputs, + output: output_ty} + }) + } + + fn t_int(&self) -> ty::t { + ty::mk_int(self.tcx) + } + + fn t_rptr_bound(&self, id: uint) -> ty::t { + ty::mk_imm_rptr(self.tcx, ty::re_bound(ty::br_anon(id)), self.t_int()) + } + + fn t_rptr_scope(&self, id: ast::node_id) -> ty::t { + ty::mk_imm_rptr(self.tcx, ty::re_scope(id), self.t_int()) + } + + fn t_rptr_free(&self, nid: ast::node_id, id: uint) -> ty::t { + ty::mk_imm_rptr(self.tcx, ty::re_free(nid, ty::br_anon(id)), + self.t_int()) + } + + fn t_rptr_static(&self) -> ty::t { + ty::mk_imm_rptr(self.tcx, ty::re_static, self.t_int()) + } + + fn lub() -> Lub { Lub(self.infcx.combine_fields(true, dummy_sp())) } + + /// Checks that `LUB(t1,t2) == t_lub` + fn check_lub(&self, t1: ty::t, t2: ty::t, t_lub: ty::t) { + match self.lub().tys(t1, t2) { + Err(e) => { + fail fmt!("Unexpected error computing LUB: %?", e) + } + Ok(t) => { + self.assert_eq(t, t_lub); + + // sanity check for good measure: + self.assert_subtype(t1, t); + self.assert_subtype(t2, t); + } + } + } + + /// Checks that `LUB(t1,t2)` is undefined + fn check_no_lub(&self, t1: ty::t, t2: ty::t) { + match self.lub().tys(t1, t2) { + Err(_) => {} + Ok(t) => { + fail fmt!("Unexpected success computing LUB: %?", + self.ty_to_str(t)) + } + } + } +} + +#[test] +fn contravariant_region_ptr() { + let env = setup_env("contravariant_region_ptr", ""); + env.create_simple_region_hierarchy(); + let t_rptr1 = env.t_rptr_scope(1); + let t_rptr10 = env.t_rptr_scope(10); + env.assert_eq(t_rptr1, t_rptr1); + env.assert_eq(t_rptr10, t_rptr10); + env.assert_strict_subtype(t_rptr1, t_rptr10); +} + +#[test] +fn lub_bound_bound() { + let env = setup_env("lub_bound_bound", ""); + let t_rptr_bound1 = env.t_rptr_bound(1); + let t_rptr_bound2 = env.t_rptr_bound(2); + env.check_lub(env.t_fn([t_rptr_bound1], env.t_int()), + env.t_fn([t_rptr_bound2], env.t_int()), + env.t_fn([t_rptr_bound1], env.t_int())); +} + +#[test] +fn lub_bound_free() { + let env = setup_env("lub_bound_free", ""); + let t_rptr_bound1 = env.t_rptr_bound(1); + let t_rptr_free1 = env.t_rptr_free(0, 1); + env.check_lub(env.t_fn([t_rptr_bound1], env.t_int()), + env.t_fn([t_rptr_free1], env.t_int()), + env.t_fn([t_rptr_free1], env.t_int())); +} + +#[test] +fn lub_bound_static() { + let env = setup_env("lub_bound_static", ""); + let t_rptr_bound1 = env.t_rptr_bound(1); + let t_rptr_static = env.t_rptr_static(); + env.check_lub(env.t_fn([t_rptr_bound1], env.t_int()), + env.t_fn([t_rptr_static], env.t_int()), + env.t_fn([t_rptr_static], env.t_int())); +} + +#[test] +fn lub_bound_bound_inverse_order() { + let env = setup_env("lub_bound_bound_inverse_order", ""); + let t_rptr_bound1 = env.t_rptr_bound(1); + let t_rptr_bound2 = env.t_rptr_bound(2); + env.check_lub(env.t_fn([t_rptr_bound1, t_rptr_bound2], t_rptr_bound1), + env.t_fn([t_rptr_bound2, t_rptr_bound1], t_rptr_bound1), + env.t_fn([t_rptr_bound1, t_rptr_bound1], t_rptr_bound1)); +} + +#[test] +fn lub_free_free() { + let env = setup_env("lub_free_free", ""); + let t_rptr_free1 = env.t_rptr_free(0, 1); + let t_rptr_free2 = env.t_rptr_free(0, 2); + let t_rptr_static = env.t_rptr_static(); + env.check_lub(env.t_fn([t_rptr_free1], env.t_int()), + env.t_fn([t_rptr_free2], env.t_int()), + env.t_fn([t_rptr_static], env.t_int())); +} + +#[test] +fn lub_returning_scope() { + let env = setup_env("lub_returning_scope", ""); + let t_rptr_scope10 = env.t_rptr_scope(10); + let t_rptr_scope11 = env.t_rptr_scope(11); + env.check_no_lub(env.t_fn([], t_rptr_scope10), + env.t_fn([], t_rptr_scope11)); +} + diff --git a/src/librustc/rustc.rc b/src/librustc/rustc.rc index 2b92255bcc5..28b21693a51 100644 --- a/src/librustc/rustc.rc +++ b/src/librustc/rustc.rc @@ -143,6 +143,9 @@ mod middle { mod to_str; #[legacy_exports] mod unify; + #[cfg(test)] + #[legacy_exports] + mod test; } #[legacy_exports] mod collect; diff --git a/src/test/compile-fail/regions-fns.rs b/src/test/compile-fail/regions-fns.rs index 9427d4875a0..b3234ca1d6b 100644 --- a/src/test/compile-fail/regions-fns.rs +++ b/src/test/compile-fail/regions-fns.rs @@ -1,21 +1,11 @@ -// Should fail region checking, because g can only accept a pointer -// with lifetime r, and a is a pointer with unspecified lifetime. -fn not_ok_1(a: &uint) { - let mut g: fn@(x: &uint) = fn@(x: &r/uint) {}; +// Before fn subtyping was properly implemented, +// we reported errors in this case: + +fn not_ok(a: &uint, b: &b/uint) { + let mut g: fn@(x: &uint) = fn@(x: &b/uint) {}; //~^ ERROR mismatched types g(a); } -// Should fail region checking, because g can only accept a pointer -// with lifetime r, and a is a pointer with lifetime s. -fn not_ok_2(s: &s/uint) -{ - let mut g: fn@(x: &uint) = fn@(x: &r/uint) {}; - //~^ ERROR mismatched types - g(s); -} - fn main() { } - - diff --git a/src/test/run-pass/regions-equiv-fns.rs b/src/test/run-pass/regions-equiv-fns.rs new file mode 100644 index 00000000000..07ce81ab777 --- /dev/null +++ b/src/test/run-pass/regions-equiv-fns.rs @@ -0,0 +1,13 @@ +// Before fn subtyping was properly implemented, +// we reported errors in this case: + +fn ok(a: &uint) { + // Here &r is an alias for &: + let mut g: fn@(x: &uint) = fn@(x: &r/uint) {}; + g(a); +} + +fn main() { +} + +