nitc/typing: document `merge_types`
[nit.git] / src / semantize / typing.nit
index ef8054a..dd98336 100644 (file)
@@ -102,9 +102,9 @@ 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 when `autocast` is true:
        # If the suptype is safe, then the return type is the one on the expression typed by `sub`.
@@ -117,7 +117,7 @@ private class TypeVisitor
                        #node.debug("Unsafe typing: expected {sup}, got {sub}")
                        return sup
                end
-               if sup isa MBottomType then return null # Skip error
+               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}`.")
@@ -600,6 +600,29 @@ private class TypeVisitor
                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
@@ -637,8 +660,7 @@ end
 class CallSite
        super MEntity
 
-       # The associated location of the callsite
-       var location: Location
+       redef var location: Location
 
        # The static type of the receiver (possibly unresolved)
        var recv: MType
@@ -678,6 +700,11 @@ class CallSite
                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
 end
 
 redef class Variable
@@ -881,6 +908,19 @@ redef class AExpr
        #
        # 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
@@ -1131,6 +1171,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
@@ -1480,14 +1521,14 @@ redef class AugmentedStringFormExpr
        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`
+       # 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
                if is_bytestring then
-                       to_bytes_with_copy = v.get_method(self, v.mmodule.native_string_type, "to_bytes_with_copy", false)
+                       to_bytes_with_copy = v.get_method(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.get_method(self, mclass.mclass_type, "to_re", false)
@@ -1666,11 +1707,15 @@ redef class AIsaExpr
 
                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(v, variable, mtype)
+
+                       # Do not adapt if there is no information gain (i.e. adapt to a supertype)
+                       if mtype == null or orig == null or not v.is_subtype(orig, mtype) then
+                               self.after_flow_context.when_true.set_var(v, variable, mtype)
+                       end
                end
 
                self.mtype = v.type_bool(self)
@@ -1680,6 +1725,16 @@ redef class AIsaExpr
        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
@@ -1845,6 +1900,15 @@ redef class ASendExpr
        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
@@ -2095,6 +2159,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
 
 ####
@@ -2175,6 +2252,15 @@ redef class ANewExpr
                var args = n_args.to_a
                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
 
 ####
@@ -2215,6 +2301,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