Skip to content

Commit

Permalink
Merge: Handle gracefuly multi-varargs
Browse files Browse the repository at this point in the history
This solve a very borderline issue when a signature contains more than one vararg parameter.
They are still refused in the common case but can occurs when multiple initializers are combined into a single constructor.

The implemented semantic is the following:

* vararg parameters remains varag
* if more than one vararg parameter exists in a signature, then the signature does not accepts additional arguments, it means that each vararg is associated to a single argument (a discarded alternative was to only keep the first/last parameter as the main vararg one)
* the associated argument can be either a single value, of a reverse vararg with an ellipsis.

~~~nit
class A
   fun x(i: Int...) is autoinit do end
   fun y(j: Int...) is autoinit do end
end
var a
a = new A(10, 20) # OK: i=[10], j=[20]
a = new A(10, 11, 20) # Refused
a = new A([10, 11]..., 20) # OK: i=[10, 11], j=[20]
a = new A([10, 11]..., [20, 21, 22]...) # OK: i=[10, 11], j=[20, 21, 22]
a = new A([10, 11], [20, 21, 22]) # Refused but a hint is given that `...` may be missing
~~~

Pull-Request: #1825
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
  • Loading branch information
privat committed Nov 12, 2015
2 parents b12bfdd + 872b872 commit a3c4be9
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 39 deletions.
4 changes: 2 additions & 2 deletions src/compiler/abstract_compiler.nit
Expand Up @@ -1224,8 +1224,8 @@ abstract class AbstractCompilerVisitor
res.add(null_instance)
continue
end
if param.is_vararg and map.vararg_decl > 0 then
var vararg = exprs.sub(j, map.vararg_decl)
if param.is_vararg and args[i].vararg_decl > 0 then
var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype
var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
res.add(arg)
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/java_compiler.nit
Expand Up @@ -526,8 +526,8 @@ class JavaCompilerVisitor
res.add(null_instance)
continue
end
if param.is_vararg and map.vararg_decl > 0 then
var vararg = exprs.sub(j, map.vararg_decl)
if param.is_vararg and args[i].vararg_decl > 0 then
var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype
var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
res.add(arg)
Expand Down
4 changes: 2 additions & 2 deletions src/interpreter/naive_interpreter.nit
Expand Up @@ -471,8 +471,8 @@ class NaiveInterpreter
res.add(null_instance)
continue
end
if param.is_vararg and map.vararg_decl > 0 then
var vararg = exprs.sub(j, map.vararg_decl)
if param.is_vararg and args[i].vararg_decl > 0 then
var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype.anchor_to(self.mainmodule, recv.mtype.as(MClassType))
var arg = self.array_instance(vararg, elttype)
res.add(arg)
Expand Down
14 changes: 12 additions & 2 deletions src/model/model.nit
Expand Up @@ -1818,16 +1818,26 @@ class MSignature
for i in [0..mparameters.length[ do
var parameter = mparameters[i]
if parameter.is_vararg then
assert vararg_rank == -1
if vararg_rank >= 0 then
# If there is more than one vararg,
# consider that additional arguments cannot be mapped.
vararg_rank = -1
break
end
vararg_rank = i
end
end
self.vararg_rank = vararg_rank
end

# The rank of the ellipsis (`...`) for vararg (starting from 0).
# The rank of the main ellipsis (`...`) for vararg (starting from 0).
# value is -1 if there is no vararg.
# Example: for "(a: Int, b: Bool..., c: Char)" #-> vararg_rank=1
#
# From a model POV, a signature can contain more than one vararg parameter,
# the `vararg_rank` just indicates the one that will receive the additional arguments.
# However, currently, if there is more that one vararg parameter, no one will be the main one,
# and additional arguments will be refused.
var vararg_rank: Int is noinit

# The number of parameters
Expand Down
6 changes: 1 addition & 5 deletions src/modelize/modelize_property.nit
Expand Up @@ -186,11 +186,7 @@ redef class ModelBuilder
var sig = mpropdef.msignature
if sig == null then continue # Skip broken method

for param in sig.mparameters do
var ret_type = param.mtype
var mparameter = new MParameter(param.name, ret_type, false)
mparameters.add(mparameter)
end
mparameters.add_all sig.mparameters
initializers.add(mpropdef.mproperty)
mpropdef.mproperty.is_autoinit = true
end
Expand Down
70 changes: 44 additions & 26 deletions src/semantize/typing.nit
Expand Up @@ -490,8 +490,12 @@ private class TypeVisitor
continue # skip the vararg
end

var paramtype = param.mtype
self.visit_expr_subtype(arg, paramtype)
if not param.is_vararg then
var paramtype = param.mtype
self.visit_expr_subtype(arg, paramtype)
else
check_one_vararg(arg, param)
end
end

if min_arity > 0 then
Expand All @@ -509,27 +513,9 @@ private class TypeVisitor
var paramtype = msignature.mparameters[vararg_rank].mtype
var first = args[vararg_rank]
if vararg_decl == 0 then
var mclass = get_mclass(node, "Array")
if mclass == null then return null # Forward error
var array_mtype = mclass.get_mtype([paramtype])
if first isa AVarargExpr then
self.visit_expr_subtype(first.n_expr, array_mtype)
first.mtype = first.n_expr.mtype
else
# only one vararg, maybe `...` was forgot, so be gentle!
var t = visit_expr(first)
if t == null then return null # Forward error
if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
# Not acceptable but could be a `...`
error(first, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
return null
end
# Standard valid vararg, finish the job
map.vararg_decl = 1
self.visit_expr_subtype(first, paramtype)
end
if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null
else
map.vararg_decl = vararg_decl + 1
first.vararg_decl = vararg_decl + 1
for i in [vararg_rank..vararg_rank+vararg_decl] do
self.visit_expr_subtype(args[i], paramtype)
end
Expand All @@ -539,6 +525,33 @@ private class TypeVisitor
return map
end

# Check an expression as a single vararg.
# The main point of the method if to handle the case of reversed vararg (see `AVarargExpr`)
fun check_one_vararg(arg: AExpr, param: MParameter): Bool
do
var paramtype = param.mtype
var mclass = get_mclass(arg, "Array")
if mclass == null then return false # Forward error
var array_mtype = mclass.get_mtype([paramtype])
if arg isa AVarargExpr then
self.visit_expr_subtype(arg.n_expr, array_mtype)
arg.mtype = arg.n_expr.mtype
else
# only one vararg, maybe `...` was forgot, so be gentle!
var t = visit_expr(arg)
if t == null then return false # Forward error
if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
# Not acceptable but could be a `...`
error(arg, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
return false
end
# Standard valid vararg, finish the job
arg.vararg_decl = 1
self.visit_expr_subtype(arg, paramtype)
end
return true
end

fun error(node: ANode, message: String)
do
self.modelbuilder.error(node, message)
Expand Down Expand Up @@ -614,10 +627,6 @@ end
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.
Expand Down Expand Up @@ -853,6 +862,15 @@ redef class AExpr
# The result of the evaluation of `self` must be
# stored inside the designated array (there is an implicit `push`)
var comprehension: nullable AArrayExpr = null

# It indicates the number of arguments collected as a vararg.
#
# When 0, the argument is used as is, without transformation.
# When 1, the argument is transformed into an singleton array.
# Above 1, the arguments and the next ones are transformed into a common array.
#
# This attribute is meaning less on expressions not used as attributes.
var vararg_decl: Int = 0
end

redef class ABlockExpr
Expand Down
51 changes: 51 additions & 0 deletions tests/base_vararg_mult.nit
@@ -0,0 +1,51 @@
# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import array

class A
fun x(ints: Int...) is autoinit do
for i in ints do
'X'.output
i.output
end
'\n'.output
end
end

class B
super A
fun y(objs: Object...) is autoinit do
for i in objs do
'Y'.output
i.output
end
'\n'.output
end
end

var x

#alt1#x = new A
x = new A(1)
x = new A(10, 20)
x = new A([100, 200, 300]...)

#aly1#x = new B(1)
x = new B(1, 2)
#alt1#x = new B(1, 2, 3)
#alt1#x = new B([10, 20], 33)
x = new B([10, 11]..., 20)
x = new B(10, [20, 21]...)
x = new B([10, 11]..., [20, 21, 23]...)
30 changes: 30 additions & 0 deletions tests/sav/base_vararg_mult.res
@@ -0,0 +1,30 @@
X1

X10
X20

X100
X200
X300

X1

Y2

X10
X11

Y20

X10

Y20
Y21

X10
X11

Y20
Y21
Y23

3 changes: 3 additions & 0 deletions tests/sav/base_vararg_mult_alt1.res
@@ -0,0 +1,3 @@
alt/base_vararg_mult_alt1.nit:40,5--7: Error: expected at least 1 argument(s) for `init(ints: Int...)`; got 0. See introduction at `core::Object::init`.
alt/base_vararg_mult_alt1.nit:47,5--7: Error: expected 2 argument(s) for `init(ints: Int..., objs: Object...)`; got 3. See introduction at `core::Object::init`.
alt/base_vararg_mult_alt1.nit:48,11--18: Type Error: expected `Int`, got `Array[Int]`. Is an ellipsis `...` missing on the argument?

0 comments on commit a3c4be9

Please sign in to comment.