nullable: type, compile and test 'isset _attr'
[nit.git] / src / syntax / typing.nit
index 0b7f6b3..6717511 100644 (file)
@@ -19,6 +19,7 @@ package typing
 
 import syntax_base
 import escape
+import control_flow
 
 redef class MMSrcModule
        # Walk trough the module and type statments and expressions
@@ -45,6 +46,9 @@ special AbsSyntaxVisitor
        # Current knowledge about variables names and types
        readable writable attr _variable_ctx: VariableContext
 
+       # Non-bypassable knowledge about variables names and types
+       readable writable attr _base_variable_ctx: VariableContext
+
        # Current knowledge about escapable blocks
        readable writable attr _escapable_ctx: EscapableContext = new EscapableContext(self)
 
@@ -64,11 +68,19 @@ special AbsSyntaxVisitor
        private meth use_if_true_variable_ctx(e: PExpr)
        do
                var ctx = e.if_true_variable_ctx
-               if ctx != null then
-                       variable_ctx = ctx
-               end
+               if ctx != null then variable_ctx = ctx
+       end
+
+       # Make the if_false_variable_ctx of the expression effective
+       private meth use_if_false_variable_ctx(e: PExpr)
+       do
+               var ctx = e.if_false_variable_ctx
+               if ctx != null then variable_ctx = ctx
        end
 
+       # Number of nested once
+       readable writable attr _once_count: Int = 0
+
        init(tc, module) do super
 
        private meth get_default_constructor_for(n: PNode, c: MMLocalClass, prop: MMSrcMethod): MMMethod
@@ -121,115 +133,6 @@ special AbsSyntaxVisitor
        end
 end
 
-# Associate symbols to variable and variables to type
-# Can be nested
-abstract class VariableContext
-       # Look for the variable from its name
-       # Return null if nothing found
-       meth [](s: Symbol): Variable
-       do
-               if _dico.has_key(s) then
-                       return _dico[s]
-               else
-                       return null
-               end
-       end
-
-       # Register a new variable with its name
-       meth add(v: Variable)
-       do
-               _dico[v.name] = v
-       end
-
-       # The effective static type of a given variable
-       # May be different from the declaration static type
-       meth stype(v: Variable): MMType
-       do
-               return v.stype
-       end
-
-       # Variables by name (in the current context only)
-       attr _dico: Map[Symbol, Variable]
-
-       # Build a new VariableContext
-       meth sub(node: PNode): SubVariableContext
-       do
-               return new SubVariableContext.with_prev(self, node)
-       end
-
-       # Build a nested VariableContext with new variable information
-       meth sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext
-       do
-               return new CastVariableContext.with_prev(self, node, v, t)
-       end
-
-       # The visitor of the context (used to display error)
-       attr _visitor: AbsSyntaxVisitor
-
-       # The syntax node that introduced the context
-       readable attr _node: PNode
-
-       init(visitor: AbsSyntaxVisitor, node: PNode)
-       do
-               _visitor = visitor
-               _node = node
-               _dico = new HashMap[Symbol, Variable]
-       end
-end
-
-class RootVariableContext
-special VariableContext
-       init(visitor: AbsSyntaxVisitor, node: PNode)
-       do
-               super(visitor, node)
-       end
-end
-
-class SubVariableContext
-special VariableContext
-       readable attr _prev: VariableContext
-
-       redef meth [](s)
-       do
-               if _dico.has_key(s) then
-                       return _dico[s]
-               else
-                       return prev[s]
-               end
-       end
-
-       redef meth stype(v)
-       do
-               return prev.stype(v)
-       end
-
-       init with_prev(p: VariableContext, node: PNode)
-       do
-               init(p._visitor, node)
-               _prev = p
-       end
-end
-
-class CastVariableContext
-special SubVariableContext
-       attr _variable: Variable
-       attr _var_type: MMType
-
-       redef meth stype(v)
-       do
-               if _variable == v then
-                       return _var_type
-               end
-               return prev.stype(v)
-       end
-
-       init with_prev(p: VariableContext, node: PNode, v: Variable, t: MMType)
-       do
-               super(p, node)
-               _variable = v
-               _var_type =t
-       end
-end
 
 ###############################################################################
 
@@ -266,11 +169,22 @@ redef class AMethPropdef
        redef meth accept_typing(v)
        do
                v.variable_ctx = new RootVariableContext(v, self)
+               v.base_variable_ctx = v.variable_ctx
                _self_var = v.self_var
                super
        end
 end
 
+redef class AConcreteMethPropdef
+       redef meth accept_typing(v)
+       do
+               super
+               if v.variable_ctx.unreash == false and method.signature.return_type != null then
+                       v.error(self, "Control error: Reached end of function (a 'return' with a value was expected).")
+               end
+       end
+end
+
 redef class AConcreteInitPropdef
        readable attr _super_init_calls: Array[MMMethod] = new Array[MMMethod]
        readable attr _explicit_super_init_calls: Array[MMMethod] = new Array[MMMethod]
@@ -339,6 +253,8 @@ redef class AClosureDecl
                v.variable_ctx.add(variable)
 
                var old_var_ctx = v.variable_ctx
+               var old_base_var_ctx = v.base_variable_ctx
+               v.base_variable_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
 
                _escapable = new EscapableClosure(self, variable.closure, null)
@@ -346,7 +262,19 @@ redef class AClosureDecl
 
                super
 
+               if n_expr != null then
+                       if v.variable_ctx.unreash == false then
+                               if variable.closure.signature.return_type != null then
+                                       v.error(self, "Control error: Reached end of block (a 'continue' with a value was expected).")
+                               else if variable.closure.is_break then
+                                       v.error(self, "Control error: Reached end of break block (an 'abort' was expected).")
+                               end
+                       end
+               end
+
+               old_var_ctx.merge(v.variable_ctx)
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
        end
 end
@@ -387,6 +315,9 @@ redef class PExpr
 
        # The variable type information if current boolean expression is true
        readable private attr _if_true_variable_ctx: VariableContext
+
+       # The variable type information if current boolean expression is false
+       readable private attr _if_false_variable_ctx: VariableContext
 end
 
 redef class AVardeclExpr
@@ -395,6 +326,7 @@ redef class AVardeclExpr
                var va = new VarVariable(n_id.to_symbol, self)
                variable = va
                v.variable_ctx.add(va)
+               if n_expr != null then v.variable_ctx.mark_is_set(va)
 
                if n_type != null then
                        va.stype = n_type.stype
@@ -415,8 +347,15 @@ redef class ABlockExpr
                var old_var_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
 
-               super
+               for e in n_expr do
+                       if v.variable_ctx.unreash and not v.variable_ctx.already_unreash then
+                               v.variable_ctx.already_unreash = true
+                               v.warning(e, "Warning: unreachable statement.")
+                       end
+                       v.visit(e)
+               end
 
+               old_var_ctx.merge(v.variable_ctx)
                v.variable_ctx = old_var_ctx
                _is_typed = true
        end
@@ -425,6 +364,7 @@ end
 redef class AReturnExpr
        redef meth after_typing(v)
        do
+               v.variable_ctx.unreash = true
                var t = v.local_property.signature.return_type
                if n_expr == null and t != null then
                        v.error(self, "Error: Return without value in a function.")
@@ -440,6 +380,7 @@ end
 redef class AContinueExpr
        redef meth after_typing(v)
        do
+               v.variable_ctx.unreash = true
                var esc = compute_escapable_block(v.escapable_ctx)
                if esc == null then return
 
@@ -463,6 +404,7 @@ end
 redef class ABreakExpr
        redef meth after_typing(v)
        do
+               v.variable_ctx.unreash = true
                var esc = compute_escapable_block(v.escapable_ctx)
                if esc == null then return
 
@@ -479,6 +421,13 @@ redef class ABreakExpr
        end
 end
 
+redef class AAbortExpr
+       redef meth after_typing(v)
+       do
+               v.variable_ctx.unreash = true
+       end
+end
+
 redef class AIfExpr
        redef meth accept_typing(v)
        do
@@ -486,16 +435,31 @@ redef class AIfExpr
                v.visit(n_expr)
                v.check_conform_expr(n_expr, v.type_bool)
 
+               # Prepare 'then' context
                v.use_if_true_variable_ctx(n_expr)
 
-               v.visit(n_then)
-               # Restore variable ctx
+               # Process the 'then'
+               if n_then != null then
+                       v.variable_ctx = v.variable_ctx.sub(n_then)
+                       v.visit(n_then)
+               end
+
+               # Remember what appened in the 'then'
+               var then_var_ctx = v.variable_ctx
+
+               # Prepare 'else' context
                v.variable_ctx = old_var_ctx
+               v.use_if_false_variable_ctx(n_expr)
 
+               # Process the 'else'
                if n_else != null then
+                       v.variable_ctx = v.variable_ctx.sub(n_else)
                        v.visit(n_else)
-                       v.variable_ctx = old_var_ctx
                end
+
+               # Merge 'then' and 'else' contexts
+               old_var_ctx.merge2(then_var_ctx, v.variable_ctx, v.base_variable_ctx)
+               v.variable_ctx = old_var_ctx
                _is_typed = true
        end
 end
@@ -509,12 +473,25 @@ redef class AWhileExpr
                _escapable = new EscapableBlock(self)
                v.escapable_ctx.push(_escapable)
                var old_var_ctx = v.variable_ctx
+               var old_base_var_ctx = v.base_variable_ctx
+               v.base_variable_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
 
-               super
-
+               # Process condition
+               v.visit(n_expr)
                v.check_conform_expr(n_expr, v.type_bool)
+
+               # Prepare inside context (assert cond)
+               v.use_if_true_variable_ctx(n_expr)
+
+               # Process inside
+               if n_block != null then
+                       v.variable_ctx = v.variable_ctx.sub(n_block)
+                       v.visit(n_block)
+               end
+
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
        end
@@ -534,6 +511,8 @@ redef class AForExpr
                v.escapable_ctx.push(_escapable)
 
                var old_var_ctx = v.variable_ctx
+               var old_base_var_ctx = v.base_variable_ctx
+               v.base_variable_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
                var va = new AutoVariable(n_id.to_symbol, self)
                variable = va
@@ -572,6 +551,7 @@ redef class AForExpr
 
                # pop context
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
                v.escapable_ctx.pop
                _is_typed = true
        end
@@ -591,6 +571,7 @@ redef class AVarExpr
 
        redef meth after_typing(v)
        do
+               v.variable_ctx.check_is_set(self, variable)
                _stype = v.variable_ctx.stype(variable)
                _is_typed = _stype != null
        end
@@ -599,31 +580,41 @@ end
 redef class AVarAssignExpr
        redef meth after_typing(v)
        do
+               v.variable_ctx.mark_is_set(variable)
                var t = v.variable_ctx.stype(variable)
-               v.check_conform_expr(n_value, t)
+
+               # Check the base type
+               var btype = v.base_variable_ctx.stype(variable)
+               if not v.check_conform_expr(n_value, btype) then return
+
+               # Always cast
+               v.variable_ctx.stype(variable) = n_value.stype
+
                _is_typed = true
        end
 end
 
 redef class AReassignFormExpr
-       # Compute and check method used through the reassigment operator 
-       private meth do_lvalue_typing(v: TypingVisitor, type_lvalue: MMType)
+       # Compute and check method used through the reassigment operator
+       # On success return the static type of the result of the reassigment operator
+       # Else display an error and return null
+       private meth do_rvalue_typing(v: TypingVisitor, type_lvalue: MMType): MMType
        do
                if type_lvalue == null then
-                       return
+                       return null
                end
                var name = n_assign_op.method_name
                var lc = type_lvalue.local_class
                if not lc.has_global_property_by_name(name) then
                        v.error(self, "Error: Method '{name}' doesn't exists in {type_lvalue}.")
-                       return
+                       return null
                end
                var prop = lc.select_method(name)
                prop.global.check_visibility(v, self, v.module, false)
                var psig = prop.signature_for(type_lvalue)
                _assign_method = prop
-               if not v.check_conform_expr(n_value, psig[0].not_for_self) then return
-               if not v.check_conform(self, psig.return_type.not_for_self, n_value.stype) then return
+               if not v.check_conform_expr(n_value, psig[0].not_for_self) then return null
+               return psig.return_type.not_for_self
        end
 
        # Method used through the reassigment operator (once computed)
@@ -633,8 +624,19 @@ end
 redef class AVarReassignExpr
        redef meth after_typing(v)
        do
+               v.variable_ctx.check_is_set(self, variable)
+               v.variable_ctx.mark_is_set(variable)
                var t = v.variable_ctx.stype(variable)
-               do_lvalue_typing(v, t)
+               var t2 = do_rvalue_typing(v, t)
+               if t2 == null then return
+
+               # Check the base type
+               var btype = v.base_variable_ctx.stype(variable)
+               if not v.check_conform(n_value, t2, btype) then return
+
+               # Always cast
+               v.variable_ctx.stype(variable) = t2
+
                _is_typed = true
        end
 end
@@ -675,6 +677,7 @@ redef class AIfexprExpr
                v.use_if_true_variable_ctx(n_expr)
                v.visit(n_then)
                v.variable_ctx = old_var_ctx
+               v.use_if_false_variable_ctx(n_expr)
                v.visit(n_else)
 
                v.check_conform_expr(n_expr, v.type_bool)
@@ -693,8 +696,22 @@ redef class ABoolExpr
 end
 
 redef class AOrExpr
-       redef meth after_typing(v)
+       redef meth accept_typing(v)
        do
+               var old_var_ctx = v.variable_ctx
+
+               v.visit(n_expr)
+               v.use_if_false_variable_ctx(n_expr)
+
+               v.visit(n_expr2)
+               if n_expr2.if_false_variable_ctx != null then 
+                       _if_false_variable_ctx = n_expr2.if_false_variable_ctx
+               else
+                       _if_false_variable_ctx = v.variable_ctx
+               end
+
+               v.variable_ctx = old_var_ctx
+
                v.check_conform_expr(n_expr, v.type_bool)
                v.check_conform_expr(n_expr2, v.type_bool)
                _stype = v.type_bool
@@ -730,6 +747,11 @@ redef class ANotExpr
        redef meth after_typing(v)
        do
                v.check_conform_expr(n_expr, v.type_bool)
+
+               # Invert if_true/if_false information
+               _if_false_variable_ctx = n_expr._if_true_variable_ctx
+               _if_true_variable_ctx = n_expr._if_false_variable_ctx
+
                _stype = v.type_bool
                _is_typed = true
        end
@@ -972,7 +994,22 @@ redef class AAttrReassignExpr
        do
                do_typing(v)
                if prop == null then return
-               do_lvalue_typing(v, attr_type)
+               var t = do_rvalue_typing(v, attr_type)
+               if t == null then return
+               v.check_conform(self, t, n_value.stype)
+               _is_typed = true
+       end
+end
+
+redef class AIssetAttrExpr
+       redef meth after_typing(v)
+       do
+               do_typing(v)
+               if prop == null then return
+               if attr_type.is_nullable then
+                       v.error(self, "Error: isset on a nullable attribute.")
+               end
+               _stype = v.type_bool
                _is_typed = true
        end
 end
@@ -1253,7 +1290,9 @@ special AReassignFormExpr
                var t = prop.signature_for(n_expr.stype).return_type
                if not n_expr.is_self then t = t.not_for_self
 
-               do_lvalue_typing(v, t)
+               var t2 = do_rvalue_typing(v, t)
+               if t2 == null then return
+               v.check_conform(self, t2, n_value.stype)
 
                _read_prop = prop
                var old_args = arguments
@@ -1279,9 +1318,55 @@ redef class ABinopExpr
 end
 redef class AEqExpr
        redef meth name do return once "==".to_symbol
+       redef meth after_typing(v)
+       do
+               super
+               if not is_typed then return
+               if n_expr.stype isa MMTypeNone and not n_expr2.stype.is_nullable or
+               n_expr2.stype isa MMTypeNone and not n_expr.stype.is_nullable then
+                       v.warning(self, "Warning: comparaison between null and a non nullable value.")
+               end
+
+               if n_expr.stype isa MMTypeNone then
+                       try_to_isa(v, n_expr2)
+               else if n_expr2.stype isa MMTypeNone then
+                       try_to_isa(v, n_expr)
+               end
+       end
+
+       private meth try_to_isa(v: TypingVisitor, n: PExpr)
+       do
+               var variable = n.its_variable
+               if variable != null then
+                       _if_false_variable_ctx = v.variable_ctx.sub_with(self, variable, n.stype.as_notnull)
+               end
+       end
 end
 redef class ANeExpr
        redef meth name do return once "!=".to_symbol
+       redef meth after_typing(v)
+       do
+               super
+               if not is_typed then return
+               if n_expr.stype isa MMTypeNone and not n_expr2.stype.is_nullable or
+               n_expr2.stype isa MMTypeNone and not n_expr.stype.is_nullable then
+                       v.warning(self, "Warning: comparaison between null and a non nullable value.")
+               end
+
+               if n_expr.stype isa MMTypeNone then
+                       try_to_isa(v, n_expr2)
+               else if n_expr2.stype isa MMTypeNone then
+                       try_to_isa(v, n_expr)
+               end
+       end
+
+       private meth try_to_isa(v: TypingVisitor, n: PExpr)
+       do
+               var variable = n.its_variable
+               if variable != null then
+                       _if_true_variable_ctx = v.variable_ctx.sub_with(self, variable, n.stype.as_notnull)
+               end
+       end
 end
 redef class ALtExpr
        redef meth name do return once "<".to_symbol
@@ -1427,6 +1512,7 @@ special AAbsAbsSendExpr
        redef meth after_typing(v)
        do
                var va = variable
+               if va.closure.is_break then v.variable_ctx.unreash = true
                var sig = va.closure.signature
                var args = process_signature(v, sig, n_id.to_symbol, n_args.to_a)
                if not n_closure_defs.is_empty then
@@ -1468,6 +1554,8 @@ redef class AClosureDef
                closure = esc.closure
 
                var old_var_ctx = v.variable_ctx
+               var old_base_var_ctx = v.base_variable_ctx
+               v.base_variable_ctx = v.variable_ctx
                v.variable_ctx = v.variable_ctx.sub(self)
                variables = new Array[AutoVariable]
                for i in [0..n_id.length[ do
@@ -1479,7 +1567,16 @@ redef class AClosureDef
 
                _accept_typing2 = true
                accept_typing(v)
+
+               if v.variable_ctx.unreash == false then
+                       if closure.signature.return_type != null then
+                               v.error(self, "Control error: Reached end of block (a 'continue' with a value was expected).")
+                       else if closure.is_break then
+                               v.error(self, "Control error: Reached end of break block (a 'break' was expected).")
+                       end
+               end
                v.variable_ctx = old_var_ctx
+               v.base_variable_ctx = old_base_var_ctx
        end
 end
 
@@ -1494,6 +1591,18 @@ special PExpr
                        v.warning(self, "Warning: Expression is already a {ttype}.")
                else if etype < ttype then
                        v.warning(self, "Warning: Expression is already a {ttype} since it is a {etype}.")
+               else if etype.is_nullable and etype.as_notnull == ttype then
+                       if ttype isa MMTypeFormal and ttype.bound.is_nullable then
+                               # No warning in this case since with
+                               #   type T: nullable A
+                               #   var x: nullable T
+                               # 'x.as(not null)' != 'x.as(T)'
+                               # 'x != null' != 'x isa T'
+                       else if self isa AIsaExpr then
+                               v.warning(self, "Warning: Prefer '!= null'.")
+                       else
+                               v.warning(self, "Warning: Prefer '.as(not null)'.")
+                       end
                end
        end
 end
@@ -1522,6 +1631,22 @@ special ATypeCheckExpr
        end
 end
 
+redef class AAsNotnullExpr
+       redef meth after_typing(v)
+       do
+               if not v.check_expr(n_expr) then return
+               var t = n_expr.stype
+               if t isa MMTypeNone then
+                       v.error(n_expr, "Type error: 'as(not null)' on 'null' value.")
+                       return
+               else if not t.is_nullable then
+                       v.warning(n_expr, "Warning: 'as(not null)' on non nullable type.")
+               end
+               _stype = n_expr.stype.as_notnull
+               _is_typed = true
+       end
+end
+
 redef class AProxyExpr
        redef meth after_typing(v)
        do
@@ -1531,3 +1656,18 @@ redef class AProxyExpr
                _stype = n_expr.stype
        end
 end
+
+redef class AOnceExpr
+       redef meth accept_typing(v)
+       do
+               if v.once_count > 0 then
+                       v.warning(self, "Useless once in a once expression.")
+               end
+               v.once_count = v.once_count + 1
+
+               super
+
+               v.once_count = v.once_count - 1
+       end
+end
+