Skip to content

Commit 9746686

Browse files
authored
Fix #678 (Quantifiers escape their scopes) (#710)
This patch does a fairly simple fix which ensures that given an explicitly-quantified signature, that the quantifiers do not appear in the typing environment after typechecking the body. The check is only performed when a function has explicit quantifiers. As an example, the program from #678: ``` var f = id(id); sig g : forall a. (a) -> a fun g(x) { f(x) } ``` Now gives the error: ``` squid-bug.links:4: Type error: The quantifiers in the type of function g: forall a.(a) -b-> a escape their scope, as they are present in the types: f: (a) -b-> a In expression: fun g(x) { f(x) }. ```
1 parent 1871870 commit 9746686

File tree

7 files changed

+101
-4
lines changed

7 files changed

+101
-4
lines changed

core/env.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ sig
1414
module Dom : Utility.Set.S
1515
val domain : 'a t -> Dom.t
1616
val range : 'a t -> 'a list
17+
val bindings : 'a t -> (name * 'a) list
1718

1819
val map : ('a -> 'b) -> 'a t -> 'b t
1920
val iter : (name -> 'a -> unit) -> 'a t -> unit
2021
val fold : (name -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
22+
val filter : (name -> 'a -> bool) -> 'a t -> 'a t
23+
val filter_map : (name -> 'a -> 'b option) -> 'a t -> 'b t
2124
end
2225

2326
module Make (Ord : Utility.OrderedShow) :
@@ -39,11 +42,14 @@ struct
3942
module Dom = Utility.Set.Make(Ord)
4043
let domain map = M.fold (fun k _ -> Dom.add k) map Dom.empty
4144
let range map = M.fold (fun _ v l -> v::l) map []
45+
let bindings map = M.fold (fun k v l -> (k, v)::l) map []
4246
let map = M.map
4347
let iter = M.iter
4448
let fold = M.fold
4549
let pp = M.pp
4650
let show = M.show
51+
let filter = M.filter
52+
let filter_map = M.filter_map
4753
end
4854

4955
module String

core/env.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,17 @@ sig
4242
val range : 'a t -> 'a list
4343
(** The range of an environment *)
4444

45+
val bindings : 'a t -> (name * 'a) list
46+
4547
val map : ('a -> 'b) -> 'a t -> 'b t
4648

4749
val iter : (name -> 'a -> unit) -> 'a t -> unit
4850

4951
val fold : (name -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
52+
53+
val filter : (name -> 'a -> bool) -> 'a t -> 'a t
54+
55+
val filter_map : (name -> 'a -> 'b option) -> 'a t -> 'b t
5056
end
5157
(** Output signature of the functor {!Env.Make}. *)
5258

core/typeSugar.ml

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ sig
380380
val inconsistent_quantifiers :
381381
pos:Position.t -> t1:Types.datatype -> t2:Types.datatype -> unit
382382

383+
val escaped_quantifier :
384+
pos:Position.t ->
385+
var:string ->
386+
annotation:Types.datatype ->
387+
escapees:((string * Types.datatype) list) ->
388+
unit
389+
383390
end
384391
= struct
385392
type griper =
@@ -1515,6 +1522,20 @@ end
15151522
"but the currently allowed effects are" ^ nli () ^
15161523
code ppr_lt)
15171524

1525+
let escaped_quantifier ~pos ~var ~annotation ~escapees =
1526+
let escaped_tys = List.map snd escapees in
1527+
build_tyvar_names (annotation :: escaped_tys);
1528+
let policy () = { (error_policy ()) with Types.Print.quantifiers = true } in
1529+
let display_ty (var, ty) =
1530+
Printf.sprintf "%s: %s" var
1531+
(Types.string_of_datatype ~policy ~refresh_tyvar_names:false ty) in
1532+
let displayed_tys =
1533+
List.map display_ty escapees
1534+
|> String.concat (nli ()) in
1535+
die pos ("The quantifiers in the type of function" ^ nli () ^
1536+
display_ty (var, annotation) ^ nl () ^
1537+
"escape their scope, as they are present in the types:" ^ nli () ^
1538+
displayed_tys)
15181539
end
15191540

15201541
type context = Types.typing_environment = {
@@ -4009,16 +4030,16 @@ and type_binding : context -> binding -> binding * context * usagemap =
40094030
let t_ann = resolve_type_annotation bndr t_ann' in
40104031

40114032
(* Check that any annotation matches the shape of the function *)
4012-
let context_body, ft =
4033+
let context_body, ft, quantifiers =
40134034
match t_ann with
40144035
| None ->
4015-
context, make_ft lin pats effects return_type
4036+
context, make_ft lin pats effects return_type, []
40164037
| Some t ->
40174038
(* Debug.print ("t: " ^ Types.string_of_datatype t); *)
40184039
(* make sure the annotation has the right shape *)
40194040
let shape = make_ft lin pats effects return_type in
40204041
let ft = if unsafe then make_unsafe_signature t else t in
4021-
let _, ft_mono = TypeUtils.split_quantified_type ft in
4042+
let quantifiers, ft_mono = TypeUtils.split_quantified_type ft in
40224043

40234044
(* Debug.print ("ft_mono: " ^ Types.string_of_datatype ft_mono); *)
40244045
let () = unify pos ~handle:Gripers.bind_fun_annotation (no_pos shape, no_pos ft_mono) in
@@ -4029,7 +4050,7 @@ and type_binding : context -> binding -> binding * context * usagemap =
40294050
the original name as the function is not
40304051
recursive) *)
40314052
let v = Utils.dummy_source_name () in
4032-
bind_var context (v, ft_mono), ft in
4053+
bind_var context (v, ft_mono), ft, quantifiers in
40334054

40344055
(* We make the patterns monomorphic after unifying with the signature. *)
40354056
make_mono pats;
@@ -4070,6 +4091,27 @@ and type_binding : context -> binding -> binding * context * usagemap =
40704091
(usages body)
40714092
else () in
40724093

4094+
(* Check that quantifiers have not escaped into the typing context *)
4095+
let check_escaped_quantifiers quantifiers env =
4096+
let quantifier_set = IntSet.of_list (List.map fst quantifiers) in
4097+
(* Note that `type_predicate` returns true iff *all* child nodes of
4098+
* the type satisfy the predicate. Thus the checker returns true if
4099+
* *all* type variables are *not* in quantifier_set *)
4100+
let checker = object(_self)
4101+
inherit Types.type_predicate
4102+
method! var_satisfies (i, _, _) = not (IntSet.mem i quantifier_set)
4103+
end in
4104+
let (is_safe, _) = checker#predicates in
4105+
let escapees =
4106+
Env.filter (fun _ dt -> not (is_safe dt)) env
4107+
|> Env.bindings in
4108+
if not (ListUtils.empty escapees) then
4109+
Gripers.escaped_quantifier ~pos ~var:name ~annotation:ft ~escapees in
4110+
4111+
let () =
4112+
if not (quantifiers = []) then
4113+
check_escaped_quantifiers quantifiers context.var_env in
4114+
40734115
let ft = if unsafe then check_unsafe_signature context unify_nopos ft t_ann' else ft in
40744116
let (tyvars, _), ft =
40754117
if fun_frozen then (TypeUtils.quantifiers ft, []), ft

core/types.mli

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type 't meta_row_var_basis =
3030

3131
type 't meta_presence_var_basis = 't meta_type_var_non_rec_basis
3232

33+
type 't meta_max_basis = 't meta_row_var_basis
34+
3335
module Abstype :
3436
sig
3537
type t [@@deriving eq,show]
@@ -394,5 +396,21 @@ sig
394396
end
395397
end
396398

399+
type visit_context = Utility.StringSet.t * TypeVarSet.t * TypeVarSet.t
400+
class virtual type_predicate :
401+
object('self_type)
402+
method var_satisfies : (int * subkind * freedom) -> bool
403+
method type_satisfies : visit_context -> typ -> bool
404+
method point_satisfies :
405+
'a 'c . (visit_context -> 'a -> bool) ->
406+
visit_context ->
407+
([< 'a meta_max_basis] as 'c) point ->
408+
bool
409+
method field_satisfies : visit_context -> field_spec -> bool
410+
method row_satisfies : visit_context -> row -> bool
411+
method type_satisfies_arg : visit_context -> type_arg -> bool
412+
method predicates : ((typ -> bool) * (row -> bool))
413+
end
414+
397415
module Transform : TYPE_VISITOR
398416
module ElimRecursiveTypeCyclesTransform : TYPE_VISITOR

core/utility.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ sig
7878
val partition : (key -> 'a -> bool) -> 'a t -> ('a t * 'a t)
7979
(** divide the map by a predicate *)
8080

81+
val filter : (key -> 'a -> bool) -> 'a t -> 'a t
82+
(** filters using both keys and values *)
83+
84+
val filter_map : (key -> 'a -> 'b option) -> 'a t -> 'b t
85+
(** filters and applies a function -- None values discarded *)
86+
8187
val show : (Format.formatter -> 'a -> unit) -> 'a t -> string
8288
val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
8389
end
@@ -222,6 +228,15 @@ struct
222228
p, add i v q)
223229
m (empty, empty)
224230

231+
let filter_map f m =
232+
fold (fun k v acc ->
233+
match f k v with
234+
| Some x -> add k x acc
235+
| None -> acc) m empty
236+
237+
let filter f =
238+
filter_map (fun k v ->
239+
if f k v then Some v else None)
225240

226241
let pp af formatter map =
227242
Format.pp_open_box formatter 0;

tests/functions.tests

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,9 @@ Closure conversion: Test function with free type variables, but no free term var
103103
./tests/functions/closure-conv-type-abstr-only.links
104104
filemode : true
105105
stdout : 42 : Int
106+
107+
Quantifiers should not escape their scopes (#687)
108+
./tests/functions/escaped-quantifier.links
109+
filemode : true
110+
stderr : @.*escape their scope.*
111+
exit : 1
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
var f = id(id);
2+
3+
sig g : forall bobsleigh. (bobsleigh) -> bobsleigh
4+
fun g(x) { f(x) }

0 commit comments

Comments
 (0)