Merge: Add annotation lazy on attributes
authorJean Privat <jean@pryen.org>
Tue, 22 Jul 2014 17:26:27 +0000 (13:26 -0400)
committerJean Privat <jean@pryen.org>
Tue, 22 Jul 2014 17:26:27 +0000 (13:26 -0400)
This annotation is basically a better `cached`.

~~~
class A
   var foo: Foo = some_complex_computation is lazy
end
var a = new A # no complex computation done yet
print a.foo # complex computation is done here
~~~

Unlike cached, the attribute is visible, and if a setter is available, the lazy can be sortcut. Thus lazy is behaving like a default value.

~~~
var b = new A
b.foo = new Foo # set before get,
print b.foo # thus complex computation is never done!
~~~

Note that with `readonly` #604 the setter can be unavailable, thus having lazy the only way to initialyze attributes.

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

src/abstract_compiler.nit
src/modelize_property.nit
src/naive_interpreter.nit
tests/base_attr_lazy.nit [new file with mode: 0644]
tests/base_attr_lazy_int.nit [new file with mode: 0644]
tests/base_attr_lazy_nullable.nit [new file with mode: 0644]
tests/sav/base_attr_lazy.res [new file with mode: 0644]
tests/sav/base_attr_lazy_alt1.res [new file with mode: 0644]
tests/sav/base_attr_lazy_int.res [new file with mode: 0644]
tests/sav/base_attr_lazy_nullable.res [new file with mode: 0644]

index 8015a5a..fb376ac 100644 (file)
@@ -2121,18 +2121,55 @@ end
 redef class AAttrPropdef
        redef fun compile_to_c(v, mpropdef, arguments)
        do
-               if arguments.length == 1 then
-                       var res = v.read_attribute(self.mpropdef.mproperty, arguments.first)
+               if mpropdef == mreadpropdef then
+                       assert arguments.length == 1
+                       var res
+                       if is_lazy then
+                               var nexpr = n_expr
+                               assert nexpr != null
+                               var set
+                               var ret = self.mpropdef.static_mtype
+                               var useiset = ret.ctype == "val*" and not ret isa MNullableType
+                               var guard = self.mlazypropdef.mproperty
+                               if useiset then
+                                       set = v.isset_attribute(self.mpropdef.mproperty, arguments.first)
+                               else
+                                       set = v.read_attribute(guard, arguments.first)
+                               end
+                               v.add("if(likely({set})) \{")
+                               res = v.read_attribute(self.mpropdef.mproperty, arguments.first)
+                               v.add("\} else \{")
+                               var value = v.expr(nexpr, self.mpropdef.static_mtype)
+                               v.write_attribute(self.mpropdef.mproperty, arguments.first, value)
+                               v.assign(res, value)
+                               if not useiset then
+                                       var true_v = v.new_expr("1", v.bool_type)
+                                       v.write_attribute(guard, arguments.first, true_v)
+                               end
+                               v.add("\}")
+                       else
+                               res = v.read_attribute(self.mpropdef.mproperty, arguments.first)
+                       end
                        v.assign(v.frame.returnvar.as(not null), res)
-               else
+               else if mpropdef == mwritepropdef then
+                       assert arguments.length == 2
                        v.write_attribute(self.mpropdef.mproperty, arguments.first, arguments[1])
+                       if is_lazy then
+                               var ret = self.mpropdef.static_mtype
+                               var useiset = ret.ctype == "val*" and not ret isa MNullableType
+                               if not useiset then
+                                       v.write_attribute(self.mlazypropdef.mproperty, arguments.first, v.new_expr("1", v.bool_type))
+                               end
+                       end
+               else
+                       abort
                end
        end
 
        fun init_expr(v: AbstractCompilerVisitor, recv: RuntimeVariable)
        do
                var nexpr = self.n_expr
-               if nexpr != null then
+               if nexpr != null and not is_lazy then
                        var oldnode = v.current_node
                        v.current_node = self
                        var old_frame = v.frame
index 96ec598..fb21a65 100644 (file)
@@ -645,10 +645,20 @@ redef class AAttrPropdef
        # Is the node tagged `noinit`?
        var noinit = false
 
+       # Is the node taggeg lazy?
+       var is_lazy = false
+
+       # The guard associated to a lasy attribute.
+       # Because some engines does not have a working `isset`,
+       # this additionnal attribute is used to guard the lazy initialization.
+       # TODO: to remove once isset is correctly implemented
+       var mlazypropdef: nullable MAttributeDef
+
        # The associated getter (read accessor) if any
        var mreadpropdef: nullable MMethodDef writable
        # The associated setter (write accessor) if any
        var mwritepropdef: nullable MMethodDef writable
+
        redef fun build_property(modelbuilder, mclassdef)
        do
                var mclass = mclassdef.mclass
@@ -718,6 +728,17 @@ redef class AAttrPropdef
                        modelbuilder.mpropdef2npropdef[mreadpropdef] = self
                        mreadpropdef.mdoc = mpropdef.mdoc
 
+                       var atlazy = self.get_single_annotation("lazy", modelbuilder)
+                       if atlazy != null then
+                               if n_expr == null then
+                                       modelbuilder.error(atlazy, "Error: a lazy attribute needs a value")
+                               end
+                               is_lazy = true
+                               var mlazyprop = new MAttribute(mclassdef, "lazy _" + name, none_visibility)
+                               var mlazypropdef = new MAttributeDef(mclassdef, mlazyprop, self.location)
+                               self.mlazypropdef = mlazypropdef
+                       end
+
                        var atreadonly = self.get_single_annotation("readonly", modelbuilder)
                        if atreadonly != null then
                                if n_expr == null then
@@ -840,7 +861,7 @@ redef class AAttrPropdef
                        mreadpropdef.msignature = msignature
                end
 
-               var msritepropdef = self.mwritepropdef
+               var mwritepropdef = self.mwritepropdef
                if mwritepropdef != null then
                        var name: String
                        if n_id != null then
@@ -852,6 +873,11 @@ redef class AAttrPropdef
                        var msignature = new MSignature([mparameter], null)
                        mwritepropdef.msignature = msignature
                end
+
+               var mlazypropdef = self.mlazypropdef
+               if mlazypropdef != null then
+                       mlazypropdef.static_mtype = modelbuilder.model.get_mclasses_by_name("Bool").first.mclass_type
+               end
        end
 
        redef fun check_signature(modelbuilder)
index ba2fb39..138c7e2 100644 (file)
@@ -441,6 +441,13 @@ private class NaiveInterpreter
                recv.attributes[mproperty] = value
        end
 
+       # Is the attribute `mproperty` initialized the instance `recv`?
+       fun isset_attribute(mproperty: MAttribute, recv: Instance): Bool
+       do
+               assert recv isa MutableInstance
+               return recv.attributes.has_key(mproperty)
+       end
+
        # Collect attributes of a type in the order of their init
        fun collect_attr_propdef(mtype: MType): Array[AAttrPropdef]
        do
@@ -982,28 +989,26 @@ redef class AAttrPropdef
                var recv = args.first
                assert recv isa MutableInstance
                var attr = self.mpropdef.mproperty
-               if args.length == 1 then
-                       return v.read_attribute(attr, recv)
-               else
+               if mpropdef == mreadpropdef then
+                       assert args.length == 1
+                       if not is_lazy or v.isset_attribute(attr, recv) then return v.read_attribute(attr, recv)
+                       return evaluate_expr(v, recv)
+               else if mpropdef == mwritepropdef then
                        assert args.length == 2
                        v.write_attribute(attr, recv, args[1])
                        return null
+               else
+                       abort
                end
        end
 
        # Evaluate and set the default value of the attribute in `recv`
        private fun init_expr(v: NaiveInterpreter, recv: Instance)
        do
-               assert recv isa MutableInstance
+               if is_lazy then return
                var nexpr = self.n_expr
                if nexpr != null then
-                       var f = new Frame(self, self.mpropdef.as(not null), [recv])
-                       v.frames.unshift(f)
-                       var val = v.expr(nexpr)
-                       assert val != null
-                       v.frames.shift
-                       assert not v.is_escaping
-                       v.write_attribute(self.mpropdef.mproperty, recv, val)
+                       evaluate_expr(v, recv)
                        return
                end
                var mtype = self.mpropdef.static_mtype.as(not null)
@@ -1012,6 +1017,21 @@ redef class AAttrPropdef
                        v.write_attribute(self.mpropdef.mproperty, recv, v.null_instance)
                end
        end
+
+       private fun evaluate_expr(v: NaiveInterpreter, recv: Instance): Instance
+       do
+               assert recv isa MutableInstance
+               var nexpr = self.n_expr
+               assert nexpr != null
+               var f = new Frame(self, self.mpropdef.as(not null), [recv])
+               v.frames.unshift(f)
+               var val = v.expr(nexpr)
+               assert val != null
+               v.frames.shift
+               assert not v.is_escaping
+               v.write_attribute(self.mpropdef.mproperty, recv, val)
+               return val
+       end
 end
 
 redef class AClassdef
@@ -1669,8 +1689,7 @@ redef class AIssetAttrExpr
                if recv == null then return null
                if recv.mtype isa MNullType then fatal(v, "Receiver is null")
                var mproperty = self.mproperty.as(not null)
-               assert recv isa MutableInstance
-               return v.bool_instance(recv.attributes.has_key(mproperty))
+               return v.bool_instance(v.isset_attribute(mproperty, recv))
        end
 end
 
diff --git a/tests/base_attr_lazy.nit b/tests/base_attr_lazy.nit
new file mode 100644 (file)
index 0000000..d271411
--- /dev/null
@@ -0,0 +1,48 @@
+# 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 Foo
+       var a1: Object = fa1 is lazy
+       var a2: Object = fa2 is lazy
+       fun fa1: Object do
+               1.output
+               return 10
+       end
+       fun fa2: Object do
+               2.output
+               return 20
+       end
+       #alt1#var a3: Object is lazy
+end
+
+var f = new Foo
+f.a1.output
+f.a1.output
+f.a2.output
+f.a2.output
+'\n'.output
+
+var g = new Foo
+g.a2.output
+g.a1.output
+g.a2.output
+g.a1.output
+'\n'.output
+
+var h = new Foo
+h.a1 = 100
+h.a1.output
+h.a1.output
diff --git a/tests/base_attr_lazy_int.nit b/tests/base_attr_lazy_int.nit
new file mode 100644 (file)
index 0000000..5197740
--- /dev/null
@@ -0,0 +1,47 @@
+# 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 Foo
+       var a1: Int = fa1 is lazy
+       var a2: Int = fa2 is lazy
+       fun fa1: Int do
+               1.output
+               return 10
+       end
+       fun fa2: Int do
+               2.output
+               return 20
+       end
+end
+
+var f = new Foo
+f.a1.output
+f.a1.output
+f.a2.output
+f.a2.output
+'\n'.output
+
+var g = new Foo
+g.a2.output
+g.a1.output
+g.a2.output
+g.a1.output
+'\n'.output
+
+var h = new Foo
+h.a1 = 100
+h.a1.output
+h.a1.output
diff --git a/tests/base_attr_lazy_nullable.nit b/tests/base_attr_lazy_nullable.nit
new file mode 100644 (file)
index 0000000..ba3b404
--- /dev/null
@@ -0,0 +1,57 @@
+# 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 Foo
+       var a1: nullable Object = fa1 is lazy
+       var a2: nullable Object = fa2 is lazy
+       fun fa1: nullable Object do
+               1.output
+               return 10
+       end
+       fun fa2: nullable Object do
+               2.output
+               return 20
+       end
+end
+
+fun o(o: nullable Object)
+do
+       if o == null then
+               'n'.output
+               '\n'.output
+       else
+               o.output
+       end
+end
+
+var f = new Foo
+o f.a1
+o f.a1
+o f.a2
+o f.a2
+'\n'.output
+
+var g = new Foo
+o g.a2
+o g.a1
+o g.a2
+o g.a1
+'\n'.output
+
+var h = new Foo
+h.a1 = 100
+h.a1.output
+h.a1.output
diff --git a/tests/sav/base_attr_lazy.res b/tests/sav/base_attr_lazy.res
new file mode 100644 (file)
index 0000000..540f9b8
--- /dev/null
@@ -0,0 +1,16 @@
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100
diff --git a/tests/sav/base_attr_lazy_alt1.res b/tests/sav/base_attr_lazy_alt1.res
new file mode 100644 (file)
index 0000000..d628b21
--- /dev/null
@@ -0,0 +1 @@
+alt/base_attr_lazy_alt1.nit:28,20--23: Error: a lazy attribute needs a value
diff --git a/tests/sav/base_attr_lazy_int.res b/tests/sav/base_attr_lazy_int.res
new file mode 100644 (file)
index 0000000..540f9b8
--- /dev/null
@@ -0,0 +1,16 @@
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100
diff --git a/tests/sav/base_attr_lazy_nullable.res b/tests/sav/base_attr_lazy_nullable.res
new file mode 100644 (file)
index 0000000..540f9b8
--- /dev/null
@@ -0,0 +1,16 @@
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100