Skip to content

Commit 1034006

Browse files
committed
feat: positivity check in type constructors with stack-based error reports
fix: perform positivity check in type arguments of type constructors test: add a simple test file for positivity checking.
1 parent 01899da commit 1034006

9 files changed

Lines changed: 238 additions & 33 deletions

File tree

src/ecHiInductive.ml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type dterror =
2323
| DTE_TypeError of TT.tyerror
2424
| DTE_DuplicatedCtor of symbol
2525
| DTE_InvalidCTorType of symbol * TT.tyerror
26-
| DTE_NonPositive
26+
| DTE_NonPositive of symbol * EI.non_positive_context
2727
| DTE_Empty
2828

2929
type fxerror =
@@ -52,7 +52,7 @@ let trans_record (env : EcEnv.env) (name : ptydname) (rc : precord) =
5252
Msym.odup unloc (List.map fst rc) |> oiter (fun (x, y) ->
5353
rcerror y.pl_loc env (RCE_DuplicatedField x.pl_desc));
5454

55-
(* Check for emptyness *)
55+
(* Check for emptiness *)
5656
if List.is_empty rc then
5757
rcerror loc env RCE_Empty;
5858

@@ -106,7 +106,7 @@ let trans_datatype (env : EcEnv.env) (name : ptydname) (dt : pdatatype) =
106106
dt |> List.map for1
107107
in
108108

109-
(* Check for emptyness *)
109+
(* Check for emptiness *)
110110
begin
111111
let rec isempty_n (ctors : (ty list) list) =
112112
List.for_all isempty_1 ctors
@@ -131,21 +131,24 @@ let trans_datatype (env : EcEnv.env) (name : ptydname) (dt : pdatatype) =
131131

132132
let tdecl = EcEnv.Ty.by_path_opt tname env0
133133
|> odfl (EcDecl.abs_tydecl ~params:(`Named tparams) lc) in
134-
let tyinst () =
135-
fun ty -> ty_instantiate tdecl.tyd_params targs ty in
134+
let tyinst = ty_instantiate tdecl.tyd_params targs in
136135

137136
match tdecl.tyd_type with
138137
| Abstract _ ->
139-
List.exists isempty (targs)
138+
List.exists isempty targs
140139

141140
| Concrete ty ->
142-
isempty_1 [tyinst () ty]
141+
isempty_1 [ tyinst ty ]
143142

144143
| Record (_, fields) ->
145-
isempty_1 (List.map (tyinst () |- snd) fields)
144+
isempty_1 (List.map (tyinst |- snd) fields)
146145

147146
| Datatype dt ->
148-
isempty_n (List.map (List.map (tyinst ()) |- snd) dt.tydt_ctors)
147+
(* FIXME: Inspecting all constructors recursively causes
148+
non-termination in some cases. One can have the same
149+
limitation as is done for positivity in order to limit this
150+
unfolding to well-behaved cases. *)
151+
isempty_n (List.map (List.map tyinst |- snd) dt.tydt_ctors)
149152

150153
in
151154

src/ecHiInductive.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type dterror =
1616
| DTE_TypeError of EcTyping.tyerror
1717
| DTE_DuplicatedCtor of symbol
1818
| DTE_InvalidCTorType of symbol * EcTyping.tyerror
19-
| DTE_NonPositive
19+
| DTE_NonPositive of symbol * non_positive_context
2020
| DTE_Empty
2121

2222
type fxerror =

src/ecInductive.ml

Lines changed: 115 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,120 @@ let datatype_ind_path (mode : indmode) (p : EcPath.path) =
8383
EcPath.pqoname (EcPath.prefix p) name
8484

8585
(* -------------------------------------------------------------------- *)
86-
exception NonPositive
87-
88-
let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) =
89-
let normty = odfl (identity : ty -> ty) normty in
86+
type non_positive_intype = Concrete | Record of symbol | Variant of symbol
87+
88+
type non_positive_description =
89+
| InType of EcIdent.ident option * non_positive_intype
90+
| NonPositiveOcc of ty
91+
| AbstractTypeRestriction
92+
| TypePositionRestriction of ty
93+
94+
type non_positive_context = (symbol * non_positive_description) list
95+
96+
exception NonPositive of non_positive_context
97+
98+
let with_context ?ident p ctx f =
99+
try f () with NonPositive l -> raise (NonPositive ((EP.basename p, InType (ident, ctx)) :: l))
100+
101+
let non_positive (p : EP.path) ctx = raise (NonPositive [(EP.basename p, ctx)])
102+
let non_positive' (s : EcIdent.ident) ctx = raise (NonPositive [(s.id_symb, ctx)])
103+
104+
(** below, [fct] designates the function that takes a path to a type constructor
105+
and returns the corresponding type declaration *)
106+
107+
(** Strict positivity enforces the following, for every variant of the datatype p:
108+
- for each subterm (a → b), p ∉ fv(a);
109+
- inductive occurences a₁ a₂ .. aₙ p are such that ∀i. p ∉ fv(aᵢ)
110+
111+
Crucially, this has to be checked whenever p occurs in an instance of
112+
another type constructor.
113+
114+
FIXME: The current implementation prohibits the use of a type which changes
115+
its type arguments like e.g.
116+
{v
117+
type ('a, 'b) t = [
118+
| Elt of 'a
119+
| Swap of ('b, 'a) t
120+
].
121+
v}
122+
to be used in some places while defining another inductive type. *)
123+
124+
let rec occurs ?(normty = identity) p t =
125+
match (normty t).ty_node with
126+
| Tconstr (p', _) when EcPath.p_equal p p' -> true
127+
| _ -> EcTypes.ty_sub_exists (occurs p) t
128+
129+
(** Tests whether the first list is a list of type variables, matching the
130+
identifiers of the second list. *)
131+
let ty_params_compat =
132+
List.for_all2 (fun ty (param_id, _) ->
133+
match ty.ty_node with
134+
| Tvar id -> EcIdent.id_equal id param_id
135+
| _ -> false)
136+
137+
(** Ensures all occurrences of type variable [ident] are positive in type
138+
declaration [decl] (with name [p]).
139+
This function provide error context in case the check fails. *)
140+
let rec check_positivity_in_decl fct p decl ident =
141+
let check x () = check_positivity_ident fct p decl.tyd_params ident x
142+
and iter l f = List.iter f l in
143+
144+
match decl.tyd_type with
145+
| Concrete ty -> with_context ~ident p Concrete (check ty)
146+
| Abstract _ -> non_positive p AbstractTypeRestriction
147+
| Datatype { tydt_ctors } ->
148+
iter tydt_ctors @@ fun (name, argty) ->
149+
iter argty @@ fun ty ->
150+
with_context ~ident p (Variant name) (check ty)
151+
| Record (_, tys) ->
152+
iter tys @@ fun (name, ty) ->
153+
with_context ~ident p (Record name) (check ty)
154+
155+
(** Ensures all occurrences of type variable [ident] are positive in type [ty] *)
156+
and check_positivity_ident fct p params ident ty =
157+
match ty.ty_node with
158+
| Tglob _ | Tunivar _ | Tvar _ -> ()
159+
| Ttuple tys -> List.iter (check_positivity_ident fct p params ident) tys
160+
| Tconstr (q, args) when EcPath.p_equal q p ->
161+
if not (ty_params_compat args params) then
162+
non_positive p (TypePositionRestriction ty)
163+
| Tconstr (q, args) ->
164+
let decl = fct q in
165+
List.iter (check_positivity_ident fct p params ident) args;
166+
List.combine args decl.tyd_params
167+
|> List.filter_map (fun (arg, (ident', _)) ->
168+
if EcTypes.var_mem ident arg then Some ident' else None)
169+
|> List.iter (check_positivity_in_decl fct q decl)
170+
| Tfun (from, to_) ->
171+
if EcTypes.var_mem ident from then non_positive' ident (NonPositiveOcc ty);
172+
check_positivity_ident fct p params ident to_
173+
174+
(** Ensures all occurrences of path [p] are positive in type [ty] *)
175+
let rec check_positivity_path fct p ty =
176+
match ty.ty_node with
177+
| Tglob _ | Tunivar _ | Tvar _ -> ()
178+
| Ttuple tys -> List.iter (check_positivity_path fct p) tys
179+
| Tconstr (q, args) when EcPath.p_equal q p ->
180+
if List.exists (occurs p) args then non_positive p (NonPositiveOcc ty)
181+
| Tconstr (q, args) ->
182+
let decl = fct q in
183+
List.iter (check_positivity_path fct p) args;
184+
List.combine args decl.tyd_params
185+
|> List.filter_map (fun (arg, (ident, _)) ->
186+
if occurs p arg then Some ident else None)
187+
|> List.iter (check_positivity_in_decl fct q decl)
188+
| Tfun (from, to_) ->
189+
if occurs p from then non_positive p (NonPositiveOcc ty);
190+
check_positivity_path fct p to_
191+
192+
let check_positivity fct dt =
193+
let check ty () = check_positivity_path fct dt.dt_path ty
194+
and iter l f = List.iter f l in
195+
iter dt.dt_ctors @@ fun (name, argty) ->
196+
iter argty @@ fun ty ->
197+
with_context dt.dt_path (Variant name) (check ty)
198+
199+
let indsc_of_datatype ?(normty = identity) (mode : indmode) (dt : datatype) =
90200
let tpath = dt.dt_path in
91201

92202
let rec scheme1 p (pred, fac) ty =
@@ -103,13 +213,11 @@ let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) =
103213
| scs -> Some (FL.f_let (LTuple xs) fac (FL.f_ands scs))
104214
end
105215

106-
| Tconstr (p', ts) ->
107-
if List.exists (occurs p) ts then raise NonPositive;
216+
| Tconstr (p', _) ->
108217
if not (EcPath.p_equal p p') then None else
109218
Some (FL.f_app pred [fac] tbool)
110219

111220
| Tfun (ty1, ty2) ->
112-
if occurs p ty1 then raise NonPositive;
113221
let x = fresh_id_of_ty ty1 in
114222
scheme1 p (pred, FL.f_app fac [FL.f_local x ty1] ty2) ty2
115223
|> omap (FL.f_forall [x, GTty ty1])
@@ -152,11 +260,6 @@ let indsc_of_datatype ?normty (mode : indmode) (dt : datatype) =
152260
let form = FL.f_forall [predx, GTty predty] form in
153261
form
154262

155-
and occurs p t =
156-
match (normty t).ty_node with
157-
| Tconstr (p', _) when EcPath.p_equal p p' -> true
158-
| _ -> EcTypes.ty_sub_exists (occurs p) t
159-
160263
in scheme mode (List.map fst dt.dt_tparams, tpath) dt.dt_ctors
161264

162265
(* -------------------------------------------------------------------- *)

src/ecInductive.mli

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,25 @@ val datatype_proj_name : symbol -> symbol
4343
val datatype_proj_path : path -> symbol -> path
4444

4545
(* -------------------------------------------------------------------- *)
46-
exception NonPositive
46+
type non_positive_intype = Concrete | Record of symbol | Variant of symbol
47+
48+
type non_positive_description =
49+
| InType of EcIdent.ident option * non_positive_intype
50+
| NonPositiveOcc of ty
51+
| AbstractTypeRestriction
52+
| TypePositionRestriction of ty
53+
54+
type non_positive_context = (symbol * non_positive_description) list
55+
56+
exception NonPositive of non_positive_context
57+
58+
val check_positivity : (path -> tydecl) -> datatype -> unit
59+
(** Evaluates whether a given datatype protype satisfies the strict
60+
positivity check. The first argument defines how to retrieve the
61+
effective definition of a type constructor from its path.
62+
63+
raises the exception [NonPositive] if the check fails, otherwise
64+
the function returns a unit value. *)
4765

4866
val indsc_of_datatype : ?normty:(ty -> ty) -> [`Elim|`Case] -> datatype -> form
4967

src/ecScope.ml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,13 +2246,16 @@ module Ty = struct
22462246
let body = transty tp_tydecl env ue bd in
22472247
EcUnify.UniEnv.tparams ue, Concrete body
22482248

2249-
| PTYD_Datatype dt ->
2250-
let datatype = EHI.trans_datatype env (mk_loc loc (args,name)) dt in
2251-
let tparams, tydt =
2252-
try ELI.datatype_as_ty_dtype datatype
2253-
with ELI.NonPositive -> EHI.dterror loc env EHI.DTE_NonPositive
2254-
in
2255-
tparams, Datatype tydt
2249+
| PTYD_Datatype dt -> (
2250+
let datatype = EHI.trans_datatype env (mk_loc loc (args, name)) dt in
2251+
let ty_from_ctor ctor = EcEnv.Ty.by_path ctor env in
2252+
try
2253+
ELI.check_positivity ty_from_ctor datatype;
2254+
let tparams, tydt = ELI.datatype_as_ty_dtype datatype in
2255+
(tparams, Datatype tydt)
2256+
with ELI.NonPositive ctx ->
2257+
let symbol = basename datatype.dt_path in
2258+
EHI.dterror loc env (EHI.DTE_NonPositive (symbol, ctx)))
22562259

22572260
| PTYD_Record rt ->
22582261
let record = EHI.trans_record env (mk_loc loc (args,name)) rt in

src/ecTypes.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ let rec ty_check_uni t =
154154
| Tunivar _ -> raise FoundUnivar
155155
| _ -> ty_iter ty_check_uni t
156156

157+
let rec var_mem ?(check_glob = false) id t =
158+
match t.ty_node with
159+
| Tvar id' -> EcIdent.id_equal id id'
160+
| Tglob id' when check_glob -> EcIdent.id_equal id id'
161+
| _ -> ty_sub_exists (var_mem ~check_glob id) t
162+
157163
(* -------------------------------------------------------------------- *)
158164
let symbol_of_ty (ty : ty) =
159165
match ty.ty_node with

src/ecTypes.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ val ty_sub_exists : (ty -> bool) -> ty -> bool
7979
val ty_fold : ('a -> ty -> 'a) -> 'a -> ty -> 'a
8080
val ty_iter : (ty -> unit) -> ty -> unit
8181

82+
val var_mem : ?check_glob:bool -> EcIdent.t -> ty -> bool
83+
8284
(* -------------------------------------------------------------------- *)
8385
val symbol_of_ty : ty -> string
8486
val fresh_id_of_ty : ty -> EcIdent.t

src/ecUserMessages.ml

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ module InductiveError : sig
573573
val pp_fxerror : env -> Format.formatter -> fxerror -> unit
574574
end = struct
575575
open EcHiInductive
576+
open EcInductive
576577
open TypingError
577578

578579
let pp_rcerror env fmt error =
@@ -591,8 +592,38 @@ end = struct
591592
| RCE_Empty ->
592593
msg "this record type is empty"
593594

595+
let format_intype fmt p (tyvar, ctx) =
596+
(match ctx with
597+
| Concrete -> Format.fprintf fmt "... in type %s" p
598+
| Record s -> Format.fprintf fmt "... in record field %s of type %s" s p
599+
| Variant s -> Format.fprintf fmt "... in variant %s of type %s" s p);
600+
let subty tyvar =
601+
Format.fprintf fmt " (in an instance of type variable %a)"
602+
EcIdent.pp_ident tyvar
603+
in
604+
Option.iter subty tyvar
605+
606+
let format_context pp fmt (p, ctx) = match ctx with
607+
| InType (tyvar, ctx) -> format_intype fmt p (tyvar, ctx)
608+
| NonPositiveOcc ty ->
609+
Format.fprintf fmt "non-positive occurrence of %s in type %a"
610+
p (EcPrinting.pp_type pp) ty
611+
| AbstractTypeRestriction ->
612+
Format.fprintf fmt "unauthorised abstract type constructor %s" p
613+
| TypePositionRestriction ty ->
614+
Format.fprintf fmt
615+
"recursive occurrence %a in the definition of %s has different \
616+
arguments, which is not allowed."
617+
(EcPrinting.pp_type pp) ty p
618+
619+
let format_context_list p l pp fmt =
620+
Format.fprintf fmt "Could not verify strict positivity of type %s:@.@;<0 2>@[<v>" p;
621+
Format.pp_print_list (format_context pp) fmt l;
622+
Format.fprintf fmt "@;@]"
623+
594624
let pp_dterror env fmt error =
595625
let msg x = Format.fprintf fmt x in
626+
let env1 = EcPrinting.PPEnv.ofenv env in
596627

597628
match error with
598629
| DTE_TypeError ee ->
@@ -605,12 +636,11 @@ end = struct
605636
msg "invalid constructor type: `%s`: %a'"
606637
name (pp_tyerror env) ee
607638

608-
| DTE_NonPositive ->
609-
msg "the datatype does not respect the positivity condition"
610-
611639
| DTE_Empty ->
612640
msg "the datatype may be empty"
613641

642+
| DTE_NonPositive (s, ctx) -> format_context_list s ctx env1 fmt
643+
614644
let pp_fxerror env fmt error =
615645
match error with
616646
| FXLowError ee ->

tests/positivity_checking.ec

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
(* Simple type *)
2+
type 'a list = [ Nil | Cons of 'a & 'a list ].
3+
4+
type 'a tree = [
5+
| Leaf
6+
(* Recursive occurrence within a pre-existing type constructor *)
7+
| Node of 'a tree list
8+
(* Positive occurrence in a function *)
9+
| Fun of (bool -> 'a tree)
10+
].
11+
12+
theory Bad.
13+
type ('a, 'b) permlist = [
14+
| N of ('a -> 'b) (* Aaaaah *)
15+
| C of 'a & ('a, 'b) permlist
16+
| P of ('b, 'a) permlist
17+
].
18+
19+
fail type posrej = [ A | B of (bool, posrej) permlist ].
20+
end Bad.
21+
22+
theory Good.
23+
type ('a, 'b) permlist = [
24+
| N (* No problem *)
25+
| C of 'a & ('a, 'b) permlist
26+
| P of ('b, 'a) permlist list (* For the sake of nesting in a list *)
27+
].
28+
29+
(* this type fails because of the same limitation,
30+
even though it is in fact strictly positive. *)
31+
fail type posrej = [ A | B of (bool, posrej) permlist ].
32+
end Good.
33+
34+
type ('a, 'b) arr = 'a -> 'b.
35+
type ('a, 'b) orr = ('a, 'b) arr.
36+
fail type 'a u = [ S | U of ('a u, bool) orr ].
37+
38+
type 'a t.
39+
fail type tt = [ N | T of tt t ].
40+
fail type 'a tt = [ N | T of 'a tt tt].

0 commit comments

Comments
 (0)