Merge: Stricter default arguments
authorJean Privat <jean@pryen.org>
Mon, 10 Aug 2015 16:12:51 +0000 (12:12 -0400)
committerJean Privat <jean@pryen.org>
Mon, 10 Aug 2015 16:12:51 +0000 (12:12 -0400)
This change the way that default arguments are handled.

Now, defaults arguments can only follow the used one.
This improve the readability of calls and the association between calls and declarations thus solve a lot of issues of users.

~~~
fun foo(a: nullable Int, b: Int, c: nullable Int) do ...
foo(null,2,null) # OK
foo(null,2) # equivalent
foo(2) # now refused! but previously accepted as equivalent.
~~~

Only exception: the last parameter of an assignment method is always the last argument

~~~
fun foo=(a: nullable Int, b: Int, c: nullable Int, d: nullable Int) do ...
foo(null,2,null) = 0 # OK
foo(null,2) = 0 # equivalent
foo(2) = 0 # now refused! but previously accepted as equivalent
~~~

No specific black magic is added to automatic constructor.
Therefore an optional constructor parameter in a class can becore mandatory in a subclass if a new mandatory attribute is introduced.

~~~
class A
   var a: nullable Int
end
class B
  super A
  var b: Int
end
var a1 = new A(null) # OK
var a2 = new A # equivalent
var b1 = new B(null, 2) # OK
var b2 = new B(2) # now refused! but previously accepted as equivalent
~~~

This issue only required some small adaptation in existing piece of code: nitdoc, sexp, serialization and dom. The latter is PRized independently in #1616 but included here for jenkins.
Most of these changes (first commits) are in fact bugfixes or make the code cleaner so this is an argument in favor of this new stricter specification.

This PR has also the advantage of simplifying the model as the whole policy of the default argument is moved to the typing analysis: it is now just a pure calling convention.

Close #1453

Pull-Request: #1618
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

1  2 
src/semantize/typing.nit

diff --combined src/semantize/typing.nit
@@@ -414,26 -414,30 +414,30 @@@ private class TypeVisito
                                return null
                        end
                else if args.length != msignature.arity then
-                       if msignature.arity == msignature.min_arity then
-                               modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
-                               return null
-                       end
+                       # Too much argument
                        if args.length > msignature.arity then
-                               modelbuilder.error(node, "Error: expected at most {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
-                               return null
-                       end
-                       if args.length < msignature.min_arity then
-                               modelbuilder.error(node, "Error: expected at least {msignature.min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                               modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
                                return null
                        end
+                       # Other cases are managed later
                end
  
                #debug("CALL {unsafe_type}.{msignature}")
  
                # Associate each parameter to a position in the arguments
                var map = new SignatureMap
  
-               var setted = args.length - msignature.min_arity
+               # Special case for the isolated last argument
+               # TODO: reify this method characteristics (where? the param, the signature, the method?)
+               var last_is_padded = mproperty.name.chars.last == '='
+               var nbargs = args.length
+               if last_is_padded then
+                       nbargs -= 1
+                       assert not args.last isa ANamedargExpr
+                       map.map[msignature.arity - 1] = args.length - 1
+                       self.visit_expr_subtype(args.last, msignature.mparameters.last.mtype)
+               end
  
                # First, handle named arguments
                for i in [0..args.length[ do
                                modelbuilder.error(e.n_id, "Error: no parameter `{name}` for `{mproperty}{msignature}`.")
                                return null
                        end
-                       if not param.is_default then
-                               modelbuilder.error(e, "Error: parameter `{name}` is not optional for `{mproperty}{msignature}`.")
-                               return null
-                       end
                        var idx = msignature.mparameters.index_of(param)
                        var prev = map.map.get_or_null(idx)
                        if prev != null then
                                return null
                        end
                        map.map[idx] = i
                        e.mtype = self.visit_expr_subtype(e.n_expr, param.mtype)
                end
  
+               # Number of minimum mandatory remaining parameters
+               var min_arity = 0
                # Second, associate remaining parameters
                var vararg_decl = args.length - msignature.arity
                var j = 0
                        if map.map.has_key(i) then continue
  
                        var param = msignature.mparameters[i]
-                       if param.is_default then
-                               if setted > 0 then
-                                       setted -= 1
-                               else
-                                       continue
-                               end
-                       end
  
                        # Search the next free argument: skip named arguments since they are already associated
-                       while args[j] isa ANamedargExpr do j += 1
+                       while j < nbargs and args[j] isa ANamedargExpr do j += 1
+                       if j >= nbargs then
+                               if not param.mtype isa MNullableType then
+                                       min_arity = j + 1
+                               end
+                               j += 1
+                               continue
+                       end
                        var arg = args[j]
                        map.map[i] = j
                        j += 1
                        self.visit_expr_subtype(arg, paramtype)
                end
  
+               if min_arity > 0 then
+                       if last_is_padded then min_arity += 1
+                       if min_arity < msignature.arity then
+                               modelbuilder.error(node, "Error: expected at least {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                       else
+                               modelbuilder.error(node, "Error: expected {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                       end
+                       return null
+               end
                # Third, check varargs
                if vararg_rank >= 0 then
                        var paramtype = msignature.mparameters[vararg_rank].mtype
  
  
  redef class ACallExpr
 -      redef fun property_name do return n_id.text
 -      redef fun property_node do return n_id
 +      redef fun property_name do return n_qid.n_id.text
 +      redef fun property_node do return n_qid
        redef fun compute_raw_arguments do return n_args.to_a
  end
  
  redef class ACallAssignExpr
 -      redef fun property_name do return n_id.text + "="
 -      redef fun property_node do return n_id
 +      redef fun property_name do return n_qid.n_id.text + "="
 +      redef fun property_node do return n_qid
        redef fun compute_raw_arguments
        do
                var res = n_args.to_a
@@@ -1841,8 -1853,8 +1853,8 @@@ redef class ASendReassignFormExp
  end
  
  redef class ACallReassignExpr
 -      redef fun property_name do return n_id.text
 -      redef fun property_node do return n_id
 +      redef fun property_name do return n_qid.n_id.text
 +      redef fun property_node do return n_qid.n_id
        redef fun compute_raw_arguments do return n_args.to_a
  end
  
@@@ -2014,11 -2026,11 +2026,11 @@@ redef class ANewExp
                var kind = recvtype.mclass.kind
  
                var name: String
 -              var nid = self.n_id
 +              var nqid = self.n_qid
                var node: ANode
 -              if nid != null then
 -                      name = nid.text
 -                      node = nid
 +              if nqid != null then
 +                      name = nqid.n_id.text
 +                      node = nqid
                else
                        name = "new"
                        node = self.n_kwnew