Merge: Warn call on nullable receiver
authorJean Privat <jean@pryen.org>
Sat, 30 May 2015 00:37:22 +0000 (20:37 -0400)
committerJean Privat <jean@pryen.org>
Sat, 30 May 2015 00:37:22 +0000 (20:37 -0400)
Followup of #1375 and #394

This introduce an advice on calls on a nullable receiver.
Since the conversion could be complex, I propose a light approach with advices (quiet warnings) instead of hard errors, so that:

1. jenkins can track them
2. nitpick (thus vim) can show them
3. mirgration can be done in an incremental and iterative way
4. issues with a strict call-on-nullable policy can be identified without breaking things
5. @Morriar can bank nitcoins in future PR

If (when?) all call on nullable can be removed, then the advice will become an error.

Pull-Request: #1414
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Romain Chanoir <chanoir.romain@courrier.uqam.ca>

1  2 
src/model/model.nit
src/semantize/typing.nit

diff --combined src/model/model.nit
@@@ -206,9 -206,6 +206,9 @@@ redef class MModul
        # The primitive type `Int`
        var int_type: MClassType = self.get_primitive_class("Int").mclass_type is lazy
  
 +      # The primitive type `Byte`
 +      var byte_type: MClassType = self.get_primitive_class("Byte").mclass_type is lazy
 +
        # The primitive type `Char`
        var char_type: MClassType = self.get_primitive_class("Char").mclass_type is lazy
  
@@@ -2156,6 -2153,10 +2156,10 @@@ class MMetho
        do
                return self.is_init
        end
+       # A specific method that is safe to call on null.
+       # Currently, only `==`, `!=` and `is_same_instance` are safe
+       fun is_null_safe: Bool do return name == "==" or name == "!=" or name == "is_same_instance"
  end
  
  # A global attribute
diff --combined src/semantize/typing.nit
@@@ -301,15 -301,9 +301,9 @@@ private class TypeVisito
  
                #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
-                               var objclass = get_mclass(node, "Object")
-                               if objclass == null then return null # Forward error
-                               unsafe_type = objclass.mclass_type
-                       else
-                               self.error(node, "Error: method `{name}` called 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)
  
                assert mproperty isa MMethod
  
+               # `null` only accepts some methods of object.
+               if recvtype isa MNullType and not mproperty.is_null_safe then
+                       self.error(node, "Error: method `{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.")
                end
@@@ -1342,15 -1344,6 +1344,15 @@@ redef class AIntExp
        end
  end
  
 +redef class AByteExpr
 +      redef fun accept_typing(v)
 +      do
 +              var mclass = v.get_mclass(self, "Byte")
 +              if mclass == null then return # Forward error
 +              self.mtype = mclass.mclass_type
 +      end
 +end
 +
  redef class AFloatExpr
        redef fun accept_typing(v)
        do