X-Git-Url: http://nitlanguage.org diff --git a/src/semantize/typing.nit b/src/semantize/typing.nit index e4fef02..b9acd5e 100644 --- a/src/semantize/typing.nit +++ b/src/semantize/typing.nit @@ -20,6 +20,7 @@ module typing 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]) @@ -35,55 +36,48 @@ private class TypeVisitor # The module of the analysis # Used to correctly query the model - var mmodule: MModule + var mmodule: MModule is noinit # The static type of the receiver # Mainly used for type tests and type resolutions - var anchor: nullable MClassType = null + var anchor: MClassType is noinit # The analyzed mclassdef - var mclassdef: nullable MClassDef = null + var mclassdef: MClassDef is noinit # The analyzed property - var mpropdef: nullable MPropDef + var mpropdef: MPropDef var selfvariable = new Variable("self") # Is `self` use restricted? # * no explicit `self` # * method called on the implicit self must be top-level + # Currently only used for `new` factory since there is no valid receiver inside var is_toplevel_context = false init do var mpropdef = self.mpropdef + var mclassdef = mpropdef.mclassdef + mmodule = mclassdef.mmodule + self.mclassdef = mclassdef + self.anchor = mclassdef.bound_mtype - if mpropdef != null then - self.mpropdef = mpropdef - var mclassdef = mpropdef.mclassdef - self.mclassdef = mclassdef - self.anchor = mclassdef.bound_mtype - - var mclass = mclassdef.mclass + var mclass = mclassdef.mclass - var selfvariable = new Variable("self") - self.selfvariable = selfvariable - selfvariable.declared_type = mclass.mclass_type + var selfvariable = new Variable("self") + self.selfvariable = selfvariable + selfvariable.declared_type = mclass.mclass_type - var mprop = mpropdef.mproperty - if mprop isa MMethod and (mprop.is_toplevel or mprop.is_new) then - is_toplevel_context = true - end + var mprop = mpropdef.mproperty + if mprop isa MMethod and mprop.is_new then + is_toplevel_context = true end end fun anchor_to(mtype: MType): MType do - var anchor = anchor - if anchor == null then - assert not mtype.need_anchor - return mtype - end return mtype.anchor_to(mmodule, anchor) end @@ -100,22 +94,28 @@ private class TypeVisitor end # Check that `sub` is a subtype of `sup`. - # 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 unsafe subtype (ie an implicit cast is required), then return `sup`. + # If `sub` is not a valid suptype, then display an error on `node` and return `null`. + # If `sub` is a safe subtype of `sup`, then return `sub`. + # If `sub` is an unsafe subtype (i.e., an implicit cast is required), then return `sup`. # - # The point of the return type is to determinate the usable type on an expression: + # The point of the return type is to determinate the usable type on an expression when `autocast` is true: # If the suptype is safe, then the return type is the one on the expression typed by `sub`. # Is the subtype is unsafe, then the return type is the one of an implicit cast on `sup`. - fun check_subtype(node: ANode, sub, sup: MType): nullable MType + fun check_subtype(node: ANode, sub, sup: MType, autocast: Bool): nullable MType do if self.is_subtype(sub, sup) then return sub - if self.is_subtype(sub, self.anchor_to(sup)) then + if autocast and self.is_subtype(sub, self.anchor_to(sup)) then # 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 - self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}") + if sup isa MErrorType 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}`.") + else + self.modelbuilder.error(node, "Type Error: expected `{sup}`, got `{sub}`.") + end return null end @@ -144,7 +144,11 @@ private class TypeVisitor end return null # forward error end - self.error(nexpr, "Type error: expected 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 @@ -160,7 +164,7 @@ private class TypeVisitor if sup == null then return null # Forward error - var res = check_subtype(nexpr, sub, sup) + var res = check_subtype(nexpr, sub, sup, true) if res != sub then nexpr.implicit_cast_to = res end @@ -177,23 +181,54 @@ private class TypeVisitor return self.visit_expr_subtype(nexpr, self.type_bool(nexpr)) 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 - self.modelbuilder.warning(node, "useless-type-test", "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, "useless-type-test", "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 + # Can `mtype` be null (up to the current knowledge)? + fun can_be_null(mtype: MType): Bool + do + if mtype isa MNullableType or mtype isa MNullType then return true + if mtype isa MFormalType then + var x = anchor_to(mtype) + if x isa MNullableType or x isa MNullType then return true + end + return false + end + + # Check that `mtype` can be null (up to the current knowledge). + # + # If not then display a `useless-null-test` warning on node and return false. + # 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 + var res = anchor_to(mtype) + modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}: {res}`.") + else + modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.") + end + return false + end + # Special verification on != and == for null # Return true fun null_test(anode: ABinopExpr) @@ -206,23 +241,27 @@ private class TypeVisitor 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 + if not can_be_null(mtype) then return + + if mtype isa MNullType then + # Because of type adaptation, we cannot just stop here + # so return use `null` as a bottom type that will be merged easily (cf) `merge_types` + mtype = null + else + mtype = mtype.as_notnull end # Check for type adaptation var variable = anode.n_expr.its_variable if variable == null then return + # One is null (mtype2 see above) the other is not null 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) + anode.after_flow_context.when_true.set_var(self, variable, mtype2) + anode.after_flow_context.when_false.set_var(self, variable, 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) + anode.after_flow_context.when_false.set_var(self, variable, mtype2) + anode.after_flow_context.when_true.set_var(self, variable, mtype) else abort end @@ -235,7 +274,7 @@ private class TypeVisitor fun resolve_mtype(node: AType): nullable MType do - return self.modelbuilder.resolve_mtype(mmodule, mclassdef, node) + return self.modelbuilder.resolve_mtype(mclassdef, node) end fun try_get_mclass(node: ANode, name: String): nullable MClass @@ -257,19 +296,21 @@ private class TypeVisitor return mclass.mclass_type end - fun get_method(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite + # Construction of a specific callsite according to the current context. + # Three entry points exist to create a callsite based on knowledge. + # The `build_callsite_by_name` is a top entry point, the method find the mpropdefs to call by the name of this. + # see `build_callsite_by_property` and `build_callsite_by_propdef` for more detail. + # If you already know the mpropdef to call use directly the `get_method_by_propdef` method + # If you just know the mproperty use the `build_callsite_by_property` method to display error if no `mpropdef` is found in the context + fun build_callsite_by_name(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite do var unsafe_type = self.anchor_to(recvtype) #debug("recv: {recvtype} (aka {unsafe_type})") if recvtype isa MNullType then - # `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 + var objclass = get_mclass(node, "Object") + if objclass == null then return null # Forward error + unsafe_type = objclass.mclass_type end var mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name) @@ -279,26 +320,49 @@ private class TypeVisitor 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 - self.modelbuilder.error(node, "Error: Method or variable '{name}' unknown in {recvtype}.") + self.modelbuilder.error(node, "Error: method or variable `{name}` unknown in `{recvtype}`.") + else if recvtype.need_anchor then + self.modelbuilder.error(node, "Error: method `{name}` does not exists in `{recvtype}: {unsafe_type}`.") else - self.modelbuilder.error(node, "Error: Method '{name}' doesn't exists in {recvtype}.") + self.modelbuilder.error(node, "Error: method `{name}` does not exists in `{recvtype}`.") end return null end assert mproperty isa MMethod + return build_callsite_by_property(node, recvtype, mproperty, recv_is_self) + end + + # The `build_callsite_by_property` finds the mpropdefs to call by the `MMethod`. + # If the mpropdef is found in the context it builds a new `Callsite`. + fun build_callsite_by_property(node: ANode, recvtype: MType, mproperty: MMethod, recv_is_self: Bool): nullable CallSite + do + var unsafe_type = self.anchor_to(recvtype) + + if recvtype isa MNullType then + var objclass = get_mclass(node, "Object") + if objclass == null then return null # Forward error + unsafe_type = objclass.mclass_type + end + # `null` only accepts some methods of object. + if recvtype isa MNullType and not mproperty.is_null_safe then + self.error(node, "Error: method `{mproperty.name}` called on `null`.") + return null + else if unsafe_type isa MNullableType and not mproperty.is_null_safe then + modelbuilder.advice(node, "call-on-nullable", "Warning: method call on a nullable receiver `{recvtype}`.") + end + if is_toplevel_context and recv_is_self and not mproperty.is_toplevel then - error(node, "Error: '{name}' is not a top-level method, thus need a receiver.") + error(node, "Error: `{mproperty.name}` is not a top-level method, thus need a receiver.") end if not recv_is_self and mproperty.is_toplevel then - error(node, "Error: cannot call '{name}', a top-level method, with a receiver.") + error(node, "Error: cannot call `{mproperty.name}`, a top-level method, with a receiver.") end if mproperty.visibility == protected_visibility and not recv_is_self and self.mmodule.visibility_for(mproperty.intro_mclassdef.mmodule) < intrude_visibility and not modelbuilder.toolcontext.opt_ignore_visibility.value then - self.modelbuilder.error(node, "Error: Method '{name}' is protected and can only acceded by self.") + self.modelbuilder.error(node, "Error: method `{mproperty.name}` is protected and can only accessed by `self`.") return null end @@ -306,25 +370,31 @@ private class TypeVisitor 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}") + self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: method `{mproperty.name}` is deprecated: {mdoc.content.first}") else - self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: Method '{name}' is deprecated.") + self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: method `{mproperty.name}` is deprecated.") end end var propdefs = mproperty.lookup_definitions(self.mmodule, unsafe_type) var mpropdef if propdefs.length == 0 then - self.modelbuilder.error(node, "Type error: no definition found for property {name} in {unsafe_type}") - return null + self.modelbuilder.error(node, "Type Error: no definition found for property `{mproperty.name}` in `{unsafe_type}`.") + abort + #return null else if propdefs.length == 1 then mpropdef = propdefs.first else - self.modelbuilder.warning(node, "property-conflict", "Warning: conflicting property definitions for property {name} in {unsafe_type}: {propdefs.join(" ")}") + self.modelbuilder.warning(node, "property-conflict", "Warning: conflicting property definitions for property `{mproperty.name}` in `{unsafe_type}`: {propdefs.join(" ")}") mpropdef = mproperty.intro end + return build_callsite_by_propdef(node, recvtype, mpropdef, recv_is_self) + end + # The `build_callsite_by_propdef` builds the callsite directly with the `mprodef` passed in argument. + fun build_callsite_by_propdef(node: ANode, recvtype: MType, mpropdef: MMethodDef, recv_is_self: Bool): nullable CallSite + do var msignature = mpropdef.new_msignature or else mpropdef.msignature if msignature == null then return null # skip error msignature = resolve_for(msignature, recvtype, recv_is_self).as(MSignature) @@ -332,7 +402,7 @@ private class TypeVisitor var erasure_cast = false var rettype = mpropdef.msignature.return_mtype if not recv_is_self and rettype != null then - rettype = rettype.as_notnullable + rettype = rettype.undecorate if rettype isa MParameterType then var erased_rettype = msignature.return_mtype assert erased_rettype != null @@ -341,78 +411,177 @@ private class TypeVisitor 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, mpropdef.mproperty, mpropdef, msignature, erasure_cast) return callsite end - fun try_get_method(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite + fun try_build_callsite_by_name(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) + return build_callsite_by_name(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 + fun check_signature(node: ANode, args: Array[AExpr], mproperty: MProperty, msignature: MSignature): nullable SignatureMap do var vararg_rank = msignature.vararg_rank if vararg_rank >= 0 then if args.length < msignature.arity then - #self.modelbuilder.error(node, "Error: Incorrect number of parameters. Got {args.length}, expected at least {msignature.arity}. Signature is {msignature}") - self.modelbuilder.error(node, "Error: arity mismatch; prototype is '{name}{msignature}'") - return false + modelbuilder.error(node, "Error: expected at least {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.") + return null end else if args.length != msignature.arity then - self.modelbuilder.error(node, "Error: Incorrect number of parameters. Got {args.length}, expected {msignature.arity}. Signature is {msignature}") - return false + # Too much argument + if args.length > msignature.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 + # Other cases are managed later end #debug("CALL {unsafe_type}.{msignature}") + # Associate each parameter to a position in the arguments + var map = new SignatureMap + + # 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 + var e = args[i] + if not e isa ANamedargExpr then continue + var name = e.n_id.text + var param = msignature.mparameter_by_name(name) + if param == null then + modelbuilder.error(e.n_id, "Error: no parameter `{name}` 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 + modelbuilder.error(e, "Error: parameter `{name}` already associated with argument #{prev} for `{mproperty}{msignature}`.") + return null + end + map.map[idx] = i + 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 for i in [0..msignature.arity[ do - var j = i - if i == vararg_rank then continue # skip the vararg - if i > vararg_rank then - j = i + vararg_decl + # Skip parameters associated by name + if map.map.has_key(i) then continue + + var param = msignature.mparameters[i] + + # Search the next free argument: skip named arguments since they are already associated + 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 + + if i == vararg_rank then + j += vararg_decl + continue # skip the vararg + end + + 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 - var paramtype = msignature.mparameters[i].mtype - self.visit_expr_subtype(args[j], paramtype) + return null end + + # Third, check varargs if vararg_rank >= 0 then var paramtype = msignature.mparameters[vararg_rank].mtype 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 + if vararg_decl == 0 then + if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null else - for j in [vararg_rank..vararg_rank+vararg_decl] do - self.visit_expr_subtype(args[j], paramtype) + first.vararg_decl = vararg_decl + 1 + for i in [vararg_rank..vararg_rank+vararg_decl] do + self.visit_expr_subtype(args[i], paramtype) end end 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 do + if not variable.is_adapted then return variable.declared_type + var flow = node.after_flow_context - if flow == null then - self.error(node, "No context!") - return null - end + if flow == null then return null # skip error if flow.vars.has_key(variable) then return flow.vars[variable] @@ -420,26 +589,64 @@ private class TypeVisitor #node.debug("*** START Collected for {variable}") var mtypes = flow.collect_types(variable) #node.debug("**** END Collected for {variable}") - if mtypes == null or mtypes.length == 0 then + if mtypes.length == 0 then return variable.declared_type else if mtypes.length == 1 then return mtypes.first else var res = merge_types(node,mtypes) - if res == null then res = variable.declared_type + if res == null then + res = variable.declared_type + # Try to fallback to a non-null version + if res != null and can_be_null(res) then do + for t in mtypes do + if t != null and can_be_null(t) then break label + end + res = res.as_notnull + end label + end return res end end end + # Some variables where type-adapted during the visit + var dirty = false + + # Some loops had been visited during the visit + var has_loop = false + fun set_variable(node: AExpr, variable: Variable, mtype: nullable MType) do var flow = node.after_flow_context assert flow != null - flow.set_var(variable, mtype) + flow.set_var(self, variable, mtype) end + # Find the exact representable most specific common super-type in `col`. + # + # Try to find the most specific common type that is a super-type of each types + # in `col`. + # In most cases, the result is simply the most general type in `col`. + # If nullables types are involved, then the nullable information is correctly preserved. + # If incomparable super-types exists in `col`, them no solution is given and the `null` + # value is returned (since union types are non representable in Nit) + # + # The `null` values in `col` are ignored, nulltypes (MNullType) are considered. + # + # Returns the `null` value if: + # + # * `col` is empty + # * `col` only have null values + # * there is a conflict + # + # Example (with a diamond A,B,C,D): + # + # * merge(A,B,C) -> A, because A is the most general type in {A,B,C} + # * merge(C,B) -> null, there is conflict, because `B or C` cannot be represented + # * merge(A,nullable B) -> nullable A, because A is the most general type and + # the nullable information is preserved fun merge_types(node: ANode, col: Array[nullable MType]): nullable MType do if col.length == 1 then return col.first @@ -448,7 +655,7 @@ private class TypeVisitor var found = true for t2 in col do if t2 == null then continue # return null - if t2 isa MNullableType or t2 isa MNullType then + if can_be_null(t2) and not can_be_null(t1) then t1 = t1.as_nullable end if not is_subtype(t2, t1) then found = false @@ -461,12 +668,99 @@ private class TypeVisitor #self.modelbuilder.warning(node, "Type Error: {col.length} conflicting types: <{col.join(", ")}>") return null end + + # Find a most general common subtype between `type1` and `type2`. + # + # Find the most general type that is a subtype of `type2` and, if possible, a subtype of `type1`. + # Basically, this return the most specific type between `type1` and `type2`. + # If nullable types are involved, the information is correctly preserved. + # If `type1` and `type2` are incomparable then `type2` is preferred (since intersection types + # are not representable in Nit). + # + # The `null` value is returned if both `type1` and `type2` are null. + # + # Examples (with diamond A,B,C,D): + # + # * intersect_types(A,B) -> B, because B is a subtype of A + # * intersect_types(B,A) -> B, because B is a subtype of A + # * intersect_types(B,C) -> C, B and C are incomparable, + # `type2` is then preferred (`B and C` cannot be represented) + # * intersect_types(nullable B,A) -> B, because B<:A and the non-null information is preserved + # * intersect_types(B,nullable C) -> C, `type2` is preferred and the non-null information is preserved + fun intersect_types(node: ANode, type1, type2: nullable MType): nullable MType + do + if type1 == null then return type2 + if type2 == null then return type1 + + if not can_be_null(type2) or not can_be_null(type1) then + type1 = type1.as_notnull + type2 = type2.as_notnull + end + + var res + if is_subtype(type1, type2) then + res = type1 + else + res = type2 + end + return res + end + + # Find a most general type that is a subtype of `type1` but not one of `type2`. + # + # Basically, this returns `type1`-`type2` but since there is no substraction type + # in Nit this just returns `type1` most of the case. + # + # The few other cases are if `type2` is a super-type and if some nullable information + # is present. + # + # The `null` value is returned if `type1` is null. + # + # Examples (with diamond A,B,C,D): + # + # * diff_types(A,B) -> A, because the notB cannot be represented + # * diff_types(B,A) -> None (absurd type), because B<:A + # * diff_types(nullable A, nullable B) -> A, because null is removed + # * diff_types(nullable B, A) -> Null, because anything but null is removed + fun diff_types(node: ANode, type1, type2: nullable MType): nullable MType + do + if type1 == null then return null + if type2 == null then return type1 + + # if t1 <: t2 then t1-t2 = bottom + if is_subtype(type1, type2) then + return modelbuilder.model.null_type.as_notnull + end + + # else if t1 <: nullable t2 then t1-t2 = nulltype + if is_subtype(type1, type2.as_nullable) then + return modelbuilder.model.null_type + end + + # else t2 can be null and type2 must accept null then null is excluded in t1 + if can_be_null(type1) and (type2 isa MNullableType or type2 isa MNullType) then + return type1.as_notnull + end + + return type1 + end +end + +# Mapping between parameters and arguments in a call. +# +# Parameters and arguments are not stored in the class but referenced by their position (starting from 0) +# +# The point of this class is to help engine and other things to map arguments in the AST to parameters of the model. +class SignatureMap + # Associate a parameter to an argument + var map = new ArrayMap[Int, Int] 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 # The static type of the receiver (possibly unresolved) var recv: MType @@ -495,58 +789,81 @@ class CallSite # Is a implicit cast required on erasure typing policy? var erasure_cast: Bool - private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool + # The mapping used on the call to associate arguments to parameters + # If null then no specific association is required. + var signaturemap: nullable SignatureMap = null + + private fun check_signature(v: TypeVisitor, node: ANode, args: Array[AExpr]): Bool do - return v.check_signature(self.node, args, self.mproperty.name, 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 + + # Information about the callsite to display on a node + fun dump_info(v: ASTDump): String do + return "{recv}.{mpropdef}{msignature}" end + + redef fun mdoc_or_fallback do return mproperty.intro.mdoc end redef class Variable # The declared type of the variable - var declared_type: nullable MType + var declared_type: nullable MType = null is writable + + # Was the variable type-adapted? + # This is used to speedup type retrieval while it remains `false` + private var is_adapted = false end redef class FlowContext # Store changes of types because of type evolution 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 variable - private fun set_var(variable: Variable, mtype: nullable MType) + private fun set_var(v: TypeVisitor, variable: Variable, mtype: nullable MType) do + if variable.declared_type == mtype and not variable.is_adapted then return + if vars.has_key(variable) and vars[variable] == mtype then return self.vars[variable] = mtype - self.cache.keys.remove(variable) + v.dirty = true + variable.is_adapted = true + #node.debug "set {variable} to {mtype or else "X"}" end - private fun collect_types(variable: Variable): nullable Array[nullable MType] + # Look in the flow and previous flow and collect all first reachable type adaptation of a local variable + private fun collect_types(variable: Variable): Array[nullable MType] do - if cache.has_key(variable) then - return cache[variable] - end - var res: nullable Array[nullable MType] = null - if vars.has_key(variable) then - var mtype = vars[variable] - res = [mtype] - else if self.previous.is_empty then - # Root flow - res = [variable.declared_type] - else - for flow in self.previous do - if flow.is_unreachable then continue - var r2 = flow.collect_types(variable) - if r2 == null then continue - if res == null then - res = r2.to_a - else - for t in r2 do - if not res.has(t) then res.add(t) - end + #node.debug "flow for {variable}" + var res = new Array[nullable MType] + + var todo = [self] + var seen = new HashSet[FlowContext] + while not todo.is_empty do + var f = todo.pop + if f.is_unreachable then continue + if seen.has(f) then continue + seen.add f + + if f.vars.has_key(variable) then + # Found something. Collect it and do not process further on this path + res.add f.vars[variable] + #f.node.debug "process {variable}: got {f.vars[variable] or else "X"}" + else + todo.add_all f.previous + todo.add_all f.loops + if f.previous.is_empty then + # Root flowcontext mean a parameter or something related + res.add variable.declared_type + #f.node.debug "root process {variable}: got {variable.declared_type or else "X"}" end end end - cache[variable] = res + #self.node.debug "##### end flow for {variable}: {res.join(" ")}" return res end end @@ -564,13 +881,10 @@ end 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 - var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef) + var v = new TypeVisitor(modelbuilder, mpropdef) self.selfvariable = v.selfvariable var mmethoddef = self.mpropdef.as(not null) @@ -587,29 +901,62 @@ redef class AMethPropdef assert variable != null variable.declared_type = mtype end - v.visit_stmt(nblock) + + 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, "Control error: Reached end of function (a 'return' with a value was expected).") + v.error(self, "Error: reached end of function; expected `return` with a value.") end 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) + var v = new TypeVisitor(modelbuilder, 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 @@ -617,7 +964,7 @@ redef class AAttrPropdef 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).") + v.error(self, "Error: reached end of block; expected `return`.") end end end @@ -654,6 +1001,28 @@ redef class AExpr # 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 + + redef fun dump_info(v) do + var res = super + var mtype = self.mtype + if mtype != null then + res += v.yellow(":{mtype}") + end + var ict = self.implicit_cast_to + if ict != null then + res += v.yellow("(.as({ict}))") + end + return res + end end redef class ABlockExpr @@ -702,7 +1071,9 @@ redef class AVardeclExpr var decltype = mtype if mtype == null or mtype isa MNullType then - decltype = v.get_mclass(self, "Object").mclass_type.as_nullable + var objclass = v.get_mclass(self, "Object") + if objclass == null then return # skip error + decltype = objclass.mclass_type.as_nullable if mtype == null then mtype = decltype end @@ -711,6 +1082,7 @@ redef class AVardeclExpr #debug("var {variable}: {mtype}") + self.mtype = mtype self.is_typed = true end end @@ -760,18 +1132,11 @@ redef class AReassignFormExpr # Return the static type of the value to store. private fun resolve_reassignment(v: TypeVisitor, readtype, writetype: MType): nullable MType do - var reassign_name: String - if self.n_assign_op isa APlusAssignOp then - reassign_name = "+" - else if self.n_assign_op isa AMinusAssignOp then - reassign_name = "-" - else - abort - end + var reassign_name = self.n_assign_op.operator self.read_type = readtype - var callsite = v.get_method(self, readtype, reassign_name, false) + var callsite = v.build_callsite_by_name(self.n_assign_op, readtype, reassign_name, false) if callsite == null then return null # Skip error self.reassign_callsite = callsite @@ -782,7 +1147,7 @@ redef class AReassignFormExpr var value_type = v.visit_expr_subtype(self.n_value, msignature.mparameters.first.mtype) if value_type == null then return null # Skip error - v.check_subtype(self, rettype, writetype) + v.check_subtype(self, rettype, writetype, false) return rettype end end @@ -805,11 +1170,10 @@ redef class AVarReassignExpr v.set_variable(self, variable, rettype) - self.is_typed = true + self.is_typed = rettype != null end end - redef class AContinueExpr redef fun accept_typing(v) do @@ -850,10 +1214,12 @@ redef class AReturnExpr v.visit_expr_subtype(nexpr, ret_type) else v.visit_expr(nexpr) - v.error(self, "Error: Return with value in a procedure.") + 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.") + v.error(self, "Error: `return` without value in a function.") + return end self.is_typed = true end @@ -896,7 +1262,7 @@ redef class AIfexprExpr var t = v.merge_types(self, [t1, t2]) if t == null then - v.error(self, "Type Error: ambiguous type {t1} vs {t2}") + v.error(self, "Type Error: ambiguous type `{t1}` vs `{t2}`.") end self.mtype = t end @@ -906,6 +1272,7 @@ redef class ADoExpr redef fun accept_typing(v) do v.visit_stmt(n_block) + v.visit_stmt(n_catch) self.is_typed = true end end @@ -913,8 +1280,8 @@ end redef class AWhileExpr redef fun accept_typing(v) do + v.has_loop = true v.visit_expr_bool(n_expr) - v.visit_stmt(n_block) self.is_typed = true end @@ -923,12 +1290,32 @@ end redef class ALoopExpr redef fun accept_typing(v) do + v.has_loop = true v.visit_stmt(n_block) self.is_typed = true 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 @@ -944,7 +1331,7 @@ redef class AForExpr private fun do_type_iterator(v: TypeVisitor, mtype: MType) do if mtype isa MNullType then - v.error(self, "Type error: 'for' cannot iterate over 'null'") + v.error(self, "Type Error: `for` cannot iterate over `null`.") return end @@ -953,9 +1340,9 @@ redef class AForExpr if objcla == null then return # check iterator method - var itdef = v.get_method(self, mtype, "iterator", n_expr isa ASelfExpr) + var itdef = v.build_callsite_by_name(self, mtype, "iterator", n_expr isa ASelfExpr) if itdef == null then - v.error(self, "Type Error: 'for' expects a type providing 'iterator' method, got '{mtype}'.") + v.error(self, "Type Error: `for` expects a type providing an `iterator` method, got `{mtype}`.") return end self.method_iterator = itdef @@ -963,7 +1350,7 @@ redef class AForExpr # check that iterator return something var ittype = itdef.msignature.return_mtype if ittype == null then - v.error(self, "Type Error: 'for' expects method 'iterator' to return an 'Iterator' or 'MapIterator' type'.") + v.error(self, "Type Error: `for` expects the method `iterator` to return an `Iterator` or `MapIterator` type.") return end @@ -978,7 +1365,7 @@ redef class AForExpr var coltype = ittype.supertype_to(v.mmodule, v.anchor, colit_cla) var variables = self.variables if variables.length != 1 then - v.error(self, "Type Error: 'for' expects only one variable when using 'Iterator'.") + v.error(self, "Type Error: `for` expects only one variable when using `Iterator`.") else variables.first.declared_type = coltype.arguments.first end @@ -990,7 +1377,7 @@ redef class AForExpr var coltype = ittype.supertype_to(v.mmodule, v.anchor, mapit_cla) var variables = self.variables if variables.length != 2 then - v.error(self, "Type Error: 'for' expects two variables when using 'MapIterator'.") + v.error(self, "Type Error: `for` expects two variables when using `MapIterator`.") else variables[0].declared_type = coltype.arguments[0] variables[1].declared_type = coltype.arguments[1] @@ -999,44 +1386,44 @@ redef class AForExpr end if not is_col and not is_map then - v.error(self, "Type Error: 'for' expects method 'iterator' to return an 'Iterator' or 'MapIterator' type'.") + v.error(self, "Type Error: `for` expects the method `iterator` to return an `Iterator` or `MapIterator` type.") return end # anchor formal and virtual types if mtype.need_anchor then mtype = v.anchor_to(mtype) - mtype = mtype.as_notnullable + mtype = mtype.undecorate self.coltype = mtype.as(MClassType) # get methods is_ok, next, item - var ikdef = v.get_method(self, ittype, "is_ok", false) + var ikdef = v.build_callsite_by_name(self, ittype, "is_ok", false) if ikdef == null then - v.error(self, "Type Error: 'for' expects a method 'is_ok' in 'Iterator' type {ittype}.") + v.error(self, "Type Error: `for` expects a method `is_ok` in type `{ittype}`.") return end self.method_is_ok = ikdef - var itemdef = v.get_method(self, ittype, "item", false) + var itemdef = v.build_callsite_by_name(self, ittype, "item", false) if itemdef == null then - v.error(self, "Type Error: 'for' expects a method 'item' in 'Iterator' type {ittype}.") + v.error(self, "Type Error: `for` expects a method `item` in type `{ittype}`.") return end self.method_item = itemdef - var nextdef = v.get_method(self, ittype, "next", false) + var nextdef = v.build_callsite_by_name(self, ittype, "next", false) if nextdef == null then - v.error(self, "Type Error: 'for' expects a method 'next' in 'Iterator' type {ittype}.") + v.error(self, "Type Error: `for` expects a method `next` in type {ittype}.") return end self.method_next = nextdef - self.method_finish = v.try_get_method(self, ittype, "finish", false) + self.method_finish = v.try_build_callsite_by_name(self, ittype, "finish", false) if is_map then - var keydef = v.get_method(self, ittype, "key", false) + var keydef = v.build_callsite_by_name(self, ittype, "key", false) if keydef == null then - v.error(self, "Type Error: 'for' expects a method 'key' in 'Iterator' type {ittype}.") + v.error(self, "Type Error: `for` expects a method `key` in type `{ittype}`.") return end self.method_key = keydef @@ -1047,21 +1434,27 @@ redef class AForExpr var vtype = variable.declared_type.as(not null) if n_expr isa AOrangeExpr then - self.method_lt = v.get_method(self, vtype, "<", false) + self.method_lt = v.build_callsite_by_name(self, vtype, "<", false) else - self.method_lt = v.get_method(self, vtype, "<=", false) + self.method_lt = v.build_callsite_by_name(self, vtype, "<=", false) end - self.method_successor = v.get_method(self, vtype, "successor", false) + self.method_successor = v.build_callsite_by_name(self, vtype, "successor", false) end end +end - redef fun accept_typing(v) +redef class AWithExpr + var method_start: nullable CallSite + var method_finish: nullable CallSite + + redef fun accept_typing(v: TypeVisitor) do var mtype = v.visit_expr(n_expr) if mtype == null then return - self.do_type_iterator(v, mtype) + method_start = v.build_callsite_by_name(self, mtype, "start", n_expr isa ASelfExpr) + method_finish = v.build_callsite_by_name(self, mtype, "finish", n_expr isa ASelfExpr) v.visit_stmt(n_block) self.mtype = n_block.mtype @@ -1106,7 +1499,6 @@ redef class AAndExpr end end - redef class ANotExpr redef fun accept_typing(v) do @@ -1125,18 +1517,35 @@ redef class AOrElseExpr return # Skip error end - t1 = t1.as_notnullable + if t1 isa MNullType then + self.mtype = t2 + return + else if v.can_be_null(t1) then + t1 = t1.as_notnull + end var t = v.merge_types(self, [t1, t2]) if t == null then - t = v.mmodule.object_type - if t2 isa MNullableType then + var c = v.get_mclass(self, "Object") + if c == null then return # forward error + t = c.mclass_type + if v.can_be_null(t2) then t = t.as_nullable end #v.error(self, "Type Error: ambiguous type {t1} vs {t2}") end self.mtype = t end + + redef fun accept_post_typing(v) + do + var t1 = n_expr.mtype + if t1 == null then + return + else + v.check_can_be_null(n_expr, t1) + end + end end redef class ATrueExpr @@ -1153,10 +1562,25 @@ redef class AFalseExpr end end -redef class AIntExpr +redef class AIntegerExpr redef fun accept_typing(v) do - var mclass = v.get_mclass(self, "Int") + 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 @@ -1172,31 +1596,76 @@ redef class AFloatExpr 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_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 + # CString::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.build_callsite_by_name(self, v.mmodule.c_string_type, "to_bytes_with_copy", false) + mclass = v.get_mclass(self, "Bytes") + else if is_re then + to_re = v.build_callsite_by_name(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.build_callsite_by_name(self, mclass.mclass_type, service, false) + else if i == 'm' then + service = "newline=" + newline = v.build_callsite_by_name(self, mclass.mclass_type, service, false) + else if i == 'b' then + service = "extended=" + extended = v.build_callsite_by_name(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 for nexpr in self.n_exprs do - v.visit_expr_subtype(nexpr, v.mmodule.object_type) + v.visit_expr_subtype(nexpr, objtype) end end end @@ -1244,13 +1713,15 @@ redef class AArrayExpr end set_comprehension(e) if mtype != null then - if v.check_subtype(e, t, mtype) == null then return # Skip error + if v.check_subtype(e, t, mtype, false) == null then return # Forward error if t == mtype then useless = true else mtypes.add(t) end end if mtype == null then + # Ensure monotony for type adaptation on loops + if self.element_mtype != null then mtypes.add self.element_mtype mtype = v.merge_types(self, mtypes) end if mtype == null or mtype isa MNullType then @@ -1268,8 +1739,8 @@ redef class AArrayExpr if mclass == null then return # Forward error var array_mtype = mclass.get_mtype([mtype]) - with_capacity_callsite = v.get_method(self, array_mtype, "with_capacity", false) - push_callsite = v.get_method(self, array_mtype, "push", false) + with_capacity_callsite = v.build_callsite_by_name(self, array_mtype, "with_capacity", false) + push_callsite = v.build_callsite_by_name(self, array_mtype, "push", false) self.mtype = array_mtype end @@ -1294,7 +1765,7 @@ redef class ARangeExpr else if v.is_subtype(t2, t1) then mtype = mclass.get_mtype([t1]) else - v.error(self, "Type Error: Cannot create range: {t1} vs {t2}") + v.error(self, "Type Error: cannot create range: `{t1}` vs `{t2}`.") return end @@ -1303,9 +1774,9 @@ redef class ARangeExpr # get the constructor var callsite if self isa ACrangeExpr then - callsite = v.get_method(self, mtype, "init", false) + callsite = v.build_callsite_by_name(self, mtype, "init", false) else if self isa AOrangeExpr then - callsite = v.get_method(self, mtype, "without_last", false) + callsite = v.build_callsite_by_name(self, mtype, "without_last", false) else abort end @@ -1326,26 +1797,62 @@ redef class AIsaExpr 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 if variable != null then - #var orig = self.n_expr.mtype + 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) + + var thentype = v.intersect_types(self, orig, mtype) + if thentype != orig then + self.after_flow_context.when_true.set_var(v, variable, thentype) + #debug "{variable}:{orig or else "?"} isa {mtype or else "?"} -> then {thentype or else "?"}" + end + + var elsetype = v.diff_types(self, orig, mtype) + if elsetype != orig then + self.after_flow_context.when_false.set_var(v, variable, elsetype) + #debug "{variable}:{orig or else "?"} isa {mtype or else "?"} -> else {elsetype or else "?"}" + end end 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 + + redef fun dump_info(v) do + var res = super + var mtype = self.cast_type + if mtype != null then + res += v.yellow(".as({mtype})") + end + return res + 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 @@ -1356,25 +1863,22 @@ redef class AAsNotnullExpr if mtype == null then return # Forward error if mtype isa MNullType then - v.error(self, "Type error: as(not null) on null") + v.error(self, "Type Error: `as(not null)` on `null`.") return end - if mtype isa MNullableType then - self.mtype = mtype.mtype - return + + if v.can_be_null(mtype) then + mtype = mtype.as_notnull end + self.mtype = mtype + end - if mtype isa MClassType then - 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, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}: {u}`.") - return - 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 @@ -1397,7 +1901,7 @@ redef class ASelfExpr redef fun accept_typing(v) do if v.is_toplevel_context and not self isa AImplicitSelfExpr then - v.error(self, "Error: self cannot be used in top-level method.") + v.error(self, "Error: `self` cannot be used in top-level method.") end var variable = v.selfvariable self.its_variable = variable @@ -1405,40 +1909,101 @@ redef class ASelfExpr end end +redef class AImplicitSelfExpr + # Is the implicit receiver `sys`? + # + # By default, the implicit receiver is `self`. + # But when there is not method for `self`, `sys` is used as a fall-back. + # Is this case this flag is set to `true`. + var is_sys = false +end + ## MESSAGE SENDING AND PROPERTY redef class ASendExpr # The property invoked by the send. var callsite: nullable CallSite + # Is self a safe call (with `x?.foo`)? + # If so and the receiver is null, then the arguments won't be evaluated + # and the call skipped (replaced with null). + var is_safe: Bool = false + + 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 recvtype = v.visit_expr(self.n_expr) + var nrecv = self.n_expr + var recvtype = v.visit_expr(nrecv) + + if nrecv isa ASafeExpr then + # Has the receiver the form `x?.foo`? + # For parsing "reasons" the `?` is in the receiver node, not the call node. + is_safe = true + end + var name = self.property_name + var node = self.property_node if recvtype == null then return # Forward error - var callsite = v.get_method(self, recvtype, name, self.n_expr isa ASelfExpr) - if callsite == null then return + var callsite = null + var unsafe_type = v.anchor_to(recvtype) + var mproperty = v.try_get_mproperty_by_name2(node, unsafe_type, name) + if mproperty == null and nrecv isa AImplicitSelfExpr then + # Special fall-back search in `sys` when noting found in the implicit receiver. + var sysclass = v.try_get_mclass(node, "Sys") + if sysclass != null then + var systype = sysclass.mclass_type + mproperty = v.try_get_mproperty_by_name2(node, systype, name) + if mproperty != null then + callsite = v.build_callsite_by_name(node, systype, name, false) + if callsite == null then return # Forward error + # Update information, we are looking at `sys` now, not `self` + nrecv.is_sys = true + nrecv.its_variable = null + nrecv.mtype = systype + recvtype = systype + end + end + end + if callsite == null then + # If still nothing, just exit + callsite = v.build_callsite_by_name(node, recvtype, name, nrecv isa ASelfExpr) + if callsite == null then return + end + self.callsite = callsite var msignature = callsite.msignature var args = compute_raw_arguments - callsite.check_signature(v, args) + if not self isa ACallrefExpr then + callsite.check_signature(v, node, args) + end if callsite.mproperty.is_init then var vmpropdef = v.mpropdef if not (vmpropdef isa MMethodDef and vmpropdef.mproperty.is_init) then - v.error(self, "Can call a init only in another init") + v.error(node, "Error: an `init` can only be called from another `init`.") end if vmpropdef isa MMethodDef and vmpropdef.mproperty.is_root_init and not callsite.mproperty.is_root_init then - v.error(self, "Error: {vmpropdef} cannot call a factory {callsite.mproperty}") + v.error(node, "Error: `{vmpropdef}` cannot call a factory `{callsite.mproperty}`.") end end var ret = msignature.return_mtype if ret != null then + if is_safe then + # A safe receiver makes that the call is not executed and returns null + ret = ret.as_nullable + end self.mtype = ret else self.is_typed = true @@ -1449,84 +2014,70 @@ redef class ASendExpr # Each subclass simply provide the correct name. private fun property_name: String is abstract + # The node identifying the name (id, operator, etc) for messages. + # + # Is `self` by default + private fun property_node: ANode do return self + # An array of all arguments (excluding self) fun raw_arguments: Array[AExpr] do return compute_raw_arguments private fun compute_raw_arguments: Array[AExpr] is abstract + + redef fun dump_info(v) do + var res = super + var callsite = self.callsite + if callsite != null then + res += v.yellow(" call="+callsite.dump_info(v)) + end + return res + end end redef class ABinopExpr redef fun compute_raw_arguments do return [n_expr2] + redef fun property_name do return operator + redef fun property_node do return n_op end -redef class AEqExpr - redef fun property_name do return "==" + +redef class AEqFormExpr redef fun accept_typing(v) do super v.null_test(self) end -end -redef class ANeExpr - redef fun property_name do return "!=" - 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 mtype == v.type_bool(self) and (n_expr2 isa AFalseExpr or n_expr2 isa ATrueExpr) then + v.modelbuilder.warning(self, "useless-truism", "Warning: useless comparison to a Bool literal.") + end + + if not mtype2 isa MNullType then return + + v.check_can_be_null(n_expr, mtype) end end -redef class ALtExpr - redef fun property_name do return "<" -end -redef class ALeExpr - redef fun property_name do return "<=" -end -redef class ALlExpr - redef fun property_name do return "<<" -end -redef class AGtExpr - redef fun property_name do return ">" -end -redef class AGeExpr - redef fun property_name do return ">=" -end -redef class AGgExpr - redef fun property_name do return ">>" -end -redef class APlusExpr - redef fun property_name do return "+" -end -redef class AMinusExpr - redef fun property_name do return "-" -end -redef class AStarshipExpr - redef fun property_name do return "<=>" -end -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 -redef class APercentExpr - redef fun property_name do return "%" -end -redef class AUminusExpr - redef fun property_name do return "unary -" +redef class AUnaryopExpr + redef fun property_name do return "unary {operator}" redef fun compute_raw_arguments do return new Array[AExpr] end - redef class ACallExpr - redef fun property_name do return n_id.text + 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_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 @@ -1558,26 +2109,27 @@ redef class ASendReassignFormExpr do var recvtype = v.visit_expr(self.n_expr) var name = self.property_name + var node = self.property_node if recvtype == null then return # Forward error var for_self = self.n_expr isa ASelfExpr - var callsite = v.get_method(self, recvtype, name, for_self) + var callsite = v.build_callsite_by_name(node, recvtype, name, for_self) if callsite == null then return self.callsite = callsite 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 - v.error(self, "Error: {name} is not a function") + v.error(node, "Error: `{name}` is not a function.") return end - var wcallsite = v.get_method(self, recvtype, name + "=", self.n_expr isa ASelfExpr) + var wcallsite = v.build_callsite_by_name(node, recvtype, name + "=", self.n_expr isa ASelfExpr) if wcallsite == null then return self.write_callsite = wcallsite @@ -1586,14 +2138,15 @@ redef class ASendReassignFormExpr 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_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 @@ -1604,9 +2157,81 @@ end redef class AInitExpr redef fun property_name do return "init" + redef fun property_node do return n_kwinit redef fun compute_raw_arguments do return n_args.to_a end +redef class ACallrefExpr + 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 + + redef fun accept_typing(v) + do + super # do the job as if it was a real call + var res = callsite.mproperty + + var msignature = callsite.mpropdef.msignature + var recv = callsite.recv + assert msignature != null + var arity = msignature.mparameters.length + + var routine_type_name = "ProcRef" + if msignature.return_mtype != null then + routine_type_name = "FunRef" + end + + var target_routine_class = "{routine_type_name}{arity}" + var routine_mclass = v.get_mclass(self, target_routine_class) + + if routine_mclass == null then + v.error(self, "Error: missing functional types, try `import functional`") + return + end + + var types_list = new Array[MType] + for param in msignature.mparameters do + if param.is_vararg then + types_list.push(v.mmodule.array_type(param.mtype)) + else + types_list.push(param.mtype) + end + end + if msignature.return_mtype != null then + types_list.push(msignature.return_mtype.as(not null)) + end + + # Why we need an anchor : + # + # ~~~~nitish + # class A[E] + # def toto(x: E) do print "{x}" + # end + # + # var a = new A[Int] + # var f = &a.toto # without anchor : ProcRef1[E] + # # with anchor : ProcRef[Int] + # ~~~~ + # However, we can only anchor if we can resolve every formal + # parameter, here's an example where we can't. + # ~~~~nitish + # class A[E] + # fun bar: A[E] do return self + # fun foo: Fun0[A[E]] do return &bar # here we can't anchor + # end + # var f1 = a1.foo # when this expression will be evaluated, + # # `a1` will anchor `&bar` returned by `foo`. + # print f1.call + # ~~~~ + var routine_type = routine_mclass.get_mtype(types_list) + if not recv.need_anchor then + routine_type = routine_type.anchor_to(v.mmodule, recv.as(MClassType)) + end + is_typed = true + self.mtype = routine_type + end +end + redef class AExprs fun to_a: Array[AExpr] do return self.n_exprs.to_a end @@ -1625,12 +2250,11 @@ redef class ASuperExpr redef fun accept_typing(v) do 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") + v.error(self, "Error: `super` only usable in a `method`.") return end var superprops = mproperty.lookup_super_definitions(v.mmodule, anchor) @@ -1639,7 +2263,7 @@ redef class ASuperExpr process_superinit(v) return end - v.error(self, "Error: No super method to call for {mproperty}.") + v.error(self, "Error: no super method to call for `{mproperty}`.") return end # FIXME: covariance of return type in linear extension? @@ -1649,7 +2273,7 @@ redef class ASuperExpr msignature = v.resolve_for(msignature, recvtype, true).as(MSignature) var args = self.n_args.to_a if args.length > 0 then - v.check_signature(self, args, mproperty.name, msignature) + signaturemap = v.check_signature(self, args, mproperty, msignature) end self.mtype = msignature.return_mtype self.is_typed = true @@ -1657,10 +2281,13 @@ redef class ASuperExpr mpropdef = v.mpropdef.as(MMethodDef) end + # The mapping used on the call to associate arguments to parameters. + # If null then no specific association is required. + var signaturemap: nullable SignatureMap + private fun process_superinit(v: TypeVisitor) do var anchor = v.anchor - assert anchor != null var recvtype = v.get_variable(self, v.selfvariable) assert recvtype != null var mpropdef = v.mpropdef @@ -1679,7 +2306,7 @@ redef class ASuperExpr continue end if superprop != null and superprop.mproperty != candidate and not superprop.mproperty.is_root_init then - v.error(self, "Error: conflicting super constructor to call for {mproperty}: {candidate.full_name}, {superprop.mproperty.full_name}") + 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, anchor) @@ -1688,29 +2315,29 @@ redef class ASuperExpr candidatedefs.add(superprop) end if candidatedefs.length > 1 then - v.error(self, "Error: conflicting 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 end if superprop == null then - v.error(self, "Error: No super method to call for {mproperty}.") + v.error(self, "Error: no super method to call for `{mproperty}`.") return 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 - v.error(self, "Error: Not enough implicit arguments to pass. Got {mpropdef.msignature.arity}, expected at least {msignature.arity}. Signature is {msignature}") + v.error(self, "Error: not enough implicit arguments to pass. Got `{mpropdef.msignature.arity}`, expected at least `{msignature.arity}`. Signature is `{msignature}`.") return end # Check that each needed parameter is conform @@ -1718,7 +2345,7 @@ redef class ASuperExpr for sp in msignature.mparameters do var p = mpropdef.msignature.mparameters[i] if not v.is_subtype(p.mtype, sp.mtype) then - v.error(self, "Type error: expected argument #{i} of type {sp.mtype}, got implicit argument {p.name} of type {p.mtype}. Signature is {msignature}") + v.error(self, "Type Error: expected argument #{i} of type `{sp.mtype}`, got implicit argument `{p.name}` of type `{p.mtype}`. Signature is {msignature}") return end i += 1 @@ -1727,6 +2354,19 @@ redef class ASuperExpr self.is_typed = true end + + redef fun dump_info(v) do + var res = super + var callsite = self.callsite + if callsite != null then + res += v.yellow(" super-init="+callsite.dump_info(v)) + end + var mpropdef = self.mpropdef + if mpropdef != null then + res += v.yellow(" call-next-method="+mpropdef.to_s) + end + return res + end end #### @@ -1745,30 +2385,50 @@ redef class ANewExpr if not recvtype isa MClassType then if recvtype isa MNullableType then - v.error(self, "Type error: cannot instantiate the nullable type {recvtype}.") + v.error(self, "Type Error: cannot instantiate the nullable type `{recvtype}`.") + return + else if recvtype isa MFormalType then + v.error(self, "Type Error: cannot instantiate the formal type `{recvtype}`.") return else - v.error(self, "Type error: cannot instantiate the formal type {recvtype}.") + v.error(self, "Type Error: cannot instantiate the type `{recvtype}`.") return end end self.recvtype = recvtype + var kind = recvtype.mclass.kind var name: String - var nid = self.n_id - if nid != null then - name = nid.text + var nqid = self.n_qid + var node: ANode + if nqid != null then + name = nqid.n_id.text + node = nqid else name = "new" + node = self.n_kwnew + end + if name == "intern" then + if kind != concrete_kind then + v.error(self, "Type Error: cannot instantiate {kind} {recvtype}.") + return + end + if n_args.n_exprs.not_empty then + v.error(n_args, "Type Error: the intern constructor expects no arguments.") + return + end + # Our job is done + self.mtype = recvtype + return end - var callsite = v.get_method(self, recvtype, name, false) + + var callsite = v.build_callsite_by_name(node, 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}.") + v.error(self, "Type Error: cannot instantiate {kind} `{recvtype}`.") return end self.mtype = recvtype @@ -1780,39 +2440,49 @@ redef class ANewExpr self.callsite = callsite if not callsite.mproperty.is_init_for(recvtype.mclass) then - v.error(self, "Error: {name} is not a constructor.") + v.error(self, "Error: `{name}` is not a constructor.") return end var args = n_args.to_a - callsite.check_signature(v, args) + callsite.check_signature(v, node, args) + end + + redef fun dump_info(v) do + var res = super + var callsite = self.callsite + if callsite != null then + res += v.yellow(" call="+callsite.dump_info(v)) + end + return res end end #### redef class AAttrFormExpr - # The attribute acceded. + # The attribute accessed. var mproperty: nullable MAttribute # The static type of the attribute. var attr_type: nullable MType - # Resolve the attribute acceded. + # Resolve the attribute accessed. private fun resolve_property(v: TypeVisitor) do var recvtype = v.visit_expr(self.n_expr) if recvtype == null then return # Skip error - var name = self.n_id.text + var node = self.n_id + var name = node.text if recvtype isa MNullType then - v.error(self, "Error: Attribute '{name}' access on 'null'.") + v.error(node, "Error: attribute `{name}` access on `null`.") return end var unsafe_type = v.anchor_to(recvtype) - var mproperty = v.try_get_mproperty_by_name2(self, unsafe_type, name) + var mproperty = v.try_get_mproperty_by_name2(node, unsafe_type, name) if mproperty == null then - v.modelbuilder.error(self, "Error: Attribute {name} doesn't exists in {recvtype}.") + v.modelbuilder.error(node, "Error: attribute `{name}` does not exist in `{recvtype}`.") return end assert mproperty isa MAttribute @@ -1826,6 +2496,16 @@ redef class AAttrFormExpr attr_type = v.resolve_for(attr_type, recvtype, self.n_expr isa ASelfExpr) self.attr_type = attr_type end + + redef fun dump_info(v) do + var res = super + var mproperty = self.mproperty + var attr_type = self.attr_type + if mproperty != null then + res += v.yellow(" attr={mproperty}:{attr_type or else "BROKEN"}") + end + return res + end end redef class AAttrExpr @@ -1836,7 +2516,6 @@ redef class AAttrExpr end end - redef class AAttrAssignExpr redef fun accept_typing(v) do @@ -1844,7 +2523,7 @@ redef class AAttrAssignExpr var mtype = self.attr_type v.visit_expr_subtype(self.n_value, mtype) - self.is_typed = true + self.is_typed = mtype != null end end @@ -1855,9 +2534,9 @@ redef class AAttrReassignExpr 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 @@ -1871,19 +2550,41 @@ redef class AIssetAttrExpr var recvtype = self.n_expr.mtype.as(not null) var bound = v.resolve_for(mtype, recvtype, false) if bound isa MNullableType then - v.error(self, "Error: isset on a nullable attribute.") + v.error(n_id, "Type Error: `isset` on a nullable attribute.") end self.mtype = v.type_bool(self) end end +redef class ASafeExpr + redef fun accept_typing(v) + do + var mtype = v.visit_expr(n_expr) + if mtype == null then return # Skip error + + if mtype isa MNullType then + # While `null?.foo` is semantically well defined and should not execute `foo` and just return `null`, + # currently `null.foo` is forbidden so it seems coherent to also forbid `null?.foo` + v.modelbuilder.error(self, "Error: safe operator `?` on `null`.") + return + end + + self.mtype = mtype.as_notnull + + if not v.can_be_null(mtype) then + v.modelbuilder.warning(self, "useless-safe", "Warning: useless safe operator `?` on non-nullable value.") + return + end + 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 `...`") + v.error(self, "Syntax Error: unexpected `...`.") end end