typing and engines: handle safe calls `x?.foo`
[nit.git] / src / semantize / typing.nit
index 1555050..3f973fd 100644 (file)
@@ -36,7 +36,7 @@ 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
@@ -60,6 +60,7 @@ private class TypeVisitor
        do
                var mpropdef = self.mpropdef
                var mclassdef = mpropdef.mclassdef
+               mmodule = mclassdef.mmodule
                self.mclassdef = mclassdef
                self.anchor = mclassdef.bound_mtype
 
@@ -274,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
@@ -860,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)
@@ -927,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
@@ -1576,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")
@@ -1904,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
@@ -1916,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
 
@@ -1966,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
@@ -2016,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)
@@ -2445,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