Source

ocaml / typing / parmatch.ml

(* Detection of partial matches and unused match cases. *)

open Misc
open Asttypes
open Typedtree


let make_pat desc ty =
  {pat_desc = desc; pat_loc = Location.none; pat_type = ty}

let omega = make_pat Tpat_any Ctype.none

let rec omegas i =
  if i <= 0 then [] else omega :: omegas (i-1)

let omega_list l = omegas(List.length l)

let has_guard act =
  match act.exp_desc with
    Texp_when(_, _) -> true
  | _ -> false

let simple_match p1 p2 = 
  match p1.pat_desc, p2.pat_desc with
    Tpat_construct(c1, _), Tpat_construct(c2, _) ->
      c1.cstr_tag = c2.cstr_tag
  | Tpat_constant(c1), Tpat_constant(c2) ->
      c1 = c2
  | Tpat_tuple(_), Tpat_tuple(_) -> true
  | Tpat_record(_), Tpat_record(_) -> true
  | _, (Tpat_any | Tpat_var(_)) -> true
  | _, _ -> false

(* Return the set of labels and number of fields for a record pattern. *)

let record_labels p =
  match p.pat_desc with
    Tpat_record((lbl1, pat1) :: rem) -> Array.to_list lbl1.lbl_all
  | _ -> fatal_error "Parmatch.record_labels"

let record_num_fields p =
  match p.pat_desc with
    Tpat_record((lbl1, pat1) :: rem) -> Array.length lbl1.lbl_all
  | _ -> fatal_error "Parmatch.record_num_fields"

let set_fields size l =
  let v = Array.new size omega in
  let rec change_rec l = match l with
    (lbl,p)::l ->  v.(lbl.lbl_pos) <- p ;  change_rec l 
  | [] -> () in
  change_rec l;
  Array.to_list v

let simple_match_args p1 p2 =
  match p2.pat_desc with
    Tpat_construct(cstr, args) -> args
  | Tpat_tuple(args)  -> args
  | Tpat_record(args) ->  set_fields (record_num_fields p1) args
  | (Tpat_any | Tpat_var(_)) ->
      begin match p1.pat_desc with
        Tpat_construct(_, args) -> omega_list args
      | Tpat_tuple(args) -> omega_list args
      | Tpat_record(args) ->  omega_list args
      | _ -> []
      end
  | _ -> []

(*
  Computes the discriminating pattern for matching by the first
  column of pss, that is:
     checks for a tuple or a record when q is a variable.
*)

let rec simple_pat q pss = match pss with
    ({pat_desc = Tpat_alias(p,_)}::ps)::pss -> 
        simple_pat q ((p::ps)::pss)
  | ({pat_desc = Tpat_or(p1,p2)}::ps)::pss ->
        simple_pat q ((p1::ps)::(p2::ps)::pss)
  | ({pat_desc = (Tpat_any | Tpat_var(_))}::_)::pss ->
        simple_pat q pss
  | (({pat_desc = Tpat_tuple(args)} as p)::_)::_ ->
        make_pat (Tpat_tuple(omega_list args)) p.pat_type
  | (({pat_desc = Tpat_record(args)} as p)::_)::pss ->
        make_pat (Tpat_record (List.map (fun lbl -> (lbl,omega)) (record_labels p)))
                 p.pat_type
  | _ -> q

let filter_one q pss =
  let rec filter_rec = function
      ({pat_desc = Tpat_alias(p,_)}::ps)::pss -> 
        filter_rec ((p::ps)::pss)
    | ({pat_desc = Tpat_or(p1,p2)}::ps)::pss ->
        filter_rec ((p1::ps)::(p2::ps)::pss)
    | (p::ps)::pss ->
        if simple_match q p
        then (simple_match_args q p @ ps) :: filter_rec pss
        else filter_rec pss
    | _ -> [] in
  filter_rec pss

let filter_extra pss =
  let rec filter_rec = function
      ({pat_desc = Tpat_alias(p,_)}::ps)::pss -> 
        filter_rec ((p::ps)::pss)
    | ({pat_desc = Tpat_or(p1,p2)}::ps)::pss ->
        filter_rec ((p1::ps)::(p2::ps)::pss)
    | ({pat_desc = (Tpat_any | Tpat_var(_))} :: qs) :: pss ->
        qs :: filter_rec pss
    | _::pss  -> filter_rec pss
    | [] -> [] in
  filter_rec pss

let filter_all pat0 pss =

  let rec insert q qs env =
    match env with
      [] -> [q, [simple_match_args q q @ qs]]
    | ((p,pss) as c)::env ->
        if simple_match q p
        then (p, ((simple_match_args p q @ qs) :: pss)) :: env
        else c :: insert q qs env in

  let rec filter_rec env = function
    ({pat_desc = Tpat_alias(p,_)}::ps)::pss ->
      filter_rec env ((p::ps)::pss)
  | ({pat_desc = Tpat_or(p1,p2)}::ps)::pss ->
      filter_rec env ((p1::ps)::(p2::ps)::pss)
  | ({pat_desc = (Tpat_any | Tpat_var(_))}::_)::pss ->
      filter_rec env pss
  | (p::ps)::pss ->
      filter_rec (insert p ps env) pss
  | _ -> env

  and filter_omega env = function
    ({pat_desc = Tpat_alias(p,_)}::ps)::pss ->
      filter_omega env ((p::ps)::pss)
  | ({pat_desc = Tpat_or(p1,p2)}::ps)::pss ->
      filter_omega env ((p1::ps)::(p2::ps)::pss)
  | ({pat_desc = (Tpat_any | Tpat_var(_))}::ps)::pss ->
      filter_omega
        (List.map (fun (q,qss) -> (q,(simple_match_args q omega @ ps) :: qss)) env)
        pss
  | _::pss -> filter_omega env pss
  | [] -> env in
        
  filter_omega
    (filter_rec
      (match pat0.pat_desc with
        (Tpat_record(_) | Tpat_tuple(_)) -> [pat0,[]]
      | _ -> [])
      pss)
    pss

      
let full_match env =
  match env with
    ({pat_desc = Tpat_construct(c,_)},_) :: _ ->
      List.length env = c.cstr_consts + c.cstr_nonconsts
  | ({pat_desc = Tpat_constant(Const_char _)},_) :: _ ->
      List.length env = 256
  | ({pat_desc = Tpat_constant(_)},_) :: _ -> false
  | ({pat_desc = Tpat_tuple(_)},_) :: _ -> true
  | ({pat_desc = Tpat_record(_)},_) :: _ -> true
  | _ -> fatal_error "Parmatch.full_match"

(*
  Is the last row of pattern matrix pss + qs satisfiable ?
        That is :
  Does there List.exists at least one value vector, es such that :
   1/ for all ps in pss ps # es (ps and es are not compatible)
   2/ qs <= es                  (es matches qs)
*)

let rec satisfiable pss qs =
  match pss with
    [] -> true
  | _ ->
    match qs with
      [] -> false
    | {pat_desc = Tpat_or(q1,q2)}::qs ->
        satisfiable pss (q1::qs) or satisfiable pss (q2::qs)
    | {pat_desc = Tpat_alias(q,_)}::qs ->
        satisfiable pss (q::qs)
    | {pat_desc = (Tpat_any | Tpat_var(_))}::qs ->
        let q0 = simple_pat omega pss in     
        begin match filter_all q0 pss with
          (* first column of pss is made of variables only *)
          [] -> satisfiable (filter_extra pss) qs 
        | constrs ->          
            let try_non_omega (p,pss) =
              satisfiable pss (simple_match_args p omega @ qs)  in
            if full_match constrs
            then List.exists try_non_omega constrs
            else satisfiable (filter_extra pss) qs or
                 List.exists try_non_omega constrs
        end
    | q::qs ->
        let q0 = simple_pat q pss in
        satisfiable (filter_one q0 pss) (simple_match_args q0 q @ qs)

let rec initial_matrix = function
    [] -> []
  | (pat, act) :: rem ->
       if has_guard act
       then initial_matrix rem
       else [pat] :: initial_matrix rem

let rec le_pat p q =
  match (p.pat_desc, q.pat_desc) with
    (Tpat_var _ | Tpat_any), _ -> true
  | Tpat_alias(p,_), _ -> le_pat p q
  | _, Tpat_alias(q,_) -> le_pat p q
  | Tpat_or(p1,p2), _ -> le_pat p1 q or le_pat p2 q
  | _, Tpat_or(q1,q2) -> le_pat p q1 & le_pat p q2
  | Tpat_constant(c1), Tpat_constant(c2) -> c1 = c2
  | Tpat_construct(c1,ps), Tpat_construct(c2,qs) ->
      c1.cstr_tag = c2.cstr_tag & le_pats ps qs
  | Tpat_tuple(ps), Tpat_tuple(qs) -> le_pats ps qs
  | Tpat_record(l1), Tpat_record(l2) ->
     let size = record_num_fields p in
     le_pats (set_fields size l1) (set_fields size l2)
  | _, _ -> false  

and le_pats ps qs =
  match ps,qs with
    p::ps, q::qs -> le_pat p q & le_pats ps qs
  | _, _         -> true

let get_mins ps =
  let rec select_rec r = function
    [] -> r
  | p::ps ->
      if List.exists (fun p0 -> le_pats p0 p) ps
      then select_rec r ps
      else select_rec (p::r) ps in
  select_rec [] (select_rec [] ps)

let check_partial loc casel =
  let pss = get_mins (initial_matrix casel) in
  if match pss with
      []     -> true
    | ps::_  -> satisfiable pss (List.map (fun _ -> omega) ps)
  then Location.print_warning loc "this pattern-matching is not exhaustive"

let location_of_clause = function
    pat :: _ -> pat.pat_loc
  | _ -> fatal_error "Parmatch.location_of_clause"

let check_unused casel =
  let prefs =   
    List.fold_right
      (fun (pat,act as clause) r ->
         if has_guard act
         then ([], ([pat], act)) :: r
         else ([], ([pat], act)) :: 
              List.map (fun (pss,clause) -> [pat]::pss,clause) r)
      casel [] in
  List.iter
    (fun (pss, ((qs, _) as clause)) ->
      if not (satisfiable pss qs) then
        Location.print_warning (location_of_clause qs)
                                "this match case is unused.")
    prefs