summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ast_util.ml8
-rw-r--r--src/ast_util.mli9
-rw-r--r--src/c_backend.ml2
-rw-r--r--src/pattern_completeness.ml294
-rw-r--r--src/sail.ml3
-rw-r--r--src/type_check.ml13
-rw-r--r--src/type_check.mli7
-rw-r--r--src/util.ml7
-rw-r--r--src/util.mli4
9 files changed, 333 insertions, 14 deletions
diff --git a/src/ast_util.ml b/src/ast_util.ml
index 5756c954..53f00825 100644
--- a/src/ast_util.ml
+++ b/src/ast_util.ml
@@ -52,6 +52,10 @@ open Ast
open Util
module Big_int = Nat_big_num
+type mut = Immutable | Mutable
+
+type lvar = Register of typ | Enum of typ | Local of mut * typ | Union of typquant * typ | Unbound
+
let no_annot = (Parse_ast.Unknown, ())
let gen_loc l = Parse_ast.Generated l
@@ -924,3 +928,7 @@ let split_defs f (Defs defs) =
Some (Defs (List.rev pre_defs), def, Defs post_defs)
let append_ast (Defs ast1) (Defs ast2) = Defs (ast1 @ ast2)
+
+let type_union_id (Tu_aux (aux, _)) = match aux with
+ | Tu_id id -> id
+ | Tu_ty_id (_, id) -> id
diff --git a/src/ast_util.mli b/src/ast_util.mli
index c9869cce..4452ee3f 100644
--- a/src/ast_util.mli
+++ b/src/ast_util.mli
@@ -53,6 +53,13 @@
open Ast
module Big_int = Nat_big_num
+type mut = Immutable | Mutable
+
+(** [lvar] is the type of variables - they can either be registers,
+ local mutable or immutable variables, nullary union constructors
+ (i.e. None in option), or unbound identifiers *)
+type lvar = Register of typ | Enum of typ | Local of mut * typ | Union of typquant * typ | Unbound
+
val no_annot : unit annot
val gen_loc : Parse_ast.l -> Parse_ast.l
@@ -293,3 +300,5 @@ val rename_fundef : id -> 'a fundef -> 'a fundef
val split_defs : ('a def -> bool) -> 'a defs -> ('a defs * 'a def * 'a defs) option
val append_ast : 'a defs -> 'a defs -> 'a defs
+
+val type_union_id : type_union -> id
diff --git a/src/c_backend.ml b/src/c_backend.ml
index ce891e3f..c1f5ddb9 100644
--- a/src/c_backend.ml
+++ b/src/c_backend.ml
@@ -78,7 +78,7 @@ type aexp =
and aval =
| AV_lit of lit * typ
- | AV_id of id * Type_check.lvar
+ | AV_id of id * lvar
| AV_tuple of aval list
| AV_C_fragment of string * typ
diff --git a/src/pattern_completeness.ml b/src/pattern_completeness.ml
new file mode 100644
index 00000000..4a2b5cfa
--- /dev/null
+++ b/src/pattern_completeness.ml
@@ -0,0 +1,294 @@
+(**************************************************************************)
+(* Sail *)
+(* *)
+(* Copyright (c) 2013-2017 *)
+(* Kathyrn Gray *)
+(* Shaked Flur *)
+(* Stephen Kell *)
+(* Gabriel Kerneis *)
+(* Robert Norton-Wright *)
+(* Christopher Pulte *)
+(* Peter Sewell *)
+(* Alasdair Armstrong *)
+(* Brian Campbell *)
+(* Thomas Bauereiss *)
+(* Anthony Fox *)
+(* Jon French *)
+(* Dominic Mulligan *)
+(* Stephen Kell *)
+(* Mark Wassell *)
+(* *)
+(* All rights reserved. *)
+(* *)
+(* This software was developed by the University of Cambridge Computer *)
+(* Laboratory as part of the Rigorous Engineering of Mainstream Systems *)
+(* (REMS) project, funded by EPSRC grant EP/K008528/1. *)
+(* *)
+(* Redistribution and use in source and binary forms, with or without *)
+(* modification, are permitted provided that the following conditions *)
+(* are met: *)
+(* 1. Redistributions of source code must retain the above copyright *)
+(* notice, this list of conditions and the following disclaimer. *)
+(* 2. Redistributions in binary form must reproduce the above copyright *)
+(* notice, this list of conditions and the following disclaimer in *)
+(* the documentation and/or other materials provided with the *)
+(* distribution. *)
+(* *)
+(* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' *)
+(* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *)
+(* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *)
+(* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR *)
+(* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *)
+(* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *)
+(* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *)
+(* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *)
+(* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *)
+(* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *)
+(* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *)
+(* SUCH DAMAGE. *)
+(**************************************************************************)
+
+open Ast
+open Ast_util
+module Big_int = Nat_big_num
+
+type ctx =
+ { lookup_id : id -> lvar;
+ enums : IdSet.t Bindings.t;
+ variants : IdSet.t Bindings.t
+ }
+
+let hex_to_bin hex =
+ Util.string_to_list hex
+ |> List.map Sail_lib.hex_char
+ |> List.concat
+ |> List.map Sail_lib.char_of_bit
+ |> (fun bits -> String.init (List.length bits) (List.nth bits))
+
+type gpat =
+ | GP_lit of lit
+ | GP_wild
+ | GP_vector of gpat list
+ | GP_vector_concat of gpat list
+ | GP_tup of gpat list
+ | GP_list of gpat list
+ | GP_cons of gpat * gpat
+ | GP_app of (gpat Bindings.t)
+ | GP_record of (gpat Bindings.t)
+
+let rec string_of_gpat = function
+ | GP_lit lit -> string_of_lit lit
+ | GP_wild -> "_"
+ | GP_vector gpats -> "[" ^ Util.string_of_list ", " string_of_gpat gpats ^ "]"
+ | GP_vector_concat gpats -> Util.string_of_list " @ " string_of_gpat gpats
+ | GP_tup gpats -> "(" ^ Util.string_of_list ", " string_of_gpat gpats ^ ")"
+ | GP_list gpats -> "[|" ^ Util.string_of_list ", " string_of_gpat gpats ^ "|]"
+ | GP_cons (gpat1, gpat2) -> string_of_gpat gpat1 ^ " :: " ^ string_of_gpat gpat2
+ | GP_app app ->
+ Util.string_of_list "|" (fun (id, gpat) -> string_of_id id ^ string_of_gpat gpat) (Bindings.bindings app)
+ | GP_record _ -> "GP RECORD"
+
+let is_wild = function
+ | GP_wild -> true
+ | _ -> false
+
+let rec generalize ctx (P_aux (p_aux, _) as pat) =
+ match p_aux with
+ | P_lit lit -> GP_lit lit
+ | P_wild -> GP_wild
+ | P_as (pat, _) -> generalize ctx pat
+ | P_typ (_, pat) -> generalize ctx pat (* This will possibly overapproximate how general P_typ is *)
+ | P_id id ->
+ begin
+ match ctx.lookup_id id with
+ | Unbound -> GP_wild
+ | Local (Immutable, _) -> GP_wild
+ | Register _ | Local (Mutable, _) -> Util.warn "Matching on register or mutable variable"; GP_wild
+ | Enum _ | Union _ -> GP_app (Bindings.singleton id GP_wild)
+ end
+ | P_var (pat, _) -> generalize ctx pat
+ | P_vector pats ->
+ let gpats = List.map (generalize ctx) pats in
+ if List.for_all is_wild gpats then GP_wild else GP_vector gpats
+ | P_vector_concat pats ->
+ let gpats = List.map (generalize ctx) pats in
+ if List.for_all is_wild gpats then GP_wild else GP_vector_concat gpats
+ | P_tup pats ->
+ let gpats = List.map (generalize ctx) pats in
+ if List.for_all is_wild gpats then GP_wild else GP_tup gpats
+ | P_list pats ->
+ let gpats = List.map (generalize ctx) pats in
+ if List.for_all is_wild gpats then GP_wild else GP_list gpats
+ | P_cons (hd_pat, tl_pat) ->
+ let ghd_pat = generalize ctx hd_pat in
+ let gtl_pat = generalize ctx tl_pat in
+ if is_wild ghd_pat && is_wild gtl_pat then GP_wild else GP_cons (ghd_pat, gtl_pat)
+ | P_app (f, pats) ->
+ let gpats = List.map (generalize ctx) pats in
+ if List.for_all is_wild gpats then
+ GP_app (Bindings.singleton f GP_wild)
+ else
+ GP_app (Bindings.singleton f (GP_tup gpats))
+ | P_record (fpats, flag) ->
+ let gfpats = List.concat (List.map (generalize_fpat ctx) fpats) in
+ GP_record (List.fold_left (fun m (fid, gpat) -> Bindings.add fid gpat m) Bindings.empty gfpats)
+
+and generalize_fpat ctx (FP_aux (FP_Fpat (field_id, pat), annot)) =
+ let gpat = generalize ctx pat in
+ if is_wild gpat then []
+ else
+ [(field_id, gpat)]
+
+let vector_pat bits =
+ let bit_pat = function
+ | '0' -> GP_lit (mk_lit L_zero)
+ | '1' -> GP_lit (mk_lit L_one)
+ | _ -> failwith "Invalid bit pattern"
+ in
+ GP_vector (List.map bit_pat (Util.string_to_list bits))
+
+let join_bits bits1 bits2 =
+ let join_bit bit1 bit2 = match bit1, bit2 with
+ | '0', '0' -> GP_lit (mk_lit L_zero)
+ | '1', '1' -> GP_lit (mk_lit L_one)
+ | _, _ -> GP_wild
+ in
+ let joined = List.map2 join_bit (Util.string_to_list bits1) (Util.string_to_list bits2) in
+ if List.for_all is_wild joined then GP_wild else GP_vector joined
+
+(* The join_lit function takes two patterns and produces a pattern
+ that matches both literals *)
+let rec join_lit (L_aux (l_aux1, _) as lit1) (L_aux (l_aux2, _) as lit2) =
+ match l_aux1, l_aux2 with
+ (* The only literal with type unit is the unit literal *)
+ | L_unit, _ -> GP_lit lit1
+ | _, L_unit -> GP_lit lit2
+
+ (* Bit literals don't change when they're the same, become wildcard
+ when we match both *)
+ | L_zero, L_zero -> GP_lit lit1
+ | L_one, L_one -> GP_lit lit1
+ | L_zero, L_one -> GP_wild
+ | L_one, L_zero -> GP_wild
+
+ (* Boolean literals work the same as bit literals *)
+ | L_false, L_false -> GP_lit lit1
+ | L_true, L_true -> GP_lit lit2
+ | L_false, L_true -> GP_wild
+ | L_true, L_false -> GP_wild
+
+ | L_hex hex, _ -> join_lit (mk_lit (L_bin (hex_to_bin hex))) lit2
+ | _, L_hex hex -> join_lit lit1 (mk_lit (L_bin (hex_to_bin hex)))
+ | L_bin bits1, L_bin bits2 -> join_bits bits1 bits2
+
+ (* The set of numbers is infinite, so no finite sequence of number
+ literals can match all numbers. As such we need a wildcard, so
+ the join lit function just returns one of the two numbers. *)
+ | L_num _, L_num _ -> GP_lit lit1
+
+ (* Strings are similar to number literals. *)
+ | L_string _, L_string _ -> GP_lit lit1
+
+ | L_real _, L_real _ -> GP_lit lit1
+
+ | L_undef, _ -> GP_wild
+ | _, L_undef -> GP_wild
+
+ | _, _ ->
+ (* This shouldn't happen if both patterns are well-typed, but we
+ include it here to ensure that join_lit won't fail. *)
+ let message =
+ Printf.sprintf "Have two differently typed pattern literals %s and %s matching the same thing"
+ (string_of_lit lit1) (string_of_lit lit2)
+ in
+ Util.warn message;
+ GP_wild
+
+let rec join ctx gpat1 gpat2 =
+ (* prerr_endline ("Join :" ^ string_of_gpat gpat1 ^ " with " ^ string_of_gpat gpat2); *)
+ match gpat1, gpat2 with
+ | GP_wild, _ -> GP_wild
+ | _, GP_wild -> GP_wild
+
+ | GP_lit lit1, GP_lit lit2 -> join_lit lit1 lit2
+
+ | GP_tup gpats1, GP_tup gpats2 ->
+ let joined = List.map2 (join ctx) gpats1 gpats2 in
+ if List.for_all is_wild joined then GP_wild else GP_tup joined
+
+ | GP_lit (L_aux (L_hex hex, _)), GP_vector _ ->
+ join ctx (vector_pat (hex_to_bin hex)) gpat2
+ | GP_lit (L_aux (L_bin bin, _)), GP_vector _ ->
+ join ctx (vector_pat bin) gpat2
+ | GP_vector _, GP_lit (L_aux (L_hex hex, annot)) ->
+ join ctx gpat1 (vector_pat (hex_to_bin hex))
+ | GP_vector _, GP_lit (L_aux (L_bin bin, _)) ->
+ join ctx gpat1 (vector_pat bin)
+
+ | GP_vector gpats1, GP_vector gpats2 ->
+ let joined = List.map2 (join ctx) gpats1 gpats2 in
+ if List.for_all is_wild joined then GP_wild else GP_vector joined
+
+ | GP_list gpats1, GP_list gpats2 ->
+ let joined = List.map2 (join ctx) gpats1 gpats2 in
+ if List.for_all is_wild joined then GP_wild else GP_list joined
+
+ | GP_app ctors1, GP_app ctors2 ->
+ let ctor_merge ctor args1 args2 =
+ match args1, args2 with
+ | None, None -> None
+ | Some args1, None -> Some args1
+ | None, Some args2 -> Some args2
+ | Some args1, Some args2 ->
+ assert false
+ in
+ let ctors = Bindings.merge ctor_merge ctors1 ctors2 in
+ if Bindings.for_all (fun _ gpat -> is_wild gpat) ctors then
+ let ids = IdSet.of_list (List.map fst (Bindings.bindings ctors)) in
+ let enums = List.map snd (Bindings.bindings ctx.enums) in
+ let variants = List.map snd (Bindings.bindings ctx.variants) in
+ if List.exists (fun ids' -> IdSet.equal ids ids') (enums @ variants) then
+ GP_wild
+ else
+ GP_app ctors
+ else
+ GP_app ctors
+
+ | _, _ -> GP_wild
+
+let combine ctx gpat (l, pat) =
+ match gpat, generalize ctx pat with
+ | GP_wild, GP_app _ ->
+ (* This warning liable to false positives as join returns a
+ pattern that overapproximates what can match, so we only
+ report when the second match is a constructor. *)
+ Util.warn (Printf.sprintf "Possible redundant pattern match at %s\n" (Reporting_basic.loc_to_string l));
+ GP_wild
+ | _, gpat' -> join ctx gpat gpat'
+
+let rec cases_to_pats = function
+ | [] -> []
+ | Pat_aux (Pat_exp (P_aux (_, (l, _)) as pat, _), _) :: cases -> (l, pat) :: cases_to_pats cases
+ (* We don't consider guarded cases *)
+ | Pat_aux (Pat_when _, _) :: cases -> cases_to_pats cases
+
+(* Just highlight the match keyword and no the whole match block. *)
+let shrink_loc = function
+ | Parse_ast.Range (n, m) ->
+ Lexing.(Parse_ast.Range (n, { n with pos_cnum = n.pos_cnum + 5 }))
+ | l -> l
+
+let check l ctx cases =
+ match cases_to_pats cases with
+ | [] -> Util.warn (Printf.sprintf "No non-guarded patterns at %s\n" (Reporting_basic.loc_to_string (shrink_loc l)))
+ | (_, pat) :: pats ->
+ let top_pat = List.fold_left (combine ctx) (generalize ctx pat) pats in
+ if is_wild top_pat then
+ ()
+ else
+ let message =
+ Printf.sprintf "Possible incomplete pattern match at %s\n\nMost general matched pattern is %s\n"
+ (Reporting_basic.loc_to_string (shrink_loc l))
+ (string_of_gpat top_pat |> Util.cyan |> Util.clear)
+ in
+ Util.warn message
diff --git a/src/sail.ml b/src/sail.ml
index d5ef684b..d6d5eebc 100644
--- a/src/sail.ml
+++ b/src/sail.ml
@@ -78,6 +78,9 @@ let options = Arg.align ([
Arg.Tuple [Arg.Set opt_interactive; Arg.Set Initial_check.opt_undefined_gen;
Arg.String (fun s -> opt_interactive_script := Some s)],
"<filename> start interactive interpreter and execute commands in script");
+ ( "-no_warn",
+ Arg.Clear Util.opt_warnings,
+ " do not print warnings");
( "-ocaml",
Arg.Tuple [Arg.Set opt_print_ocaml; Arg.Set Initial_check.opt_undefined_gen],
" output an OCaml translated version of the input");
diff --git a/src/type_check.ml b/src/type_check.ml
index d4920c5a..41438592 100644
--- a/src/type_check.ml
+++ b/src/type_check.ml
@@ -285,10 +285,6 @@ let typquant_subst_kid sv subst (TypQ_aux (typq, l)) = TypQ_aux (typquant_subst_
(* 2. Environment *)
(**************************************************************************)
-type mut = Immutable | Mutable
-
-type lvar = Register of typ | Enum of typ | Local of mut * typ | Union of typquant * typ | Unbound
-
module Env : sig
type t
val add_val_spec : id -> typquant * typ -> t -> t
@@ -357,6 +353,8 @@ module Env : sig
(* This must not be exported, initial_env sets up a correct initial
environment. *)
val empty : t
+
+ val pattern_completeness_ctx : t -> Pattern_completeness.ctx
end = struct
type t =
{ top_val_specs : (typquant * typ) Bindings.t;
@@ -927,6 +925,12 @@ end = struct
{ env with poly_undefineds = true }
let polymorphic_undefineds env = env.poly_undefineds
+
+ let pattern_completeness_ctx env =
+ { Pattern_completeness.lookup_id = (fun id -> lookup_id id env);
+ Pattern_completeness.enums = env.enums;
+ Pattern_completeness.variants = Bindings.map (fun (_, tus) -> IdSet.of_list (List.map type_union_id tus)) env.variants
+ }
end
@@ -2053,6 +2057,7 @@ let rec check_exp env (E_aux (exp_aux, (l, ())) as exp : unit exp) (Typ_aux (typ
annot_exp (E_block (check_block l env exps typ)) typ
end
| E_case (exp, cases), _ ->
+ Pattern_completeness.check l (Env.pattern_completeness_ctx env) cases;
let inferred_exp = irule infer_exp env exp in
let inferred_typ = typ_of inferred_exp in
annot_exp (E_case (inferred_exp, List.map (fun case -> check_case env inferred_typ case typ) cases)) typ
diff --git a/src/type_check.mli b/src/type_check.mli
index b904769f..340cf6e0 100644
--- a/src/type_check.mli
+++ b/src/type_check.mli
@@ -63,13 +63,6 @@ val opt_tc_debug : int ref
re-writer passes, so it should only be used for debugging. *)
val opt_no_effects : bool ref
-type mut = Immutable | Mutable
-
-(** [lvar] is the type of variables - they can either be registers,
- local mutable or immutable variables, nullary union constructors
- (i.e. None in option), or unbound identifiers *)
-type lvar = Register of typ | Enum of typ | Local of mut * typ | Union of typquant * typ | Unbound
-
(** {2 Type errors} *)
type type_error =
diff --git a/src/util.ml b/src/util.ml
index 6d4b3726..e2dc9b9f 100644
--- a/src/util.ml
+++ b/src/util.ml
@@ -94,6 +94,8 @@
(* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *)
(**************************************************************************)
+let opt_warnings = ref true
+
let rec last = function
| [x] -> x
| _ :: xs -> last xs
@@ -414,4 +416,7 @@ let zencode_string str = "z" ^ List.fold_left (fun s1 s2 -> s1 ^ s2) "" (List.ma
let zencode_upper_string str = "Z" ^ List.fold_left (fun s1 s2 -> s1 ^ s2) "" (List.map zchar (string_to_list str))
-let warn str = prerr_endline (("Warning" |> yellow |> clear) ^ ": " ^ str)
+let warn str =
+ if !opt_warnings then
+ prerr_endline (("Warning" |> yellow |> clear) ^ ": " ^ str)
+ else ()
diff --git a/src/util.mli b/src/util.mli
index c9eae02f..2b4d2e93 100644
--- a/src/util.mli
+++ b/src/util.mli
@@ -51,6 +51,8 @@
(* Last element of a list *)
val last : 'a list -> 'a
+val opt_warnings : bool ref
+
val butlast : 'a list -> 'a list
(** Mixed useful things *)
@@ -241,6 +243,6 @@ val blue : string -> string
val clear : string -> string
val warn : string -> unit
-
+
val zencode_string : string -> string
val zencode_upper_string : string -> string