Callref bugfix in interpreter and compilers + autosav
[nit.git] / src / semantize / typing.nit
index df3b33a..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,17 +36,17 @@ private class TypeVisitor
 
        # The module of the analysis
        # Used to correctly query the model
-       var mmodule: MModule
+       var mmodule: MModule is noinit
 
        # The static type of the receiver
        # Mainly used for type tests and type resolutions
-       var anchor: nullable MClassType = null
+       var anchor: MClassType is noinit
 
        # The analyzed mclassdef
-       var mclassdef: nullable MClassDef = null
+       var mclassdef: MClassDef is noinit
 
        # The analyzed property
-       var mpropdef: nullable MPropDef
+       var mpropdef: MPropDef
 
        var selfvariable = new Variable("self")
 
@@ -58,33 +59,25 @@ private class TypeVisitor
        init
        do
                var mpropdef = self.mpropdef
+               var mclassdef = mpropdef.mclassdef
+               mmodule = mclassdef.mmodule
+               self.mclassdef = mclassdef
+               self.anchor = mclassdef.bound_mtype
 
-               if mpropdef != null then
-                       self.mpropdef = mpropdef
-                       var mclassdef = mpropdef.mclassdef
-                       self.mclassdef = mclassdef
-                       self.anchor = mclassdef.bound_mtype
-
-                       var mclass = mclassdef.mclass
+               var mclass = mclassdef.mclass
 
-                       var selfvariable = new Variable("self")
-                       self.selfvariable = selfvariable
-                       selfvariable.declared_type = mclass.mclass_type
+               var selfvariable = new Variable("self")
+               self.selfvariable = selfvariable
+               selfvariable.declared_type = mclass.mclass_type
 
-                       var mprop = mpropdef.mproperty
-                       if mprop isa MMethod and mprop.is_new then
-                               is_toplevel_context = true
-                       end
+               var mprop = mpropdef.mproperty
+               if mprop isa MMethod and mprop.is_new then
+                       is_toplevel_context = true
                end
        end
 
        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
 
@@ -101,9 +94,9 @@ 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 when `autocast` is true:
        # If the suptype is safe, then the return type is the one on the expression typed by `sub`.
@@ -116,6 +109,7 @@ private class TypeVisitor
                        #node.debug("Unsafe typing: expected {sup}, got {sub}")
                        return sup
                end
+               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}`.")
@@ -150,7 +144,11 @@ private class TypeVisitor
                        end
                        return null # forward error
                end
-               self.error(nexpr, "Error: expected an 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
 
@@ -183,13 +181,12 @@ 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
@@ -217,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
@@ -240,7 +241,7 @@ private class TypeVisitor
                if not mtype2 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
 
                if mtype isa MNullType then
                        # Because of type adaptation, we cannot just stop here
@@ -273,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
@@ -295,21 +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
-                               var objclass = get_mclass(node, "Object")
-                               if objclass == null then return null # Forward error
-                               unsafe_type = objclass.mclass_type
-                       else
-                               self.error(node, "Error: method `{name}` called on `null`.")
-                               return null
-                       end
+                       var objclass = get_mclass(node, "Object")
+                       if objclass == null then return null # Forward error
+                       unsafe_type = objclass.mclass_type
                end
 
                var mproperty = self.try_get_mproperty_by_name2(node, unsafe_type, name)
@@ -331,15 +332,37 @@ private class TypeVisitor
 
                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 accessed by `self`.")
+                       self.modelbuilder.error(node, "Error: method `{mproperty.name}` is protected and can only accessed by `self`.")
                        return null
                end
 
@@ -347,25 +370,31 @@ 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
 
+       # 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)
@@ -382,19 +411,18 @@ 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
@@ -407,18 +435,12 @@ private class TypeVisitor
                                return null
                        end
                else if args.length != msignature.arity then
-                       if msignature.arity == msignature.min_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
+                       # Too much argument
                        if args.length > msignature.arity then
-                               modelbuilder.error(node, "Error: expected at most {msignature.arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
-                               return null
-                       end
-                       if args.length < msignature.min_arity then
-                               modelbuilder.error(node, "Error: expected at least {msignature.min_arity} argument(s) for `{mproperty}{msignature}`; got {args.length}. See introduction at `{mproperty.full_name}`.")
+                               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}")
@@ -426,17 +448,57 @@ private class TypeVisitor
                # Associate each parameter to a position in the arguments
                var map = new SignatureMap
 
-               var setted = args.length - msignature.min_arity
+               # 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
+                       # Skip parameters associated by name
+                       if map.map.has_key(i) then continue
+
                        var param = msignature.mparameters[i]
-                       if param.is_default then
-                               if setted > 0 then
-                                       setted -= 1
-                               else
-                                       continue
+
+                       # 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
@@ -447,34 +509,32 @@ private class TypeVisitor
                                continue # skip the vararg
                        end
 
-                       var paramtype = param.mtype
-                       self.visit_expr_subtype(arg, 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 then
-                               var mclass = get_mclass(node, "Array")
-                               if mclass == null then return null # Forward error
-                               var array_mtype = mclass.get_mtype([paramtype])
-                               if first isa AVarargExpr then
-                                       self.visit_expr_subtype(first.n_expr, array_mtype)
-                                       first.mtype  = first.n_expr.mtype
-                               else
-                                       # only one vararg, maybe `...` was forgot, so be gentle!
-                                       var t = visit_expr(first)
-                                       if t == null then return null # Forward error
-                                       if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
-                                               # Not acceptable but could be a `...`
-                                               error(first, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
-                                               return null
-                                       end
-                                       # Standard valid vararg, finish the job
-                                       map.vararg_decl = 1
-                                       self.visit_expr_subtype(first, paramtype)
-                               end
+                               if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null
                        else
-                               map.vararg_decl = vararg_decl + 1
+                               first.vararg_decl = vararg_decl + 1
                                for i in [vararg_rank..vararg_rank+vararg_decl] do
                                        self.visit_expr_subtype(args[i], paramtype)
                                end
@@ -484,9 +544,36 @@ private class TypeVisitor
                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
@@ -508,7 +595,16 @@ private class TypeVisitor
                                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
@@ -528,6 +624,29 @@ private class TypeVisitor
                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
@@ -549,6 +668,82 @@ 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.
@@ -559,16 +754,13 @@ end
 class SignatureMap
        # Associate a parameter to an argument
        var map = new ArrayMap[Int, Int]
-
-       # The length of the vararg sequence
-       # 0 if no vararg or if reverse vararg (cf `AVarargExpr`)
-       var vararg_decl: Int = 0
 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
@@ -601,17 +793,25 @@ class CallSite
        # If null then no specific association is required.
        var signaturemap: nullable SignatureMap = null
 
-       private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool
+       private fun check_signature(v: TypeVisitor, node: ANode, args: Array[AExpr]): Bool
        do
-               var map = v.check_signature(self.node, args, self.mproperty, 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`
@@ -681,13 +881,10 @@ 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 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)
@@ -705,12 +902,18 @@ redef class AMethPropdef
                        variable.declared_type = mtype
                end
 
+               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, "Error: reached end of function; expected `return` with a value.")
@@ -718,20 +921,42 @@ redef class AMethPropdef
        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
-               if mpropdef == null then return # skip error
+               var mpropdef = self.mreadpropdef
+               if mpropdef == null or mpropdef.msignature == null then return # skip error
 
-               var v = new TypeVisitor(modelbuilder, mpropdef.mclassdef.mmodule, mpropdef)
+               var v = new TypeVisitor(modelbuilder, mpropdef)
                self.selfvariable = v.selfvariable
 
                var nexpr = self.n_expr
                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
@@ -776,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
@@ -889,7 +1136,7 @@ redef class AReassignFormExpr
 
                self.read_type = readtype
 
-               var callsite = v.get_method(self.n_assign_op, 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
 
@@ -923,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
@@ -969,9 +1215,11 @@ redef class AReturnExpr
                        else
                                v.visit_expr(nexpr)
                                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.")
+                       return
                end
                self.is_typed = true
        end
@@ -1024,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
@@ -1048,6 +1297,25 @@ redef class ALoopExpr
 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
@@ -1072,7 +1340,7 @@ 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 an `iterator` method, got `{mtype}`.")
                        return
@@ -1129,31 +1397,31 @@ 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 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 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 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 type `{ittype}`.")
                                return
@@ -1166,28 +1434,14 @@ 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
-
-       redef fun accept_typing(v)
-       do
-               v.has_loop = true
-               var mtype = v.visit_expr(n_expr)
-               if mtype == null then return
-
-               self.do_type_iterator(v, mtype)
-
-               v.visit_stmt(n_block)
-
-               self.mtype = n_block.mtype
-               self.is_typed = true
-       end
 end
 
 redef class AWithExpr
@@ -1199,8 +1453,8 @@ redef class AWithExpr
                var mtype = v.visit_expr(n_expr)
                if mtype == null then return
 
-               method_start = v.get_method(self, mtype, "start", n_expr isa ASelfExpr)
-               method_finish = v.get_method(self, mtype, "finish", n_expr isa ASelfExpr)
+               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
@@ -1245,7 +1499,6 @@ redef class AAndExpr
        end
 end
 
-
 redef class ANotExpr
        redef fun accept_typing(v)
        do
@@ -1265,8 +1518,9 @@ 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
 
@@ -1282,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
@@ -1298,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
@@ -1317,29 +1596,71 @@ 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
@@ -1418,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
@@ -1453,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
@@ -1476,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(v, 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
 
@@ -1510,12 +1867,19 @@ redef class AAsNotnullExpr
                        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
@@ -1560,10 +1924,30 @@ 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 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
 
@@ -1579,7 +1963,7 @@ redef class ASendExpr
                                var systype = sysclass.mclass_type
                                mproperty = v.try_get_mproperty_by_name2(node, systype, name)
                                if mproperty != null then
-                                       callsite = v.get_method(node, systype, name, false)
+                                       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
@@ -1591,7 +1975,7 @@ redef class ASendExpr
                end
                if callsite == null then
                        # If still nothing, just exit
-                       callsite = v.get_method(node, recvtype, name, nrecv isa ASelfExpr)
+                       callsite = v.build_callsite_by_name(node, recvtype, name, nrecv isa ASelfExpr)
                        if callsite == null then return
                end
 
@@ -1600,7 +1984,9 @@ redef class ASendExpr
 
                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
@@ -1614,6 +2000,10 @@ redef class ASendExpr
 
                var ret = msignature.return_mtype
                if ret != null then
+                       if is_safe then
+                               # A safe receiver makes that the call is not executed and returns null
+                               ret = ret.as_nullable
+                       end
                        self.mtype = ret
                else
                        self.is_typed = true
@@ -1633,6 +2023,15 @@ redef class ASendExpr
        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
@@ -1640,41 +2039,45 @@ redef class ABinopExpr
        redef fun property_name do return operator
        redef fun property_node do return n_op
 end
-redef class AEqExpr
+
+redef class AEqFormExpr
        redef fun accept_typing(v)
        do
                super
                v.null_test(self)
        end
-end
-redef class ANeExpr
-       redef fun accept_typing(v)
+
+       redef fun accept_post_typing(v)
        do
-               super
-               v.null_test(self)
-       end
-end
+               var mtype = n_expr.mtype
+               var mtype2 = n_expr2.mtype
 
-redef class AUplusExpr
-       redef fun property_name do return "unary +"
-       redef fun compute_raw_arguments do return new Array[AExpr]
+               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 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_node do return n_id
+       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_node do return n_id
+       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
@@ -1711,14 +2114,14 @@ redef class ASendReassignFormExpr
                if recvtype == null then return # Forward error
 
                var for_self = self.n_expr isa ASelfExpr
-               var callsite = v.get_method(node, 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
@@ -1726,7 +2129,7 @@ redef class ASendReassignFormExpr
                        return
                end
 
-               var wcallsite = v.get_method(node, 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
 
@@ -1735,15 +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_node do return n_id
+       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
 
@@ -1758,6 +2161,77 @@ redef class AInitExpr
        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
@@ -1776,7 +2250,6 @@ redef class ASuperExpr
        redef fun accept_typing(v)
        do
                var anchor = v.anchor
-               assert anchor != null
                var recvtype = v.get_variable(self, v.selfvariable)
                assert recvtype != null
                var mproperty = v.mpropdef.mproperty
@@ -1815,7 +2288,6 @@ redef class ASuperExpr
        private fun process_superinit(v: TypeVisitor)
        do
                var anchor = v.anchor
-               assert anchor != null
                var recvtype = v.get_variable(self, v.selfvariable)
                assert recvtype != null
                var mpropdef = v.mpropdef
@@ -1856,12 +2328,12 @@ redef class ASuperExpr
                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
@@ -1882,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
 
 ####
@@ -1915,11 +2400,11 @@ redef class ANewExpr
                var kind = recvtype.mclass.kind
 
                var name: String
-               var nid = self.n_id
+               var nqid = self.n_qid
                var node: ANode
-               if nid != null then
-                       name = nid.text
-                       node = nid
+               if nqid != null then
+                       name = nqid.n_id.text
+                       node = nqid
                else
                        name = "new"
                        node = self.n_kwnew
@@ -1938,7 +2423,7 @@ redef class ANewExpr
                        return
                end
 
-               var callsite = v.get_method(node, recvtype, name, false)
+               var callsite = v.build_callsite_by_name(node, recvtype, name, false)
                if callsite == null then return
 
                if not callsite.mproperty.is_new then
@@ -1960,7 +2445,16 @@ redef class ANewExpr
                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
 
@@ -2002,6 +2496,16 @@ redef class AAttrFormExpr
                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
@@ -2012,7 +2516,6 @@ redef class AAttrExpr
        end
 end
 
-
 redef class AAttrAssignExpr
        redef fun accept_typing(v)
        do
@@ -2020,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
 
@@ -2031,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
 
@@ -2053,6 +2556,28 @@ redef class AIssetAttrExpr
        end
 end
 
+redef class ASafeExpr
+       redef fun accept_typing(v)
+       do
+               var mtype = v.visit_expr(n_expr)
+               if mtype == null then return # Skip error
+
+               if mtype isa MNullType then
+                       # While `null?.foo` is semantically well defined and should not execute `foo` and just return `null`,
+                       # currently `null.foo` is forbidden so it seems coherent to also forbid `null?.foo`
+                       v.modelbuilder.error(self, "Error: safe operator `?` on `null`.")
+                       return
+               end
+
+               self.mtype = mtype.as_notnull
+
+               if not v.can_be_null(mtype) then
+                       v.modelbuilder.warning(self, "useless-safe", "Warning: useless safe operator `?` on non-nullable value.")
+                       return
+               end
+       end
+end
+
 redef class AVarargExpr
        redef fun accept_typing(v)
        do