Property definitions

nitc $ JavaCompilerVisitor :: defaultinit
# The class visiting the AST
#
# A visitor is attached to one JavaCodeFile it writes into.
class JavaCompilerVisitor
	super Visitor

	# JavaCompiler used with this visitor
	type COMPILER: JavaCompiler

	# The associated compiler
	var compiler: JavaCompiler

	# The file to write generated code into
	var file: JavaCodeFile

	# Names handling

	private var names = new HashSet[String]
	private var last: Int = 0

	# Return a new name based on `s` and unique in the visitor
	fun get_name(s: String): String do
		if not self.names.has(s) then
			self.names.add(s)
			return s
		end
		var i = self.last + 1
		loop
			var s2 = s + i.to_s
			if not self.names.has(s2) then
				self.last = i
				self.names.add(s2)
				return s2
			end
			i = i + 1
		end
	end

	# Return an unique and stable identifier associated with an escapemark
	fun escapemark_name(e: nullable EscapeMark): String do
		assert e != null
		var frame = self.frame
		assert frame != null
		if frame.escapemark_names.has_key(e) then return frame.escapemark_names[e]
		var name = e.name
		if name == null then name = "label"
		name = get_name(name)
		frame.escapemark_names[e] = name
		return name
	end

	# Insert a C label for associated with an escapemark
	fun add_escape_label(e: nullable EscapeMark) do
		if e == null then return
		if e.escapes.is_empty then return
		add("BREAK_{escapemark_name(e)}: ")
	end

	# Variables handling

	# Registered variables
	protected var variables = new HashMap[Variable, RuntimeVariable]

	# Return the local RuntimeVariable associated to a Nit local variable
	fun variable(variable: Variable): RuntimeVariable do
		if variables.has_key(variable) then
			return variables[variable]
		else
			var name = get_name("var_{variable.name}")
			var mtype = variable.declared_type.as(not null)
			mtype = anchor(mtype)
			var res = decl_var(name, mtype)
			variables[variable] = res
			return res
		end
	end

	# Return a new uninitialized local RuntimeVariable with `name`
	fun decl_var(name: String, mtype: MType): RuntimeVariable do
		var res = new RuntimeVariable(name, mtype, mtype)
		res.is_boxed = not mtype.is_java_primitive
		add("{mtype.java_type} {name} /* : {mtype} */;")
		return res
	end

	# Return a new uninitialized local RuntimeVariable
	fun new_var(mtype: MType): RuntimeVariable do
		mtype = anchor(mtype)
		var name = self.get_name("var")
		return decl_var(name, mtype)
	end

	# Calls handling

	# The current `JavaStaticFrame`
	var frame: nullable JavaStaticFrame = null is writable

	# Return a new local RuntimeVariable initialized from `args[0]`
	fun new_recv(mtype: MType): RuntimeVariable do
		var res = new_var(mtype)
		add("{res} = args[0];")
		return res
	end

	# Calls handling

	# Compile a call within a callsite
	fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
		var initializers = callsite.mpropdef.initializers
		if not initializers.is_empty then
			var recv = arguments.first

			var i = 1
			for p in initializers do
				if p isa MMethod then
					var args = [recv]
					var msignature = p.intro.msignature
					if msignature != null then
						for x in msignature.mparameters do
							args.add arguments[i]
							i += 1
						end
					end
					send(p, args)
				else if p isa MAttribute then
					info("NOT YET IMPLEMENTED {class_name}::compile_callsite for MAttribute `{p}`")
					#self.write_attribute(p, recv, arguments[i])
					i += 1
				else abort
			end
			assert i == arguments.length

			return send(callsite.mproperty, [recv])
		end

		return send(callsite.mproperty, arguments)
	end

	# 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, map: nullable SignatureMap, recv: RuntimeVariable, args: SequenceRead[AExpr]): Array[RuntimeVariable] do
		var msignature = mpropdef.msignature.as(not null)
		var res = new Array[RuntimeVariable]
		res.add(recv)

		if msignature.arity == 0 then return res

		if map == null then
			assert args.length == msignature.arity
			for ne in args do
				res.add 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 expr(ne, null)
		end

		# Fill `res` with the result of the evaluation according to the mapping
		for i in [0..msignature.arity[ do
			var param = msignature.mparameters[i]
			var j = map.map.get_or_null(i)
			if j == null then
				# default value
				res.add(null_instance)
				continue
			end
			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)
				continue
			end
			res.add exprs[j]
		end
		return res
	end

	#  Generate a static call on a method definition (no receiver needed).
	fun static_call(mmethoddef: MMethodDef, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
		var res: nullable RuntimeVariable
		var ret = mmethoddef.msignature.as(not null).return_mtype
		if ret == null then
			res = null
		else
			ret = ret.resolve_for(mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.bound_mtype, mmethoddef.mclassdef.mmodule, true)
			res = self.new_var(ret)
		end

		# Autobox arguments
		adapt_signature(mmethoddef, arguments)

		var rt_name = mmethoddef.rt_name
		if res == null then
			add("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});")
			return null
		end
		var ress = new_expr("{rt_name}.get{rt_name}().exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
		assign(res, ress)
		return res
	end

	#  Generate a polymorphic send for `method` with `arguments`
	fun send(mmethod: MMethod, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
		# Shortcut calls on primitives
		if arguments.first.mcasttype.is_java_primitive then
			return monomorphic_send(mmethod, arguments.first.mcasttype, arguments)
		end
		# Polymorphic send
		return table_send(mmethod, arguments)
	end


	# Handle common special cases before doing the effective method invocation
	# This methods handle the `==` and `!=` methods and the case of the null receiver.
	# Note: a { is open in the generated C, that enclose and protect the effective method invocation.
	# Client must not forget to close the } after them.
	#
	# The value returned is the result of the common special cases.
	# If not null, client must compile it with the result of their own effective method invocation.
	#
	# If `before_send` can shortcut the whole message sending, a dummy `if(0){`
	# is generated to cancel the effective method invocation that will follow
	# TODO: find a better approach
	private fun before_send(res: nullable RuntimeVariable, mmethod: MMethodDef, arguments: Array[RuntimeVariable]) do
		var bool_type = compiler.mainmodule.bool_type
		var recv = arguments.first
		var consider_null = mmethod.name == "==" or mmethod.name == "!=" or mmethod.name == "is_same_instance"
		if recv.mcasttype isa MNullableType or recv.mcasttype isa MNullType then
			add("if ({recv} == null || {recv}.is_null()) \{")
			if mmethod.name == "==" or mmethod.name == "is_same_instance" then
				if res == null then res = new_var(bool_type)
				var arg = arguments[1]
				if arg.mcasttype isa MNullableType then
					add("{res} = ({arg} == null || {arg}.is_null());")
				else if arg.mcasttype isa MNullType then
					add("{res} = true; /* is null */")
				else
					add("{res} = false; /* {arg.inspect} cannot be null */")
				end
			else if mmethod.name == "!=" then
				if res == null then res = new_var(bool_type)
				# res = self.new_var(bool_type)
				var arg = arguments[1]
				if arg.mcasttype isa MNullableType then
					add("{res} = ({arg} != null && !{arg}.is_null());")
				else if arg.mcasttype isa MNullType then
					add("{res} = false; /* is null */")
				else
					add("{res} = true; /* {arg.inspect} cannot be null */")
				end
			else
				add_abort("Receiver is null")
				ret(null_instance)
			end
			add("\} else \{")
		else
			add "\{"
			add "/* recv ({recv}) cannot be null since it's a {recv.mcasttype}"
		end
		if consider_null then
			var arg = arguments[1]
			if arg.mcasttype isa MNullType then
				if res == null then res = new_var(bool_type)
				if mmethod.name == "!=" then
					add("{res} = true; /* arg is null and recv is not */")
				else # `==` and `is_same_instance`
					add("{res} = false; /* arg is null but recv is not */")
				end
				add("\}") # closes the null case
				add("if (false) \{") # what follow is useless, Javac will drop it
			end
		end
	end

	# Perform a method call through vft
	private fun table_send(mmethod: TableCallable, arguments: Array[RuntimeVariable]): nullable RuntimeVariable do
		var mdef: MMethodDef
		var name: String
		if mmethod isa MMethod then
			mdef = mmethod.intro
			name = mmethod.full_name
		else if mmethod isa MMethodDef then
			mdef = mmethod
			name = mmethod.full_name
		else
			abort
		end

		var recv = arguments.first
		var rect = mdef.mclassdef.bound_mtype
		var msignature = mdef.msignature.as(not null)
		msignature = msignature.resolve_for(rect, rect, compiler.mainmodule, true)
		adapt_signature(mdef, arguments)

		var res: nullable RuntimeVariable
		var ret = msignature.return_mtype
		if ret == null then
			res = null
		else
			res = self.new_var(ret)
		end

		before_send(res, mdef, arguments)

		add "/* concrete call to {mdef} */"
		if res != null then
			var ress = new_expr("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});", compiler.mainmodule.object_type)
			assign(res, ress)
		else
			add("{recv}.rtclass.vft.get(\"{name}\").exec(new RTVal[]\{{arguments.join(",")}\});")
		end

		add("\}") # closes the null case

		return res
	end

	# Generate a super call from a method definition
	fun supercall(m: MMethodDef, recvtype: MClassType, args: Array[RuntimeVariable]): nullable RuntimeVariable do
		return table_send(m, args)
	end

	# Generate a monomorphic send for the method `m`, the type `t` and the arguments `args`
	fun monomorphic_send(m: MMethod, t: MType, args: Array[RuntimeVariable]): nullable RuntimeVariable do
		assert t isa MClassType
		var propdef = m.lookup_first_definition(self.compiler.mainmodule, t)
		return self.static_call(propdef, args)
	end

	# Code generation

	# Add a line (will be suffixed by `\n`)
	fun add(line: String) do file.lines.add("{line}\n")

	# Add a new partial line (no `\n` suffix)
	fun addn(line: String) do file.lines.add(line)

	# Compile a statement (if any)
	fun stmt(nexpr: nullable AExpr) do
		if nexpr == null then return
		if nexpr.mtype == null and not nexpr.is_typed then
			# Untyped expression.
			# Might mean dead code or invalid code
			# so aborts
			add_abort("FATAL: bad statement executed.")
			return
		end

		var old = self.current_node
		current_node = nexpr
		nexpr.stmt(self)
		current_node = old
	end

	# Compile an expression an return its result
	# `mtype` is the expected return type, pass null if no specific type is expected.
	fun expr(nexpr: AExpr, mtype: nullable MType): RuntimeVariable do
		var old = current_node
		current_node = nexpr

		var res = null
		if nexpr.mtype != null then
			res = nexpr.expr(self)
		end

		if res == null then
			# Untyped expression.
			# Might mean dead code or invalid code.
			# so aborts
			add_abort("FATAL: bad expression executed.")
			# and return a placebo result to please the C compiler
			if mtype == null then mtype = compiler.mainmodule.object_type
			res = null_instance

			self.current_node = old
			return res
		end

		if mtype != null then
			mtype = anchor(mtype)
			res = autobox(res, mtype)
		end

		current_node = old
		return res
	end

	# Alias for `self.expr(nexpr, self.bool_type)`
	fun expr_bool(nexpr: AExpr): RuntimeVariable do
		return expr(nexpr, compiler.mainmodule.bool_type)
	end

	# Correctly assign a left and a right value
	# Boxing and unboxing is performed if required
	fun assign(left, right: RuntimeVariable) do
		add("{left} = {autobox(right, left.mtype)};")
	end

	# Generate a return with `value`
	fun ret(value: RuntimeVariable) do
		var frame = self.frame
		assert frame != null
		var returnvar = frame.returnvar
		if returnvar != null then
			assign(returnvar, value)
		end
		self.add("break {frame.returnlabel.as(not null)};")
	end

	# Return a new local RuntimeVariable initialized with the Java expression `jexpr`.
	#
	# `mtype` is used for the Java return variable initialization.
	fun new_expr(jexpr: String, mtype: MType): RuntimeVariable do
		var res = new_var(mtype)
		add("{res} = {jexpr};")
		return res
	end

	# Generate generic abort
	#
	# Used by aborts, asserts, casts, etc.
	fun add_abort(message: String) do
		add("System.err.print(\"Runtime error: {message}\");")
		add_raw_abort
	end

	# Abort without displaying the cause.
	#
	# Used to customizable errors.
	private fun add_raw_abort do
		var node = current_node
		if node != null then
			add("System.err.print(\" ({node.location.short_location})\");")
		end
		add("System.err.println(\"\");")
		add("System.exit(1);")
	end

	# Add a dynamic cast
	fun add_cast(value: RuntimeVariable, mtype: MType) do
		var res = type_test(value, mtype)
		add("if (!{res}) \{")
		add("System.err.print(\"Runtime error: Cast failed. Expected `{mtype.to_s.escape_to_c}`, got `\" + {value}.rtclass.class_name + \"`\");")
		add_raw_abort
		add("\}")
	end

	# Types handling

	# Anchor a type to the main module and the current receiver
	fun anchor(mtype: MType): MType do
		if not mtype.need_anchor then return mtype
		return mtype.anchor_to(compiler.mainmodule, frame.as(not null).receiver)
	end

	# Adapt the arguments of a method according to targetted `MMethodDef`
	fun adapt_signature(m: MMethodDef, args: Array[RuntimeVariable]) do
		var msignature = m.msignature.as(not null).resolve_for(
			m.mclassdef.bound_mtype,
			m.mclassdef.bound_mtype,
			m.mclassdef.mmodule, true)
		args.first = autobox(args.first, compiler.mainmodule.object_type)
		for i in [0..msignature.arity[ do
			args[i+1] = autobox(args[i + 1], compiler.mainmodule.object_type)
		end
	end

	# Box primitive `value` to `mtype`.
	private fun box(value: RuntimeVariable, mtype: MType): RuntimeVariable do
		if value.is_boxed then return value
		var obj_type = compiler.mainmodule.object_type
		if value.mtype isa MNullType then
			return new_expr("new RTVal(null, null)", compiler.mainmodule.model.null_type)
		end
		var mbox = value.mtype.as(MClassType).mclass
		return new_expr("new RTVal({mbox.rt_name}.get{mbox.rt_name}(), {value})", obj_type)
	end

	# Unbox primitive `value` to `mtype`.
	private fun unbox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
		if not value.is_boxed then return value
		if not mtype.is_java_primitive then return value
		if compiler.box_kinds.has(mtype) then
			return new_expr("({mtype.java_type}){value}.value", mtype)
		else
			info "NOT YET IMPLEMENTED unbox for {value} ({mtype})"
			abort
		end
	end

	# Box or unbox primitive `value` to `mtype` if needed.
	private fun autobox(value: RuntimeVariable, mtype: MType): RuntimeVariable do
		if mtype.is_java_primitive then return unbox(value, mtype)
		return box(value, mtype)
	end

	# Can this `value` be a primitive Java value?
	private fun can_be_primitive(value: RuntimeVariable): Bool do
		var t = value.mcasttype.undecorate
		if not t isa MClassType then return false
		var k = t.mclass.kind
		return k == interface_kind or t.is_java_primitive
	end

	#  Generate a polymorphic subtype test
	fun type_test(value: RuntimeVariable, mtype: MType): RuntimeVariable do
		add("/* {value.inspect} isa {mtype} */")
		var res = self.new_var(compiler.mainmodule.bool_type)

		# check color is in table
		var maybenull = (value.mcasttype isa MNullableType or value.mcasttype isa MNullType)
		if maybenull then
			add("if({value} == null || {value}.is_null()) \{")
			add("{res} = true && {mtype isa MNullableType};")
			add("\} else \{")
		end
		if mtype isa MNullableType then mtype = mtype.mtype
		var mclass = mtype.as(MClassType).mclass
		add("{res} = {value}.rtclass.supers.get(\"{mclass.jname}\") == {mclass.rt_name}.get{mclass.rt_name}();")
		if maybenull then
			add("\}")
		end
		return res
	end

	# Generate the code required to dynamically check if 2 objects share the same runtime type
	fun is_same_type_test(value1, value2: RuntimeVariable): RuntimeVariable do
		var res = self.new_var(compiler.mainmodule.bool_type)
		add("{res} = {value1}.rtclass == {value2}.rtclass;")
		return res
	end

	# Native instances

	# Generate an integer value
	fun int_instance(value: Int): RuntimeVariable do
		var t = compiler.mainmodule.int_type
		return new RuntimeVariable(value.to_s, t, t)
	end

	# Generate a byte value
	fun byte_instance(value: Byte): RuntimeVariable do
		var t = compiler.mainmodule.byte_type
		return new RuntimeVariable(value.to_s, t, t)
	end

	# Generate a char value
	fun char_instance(value: Char): RuntimeVariable do
		var t = compiler.mainmodule.char_type
		return new RuntimeVariable("'{value.to_s.escape_to_c}'", t, t)
	end

	# Generate a float value
	#
	# FIXME pass a Float, not a string
	fun float_instance(value: String): RuntimeVariable do
		var t = compiler.mainmodule.float_type
		return new RuntimeVariable(value.to_s, t, t)
	end

	# Generate an integer value
	fun bool_instance(value: Bool): RuntimeVariable do
		var t = compiler.mainmodule.bool_type
		return new RuntimeVariable(value.to_s, t, t)
	end

	# Generate the `null` value
	fun null_instance: RuntimeVariable do
		var t = compiler.mainmodule.model.null_type
		return new RuntimeVariable("null", t, t)
	end

	# Get an instance of a array for a vararg
	fun vararg_instance(mpropdef: MPropDef, recv: RuntimeVariable, varargs: Array[RuntimeVariable], elttype: MType): RuntimeVariable do
		# TODO handle dynamic types
		info("NOT YET IMPLEMENTED vararg_instance")
		return null_instance
		# TODO return array_instance(varargs, elttype)
	end

	# Nit instances

	# Generate a alloc-instance + init-attributes
	fun init_instance(mtype: MClassType): RuntimeVariable do
		var rt_name = mtype.mclass.rt_name
		var res = new_expr("new RTVal({rt_name}.get{rt_name}())", mtype)
		generate_init_attr(self, res, mtype)
		return res
	end

	# Generate code that initialize the attributes on a new instance
	fun generate_init_attr(v: JavaCompilerVisitor, recv: RuntimeVariable, mtype: MClassType) do
		var cds = mtype.collect_mclassdefs(v.compiler.mainmodule).to_a
		v.compiler.mainmodule.linearize_mclassdefs(cds)
		for cd in cds do
			for npropdef in v.compiler.modelbuilder.collect_attr_propdef(cd) do
				npropdef.init_expr(v, recv)
			end
		end
	end

	#  Generate a Nit "is" for two runtime_variables
	fun equal_test(value1, value2: RuntimeVariable): RuntimeVariable do
		var res = new_var(compiler.mainmodule.bool_type)
		if value2.mtype.is_java_primitive and not value1.mtype.is_java_primitive then
			var tmp = value1
			value1 = value2
			value2 = tmp
		end
		if value1.mtype.is_java_primitive then
			if value2.mtype == value1.mtype then
				add("{res} = {value1} == {value2}; /* == with two primitives */")
			else if value2.mtype.is_java_primitive then
				add("{res} = true; /* incompatible types {value1.mtype} vs. {value2.mtype}*/")
			# else if value1.mtype.is_tagged then
				# add("{res} = ({value2} != NULL) && ({autobox(value2, value1.mtype)} == {value1});")
			else
				var rt_name = value1.mtype.as(MClassType).mclass.rt_name
				add("{res} = ({value2} != null) && ({value2}.rtclass == {rt_name}.get{rt_name}());")
				add("if ({res}) \{")
				add("{res} = ({self.autobox(value2, value1.mtype)} == {value1});")
				add("\}")
			end
			return res
		end
		var maybe_null = true
		var test = new Array[String]
		var t1 = value1.mcasttype
		if t1 isa MNullableType then
			test.add("{value1} != null && !{value1}.is_null()")
			t1 = t1.mtype
		else
			maybe_null = false
		end
		var t2 = value2.mcasttype
		if t2 isa MNullableType then
			test.add("{value2} != null && !{value2}.is_null()")
			t2 = t2.mtype
		else
			maybe_null = false
		end

		var incompatible = false
		var primitive
		if t1.is_java_primitive then
			primitive = t1
			if t1 == t2 then
				# No need to compare class
			else if t2.is_java_primitive then
				incompatible = true
			else if can_be_primitive(value2) then
				if t1.is_java_primitive then
					self.add("{res} = {value1} == {value2}; /* t1 is primitive and t2 can be */")
					return res
				end
				# if not compiler.modelbuilder.toolcontext.opt_no_tag_primitives.value then
					# test.add("(!{extract_tag(value2)})")
				# end
				test.add("{value1}.rtclass == {value2}.rtclass")
			else
				incompatible = true
			end
		else if t2.is_java_primitive then
			primitive = t2
			if can_be_primitive(value1) then
				if t2.is_java_primitive then
					self.add("{res} = {value1} == {value2}; /* t2 is primitive and t1 can be */")
					return res
				end
				test.add("{value1}.rtclass == {value2}.rtclass")
			else
				incompatible = true
			end
		else
			primitive = null
		end

		if incompatible then
			if maybe_null then
				self.add("{res} = {value1} == {value2}; /* incompatible types {t1} vs. {t2}; but may be NULL*/")
				return res
			else
				self.add("{res} = false; /* incompatible types {t1} vs. {t2}; cannot be NULL */")
				return res
			end
		end
		if primitive != null then
			if primitive.is_java_primitive then
				self.add("{res} = {value1} == {value2};")
				return res
			end
			test.add("({value1}.value == {value2}.value")
		else if can_be_primitive(value1) and can_be_primitive(value2) then
			test.add("{value1}.rtclass == {value2}.rtclass")
			var s = new Array[String]
			for b in compiler.box_kinds do
				var rt_name = b.mclass.rt_name
				s.add "({value1}.rtclass == {rt_name}.get{rt_name}()) && ({value1}.value.equals({value2}.value))"
				if b.mclass.name == "Float" then
					s.add "({value1}.rtclass == RTClass_kernel_Float.getRTClass_kernel_Float() && {value1}.rtclass == {value2}.rtclass && Math.abs((double)({value1}.value)) == 0.0 && Math.abs((double)({value2}.value)) == 0.0)"
				end
			end
			if s.is_empty then
				self.add("{res} = {value1} == {value2}; /* both can be primitive */")
				return res
			end
			test.add("({s.join(" || ")})")
		else
			self.add("{res} = {value1} == {value2}; /* no primitives */")
			return res
		end
		self.add("{res} = {value1} == {value2} || ({test.join(" && ")});")
		return res
	end

	# Attributes

	# Generate a polymorphic attribute is_set test
	fun isset_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do
		# TODO self.check_recv_notnull(recv)
		var res = new_var(compiler.mainmodule.bool_type)

		# What is the declared type of the attribute?
		var mtype = a.intro.static_mtype.as(not null)
		var intromclassdef = a.intro.mclassdef
		mtype = mtype.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true)

		if mtype isa MNullableType then
			add("{res} = true; /* easy isset: {a} on {recv.inspect} */")
			return res
		end
		add("{res} = {recv}.attrs.get(\"{a.jname}\") != null; /* {a} on {recv.inspect} */")
		return res
	end

	# Generate a polymorphic attribute read
	fun read_attribute(a: MAttribute, recv: RuntimeVariable): RuntimeVariable do
		# TODO check_recv_notnull(recv)
		# TODO compile_check(v)
		# What is the declared type of the attribute?
		var ret = a.intro.static_mtype.as(not null)
		var intromclassdef = a.intro.mclassdef
		ret = ret.resolve_for(intromclassdef.bound_mtype, intromclassdef.bound_mtype, intromclassdef.mmodule, true)

		# Check for Uninitialized attribute
		if not ret isa MNullableType then check_attribute(a, recv)

		return new_expr("{recv}.attrs.get(\"{a.jname}\")", ret)
	end

	# Generate a polymorphic attribute write
	fun write_attribute(a: MAttribute, recv: RuntimeVariable, value: RuntimeVariable) do
		# TODO check_recv_notnull(recv)
		add "{recv}.attrs.put(\"{a.jname}\", {autobox(value, compiler.mainmodule.object_type)});"
	end

	# Check uninitialized attribute
	fun check_attribute(a: MAttribute, recv: RuntimeVariable) do
		add "if({recv}.attrs.get(\"{a.jname}\") == null) \{"
		add_abort "Uninitialized attribute {a.name}"
		add "\}"
	end

	# Utils

	# Display a info message
	fun info(str: String) do compiler.modelbuilder.toolcontext.info(str, 0)
end
src/compiler/java_compiler.nit:357,1--1131,3