import modelize
import local_var_init
+import literal
redef class ToolContext
var typing_phase: Phase = new TypingPhase(self, [flow_phase, modelize_property_phase, local_var_init_phase])
#node.debug("Unsafe typing: expected {sup}, got {sub}")
return sup
end
+ if sup isa MBottomType then return null # Skip error
if sub.need_anchor then
var u = anchor_to(sub)
self.modelbuilder.error(node, "Type Error: expected `{sup}`, got `{sub}: {u}`.")
end
return null # forward error
end
- self.error(nexpr, "Error: expected an expression.")
+ var more_message = null
+ var p = nexpr.parent
+ if p != null then more_message = p.bad_expr_message(nexpr)
+ if more_message == null then more_message = "" else more_message = " " + more_message
+ self.error(nexpr, "Error: expected an expression{more_message}.")
return null
end
end
- fun visit_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
+ fun check_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
do
- var sub = visit_expr(nexpr)
+ var sub = nexpr.mtype
if sub == null then return null # Forward error
- var sup = self.resolve_mtype(ntype)
+ var sup = ntype.mtype
if sup == null then return null # Forward error
if sup == sub then
# Else return true.
fun check_can_be_null(anode: ANode, mtype: MType): Bool
do
+ if mtype isa MNullType then
+ modelbuilder.warning(anode, "useless-null-test", "Warning: expression is always `null`.")
+ return true
+ end
if can_be_null(mtype) then return true
if mtype isa MFormalType then
if not mtype2 isa MNullType then return
# Check of useless null
- if not check_can_be_null(anode.n_expr, mtype) then return
+ if not can_be_null(mtype) then return
if mtype isa MNullType then
# Because of type adaptation, we cannot just stop here
end
end
- var callsite = new CallSite(node, recvtype, mmodule, anchor, recv_is_self, mproperty, mpropdef, msignature, erasure_cast)
+ var callsite = new CallSite(node.hot_location, recvtype, mmodule, anchor, recv_is_self, mproperty, mpropdef, msignature, erasure_cast)
return callsite
end
return null
end
else if args.length != msignature.arity then
- if msignature.arity == msignature.min_arity then
- modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
- return null
- end
+ # Too much argument
if args.length > msignature.arity then
- modelbuilder.error(node, "Error: expected at most {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
- return null
- end
- if args.length < msignature.min_arity then
- modelbuilder.error(node, "Error: expected at least {msignature.min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
return null
end
+ # Other cases are managed later
end
+
#debug("CALL {unsafe_type}.{msignature}")
# Associate each parameter to a position in the arguments
var map = new SignatureMap
- var setted = args.length - msignature.min_arity
+ # Special case for the isolated last argument
+ # TODO: reify this method characteristics (where? the param, the signature, the method?)
+ var last_is_padded = mproperty.name.chars.last == '='
+ var nbargs = args.length
+ if last_is_padded then
+ nbargs -= 1
+ assert not args.last isa ANamedargExpr
+ map.map[msignature.arity - 1] = args.length - 1
+ self.visit_expr_subtype(args.last, msignature.mparameters.last.mtype)
+ end
# First, handle named arguments
for i in [0..args.length[ do
modelbuilder.error(e.n_id, "Error: no parameter `{name}` for `{mproperty}{msignature}`.")
return null
end
- if not param.is_default then
- modelbuilder.error(e, "Error: parameter `{name}` is not optional for `{mproperty}{msignature}`.")
- return null
- end
var idx = msignature.mparameters.index_of(param)
var prev = map.map.get_or_null(idx)
if prev != null then
return null
end
map.map[idx] = i
- setted -= 1
e.mtype = self.visit_expr_subtype(e.n_expr, param.mtype)
end
+ # Number of minimum mandatory remaining parameters
+ var min_arity = 0
+
# Second, associate remaining parameters
var vararg_decl = args.length - msignature.arity
var j = 0
if map.map.has_key(i) then continue
var param = msignature.mparameters[i]
- if param.is_default then
- if setted > 0 then
- setted -= 1
- else
- continue
- end
- end
# Search the next free argument: skip named arguments since they are already associated
- while args[j] isa ANamedargExpr do j += 1
+ while j < nbargs and args[j] isa ANamedargExpr do j += 1
+ if j >= nbargs then
+ if not param.mtype isa MNullableType then
+ min_arity = j + 1
+ end
+ j += 1
+ continue
+ end
var arg = args[j]
map.map[i] = j
j += 1
continue # skip the vararg
end
- var paramtype = param.mtype
- self.visit_expr_subtype(arg, paramtype)
+ if not param.is_vararg then
+ var paramtype = param.mtype
+ self.visit_expr_subtype(arg, paramtype)
+ else
+ check_one_vararg(arg, param)
+ end
+ end
+
+ if min_arity > 0 then
+ if last_is_padded then min_arity += 1
+ if min_arity < msignature.arity then
+ modelbuilder.error(node, "Error: expected at least {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ else
+ modelbuilder.error(node, "Error: expected {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+ end
+ return null
end
# Third, check varargs
var paramtype = msignature.mparameters[vararg_rank].mtype
var first = args[vararg_rank]
if vararg_decl == 0 then
- var mclass = get_mclass(node, "Array")
- if mclass == null then return null # Forward error
- var array_mtype = mclass.get_mtype([paramtype])
- if first isa AVarargExpr then
- self.visit_expr_subtype(first.n_expr, array_mtype)
- first.mtype = first.n_expr.mtype
- else
- # only one vararg, maybe `...` was forgot, so be gentle!
- var t = visit_expr(first)
- if t == null then return null # Forward error
- if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
- # Not acceptable but could be a `...`
- error(first, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
- return null
- end
- # Standard valid vararg, finish the job
- map.vararg_decl = 1
- self.visit_expr_subtype(first, paramtype)
- end
+ if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null
else
- map.vararg_decl = vararg_decl + 1
+ first.vararg_decl = vararg_decl + 1
for i in [vararg_rank..vararg_rank+vararg_decl] do
self.visit_expr_subtype(args[i], paramtype)
end
return map
end
+ # Check an expression as a single vararg.
+ # The main point of the method if to handle the case of reversed vararg (see `AVarargExpr`)
+ fun check_one_vararg(arg: AExpr, param: MParameter): Bool
+ do
+ var paramtype = param.mtype
+ var mclass = get_mclass(arg, "Array")
+ if mclass == null then return false # Forward error
+ var array_mtype = mclass.get_mtype([paramtype])
+ if arg isa AVarargExpr then
+ self.visit_expr_subtype(arg.n_expr, array_mtype)
+ arg.mtype = arg.n_expr.mtype
+ else
+ # only one vararg, maybe `...` was forgot, so be gentle!
+ var t = visit_expr(arg)
+ if t == null then return false # Forward error
+ if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
+ # Not acceptable but could be a `...`
+ error(arg, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
+ return false
+ end
+ # Standard valid vararg, finish the job
+ arg.vararg_decl = 1
+ self.visit_expr_subtype(arg, paramtype)
+ end
+ return true
+ end
+
fun error(node: ANode, message: String)
do
- self.modelbuilder.toolcontext.error(node.hot_location, message)
+ self.modelbuilder.error(node, message)
end
fun get_variable(node: AExpr, variable: Variable): nullable MType
class SignatureMap
# Associate a parameter to an argument
var map = new ArrayMap[Int, Int]
-
- # The length of the vararg sequence
- # 0 if no vararg or if reverse vararg (cf `AVarargExpr`)
- var vararg_decl: Int = 0
end
# A specific method call site with its associated informations.
class CallSite
- # The associated node for location
- var node: ANode
+ super MEntity
+
+ redef var location: Location
# The static type of the receiver (possibly unresolved)
var recv: MType
# If null then no specific association is required.
var signaturemap: nullable SignatureMap = null
- private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool
+ private fun check_signature(v: TypeVisitor, node: ANode, args: Array[AExpr]): Bool
do
- var map = v.check_signature(self.node, args, self.mproperty, self.msignature)
+ var map = v.check_signature(node, args, self.mproperty, self.msignature)
signaturemap = map
+ if map == null then is_broken = true
return map == null
end
end
redef class Variable
# The declared type of the variable
- var declared_type: nullable MType
+ var declared_type: nullable MType is writable
# Was the variable type-adapted?
# This is used to speedup type retrieval while it remains `false`
redef class AMethPropdef
redef fun do_typing(modelbuilder: ModelBuilder)
do
- var nblock = self.n_block
- if nblock == null then return
-
var mpropdef = self.mpropdef
if mpropdef == null then return # skip error
variable.declared_type = mtype
end
+ var nblock = self.n_block
+ if nblock == null then return
+
loop
v.dirty = false
v.visit_stmt(nblock)
if not v.has_loop or not v.dirty then break
end
+ var post_visitor = new PostTypingVisitor(v)
+ post_visitor.enter_visit(self)
+
if not nblock.after_flow_context.is_unreachable and msignature.return_mtype != null then
# We reach the end of the function without having a return, it is bad
v.error(self, "Error: reached end of function; expected `return` with a value.")
end
end
+private class PostTypingVisitor
+ super Visitor
+ var type_visitor: TypeVisitor
+ redef fun visit(n) do
+ n.visit_all(self)
+ n.accept_post_typing(type_visitor)
+ if n isa AExpr and n.mtype == null and not n.is_typed then
+ n.is_broken = true
+ end
+ end
+end
+
+redef class ANode
+ private fun accept_post_typing(v: TypeVisitor) do end
+
+ # An additional information message to explain the role of a child expression.
+ #
+ # The point of the method is to allow some kind of double dispatch so the parent
+ # choose how to describe its children.
+ private fun bad_expr_message(child: AExpr): nullable String do return null
+end
+
redef class AAttrPropdef
redef fun do_typing(modelbuilder: ModelBuilder)
do
if not has_value then return
- var mpropdef = self.mpropdef
- if mpropdef == null then return # skip error
+ var mpropdef = self.mreadpropdef
+ if mpropdef == null or mpropdef.msignature == null then return # skip error
var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
self.selfvariable = v.selfvariable
var nexpr = self.n_expr
if nexpr != null then
- var mtype = self.mpropdef.static_mtype
+ var mtype = self.mtype
v.visit_expr_subtype(nexpr, mtype)
end
var nblock = self.n_block
# The result of the evaluation of `self` must be
# stored inside the designated array (there is an implicit `push`)
var comprehension: nullable AArrayExpr = null
+
+ # It indicates the number of arguments collected as a vararg.
+ #
+ # When 0, the argument is used as is, without transformation.
+ # When 1, the argument is transformed into an singleton array.
+ # Above 1, the arguments and the next ones are transformed into a common array.
+ #
+ # This attribute is meaning less on expressions not used as attributes.
+ var vararg_decl: Int = 0
end
redef class ABlockExpr
v.set_variable(self, variable, rettype)
- self.is_typed = true
+ self.is_typed = rettype != null
end
end
else
v.visit_expr(nexpr)
v.error(nexpr, "Error: `return` with value in a procedure.")
+ return
end
else if ret_type != null then
v.error(self, "Error: `return` without value in a function.")
+ return
end
self.is_typed = true
end
redef fun accept_typing(v)
do
v.visit_stmt(n_block)
+ v.visit_stmt(n_catch)
self.is_typed = true
end
end
end
redef class AForExpr
+ redef fun accept_typing(v)
+ do
+ v.has_loop = true
+
+ for g in n_groups do
+ var mtype = v.visit_expr(g.n_expr)
+ if mtype == null then return
+ g.do_type_iterator(v, mtype)
+ if g.is_broken then is_broken = true
+ end
+
+ v.visit_stmt(n_block)
+
+ self.mtype = n_block.mtype
+ self.is_typed = true
+ end
+end
+
+redef class AForGroup
var coltype: nullable MClassType
var method_iterator: nullable CallSite
self.method_successor = v.get_method(self, vtype, "successor", false)
end
end
-
- redef fun accept_typing(v)
- do
- v.has_loop = true
- var mtype = v.visit_expr(n_expr)
- if mtype == null then return
-
- self.do_type_iterator(v, mtype)
-
- v.visit_stmt(n_block)
-
- self.mtype = n_block.mtype
- self.is_typed = true
- end
end
redef class AWithExpr
end
if t1 isa MNullType then
- v.error(n_expr, "Type Error: `or else` on `null`.")
- else if v.check_can_be_null(n_expr, t1) then
+ self.mtype = t2
+ return
+ else if v.can_be_null(t1) then
t1 = t1.as_notnull
end
end
self.mtype = t
end
-end
-redef class ATrueExpr
- redef fun accept_typing(v)
+ redef fun accept_post_typing(v)
do
- self.mtype = v.type_bool(self)
+ var t1 = n_expr.mtype
+ if t1 == null then
+ return
+ else
+ v.check_can_be_null(n_expr, t1)
+ end
end
end
-redef class AFalseExpr
+redef class ATrueExpr
redef fun accept_typing(v)
do
self.mtype = v.type_bool(self)
end
end
-redef class AIntExpr
+redef class AFalseExpr
redef fun accept_typing(v)
do
- var mclass = v.get_mclass(self, "Int")
- if mclass == null then return # Forward error
- self.mtype = mclass.mclass_type
+ self.mtype = v.type_bool(self)
end
end
-redef class AByteExpr
+redef class AIntegerExpr
redef fun accept_typing(v)
do
- var mclass = v.get_mclass(self, "Byte")
+ var mclass: nullable MClass = null
+ if value isa Byte then
+ mclass = v.get_mclass(self, "Byte")
+ else if value isa Int then
+ mclass = v.get_mclass(self, "Int")
+ else if value isa Int8 then
+ mclass = v.get_mclass(self, "Int8")
+ else if value isa Int16 then
+ mclass = v.get_mclass(self, "Int16")
+ else if value isa UInt16 then
+ mclass = v.get_mclass(self, "UInt16")
+ else if value isa Int32 then
+ mclass = v.get_mclass(self, "Int32")
+ else if value isa UInt32 then
+ mclass = v.get_mclass(self, "UInt32")
+ end
if mclass == null then return # Forward error
self.mtype = mclass.mclass_type
end
end
redef class ACharExpr
- redef fun accept_typing(v)
- do
- var mclass = v.get_mclass(self, "Char")
+ redef fun accept_typing(v) do
+ var mclass: nullable MClass = null
+ if is_ascii then
+ mclass = v.get_mclass(self, "Byte")
+ else if is_code_point then
+ mclass = v.get_mclass(self, "Int")
+ else
+ mclass = v.get_mclass(self, "Char")
+ end
if mclass == null then return # Forward error
self.mtype = mclass.mclass_type
end
end
-redef class AStringFormExpr
- redef fun accept_typing(v)
- do
+redef class AugmentedStringFormExpr
+ super AExpr
+
+ # Text::to_re, used for prefix `re`
+ var to_re: nullable CallSite = null
+ # Regex::ignore_case, used for suffix `i` on `re`
+ var ignore_case: nullable CallSite = null
+ # Regex::newline, used for suffix `m` on `re`
+ var newline: nullable CallSite = null
+ # Regex::extended, used for suffix `b` on `re`
+ var extended: nullable CallSite = null
+ # NativeString::to_bytes_with_copy, used for prefix `b`
+ var to_bytes_with_copy: nullable CallSite = null
+
+ redef fun accept_typing(v) do
var mclass = v.get_mclass(self, "String")
if mclass == null then return # Forward error
- self.mtype = mclass.mclass_type
+ if is_bytestring then
+ to_bytes_with_copy = v.get_method(self, v.mmodule.native_string_type, "to_bytes_with_copy", false)
+ mclass = v.get_mclass(self, "Bytes")
+ else if is_re then
+ to_re = v.get_method(self, mclass.mclass_type, "to_re", false)
+ for i in suffix.chars do
+ mclass = v.get_mclass(self, "Regex")
+ if mclass == null then
+ v.error(self, "Error: `Regex` class unknown")
+ return
+ end
+ var service = ""
+ if i == 'i' then
+ service = "ignore_case="
+ ignore_case = v.get_method(self, mclass.mclass_type, service, false)
+ else if i == 'm' then
+ service = "newline="
+ newline = v.get_method(self, mclass.mclass_type, service, false)
+ else if i == 'b' then
+ service = "extended="
+ extended = v.get_method(self, mclass.mclass_type, service, false)
+ else
+ v.error(self, "Type Error: Unrecognized suffix {i} in prefixed Regex")
+ abort
+ end
+ end
+ end
+ if mclass == null then return # Forward error
+ mtype = mclass.mclass_type
end
end
redef class ASuperstringExpr
redef fun accept_typing(v)
do
- var mclass = v.get_mclass(self, "String")
- if mclass == null then return # Forward error
- self.mtype = mclass.mclass_type
+ super
var objclass = v.get_mclass(self, "Object")
if objclass == null then return # Forward error
var objtype = objclass.mclass_type
var cast_type: nullable MType
redef fun accept_typing(v)
do
- var mtype = v.visit_expr_cast(self, self.n_expr, self.n_type)
+ v.visit_expr(n_expr)
+
+ var mtype = v.resolve_mtype(n_type)
+
self.cast_type = mtype
var variable = self.n_expr.its_variable
self.mtype = v.type_bool(self)
end
+
+ redef fun accept_post_typing(v)
+ do
+ v.check_expr_cast(self, self.n_expr, self.n_type)
+ end
end
redef class AAsCastExpr
redef fun accept_typing(v)
do
- self.mtype = v.visit_expr_cast(self, self.n_expr, self.n_type)
+ v.visit_expr(n_expr)
+
+ self.mtype = v.resolve_mtype(n_type)
+ end
+
+ redef fun accept_post_typing(v)
+ do
+ v.check_expr_cast(self, self.n_expr, self.n_type)
end
end
return
end
- if v.check_can_be_null(n_expr, mtype) then
+ if v.can_be_null(mtype) then
mtype = mtype.as_notnull
end
self.mtype = mtype
end
+
+ redef fun accept_post_typing(v)
+ do
+ var mtype = n_expr.mtype
+ if mtype == null then return
+ v.check_can_be_null(n_expr, mtype)
+ end
end
redef class AParExpr
# The property invoked by the send.
var callsite: nullable CallSite
+ redef fun bad_expr_message(child)
+ do
+ if child == self.n_expr then
+ return "to be the receiver of `{self.property_name}`"
+ end
+ return null
+ end
+
redef fun accept_typing(v)
do
var nrecv = self.n_expr
var args = compute_raw_arguments
- callsite.check_signature(v, args)
+ callsite.check_signature(v, node, args)
if callsite.mproperty.is_init then
var vmpropdef = v.mpropdef
redef fun property_name do return operator
redef fun property_node do return n_op
end
-redef class AEqExpr
+
+redef class AEqFormExpr
redef fun accept_typing(v)
do
super
v.null_test(self)
end
-end
-redef class ANeExpr
- redef fun accept_typing(v)
+
+ redef fun accept_post_typing(v)
do
- super
- v.null_test(self)
+ var mtype = n_expr.mtype
+ var mtype2 = n_expr2.mtype
+
+ if mtype == null or mtype2 == null then return
+
+ if not mtype2 isa MNullType then return
+
+ v.check_can_be_null(n_expr, mtype)
end
end
redef class ACallExpr
- redef fun property_name do return n_id.text
- redef fun property_node do return n_id
+ redef fun property_name do return n_qid.n_id.text
+ redef fun property_node do return n_qid
redef fun compute_raw_arguments do return n_args.to_a
end
redef class ACallAssignExpr
- redef fun property_name do return n_id.text + "="
- redef fun property_node do return n_id
+ redef fun property_name do return n_qid.n_id.text + "="
+ redef fun property_node do return n_qid
redef fun compute_raw_arguments
do
var res = n_args.to_a
var args = compute_raw_arguments
- callsite.check_signature(v, args)
+ callsite.check_signature(v, node, args)
var readtype = callsite.msignature.return_mtype
if readtype == null then
args = args.to_a # duplicate so raw_arguments keeps only the getter args
args.add(self.n_value)
- wcallsite.check_signature(v, args)
+ wcallsite.check_signature(v, node, args)
self.is_typed = true
end
end
redef class ACallReassignExpr
- redef fun property_name do return n_id.text
- redef fun property_node do return n_id
+ redef fun property_name do return n_qid.n_id.text
+ redef fun property_node do return n_qid.n_id
redef fun compute_raw_arguments do return n_args.to_a
end
var msignature = superprop.new_msignature or else superprop.msignature.as(not null)
msignature = v.resolve_for(msignature, recvtype, true).as(MSignature)
- var callsite = new CallSite(self, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
+ var callsite = new CallSite(hot_location, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
self.callsite = callsite
var args = self.n_args.to_a
if args.length > 0 then
- callsite.check_signature(v, args)
+ callsite.check_signature(v, self, args)
else
# Check there is at least enough parameters
if mpropdef.msignature.arity < msignature.arity then
var kind = recvtype.mclass.kind
var name: String
- var nid = self.n_id
+ var nqid = self.n_qid
var node: ANode
- if nid != null then
- name = nid.text
- node = nid
+ if nqid != null then
+ name = nqid.n_id.text
+ node = nqid
else
name = "new"
node = self.n_kwnew
end
var args = n_args.to_a
- callsite.check_signature(v, args)
+ callsite.check_signature(v, node, args)
end
end
var mtype = self.attr_type
v.visit_expr_subtype(self.n_value, mtype)
- self.is_typed = true
+ self.is_typed = mtype != null
end
end
var mtype = self.attr_type
if mtype == null then return # Skip error
- self.resolve_reassignment(v, mtype, mtype)
+ var rettype = self.resolve_reassignment(v, mtype, mtype)
- self.is_typed = true
+ self.is_typed = rettype != null
end
end