Merge: Check operator
authorJean Privat <jean@pryen.org>
Sat, 18 Apr 2015 09:47:00 +0000 (16:47 +0700)
committerJean Privat <jean@pryen.org>
Sat, 18 Apr 2015 09:47:00 +0000 (16:47 +0700)
Previously, no checking was done on the signature of operators.
While this is not an issue for the model nor the tools this could yield to not POLA error messages when the operator is used. eg.

~~~nit
class A
   fun +(other: A) do print "hello"
end
var a = new A
var b = a + a # Error expected expression
a + a # Error unexpected operator +
# no way do invoke `+` in fact.
~~~

With the PR, we have

~~~
Error: mandatory return type for `+`.
fun +(other: A) do print "hello"
    ^
~~~

The following errors are added by the PR:

* mandatory return
* not enough parameters
* too much parameters
* illegal variadic parameter

Pull-Request: #1269
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Romain Chanoir <chanoir.romain@courrier.uqam.ca>

lib/standard/collection/array.nit
src/compiler/abstract_compiler.nit
src/interpreter/naive_interpreter.nit
src/semantize/typing.nit
tests/sav/test_new_native_alt1.res

index 3cc0b57..cdb5242 100644 (file)
@@ -147,6 +147,55 @@ abstract class AbstractArrayRead[E]
        private var free_iterator: nullable ArrayIterator[E] = null
 
        redef fun reverse_iterator do return new ArrayReverseIterator[E](self)
+
+       # Returns a sub-array containing `count` elements starting from `from`.
+       #
+       # For most cases (see other case bellow),
+       # the first element is `from` and
+       # the last element is `from+count-1`.
+       #
+       # ~~~
+       # var a = [10, 20, 30, 40, 50]
+       # assert a.sub(0, 3) == [10, 20, 30]
+       # assert a.sub(3, 2) == [40, 50]
+       # assert a.sub(3, 1) == [40]
+       # ~~~
+       #
+       # If `count` is 0 or negative then an empty array is returned
+       #
+       # ~~~
+       # assert a.sub(3,0).is_empty
+       # assert a.sub(3,-1).is_empty
+       # ~~~
+       #
+       # If `from < 0` or `from+count>length` then inexistent elements are ignored.
+       # In this case the length of the result is lower than count.
+       #
+       # ~~~
+       # assert a.sub(-2, 4)  == [10, 20]
+       # assert a.sub(4, 99)  == [50]
+       # assert a.sub(-9, 99) == [10,20,30,40,50]
+       # assert a.sub(-99, 9).is_empty
+       # ~~~
+       fun sub(from: Int, count: Int): Array[E] do
+               if from < 0 then
+                       count += from
+                       from = 0
+               end
+               if count < 0 then
+                       count = 0
+               end
+               var to = from + count
+               if to > length then
+                       to = length
+               end
+               var res = new Array[E].with_capacity(to - from)
+               while from < to do
+                       res.add(self[from])
+                       from += 1
+               end
+               return res
+       end
 end
 
 # Resizable one dimension array of objects.
index 2359c7d..1b8bf6a 100644 (file)
@@ -1146,40 +1146,43 @@ abstract class AbstractCompilerVisitor
        # Evaluate `args` as expressions in the call of `mpropdef` on `recv`.
        # This method is used to manage varargs in signatures and returns the real array
        # of runtime variables to use in the call.
-       fun varargize(mpropdef: MMethodDef, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable]
+       fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable]
        do
                var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
                var res = new Array[RuntimeVariable]
                res.add(recv)
 
-               if args.is_empty then return res
+               if msignature.arity == 0 then return res
 
-               var vararg_rank = msignature.vararg_rank
-               var vararg_len = args.length - msignature.arity
-               if vararg_len < 0 then vararg_len = 0
+               if map == null then
+                       assert args.length == msignature.arity
+                       for ne in args do
+                               res.add self.expr(ne, null)
+                       end
+                       return res
+               end
+
+               # Eval in order of arguments, not parameters
+               var exprs = new Array[RuntimeVariable].with_capacity(args.length)
+               for ne in args do
+                       exprs.add self.expr(ne, null)
+               end
 
+               # Fill `res` with the result of the evaluation according to the mapping
                for i in [0..msignature.arity[ do
-                       if i == vararg_rank then
-                               var ne = args[i]
-                               if ne isa AVarargExpr then
-                                       var e = self.expr(ne.n_expr, null)
-                                       res.add(e)
-                                       continue
-                               end
-                               var vararg = new Array[RuntimeVariable]
-                               for j in [vararg_rank..vararg_rank+vararg_len] do
-                                       var e = self.expr(args[j], null)
-                                       vararg.add(e)
-                               end
-                               var elttype = msignature.mparameters[vararg_rank].mtype
+                       var param = msignature.mparameters[i]
+                       var j = map.map.get_or_null(i)
+                       if j == null then
+                               continue
+                       end
+                       if param.is_vararg and map.vararg_decl > 0 then
+                               var vararg = exprs.sub(j, map.vararg_decl)
+                               var elttype = param.mtype
                                var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
                                res.add(arg)
-                       else
-                               var j = i
-                               if i > vararg_rank then j += vararg_len
-                               var e = self.expr(args[j], null)
-                               res.add(e)
+                               continue
                        end
+                       res.add exprs[j]
                end
                return res
        end
@@ -2975,7 +2978,7 @@ redef class ASendExpr
        do
                var recv = v.expr(self.n_expr, null)
                var callsite = self.callsite.as(not null)
-               var args = v.varargize(callsite.mpropdef, recv, self.raw_arguments)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
                return v.compile_callsite(callsite, args)
        end
 end
@@ -2985,7 +2988,7 @@ redef class ASendReassignFormExpr
        do
                var recv = v.expr(self.n_expr, null)
                var callsite = self.callsite.as(not null)
-               var args = v.varargize(callsite.mpropdef, recv, self.raw_arguments)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
 
                var value = v.expr(self.n_value, null)
 
@@ -3007,26 +3010,33 @@ redef class ASuperExpr
 
                var callsite = self.callsite
                if callsite != null then
-                       var args = v.varargize(callsite.mpropdef, recv, self.n_args.n_exprs)
+                       var args
 
-                       # Add additional arguments for the super init call
-                       if args.length == 1 then
+                       if self.n_args.n_exprs.is_empty then
+                               # Add automatic arguments for the super init call
+                               args = [recv]
                                for i in [0..callsite.msignature.arity[ do
                                        args.add(v.frame.arguments[i+1])
                                end
+                       else
+                               args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs)
                        end
+
                        # Super init call
                        var res = v.compile_callsite(callsite, args)
                        return res
                end
 
                var mpropdef = self.mpropdef.as(not null)
-               var args = v.varargize(mpropdef, recv, self.n_args.n_exprs)
-               if args.length == 1 then
+
+               var args
+               if self.n_args.n_exprs.is_empty then
                        args = v.frame.arguments
+               else
+                       args = v.varargize(mpropdef, signaturemap, recv, self.n_args.n_exprs)
                end
 
-               # stantard call-next-method
+               # Standard call-next-method
                return v.supercall(mpropdef, recv.mtype.as(MClassType), args)
        end
 end
@@ -3050,7 +3060,7 @@ redef class ANewExpr
                var callsite = self.callsite
                if callsite == null then return recv
 
-               var args = v.varargize(callsite.mpropdef, recv, self.n_args.n_exprs)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs)
                var res2 = v.compile_callsite(callsite, args)
                if res2 != null then
                        #self.debug("got {res2} from {mproperty}. drop {recv}")
@@ -3102,6 +3112,13 @@ redef class AIssetAttrExpr
        end
 end
 
+redef class AVarargExpr
+       redef fun expr(v)
+       do
+               return v.expr(self.n_expr, null)
+       end
+end
+
 redef class ADebugTypeExpr
        redef fun stmt(v)
        do
index 7d7cab9..c1bd59b 100644 (file)
@@ -370,42 +370,48 @@ class NaiveInterpreter
        # This method is used to manage varargs in signatures and returns the real array
        # of instances to use in the call.
        # Return `null` if one of the evaluation of the arguments return null.
-       fun varargize(mpropdef: MMethodDef, recv: Instance, args: SequenceRead[AExpr]): nullable Array[Instance]
+       fun varargize(mpropdef: MMethodDef, map: nullable SignatureMap, recv: Instance, args: SequenceRead[AExpr]): nullable Array[Instance]
        do
                var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
                var res = new Array[Instance]
                res.add(recv)
 
-               if args.is_empty then return res
+               if msignature.arity == 0 then return res
+
+               if map == null then
+                       assert args.length == msignature.arity else debug("Expected {msignature.arity} args, got {args.length}")
+                       for ne in args do
+                               var e = self.expr(ne)
+                               if e == null then return null
+                               res.add e
+                       end
+                       return res
+               end
+
+               # Eval in order of arguments, not parameters
+               var exprs = new Array[Instance].with_capacity(args.length)
+               for ne in args do
+                       var e = self.expr(ne)
+                       if e == null then return null
+                       exprs.add e
+               end
 
-               var vararg_rank = msignature.vararg_rank
-               var vararg_len = args.length - msignature.arity
-               if vararg_len < 0 then vararg_len = 0
 
+               # Fill `res` with the result of the evaluation according to the mapping
                for i in [0..msignature.arity[ do
-                       if i == vararg_rank then
-                               var ne = args[i]
-                               if ne isa AVarargExpr then
-                                       var e = self.expr(ne.n_expr)
-                                       if e == null then return null
-                                       res.add(e)
-                                       continue
-                               end
-                               var vararg = new Array[Instance]
-                               for j in [vararg_rank..vararg_rank+vararg_len] do
-                                       var e = self.expr(args[j])
-                                       if e == null then return null
-                                       vararg.add(e)
-                               end
-                               var elttype = msignature.mparameters[vararg_rank].mtype.anchor_to(self.mainmodule, recv.mtype.as(MClassType))
-                               res.add(self.array_instance(vararg, elttype))
-                       else
-                               var j = i
-                               if i > vararg_rank then j += vararg_len
-                               var e = self.expr(args[j])
-                               if e == null then return null
-                               res.add(e)
+                       var param = msignature.mparameters[i]
+                       var j = map.map.get_or_null(i)
+                       if j == null then
+                               continue
+                       end
+                       if param.is_vararg and map.vararg_decl > 0 then
+                               var vararg = exprs.sub(j, map.vararg_decl)
+                               var elttype = param.mtype.anchor_to(self.mainmodule, recv.mtype.as(MClassType))
+                               var arg = self.array_instance(vararg, elttype)
+                               res.add(arg)
+                               continue
                        end
+                       res.add exprs[j]
                end
                return res
        end
@@ -1740,7 +1746,7 @@ redef class ASendExpr
        do
                var recv = v.expr(self.n_expr)
                if recv == null then return null
-               var args = v.varargize(callsite.mpropdef, recv, self.raw_arguments)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
                if args == null then return null
 
                var res = v.callsite(callsite, args)
@@ -1753,7 +1759,7 @@ redef class ASendReassignFormExpr
        do
                var recv = v.expr(self.n_expr)
                if recv == null then return
-               var args = v.varargize(callsite.mpropdef, recv, self.raw_arguments)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
                if args == null then return
                var value = v.expr(self.n_value)
                if value == null then return
@@ -1777,29 +1783,35 @@ redef class ASuperExpr
 
                var callsite = self.callsite
                if callsite != null then
-                       var args = v.varargize(callsite.mpropdef, recv, self.n_args.n_exprs)
-                       if args == null then return null
-                       # Add additional arguments for the super init call
-                       if args.length == 1 then
+                       var args
+                       if self.n_args.n_exprs.is_empty then
+                               # Add automatic arguments for the super init call
+                               args = [recv]
                                for i in [0..callsite.msignature.arity[ do
                                        args.add(v.frame.arguments[i+1])
                                end
+                       else
+                               args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs)
+                               if args == null then return null
                        end
+
                        # Super init call
                        var res = v.callsite(callsite, args)
                        return res
                end
 
-               # standard call-next-method
+               # Standard call-next-method
                var mpropdef = self.mpropdef
                mpropdef = mpropdef.lookup_next_definition(v.mainmodule, recv.mtype)
 
-               var args = v.varargize(mpropdef, recv, self.n_args.n_exprs)
-               if args == null then return null
-
-               if args.length == 1 then
+               var args
+               if self.n_args.n_exprs.is_empty then
                        args = v.frame.arguments
+               else
+                       args = v.varargize(mpropdef, signaturemap, recv, self.n_args.n_exprs)
+                       if args == null then return null
                end
+
                var res = v.call(mpropdef, args)
                return res
        end
@@ -1814,7 +1826,7 @@ redef class ANewExpr
                var callsite = self.callsite
                if callsite == null then return recv
 
-               var args = v.varargize(callsite.mpropdef, recv, self.n_args.n_exprs)
+               var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs)
                if args == null then return null
                var res2 = v.callsite(callsite, args)
                if res2 != null then
@@ -1876,6 +1888,13 @@ redef class AIssetAttrExpr
        end
 end
 
+redef class AVarargExpr
+       redef fun expr(v)
+       do
+               return v.expr(self.n_expr)
+       end
+end
+
 redef class ADebugTypeExpr
        redef fun stmt(v)
        do
index 7ef06e0..8fe169d 100644 (file)
@@ -398,47 +398,58 @@ private class TypeVisitor
        # Visit the expressions of args and check their conformity with the corresponding type in signature
        # The point of this method is to handle varargs correctly
        # Note: The signature must be correctly adapted
-       fun check_signature(node: ANode, args: Array[AExpr], mproperty: MProperty, msignature: MSignature): Bool
+       fun check_signature(node: ANode, args: Array[AExpr], mproperty: MProperty, msignature: MSignature): nullable SignatureMap
        do
                var vararg_rank = msignature.vararg_rank
                if vararg_rank >= 0 then
                        if args.length < msignature.arity then
                                modelbuilder.error(node, "Error: expected at least {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
-                               return false
+                               return null
                        end
                else if args.length != msignature.arity then
                        modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
-                       return false
+                       return null
                end
 
                #debug("CALL {unsafe_type}.{msignature}")
 
+               # Associate each parameter to a position in the arguments
+               var map = new SignatureMap
+
                var vararg_decl = args.length - msignature.arity
+               var j = 0
                for i in [0..msignature.arity[ do
-                       var j = i
-                       if i == vararg_rank then continue # skip the vararg
-                       if i > vararg_rank then
-                               j = i + vararg_decl
+                       var param = msignature.mparameters[i]
+                       var arg = args[j]
+                       map.map[i] = j
+                       j += 1
+
+                       if i == vararg_rank then
+                               j += vararg_decl
+                               continue # skip the vararg
                        end
-                       var paramtype = msignature.mparameters[i].mtype
-                       self.visit_expr_subtype(args[j], paramtype)
+
+                       var paramtype = param.mtype
+                       self.visit_expr_subtype(arg, paramtype)
                end
                if vararg_rank >= 0 then
                        var paramtype = msignature.mparameters[vararg_rank].mtype
                        var first = args[vararg_rank]
                        if vararg_decl == 0 and first isa AVarargExpr then
                                var mclass = get_mclass(node, "Array")
-                               if mclass == null then return false # Forward error
+                               if mclass == null then return null # Forward error
                                var array_mtype = mclass.get_mtype([paramtype])
                                self.visit_expr_subtype(first.n_expr, array_mtype)
                                first.mtype  = first.n_expr.mtype
                        else
-                               for j in [vararg_rank..vararg_rank+vararg_decl] do
-                                       self.visit_expr_subtype(args[j], paramtype)
+                               map.vararg_decl = vararg_decl + 1
+                               for i in [vararg_rank..vararg_rank+vararg_decl] do
+                                       self.visit_expr_subtype(args[i], paramtype)
                                end
                        end
                end
-               return true
+
+               return map
        end
 
        fun error(node: ANode, message: String)
@@ -508,6 +519,20 @@ private class TypeVisitor
        end
 end
 
+# Mapping between parameters and arguments in a call.
+#
+# Parameters and arguments are not stored in the class but referenced by their position (starting from 0)
+#
+# The point of this class is to help engine and other things to map arguments in the AST to parameters of the model.
+class SignatureMap
+       # Associate a parameter to an argument
+       var map = new ArrayMap[Int, Int]
+
+       # The length of the vararg sequence
+       # 0 if no vararg or if reverse vararg (cf `AVarargExpr`)
+       var vararg_decl: Int = 0
+end
+
 # A specific method call site with its associated informations.
 class CallSite
        # The associated node for location
@@ -540,9 +565,15 @@ class CallSite
        # Is a implicit cast required on erasure typing policy?
        var erasure_cast: Bool
 
+       # The mapping used on the call to associate arguments to parameters
+       # If null then no specific association is required.
+       var signaturemap: nullable SignatureMap = null
+
        private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool
        do
-               return v.check_signature(self.node, args, self.mproperty, self.msignature)
+               var map = v.check_signature(self.node, args, self.mproperty, self.msignature)
+               signaturemap = map
+               return map == null
        end
 end
 
@@ -1737,7 +1768,7 @@ redef class ASuperExpr
                msignature = v.resolve_for(msignature, recvtype, true).as(MSignature)
                var args = self.n_args.to_a
                if args.length > 0 then
-                       v.check_signature(self, args, mproperty, msignature)
+                       signaturemap = v.check_signature(self, args, mproperty, msignature)
                end
                self.mtype = msignature.return_mtype
                self.is_typed = true
@@ -1745,6 +1776,10 @@ redef class ASuperExpr
                mpropdef = v.mpropdef.as(MMethodDef)
        end
 
+       # The mapping used on the call to associate arguments to parameters.
+       # If null then no specific association is required.
+       var signaturemap: nullable SignatureMap
+
        private fun process_superinit(v: TypeVisitor)
        do
                var anchor = v.anchor
index 5789941..43e3636 100644 (file)
@@ -1,4 +1,4 @@
-Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:911)
+Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:960)
 NativeString
 N
 Nit