typing and engines: handle safe calls `x?.foo`
[nit.git] / src / semantize / typing.nit
index 7b87faf..3f973fd 100644 (file)
@@ -36,17 +36,17 @@ 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")
 
@@ -59,33 +59,25 @@ private class TypeVisitor
        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_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
 
@@ -283,7 +275,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
@@ -580,7 +572,16 @@ private class TypeVisitor
                                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
@@ -600,6 +601,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
@@ -621,6 +645,82 @@ 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.
@@ -637,7 +737,7 @@ end
 class CallSite
        super MEntity
 
-       redef var location: Location
+       redef var location
 
        # The static type of the receiver (possibly unresolved)
        var recv: MType
@@ -677,11 +777,18 @@ 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
+
+       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 is writable
+       var declared_type: nullable MType = null is writable
 
        # Was the variable type-adapted?
        # This is used to speedup type retrieval while it remains `false`
@@ -754,7 +861,7 @@ redef class AMethPropdef
                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)
@@ -821,7 +928,7 @@ redef class AAttrPropdef
                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
@@ -880,6 +987,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
@@ -1457,9 +1577,7 @@ end
 redef class ACharExpr
        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
+               if is_code_point then
                        mclass = v.get_mclass(self, "Int")
                else
                        mclass = v.get_mclass(self, "Char")
@@ -1671,9 +1789,16 @@ redef class AIsaExpr
                        #var to = if mtype != null then mtype.to_s else "invalid"
                        #debug("adapt {variable}: {from} -> {to}")
 
-                       # 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)
+                       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
 
@@ -1684,6 +1809,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
@@ -1768,6 +1903,11 @@ 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
@@ -1780,6 +1920,13 @@ redef class ASendExpr
        do
                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
 
@@ -1830,6 +1977,10 @@ redef class ASendExpr
 
                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
@@ -1849,6 +2000,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
@@ -1871,6 +2031,10 @@ redef class AEqFormExpr
 
                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)
@@ -1993,7 +2157,6 @@ 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
@@ -2032,7 +2195,6 @@ redef class ASuperExpr
        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
@@ -2099,6 +2261,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
 
 ####
@@ -2179,6 +2354,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
 
 ####
@@ -2219,6 +2403,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
@@ -2270,6 +2464,28 @@ redef class AIssetAttrExpr
        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