Merge: Not null types
authorJean Privat <jean@pryen.org>
Wed, 8 Apr 2015 01:01:49 +0000 (08:01 +0700)
committerJean Privat <jean@pryen.org>
Wed, 8 Apr 2015 01:01:49 +0000 (08:01 +0700)
In order to fix #86 and #1238 some preliminary work to solve remaining issues with the type system is needed. This PR is a step toward this goal.

This introduce not-null types, that is a modifier to indicate that a type cannot contain null.
Basically, this new modifier is almost useless because it is the semantic of all the types (except obviously null and nullable things). Except in one case: when one adapts a formal type whose bound is nullable.

Before the PR, the semantic was the following

~~~nit
class A[E: nullable Object]
   fun foo(e: E, o: nullable Object) do
      var o2 = o.as(not null) # the static type of o2 is `Object`
      print o2 # OK
      var e2 = e.as(not null) # the static type of e2 is still `E` because there is no `nullable` to remove
      print e2 # Error: expected Object, got E
   end
end
~~~

Obviously, the issue was not that important because people managed to program complex things in Nit and I do not remember getting some complain about that particular issue. For the rare cases of this unexpected behavior, a workaround was possible: to cast on the non-nullable bound

~~~nit
   var e2 = e.as(Object)
   print e2 # OK
~~~

Nevertheless, the behavior was still buggy since type information was lost and not POLA. Moreover, `!= null` and `or else` did not have a workaround.

So, this PR introduces a special new type-modifier for this case so that everything become sensible.

~~~nit
      var e2 = e.as(not null) # the static type of e2 is now `not null E`
      print e2 # OK
~~~

Moreover, a lot of local refactorisation was done in model.nit and typing.nit to clean and harmonize the code. So that independently of the new notnull modifier, the code is cleaner, some bugs where removed and some small features added, especially the detection of useless `or else`.

Last, but not least, the `not null` thing is only an internal modifier and is not usable as a syntactic type construction (the grammar and the AST is unchanged); `not null` can however be shown to the programmer in messages.

~~~nit
      var e2 = e.as(not null) # the static type of e2 is now `not null E`
      var e3 = e2.as(not null) # << Warning: expression is not null, since it is a `not null E` >>
~~~

I could easily add `not null` as a specific syntactic construction since everything internally is ready. but 1. does this worth it?. 2. I do not want to conflict with #1243 that also change the grammar.
As an example, is it useful to write the following? (currently refused but very easy to add after this PR)

~~~nit
class A[E: nullable Object]
   fun foo(e: not null E): not null E do
      var x = e.to_s # no null pointer exception
      # ...
      return e
   end
end

var a = new A[nullable Int]
var i = a.foo(5)
~~~

Pull-Request: #1244
Reviewed-by: Etienne M. Gagnon <egagnon@j-meg.com>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Romain Chanoir <chanoir.romain@courrier.uqam.ca>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

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

diff --combined src/model/model.nit
@@@ -251,9 -251,7 +251,9 @@@ redef class MModul
        fun get_primitive_class(name: String): MClass
        do
                var cla = self.model.get_mclasses_by_name(name)
 -              if cla == null then
 +              # Filter classes by introducing module
 +              if cla != null then cla = [for c in cla do if self.in_importation <= c.intro_mmodule then c]
 +              if cla == null or cla.is_empty then
                        if name == "Bool" and self.model.get_mclasses_by_name("Object") != null then
                                # Bool is injected because it is needed by engine to code the result
                                # of the implicit casts.
                                cladef.add_in_hierarchy
                                return c
                        end
 -                      print("Fatal Error: no primitive class {name}")
 +                      print("Fatal Error: no primitive class {name} in {self}")
                        exit(1)
                end
                if cla.length != 1 then
 -                      var msg = "Fatal Error: more than one primitive class {name}:"
 +                      var msg = "Fatal Error: more than one primitive class {name} in {self}:"
                        for c in cla do msg += " {c.full_name}"
                        print msg
                        #exit(1)
@@@ -435,17 -433,8 +435,17 @@@ class MClas
        #
        # Warning: such a definition may not exist in the early life of the object.
        # In this case, the method will abort.
 +      #
 +      # Use `try_intro` instead
        var intro: MClassDef is noinit
  
 +      # The definition that introduces the class or null if not yet known.
 +      #
 +      # See `intro`
 +      fun try_intro: nullable MClassDef do
 +              if isset _intro then return _intro else return null
 +      end
 +
        # Return the class `self` in the class hierarchy of the module `mmodule`.
        #
        # SEE: `MModule::flatten_mclass_hierarchy`
@@@ -638,7 -627,7 +638,7 @@@ class MClassDe
        var in_hierarchy: nullable POSetElement[MClassDef] = null
  
        # Is the definition the one that introduced `mclass`?
 -      fun is_intro: Bool do return mclass.intro == self
 +      fun is_intro: Bool do return isset mclass._intro and mclass.intro == self
  
        # All properties introduced by the classdef
        var intro_mproperties = new Array[MProperty]
@@@ -708,6 -697,8 +708,8 @@@ abstract class MTyp
                if sup isa MNullableType then
                        sup_accept_null = true
                        sup = sup.mtype
+               else if sup isa MNotNullType then
+                       sup = sup.mtype
                else if sup isa MNullType then
                        sup_accept_null = true
                end
                # Can `sub` provide null or not?
                # Thus we can match with `sup_accept_null`
                # Also discard the nullable marker if it exists
+               var sub_reject_null = false
                if sub isa MNullableType then
                        if not sup_accept_null then return false
                        sub = sub.mtype
+               else if sub isa MNotNullType then
+                       sub_reject_null = true
+                       sub = sub.mtype
                else if sub isa MNullType then
                        return sup_accept_null
                end
                # Now the case of direct null and nullable is over.
  
                # If `sub` is a formal type, then it is accepted if its bound is accepted
-               while sub isa MParameterType or sub isa MVirtualType do
+               while sub isa MFormalType do
                        #print "3.is {sub} a {sup}?"
  
                        # A unfixed formal type can only accept itself
  
                        assert anchor != null
                        sub = sub.lookup_bound(mmodule, anchor)
+                       if sub_reject_null then sub = sub.as_notnull
  
                        #print "3.is {sub} a {sup}?"
  
                        # Manage the second layer of null/nullable
                        if sub isa MNullableType then
-                               if not sup_accept_null then return false
+                               if not sup_accept_null and not sub_reject_null then return false
+                               sub = sub.mtype
+                       else if sub isa MNotNullType then
+                               sub_reject_null = true
                                sub = sub.mtype
                        else if sub isa MNullType then
                                return sup_accept_null
                end
                #print "4.is {sub} a {sup}? <- no more resolution"
  
-               assert sub isa MClassType # It is the only remaining type
+               assert sub isa MClassType else print "{sub} <? {sub}" # It is the only remaining type
  
                # A unfixed formal type can only accept itself
-               if sup isa MParameterType or sup isa MVirtualType then
+               if sup isa MFormalType then
                        return false
                end
  
                return res
        end
  
-       # Return the not nullable version of the type
-       # Is the type is already not nullable, then self is returned.
+       # Remove the base type of a decorated (proxy) type.
+       # Is the type is not decorated, then self is returned.
        #
-       # Note: this just remove the `nullable` notation, but the result can still contains null.
+       # Most of the time it is used to return the not nullable version of a nullable type.
+       # In this case, this just remove the `nullable` notation, but the result can still contains null.
        # For instance if `self isa MNullType` or self is a formal type bounded by a nullable type.
-       fun as_notnullable: MType
+       # If you really want to exclude the `null` value, then use `as_notnull`
+       fun undecorate: MType
        do
                return self
        end
  
+       # Returns the not null version of the type.
+       # That is `self` minus the `null` value.
+       #
+       # For most types, this return `self`.
+       # For formal types, this returns a special `MNotNullType`
+       fun as_notnull: MType do return self
        private var as_nullable_cache: nullable MType = null
  
  
@@@ -1270,9 -1278,19 +1289,19 @@@ class MGenericTyp
        end
  end
  
+ # A formal type (either virtual of parametric).
+ #
+ # The main issue with formal types is that they offer very little information on their own
+ # and need a context (anchor and mmodule) to be useful.
+ abstract class MFormalType
+       super MType
+       redef var as_notnull = new MNotNullType(self) is lazy
+ end
  # A virtual formal type.
  class MVirtualType
-       super MType
+       super MFormalType
  
        # The property associated with the type.
        # Its the definitions of this property that determine the bound or the virtual type.
        redef fun lookup_fixed(mmodule: MModule, resolved_receiver: MType): MType
        do
                assert not resolved_receiver.need_anchor
-               resolved_receiver = resolved_receiver.as_notnullable
+               resolved_receiver = resolved_receiver.undecorate
                assert resolved_receiver isa MClassType # It is the only remaining type
  
                var prop = lookup_single_definition(mmodule, resolved_receiver)
@@@ -1400,7 -1418,7 +1429,7 @@@ en
  # Note that parameter types are shared among class refinements.
  # Therefore parameter only have an internal name (see `to_s` for details).
  class MParameterType
-       super MType
+       super MFormalType
  
        # The generic class where the parameter belong
        var mclass: MClass
        redef fun lookup_bound(mmodule: MModule, resolved_receiver: MType): MType
        do
                assert not resolved_receiver.need_anchor
-               resolved_receiver = resolved_receiver.as_notnullable
+               resolved_receiver = resolved_receiver.undecorate
                assert resolved_receiver isa MClassType # It is the only remaining type
                var goalclass = self.mclass
                if resolved_receiver.mclass == goalclass then
        redef fun lookup_fixed(mmodule: MModule, resolved_receiver: MType): MType
        do
                assert not resolved_receiver.need_anchor
-               resolved_receiver = resolved_receiver.as_notnullable
+               resolved_receiver = resolved_receiver.undecorate
                assert resolved_receiver isa MClassType # It is the only remaining type
                var res = self.resolve_for(resolved_receiver.mclass.mclass_type, resolved_receiver, mmodule, false)
                return res
        end
  end
  
- # A type prefixed with "nullable"
- class MNullableType
+ # A type that decorates another type.
+ #
+ # The point of this class is to provide a common implementation of sevices that just forward to the original type.
+ # Specific decorator are expected to redefine (or to extend) the default implementation as this suit them.
+ abstract class MProxyType
        super MType
-       # The base type of the nullable type
+       # The base type
        var mtype: MType
  
        redef fun model do return self.mtype.model
-       init
-       do
-               self.to_s = "nullable {mtype}"
-       end
-       redef var to_s: String is noinit
-       redef var full_name is lazy do return "nullable {mtype.full_name}"
-       redef var c_name is lazy do return "nullable__{mtype.c_name}"
        redef fun need_anchor do return mtype.need_anchor
-       redef fun as_nullable do return self
-       redef fun as_notnullable do return mtype
+       redef fun as_nullable do return mtype.as_nullable
+       redef fun as_notnull do return mtype.as_notnull
+       redef fun undecorate do return mtype.undecorate
        redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual)
        do
                var res = self.mtype.resolve_for(mtype, anchor, mmodule, cleanup_virtual)
-               return res.as_nullable
+               return res
        end
  
        redef fun can_resolve_for(mtype, anchor, mmodule)
                return self.mtype.can_resolve_for(mtype, anchor, mmodule)
        end
  
        redef fun lookup_fixed(mmodule, resolved_receiver)
        do
                var t = mtype.lookup_fixed(mmodule, resolved_receiver)
-               if t == mtype then return self
-               return t.as_nullable
+               return t
        end
  
        redef fun depth do return self.mtype.depth
        end
  end
  
+ # A type prefixed with "nullable"
+ class MNullableType
+       super MProxyType
+       init
+       do
+               self.to_s = "nullable {mtype}"
+       end
+       redef var to_s: String is noinit
+       redef var full_name is lazy do return "nullable {mtype.full_name}"
+       redef var c_name is lazy do return "nullable__{mtype.c_name}"
+       redef fun as_nullable do return self
+       redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual)
+       do
+               var res = super
+               return res.as_nullable
+       end
+       # Efficiently returns `mtype.lookup_fixed(mmodule, resolved_receiver).as_nullable`
+       redef fun lookup_fixed(mmodule, resolved_receiver)
+       do
+               var t = super
+               if t == mtype then return self
+               return t.as_nullable
+       end
+ end
+ # A non-null version of a formal type.
+ #
+ # When a formal type in bounded to a nullable type, this is the type of the not null version of it.
+ class MNotNullType
+       super MProxyType
+       redef fun to_s do return "not null {mtype}"
+       redef var full_name is lazy do return "not null {mtype.full_name}"
+       redef var c_name is lazy do return "notnull__{mtype.c_name}"
+       redef fun as_notnull do return self
+       redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual)
+       do
+               var res = super
+               return res.as_notnull
+       end
+       # Efficiently returns `mtype.lookup_fixed(mmodule, resolved_receiver).as_notnull`
+       redef fun lookup_fixed(mmodule, resolved_receiver)
+       do
+               var t = super
+               if t == mtype then return self
+               return t.as_notnull
+       end
+ end
  # The type of the only value null
  #
  # The is only one null type per model, see `MModel::null_type`.
@@@ -1597,6 -1662,9 +1673,9 @@@ class MNullTyp
        redef fun full_name do return "null"
        redef fun c_name do return "null"
        redef fun as_nullable do return self
+       # Aborts on `null`
+       redef fun as_notnull do abort # sorry...
        redef fun need_anchor do return false
        redef fun resolve_for(mtype, anchor, mmodule, cleanup_virtual) do return self
        redef fun can_resolve_for(mtype, anchor, mmodule) do return true
@@@ -1819,7 -1887,7 +1898,7 @@@ abstract class MPropert
        fun lookup_definitions(mmodule: MModule, mtype: MType): Array[MPROPDEF]
        do
                assert not mtype.need_anchor
-               mtype = mtype.as_notnullable
+               mtype = mtype.undecorate
  
                var cache = self.lookup_definitions_cache[mmodule, mtype]
                if cache != null then return cache
        fun lookup_super_definitions(mmodule: MModule, mtype: MType): Array[MPROPDEF]
        do
                assert not mtype.need_anchor
-               mtype = mtype.as_notnullable
+               mtype = mtype.undecorate
  
                # First, select all candidates
                var candidates = new Array[MPROPDEF]
        # REQUIRE: `mtype.has_mproperty(mmodule, self)`
        fun lookup_all_definitions(mmodule: MModule, mtype: MType): Array[MPROPDEF]
        do
-               mtype = mtype.as_notnullable
+               mtype = mtype.undecorate
  
                var cache = self.lookup_all_definitions_cache[mmodule, mtype]
                if cache != null then return cache
@@@ -86,8 -86,7 +86,8 @@@ redef class ModelBuilde
                # Force building recursively
                if nclassdef.build_properties_is_done then return
                nclassdef.build_properties_is_done = true
 -              var mclassdef = nclassdef.mclassdef.as(not null)
 +              var mclassdef = nclassdef.mclassdef
 +              if mclassdef == null then return # skip error
                if mclassdef.in_hierarchy == null then return # Skip error
                for superclassdef in mclassdef.in_hierarchy.direct_greaters do
                        if not mclassdef2nclassdef.has_key(superclassdef) then continue
                                npropdef.build_signature(self)
                        end
                        for npropdef in nclassdef2.n_propdefs do
 +                              if not npropdef isa ATypePropdef then continue
 +                              # Check circularity
 +                              var mpropdef = npropdef.mpropdef
 +                              if mpropdef == null then continue
 +                              if mpropdef.bound == null then continue
 +                              if not check_virtual_types_circularity(npropdef, mpropdef.mproperty, mclassdef.bound_mtype, mclassdef.mmodule) then
 +                                      # Invalidate the bound
 +                                      mpropdef.bound = mclassdef.mmodule.model.null_type
 +                              end
 +                      end
 +                      for npropdef in nclassdef2.n_propdefs do
 +                              # Check ATypePropdef first since they may be required for the other properties
 +                              if not npropdef isa ATypePropdef then continue
 +                              npropdef.check_signature(self)
 +                      end
 +
 +                      for npropdef in nclassdef2.n_propdefs do
 +                              if npropdef isa ATypePropdef then continue
                                npropdef.check_signature(self)
                        end
                end
                                        for p in spd.initializers do
                                                if p != longest.initializers[i] then
                                                        self.error(nclassdef, "Error: conflict for inherited inits {spd}({spd.initializers.join(", ")}) and {longest}({longest.initializers.join(", ")})")
 +                                                      # TODO: invalidate the initializer to avoid more errors
                                                        return
                                                end
                                                i += 1
                # It is a case-by case
                var vis_type: nullable MVisibility = null # The own visibility of the type
                var mmodule_type: nullable MModule = null # The original module of the type
-               mtype = mtype.as_notnullable
+               mtype = mtype.undecorate
                if mtype isa MClassType then
                        vis_type = mtype.mclass.visibility
                        mmodule_type = mtype.mclass.intro.mmodule
                        mmodule_type = mtype.mproperty.intro_mclassdef.mmodule
                else if mtype isa MParameterType then
                        # nothing, always visible
 +              else if mtype isa MNullType then
 +                      # nothing to do.
                else
                        node.debug "Unexpected type {mtype}"
                        abort
                        for t in mtype.arguments do check_visibility(node, t, mpropdef)
                end
        end
 +
 +      # Detect circularity errors for virtual types.
 +      fun check_virtual_types_circularity(node: ANode, mproperty: MVirtualTypeProp, recv: MType, mmodule: MModule): Bool
 +      do
 +              # Check circularity
 +              # Slow case: progress on each resolution until we visit all without getting a loop
 +
 +              # The graph used to detect loops
 +              var mtype = mproperty.mvirtualtype
 +              var poset = new POSet[MType]
 +
 +              # The work-list of types to resolve
 +              var todo = new List[MType]
 +              todo.add mtype
 +
 +              while not todo.is_empty do
 +                      # The visited type
 +                      var t = todo.pop
 +
 +                      if not t.need_anchor then continue
 +
 +                      # Get the types derived of `t` (subtypes and bounds)
 +                      var nexts
 +                      if t isa MNullableType then
 +                              nexts = [t.mtype]
 +                      else if t isa MGenericType then
 +                              nexts = t.arguments
 +                      else if t isa MVirtualType then
 +                              var vt = t.mproperty
 +                              # Because `vt` is possibly unchecked, we have to do the bound-lookup manually
 +                              var defs = vt.lookup_definitions(mmodule, recv)
 +                              # TODO something to manage correctly bound conflicts
 +                              assert not defs.is_empty
 +                              nexts = new Array[MType]
 +                              for d in defs do
 +                                      var next = defs.first.bound
 +                                      if next == null then return false
 +                                      nexts.add next
 +                              end
 +                      else if t isa MClassType then
 +                              # Basic type, nothing to to
 +                              continue
 +                      else if t isa MParameterType then
 +                              # Parameter types cannot depend on virtual types, so nothing to do
 +                              continue
 +                      else
 +                              abort
 +                      end
 +
 +                      # For each one
 +                      for next in nexts do
 +                              if poset.has_edge(next, t) then
 +                                      if mtype == next then
 +                                              error(node, "Error: circularity of virtual type definition: {next} <-> {t}")
 +                                      else
 +                                              error(node, "Error: circularity of virtual type definition: {mtype} -> {next} <-> {t}")
 +                                      end
 +                                      return false
 +                              else
 +                                      poset.add_edge(t, next)
 +                                      todo.add next
 +                              end
 +                      end
 +              end
 +              return true
 +      end
  end
  
  redef class MPropDef
@@@ -685,7 -597,7 +685,7 @@@ redef class ASignatur
                        param_names.add(np.n_id.text)
                        var ntype = np.n_type
                        if ntype != null then
 -                              var mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
 +                              var mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                                if mtype == null then return false # Skip error
                                for i in [0..param_names.length-param_types.length[ do
                                        param_types.add(mtype)
                end
                var ntype = self.n_type
                if ntype != null then
 -                      self.ret_type = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
 +                      self.ret_type = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                        if self.ret_type == null then return false # Skip error
                end
  
                return true
        end
  
 -      # Build a visited signature
 -      fun build_signature(modelbuilder: ModelBuilder): nullable MSignature
 +      private fun check_signature(modelbuilder: ModelBuilder, mclassdef: MClassDef): Bool
        do
 -              if param_names.length != param_types.length then
 -                      # Some parameters are typed, other parameters are not typed.
 -                      modelbuilder.error(self.n_params[param_types.length], "Error: Untyped parameter `{param_names[param_types.length]}'.")
 -                      return null
 +              var res = true
 +              for np in self.n_params do
 +                      var ntype = np.n_type
 +                      if ntype != null then
 +                              if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
 +                                      res = false
 +                              end
 +                      end
                end
 -
 -              var mparameters = new Array[MParameter]
 -              for i in [0..param_names.length[ do
 -                      var mparameter = new MParameter(param_names[i], param_types[i], i == vararg_rank)
 -                      self.n_params[i].mparameter = mparameter
 -                      mparameters.add(mparameter)
 +              var ntype = self.n_type
 +              if ntype != null then
 +                      if modelbuilder.resolve_mtype(mclassdef.mmodule, mclassdef, ntype) == null then
 +                              res = false
 +                      end
                end
 -
 -              var msignature = new MSignature(mparameters, ret_type)
 -              return msignature
 +              return res
        end
  end
  
@@@ -968,14 -880,6 +968,14 @@@ redef class AMethPropde
                var mysignature = self.mpropdef.msignature
                if mysignature == null then return # Error thus skiped
  
 +              # Check
 +              if nsig != null then
 +                      if not nsig.check_signature(modelbuilder, mclassdef) then
 +                              self.mpropdef.msignature = null # invalidate
 +                              return # Forward error
 +                      end
 +              end
 +
                # Lookup for signature in the precursor
                # FIXME all precursors should be considered
                if not mpropdef.is_intro then
                        var ret_type = mysignature.return_mtype
                        if ret_type != null and precursor_ret_type == null then
                                modelbuilder.error(nsig.n_type.as(not null), "Redef Error: {mpropdef.mproperty} is a procedure, not a function.")
 +                              self.mpropdef.msignature = null
                                return
                        end
  
                                        var node = nsig.n_params[i]
                                        if not modelbuilder.check_sametype(node, mmodule, mclassdef.bound_mtype, myt, prt) then
                                                modelbuilder.error(node, "Redef Error: Wrong type for parameter `{mysignature.mparameters[i].name}'. found {myt}, expected {prt} as in {mpropdef.mproperty.intro}.")
 +                                              self.mpropdef.msignature = null
                                        end
                                end
                        end
                                        ret_type = precursor_ret_type
                                else if not modelbuilder.check_subtype(node, mmodule, mclassdef.bound_mtype, ret_type, precursor_ret_type) then
                                        modelbuilder.error(node, "Redef Error: Wrong return type. found {ret_type}, expected {precursor_ret_type} as in {mpropdef.mproperty.intro}.")
 +                                      self.mpropdef.msignature = null
                                end
                        end
                end
@@@ -1127,7 -1028,6 +1127,7 @@@ redef class AAttrPropde
                                else if atautoinit != null then
                                        modelbuilder.error(atautoinit, "Error: a autoinit attribute needs a value")
                                end
 +                              has_value = true
                                return
                        end
                        is_lazy = true
  
                var ntype = self.n_type
                if ntype != null then
 -                      mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
 +                      mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                        if mtype == null then return
                end
  
                if mtype == null then
                        if nexpr != null then
                                if nexpr isa ANewExpr then
 -                                      mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type)
 +                                      mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
                                else if nexpr isa AIntExpr then
                                        var cla = modelbuilder.try_get_mclass_by_name(nexpr, mmodule, "Int")
                                        if cla != null then mtype = cla.mclass_type
                        end
                else if ntype != null and inherited_type == mtype then
                        if nexpr isa ANewExpr then
 -                              var xmtype = modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type)
 +                              var xmtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
                                if xmtype == mtype then
                                        modelbuilder.advice(ntype, "useless-type", "Warning: useless type definition")
                                end
                var mtype = self.mpropdef.static_mtype
                if mtype == null then return # Error thus skipped
  
 +              var mclassdef = mpropdef.mclassdef
 +              var mmodule = mclassdef.mmodule
 +
 +              # Check types
 +              if ntype != null then
 +                      if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then return
 +              end
 +              var nexpr = n_expr
 +              if nexpr isa ANewExpr then
 +                      if modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type) == null then return
 +              end
 +
                # Lookup for signature in the precursor
                # FIXME all precursors should be considered
                if not mpropdef.is_intro then
@@@ -1427,7 -1315,7 +1427,7 @@@ redef class ATypePropde
                var mtype: nullable MType = null
  
                var ntype = self.n_type
 -              mtype = modelbuilder.resolve_mtype(mmodule, mclassdef, ntype)
 +              mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, ntype, true)
                if mtype == null then return
  
                mpropdef.bound = mtype
                var mpropdef = self.mpropdef
                if mpropdef == null then return # Error thus skipped
  
 -              var bound = self.mpropdef.bound
 +              var bound = mpropdef.bound
                if bound == null then return # Error thus skipped
  
                modelbuilder.check_visibility(n_type, bound, mpropdef)
                var mmodule = mclassdef.mmodule
                var anchor = mclassdef.bound_mtype
  
 -              # Check circularity
 -              if bound isa MVirtualType then
 -                      # Slow case: progress on each resolution until: (i) we loop, or (ii) we found a non formal type
 -                      var seen = [self.mpropdef.mproperty.mvirtualtype]
 -                      loop
 -                              if seen.has(bound) then
 -                                      seen.add(bound)
 -                                      modelbuilder.error(self, "Error: circularity of virtual type definition: {seen.join(" -> ")}")
 -                                      return
 -                              end
 -                              seen.add(bound)
 -                              var next = bound.lookup_bound(mmodule, anchor)
 -                              if not next isa MVirtualType then break
 -                              bound = next
 -                      end
 +              var ntype = self.n_type
 +              if modelbuilder.resolve_mtype(mmodule, mclassdef, ntype) == null then
 +                      mpropdef.bound = null
 +                      return
                end
  
                # Check redefinitions
 -              bound = mpropdef.bound.as(not null)
                for p in mpropdef.mproperty.lookup_super_definitions(mmodule, anchor) do
                        var supbound = p.bound
                        if supbound == null then break # broken super bound, skip error
diff --combined src/semantize/typing.nit
@@@ -115,7 -115,12 +115,12 @@@ private class TypeVisito
                        #node.debug("Unsafe typing: expected {sup}, got {sub}")
                        return sup
                end
-               self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}")
+               if sub.need_anchor then
+                       var u = anchor_to(sub)
+                       self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}: {u}")
+               else
+                       self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}")
+               end
                return null
        end
  
                return sup
        end
  
+       # Can `mtype` be null (up to the current knowledge)?
+       fun can_be_null(mtype: MType): Bool
+       do
+               if mtype isa MNullableType or mtype isa MNullType then return true
+               if mtype isa MFormalType then
+                       var x = anchor_to(mtype)
+                       if x isa MNullableType or x isa MNullType then return true
+               end
+               return false
+       end
+       # Check that `mtype` can be null (up to the current knowledge).
+       #
+       # If not then display a `useless-null-test` warning on node and return false.
+       # Else return true.
+       fun check_can_be_null(anode: ANode, mtype: MType): Bool
+       do
+               if can_be_null(mtype) then return true
+               if mtype isa MFormalType then
+                       var res = anchor_to(mtype)
+                       modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}: {res}`.")
+               else
+                       modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.")
+               end
+               return false
+       end
        # Special verification on != and == for null
        # Return true
        fun null_test(anode: ABinopExpr)
  
                if not mtype2 isa MNullType then return
  
+               if mtype isa MNullType then return
                # Check of useless null
-               if not mtype isa MNullableType then
-                       if not anchor_to(mtype) isa MNullableType then
-                               modelbuilder.warning(anode, "useless-null-test", "Warning: expression is not null, since it is a `{mtype}`.")
-                       end
-                       return
-               end
+               if not check_can_be_null(anode.n_expr, mtype) then return
+               mtype = mtype.as_notnull
  
                # Check for type adaptation
                var variable = anode.n_expr.its_variable
                if variable == null then return
  
+               # One is null (mtype2 see above) the other is not null
                if anode isa AEqExpr then
                        anode.after_flow_context.when_true.set_var(variable, mtype2)
-                       anode.after_flow_context.when_false.set_var(variable, mtype.mtype)
+                       anode.after_flow_context.when_false.set_var(variable, mtype)
                else if anode isa ANeExpr then
                        anode.after_flow_context.when_false.set_var(variable, mtype2)
-                       anode.after_flow_context.when_true.set_var(variable, mtype.mtype)
+                       anode.after_flow_context.when_true.set_var(variable, mtype)
                else
                        abort
                end
  
        fun get_mclass(node: ANode, name: String): nullable MClass
        do
 -              var mclass = modelbuilder.try_get_mclass_by_name(node, mmodule, name)
 -              if mclass == null then
 -                      self.modelbuilder.error(node, "Type Error: missing primitive class `{name}'.")
 -              end
 +              var mclass = modelbuilder.get_mclass_by_name(node, mmodule, name)
                return mclass
        end
  
                if recvtype isa MNullType then
                        # `null` only accepts some methods of object.
                        if name == "==" or name == "!=" or name == "is_same_instance" then
 -                              unsafe_type = mmodule.object_type.as_nullable
 +                              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}' call on 'null'.")
                                return null
                end
  
  
 -              var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
 +              var msignature = mpropdef.new_msignature or else mpropdef.msignature
 +              if msignature == null then return null # skip error
                msignature = resolve_for(msignature, recvtype, recv_is_self).as(MSignature)
  
                var erasure_cast = false
                var rettype = mpropdef.msignature.return_mtype
                if not recv_is_self and rettype != null then
-                       rettype = rettype.as_notnullable
+                       rettype = rettype.undecorate
                        if rettype isa MParameterType then
                                var erased_rettype = msignature.return_mtype
                                assert erased_rettype != null
                        var found = true
                        for t2 in col do
                                if t2 == null then continue # return null
-                               if t2 isa MNullableType or t2 isa MNullType then
+                               if can_be_null(t2) and not can_be_null(t1) then
                                        t1 = t1.as_nullable
                                end
                                if not is_subtype(t2, t1) then found = false
@@@ -569,18 -602,14 +602,18 @@@ redef class AMethPropde
                var nblock = self.n_block
                if nblock == null then return
  
 -              var mpropdef = self.mpropdef.as(not null)
 +              var mpropdef = self.mpropdef
 +              if mpropdef == null then return # skip error
 +
                var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
                self.selfvariable = v.selfvariable
  
                var mmethoddef = self.mpropdef.as(not null)
 -              for i in [0..mmethoddef.msignature.arity[ do
 -                      var mtype = mmethoddef.msignature.mparameters[i].mtype
 -                      if mmethoddef.msignature.vararg_rank == i then
 +              var msignature = mmethoddef.msignature
 +              if msignature == null then return # skip error
 +              for i in [0..msignature.arity[ do
 +                      var mtype = msignature.mparameters[i].mtype
 +                      if msignature.vararg_rank == i then
                                var arrayclass = v.get_mclass(self.n_signature.n_params[i], "Array")
                                if arrayclass == null then return # Skip error
                                mtype = arrayclass.get_mtype([mtype])
                end
                v.visit_stmt(nblock)
  
 -              if not nblock.after_flow_context.is_unreachable and mmethoddef.msignature.return_mtype != null then
 +              if not nblock.after_flow_context.is_unreachable and msignature.return_mtype != null then
                        # We reach the end of the function without having a return, it is bad
                        v.error(self, "Control error: Reached end of function (a 'return' with a value was expected).")
                end
@@@ -603,9 -632,7 +636,9 @@@ redef class AAttrPropde
        do
                if not has_value then return
  
 -              var mpropdef = self.mpropdef.as(not null)
 +              var mpropdef = self.mpropdef
 +              if mpropdef == null then return # skip error
 +
                var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
                self.selfvariable = v.selfvariable
  
@@@ -704,9 -731,7 +737,9 @@@ redef class AVardeclExp
  
                var decltype = mtype
                if mtype == null or mtype isa MNullType then
 -                      decltype = v.get_mclass(self, "Object").mclass_type.as_nullable
 +                      var objclass = v.get_mclass(self, "Object")
 +                      if objclass == null then return # skip error
 +                      decltype = objclass.mclass_type.as_nullable
                        if mtype == null then mtype = decltype
                end
  
@@@ -1010,7 -1035,7 +1043,7 @@@ redef class AForExp
                # anchor formal and virtual types
                if mtype.need_anchor then mtype = v.anchor_to(mtype)
  
-               mtype = mtype.as_notnullable
+               mtype = mtype.undecorate
                self.coltype = mtype.as(MClassType)
  
                # get methods is_ok, next, item
@@@ -1129,14 -1154,16 +1162,18 @@@ redef class AOrElseExp
                        return # Skip error
                end
  
-               t1 = t1.as_notnullable
+               if t1 isa MNullType then
+                       v.error(n_expr, "Type error: or else on null")
+               else if v.check_can_be_null(n_expr, t1) then
+                       t1 = t1.as_notnull
+               end
  
                var t = v.merge_types(self, [t1, t2])
                if t == null then
 -                      t = v.mmodule.object_type
 +                      var c = v.get_mclass(self, "Object")
 +                      if c == null then return # forward error
 +                      t = c.mclass_type
-                       if t2 isa MNullableType then
+                       if v.can_be_null(t2) then
                                t = t.as_nullable
                        end
                        #v.error(self, "Type Error: ambiguous type {t1} vs {t2}")
@@@ -1201,11 -1228,8 +1238,11 @@@ redef class ASuperstringExp
                var mclass = v.get_mclass(self, "String")
                if mclass == null then return # Forward error
                self.mtype = mclass.mclass_type
 +              var objclass = v.get_mclass(self, "Object")
 +              if objclass == null then return # Forward error
 +              var objtype = objclass.mclass_type
                for nexpr in self.n_exprs do
 -                      v.visit_expr_subtype(nexpr, v.mmodule.object_type)
 +                      v.visit_expr_subtype(nexpr, objtype)
                end
        end
  end
@@@ -1368,22 -1392,12 +1405,12 @@@ redef class AAsNotnullExp
                        v.error(self, "Type error: as(not null) on null")
                        return
                end
-               if mtype isa MNullableType then
-                       self.mtype = mtype.mtype
-                       return
-               end
-               self.mtype = mtype
  
-               if mtype isa MClassType then
-                       v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}`.")
-                       return
-               end
-               assert mtype.need_anchor
-               var u = v.anchor_to(mtype)
-               if not u isa MNullableType then
-                       v.modelbuilder.warning(self, "useless-type-test", "Warning: expression is already not null, since it is a `{mtype}: {u}`.")
-                       return
+               if v.check_can_be_null(n_expr, mtype) then
+                       mtype = mtype.as_notnull
                end
+               self.mtype = mtype
        end
  end
  
@@@ -1830,8 -1844,7 +1857,8 @@@ redef class AAttrFormExp
                var mpropdefs = mproperty.lookup_definitions(v.mmodule, unsafe_type)
                assert mpropdefs.length == 1
                var mpropdef = mpropdefs.first
 -              var attr_type = mpropdef.static_mtype.as(not null)
 +              var attr_type = mpropdef.static_mtype
 +              if attr_type == null then return # skip error
                attr_type = v.resolve_for(attr_type, recvtype, self.n_expr isa ASelfExpr)
                self.attr_type = attr_type
        end