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>
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
# 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
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
mreadpropdef.msignature = msignature
end
- var msritepropdef = self.mwritepropdef
+ var mwritepropdef = self.mwritepropdef
if mwritepropdef != null then
var name: String
if n_id != null then
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)
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
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)
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
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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100
--- /dev/null
+alt/base_attr_lazy_alt1.nit:28,20--23: Error: a lazy attribute needs a value
--- /dev/null
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100
--- /dev/null
+1
+10
+10
+2
+20
+20
+
+2
+20
+1
+10
+20
+10
+
+100
+100