diff options
Diffstat (limited to 'src/pattern_completeness.ml')
| -rw-r--r-- | src/pattern_completeness.ml | 294 |
1 files changed, 294 insertions, 0 deletions
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 |
