[MLton] Implementing warnExnMatch

Vesa Karvonen vesa.karvonen@cs.helsinki.fi
Mon, 25 Jul 2005 19:27:58 +0300


I've made a bit of progress on implementing `warnExnMatch' and I have a few
questions. The code in this message is preliminary and incomplete.

It was easy to add the `warnExnMatch' annotation:

<--- cvs diff -u --->
Index: mlton/control/control-flags.sig
===================================================================
RCS file: /cvsroot/mlton/mlton/mlton/control/control-flags.sig,v
retrieving revision 1.4
diff -u -r1.4 control-flags.sig
--- mlton/control/control-flags.sig     23 Jul 2005 11:55:39 -0000      1.4
+++ mlton/control/control-flags.sig     25 Jul 2005 15:22:13 -0000
@@ -72,6 +72,7 @@
            (* in (e1; e2), require e1: unit. *)
            val sequenceUnit: (bool,bool) t
            val warnMatch: (bool,bool) t
+           val warnExnMatch: (bool,bool) t
            val warnUnused: (bool,bool) t

            val current: ('args, 'st) t -> 'st
Index: mlton/control/control-flags.sml
===================================================================
RCS file: /cvsroot/mlton/mlton/mlton/control/control-flags.sml,v
retrieving revision 1.5
diff -u -r1.5 control-flags.sml
--- mlton/control/control-flags.sml     23 Jul 2005 11:55:39 -0000      1.5
+++ mlton/control/control-flags.sml     25 Jul 2005 15:22:14 -0000
@@ -334,6 +334,8 @@
            makeBool ({name = "sequenceUnit", default = false, expert = false}, ac)
         val (warnMatch, ac) =
            makeBool ({name = "warnMatch", default = true, expert = false}, ac)
+        val (warnExnMatch, ac) =
+           makeBool ({name = "warnExnMatch", default = true, expert = false}, ac)
         val (warnUnused, ac) =
            makeBool ({name = "warnUnused", default = false, expert = false}, ac)
         val {parseId, parseIdAndArgs, withDef, snapshot} = ac
<--- cvs diff -u --->

Should we also provide a separate command line option (`-warn-exn-match')?
I'm quite happy with just the annotation, which can be specified on the
command line through `-default-ann'.

Figuring out where to put the code that actually disables the warning and
how to test whether a type is the same type as `exn' took me much longer.

One source of confusion was that the `warnMatch' warning is actually
generated by the Defunctorize pass, but the Elaborate pass determines
whether a warning should be generated for a specific declaration or
expression.

After finding a proper spot to silence the warning, I first tried to find
an `equals' method on types. Then I briefly though about using unification,
but it seemed wrong. Finally, I saw that Tycons can be compared for equality.

I then modifed elaborate as follows (to see if it works):

<--- cvs diff -u --->
Index: mlton/elaborate/elaborate-core.fun
===================================================================
RCS file: /cvsroot/mlton/mlton/mlton/elaborate/elaborate-core.fun,v
retrieving revision 1.153
diff -u -r1.153 elaborate-core.fun
--- mlton/elaborate/elaborate-core.fun  23 Jul 2005 11:55:40 -0000      1.153
+++ mlton/elaborate/elaborate-core.fun  25 Jul 2005 15:22:14 -0000
@@ -17,6 +17,7 @@
    val allowRebindEquals = fn () => current allowRebindEquals
    val sequenceUnit = fn () => current sequenceUnit
    val warnMatch = fn () => current warnMatch
+   val warnExnMatch = fn () => current warnExnMatch
 end

 local
@@ -2911,7 +2912,12 @@
                           region = region,
                           rules = rules,
                           test = Cexp.var (arg, argType),
-                          warnMatch = warnMatch ()}
+                          warnMatch = if (case Type.deConOpt argType of
+                                            NONE => false
+                                          | SOME (c,_) => Tycon.equals (c, Tycon.exn))
+                                         then warnMatch () andalso warnExnMatch ()
+                                      else
+                                         warnMatch ()}
         in
           {arg = arg,
            argType = argType,
<--- cvs diff -u --->

Is the above the correct way test for the `exn' type?

Of course, the test is probably best factored into an auxiliary function.
Actually, there seems to be some room for abstraction / refactoring. If
you look for `val isBool' in `elaborate-core.fun', you'll find out that it
is defined three times identically.

One question I have in mind is the proper behavior of `warnExnMatch' in
conjunction with `warnMatch'. One reasonable behavior would be that
`warnExnMatch' can only be used to turn warnings off, but never to turn
them on:

         warnMatch   t t f f
      warnExnMatch   t f t f
    --------------------------
       warn on exn   t f f f
                         *

Another alternative would be to allow warnExnMatch to also turn warnings
on:

         warnMatch   t t f f
      warnExnMatch   t f t f
    --------------------------
       warn on exn   t f t f
                         *

Which should be preferred?

Another question is which match contexts should be subject to
`warnExnMatch'? I see 5 places in `elaborate-core.fun' that refer to
`warnMatch ()' and seem to correspond to (in order from top to bottom):

  1.  fun declaration (Adec.Fun / fun ...)
  2a. val rec declaration (Adec.Val / val rec ... = fn ...)
  2b. val declaration (Adec.Val / val ...)
  3.  case expression (Aexp.Case / case ...)
  4.  fn expression (elabMatchFn / Aexp.Fn / fn ...)
     [elabMatchFn is also called on Aexp.Handle]

Is the above list correct?

Should all of the contexts be subject to `warnExnMatch' or would it be
desirable to limit `warnExnMatch' to only certain kinds of matches? My
code will probably only use case 3, but I think that it would be more
logical to apply `warnExnMatch' to all except perhaps case 2b.

-Vesa Karvonen