X-Git-Url: http://nitlanguage.org diff --git a/src/syntax/typing.nit b/src/syntax/typing.nit index 93640e6..c1d6399 100644 --- a/src/syntax/typing.nit +++ b/src/syntax/typing.nit @@ -18,14 +18,16 @@ package typing import syntax_base +import flow +import scope redef class MMSrcModule # Walk trough the module and type statments and expressions # Require than supermodules are processed - meth do_typing(tc: ToolContext) + fun do_typing(tc: ToolContext) do var tv = new TypingVisitor(tc, self) - tv.visit(node) + tv.enter_visit(node) end end @@ -36,38 +38,80 @@ end # * Check type conformance private class TypingVisitor special AbsSyntaxVisitor - redef meth visit(n) + redef fun visit(n) do if n != null then n.accept_typing(self) end - # Current knowledge about variables names and types - readable writable attr _variable_ctx: VariableContext + # Current knowledge about scoped things (variable, labels, etc.) + readable var _scope_ctx: ScopeContext = new ScopeContext(self) - # The current reciever - readable writable attr _self_var: ParamVariable + # Current knowledge about control flow + fun flow_ctx: FlowContext do return _flow_ctx.as(not null) + writable var _flow_ctx: nullable FlowContext - # Block of the current method - readable writable attr _top_block: PExpr + # Mark a local variable as set + fun mark_is_set(va: Variable) + do + if flow_ctx.is_set(va) then return + flow_ctx = flow_ctx.sub_setvariable(va) + end - # Current closure (if any) - readable writable attr _closure: MMClosure + # Mark the flow context as unreashable + fun mark_unreash(n: ANode) + do + flow_ctx = flow_ctx.sub_unreash(n) + end - # Current closure method return type (for break) (if any) - readable writable attr _closure_break_stype: MMType = null + # Enter in an expression as inside a new local variable scope + fun enter_visit_block(node: nullable AExpr) + do + if node == null then return + scope_ctx.push(node) + enter_visit(node) + scope_ctx.pop + end - # Current closure break expressions (if any) - readable writable attr _break_list: Array[PExpr] + # Non-bypassable knowledge about variables names and types + fun base_flow_ctx: FlowContext do return _base_flow_ctx.as(not null) + writable var _base_flow_ctx: nullable FlowContext + + # The current reciever + fun self_var: ParamVariable do return _self_var.as(not null) + writable var _self_var: nullable ParamVariable + + # Block of the current method + readable writable var _top_block: nullable AExpr # List of explicit invocation of constructors of super-classes - readable writable attr _explicit_super_init_calls: Array[MMMethod] + readable writable var _explicit_super_init_calls: nullable Array[MMMethod] # Is a other constructor of the same class invoked - readable writable attr _explicit_other_init_call: Bool + readable writable var _explicit_other_init_call: Bool = false + + # Make the if_true_flow_ctx of the expression effective + private fun use_if_true_flow_ctx(e: AExpr) + do + var ctx = e.if_true_flow_ctx + if ctx != null then flow_ctx = ctx + end + + # Make the if_false_flow_ctx of the expression effective + private fun use_if_false_flow_ctx(e: AExpr) + do + var ctx = e.if_false_flow_ctx + if ctx != null then flow_ctx = ctx + end + + # Are we inside a default closure definition ? + readable writable var _is_default_closure_definition: Bool = false + + # Number of nested once + readable writable var _once_count: Int = 0 init(tc, module) do super - private meth get_default_constructor_for(n: PNode, c: MMLocalClass, prop: MMSrcMethod): MMMethod + private fun get_default_constructor_for(n: ANode, c: MMLocalClass, prop: MMSrcMethod): nullable MMMethod do var v = self #var prop = v.local_property @@ -81,7 +125,7 @@ special AbsSyntaxVisitor var gps = gp.signature_for(c.get_type) assert gp isa MMSrcMethod var garity = gps.arity - if prop != null and gp.name == prop.name then + if gp.name == prop.name then if garity == 0 or (parity == garity and prop.signature < gps) then return gp else @@ -99,14 +143,14 @@ special AbsSyntaxVisitor else if candidates.length > 0 then var a = new Array[String] for p in candidates do - a.add("{p.full_name}{p.signature}") + a.add("{p.full_name}{p.signature.as(not null)}") end v.error(n, "Error: Conflicting default constructor to call for {c}: {a.join(", ")}.") return null else if false_candidates.length > 0 then var a = new Array[String] for p in false_candidates do - a.add("{p.full_name}{p.signature}") + a.add("{p.full_name}{p.signature.as(not null)}") end v.error(n, "Error: there is no available compatible constrctor in {c}. Discarded candidates are {a.join(", ")}.") return null @@ -117,101 +161,20 @@ special AbsSyntaxVisitor end end -# Associate symbols to variable and variables to type -# Can be nested -private 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: SubVariableContext - do - return new SubVariableContext.with_prev(self, null, null) - end - - # Build a nested VariableContext with new variable information - meth sub_with(v: Variable, t: MMType): SubVariableContext - do - return new SubVariableContext.with_prev(self, v, t) - end - - init - do - _dico = new HashMap[Symbol, Variable] - end -end - -private class SubVariableContext -special VariableContext - readable attr _prev: VariableContext - attr _variable: Variable - attr _var_type: MMType - - redef meth [](s) - do - if _dico.has_key(s) then - return _dico[s] - else - return prev[s] - end - end - - redef meth stype(v) - do - if _variable == v then - return _var_type - end - return prev.stype(v) - end - - init with_prev(p: VariableContext, v: Variable, t: MMType) - do - init - _prev = p - _variable = v - _var_type =t - end -end - ############################################################################### -redef class PNode - private meth accept_typing(v: TypingVisitor) +redef class ANode + private fun accept_typing(v: TypingVisitor) do accept_abs_syntax_visitor(v) after_typing(v) end - private meth after_typing(v: TypingVisitor) do end + private fun after_typing(v: TypingVisitor) do end end -redef class PClassdef - redef meth accept_typing(v) +redef class AClassdef + redef fun accept_typing(v) do v.self_var = new ParamVariable("self".to_symbol, self) v.self_var.stype = local_class.get_type @@ -219,42 +182,69 @@ redef class PClassdef end end +redef class APropdef + redef fun self_var do return _self_var.as(not null) + var _self_var: nullable ParamVariable +end + redef class AAttrPropdef - redef meth accept_typing(v) + redef fun accept_typing(v) do + v.flow_ctx = new RootFlowContext(v, self) + v.base_flow_ctx = v.flow_ctx + + v.scope_ctx.push(self) + _self_var = v.self_var super if n_expr != null then - v.check_conform_expr(n_expr, prop.signature.return_type) + v.check_conform_expr(n_expr.as(not null), prop.signature.return_type.as(not null)) end + v.scope_ctx.pop end end redef class AMethPropdef - redef readable attr _self_var: ParamVariable - redef meth accept_typing(v) + redef fun accept_typing(v) do - v.variable_ctx = new VariableContext + v.flow_ctx = new RootFlowContext(v, self) + v.base_flow_ctx = v.flow_ctx + + v.scope_ctx.push(self) _self_var = v.self_var super + v.scope_ctx.pop + end +end + +redef class AConcreteMethPropdef + redef fun after_typing(v) + do + super + if not v.flow_ctx.unreash 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] - redef meth accept_typing(v) + redef fun accept_typing(v) do v.top_block = n_block v.explicit_super_init_calls = explicit_super_init_calls v.explicit_other_init_call = false super + end + + redef fun after_typing(v) + do + super if v.explicit_other_init_call or method.global.intro != method then # TODO: something? else var i = 0 var l = explicit_super_init_calls.length - var cur_m: MMMethod = null - var cur_c: MMLocalClass = null + var cur_m: nullable MMMethod = null + var cur_c: nullable MMLocalClass = null if i < l then cur_m = explicit_super_init_calls[i] cur_c = cur_m.global.intro.local_class.for_module(v.module) @@ -266,7 +256,7 @@ redef class AConcreteInitPropdef j += 1 else if cur_c != null and (c.cshe <= cur_c or cur_c.global.is_mixin) then if c == cur_c then j += 1 - super_init_calls.add(cur_m) + super_init_calls.add(cur_m.as(not null)) i += 1 if i < l then cur_m = explicit_super_init_calls[i] @@ -287,474 +277,774 @@ redef class AConcreteInitPropdef end end -redef class PParam - redef meth after_typing(v) +redef class AParam + redef fun after_typing(v) do - # TODO: why the test? - if v.variable_ctx != null then - v.variable_ctx.add(variable) - end + v.scope_ctx.add_variable(variable) end end redef class AClosureDecl - redef meth accept_typing(v) + # The corresponding escapable object + readable var _escapable: nullable EscapableBlock + + redef fun accept_typing(v) do # Register the closure for ClosureCallExpr - v.variable_ctx.add(variable) + v.scope_ctx.add_variable(variable) - var old_var_ctx = v.variable_ctx - v.variable_ctx = v.variable_ctx.sub - v.closure = variable.closure + var old_flow_ctx = v.flow_ctx + var old_base_flow_ctx = v.base_flow_ctx + v.base_flow_ctx = v.flow_ctx + + var blist: nullable Array[AExpr] = null + var t = v.local_property.signature.return_type + if t != null then blist = new Array[AExpr] + var escapable = new EscapableClosure(self, variable.closure, blist) + _escapable = escapable + v.scope_ctx.push_escapable(escapable, null) + + v.is_default_closure_definition = true super - v.variable_ctx = old_var_ctx - v.closure = null + v.is_default_closure_definition = false + + if n_expr != null then + if v.flow_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 and escapable.break_list != null then + v.error(self, "Control error: Reached end of break block (a 'break' with a value was expected).") + end + end + end + if blist != null then for x in blist do + v.check_conform_expr(x, t) + end + + v.flow_ctx = old_flow_ctx + v.base_flow_ctx = old_base_flow_ctx + v.scope_ctx.pop end end -redef class PType - readable attr _stype: MMType - redef meth after_typing(v) +redef class AType + redef fun stype: MMType do return _stype.as(not null) + redef fun is_typed: Bool do return _stype != null + var _stype: nullable MMType + + redef fun after_typing(v) do _stype = get_stype(v) end end -redef class PExpr - redef readable attr _stype: MMType - +redef class AExpr + redef readable var _is_typed: Bool = false + redef fun is_statement: Bool do return _stype == null + redef fun stype + do + if not is_typed then + print "{location}: not is_typed" + abort + end + if is_statement then + print "{location}: is_statement" + abort + end + return _stype.as(not null) + end + var _stype: nullable MMType + + redef fun after_typing(v) + do + # Default behavior is to be happy + _is_typed = true + end + # Is the expression the implicit receiver - meth is_implicit_self: Bool do return false + fun is_implicit_self: Bool do return false # Is the expression the current receiver (implicit or explicit) - meth is_self: Bool do return false + fun is_self: Bool do return false # The variable accessed is any - meth its_variable: Variable do return null + fun its_variable: nullable Variable do return null + + # The control flow information if current boolean expression is true + readable private var _if_true_flow_ctx: nullable FlowContext - # The variable type information if current boolean expression is true - readable private attr _if_true_variable_ctx: VariableContext + # The control flow information if current boolean expression is false + readable private var _if_false_flow_ctx: nullable FlowContext end redef class AVardeclExpr - redef meth after_typing(v) + var _variable: nullable VarVariable + redef fun variable do return _variable.as(not null) + + redef fun after_typing(v) do - var va = new VarVariable(n_id.to_symbol, self) - variable = va - v.variable_ctx.add(va) + var va = new VarVariable(n_id.to_symbol, n_id) + _variable = va + v.scope_ctx.add_variable(va) + var ne = n_expr + if ne != null then v.mark_is_set(va) if n_type != null then + if not n_type.is_typed then return va.stype = n_type.stype - if n_expr != null then - v.check_conform_expr(n_expr, va.stype) + if ne != null then + v.check_conform_expr(ne, va.stype) end + else if ne != null then + if not v.check_expr(ne) then return + va.stype = ne.stype else - v.check_expr(n_expr) - va.stype = n_expr.stype + va.stype = v.type_object.as_nullable end + _is_typed = true end end redef class ABlockExpr - redef meth accept_typing(v) - do - var old_var_ctx = v.variable_ctx - v.variable_ctx = v.variable_ctx.sub - - super + redef fun accept_typing(v) + do + for e in n_expr do + if not v.flow_ctx.unreash then + v.enter_visit(e) + else if not v.flow_ctx.already_unreash then + v.flow_ctx.already_unreash = true + v.error(e, "Error: unreachable statement.") + end + end - v.variable_ctx = old_var_ctx + _is_typed = true end end redef class AReturnExpr - redef meth after_typing(v) + redef fun after_typing(v) do + v.mark_unreash(self) var t = v.local_property.signature.return_type - if n_expr == null and t != null then + + if v.is_default_closure_definition then + v.error(self, "Error: 'return' invalid in default closure definitions. Use 'continue' or 'break'.") + return + end + + var e = n_expr + if e == null and t != null then v.error(self, "Error: Return without value in a function.") - else if n_expr != null and t == null then + else if e != null and t == null then v.error(self, "Error: Return with value in a procedure.") - else if n_expr != null and t != null then - v.check_conform_expr(n_expr, t) + else if e != null and t != null then + v.check_conform_expr(e, t) end + _is_typed = true end end redef class AContinueExpr - redef meth after_typing(v) + redef fun after_typing(v) do - var c = v.closure - var t: MMType = null - if c != null then - if c.is_break then - v.error(self, "Error: 'continue' forbiden in break blocks.") - return - end - t = c.signature.return_type + v.mark_unreash(self) + var esc = compute_escapable_block(v.scope_ctx) + if esc == null then return + + if esc.is_break_block then + v.error(self, "Error: cannot 'continue', only 'break'.") + return end + var t = esc.continue_stype if n_expr == null and t != null then - v.error(self, "Error: continue with a value required in this bloc.") + v.error(self, "Error: continue with a value required in this block.") else if n_expr != null and t == null then - v.error(self, "Error: continue without value required in this bloc.") + v.error(self, "Error: continue without value required in this block.") else if n_expr != null and t != null then - v.check_conform_expr(n_expr, t) + v.check_conform_expr(n_expr.as(not null), t) end + _is_typed = true end end redef class ABreakExpr - redef meth after_typing(v) + redef fun after_typing(v) do - var t = v.closure_break_stype - if n_expr == null and t != null then - v.error(self, "Error: break with a value required in this bloc.") - else if n_expr != null and t == null then - v.error(self, "Error: break without value required in this bloc.") - else if n_expr != null and t != null then + var old_flow_ctx = v.flow_ctx + v.mark_unreash(self) + var esc = compute_escapable_block(v.scope_ctx) + if esc == null then return + + esc.break_flow_contexts.add(old_flow_ctx) + + var bl = esc.break_list + if n_expr == null and bl != null then + v.error(self, "Error: break with a value required in this block.") + else if n_expr != null and bl == null then + v.error(self, "Error: break without value required in this block.") + else if n_expr != null and bl != null then # Typing check can only be done later - v.break_list.add(n_expr) + bl.add(n_expr.as(not null)) + end + _is_typed = true + end +end + +redef class AAbortExpr + redef fun after_typing(v) + do + v.mark_unreash(self) + _is_typed = true + end +end + +# An abstract control structure with feature escapable block +class AAbsControl +special AExpr + # The corresponding escapable block + readable var _escapable: nullable EscapableBlock + + # Enter and process a control structure + private fun process_control(v: TypingVisitor, escapable: EscapableBlock, n_label: nullable ALabel, is_loop: Bool) + do + # Register the escapable block + _escapable = escapable + v.scope_ctx.push_escapable(escapable, n_label) + + # Save an prepare the contextes + var old_flow_ctx = v.flow_ctx + var old_base_flow_ctx = v.base_flow_ctx + if is_loop then v.base_flow_ctx = v.flow_ctx + + # Do the main processing + process_control_inside(v) + + # Add the end of the block as an exit context + if not v.flow_ctx.unreash then + escapable.break_flow_contexts.add(v.flow_ctx) end + + # Merge all exit contexts + if escapable.break_flow_contexts.is_empty then + v.flow_ctx = old_flow_ctx + v.mark_unreash(self) + else + v.flow_ctx = old_base_flow_ctx.merge(self, escapable.break_flow_contexts) + end + + if is_loop then v.base_flow_ctx = old_base_flow_ctx + v.scope_ctx.pop + _is_typed = true + end + + # What to do inside the control block? + private fun process_control_inside(v: TypingVisitor) is abstract +end + +redef class ADoExpr +special AAbsControl + redef fun accept_typing(v) + do + process_control(v, new BreakOnlyEscapableBlock(self), n_label, false) + end + + redef fun process_control_inside(v) + do + v.enter_visit_block(n_block) end end redef class AIfExpr - redef meth accept_typing(v) + redef fun accept_typing(v) do - var old_var_ctx = v.variable_ctx - v.visit(n_expr) + v.enter_visit(n_expr) v.check_conform_expr(n_expr, v.type_bool) - if n_expr.if_true_variable_ctx != null then - v.variable_ctx = n_expr.if_true_variable_ctx - end + # Prepare 'then' context + var old_flow_ctx = v.flow_ctx + v.use_if_true_flow_ctx(n_expr) - v.visit(n_then) - # Restore variable ctx - v.variable_ctx = old_var_ctx + # Process the 'then' + v.enter_visit_block(n_then) - if n_else != null then - v.visit(n_else) - v.variable_ctx = old_var_ctx - end + # Remember what appened in the 'then' + var then_flow_ctx = v.flow_ctx + + # Prepare 'else' context + v.flow_ctx = old_flow_ctx + v.use_if_false_flow_ctx(n_expr) + + # Process the 'else' + v.enter_visit_block(n_else) + + # Merge 'then' and 'else' contexts + v.flow_ctx = v.base_flow_ctx.merge_reash(self, then_flow_ctx, v.flow_ctx) + _is_typed = true end end redef class AWhileExpr - redef meth after_typing(v) +special AAbsControl + redef fun accept_typing(v) + do + process_control(v, new EscapableBlock(self), n_label, true) + end + + redef fun process_control_inside(v) do + var old_flow_ctx = v.flow_ctx + + # Process condition + v.enter_visit(n_expr) v.check_conform_expr(n_expr, v.type_bool) + + if n_expr isa ATrueExpr then + v.warning(self, "Warning: use 'loop' instead of 'while true do'.") + end + + # Prepare inside context (assert cond) + v.use_if_true_flow_ctx(n_expr) + + # Process inside + v.enter_visit_block(n_block) + + # Compute outside context (assert !cond + all breaks) + v.flow_ctx = old_flow_ctx + v.use_if_false_flow_ctx(n_expr) + escapable.break_flow_contexts.add(v.flow_ctx) end end -redef class AForExpr - redef meth after_typing(v) +redef class ALoopExpr +special AAbsControl + redef fun accept_typing(v) do - # pop context created in AForVardeclExpr - var varctx = v.variable_ctx - assert varctx isa SubVariableContext - v.variable_ctx = varctx.prev + process_control(v, new EscapableBlock(self), n_label, true) + end + + redef fun process_control_inside(v) + do + # Process inside + v.enter_visit_block(n_block) + + # Never automatically reach after the loop + v.mark_unreash(self) end end -redef class AForVardeclExpr - readable attr _meth_iterator: MMMethod - readable attr _meth_is_ok: MMMethod - readable attr _meth_item: MMMethod - readable attr _meth_next: MMMethod - redef meth after_typing(v) +redef class AForExpr +special AAbsControl + var _variable: nullable AutoVariable + redef fun variable do return _variable.as(not null) + + redef fun accept_typing(v) + do + process_control(v, new EscapableBlock(self), n_label, true) + end + + redef fun process_control_inside(v) do - v.variable_ctx = v.variable_ctx.sub - var va = new AutoVariable(n_id.to_symbol, self) - variable = va - v.variable_ctx.add(va) + v.scope_ctx.push(self) + var old_flow_ctx = v.flow_ctx + # Create the automatic variable + var va = new AutoVariable(n_id.to_symbol, n_id) + _variable = va + v.scope_ctx.add_variable(va) + + # Process collection + v.enter_visit(n_expr) + + if not v.check_conform_expr(n_expr, v.type_collection) then return var expr_type = n_expr.stype - if not v.check_conform_expr(n_expr, v.type_collection) then - return - end - _meth_iterator = expr_type.local_class.select_method(once ("iterator".to_symbol)) - if _meth_iterator == null then - v.error(self, "Error: Collection MUST have an iterate method") - return - end - var iter_type = _meth_iterator.signature_for(expr_type).return_type - _meth_is_ok = iter_type.local_class.select_method(once ("is_ok".to_symbol)) - if _meth_is_ok == null then - v.error(self, "Error: {iter_type} MUST have an is_ok method") - return - end - _meth_item = iter_type.local_class.select_method(once ("item".to_symbol)) - if _meth_item == null then - v.error(self, "Error: {iter_type} MUST have an item method") - return - end - _meth_next = iter_type.local_class.select_method(once ("next".to_symbol)) - if _meth_next == null then - v.error(self, "Error: {iter_type} MUST have a next method") - return - end - var t = _meth_item.signature_for(iter_type).return_type - if not n_expr.is_self then t = t.not_for_self - va.stype = t + + # Get iterator + var meth_iterator = v.get_method(expr_type, once "iterator".to_symbol) + var iter_type = meth_iterator.signature_for(expr_type).return_type.as(not null) + var meth_item = v.get_method(iter_type, once ("item".to_symbol)) + var va_stype = meth_item.signature_for(iter_type).return_type.as(not null) + if not n_expr.is_self then va_stype = va_stype.not_for_self + va.stype = va_stype + + # Process inside + v.enter_visit_block(n_block) + + # end == begin of the loop + v.flow_ctx = old_flow_ctx + v.scope_ctx.pop end end redef class AAssertExpr - redef meth after_typing(v) + redef fun accept_typing(v) do + # Process condition + v.enter_visit(n_expr) v.check_conform_expr(n_expr, v.type_bool) - if n_expr.if_true_variable_ctx != null then v.variable_ctx = n_expr.if_true_variable_ctx + + # Process optional 'else' part + if n_else != null then + var old_flow_ctx = v.flow_ctx + v.use_if_false_flow_ctx(n_expr) + v.enter_visit(n_else) + v.flow_ctx = old_flow_ctx + end + + # Prepare outside + v.use_if_true_flow_ctx(n_expr) + _is_typed = true end end +redef class AVarFormExpr + var _variable: nullable Variable + redef fun variable do return _variable.as(not null) +end + redef class AVarExpr - redef meth its_variable do return variable + redef fun its_variable do return variable - redef meth after_typing(v) + redef fun after_typing(v) do - _stype = v.variable_ctx.stype(variable) + v.flow_ctx.check_is_set(self, variable) + _stype = v.flow_ctx.stype(variable) + _is_typed = _stype != null end end redef class AVarAssignExpr - redef meth after_typing(v) + redef fun after_typing(v) do - var t = v.variable_ctx.stype(variable) - v.check_conform_expr(n_value, t) + v.mark_is_set(variable) + + # Check the base type + var btype = v.base_flow_ctx.stype(variable) + if not v.check_expr(n_value) then return + if btype != null and not v.check_conform_expr(n_value, btype) then return + + # Always cast + v.flow_ctx = v.flow_ctx.sub_with(self, 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 fun do_rvalue_typing(v: TypingVisitor, type_lvalue: nullable MMType): nullable MMType do if type_lvalue == null then - return + return null end var name = n_assign_op.method_name - var prop = type_lvalue.local_class.select_method(name) - if prop == null then + if type_lvalue isa MMTypeNone then + v.error(self, "Error: Method '{name}' call on 'null'.") + return null + end + 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 - v.check_conform_expr(n_value, psig[0].not_for_self) - v.check_conform(self, psig.return_type.not_for_self, n_value.stype) + 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) - readable attr _assign_method: MMMethod + redef fun assign_method do return _assign_method.as(not null) + var _assign_method: nullable MMMethod end redef class AVarReassignExpr - redef meth after_typing(v) + redef fun after_typing(v) do - var t = v.variable_ctx.stype(variable) - do_lvalue_typing(v, t) + v.flow_ctx.check_is_set(self, variable) + v.mark_is_set(variable) + var t = v.flow_ctx.stype(variable) + var t2 = do_rvalue_typing(v, t) + if t2 == null then return + + # Check the base type + var btype = v.base_flow_ctx.stype(variable) + if not v.check_expr(n_value) then return + if btype != null and not v.check_conform(n_value, t2, btype) then return + + # Always cast + v.flow_ctx = v.flow_ctx.sub_with(self, variable, t2) + + _is_typed = true end end -redef class PAssignOp - meth method_name: Symbol is abstract +redef class AAssignOp + fun method_name: Symbol is abstract end redef class APlusAssignOp - redef meth method_name do return once "+".to_symbol + redef fun method_name do return once "+".to_symbol end redef class AMinusAssignOp - redef meth method_name do return once "-".to_symbol + redef fun method_name do return once "-".to_symbol end redef class ASelfExpr - redef meth its_variable do return variable + var _variable: nullable ParamVariable + redef fun variable do return _variable.as(not null) - redef meth after_typing(v) + redef fun its_variable do return variable + + redef fun after_typing(v) do - variable = v.self_var - _stype = v.variable_ctx.stype(variable) + _variable = v.self_var + _stype = v.flow_ctx.stype(variable) + _is_typed = true end - redef meth is_self do return true + redef fun is_self do return true end redef class AImplicitSelfExpr - redef meth is_implicit_self do return true + redef fun is_implicit_self do return true end redef class AIfexprExpr - redef meth accept_typing(v) + redef fun accept_typing(v) do - var old_var_ctx = v.variable_ctx - - v.visit(n_expr) - if n_expr.if_true_variable_ctx != null then v.variable_ctx = n_expr.if_true_variable_ctx - v.visit(n_then) - v.variable_ctx = old_var_ctx - v.visit(n_else) + var old_flow_ctx = v.flow_ctx + # Process condition + v.enter_visit(n_expr) v.check_conform_expr(n_expr, v.type_bool) - if not v.check_expr(n_then) or not v.check_expr(n_else) then return + # Prepare 'then' context + v.use_if_true_flow_ctx(n_expr) - var t = n_then.stype - var te = n_else.stype - if t < te then - t = te - else if not te < t then - v.error(self, "Type error: {te} is not a subtype of {t}.") - return - end - - _stype = t + # Process 'then' + v.enter_visit_block(n_then) + + # Remember what appened in the 'then' + var then_flow_ctx = v.flow_ctx + + # Prepare 'else' context + v.flow_ctx = old_flow_ctx + v.use_if_false_flow_ctx(n_expr) + + # Process 'else' + v.enter_visit_block(n_else) + + # Merge 'then' and 'else' contexts + v.flow_ctx = v.base_flow_ctx.merge_reash(self, then_flow_ctx, v.flow_ctx) + + var stype = v.check_conform_multiexpr(null, [n_then, n_else]) + if stype == null then return + + _stype = stype + _is_typed = true end end redef class ABoolExpr - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_bool + _is_typed = true end end redef class AOrExpr - redef meth after_typing(v) + redef fun accept_typing(v) do - v.check_conform_expr(n_expr, v.type_bool) - v.check_conform_expr(n_expr2, v.type_bool) - _stype = v.type_bool + var old_flow_ctx = v.flow_ctx + var stype = v.type_bool + _stype = stype + + # Process left operand + v.enter_visit(n_expr) + + # Prepare right operand context + v.use_if_false_flow_ctx(n_expr) + + # Process right operand + v.enter_visit(n_expr2) + if n_expr2.if_false_flow_ctx != null then + _if_false_flow_ctx = n_expr2.if_false_flow_ctx + else + _if_false_flow_ctx = v.flow_ctx + end + + v.flow_ctx = old_flow_ctx + + v.check_conform_expr(n_expr, stype) + v.check_conform_expr(n_expr2, stype) + _stype = stype + _is_typed = true end end redef class AAndExpr - redef meth accept_typing(v) + redef fun accept_typing(v) do - var old_var_ctx = v.variable_ctx + var old_flow_ctx = v.flow_ctx + var stype = v.type_bool + + # Process left operand + v.enter_visit(n_expr) - v.visit(n_expr) - if n_expr.if_true_variable_ctx != null then v.variable_ctx = n_expr.if_true_variable_ctx + # Prepare right operand context + v.use_if_true_flow_ctx(n_expr) - v.visit(n_expr2) - if n_expr2.if_true_variable_ctx != null then - _if_true_variable_ctx = n_expr2.if_true_variable_ctx + # Process right operand + v.enter_visit(n_expr2) + if n_expr2.if_true_flow_ctx != null then + _if_true_flow_ctx = n_expr2.if_true_flow_ctx else - _if_true_variable_ctx = v.variable_ctx + _if_true_flow_ctx = v.flow_ctx end - v.variable_ctx = old_var_ctx + v.flow_ctx = old_flow_ctx - v.check_conform_expr(n_expr, v.type_bool) - v.check_conform_expr(n_expr2, v.type_bool) - _stype = v.type_bool + v.check_conform_expr(n_expr, stype) + v.check_conform_expr(n_expr2, stype) + _stype = stype + _is_typed = true end end redef class ANotExpr - redef meth after_typing(v) + redef fun after_typing(v) do v.check_conform_expr(n_expr, v.type_bool) + + # Invert if_true/if_false information + _if_false_flow_ctx = n_expr._if_true_flow_ctx + _if_true_flow_ctx = n_expr._if_false_flow_ctx + _stype = v.type_bool + _is_typed = true + end +end + +redef class AOrElseExpr + redef fun after_typing(v) + do + var old_flow_ctx = v.flow_ctx + + # Process left operand + v.enter_visit(n_expr) + v.check_expr(n_expr) + + # Consider the type of the left operand + var t = n_expr.stype + if not t.is_nullable then + v.warning(n_expr, "Warning: left operant of a 'or else' is not a nullable type.") + else + t = t.as_notnull + end + + # Prepare the else context : ie the first expression is null + var variable = n_expr.its_variable + if variable != null then + v.flow_ctx.sub_with(self, variable, v.type_none) + end + + # Process right operand + v.enter_visit(n_expr2) + v.check_expr(n_expr) + + # Restore the context + v.flow_ctx = old_flow_ctx + + # Merge the types + var stype = v.check_conform_multiexpr(t, [n_expr2]) + if stype == null then return + + _stype = stype + _is_typed = true end end redef class AIntExpr - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_int - + _is_typed = true end end redef class AFloatExpr - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_float + _is_typed = true end end redef class ACharExpr - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_char + _is_typed = true end end redef class AStringFormExpr - readable attr _meth_with_native: MMMethod - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_string - _meth_with_native = _stype.local_class.select_method(once "with_native".to_symbol) - if _meth_with_native == null then v.error(self, "{_stype} MUST have a with_native method.") + _is_typed = true end end redef class ASuperstringExpr - readable attr _meth_init: MMMethod - readable attr _meth_append: MMMethod - readable attr _meth_to_s: MMMethod - redef meth after_typing(v) + redef fun atype do return _atype.as(not null) + var _atype: nullable MMType + redef fun after_typing(v) do - _stype = v.type_string - _meth_init = _stype.local_class.select_method(once "init".to_symbol) - if _meth_init == null then v.error(self, "{_stype} MUST have an init method.") - _meth_append = _stype.local_class.select_method(once "append".to_symbol) - if _meth_append == null then v.error(self, "{_stype} MUST have a append method.") - _meth_to_s = v.type_object.local_class.select_method(once "to_s".to_symbol) - if _meth_to_s == null then v.error(self, "Object MUST have a to_s method.") + var otype = v.type_object + var stype = v.type_string + _stype = stype + for e in n_exprs do v.check_conform_expr(e, otype) + var atype = v.type_array(stype) + _atype = atype + _is_typed = true end end redef class ANullExpr - redef meth after_typing(v) + redef fun after_typing(v) do _stype = v.type_none + _is_typed = true end end redef class AArrayExpr - readable attr _meth_with_capacity: MMMethod - readable attr _meth_add: MMMethod - - redef meth after_typing(v) + redef fun after_typing(v) do - var stype: MMType = null - for n in n_exprs do - var ntype = n.stype - if stype == null or (ntype != null and stype < ntype) then - stype = ntype - end - end - for n in n_exprs do - v.check_conform_expr(n, stype) - end - do_typing(v, stype) + var stype = v.check_conform_multiexpr(null, n_exprs) + if stype != null then do_typing(v, stype) end - private meth do_typing(v: TypingVisitor, element_type: MMType) + private fun do_typing(v: TypingVisitor, element_type: MMType) do _stype = v.type_array(element_type) - - _meth_with_capacity = _stype.local_class.select_method(once "with_capacity".to_symbol) - if _meth_with_capacity == null then v.error(self, "{_stype} MUST have a with_capacity method.") - _meth_add = _stype.local_class.select_method(once "add".to_symbol) - if _meth_add == null then v.error(self, "{_stype} MUST have an add method.") + _is_typed = true end end redef class ARangeExpr - readable attr _meth_init: MMMethod - redef meth after_typing(v) + redef fun after_typing(v) do + if not v.check_expr(n_expr) or not v.check_expr(n_expr2) then return var ntype = n_expr.stype var ntype2 = n_expr2.stype - if ntype == null or ntype == null then - return - end if ntype < ntype2 then ntype = ntype2 else if not ntype2 < ntype then @@ -762,33 +1052,16 @@ redef class ARangeExpr return end var dtype = v.type_discrete - v.check_conform_expr(n_expr, dtype) - v.check_conform_expr(n_expr2, dtype) + if not v.check_conform_expr(n_expr, dtype) or not v.check_conform_expr(n_expr2, dtype) then return _stype = v.type_range(ntype) + _is_typed = true end end -redef class ACrangeExpr - redef meth after_typing(v) - do - super - _meth_init = stype.local_class.select_method(once "init".to_symbol) - end -end -redef class AOrangeExpr - redef meth after_typing(v) - do - super - _meth_init = stype.local_class.select_method(once "without_last".to_symbol) - end -end - - redef class ASuperExpr -special ASuperInitCall - # readable attr _prop: MMSrcMethod - readable attr _init_in_superclass: MMMethod - redef meth after_typing(v) + redef readable var _init_in_superclass: nullable MMMethod + redef fun compute_raw_arguments do return n_args.to_a + redef fun after_typing(v) do var precs: Array[MMLocalProperty] = v.local_property.prhe.direct_greaters if not precs.is_empty then @@ -814,54 +1087,61 @@ special ASuperInitCall _init_in_superclass = p register_super_init_call(v, p) if n_args.length > 0 then - var signature = get_signature(v, v.self_var.stype, p, true) - _arguments = process_signature(v, signature, p.name, n_args.to_a) + var signature = get_signature(v, v.self_var.stype.as(not null), p, true) + process_signature(v, signature, p.name, compute_raw_arguments) end else v.error(self, "Error: No super method to call for {v.local_property}.") return end - if precs.first.signature_for(v.self_var.stype).return_type != null then + if precs.first.signature_for(v.self_var.stype.as(not null)).return_type != null then var stypes = new Array[MMType] - var stype: MMType = null + var stype: nullable MMType = null for prop in precs do assert prop isa MMMethod - var t = prop.signature_for(v.self_var.stype).return_type.for_module(v.module).adapt_to(v.local_property.signature.recv) + var t = prop.signature_for(v.self_var.stype.as(not null)).return_type.for_module(v.module).adapt_to(v.local_property.signature.recv) stypes.add(t) if stype == null or stype < t then stype = t end end for t in stypes do - v.check_conform(self, t, stype) + v.check_conform(self, t, stype.as(not null)) end _stype = stype end var p = v.local_property assert p isa MMSrcMethod _prop = p + _is_typed = true end end redef class AAttrFormExpr - # Attribute accessed - readable attr _prop: MMAttribute + redef fun prop do return _prop.as(not null) + var _prop: nullable MMAttribute - # Attribute type of the acceded attribute - readable attr _attr_type: MMType + redef fun attr_type do return _attr_type.as(not null) + var _attr_type: nullable MMType # Compute the attribute accessed - private meth do_typing(v: TypingVisitor) + private fun do_typing(v: TypingVisitor) do if not v.check_expr(n_expr) then return var type_recv = n_expr.stype var name = n_id.to_symbol - var prop = type_recv.local_class.select_attribute(name) - if prop == null then + if type_recv isa MMTypeNone then + v.error(self, "Error: Attribute '{name}' access on 'null'.") + return + end + var lc = type_recv.local_class + if not lc.has_global_property_by_name(name) then v.error(self, "Error: Attribute {name} doesn't exists in {type_recv}.") return - else if v.module.visibility_for(prop.global.local_class.module) < 3 then + end + var prop = lc.select_attribute(name) + if v.module.visibility_for(prop.global.local_class.module) < 3 then v.error(self, "Error: Attribute {name} from {prop.global.local_class.module} is invisible in {v.module}") end _prop = prop @@ -872,134 +1152,109 @@ redef class AAttrFormExpr end redef class AAttrExpr - redef meth after_typing(v) + redef fun after_typing(v) do do_typing(v) - if prop == null then - return - end + if _prop == null then return _stype = attr_type + _is_typed = true end end redef class AAttrAssignExpr - redef meth after_typing(v) + redef fun after_typing(v) do do_typing(v) - if prop == null then - return - end - v.check_conform_expr(n_value, attr_type) + if _prop == null then return + if not v.check_conform_expr(n_value, attr_type) then return + _is_typed = true end end redef class AAttrReassignExpr - redef meth after_typing(v) + redef fun after_typing(v) do do_typing(v) - if prop == null then - return - end - do_lvalue_typing(v, attr_type) + if _prop == null then return + 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 -class AAbsSendExpr -special PExpr - # The signature of the called property - readable attr _prop_signature: MMSignature - - # Compute the called global property - private meth do_typing(v: TypingVisitor, type_recv: MMType, is_implicit_self: Bool, recv_is_self: Bool, name: Symbol, raw_args: Array[PExpr], closure_defs: Array[PClosureDef]) +redef class AIssetAttrExpr + redef fun after_typing(v) do - var prop = get_property(v, type_recv, is_implicit_self, name) - if prop == null then return - var sig = get_signature(v, type_recv, prop, recv_is_self) - if sig == null then return - var args = process_signature(v, sig, prop.name, raw_args) - if args == null then return - var rtype = process_closures(v, sig, prop.name, closure_defs) - _prop = prop - _prop_signature = sig - _arguments = args - _return_type = rtype + 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 - private meth get_property(v: TypingVisitor, type_recv: MMType, is_implicit_self: Bool, name: Symbol): MMMethod - do - if type_recv == null then return null - var prop = type_recv.local_class.select_method(name) - if prop == null and v.local_property.global.is_init then - var props = type_recv.local_class.super_methods_named(name) - if props.length > 1 then - v.error(self, "Error: Ambigous method name '{name}' for {props.join(", ")}. Use explicit designation.") - return null - else if props.length == 1 then - var p = type_recv.local_class[props.first.global] - assert p isa MMMethod - prop = p - end +redef class AAbsAbsSendExpr + # The signature of the called property + redef fun prop_signature do return _prop_signature.as(not null) + var _prop_signature: nullable MMSignature + # Raw arguments used (without vararg transformation) + redef fun raw_arguments: Array[AExpr] + do + var res = _raw_arguments_cache + if res != null then + return res + else + res = compute_raw_arguments + if res == null then res = new Array[AExpr] + _raw_arguments_cache = res + return res end - if prop == null then - if is_implicit_self then - v.error(self, "Error: Method or variable '{name}' unknown in {type_recv}.") - else - v.error(self, "Error: Method '{name}' doesn't exists in {type_recv}.") - end - return null - end - return prop end - # Get the signature for a local property and a receiver - private meth get_signature(v: TypingVisitor, type_recv: MMType, prop: MMMethod, recv_is_self: Bool): MMSignature + var _raw_arguments_cache: nullable Array[AExpr] = null + + fun compute_raw_arguments: nullable Array[AExpr] do - prop.global.check_visibility(v, self, v.module, recv_is_self) - var psig = prop.signature_for(type_recv) - if not recv_is_self then psig = psig.not_for_self - return psig + print "{location} no compute_raw_arguments" + return null end # Check the conformity of a set of arguments `raw_args' to a signature. - private meth process_signature(v: TypingVisitor, psig: MMSignature, name: Symbol, raw_args: Array[PExpr]): Array[PExpr] + private fun process_signature(v: TypingVisitor, psig: MMSignature, name: Symbol, raw_args: nullable Array[AExpr]): Bool do var par_vararg = psig.vararg_rank var par_arity = psig.arity var raw_arity: Int if raw_args == null then raw_arity = 0 else raw_arity = raw_args.length if par_arity > raw_arity or (par_arity != raw_arity and par_vararg == -1) then - v.error(self, "Error: '{name}' arity missmatch.") - return null + v.error(self, "Error: arity missmatch; prototype is '{name}{psig}'.") + return false end var arg_idx = 0 - var args = new Array[PExpr] for par_idx in [0..par_arity[ do - var a: PExpr + var a: AExpr var par_type = psig[par_idx] if par_idx == par_vararg then - var star = new Array[PExpr] for i in [0..(raw_arity-par_arity)] do a = raw_args[arg_idx] v.check_conform_expr(a, par_type) - star.add(a) arg_idx = arg_idx + 1 end - var aa = new AArrayExpr.init_aarrayexpr(star) - aa.do_typing(v, par_type) - a = aa else a = raw_args[arg_idx] v.check_conform_expr(a, par_type) arg_idx = arg_idx + 1 end - args.add(a) end - return args + return true end # Check the conformity of a set of defined closures - private meth process_closures(v: TypingVisitor, psig: MMSignature, name: Symbol, cd: Array[PClosureDef]): MMType + private fun process_closures(v: TypingVisitor, psig: MMSignature, name: Symbol, cd: nullable Array[AClosureDef]): nullable MMType do var t = psig.return_type var cs = psig.closures # Declared closures @@ -1007,61 +1262,136 @@ special PExpr for c in cs do if not c.is_optional then min_arity += 1 end - if cd != null then - if cs.length == 0 then - v.error(self, "Error: {name} does not require blocs.") - else if cd.length > cs.length or cd.length < min_arity then - v.error(self, "Error: {name} requires {cs.length} blocs, {cd.length} found.") + var arity = 0 + if cd != null then arity = cd.length + if cs.length > 0 then + if arity == 0 and min_arity > 0 then + v.error(self, "Error: {name} requires {cs.length} blocks.") + else if arity > cs.length or arity < min_arity then + v.error(self, "Error: {name} requires {cs.length} blocks, {cd.length} found.") else - var old_bbst = v.closure_break_stype - var old_bl = v.break_list - v.closure_break_stype = t - v.break_list = new Array[ABreakExpr] - for i in [0..cd.length[ do - cd[i].accept_typing2(v, cs[i]) - end - for n in v.break_list do - var ntype = n.stype - if t == null or (t != null and t < ntype) then - t = ntype + # Initialize the break list if a value is required for breaks (ie. if the method is a function) + var break_list: nullable Array[ABreakExpr] = null + if t != null then break_list = new Array[ABreakExpr] + + # The n_label, is any in only set on the last decl + var n_label = if arity > 0 then cd[arity-1].n_label else null + + # Process each closure definition + for i in [0..arity[ do + var cdi = cd[i] + var cni = cdi.n_id.to_symbol + var csi = psig.closure_named(cni) + if csi != null then + var esc = new EscapableClosure(cdi, csi, break_list) + v.scope_ctx.push_escapable(esc, n_label) + cdi.accept_typing2(v, esc) + v.scope_ctx.pop + else if cs.length == 1 then + v.error(cdi.n_id, "Error: no closure named '!{cni}' in {name}; only closure is !{cs.first.name}.") + else + var a = new Array[String] + for c in cs do + a.add("!{c.name}") + end + v.error(cdi.n_id, "Error: no closure named '!{cni}' in {name}; only closures are {a.join(",")}.") end end - for n in v.break_list do - v.check_conform_expr(n, t) - end - v.closure_break_stype = old_bbst - v.break_list = old_bl + # Check break type conformity + if break_list != null then + t = v.check_conform_multiexpr(t, break_list) + end end - else if min_arity != 0 then - v.error(self, "Error: {name} requires {cs.length} blocs.") + else if arity != 0 then + v.error(self, "Error: {name} does not require blocks.") end return t end +end - # The invoked method (once computed) - readable attr _prop: MMMethod +redef class AAbsSendExpr + # Compute the called global property + private fun do_typing(v: TypingVisitor, type_recv: MMType, is_implicit_self: Bool, recv_is_self: Bool, name: Symbol, raw_args: nullable Array[AExpr], closure_defs: nullable Array[AClosureDef]) + do + var prop = get_property(v, type_recv, is_implicit_self, name) + if prop == null then return + var sig = get_signature(v, type_recv, prop, recv_is_self) + if not process_signature(v, sig, prop.name, raw_args) then return + var rtype = process_closures(v, sig, prop.name, closure_defs) + if rtype == null and sig.return_type != null then return + _prop = prop + _prop_signature = sig + _return_type = rtype + end - # The real arguments used (after star transformation) (once computed) - readable attr _arguments: Array[PExpr] + private fun get_property(v: TypingVisitor, type_recv: MMType, is_implicit_self: Bool, name: Symbol): nullable MMMethod + do + if type_recv isa MMTypeNone then + if name == (once "==".to_symbol) or name == (once "!=".to_symbol) then + # Special case on != and == that are allowed for 'null' + type_recv = v.type_object.as_nullable + else + v.error(self, "Error: Method '{name}' call on 'null'.") + return null + end + end + var lc = type_recv.local_class + var prop: nullable MMMethod = null + if lc.has_global_property_by_name(name) then prop = lc.select_method(name) + if prop == null and v.local_property.global.is_init then + var props = lc.super_methods_named(name) + if props.length > 1 then + v.error(self, "Error: Ambigous method name '{name}' for {props.join(", ")}. Use explicit designation.") + return null + else if props.length == 1 then + var p = lc[props.first.global] + assert p isa MMMethod + prop = p + end + + end + if prop == null then + if is_implicit_self then + v.error(self, "Error: Method or variable '{name}' unknown in {type_recv}.") + else + v.error(self, "Error: Method '{name}' doesn't exists in {type_recv}.") + end + return null + end + return prop + end + + # Get the signature for a local property and a receiver + private fun get_signature(v: TypingVisitor, type_recv: MMType, prop: MMMethod, recv_is_self: Bool): MMSignature + do + prop.global.check_visibility(v, self, v.module, recv_is_self) + var psig = prop.signature_for(type_recv) + if not recv_is_self then psig = psig.not_for_self + return psig + end + + # The invoked method (once computed) + redef fun prop do return _prop.as(not null) + var _prop: nullable MMMethod # The return type (if any) (once computed) - readable attr _return_type: MMType + redef readable var _return_type: nullable MMType end # A possible call of constructor in a super class # Could be an explicit call or with the 'super' keyword -class ASuperInitCall -special AAbsSendExpr - private meth register_super_init_call(v: TypingVisitor, property: MMMethod) +redef class ASuperInitCall + private fun register_super_init_call(v: TypingVisitor, property: MMMethod) do if parent != v.top_block and self != v.top_block then v.error(self, "Error: Constructor invocation {property} must not be in nested block.") end var cla = v.module[property.global.intro.local_class.global] - var prev_class: MMLocalClass = null - if not v.explicit_super_init_calls.is_empty then - prev_class = v.explicit_super_init_calls.last.global.intro.local_class + var prev_class: nullable MMLocalClass = null + var esic = v.explicit_super_init_calls.as(not null) + if not esic.is_empty then + prev_class = esic.last.global.intro.local_class end var order = v.local_class.cshe.reverse_linear_extension if cla == v.local_class then @@ -1071,15 +1401,14 @@ special AAbsSendExpr else if cla == prev_class then v.error(self, "Error: Only one super constructor invocation of class {cla} is allowed.") else - var last_is_found = prev_class == null for c in order do if c == prev_class then - last_is_found = true + prev_class = null else if c == cla then - if not last_is_found then + if prev_class != null then v.error(self, "Error: Constructor of {c} must be invoked before constructor of {prev_class}") end - v.explicit_super_init_calls.add(property) + esic.add(property) break end end @@ -1089,11 +1418,11 @@ special AAbsSendExpr end redef class ANewExpr -special AAbsSendExpr - redef meth after_typing(v) + redef fun compute_raw_arguments do return n_args.to_a + redef fun after_typing(v) do + if not n_type.is_typed then return var t = n_type.stype - if t == null then return if t.local_class.global.is_abstract then v.error(self, "Error: try to instantiate abstract class {t.local_class}.") return @@ -1105,38 +1434,37 @@ special AAbsSendExpr name = n_id.to_symbol end - do_typing(v, t, false, false, name, n_args.to_a, null) - if prop == null then return + do_typing(v, t, false, false, name, raw_arguments, null) + if _prop == null then return if not prop.global.is_init then v.error(self, "Error: {prop} is not a constructor.") + return end _stype = t + _is_typed = true end end redef class ASendExpr -special ASuperInitCall # Name of the invoked property - meth name: Symbol is abstract - - # Raw arguments used (withour star transformation) - meth raw_arguments: Array[PExpr] is abstract + fun name: Symbol is abstract # Closure definitions - meth closure_defs: Array[PClosureDef] do return null + redef fun closure_defs: nullable Array[AClosureDef] do return null - redef meth after_typing(v) + redef fun after_typing(v) do do_all_typing(v) end - private meth do_all_typing(v: TypingVisitor) + private fun do_all_typing(v: TypingVisitor) do if not v.check_expr(n_expr) then return do_typing(v, n_expr.stype, n_expr.is_implicit_self, n_expr.is_self, name, raw_arguments, closure_defs) - if prop == null then return + if _prop == null then return + var prop = _prop.as(not null) if prop.global.is_init then if not v.local_property.global.is_init then @@ -1149,18 +1477,19 @@ special ASuperInitCall end _stype = return_type + _is_typed = true end end -class ASendReassignExpr -special ASendExpr -special AReassignFormExpr - readable attr _read_prop: MMMethod - redef meth do_all_typing(v) +redef class ASendReassignExpr + redef fun read_prop do return _read_prop.as(not null) + var _read_prop: nullable MMMethod + redef fun do_all_typing(v) do if not v.check_expr(n_expr) then return var raw_args = raw_arguments do_typing(v, n_expr.stype, n_expr.is_implicit_self, n_expr.is_self, name, raw_args, null) + var prop = _prop if prop == null then return if prop.global.is_init then if not v.local_property.global.is_init then @@ -1169,17 +1498,18 @@ special AReassignFormExpr v.error(self, "Error: constructor {prop} is not invoken on 'self'.") end end - var t = prop.signature_for(n_expr.stype).return_type + var t = prop.signature_for(n_expr.stype).return_type.as(not null) 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 + raw_args = raw_args.to_a raw_args.add(n_value) do_typing(v, n_expr.stype, n_expr.is_implicit_self, n_expr.is_self, "{name}=".to_symbol, raw_args, null) - if prop == null then return if prop.global.is_init then if not v.local_property.global.is_init then v.error(self, "Error: try to invoke constructor {prop} in a method.") @@ -1188,86 +1518,148 @@ special AReassignFormExpr end end - _arguments = old_args # FIXME: What if star parameters do not match betwen the two methods? + _is_typed = true end end redef class ABinopExpr - redef meth raw_arguments do return [n_expr2] + redef fun compute_raw_arguments do return [n_expr2] end redef class AEqExpr - redef meth name do return once "==".to_symbol + redef fun name do return once "==".to_symbol + redef fun after_typing(v) + do + super + if not n_expr.is_typed or not n_expr2.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 + if n_expr2.stype isa MMTypeNone then + v.warning(self, "Warning: comparaison between 2 null values.") + else + try_to_isa(v, n_expr2) + end + else if n_expr2.stype isa MMTypeNone then + try_to_isa(v, n_expr) + end + end + + private fun try_to_isa(v: TypingVisitor, n: AExpr) + do + var variable = n.its_variable + if variable != null and n.stype isa MMNullableType then + _if_false_flow_ctx = v.flow_ctx.sub_with(self, variable, n.stype.as_notnull) + _if_true_flow_ctx = v.flow_ctx.sub_with(self, variable, v.type_none) + end + end end redef class ANeExpr - redef meth name do return once "!=".to_symbol + redef fun name do return once "!=".to_symbol + redef fun after_typing(v) + do + super + if not n_expr.is_typed or not n_expr2.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 + if n_expr2.stype isa MMTypeNone then + v.warning(self, "Warning: comparaison between 2 null values.") + else + try_to_isa(v, n_expr2) + end + else if n_expr2.stype isa MMTypeNone then + try_to_isa(v, n_expr) + end + end + + private fun try_to_isa(v: TypingVisitor, n: AExpr) + do + var variable = n.its_variable + if variable != null and n.stype isa MMNullableType then + _if_true_flow_ctx = v.flow_ctx.sub_with(self, variable, n.stype.as_notnull) + _if_false_flow_ctx = v.flow_ctx.sub_with(self, variable, v.type_none) + end + end end redef class ALtExpr - redef meth name do return once "<".to_symbol + redef fun name do return once "<".to_symbol end redef class ALeExpr - redef meth name do return once "<=".to_symbol + redef fun name do return once "<=".to_symbol +end +redef class ALlExpr + redef fun name do return once "<<".to_symbol end redef class AGtExpr - redef meth name do return once ">".to_symbol + redef fun name do return once ">".to_symbol end redef class AGeExpr - redef meth name do return once ">=".to_symbol + redef fun name do return once ">=".to_symbol +end +redef class AGgExpr + redef fun name do return once ">>".to_symbol end redef class APlusExpr - redef meth name do return once "+".to_symbol + redef fun name do return once "+".to_symbol end redef class AMinusExpr - redef meth name do return once "-".to_symbol + redef fun name do return once "-".to_symbol end redef class AStarshipExpr - redef meth name do return once "<=>".to_symbol + redef fun name do return once "<=>".to_symbol end redef class AStarExpr - redef meth name do return once "*".to_symbol + redef fun name do return once "*".to_symbol end redef class ASlashExpr - redef meth name do return once "/".to_symbol + redef fun name do return once "/".to_symbol end redef class APercentExpr - redef meth name do return once "%".to_symbol + redef fun name do return once "%".to_symbol end redef class AUminusExpr - redef meth name do return once "unary -".to_symbol - redef meth raw_arguments do return null + redef fun name do return once "unary -".to_symbol + redef fun compute_raw_arguments do return null end redef class ACallFormExpr - redef meth after_typing(v) + redef fun after_typing(v) do - if n_expr != null and n_expr.is_implicit_self then + if n_expr.is_implicit_self then var name = n_id.to_symbol - var variable = v.variable_ctx[name] + var variable = v.scope_ctx[name] if variable != null then + var n: AExpr if variable isa ClosureVariable then - var n = new AClosureCallExpr(n_id, n_args, n_closure_defs) - replace_with(n) - n.variable = variable - n.after_typing(v) - return + n = new AClosureCallExpr.init_aclosurecallexpr(n_id, n_args, n_closure_defs) + n._variable = variable else if not n_args.is_empty then v.error(self, "Error: {name} is variable, not a function.") + return end - var vform = variable_create(variable) - vform.variable = variable - replace_with(vform) - vform.after_typing(v) - return + n = variable_create(variable) + n._variable = variable end + replace_with(n) + n.after_typing(v) + return end end + super end - - redef meth closure_defs + + redef fun closure_defs do - if n_closure_defs == null or n_closure_defs.is_empty then + if n_closure_defs.is_empty then return null else return n_closure_defs.to_a @@ -1275,27 +1667,27 @@ redef class ACallFormExpr end # Create a variable acces corresponding to the call form - meth variable_create(variable: Variable): AVarFormExpr is abstract + fun variable_create(variable: Variable): AVarFormExpr is abstract end redef class ACallExpr - redef meth variable_create(variable) + redef fun variable_create(variable) do return new AVarExpr.init_avarexpr(n_id) end - redef meth name do return n_id.to_symbol - redef meth raw_arguments do return n_args.to_a + redef fun name do return n_id.to_symbol + redef fun compute_raw_arguments do return n_args.to_a end redef class ACallAssignExpr - redef meth variable_create(variable) + redef fun variable_create(variable) do return new AVarAssignExpr.init_avarassignexpr(n_id, n_assign, n_value) end - redef meth name do return (n_id.text + "=").to_symbol - redef meth raw_arguments do + redef fun name do return (n_id.text + "=").to_symbol + redef fun compute_raw_arguments do var res = n_args.to_a res.add(n_value) return res @@ -1303,24 +1695,31 @@ redef class ACallAssignExpr end redef class ACallReassignExpr -special ASendReassignExpr - redef meth variable_create(variable) + redef fun variable_create(variable) do return new AVarReassignExpr.init_avarreassignexpr(n_id, n_assign_op, n_value) end - redef meth name do return n_id.to_symbol - redef meth raw_arguments do return n_args.to_a + redef fun name do return n_id.to_symbol + redef fun compute_raw_arguments do return n_args.to_a end redef class ABraExpr - redef meth name do return once "[]".to_symbol - redef meth raw_arguments do return n_args.to_a + redef fun name do return once "[]".to_symbol + redef fun compute_raw_arguments do return n_args.to_a + redef fun closure_defs + do + if n_closure_defs.is_empty then + return null + else + return n_closure_defs.to_a + end + end end redef class ABraAssignExpr - redef meth name do return once "[]=".to_symbol - redef meth raw_arguments do + redef fun name do return once "[]=".to_symbol + redef fun compute_raw_arguments do var res = n_args.to_a res.add(n_value) return res @@ -1328,96 +1727,194 @@ redef class ABraAssignExpr end redef class ABraReassignExpr -special ASendReassignExpr - redef meth name do return once "[]".to_symbol - redef meth raw_arguments do return n_args.to_a + redef fun name do return once "[]".to_symbol + redef fun compute_raw_arguments do return n_args.to_a end redef class AInitExpr - redef meth name do return once "init".to_symbol - redef meth raw_arguments do return n_args.to_a + redef fun name do return once "init".to_symbol + redef fun compute_raw_arguments do return n_args.to_a end redef class AClosureCallExpr - redef meth after_typing(v) + var _variable: nullable ClosureVariable + redef fun variable do return _variable.as(not null) + redef fun compute_raw_arguments do return n_args.to_a + + redef fun after_typing(v) do var va = variable + if va.closure.is_break then v.mark_unreash(self) var sig = va.closure.signature - var args = process_signature(v, sig, n_id.to_symbol, n_args.to_a) - if closure_defs != null then - process_closures(v, sig, n_id.to_symbol, closure_defs) + var s = process_signature(v, sig, n_id.to_symbol, compute_raw_arguments) + if not n_closure_defs.is_empty then + process_closures(v, sig, n_id.to_symbol, n_closure_defs.to_a) end - if args == null then return - _prop = null + if not s then return _prop_signature = sig - _arguments = args _stype = sig.return_type + _is_typed = true end end -redef class PClosureDef - attr _accept_typing2: Bool - redef meth accept_typing(v) +redef class AClosureId + fun to_symbol: Symbol is abstract +end +redef class ASimpleClosureId + redef fun to_symbol: Symbol do return n_id.to_symbol +end +redef class ABreakClosureId + redef fun to_symbol: Symbol do return n_kwbreak.to_symbol +end + +redef class AClosureDef + var _closure: nullable MMClosure + redef fun closure do return _closure.as(not null) + + # The corresponding escapable object + readable var _escapable: nullable EscapableBlock + + var _accept_typing2: Bool = false + redef fun accept_typing(v) do # Typing is deferred, wait accept_typing2(v) if _accept_typing2 then super end - private meth accept_typing2(v: TypingVisitor, clos: MMClosure) is abstract -end + private fun accept_typing2(v: TypingVisitor, esc: EscapableClosure) + do + _escapable = esc -redef class AClosureDef - redef meth accept_typing2(v, clos) - do - var sig = clos.signature - if sig.arity != n_id.length then - v.error(self, "Error: {sig.arity} automatic variable names expected, {n_id.length} found.") + var sig = esc.closure.signature + if sig.arity != n_ids.length then + v.error(self, "Error: {sig.arity} automatic variable names expected, {n_ids.length} found.") return end - closure = clos + _closure = esc.closure - var old_clos = v.closure - v.closure = clos - - v.variable_ctx = v.variable_ctx.sub + v.scope_ctx.push(self) + var old_flow_ctx = v.flow_ctx + var old_base_flow_ctx = v.base_flow_ctx + v.base_flow_ctx = v.flow_ctx variables = new Array[AutoVariable] - for i in [0..n_id.length[ do - var va = new AutoVariable(n_id[i].to_symbol, self) + for i in [0..n_ids.length[ do + var va = new AutoVariable(n_ids[i].to_symbol, n_ids[i]) variables.add(va) va.stype = sig[i] - v.variable_ctx.add(va) + v.scope_ctx.add_variable(va) end _accept_typing2 = true accept_typing(v) - v.closure = old_clos + if v.flow_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 and esc.break_list != null then + v.error(self, "Control error: Reached end of break block (a 'break' with a value was expected).") + end + end + v.flow_ctx = old_flow_ctx + v.base_flow_ctx = old_base_flow_ctx + v.scope_ctx.pop + end +end + +class ATypeCheckExpr +special AExpr + private fun check_expr_cast(v: TypingVisitor, n_expr: AExpr, n_type: AType) + do + if not v.check_expr(n_expr) then return + if not n_type.is_typed then return + var etype = n_expr.stype + var ttype = n_type.stype + if etype == ttype then + 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 isa MMTypeNone then + # ttype is not nullable because of prevous test + v.warning(self, "Warning: Expression is null therefore cannot be a {ttype}.") + 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 redef class AIsaExpr - redef meth after_typing(v) +special ATypeCheckExpr + redef fun after_typing(v) do + check_expr_cast(v, n_expr, n_type) + if not n_type.is_typed then return var variable = n_expr.its_variable if variable != null then - _if_true_variable_ctx = v.variable_ctx.sub_with(variable, n_type.stype) + _if_true_flow_ctx = v.flow_ctx.sub_with(self, variable, n_type.stype) end _stype = v.type_bool + _is_typed = true end end redef class AAsCastExpr - redef meth after_typing(v) +special ATypeCheckExpr + redef fun after_typing(v) do - v.check_expr(n_expr) + check_expr_cast(v, n_expr, n_type) + if not n_type.is_typed then return _stype = n_type.stype + _is_typed = _stype != null + end +end + +redef class AAsNotnullExpr + redef fun 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) + redef fun after_typing(v) do + if not n_expr.is_typed then return + _is_typed = true + if n_expr.is_statement then return _stype = n_expr.stype end end + +redef class AOnceExpr + redef fun 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 +