Callref bugfix in interpreter and compilers + autosav
[nit.git] / src / semantize / typing.nit
index a1f1220..b9acd5e 100644 (file)
@@ -20,6 +20,7 @@ module typing
 
 import modelize
 import local_var_init
+import literal
 
 redef class ToolContext
        var typing_phase: Phase = new TypingPhase(self, [flow_phase, modelize_property_phase, local_var_init_phase])
@@ -35,55 +36,48 @@ 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")
 
        # Is `self` use restricted?
        # * no explicit `self`
        # * method called on the implicit self must be top-level
+       # Currently only used for `new` factory since there is no valid receiver inside
        var is_toplevel_context = false
 
        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_toplevel or 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
 
@@ -100,22 +94,28 @@ private class TypeVisitor
        end
 
        # Check that `sub` is a subtype of `sup`.
-       # If `sub` is not a valid suptype, then display an error on `node` an return null.
-       # If `sub` is a safe subtype of `sup` then return `sub`.
-       # If `sub` is an unsafe subtype (ie an implicit cast is required), then return `sup`.
+       # If `sub` is not a valid suptype, then display an error on `node` and return `null`.
+       # If `sub` is a safe subtype of `sup`, then return `sub`.
+       # If `sub` is an unsafe subtype (i.e., an implicit cast is required), then return `sup`.
        #
-       # The point of the return type is to determinate the usable type on an expression:
+       # The point of the return type is to determinate the usable type on an expression when `autocast` is true:
        # If the suptype is safe, then the return type is the one on the expression typed by `sub`.
        # Is the subtype is unsafe, then the return type is the one of an implicit cast on `sup`.
-       fun check_subtype(node: ANode, sub, sup: MType): nullable MType
+       fun check_subtype(node: ANode, sub, sup: MType, autocast: Bool): nullable MType
        do
                if self.is_subtype(sub, sup) then return sub
-               if self.is_subtype(sub, self.anchor_to(sup)) then
+               if autocast and self.is_subtype(sub, self.anchor_to(sup)) then
                        # FIXME workaround to the current unsafe typing policy. To remove once fixed virtual types exists.
                        #node.debug("Unsafe typing: expected {sup}, got {sub}")
                        return sup
                end
-               self.modelbuilder.error(node, "Type error: expected {sup}, got {sub}")
+               if sup isa MErrorType then return null # Skip error
+               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
 
@@ -144,7 +144,11 @@ private class TypeVisitor
                        end
                        return null # forward error
                end
-               self.error(nexpr, "Type error: expected expression.")
+               var more_message = null
+               var p = nexpr.parent
+               if p != null then more_message = p.bad_expr_message(nexpr)
+               if more_message == null then more_message = "" else more_message = " " + more_message
+               self.error(nexpr, "Error: expected an expression{more_message}.")
                return null
        end
 
@@ -160,7 +164,7 @@ private class TypeVisitor
 
                if sup == null then return null # Forward error
 
-               var res = check_subtype(nexpr, sub, sup)
+               var res = check_subtype(nexpr, sub, sup, true)
                if res != sub then
                        nexpr.implicit_cast_to = res
                end
@@ -177,19 +181,18 @@ private class TypeVisitor
                return self.visit_expr_subtype(nexpr, self.type_bool(nexpr))
        end
 
-
-       fun visit_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
+       fun check_expr_cast(node: ANode, nexpr: AExpr, ntype: AType): nullable MType
        do
-               var sub = visit_expr(nexpr)
+               var sub = nexpr.mtype
                if sub == null then return null # Forward error
 
-               var sup = self.resolve_mtype(ntype)
+               var sup = ntype.mtype
                if sup == null then return null # Forward error
 
                if sup == sub then
-                       self.modelbuilder.warning(node, "useless-type-test", "Warning: Expression is already a {sup}.")
+                       self.modelbuilder.warning(node, "useless-type-test", "Warning: expression is already a `{sup}`.")
                else if self.is_subtype(sub, sup) then
-                       self.modelbuilder.warning(node, "useless-type-test", "Warning: Expression is already a {sup} since it is a {sub}.")
+                       self.modelbuilder.warning(node, "useless-type-test", "Warning: expression is already a `{sup}` since it is a `{sub}`.")
                end
                return sup
        end
@@ -211,6 +214,10 @@ private class TypeVisitor
        # Else return true.
        fun check_can_be_null(anode: ANode, mtype: MType): Bool
        do
+               if mtype isa MNullType then
+                       modelbuilder.warning(anode, "useless-null-test", "Warning: expression is always `null`.")
+                       return true
+               end
                if can_be_null(mtype) then return true
 
                if mtype isa MFormalType then
@@ -233,12 +240,16 @@ private class TypeVisitor
 
                if not mtype2 isa MNullType then return
 
-               if mtype isa MNullType then return
-
                # Check of useless null
-               if not check_can_be_null(anode.n_expr, mtype) then return
+               if not can_be_null(mtype) then return
 
-               mtype = mtype.as_notnull
+               if mtype isa MNullType then
+                       # Because of type adaptation, we cannot just stop here
+                       # so return use `null` as a bottom type that will be merged easily (cf) `merge_types`
+                       mtype = null
+               else
+                       mtype = mtype.as_notnull
+               end
 
                # Check for type adaptation
                var variable = anode.n_expr.its_variable
@@ -246,11 +257,11 @@ private class TypeVisitor
 
                # 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)
+                       anode.after_flow_context.when_true.set_var(self, variable, mtype2)
+                       anode.after_flow_context.when_false.set_var(self, 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)
+                       anode.after_flow_context.when_false.set_var(self, variable, mtype2)
+                       anode.after_flow_context.when_true.set_var(self, variable, mtype)
                else
                        abort
                end
@@ -263,7 +274,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
@@ -274,10 +285,7 @@ private class TypeVisitor
 
        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
 
@@ -288,19 +296,21 @@ private class TypeVisitor
                return mclass.mclass_type
        end
 
-       fun get_method(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite
+       # Construction of a specific callsite according to the current context.
+       # Three entry points exist to create a callsite based on knowledge.
+       # The `build_callsite_by_name` is a top entry point, the method find the mpropdefs to call by the name of this.
+       # see `build_callsite_by_property` and `build_callsite_by_propdef` for more detail.
+       # If you already know the mpropdef to call use directly the `get_method_by_propdef` method
+       # If you just know the mproperty use the `build_callsite_by_property` method to display error if no `mpropdef` is found in the context
+       fun build_callsite_by_name(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite
        do
                var unsafe_type = self.anchor_to(recvtype)
 
                #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
-                               unsafe_type = mmodule.object_type.as_nullable
-                       else
-                               self.error(node, "Error: Method '{name}' call 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)
@@ -310,26 +320,49 @@ private class TypeVisitor
                end
 
                if mproperty == null then
-                       #self.modelbuilder.error(node, "Type error: property {name} not found in {unsafe_type} (ie {recvtype})")
                        if recv_is_self then
-                               self.modelbuilder.error(node, "Error: Method or variable '{name}' unknown in {recvtype}.")
+                               self.modelbuilder.error(node, "Error: method or variable `{name}` unknown in `{recvtype}`.")
+                       else if recvtype.need_anchor then
+                               self.modelbuilder.error(node, "Error: method `{name}` does not exists in `{recvtype}: {unsafe_type}`.")
                        else
-                               self.modelbuilder.error(node, "Error: Method '{name}' doesn't exists in {recvtype}.")
+                               self.modelbuilder.error(node, "Error: method `{name}` does not exists in `{recvtype}`.")
                        end
                        return null
                end
 
                assert mproperty isa MMethod
 
+               return build_callsite_by_property(node, recvtype, mproperty, recv_is_self)
+       end
+
+       # The `build_callsite_by_property` finds the mpropdefs to call by the `MMethod`.
+       # If the mpropdef is found in the context it builds a new `Callsite`.
+       fun build_callsite_by_property(node: ANode, recvtype: MType, mproperty: MMethod, recv_is_self: Bool): nullable CallSite
+       do
+               var unsafe_type = self.anchor_to(recvtype)
+
+               if recvtype isa MNullType then
+                       var objclass = get_mclass(node, "Object")
+                       if objclass == null then return null # Forward error
+                       unsafe_type = objclass.mclass_type
+               end
+               # `null` only accepts some methods of object.
+               if recvtype isa MNullType and not mproperty.is_null_safe then
+                       self.error(node, "Error: method `{mproperty.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.")
+                       error(node, "Error: `{mproperty.name}` is not a top-level method, thus need a receiver.")
                end
                if not recv_is_self and mproperty.is_toplevel then
-                       error(node, "Error: cannot call '{name}', a top-level method, with a receiver.")
+                       error(node, "Error: cannot call `{mproperty.name}`, a top-level method, with a receiver.")
                end
 
                if mproperty.visibility == protected_visibility and not recv_is_self and self.mmodule.visibility_for(mproperty.intro_mclassdef.mmodule) < intrude_visibility and not modelbuilder.toolcontext.opt_ignore_visibility.value then
-                       self.modelbuilder.error(node, "Error: Method '{name}' is protected and can only acceded by self.")
+                       self.modelbuilder.error(node, "Error: method `{mproperty.name}` is protected and can only accessed by `self`.")
                        return null
                end
 
@@ -337,26 +370,33 @@ private class TypeVisitor
                if info != null and self.mpropdef.mproperty.deprecation == null then
                        var mdoc = info.mdoc
                        if mdoc != null then
-                               self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: Method '{name}' is deprecated: {mdoc.content.first}")
+                               self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: method `{mproperty.name}` is deprecated: {mdoc.content.first}")
                        else
-                               self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: Method '{name}' is deprecated.")
+                               self.modelbuilder.warning(node, "deprecated-method", "Deprecation Warning: method `{mproperty.name}` is deprecated.")
                        end
                end
 
                var propdefs = mproperty.lookup_definitions(self.mmodule, unsafe_type)
                var mpropdef
                if propdefs.length == 0 then
-                       self.modelbuilder.error(node, "Type error: no definition found for property {name} in {unsafe_type}")
-                       return null
+                       self.modelbuilder.error(node, "Type Error: no definition found for property `{mproperty.name}` in `{unsafe_type}`.")
+                       abort
+                       #return null
                else if propdefs.length == 1 then
                        mpropdef = propdefs.first
                else
-                       self.modelbuilder.warning(node, "property-conflict", "Warning: conflicting property definitions for property {name} in {unsafe_type}: {propdefs.join(" ")}")
+                       self.modelbuilder.warning(node, "property-conflict", "Warning: conflicting property definitions for property `{mproperty.name}` in `{unsafe_type}`: {propdefs.join(" ")}")
                        mpropdef = mproperty.intro
                end
 
+               return build_callsite_by_propdef(node, recvtype, mpropdef, recv_is_self)
+       end
 
-               var msignature = mpropdef.new_msignature or else mpropdef.msignature.as(not null)
+       # The `build_callsite_by_propdef` builds the callsite directly with the `mprodef` passed in argument.
+       fun build_callsite_by_propdef(node: ANode, recvtype: MType, mpropdef: MMethodDef, recv_is_self: Bool): nullable CallSite
+       do
+               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
@@ -371,78 +411,177 @@ private class TypeVisitor
                        end
                end
 
-               var callsite = new CallSite(node, recvtype, mmodule, anchor, recv_is_self, mproperty, mpropdef, msignature, erasure_cast)
+               var callsite = new CallSite(node.hot_location, recvtype, mmodule, anchor, recv_is_self, mpropdef.mproperty, mpropdef, msignature, erasure_cast)
                return callsite
        end
 
-       fun try_get_method(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite
+       fun try_build_callsite_by_name(node: ANode, recvtype: MType, name: String, recv_is_self: Bool): nullable CallSite
        do
                var unsafe_type = self.anchor_to(recvtype)
                var mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name)
                if mproperty == null then return null
-               return get_method(node, recvtype, name, recv_is_self)
+               return build_callsite_by_name(node, recvtype, name, recv_is_self)
        end
 
-
        # Visit the expressions of args and check their conformity with the corresponding type in signature
        # The point of this method is to handle varargs correctly
        # Note: The signature must be correctly adapted
-       fun check_signature(node: ANode, args: Array[AExpr], name: String, msignature: MSignature): Bool
+       fun check_signature(node: ANode, args: Array[AExpr], mproperty: MProperty, msignature: MSignature): nullable SignatureMap
        do
                var vararg_rank = msignature.vararg_rank
                if vararg_rank >= 0 then
                        if args.length < msignature.arity then
-                               #self.modelbuilder.error(node, "Error: Incorrect number of parameters. Got {args.length}, expected at least {msignature.arity}. Signature is {msignature}")
-                               self.modelbuilder.error(node, "Error: arity mismatch; prototype is '{name}{msignature}'")
-                               return false
+                               modelbuilder.error(node, "Error: expected at least {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                               return null
                        end
                else if args.length != msignature.arity then
-                       self.modelbuilder.error(node, "Error: Incorrect number of parameters. Got {args.length}, expected {msignature.arity}. Signature is {msignature}")
-                       return false
+                       # Too much argument
+                       if args.length > msignature.arity then
+                               modelbuilder.error(node, "Error: expected {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                               return null
+                       end
+                       # Other cases are managed later
                end
 
                #debug("CALL {unsafe_type}.{msignature}")
 
+               # Associate each parameter to a position in the arguments
+               var map = new SignatureMap
+
+               # Special case for the isolated last argument
+               # TODO: reify this method characteristics (where? the param, the signature, the method?)
+               var last_is_padded = mproperty.name.chars.last == '='
+               var nbargs = args.length
+               if last_is_padded then
+                       nbargs -= 1
+                       assert not args.last isa ANamedargExpr
+                       map.map[msignature.arity - 1] = args.length - 1
+                       self.visit_expr_subtype(args.last, msignature.mparameters.last.mtype)
+               end
+
+               # First, handle named arguments
+               for i in [0..args.length[ do
+                       var e = args[i]
+                       if not e isa ANamedargExpr then continue
+                       var name = e.n_id.text
+                       var param = msignature.mparameter_by_name(name)
+                       if param == null then
+                               modelbuilder.error(e.n_id, "Error: no parameter `{name}` for `{mproperty}{msignature}`.")
+                               return null
+                       end
+                       var idx = msignature.mparameters.index_of(param)
+                       var prev = map.map.get_or_null(idx)
+                       if prev != null then
+                               modelbuilder.error(e, "Error: parameter `{name}` already associated with argument #{prev} for `{mproperty}{msignature}`.")
+                               return null
+                       end
+                       map.map[idx] = i
+                       e.mtype = self.visit_expr_subtype(e.n_expr, param.mtype)
+               end
+
+               # Number of minimum mandatory remaining parameters
+               var min_arity = 0
+
+               # Second, associate remaining parameters
                var vararg_decl = args.length - msignature.arity
+               var j = 0
                for i in [0..msignature.arity[ do
-                       var j = i
-                       if i == vararg_rank then continue # skip the vararg
-                       if i > vararg_rank then
-                               j = i + vararg_decl
+                       # Skip parameters associated by name
+                       if map.map.has_key(i) then continue
+
+                       var param = msignature.mparameters[i]
+
+                       # Search the next free argument: skip named arguments since they are already associated
+                       while j < nbargs and args[j] isa ANamedargExpr do j += 1
+                       if j >= nbargs then
+                               if not param.mtype isa MNullableType then
+                                       min_arity = j + 1
+                               end
+                               j += 1
+                               continue
+                       end
+                       var arg = args[j]
+                       map.map[i] = j
+                       j += 1
+
+                       if i == vararg_rank then
+                               j += vararg_decl
+                               continue # skip the vararg
                        end
-                       var paramtype = msignature.mparameters[i].mtype
-                       self.visit_expr_subtype(args[j], paramtype)
+
+                       if not param.is_vararg then
+                               var paramtype = param.mtype
+                               self.visit_expr_subtype(arg, paramtype)
+                       else
+                               check_one_vararg(arg, param)
+                       end
+               end
+
+               if min_arity > 0 then
+                       if last_is_padded then min_arity += 1
+                       if min_arity < msignature.arity then
+                               modelbuilder.error(node, "Error: expected at least {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                       else
+                               modelbuilder.error(node, "Error: expected {min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                       end
+                       return null
                end
+
+               # Third, check varargs
                if vararg_rank >= 0 then
                        var paramtype = msignature.mparameters[vararg_rank].mtype
                        var first = args[vararg_rank]
-                       if vararg_decl == 0 and first isa AVarargExpr then
-                               var mclass = get_mclass(node, "Array")
-                               if mclass == null then return false # Forward error
-                               var array_mtype = mclass.get_mtype([paramtype])
-                               self.visit_expr_subtype(first.n_expr, array_mtype)
-                               first.mtype  = first.n_expr.mtype
+                       if vararg_decl == 0 then
+                               if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null
                        else
-                               for j in [vararg_rank..vararg_rank+vararg_decl] do
-                                       self.visit_expr_subtype(args[j], paramtype)
+                               first.vararg_decl = vararg_decl + 1
+                               for i in [vararg_rank..vararg_rank+vararg_decl] do
+                                       self.visit_expr_subtype(args[i], paramtype)
                                end
                        end
                end
+
+               return map
+       end
+
+       # Check an expression as a single vararg.
+       # The main point of the method if to handle the case of reversed vararg (see `AVarargExpr`)
+       fun check_one_vararg(arg: AExpr, param: MParameter): Bool
+       do
+               var paramtype = param.mtype
+               var mclass = get_mclass(arg, "Array")
+               if mclass == null then return false # Forward error
+               var array_mtype = mclass.get_mtype([paramtype])
+               if arg isa AVarargExpr then
+                       self.visit_expr_subtype(arg.n_expr, array_mtype)
+                       arg.mtype  = arg.n_expr.mtype
+               else
+                       # only one vararg, maybe `...` was forgot, so be gentle!
+                       var t = visit_expr(arg)
+                       if t == null then return false # Forward error
+                       if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
+                               # Not acceptable but could be a `...`
+                               error(arg, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
+                               return false
+                       end
+                       # Standard valid vararg, finish the job
+                       arg.vararg_decl = 1
+                       self.visit_expr_subtype(arg, paramtype)
+               end
                return true
        end
 
        fun error(node: ANode, message: String)
        do
-               self.modelbuilder.toolcontext.error(node.hot_location, message)
+               self.modelbuilder.error(node, message)
        end
 
        fun get_variable(node: AExpr, variable: Variable): nullable MType
        do
+               if not variable.is_adapted then return variable.declared_type
+
                var flow = node.after_flow_context
-               if flow == null then
-                       self.error(node, "No context!")
-                       return null
-               end
+               if flow == null then return null # skip error
 
                if flow.vars.has_key(variable) then
                        return flow.vars[variable]
@@ -450,26 +589,64 @@ private class TypeVisitor
                        #node.debug("*** START Collected for {variable}")
                        var mtypes = flow.collect_types(variable)
                        #node.debug("**** END Collected for {variable}")
-                       if mtypes == null or mtypes.length == 0 then
+                       if mtypes.length == 0 then
                                return variable.declared_type
                        else if mtypes.length == 1 then
                                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
        end
 
+       # Some variables where type-adapted during the visit
+       var dirty = false
+
+       # Some loops had been visited during the visit
+       var has_loop = false
+
        fun set_variable(node: AExpr, variable: Variable, mtype: nullable MType)
        do
                var flow = node.after_flow_context
                assert flow != null
 
-               flow.set_var(variable, mtype)
+               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
@@ -491,12 +668,99 @@ 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.
+#
+# Parameters and arguments are not stored in the class but referenced by their position (starting from 0)
+#
+# The point of this class is to help engine and other things to map arguments in the AST to parameters of the model.
+class SignatureMap
+       # Associate a parameter to an argument
+       var map = new ArrayMap[Int, Int]
 end
 
 # A specific method call site with its associated informations.
 class CallSite
-       # The associated node for location
-       var node: ANode
+       super MEntity
+
+       redef var location
 
        # The static type of the receiver (possibly unresolved)
        var recv: MType
@@ -525,58 +789,81 @@ class CallSite
        # Is a implicit cast required on erasure typing policy?
        var erasure_cast: Bool
 
-       private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool
+       # The mapping used on the call to associate arguments to parameters
+       # If null then no specific association is required.
+       var signaturemap: nullable SignatureMap = null
+
+       private fun check_signature(v: TypeVisitor, node: ANode, args: Array[AExpr]): Bool
        do
-               return v.check_signature(self.node, args, self.mproperty.name, self.msignature)
+               var map = v.check_signature(node, args, self.mproperty, self.msignature)
+               signaturemap = map
+               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
+       var declared_type: nullable MType = null is writable
+
+       # Was the variable type-adapted?
+       # This is used to speedup type retrieval while it remains `false`
+       private var is_adapted = false
 end
 
 redef class FlowContext
        # Store changes of types because of type evolution
        private var vars = new HashMap[Variable, nullable MType]
-       private var cache = new HashMap[Variable, nullable Array[nullable MType]]
 
        # Adapt the variable to a static type
        # Warning1: do not modify vars directly.
        # Warning2: sub-flow may have cached a unadapted variable
-       private fun set_var(variable: Variable, mtype: nullable MType)
+       private fun set_var(v: TypeVisitor, variable: Variable, mtype: nullable MType)
        do
+               if variable.declared_type == mtype and not variable.is_adapted then return
+               if vars.has_key(variable) and vars[variable] == mtype then return
                self.vars[variable] = mtype
-               self.cache.keys.remove(variable)
+               v.dirty = true
+               variable.is_adapted = true
+               #node.debug "set {variable} to {mtype or else "X"}"
        end
 
-       private fun collect_types(variable: Variable): nullable Array[nullable MType]
+       # Look in the flow and previous flow and collect all first reachable type adaptation of a local variable
+       private fun collect_types(variable: Variable): Array[nullable MType]
        do
-               if cache.has_key(variable) then
-                       return cache[variable]
-               end
-               var res: nullable Array[nullable MType] = null
-               if vars.has_key(variable) then
-                       var mtype = vars[variable]
-                       res = [mtype]
-               else if self.previous.is_empty then
-                       # Root flow
-                       res = [variable.declared_type]
-               else
-                       for flow in self.previous do
-                               if flow.is_unreachable then continue
-                               var r2 = flow.collect_types(variable)
-                               if r2 == null then continue
-                               if res == null then
-                                       res = r2.to_a
-                               else
-                                       for t in r2 do
-                                               if not res.has(t) then res.add(t)
-                                       end
+               #node.debug "flow for {variable}"
+               var res = new Array[nullable MType]
+
+               var todo = [self]
+               var seen = new HashSet[FlowContext]
+               while not todo.is_empty do
+                       var f = todo.pop
+                       if f.is_unreachable then continue
+                       if seen.has(f) then continue
+                       seen.add f
+
+                       if f.vars.has_key(variable) then
+                               # Found something. Collect it and do not process further on this path
+                               res.add f.vars[variable]
+                               #f.node.debug "process {variable}: got {f.vars[variable] or else "X"}"
+                       else
+                               todo.add_all f.previous
+                               todo.add_all f.loops
+                               if f.previous.is_empty then
+                                       # Root flowcontext mean a parameter or something related
+                                       res.add variable.declared_type
+                                       #f.node.debug "root process {variable}: got {variable.declared_type or else "X"}"
                                end
                        end
                end
-               cache[variable] = res
+               #self.node.debug "##### end flow for {variable}: {res.join(" ")}"
                return res
        end
 end
@@ -594,17 +881,18 @@ end
 redef class AMethPropdef
        redef fun do_typing(modelbuilder: ModelBuilder)
        do
-               var nblock = self.n_block
-               if nblock == null then return
+               var mpropdef = self.mpropdef
+               if mpropdef == null then return # skip error
 
-               var mpropdef = self.mpropdef.as(not null)
-               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)
-               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])
@@ -613,27 +901,62 @@ redef class AMethPropdef
                        assert variable != null
                        variable.declared_type = mtype
                end
-               v.visit_stmt(nblock)
 
-               if not nblock.after_flow_context.is_unreachable and mmethoddef.msignature.return_mtype != null then
+               var nblock = self.n_block
+               if nblock == null then return
+
+               loop
+                       v.dirty = false
+                       v.visit_stmt(nblock)
+                       if not v.has_loop or not v.dirty then break
+               end
+
+               var post_visitor = new PostTypingVisitor(v)
+               post_visitor.enter_visit(self)
+
+               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).")
+                       v.error(self, "Error: reached end of function; expected `return` with a value.")
+               end
+       end
+end
+
+private class PostTypingVisitor
+       super Visitor
+       var type_visitor: TypeVisitor
+       redef fun visit(n) do
+               n.visit_all(self)
+               n.accept_post_typing(type_visitor)
+               if n isa AExpr and n.mtype == null and not n.is_typed then
+                       n.is_broken = true
                end
        end
 end
 
+redef class ANode
+       private fun accept_post_typing(v: TypeVisitor) do end
+
+       # An additional information message to explain the role of a child expression.
+       #
+       # The point of the method is to allow some kind of double dispatch so the parent
+       # choose how to describe its children.
+       private fun bad_expr_message(child: AExpr): nullable String do return null
+end
+
 redef class AAttrPropdef
        redef fun do_typing(modelbuilder: ModelBuilder)
        do
                if not has_value then return
 
-               var mpropdef = self.mpropdef.as(not null)
-               var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
+               var mpropdef = self.mreadpropdef
+               if mpropdef == null or mpropdef.msignature == null then return # skip error
+
+               var v = new TypeVisitor(modelbuilder, mpropdef)
                self.selfvariable = v.selfvariable
 
                var nexpr = self.n_expr
                if nexpr != null then
-                       var mtype = self.mpropdef.static_mtype
+                       var mtype = self.mtype
                        v.visit_expr_subtype(nexpr, mtype)
                end
                var nblock = self.n_block
@@ -641,7 +964,7 @@ redef class AAttrPropdef
                        v.visit_stmt(nblock)
                        if not nblock.after_flow_context.is_unreachable then
                                # We reach the end of the init without having a return, it is bad
-                               v.error(self, "Control error: Reached end of block (a 'return' with a value was expected).")
+                               v.error(self, "Error: reached end of block; expected `return`.")
                        end
                end
        end
@@ -678,6 +1001,28 @@ redef class AExpr
        # The result of the evaluation of `self` must be
        # stored inside the designated array (there is an implicit `push`)
        var comprehension: nullable AArrayExpr = null
+
+       # It indicates the number of arguments collected as a vararg.
+       #
+       # When 0, the argument is used as is, without transformation.
+       # When 1, the argument is transformed into an singleton array.
+       # Above 1, the arguments and the next ones are transformed into a common array.
+       #
+       # 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
@@ -726,7 +1071,9 @@ redef class AVardeclExpr
 
                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
 
@@ -735,6 +1082,7 @@ redef class AVardeclExpr
 
                #debug("var {variable}: {mtype}")
 
+               self.mtype = mtype
                self.is_typed = true
        end
 end
@@ -784,18 +1132,11 @@ redef class AReassignFormExpr
        # Return the static type of the value to store.
        private fun resolve_reassignment(v: TypeVisitor, readtype, writetype: MType): nullable MType
        do
-               var reassign_name: String
-               if self.n_assign_op isa APlusAssignOp then
-                       reassign_name = "+"
-               else if self.n_assign_op isa AMinusAssignOp then
-                       reassign_name = "-"
-               else
-                       abort
-               end
+               var reassign_name = self.n_assign_op.operator
 
                self.read_type = readtype
 
-               var callsite = v.get_method(self, readtype, reassign_name, false)
+               var callsite = v.build_callsite_by_name(self.n_assign_op, readtype, reassign_name, false)
                if callsite == null then return null # Skip error
                self.reassign_callsite = callsite
 
@@ -806,7 +1147,7 @@ redef class AReassignFormExpr
                var value_type = v.visit_expr_subtype(self.n_value, msignature.mparameters.first.mtype)
                if value_type == null then return null # Skip error
 
-               v.check_subtype(self, rettype, writetype)
+               v.check_subtype(self, rettype, writetype, false)
                return rettype
        end
 end
@@ -829,11 +1170,10 @@ redef class AVarReassignExpr
 
                v.set_variable(self, variable, rettype)
 
-               self.is_typed = true
+               self.is_typed = rettype != null
        end
 end
 
-
 redef class AContinueExpr
        redef fun accept_typing(v)
        do
@@ -874,10 +1214,12 @@ redef class AReturnExpr
                                v.visit_expr_subtype(nexpr, ret_type)
                        else
                                v.visit_expr(nexpr)
-                               v.error(self, "Error: Return with value in a procedure.")
+                               v.error(nexpr, "Error: `return` with value in a procedure.")
+                               return
                        end
                else if ret_type != null then
-                       v.error(self, "Error: Return without value in a function.")
+                       v.error(self, "Error: `return` without value in a function.")
+                       return
                end
                self.is_typed = true
        end
@@ -920,7 +1262,7 @@ redef class AIfexprExpr
 
                var t = v.merge_types(self, [t1, t2])
                if t == null then
-                       v.error(self, "Type Error: ambiguous type {t1} vs {t2}")
+                       v.error(self, "Type Error: ambiguous type `{t1}` vs `{t2}`.")
                end
                self.mtype = t
        end
@@ -930,6 +1272,7 @@ redef class ADoExpr
        redef fun accept_typing(v)
        do
                v.visit_stmt(n_block)
+               v.visit_stmt(n_catch)
                self.is_typed = true
        end
 end
@@ -937,8 +1280,8 @@ end
 redef class AWhileExpr
        redef fun accept_typing(v)
        do
+               v.has_loop = true
                v.visit_expr_bool(n_expr)
-
                v.visit_stmt(n_block)
                self.is_typed = true
        end
@@ -947,12 +1290,32 @@ end
 redef class ALoopExpr
        redef fun accept_typing(v)
        do
+               v.has_loop = true
                v.visit_stmt(n_block)
                self.is_typed = true
        end
 end
 
 redef class AForExpr
+       redef fun accept_typing(v)
+       do
+               v.has_loop = true
+
+               for g in n_groups do
+                       var mtype = v.visit_expr(g.n_expr)
+                       if mtype == null then return
+                       g.do_type_iterator(v, mtype)
+                       if g.is_broken then is_broken = true
+               end
+
+               v.visit_stmt(n_block)
+
+               self.mtype = n_block.mtype
+               self.is_typed = true
+       end
+end
+
+redef class AForGroup
        var coltype: nullable MClassType
 
        var method_iterator: nullable CallSite
@@ -968,7 +1331,7 @@ redef class AForExpr
        private fun do_type_iterator(v: TypeVisitor, mtype: MType)
        do
                if mtype isa MNullType then
-                       v.error(self, "Type error: 'for' cannot iterate over 'null'")
+                       v.error(self, "Type Error: `for` cannot iterate over `null`.")
                        return
                end
 
@@ -977,9 +1340,9 @@ redef class AForExpr
                if objcla == null then return
 
                # check iterator method
-               var itdef = v.get_method(self, mtype, "iterator", n_expr isa ASelfExpr)
+               var itdef = v.build_callsite_by_name(self, mtype, "iterator", n_expr isa ASelfExpr)
                if itdef == null then
-                       v.error(self, "Type Error: 'for' expects a type providing 'iterator' method, got '{mtype}'.")
+                       v.error(self, "Type Error: `for` expects a type providing an `iterator` method, got `{mtype}`.")
                        return
                end
                self.method_iterator = itdef
@@ -987,7 +1350,7 @@ redef class AForExpr
                # check that iterator return something
                var ittype = itdef.msignature.return_mtype
                if ittype == null then
-                       v.error(self, "Type Error: 'for' expects method 'iterator' to return an 'Iterator' or 'MapIterator' type'.")
+                       v.error(self, "Type Error: `for` expects the method `iterator` to return an `Iterator` or `MapIterator` type.")
                        return
                end
 
@@ -1002,7 +1365,7 @@ redef class AForExpr
                        var coltype = ittype.supertype_to(v.mmodule, v.anchor, colit_cla)
                        var variables =  self.variables
                        if variables.length != 1 then
-                               v.error(self, "Type Error: 'for' expects only one variable when using 'Iterator'.")
+                               v.error(self, "Type Error: `for` expects only one variable when using `Iterator`.")
                        else
                                variables.first.declared_type = coltype.arguments.first
                        end
@@ -1014,7 +1377,7 @@ redef class AForExpr
                        var coltype = ittype.supertype_to(v.mmodule, v.anchor, mapit_cla)
                        var variables = self.variables
                        if variables.length != 2 then
-                               v.error(self, "Type Error: 'for' expects two variables when using 'MapIterator'.")
+                               v.error(self, "Type Error: `for` expects two variables when using `MapIterator`.")
                        else
                                variables[0].declared_type = coltype.arguments[0]
                                variables[1].declared_type = coltype.arguments[1]
@@ -1023,7 +1386,7 @@ redef class AForExpr
                end
 
                if not is_col and not is_map then
-                       v.error(self, "Type Error: 'for' expects method 'iterator' to return an 'Iterator' or 'MapIterator' type'.")
+                       v.error(self, "Type Error: `for` expects the method `iterator` to return an `Iterator` or `MapIterator` type.")
                        return
                end
 
@@ -1034,33 +1397,33 @@ redef class AForExpr
                self.coltype = mtype.as(MClassType)
 
                # get methods is_ok, next, item
-               var ikdef = v.get_method(self, ittype, "is_ok", false)
+               var ikdef = v.build_callsite_by_name(self, ittype, "is_ok", false)
                if ikdef == null then
-                       v.error(self, "Type Error: 'for' expects a method 'is_ok' in 'Iterator' type {ittype}.")
+                       v.error(self, "Type Error: `for` expects a method `is_ok` in type `{ittype}`.")
                        return
                end
                self.method_is_ok = ikdef
 
-               var itemdef = v.get_method(self, ittype, "item", false)
+               var itemdef = v.build_callsite_by_name(self, ittype, "item", false)
                if itemdef == null then
-                       v.error(self, "Type Error: 'for' expects a method 'item' in 'Iterator' type {ittype}.")
+                       v.error(self, "Type Error: `for` expects a method `item` in type `{ittype}`.")
                        return
                end
                self.method_item = itemdef
 
-               var nextdef = v.get_method(self, ittype, "next", false)
+               var nextdef = v.build_callsite_by_name(self, ittype, "next", false)
                if nextdef == null then
-                       v.error(self, "Type Error: 'for' expects a method 'next' in 'Iterator' type {ittype}.")
+                       v.error(self, "Type Error: `for` expects a method `next` in type {ittype}.")
                        return
                end
                self.method_next = nextdef
 
-               self.method_finish = v.try_get_method(self, ittype, "finish", false)
+               self.method_finish = v.try_build_callsite_by_name(self, ittype, "finish", false)
 
                if is_map then
-                       var keydef = v.get_method(self, ittype, "key", false)
+                       var keydef = v.build_callsite_by_name(self, ittype, "key", false)
                        if keydef == null then
-                               v.error(self, "Type Error: 'for' expects a method 'key' in 'Iterator' type {ittype}.")
+                               v.error(self, "Type Error: `for` expects a method `key` in type `{ittype}`.")
                                return
                        end
                        self.method_key = keydef
@@ -1071,21 +1434,27 @@ redef class AForExpr
                        var vtype = variable.declared_type.as(not null)
 
                        if n_expr isa AOrangeExpr then
-                               self.method_lt = v.get_method(self, vtype, "<", false)
+                               self.method_lt = v.build_callsite_by_name(self, vtype, "<", false)
                        else
-                               self.method_lt = v.get_method(self, vtype, "<=", false)
+                               self.method_lt = v.build_callsite_by_name(self, vtype, "<=", false)
                        end
 
-                       self.method_successor = v.get_method(self, vtype, "successor", false)
+                       self.method_successor = v.build_callsite_by_name(self, vtype, "successor", false)
                end
        end
+end
 
-       redef fun accept_typing(v)
+redef class AWithExpr
+       var method_start: nullable CallSite
+       var method_finish: nullable CallSite
+
+       redef fun accept_typing(v: TypeVisitor)
        do
                var mtype = v.visit_expr(n_expr)
                if mtype == null then return
 
-               self.do_type_iterator(v, mtype)
+               method_start = v.build_callsite_by_name(self, mtype, "start", n_expr isa ASelfExpr)
+               method_finish = v.build_callsite_by_name(self, mtype, "finish", n_expr isa ASelfExpr)
 
                v.visit_stmt(n_block)
                self.mtype = n_block.mtype
@@ -1130,7 +1499,6 @@ redef class AAndExpr
        end
 end
 
-
 redef class ANotExpr
        redef fun accept_typing(v)
        do
@@ -1150,14 +1518,17 @@ redef class AOrElseExpr
                end
 
                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
+                       self.mtype = t2
+                       return
+               else if v.can_be_null(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 v.can_be_null(t2) then
                                t = t.as_nullable
                        end
@@ -1165,6 +1536,16 @@ redef class AOrElseExpr
                end
                self.mtype = t
        end
+
+       redef fun accept_post_typing(v)
+       do
+               var t1 = n_expr.mtype
+               if t1 == null then
+                       return
+               else
+                       v.check_can_be_null(n_expr, t1)
+               end
+       end
 end
 
 redef class ATrueExpr
@@ -1181,10 +1562,25 @@ redef class AFalseExpr
        end
 end
 
-redef class AIntExpr
+redef class AIntegerExpr
        redef fun accept_typing(v)
        do
-               var mclass = v.get_mclass(self, "Int")
+               var mclass: nullable MClass = null
+               if value isa Byte then
+                       mclass = v.get_mclass(self, "Byte")
+               else if value isa Int then
+                       mclass = v.get_mclass(self, "Int")
+               else if value isa Int8 then
+                       mclass = v.get_mclass(self, "Int8")
+               else if value isa Int16 then
+                       mclass = v.get_mclass(self, "Int16")
+               else if value isa UInt16 then
+                       mclass = v.get_mclass(self, "UInt16")
+               else if value isa Int32 then
+                       mclass = v.get_mclass(self, "Int32")
+               else if value isa UInt32 then
+                       mclass = v.get_mclass(self, "UInt32")
+               end
                if mclass == null then return # Forward error
                self.mtype = mclass.mclass_type
        end
@@ -1200,31 +1596,76 @@ redef class AFloatExpr
 end
 
 redef class ACharExpr
-       redef fun accept_typing(v)
-       do
-               var mclass = v.get_mclass(self, "Char")
+       redef fun accept_typing(v) do
+               var mclass: nullable MClass = null
+               if is_code_point then
+                       mclass = v.get_mclass(self, "Int")
+               else
+                       mclass = v.get_mclass(self, "Char")
+               end
                if mclass == null then return # Forward error
                self.mtype = mclass.mclass_type
        end
 end
 
-redef class AStringFormExpr
-       redef fun accept_typing(v)
-       do
+redef class AugmentedStringFormExpr
+       super AExpr
+
+       # Text::to_re, used for prefix `re`
+       var to_re: nullable CallSite = null
+       # Regex::ignore_case, used for suffix `i` on `re`
+       var ignore_case: nullable CallSite = null
+       # Regex::newline, used for suffix `m` on `re`
+       var newline: nullable CallSite = null
+       # Regex::extended, used for suffix `b` on `re`
+       var extended: nullable CallSite = null
+       # CString::to_bytes_with_copy, used for prefix `b`
+       var to_bytes_with_copy: nullable CallSite = null
+
+       redef fun accept_typing(v) do
                var mclass = v.get_mclass(self, "String")
                if mclass == null then return # Forward error
-               self.mtype = mclass.mclass_type
+               if is_bytestring then
+                       to_bytes_with_copy = v.build_callsite_by_name(self, v.mmodule.c_string_type, "to_bytes_with_copy", false)
+                       mclass = v.get_mclass(self, "Bytes")
+               else if is_re then
+                       to_re = v.build_callsite_by_name(self, mclass.mclass_type, "to_re", false)
+                       for i in suffix.chars do
+                               mclass = v.get_mclass(self, "Regex")
+                               if mclass == null then
+                                       v.error(self, "Error: `Regex` class unknown")
+                                       return
+                               end
+                               var service = ""
+                               if i == 'i' then
+                                       service = "ignore_case="
+                                       ignore_case = v.build_callsite_by_name(self, mclass.mclass_type, service, false)
+                               else if i == 'm' then
+                                       service = "newline="
+                                       newline = v.build_callsite_by_name(self, mclass.mclass_type, service, false)
+                               else if i == 'b' then
+                                       service = "extended="
+                                       extended = v.build_callsite_by_name(self, mclass.mclass_type, service, false)
+                               else
+                                       v.error(self, "Type Error: Unrecognized suffix {i} in prefixed Regex")
+                                       abort
+                               end
+                       end
+               end
+               if mclass == null then return # Forward error
+               mtype = mclass.mclass_type
        end
 end
 
 redef class ASuperstringExpr
        redef fun accept_typing(v)
        do
-               var mclass = v.get_mclass(self, "String")
-               if mclass == null then return # Forward error
-               self.mtype = mclass.mclass_type
+               super
+               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
@@ -1272,13 +1713,15 @@ redef class AArrayExpr
                        end
                        set_comprehension(e)
                        if mtype != null then
-                               if v.check_subtype(e, t, mtype) == null then return # Skip error
+                               if v.check_subtype(e, t, mtype, false) == null then return # Forward error
                                if t == mtype then useless = true
                        else
                                mtypes.add(t)
                        end
                end
                if mtype == null then
+                       # Ensure monotony for type adaptation on loops
+                       if self.element_mtype != null then mtypes.add self.element_mtype
                        mtype = v.merge_types(self, mtypes)
                end
                if mtype == null or mtype isa MNullType then
@@ -1296,8 +1739,8 @@ redef class AArrayExpr
                if mclass == null then return # Forward error
                var array_mtype = mclass.get_mtype([mtype])
 
-               with_capacity_callsite = v.get_method(self, array_mtype, "with_capacity", false)
-               push_callsite = v.get_method(self, array_mtype, "push", false)
+               with_capacity_callsite = v.build_callsite_by_name(self, array_mtype, "with_capacity", false)
+               push_callsite = v.build_callsite_by_name(self, array_mtype, "push", false)
 
                self.mtype = array_mtype
        end
@@ -1322,7 +1765,7 @@ redef class ARangeExpr
                else if v.is_subtype(t2, t1) then
                        mtype = mclass.get_mtype([t1])
                else
-                       v.error(self, "Type Error: Cannot create range: {t1} vs {t2}")
+                       v.error(self, "Type Error: cannot create range: `{t1}` vs `{t2}`.")
                        return
                end
 
@@ -1331,9 +1774,9 @@ redef class ARangeExpr
                # get the constructor
                var callsite
                if self isa ACrangeExpr then
-                       callsite = v.get_method(self, mtype, "init", false)
+                       callsite = v.build_callsite_by_name(self, mtype, "init", false)
                else if self isa AOrangeExpr then
-                       callsite = v.get_method(self, mtype, "without_last", false)
+                       callsite = v.build_callsite_by_name(self, mtype, "without_last", false)
                else
                        abort
                end
@@ -1354,26 +1797,62 @@ redef class AIsaExpr
        var cast_type: nullable MType
        redef fun accept_typing(v)
        do
-               var mtype = v.visit_expr_cast(self, self.n_expr, self.n_type)
+               v.visit_expr(n_expr)
+
+               var mtype = v.resolve_mtype(n_type)
+
                self.cast_type = mtype
 
                var variable = self.n_expr.its_variable
                if variable != null then
-                       #var orig = self.n_expr.mtype
+                       var orig = self.n_expr.mtype
                        #var from = if orig != null then orig.to_s else "invalid"
                        #var to = if mtype != null then mtype.to_s else "invalid"
                        #debug("adapt {variable}: {from} -> {to}")
-                       self.after_flow_context.when_true.set_var(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
 
                self.mtype = v.type_bool(self)
        end
+
+       redef fun accept_post_typing(v)
+       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
        redef fun accept_typing(v)
        do
-               self.mtype = v.visit_expr_cast(self, self.n_expr, self.n_type)
+               v.visit_expr(n_expr)
+
+               self.mtype = v.resolve_mtype(n_type)
+       end
+
+       redef fun accept_post_typing(v)
+       do
+               v.check_expr_cast(self, self.n_expr, self.n_type)
        end
 end
 
@@ -1384,16 +1863,23 @@ redef class AAsNotnullExpr
                if mtype == null then return # Forward error
 
                if mtype isa MNullType then
-                       v.error(self, "Type error: as(not null) on null")
+                       v.error(self, "Type Error: `as(not null)` on `null`.")
                        return
                end
 
-               if v.check_can_be_null(n_expr, mtype) then
+               if v.can_be_null(mtype) then
                        mtype = mtype.as_notnull
                end
 
                self.mtype = mtype
        end
+
+       redef fun accept_post_typing(v)
+       do
+               var mtype = n_expr.mtype
+               if mtype == null then return
+               v.check_can_be_null(n_expr, mtype)
+       end
 end
 
 redef class AParExpr
@@ -1415,7 +1901,7 @@ redef class ASelfExpr
        redef fun accept_typing(v)
        do
                if v.is_toplevel_context and not self isa AImplicitSelfExpr then
-                       v.error(self, "Error: self cannot be used in top-level method.")
+                       v.error(self, "Error: `self` cannot be used in top-level method.")
                end
                var variable = v.selfvariable
                self.its_variable = variable
@@ -1423,40 +1909,101 @@ redef class ASelfExpr
        end
 end
 
+redef class AImplicitSelfExpr
+       # Is the implicit receiver `sys`?
+       #
+       # By default, the implicit receiver is `self`.
+       # But when there is not method for `self`, `sys` is used as a fall-back.
+       # Is this case this flag is set to `true`.
+       var is_sys = false
+end
+
 ## MESSAGE SENDING AND PROPERTY
 
 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
+                       return "to be the receiver of `{self.property_name}`"
+               end
+               return null
+       end
+
        redef fun accept_typing(v)
        do
-               var recvtype = v.visit_expr(self.n_expr)
+               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
 
                if recvtype == null then return # Forward error
 
-               var callsite = v.get_method(self, recvtype, name, self.n_expr isa ASelfExpr)
-               if callsite == null then return
+               var callsite = null
+               var unsafe_type = v.anchor_to(recvtype)
+               var mproperty = v.try_get_mproperty_by_name2(node, unsafe_type, name)
+               if mproperty == null and nrecv isa AImplicitSelfExpr then
+                       # Special fall-back search in `sys` when noting found in the implicit receiver.
+                       var sysclass = v.try_get_mclass(node, "Sys")
+                       if sysclass != null then
+                               var systype = sysclass.mclass_type
+                               mproperty = v.try_get_mproperty_by_name2(node, systype, name)
+                               if mproperty != null then
+                                       callsite = v.build_callsite_by_name(node, systype, name, false)
+                                       if callsite == null then return # Forward error
+                                       # Update information, we are looking at `sys` now, not `self`
+                                       nrecv.is_sys = true
+                                       nrecv.its_variable = null
+                                       nrecv.mtype = systype
+                                       recvtype = systype
+                               end
+                       end
+               end
+               if callsite == null then
+                       # If still nothing, just exit
+                       callsite = v.build_callsite_by_name(node, recvtype, name, nrecv isa ASelfExpr)
+                       if callsite == null then return
+               end
+
                self.callsite = callsite
                var msignature = callsite.msignature
 
                var args = compute_raw_arguments
 
-               callsite.check_signature(v, args)
+                if not self isa ACallrefExpr then
+                       callsite.check_signature(v, node, args)
+                end
 
                if callsite.mproperty.is_init then
                        var vmpropdef = v.mpropdef
                        if not (vmpropdef isa MMethodDef and vmpropdef.mproperty.is_init) then
-                               v.error(self, "Can call a init only in another init")
+                               v.error(node, "Error: an `init` can only be called from another `init`.")
                        end
                        if vmpropdef isa MMethodDef and vmpropdef.mproperty.is_root_init and not callsite.mproperty.is_root_init then
-                               v.error(self, "Error: {vmpropdef} cannot call a factory {callsite.mproperty}")
+                               v.error(node, "Error: `{vmpropdef}` cannot call a factory `{callsite.mproperty}`.")
                        end
                end
 
                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
@@ -1467,84 +2014,70 @@ redef class ASendExpr
        # Each subclass simply provide the correct name.
        private fun property_name: String is abstract
 
+       # The node identifying the name (id, operator, etc) for messages.
+       #
+       # Is `self` by default
+       private fun property_node: ANode do return self
+
        # An array of all arguments (excluding self)
        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
        redef fun compute_raw_arguments do return [n_expr2]
+       redef fun property_name do return operator
+       redef fun property_node do return n_op
 end
-redef class AEqExpr
-       redef fun property_name do return "=="
+
+redef class AEqFormExpr
        redef fun accept_typing(v)
        do
                super
                v.null_test(self)
        end
-end
-redef class ANeExpr
-       redef fun property_name do return "!="
-       redef fun accept_typing(v)
+
+       redef fun accept_post_typing(v)
        do
-               super
-               v.null_test(self)
+               var mtype = n_expr.mtype
+               var mtype2 = n_expr2.mtype
+
+               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)
        end
 end
-redef class ALtExpr
-       redef fun property_name do return "<"
-end
-redef class ALeExpr
-       redef fun property_name do return "<="
-end
-redef class ALlExpr
-       redef fun property_name do return "<<"
-end
-redef class AGtExpr
-       redef fun property_name do return ">"
-end
-redef class AGeExpr
-       redef fun property_name do return ">="
-end
-redef class AGgExpr
-       redef fun property_name do return ">>"
-end
-redef class APlusExpr
-       redef fun property_name do return "+"
-end
-redef class AMinusExpr
-       redef fun property_name do return "-"
-end
-redef class AStarshipExpr
-       redef fun property_name do return "<=>"
-end
-redef class AStarExpr
-       redef fun property_name do return "*"
-end
-redef class AStarstarExpr
-       redef fun property_name do return "**"
-end
-redef class ASlashExpr
-       redef fun property_name do return "/"
-end
-redef class APercentExpr
-       redef fun property_name do return "%"
-end
 
-redef class AUminusExpr
-       redef fun property_name do return "unary -"
+redef class AUnaryopExpr
+       redef fun property_name do return "unary {operator}"
        redef fun compute_raw_arguments do return new Array[AExpr]
 end
 
-
 redef class ACallExpr
-       redef fun property_name do return n_id.text
+       redef fun property_name do return n_qid.n_id.text
+       redef fun property_node do return n_qid
        redef fun compute_raw_arguments do return n_args.to_a
 end
 
 redef class ACallAssignExpr
-       redef fun property_name do return n_id.text + "="
+       redef fun property_name do return n_qid.n_id.text + "="
+       redef fun property_node do return n_qid
        redef fun compute_raw_arguments
        do
                var res = n_args.to_a
@@ -1576,26 +2109,27 @@ redef class ASendReassignFormExpr
        do
                var recvtype = v.visit_expr(self.n_expr)
                var name = self.property_name
+               var node = self.property_node
 
                if recvtype == null then return # Forward error
 
                var for_self = self.n_expr isa ASelfExpr
-               var callsite = v.get_method(self, recvtype, name, for_self)
+               var callsite = v.build_callsite_by_name(node, recvtype, name, for_self)
 
                if callsite == null then return
                self.callsite = callsite
 
                var args = compute_raw_arguments
 
-               callsite.check_signature(v, args)
+               callsite.check_signature(v, node, args)
 
                var readtype = callsite.msignature.return_mtype
                if readtype == null then
-                       v.error(self, "Error: {name} is not a function")
+                       v.error(node, "Error: `{name}` is not a function.")
                        return
                end
 
-               var wcallsite = v.get_method(self, recvtype, name + "=", self.n_expr isa ASelfExpr)
+               var wcallsite = v.build_callsite_by_name(node, recvtype, name + "=", self.n_expr isa ASelfExpr)
                if wcallsite == null then return
                self.write_callsite = wcallsite
 
@@ -1604,14 +2138,15 @@ redef class ASendReassignFormExpr
 
                args = args.to_a # duplicate so raw_arguments keeps only the getter args
                args.add(self.n_value)
-               wcallsite.check_signature(v, args)
+               wcallsite.check_signature(v, node, args)
 
                self.is_typed = true
        end
 end
 
 redef class ACallReassignExpr
-       redef fun property_name do return n_id.text
+       redef fun property_name do return n_qid.n_id.text
+       redef fun property_node do return n_qid.n_id
        redef fun compute_raw_arguments do return n_args.to_a
 end
 
@@ -1622,9 +2157,81 @@ end
 
 redef class AInitExpr
        redef fun property_name do return "init"
+       redef fun property_node do return n_kwinit
        redef fun compute_raw_arguments do return n_args.to_a
 end
 
+redef class ACallrefExpr
+       redef fun property_name do return n_qid.n_id.text
+       redef fun property_node do return n_qid
+       redef fun compute_raw_arguments do return n_args.to_a
+
+       redef fun accept_typing(v)
+       do
+               super # do the job as if it was a real call
+               var res = callsite.mproperty
+
+                var msignature = callsite.mpropdef.msignature
+                var recv = callsite.recv
+                assert msignature != null
+                var arity = msignature.mparameters.length
+
+                var routine_type_name = "ProcRef"
+                if msignature.return_mtype != null then
+                        routine_type_name = "FunRef"
+                end
+
+                var target_routine_class = "{routine_type_name}{arity}"
+                var routine_mclass = v.get_mclass(self, target_routine_class)
+
+                if routine_mclass == null then
+                        v.error(self, "Error: missing functional types, try `import functional`")
+                        return
+                end
+
+                var types_list = new Array[MType]
+                for param in msignature.mparameters do
+                        if param.is_vararg then
+                                types_list.push(v.mmodule.array_type(param.mtype))
+                        else
+                                types_list.push(param.mtype)
+                        end
+                end
+                if msignature.return_mtype != null then
+                        types_list.push(msignature.return_mtype.as(not null))
+                end
+
+                # Why we need an anchor :
+                #
+                # ~~~~nitish
+                # class A[E]
+                #       def toto(x: E) do print "{x}"
+                # end
+                #
+                # var a = new A[Int]
+                # var f = &a.toto # without anchor : ProcRef1[E]
+                #                # with anchor : ProcRef[Int]
+                # ~~~~
+               # However, we can only anchor if we can resolve every formal
+               # parameter, here's an example where we can't.
+               # ~~~~nitish
+               # class A[E]
+               #       fun bar: A[E] do return self
+               #       fun foo: Fun0[A[E]] do return &bar # here we can't anchor
+               # end
+               # var f1 = a1.foo # when this expression will be evaluated,
+               #                 # `a1` will anchor `&bar` returned by `foo`.
+               # print f1.call
+               # ~~~~
+               var routine_type = routine_mclass.get_mtype(types_list)
+               if not recv.need_anchor then
+                       routine_type = routine_type.anchor_to(v.mmodule, recv.as(MClassType))
+               end
+                is_typed = true
+               self.mtype = routine_type
+       end
+end
+
 redef class AExprs
        fun to_a: Array[AExpr] do return self.n_exprs.to_a
 end
@@ -1643,12 +2250,11 @@ 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
                if not mproperty isa MMethod then
-                       v.error(self, "Error: super only usable in a method")
+                       v.error(self, "Error: `super` only usable in a `method`.")
                        return
                end
                var superprops = mproperty.lookup_super_definitions(v.mmodule, anchor)
@@ -1657,7 +2263,7 @@ redef class ASuperExpr
                                process_superinit(v)
                                return
                        end
-                       v.error(self, "Error: No super method to call for {mproperty}.")
+                       v.error(self, "Error: no super method to call for `{mproperty}`.")
                        return
                end
                # FIXME: covariance of return type in linear extension?
@@ -1667,7 +2273,7 @@ redef class ASuperExpr
                msignature = v.resolve_for(msignature, recvtype, true).as(MSignature)
                var args = self.n_args.to_a
                if args.length > 0 then
-                       v.check_signature(self, args, mproperty.name, msignature)
+                       signaturemap = v.check_signature(self, args, mproperty, msignature)
                end
                self.mtype = msignature.return_mtype
                self.is_typed = true
@@ -1675,10 +2281,13 @@ redef class ASuperExpr
                mpropdef = v.mpropdef.as(MMethodDef)
        end
 
+       # The mapping used on the call to associate arguments to parameters.
+       # If null then no specific association is required.
+       var signaturemap: nullable SignatureMap
+
        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
@@ -1697,7 +2306,7 @@ redef class ASuperExpr
                                continue
                        end
                        if superprop != null and superprop.mproperty != candidate and not superprop.mproperty.is_root_init then
-                               v.error(self, "Error: conflicting super constructor to call for {mproperty}: {candidate.full_name}, {superprop.mproperty.full_name}")
+                               v.error(self, "Error: conflicting super constructor to call for `{mproperty}`: `{candidate.full_name}`, `{superprop.mproperty.full_name}`")
                                return
                        end
                        var candidatedefs = candidate.lookup_definitions(v.mmodule, anchor)
@@ -1706,29 +2315,29 @@ redef class ASuperExpr
                                candidatedefs.add(superprop)
                        end
                        if candidatedefs.length > 1 then
-                               v.error(self, "Error: conflicting property definitions for property {mproperty} in {recvtype}: {candidatedefs.join(", ")}")
+                               v.error(self, "Error: conflicting property definitions for property `{mproperty}` in `{recvtype}`: {candidatedefs.join(", ")}")
                                return
                        end
                        superprop = candidatedefs.first
                end
                if superprop == null then
-                       v.error(self, "Error: No super method to call for {mproperty}.")
+                       v.error(self, "Error: no super method to call for `{mproperty}`.")
                        return
                end
 
                var msignature = superprop.new_msignature or else superprop.msignature.as(not null)
                msignature = v.resolve_for(msignature, recvtype, true).as(MSignature)
 
-               var callsite = new CallSite(self, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
+               var callsite = new CallSite(hot_location, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
                self.callsite = callsite
 
                var args = self.n_args.to_a
                if args.length > 0 then
-                       callsite.check_signature(v, args)
+                       callsite.check_signature(v, self, args)
                else
                        # Check there is at least enough parameters
                        if mpropdef.msignature.arity < msignature.arity then
-                               v.error(self, "Error: Not enough implicit arguments to pass. Got {mpropdef.msignature.arity}, expected at least {msignature.arity}. Signature is {msignature}")
+                               v.error(self, "Error: not enough implicit arguments to pass. Got `{mpropdef.msignature.arity}`, expected at least `{msignature.arity}`. Signature is `{msignature}`.")
                                return
                        end
                        # Check that each needed parameter is conform
@@ -1736,7 +2345,7 @@ redef class ASuperExpr
                        for sp in msignature.mparameters do
                                var p = mpropdef.msignature.mparameters[i]
                                if not v.is_subtype(p.mtype, sp.mtype) then
-                                       v.error(self, "Type error: expected argument #{i} of type {sp.mtype}, got implicit argument {p.name} of type {p.mtype}. Signature is {msignature}")
+                                       v.error(self, "Type Error: expected argument #{i} of type `{sp.mtype}`, got implicit argument `{p.name}` of type `{p.mtype}`. Signature is {msignature}")
                                        return
                                end
                                i += 1
@@ -1745,6 +2354,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
 
 ####
@@ -1763,30 +2385,50 @@ redef class ANewExpr
 
                if not recvtype isa MClassType then
                        if recvtype isa MNullableType then
-                               v.error(self, "Type error: cannot instantiate the nullable type {recvtype}.")
+                               v.error(self, "Type Error: cannot instantiate the nullable type `{recvtype}`.")
+                               return
+                       else if recvtype isa MFormalType then
+                               v.error(self, "Type Error: cannot instantiate the formal type `{recvtype}`.")
                                return
                        else
-                               v.error(self, "Type error: cannot instantiate the formal type {recvtype}.")
+                               v.error(self, "Type Error: cannot instantiate the type `{recvtype}`.")
                                return
                        end
                end
 
                self.recvtype = recvtype
+               var kind = recvtype.mclass.kind
 
                var name: String
-               var nid = self.n_id
-               if nid != null then
-                       name = nid.text
+               var nqid = self.n_qid
+               var node: ANode
+               if nqid != null then
+                       name = nqid.n_id.text
+                       node = nqid
                else
                        name = "new"
+                       node = self.n_kwnew
                end
-               var callsite = v.get_method(self, recvtype, name, false)
+               if name == "intern" then
+                       if kind != concrete_kind then
+                               v.error(self, "Type Error: cannot instantiate {kind} {recvtype}.")
+                               return
+                       end
+                       if n_args.n_exprs.not_empty then
+                               v.error(n_args, "Type Error: the intern constructor expects no arguments.")
+                               return
+                       end
+                       # Our job is done
+                       self.mtype = recvtype
+                       return
+               end
+
+               var callsite = v.build_callsite_by_name(node, recvtype, name, false)
                if callsite == null then return
 
                if not callsite.mproperty.is_new then
-                       var kind = recvtype.mclass.kind
                        if kind != concrete_kind then
-                               v.error(self, "Type Error: Cannot instantiate {kind} {recvtype}.")
+                               v.error(self, "Type Error: cannot instantiate {kind} `{recvtype}`.")
                                return
                        end
                        self.mtype = recvtype
@@ -1798,39 +2440,49 @@ redef class ANewExpr
                self.callsite = callsite
 
                if not callsite.mproperty.is_init_for(recvtype.mclass) then
-                       v.error(self, "Error: {name} is not a constructor.")
+                       v.error(self, "Error: `{name}` is not a constructor.")
                        return
                end
 
                var args = n_args.to_a
-               callsite.check_signature(v, args)
+               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
 
 ####
 
 redef class AAttrFormExpr
-       # The attribute acceded.
+       # The attribute accessed.
        var mproperty: nullable MAttribute
 
        # The static type of the attribute.
        var attr_type: nullable MType
 
-       # Resolve the attribute acceded.
+       # Resolve the attribute accessed.
        private fun resolve_property(v: TypeVisitor)
        do
                var recvtype = v.visit_expr(self.n_expr)
                if recvtype == null then return # Skip error
-               var name = self.n_id.text
+               var node = self.n_id
+               var name = node.text
                if recvtype isa MNullType then
-                       v.error(self, "Error: Attribute '{name}' access on 'null'.")
+                       v.error(node, "Error: attribute `{name}` access on `null`.")
                        return
                end
 
                var unsafe_type = v.anchor_to(recvtype)
-               var mproperty = v.try_get_mproperty_by_name2(self, unsafe_type, name)
+               var mproperty = v.try_get_mproperty_by_name2(node, unsafe_type, name)
                if mproperty == null then
-                       v.modelbuilder.error(self, "Error: Attribute {name} doesn't exists in {recvtype}.")
+                       v.modelbuilder.error(node, "Error: attribute `{name}` does not exist in `{recvtype}`.")
                        return
                end
                assert mproperty isa MAttribute
@@ -1839,10 +2491,21 @@ redef class AAttrFormExpr
                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
+
+       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
@@ -1853,7 +2516,6 @@ redef class AAttrExpr
        end
 end
 
-
 redef class AAttrAssignExpr
        redef fun accept_typing(v)
        do
@@ -1861,7 +2523,7 @@ redef class AAttrAssignExpr
                var mtype = self.attr_type
 
                v.visit_expr_subtype(self.n_value, mtype)
-               self.is_typed = true
+               self.is_typed = mtype != null
        end
 end
 
@@ -1872,9 +2534,9 @@ redef class AAttrReassignExpr
                var mtype = self.attr_type
                if mtype == null then return # Skip error
 
-               self.resolve_reassignment(v, mtype, mtype)
+               var rettype = self.resolve_reassignment(v, mtype, mtype)
 
-               self.is_typed = true
+               self.is_typed = rettype != null
        end
 end
 
@@ -1888,19 +2550,41 @@ redef class AIssetAttrExpr
                var recvtype = self.n_expr.mtype.as(not null)
                var bound = v.resolve_for(mtype, recvtype, false)
                if bound isa MNullableType then
-                       v.error(self, "Error: isset on a nullable attribute.")
+                       v.error(n_id, "Type Error: `isset` on a nullable attribute.")
                end
                self.mtype = v.type_bool(self)
        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
                # This kind of pseudo-expression can be only processed trough a signature
                # See `check_signature`
                # Other cases are a syntax error.
-               v.error(self, "Syntax error: unexpected `...`")
+               v.error(self, "Syntax Error: unexpected `...`.")
        end
 end