typing and engines: handle safe calls `x?.foo`
[nit.git] / src / semantize / typing.nit
index fc89e5f..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,23 +59,20 @@ 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
 
@@ -278,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
@@ -740,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
@@ -791,7 +788,7 @@ 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`
@@ -864,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)
@@ -931,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
@@ -1580,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")
@@ -1908,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
@@ -1920,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
 
@@ -1970,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
@@ -2020,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)
@@ -2142,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
@@ -2181,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
@@ -2451,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