Merge: New `optional` annotation on attributes
authorJean Privat <jean@pryen.org>
Thu, 3 Mar 2016 07:35:09 +0000 (02:35 -0500)
committerJean Privat <jean@pryen.org>
Thu, 3 Mar 2016 07:35:09 +0000 (02:35 -0500)
As requested at #1843 by @R4PaSs, a new annotation `optional` is now available on attributes.

~~~nit
class A
    var x: Int = 99 is optional
   # Because of `optional`, the automatic signature of the A constructor is `init(x: nullable Int)`
end

var a = new A
print a.x # outputs 99
var b = new A(4)
print b.x # output 4
~~~

In the model, the `optional` annotation only affects the signature and the behavior of the setter.
It transforms the argument to a `nullable` one and use the provided value if `null` is given as a parameter.

The `nullable` parameter is then propagated to the automatic constructors in the usual way.

Basically, the previous example is equivalent to having written:

~~~
class A
    var x: Int is noautoinit
    fun x=(x: nullable Int) is autoinit do if x != null then self.x = x else self.x = 99
end
~~~

Pull-Request: #1965
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>

src/compiler/abstract_compiler.nit
src/frontend/check_annotation.nit
src/interpreter/naive_interpreter.nit
src/modelize/modelize_property.nit
tests/base_attr_optional.nit [new file with mode: 0644]
tests/sav/base_attr_optional.res [new file with mode: 0644]

index 347aa11..a39e59b 100644 (file)
@@ -3094,7 +3094,18 @@ redef class AAttrPropdef
                        v.assign(v.frame.returnvar.as(not null), res)
                else if mpropdef == mwritepropdef then
                        assert arguments.length == 2
-                       v.write_attribute(self.mpropdef.mproperty, arguments.first, arguments[1])
+                       var recv = arguments.first
+                       var arg = arguments[1]
+                       if is_optional then
+                               var value = v.new_var(self.mpropdef.static_mtype.as(not null))
+                               v.add("if ({arg} == NULL) \{")
+                               v.assign(value, evaluate_expr(v, recv))
+                               v.add("\} else \{")
+                               v.assign(value, arg)
+                               v.add("\}")
+                               arg = value
+                       end
+                       v.write_attribute(self.mpropdef.mproperty, arguments.first, arg)
                        if is_lazy then
                                var ret = self.mtype
                                var useiset = not ret.is_c_primitive and not ret isa MNullableType
index d640ac2..5adcabd 100644 (file)
@@ -84,6 +84,7 @@ lazy
 noinit
 readonly
 writable
+optional
 autoinit
 noautoinit
 lateinit
index bbe07d1..23d3ba1 100644 (file)
@@ -1497,7 +1497,12 @@ redef class AAttrPropdef
                        return evaluate_expr(v, recv, f)
                else if mpropdef == mwritepropdef then
                        assert args.length == 2
-                       v.write_attribute(attr, recv, args[1])
+                       var arg = args[1]
+                       if is_optional and arg.mtype isa MNullType then
+                               var f = v.new_frame(self, mpropdef, args)
+                               arg = evaluate_expr(v, recv, f)
+                       end
+                       v.write_attribute(attr, recv, arg)
                        return null
                else
                        abort
index 03d9951..6588268 100644 (file)
@@ -203,19 +203,21 @@ redef class ModelBuilder
                                        mreadpropdef.mproperty.is_autoinit = true
                                        continue
                                end
-                               if npropdef.has_value then continue
-                               var paramname = mreadpropdef.mproperty.name
-                               var ret_type = msignature.return_mtype
-                               if ret_type == null then return
-                               var mparameter = new MParameter(paramname, ret_type, false)
-                               mparameters.add(mparameter)
+                               if npropdef.has_value and not npropdef.is_optional then continue
                                var msetter = npropdef.mwritepropdef
                                if msetter == null then
                                        # No setter, it is a readonly attribute, so just add it
+                                       var paramname = mreadpropdef.mproperty.name
+                                       var ret_type = msignature.return_mtype
+                                       if ret_type == null then return
+                                       var mparameter = new MParameter(paramname, ret_type, false)
+                                       mparameters.add(mparameter)
+
                                        initializers.add(npropdef.mpropdef.mproperty)
                                        npropdef.mpropdef.mproperty.is_autoinit = true
                                else
                                        # Add the setter to the list
+                                       mparameters.add_all msetter.msignature.mparameters
                                        initializers.add(msetter.mproperty)
                                        msetter.mproperty.is_autoinit = true
                                end
@@ -1154,6 +1156,9 @@ redef class AAttrPropdef
        # Is the node tagged lazy?
        var is_lazy = false
 
+       # Is the node tagged optional?
+       var is_optional = false
+
        # Has the node a default value?
        # Could be through `n_expr` or `n_block`
        var has_value = false
@@ -1250,6 +1255,14 @@ redef class AAttrPropdef
                        self.mlazypropdef = mlazypropdef
                end
 
+               var atoptional = self.get_single_annotation("optional", modelbuilder)
+               if atoptional != null then
+                       if not has_value then
+                               modelbuilder.error(atoptional, "Error: `optional` attributes need a default value.")
+                       end
+                       is_optional = true
+               end
+
                var atreadonly = self.get_single_annotation("readonly", modelbuilder)
                if atreadonly != null then
                        if not has_value then
@@ -1421,9 +1434,13 @@ redef class AAttrPropdef
 
                var mwritepropdef = self.mwritepropdef
                if mwritepropdef != null then
+                       var mwritetype = mtype
+                       if is_optional then
+                               mwritetype = mwritetype.as_nullable
+                       end
                        var name: String
                        name = n_id2.text
-                       var mparameter = new MParameter(name, mtype, false)
+                       var mparameter = new MParameter(name, mwritetype, false)
                        var msignature = new MSignature([mparameter], null)
                        mwritepropdef.msignature = msignature
                end
diff --git a/tests/base_attr_optional.nit b/tests/base_attr_optional.nit
new file mode 100644 (file)
index 0000000..4417afe
--- /dev/null
@@ -0,0 +1,43 @@
+# 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 kernel
+
+class A
+       var i: Int = 99 is optional
+
+       var o: Object = 999 is optional
+end
+
+var a = new A
+a.i.output
+a.o.output
+
+a.i = 1
+a.o = 10
+a.i.output
+a.o.output
+
+a.i = null
+a.o = null
+a.i.output
+a.o.output
+
+a = new A(2)
+a.i.output
+a.o.output
+
+a = new A(3, true)
+a.i.output
+a.o.output
diff --git a/tests/sav/base_attr_optional.res b/tests/sav/base_attr_optional.res
new file mode 100644 (file)
index 0000000..c879e94
--- /dev/null
@@ -0,0 +1,10 @@
+99
+999
+1
+10
+99
+999
+2
+999
+3
+true