Mentions légales du service

Skip to content
Snippets Groups Projects

typed holes (with subterms) for shrinking

Merged SCHERER Gabriel requested to merge gscherer/inferno:typed-holes into master
12 files
+ 198
100
Compare changes
  • Side-by-side
  • Inline
Files
12
+ 143
91
@@ -28,6 +28,7 @@ let rec size accu = function
@@ -28,6 +28,7 @@ let rec size accu = function
| ML.LetProd (_, t1, t2)
| ML.LetProd (_, t1, t2)
-> size (size (accu + 1) t1) t2
-> size (size (accu + 1) t1) t2
| ML.Tuple ts
| ML.Tuple ts
 
| ML.Hole ts
-> List.fold_left size (accu + 1) ts
-> List.fold_left size (accu + 1) ts
| ML.Match (t, brs)
| ML.Match (t, brs)
-> List.fold_left size_branch (size (accu + 1) t) brs
-> List.fold_left size_branch (size (accu + 1) t) brs
@@ -127,116 +128,166 @@ module Shrinker = struct
@@ -127,116 +128,166 @@ module Shrinker = struct
module Shrink = QCheck.Shrink
module Shrink = QCheck.Shrink
module Iter = QCheck.Iter
module Iter = QCheck.Iter
let rec fv x = function
let rec bv_pat = function
 
| ML.PVar y -> [y]
 
| ML.PWildcard | ML.PVariant (_, None) -> []
 
| ML.PAnnot (pat, _)
 
| ML.PVariant (_, Some pat) ->
 
bv_pat pat
 
| ML.PTuple ps ->
 
List.concat_map bv_pat ps
 
 
(* t[u/x] *)
 
let rec subst t x u =
 
match t with
| ML.Var y ->
| ML.Var y ->
x = y
if x = y then u
 
else ML.Var y
| ML.Abs (y, t) ->
| ML.Abs (y, t) ->
if x = y then false
ML.Abs (y, subst_under [y] t x u)
else fv x t
| ML.App (t1, t2) ->
| ML.App (t1, t2) ->
fv x t1 || fv x t2
ML.App (subst t1 x u, subst t2 x u)
| ML.Let (y, t, u) ->
| ML.Let (y, t1, t2) ->
fv x t || (x <> y && fv x u)
ML.Let (y, subst t1 x u, subst_under [y] t2 x u)
| ML.Tuple ts ->
| ML.Tuple ts ->
List.exists (fv x) ts
ML.Tuple (List.map (fun t -> subst t x u) ts)
| ML.LetProd (xs, t, u) ->
| ML.Hole ts ->
fv x t || (List.for_all ((<>) x) xs && fv x u)
ML.Hole (List.map (fun t -> subst t x u) ts)
| ML.Annot (t, _)
| ML.LetProd (ys, t1, t2) ->
| ML.Variant (_, Some t) ->
ML.LetProd (ys, subst t1 x u, subst_under ys t2 x u)
fv x t
| ML.Annot (t, ty) ->
| ML.Variant (_, None) ->
ML.Annot (subst t x u, ty)
false
| ML.Variant (ty, t) ->
 
ML.Variant (ty, Option.map (fun t -> subst t x u) t)
| ML.Match (t, brs) ->
| ML.Match (t, brs) ->
fv x t || List.exists (fv_br x) brs
ML.Match (subst t x u, List.map (fun br -> subst_branch br x u) brs)
and fv_br x (pat, t) =
(* (y.t)[u/x] *)
if bv_pat x pat then
and subst_under ys t x u =
false
if List.mem x ys then t
else
else subst t x u
fv x t
and subst_under_pat p t x u =
and bv_pat x = function
subst_under (bv_pat p) t x u
| PVar y ->
x = y
and subst_branch (p, t) x u =
| PWildcard ->
(p, subst_under_pat p t x u)
false
| PAnnot (pat, _)
let remove_variable t x =
| PVariant (_, Some pat) ->
subst t x (Hole [])
bv_pat x pat
let remove_variables t xs =
| PVariant (_, None) ->
List.fold_left remove_variable t xs
false
| PTuple ps ->
let rec shrink_term t = Iter.(
List.exists (bv_pat x) ps
(* The Iter product is not suitable for shrinking:
(and+) (shrink a) (shrink b)
let (let+) a f = Iter.map f a
will try to shrink *both* a and b simultaneously,
but never try to shrink just one of them.
let rec shrink_term gamma t = Iter.(
(match t, List.rev gamma with
We shadow this unsuitable operator and use a "pointed product" (and++),
| ML.Var _, _ ->
which tries to shrink a (with b constant) and then b (with a constant).
empty
| _, x0 :: _ ->
This corresponds to the behavior of Shrink.pair, but Shrink.pair works
return (ML.Var x0)
on function types ('a -> 'a Iter.t), which is inconvenient
| _, [] ->
to write shrinkers directly -- no 'map' function. *)
 
let (and+) = () in ignore ((and+));
 
let (let++) (_a, it) f = Iter.map f it in
 
let (and++) (a, ita) (b, itb) =
 
(a, b),
 
Iter.append
 
(let+ a' = ita in (a', b))
 
(let+ b' = itb in (a, b'))
 
in
 
let subterms ts =
 
(* ...[t1, t2] is not always a desirable shrinking choice for a term
 
containing [t1, t2] as subterms, because it breaks the property that
 
the types of t1 and t2 appear in the resulting type, which is sometimes
 
important to reproduce typing bugs.
 
So we start with just (t1) and (t2) as shrinking choices,
 
even though they typically do not preserve typability. *)
 
of_list ts <+> return (ML.Hole ts) in
 
(match t with
 
| ML.Hole [] ->
empty
empty
 
| _ ->
 
return (ML.Hole [])
) <+> (
) <+> (
match t with
match t with
| ML.Var _ ->
| ML.Var _ ->
empty
empty
 
| ML.Hole ts ->
 
(match ts with [t] -> return t | _ -> empty)
 
<+> (
 
let+ ts' = Shrink.list ~shrink:shrink_term ts
 
in ML.Hole ts'
 
)
 
| ML.Abs (x, t) ->
| ML.Abs (x, t) ->
let t' = shrink_term (x::gamma) t in
subterms [remove_variable t x]
(if fv x t then empty else return t)
<+> (
<+> (let+ t' = t' in ML.Abs (x, t'))
let+ t' = shrink_term t in
 
ML.Abs (x, t')
 
)
| ML.App (t1, t2) ->
| ML.App (t1, t2) ->
let t1' = shrink_term gamma t1 in
subterms [t1; t2]
let t2' = shrink_term gamma t2 in
<+> (
return t1
let++ t1' = t1, shrink_term t1
<+> t2'
and++ t2' = t2, shrink_term t2
<+> (let+ t1' = t1' and+ t2' = t2' in ML.App (t1',t2'))
in ML.App (t1',t2')
 
)
| ML.Let (x, t, u) ->
| ML.Let (x, t, u) ->
let t' = shrink_term gamma t in
subterms [t; remove_variable u x]
let u' = shrink_term (x::gamma) u in
<+> (
return t
let++ t' = t, shrink_term t
<+> (if fv x u then empty else return u)
and++ u' = u, shrink_term u
<+> (let+ t' = t' and+ u' = u' in ML.Let (x, t', u'))
in ML.Let (x, t', u')
 
)
| ML.LetProd (xs, t, u) ->
| ML.LetProd (xs, t, u) ->
let t' = shrink_term gamma t in
subterms [t; remove_variables u xs]
let u' = shrink_term (xs@gamma) u in
<+> (match t with
| ML.Hole _ ->
return t
let+ xs' = Shrink.list_spine xs in
(* A more clever approach would be to inspect [t] to see if it is a
let u =
tuple and decrease the size of the tuple and the number of variables
List.filter (fun x -> not (List.mem x xs')) xs
in the let-binding, when the variable isn't free in the body.
|> remove_variables u
For instance
in
if xs' = [] then u
let (x, y, z) = (t1, t2, t3) in x z
else ML.LetProd (xs', t, u)
| _ -> empty
can be shrunk into
)
<+> (
let (x, z) = (t1, t3) in x z
let++ t' = t, shrink_term t
*)
and++ u' = u, shrink_term u
<+> (let xs' = List.filter (fun x -> fv x u) xs in
in ML.LetProd (xs, t', u')
if List.length xs == List.length xs' then
)
empty
else
match xs' with
| [] ->
return u
| _ ->
return (ML.LetProd (xs', t, u))
)
<+> (let+ t' = t' and+ u' = u' in ML.LetProd (xs, t', u'))
| ML.Tuple ts ->
| ML.Tuple ts ->
of_list ts
subterms ts
<+> let+ ts' = Shrink.list ~shrink:(shrink_term gamma) ts
<+> (
in ML.Tuple ts'
let+ ts' = Shrink.list ~shrink:shrink_term ts
 
in ML.Tuple ts'
 
)
 
 
| ML.Annot (t, _ty) ->
 
subterms [t]
 
 
| ML.Variant (lab, t) ->
 
subterms (Option.to_list t)
 
<+> (
 
match t with
 
| None -> empty
 
| Some t ->
 
let+ t' = shrink_term t
 
in ML.Variant (lab, Some t')
 
)
 
 
| ML.Match (_t, _branches) ->
 
failwith "unsupported"
| _ ->
assert false
)
)
)
)
end
end
@@ -280,7 +331,7 @@ let () =
@@ -280,7 +331,7 @@ let () =
let arb =
let arb =
QCheck.make
QCheck.make
~print:MLPrinter.to_string
~print:MLPrinter.to_string
~shrink:(Shrinker.shrink_term [])
~shrink:Shrinker.shrink_term
(term_gen (0, n))
(term_gen (0, n))
in
in
let prop t =
let prop t =
@@ -297,7 +348,8 @@ let () =
@@ -297,7 +348,8 @@ let () =
prop
prop
) pairs in
) pairs in
QCheck_runner.set_seed 0;
QCheck_runner.set_seed 0;
let _ = QCheck_runner.run_tests ~colors:true ~verbose:true tests in
let debug_shrink = None (* use (Some stdout) for a shrinking trace *) in
 
let _ = QCheck_runner.run_tests ~colors:true ~verbose:true ~debug_shrink tests in
Printf.printf
Printf.printf
"In total, %d out of %d terms (%.1f%%) were considered well-typed.\n%!"
"In total, %d out of %d terms (%.1f%%) were considered well-typed.\n%!"
!count_success !count_total
!count_success !count_total
Loading