# The static type of the receiver
# Mainly used for type tests and type resolutions
- var anchor: nullable MClassType
+ var anchor: nullable MClassType = null
# The analyzed mclassdef
- var mclassdef: nullable MClassDef
+ var mclassdef: nullable MClassDef = null
# The analyzed property
var mpropdef: nullable MPropDef
- var selfvariable: Variable = new Variable("self")
+ var selfvariable = new Variable("self")
# Is `self` use restricted?
# * no explicit `self`
# * method called on the implicit self must be top-level
var is_toplevel_context = false
- init(modelbuilder: ModelBuilder, mmodule: MModule, mpropdef: nullable MPropDef)
+ init
do
- self.modelbuilder = modelbuilder
- self.mmodule = mmodule
+ var mpropdef = self.mpropdef
if mpropdef != null then
self.mpropdef = mpropdef
selfvariable.declared_type = mclass.mclass_type
var mprop = mpropdef.mproperty
- if mprop isa MMethod and mprop.is_toplevel then
+ if mprop isa MMethod and (mprop.is_toplevel or mprop.is_new) then
is_toplevel_context = true
end
end
end
# Check that `sub` is a subtype of `sup`.
- # If `sub` is not a valud suptype, then display an error on `node` an return null.
+ # If `sub` is not a valid suptype, then display an error on `node` an return null.
# If `sub` is a safe subtype of `sup` then return `sub`.
- # If `sub` is an insafe subtype (ie an imlicit cast is required), then return `sup`.
+ # If `sub` is an unsafe subtype (ie an implicit cast is required), then return `sup`.
#
# The point of the return type is to determinate the usable type on an expression:
# If the suptype is safe, then the return type is the one on the expression typed by `sub`.
do
if self.is_subtype(sub, sup) then return sub
if self.is_subtype(sub, self.anchor_to(sup)) then
- # FIXME workarround to the current unsafe typing policy. To remove once fixed virtual types exists.
+ # FIXME workaround to the current unsafe typing policy. To remove once fixed virtual types exists.
#node.debug("Unsafe typing: expected {sup}, got {sub}")
return sup
end
end
- private fun visit_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
+ fun visit_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
do
var sub = visit_expr(nexpr)
if sub == null then return null # Forward error
if sup == null then return null # Forward error
if sup == sub then
- self.modelbuilder.warning(node, "Warning: Expression is already a {sup}.")
+ self.modelbuilder.warning(node, "useless-type-test", "Warning: Expression is already a {sup}.")
else if self.is_subtype(sub, sup) then
- self.modelbuilder.warning(node, "Warning: Expression is already a {sup} since it is a {sub}.")
+ self.modelbuilder.warning(node, "useless-type-test", "Warning: Expression is already a {sup} since it is a {sub}.")
end
return sup
end
+ # Special verification on != and == for null
+ # Return true
+ fun null_test(anode: ABinopExpr)
+ do
+ var mtype = anode.n_expr.mtype
+ var mtype2 = anode.n_expr2.mtype
+
+ if mtype == null or mtype2 == null then return
+
+ if not mtype2 isa MNullType then return
+
+ # Check of useless null
+ if not mtype isa MNullableType then
+ if not anchor_to(mtype) isa MNullableType then
+ modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.")
+ end
+ return
+ end
+
+ # Check for type adaptation
+ var variable = anode.n_expr.its_variable
+ if variable == null then return
+
+ if anode isa AEqExpr then
+ anode.after_flow_context.when_true.set_var(variable, mtype2)
+ anode.after_flow_context.when_false.set_var(variable, mtype.mtype)
+ else if anode isa ANeExpr then
+ anode.after_flow_context.when_false.set_var(variable, mtype2)
+ anode.after_flow_context.when_true.set_var(variable, mtype.mtype)
+ else
+ abort
+ end
+ end
+
fun try_get_mproperty_by_name2(anode: ANode, mtype: MType, name: String): nullable MProperty
do
return self.modelbuilder.try_get_mproperty_by_name2(anode, mmodule, mtype, name)
#debug("recv: {recvtype} (aka {unsafe_type})")
if recvtype isa MNullType then
- self.error(node, "Error: Method '{name}' call on 'null'.")
- return null
+ # `null` only accepts some methods of object.
+ if name == "==" or name == "!=" or name == "is_same_instance" then
+ unsafe_type = mmodule.object_type.as_nullable
+ else
+ self.error(node, "Error: Method '{name}' call on 'null'.")
+ return null
+ end
end
var mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name)
+ if name == "new" and mproperty == null then
+ name = "init"
+ mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name)
+ end
+
if mproperty == null then
#self.modelbuilder.error(node, "Type error: property {name} not found in {unsafe_type} (ie {recvtype})")
if recv_is_self then
return null
end
+ var info = mproperty.deprecation
+ if info != null and self.mpropdef.mproperty.deprecation == null then
+ var mdoc = info.mdoc
+ if mdoc != null then
+ self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: Method '{name}' is deprecated: {mdoc.content.first}")
+ else
+ self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: Method '{name}' is deprecated.")
+ end
+ end
+
var propdefs = mproperty.lookup_definitions(self.mmodule, unsafe_type)
var mpropdef
if propdefs.length == 0 then
else if propdefs.length == 1 then
mpropdef = propdefs.first
else
- self.modelbuilder.warning(node, "Warning: confliting property definitions for property {name} in {unsafe_type}: {propdefs.join(" ")}")
+ self.modelbuilder.warning(node, "property-conflict", "Warning: conflicting property definitions for property {name} in {unsafe_type}: {propdefs.join(" ")}")
mpropdef = mproperty.intro
end
return callsite
end
- # Visit the expressions of args and cheik their conformity with the corresponding typi in signature
+ fun try_get_method(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite
+ do
+ var unsafe_type = self.anchor_to(recvtype)
+ var mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name)
+ if mproperty == null then return null
+ return get_method(node, recvtype, name, recv_is_self)
+ end
+
+
+ # Visit the expressions of args and check their conformity with the corresponding type in signature
# The point of this method is to handle varargs correctly
# Note: The signature must be correctly adapted
fun check_signature(node: ANode, args: Array[AExpr], name: String, msignature: MSignature): Bool
self.visit_expr_subtype(args[j], paramtype)
end
if vararg_rank >= 0 then
- var varargs = new Array[AExpr]
var paramtype = msignature.mparameters[vararg_rank].mtype
- for j in [vararg_rank..vararg_rank+vararg_decl] do
- varargs.add(args[j])
- self.visit_expr_subtype(args[j], paramtype)
+ var first = args[vararg_rank]
+ if vararg_decl == 0 and first isa AVarargExpr then
+ var mclass = get_mclass(node, "Array")
+ if mclass == null then return false # Forward error
+ var array_mtype = mclass.get_mtype([paramtype])
+ self.visit_expr_subtype(first.n_expr, array_mtype)
+ first.mtype = first.n_expr.mtype
+ else
+ for j in [vararg_rank..vararg_rank+vararg_decl] do
+ self.visit_expr_subtype(args[j], paramtype)
+ end
end
end
return true
fun merge_types(node: ANode, col: Array[nullable MType]): nullable MType
do
if col.length == 1 then return col.first
- var res = new Array[nullable MType]
for t1 in col do
if t1 == null then continue # return null
var found = true
# A specific method call site with its associated informations.
class CallSite
- # The assiciated node for location
+ # The associated node for location
var node: ANode
# The static type of the receiver (possibly unresolved)
var anchor: nullable MClassType
# Is the receiver self?
- # If "for_self", virtual types of the signature are keeped
+ # If "for_self", virtual types of the signature are kept
# If "not_for_self", virtual type are erased
var recv_is_self: Bool
redef class FlowContext
# Store changes of types because of type evolution
- private var vars: HashMap[Variable, nullable MType] = new HashMap[Variable, nullable MType]
- private var cache: HashMap[Variable, nullable Array[nullable MType]] = new HashMap[Variable, nullable Array[nullable MType]]
+ private var vars = new HashMap[Variable, nullable MType]
+ private var cache = new HashMap[Variable, nullable Array[nullable MType]]
# Adapt the variable to a static type
# Warning1: do not modify vars directly.
- # Warning2: sub-flow may have cached a unadapted variabial
+ # Warning2: sub-flow may have cached a unadapted variable
private fun set_var(variable: Variable, mtype: nullable MType)
do
self.vars[variable] = mtype
do
end
- # The variable associated to the reciever (if any)
+ # The variable associated to the receiver (if any)
var selfvariable: nullable Variable
end
var mtype = self.mpropdef.static_mtype
v.visit_expr_subtype(nexpr, mtype)
end
+ var nblock = self.n_block
+ if nblock != null then
+ v.visit_stmt(nblock)
+ if not nblock.after_flow_context.is_unreachable then
+ # We reach the end of the init without having a return, it is bad
+ v.error(self, "Control error: Reached end of block (a 'return' with a value was expected).")
+ end
+ end
end
end
do
v.error(self, "no implemented accept_typing for {self.class_name}")
end
+
+ # Is non-null if `self` is a leaf of a comprehension array construction.
+ # In this case, the enclosing literal array node is designated.
+ # The result of the evaluation of `self` must be
+ # stored inside the designated array (there is an implicit `push`)
+ var comprehension: nullable AArrayExpr = null
end
redef class ABlockExpr
var nexpr = self.n_expr
if nexpr != null then
if mtype != null then
- v.visit_expr_subtype(nexpr, mtype)
+ var etype = v.visit_expr_subtype(nexpr, mtype)
+ if etype == mtype then
+ assert ntype != null
+ v.modelbuilder.advice(ntype, "useless-type", "Warning: useless type definition for variable `{variable.name}`")
+ end
else
mtype = v.visit_expr(nexpr)
if mtype == null then return # Skip error
self.read_type = readtype
- if readtype isa MNullType then
- v.error(self, "Error: Method '{reassign_name}' call on 'null'.")
- return null
- end
-
var callsite = v.get_method(self, readtype, reassign_name, false)
if callsite == null then return null # Skip error
self.reassign_callsite = callsite
do
var nexpr = self.n_expr
if nexpr != null then
- var mtype = v.visit_expr(nexpr)
+ v.visit_expr(nexpr)
end
self.is_typed = true
end
do
var nexpr = self.n_expr
if nexpr != null then
- var mtype = v.visit_expr(nexpr)
+ v.visit_expr(nexpr)
end
self.is_typed = true
end
redef fun accept_typing(v)
do
var nexpr = self.n_expr
- var ret_type = v.mpropdef.as(MMethodDef).msignature.return_mtype
+ var ret_type
+ var mpropdef = v.mpropdef
+ if mpropdef isa MMethodDef then
+ ret_type = mpropdef.msignature.return_mtype
+ else if mpropdef isa MAttributeDef then
+ ret_type = mpropdef.static_mtype
+ else
+ abort
+ end
if nexpr != null then
if ret_type != null then
- var mtype = v.visit_expr_subtype(nexpr, ret_type)
+ v.visit_expr_subtype(nexpr, ret_type)
else
- var mtype = v.visit_expr(nexpr)
+ v.visit_expr(nexpr)
v.error(self, "Error: Return with value in a procedure.")
end
else if ret_type != null then
v.visit_stmt(n_then)
v.visit_stmt(n_else)
+
self.is_typed = true
+
+ if n_then != null and n_else == null then
+ self.mtype = n_then.mtype
+ end
end
end
var method_item: nullable CallSite
var method_next: nullable CallSite
var method_key: nullable CallSite
+ var method_finish: nullable CallSite
+
+ var method_lt: nullable CallSite
+ var method_successor: nullable CallSite
private fun do_type_iterator(v: TypeVisitor, mtype: MType)
do
is_col = true
end
- if mapit_cla != null and v.is_subtype(ittype, mapit_cla.get_mtype([objcla.mclass_type, objcla.mclass_type.as_nullable])) then
+ if mapit_cla != null and v.is_subtype(ittype, mapit_cla.get_mtype([objcla.mclass_type.as_nullable, objcla.mclass_type.as_nullable])) then
# Map Iterator
var coltype = ittype.supertype_to(v.mmodule, v.anchor, mapit_cla)
var variables = self.variables
end
self.method_next = nextdef
+ self.method_finish = v.try_get_method(self, ittype, "finish", false)
+
if is_map then
var keydef = v.get_method(self, ittype, "key", false)
if keydef == null then
end
self.method_key = keydef
end
+
+ if self.variables.length == 1 and n_expr isa ARangeExpr then
+ var variable = variables.first
+ var vtype = variable.declared_type.as(not null)
+
+ if n_expr isa AOrangeExpr then
+ self.method_lt = v.get_method(self, vtype, "<", false)
+ else
+ self.method_lt = v.get_method(self, vtype, "<=", false)
+ end
+
+ self.method_successor = v.get_method(self, vtype, "successor", false)
+ end
end
redef fun accept_typing(v)
self.do_type_iterator(v, mtype)
v.visit_stmt(n_block)
+ self.mtype = n_block.mtype
self.is_typed = true
end
end
end
redef class AArrayExpr
+ # The `with_capacity` method on Array
var with_capacity_callsite: nullable CallSite
+
+ # The `push` method on arrays
var push_callsite: nullable CallSite
+ # The element of each type
+ var element_mtype: nullable MType
+
+ # Set that `self` is a part of comprehension array `na`
+ # If `self` is a `for`, or a `if`, then `set_comprehension` is recursively applied.
+ private fun set_comprehension(n: nullable AExpr)
+ do
+ if n == null then
+ return
+ else if n isa AForExpr then
+ set_comprehension(n.n_block)
+ else if n isa AIfExpr then
+ set_comprehension(n.n_then)
+ set_comprehension(n.n_else)
+ else
+ # is a leave
+ n.comprehension = self
+ end
+ end
redef fun accept_typing(v)
do
+ var mtype: nullable MType = null
+ var ntype = self.n_type
+ if ntype != null then
+ mtype = v.resolve_mtype(ntype)
+ if mtype == null then return # Skip error
+ end
var mtypes = new Array[nullable MType]
- for e in self.n_exprs.n_exprs do
+ var useless = false
+ for e in self.n_exprs do
var t = v.visit_expr(e)
if t == null then
return # Skip error
end
- mtypes.add(t)
+ set_comprehension(e)
+ if mtype != null then
+ if v.check_subtype(e, t, mtype) == null then return # Skip error
+ if t == mtype then useless = true
+ else
+ mtypes.add(t)
+ end
end
- var mtype = v.merge_types(self, mtypes)
if mtype == null then
+ mtype = v.merge_types(self, mtypes)
+ end
+ if mtype == null or mtype isa MNullType then
v.error(self, "Type Error: ambiguous array type {mtypes.join(" ")}")
return
end
+ if useless then
+ assert ntype != null
+ v.modelbuilder.warning(ntype, "useless-type", "Warning: useless type declaration `{mtype}` in literal Array since it can be inferred from the elements type.")
+ end
+
+ self.element_mtype = mtype
+
var mclass = v.get_mclass(self, "Array")
if mclass == null then return # Forward error
var array_mtype = mclass.get_mtype([mtype])
var variable = self.n_expr.its_variable
if variable != null then
- var orig = self.n_expr.mtype
- var from = if orig != null then orig.to_s else "invalid"
- var to = if mtype != null then mtype.to_s else "invalid"
+ #var orig = self.n_expr.mtype
+ #var from = if orig != null then orig.to_s else "invalid"
+ #var to = if mtype != null then mtype.to_s else "invalid"
#debug("adapt {variable}: {from} -> {to}")
self.after_flow_context.when_true.set_var(variable, mtype)
end
self.mtype = mtype
if mtype isa MClassType then
- v.modelbuilder.warning(self, "Warning: expression is already not null, since it is a `{mtype}`.")
+ v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}`.")
return
end
assert mtype.need_anchor
var u = v.anchor_to(mtype)
if not u isa MNullableType then
- v.modelbuilder.warning(self, "Warning: expression is already not null, since it is a `{mtype}: {u}`.")
+ v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}: {u}`.")
return
end
end
end
-redef class AProxyExpr
+redef class AParExpr
+ redef fun accept_typing(v)
+ do
+ self.mtype = v.visit_expr(self.n_expr)
+ end
+end
+
+redef class AOnceExpr
redef fun accept_typing(v)
do
self.mtype = v.visit_expr(self.n_expr)
var name = self.property_name
if recvtype == null then return # Forward error
- if recvtype isa MNullType then
- v.error(self, "Error: Method '{name}' call on 'null'.")
- return
- end
var callsite = v.get_method(self, recvtype, name, self.n_expr isa ASelfExpr)
if callsite == null then return
redef fun accept_typing(v)
do
super
-
- var variable = self.n_expr.its_variable
- if variable == null then return
- var mtype = self.n_expr2.mtype
- if not mtype isa MNullType then return
- var vartype = v.get_variable(self, variable)
- if not vartype isa MNullableType then return
- self.after_flow_context.when_true.set_var(variable, mtype)
- self.after_flow_context.when_false.set_var(variable, vartype.mtype)
- #debug("adapt {variable}:{vartype} ; true->{mtype} false->{vartype.mtype}")
+ v.null_test(self)
end
end
redef class ANeExpr
redef fun accept_typing(v)
do
super
-
- var variable = self.n_expr.its_variable
- if variable == null then return
- var mtype = self.n_expr2.mtype
- if not mtype isa MNullType then return
- var vartype = v.get_variable(self, variable)
- if not vartype isa MNullableType then return
- self.after_flow_context.when_false.set_var(variable, mtype)
- self.after_flow_context.when_true.set_var(variable, vartype.mtype)
- #debug("adapt {variable}:{vartype} ; true->{vartype.mtype} false->{mtype}")
+ v.null_test(self)
end
end
redef class ALtExpr
redef class AStarExpr
redef fun property_name do return "*"
end
+redef class AStarstarExpr
+ redef fun property_name do return "**"
+end
redef class ASlashExpr
redef fun property_name do return "/"
end
var name = self.property_name
if recvtype == null then return # Forward error
- if recvtype isa MNullType then
- v.error(self, "Error: Method '{name}' call on 'null'.")
- return
- end
var for_self = self.n_expr isa ASelfExpr
var callsite = v.get_method(self, recvtype, name, for_self)
redef fun accept_typing(v)
do
- var recvtype = v.anchor
+ var anchor = v.anchor
+ assert anchor != null
+ var recvtype = v.get_variable(self, v.selfvariable)
assert recvtype != null
var mproperty = v.mpropdef.mproperty
if not mproperty isa MMethod then
v.error(self, "Error: super only usable in a method")
return
end
- var superprops = mproperty.lookup_super_definitions(v.mmodule, recvtype)
+ var superprops = mproperty.lookup_super_definitions(v.mmodule, anchor)
if superprops.length == 0 then
if mproperty.is_init and v.mpropdef.is_intro then
process_superinit(v)
private fun process_superinit(v: TypeVisitor)
do
- var recvtype = v.anchor
+ var anchor = v.anchor
+ assert anchor != null
+ var recvtype = v.get_variable(self, v.selfvariable)
assert recvtype != null
var mpropdef = v.mpropdef
assert mpropdef isa MMethodDef
var mproperty = mpropdef.mproperty
var superprop: nullable MMethodDef = null
for msupertype in mpropdef.mclassdef.supertypes do
- msupertype = msupertype.anchor_to(v.mmodule, recvtype)
+ msupertype = msupertype.anchor_to(v.mmodule, anchor)
var errcount = v.modelbuilder.toolcontext.error_count
var candidate = v.try_get_mproperty_by_name2(self, msupertype, mproperty.name).as(nullable MMethod)
if candidate == null then
- if v.modelbuilder.toolcontext.error_count > errcount then return # Forard error
+ if v.modelbuilder.toolcontext.error_count > errcount then return # Forward error
continue # Try next super-class
end
if superprop != null and candidate.is_root_init then
v.error(self, "Error: conflicting super constructor to call for {mproperty}: {candidate.full_name}, {superprop.mproperty.full_name}")
return
end
- var candidatedefs = candidate.lookup_definitions(v.mmodule, recvtype)
+ var candidatedefs = candidate.lookup_definitions(v.mmodule, anchor)
if superprop != null and superprop.mproperty == candidate then
if superprop == candidatedefs.first then continue
candidatedefs.add(superprop)
end
if candidatedefs.length > 1 then
- v.error(self, "Error: confliting property definitions for property {mproperty} in {recvtype}: {candidatedefs.join(", ")}")
+ v.error(self, "Error: conflicting property definitions for property {mproperty} in {recvtype}: {candidatedefs.join(", ")}")
return
end
superprop = candidatedefs.first
# The constructor invoked by the new.
var callsite: nullable CallSite
+ # The designated type
+ var recvtype: nullable MClassType
+
redef fun accept_typing(v)
do
var recvtype = v.resolve_mtype(self.n_type)
if recvtype == null then return
- self.mtype = recvtype
if not recvtype isa MClassType then
if recvtype isa MNullableType then
v.error(self, "Type error: cannot instantiate the formal type {recvtype}.")
return
end
- else
- if recvtype.mclass.kind == abstract_kind then
- v.error(self, "Cannot instantiate abstract class {recvtype}.")
- return
- else if recvtype.mclass.kind == interface_kind then
- v.error(self, "Cannot instantiate interface {recvtype}.")
- return
- end
end
+ self.recvtype = recvtype
+
var name: String
var nid = self.n_id
if nid != null then
name = nid.text
else
- name = "init"
+ name = "new"
end
var callsite = v.get_method(self, recvtype, name, false)
if callsite == null then return
+ if not callsite.mproperty.is_new then
+ var kind = recvtype.mclass.kind
+ if kind != concrete_kind then
+ v.error(self, "Type Error: Cannot instantiate {kind} {recvtype}.")
+ return
+ end
+ self.mtype = recvtype
+ else
+ self.mtype = callsite.msignature.return_mtype
+ assert self.mtype != null
+ end
+
self.callsite = callsite
if not callsite.mproperty.is_init_for(recvtype.mclass) then
end
end
+redef class AVarargExpr
+ redef fun accept_typing(v)
+ do
+ # This kind of pseudo-expression can be only processed trough a signature
+ # See `check_signature`
+ # Other cases are a syntax error.
+ v.error(self, "Syntax error: unexpected `...`")
+ end
+end
+
###
redef class ADebugTypeExpr
var mtype = v.resolve_mtype(ntype)
if mtype != null and mtype != expr then
var umtype = v.anchor_to(mtype)
- v.modelbuilder.warning(self, "Found type {expr} (-> {unsafe}), expected {mtype} (-> {umtype})")
+ v.modelbuilder.warning(self, "debug", "Found type {expr} (-> {unsafe}), expected {mtype} (-> {umtype})")
end
self.is_typed = true
end