X-Git-Url: http://nitlanguage.org diff --git a/src/syntax/control_flow.nit b/src/syntax/control_flow.nit index 0903077..bed98a0 100644 --- a/src/syntax/control_flow.nit +++ b/src/syntax/control_flow.nit @@ -1,6 +1,6 @@ # This file is part of NIT ( http://www.nitlanguage.org ). # -# Copyright 2008 Jean Privat +# Copyright 2008-2009 Jean Privat # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,363 +14,234 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Analysis control flow in property bodies, statements and expressions +# Analysis control flow and variable visibility in property bodies, statements and expressions package control_flow import syntax_base -redef class MMSrcModule - # Walk trough the module and type statments and expressions - # Require than supermodules are processed - meth do_control_flow(tc: ToolContext) +# 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): nullable Variable do - var tv = new ControlFlowVisitor(tc, self) - tv.visit(node) + if _dico.has_key(s) then + return _dico[s] + else + return null + end end -end -redef class Variable - # Is the variable must be set before being used ? - meth must_be_set: Bool do return false -end - -redef class VarVariable - redef meth must_be_set do return true -end - - - -# Control flow visitor -# * Check reachability in methods -# * Associate breaks and continues -# * Check some other warning -private class ControlFlowVisitor -special AbsSyntaxVisitor - redef meth visit(n) + # Register a new variable with its name + meth add(v: Variable) do - if n != null then n.accept_control_flow(self) + _dico[v.name] = v + _all_variables.add(v) end - # Number of nested once - readable writable attr _once_count: Int = 0 - - # Current knowledge about variables types - readable writable attr _control_flow_ctx: ControlFlowContext + meth mark_is_set(v: Variable) + do + _set_variables.add(v) + end meth check_is_set(n: PNode, v: Variable) do - if v.must_be_set and not control_flow_ctx.is_set(v) then - error(n, "Error: variable '{v}' is possibly unset.") - var cfc = control_flow_ctx - while cfc != null do - print("cfc: " + cfc.set_variables.join(" ")) - cfc = cfc.prev + if v.must_be_set and not is_set(v) then + _visitor.error(n, "Error: variable '{v}' is possibly unset.") + var x = self + while true do + print " {x.node.locate}: {x._set_variables.join(", ")} ; {x._dico.join(", ")}" + var x0 = x + if x0 isa SubVariableContext then + x = x0.prev + else + break + end end end end - meth mark_is_set(v: Variable) + # The effective static type of a given variable + # May be different from the declaration static type + meth stype(v: Variable): nullable MMType do - control_flow_ctx.set_variables.add(v) + if _stypes.has_key(v) then + return _stypes[v] + else + return v.stype + end end - init(tc, m) do super -end - -private class ControlFlowContext - # Previous control flow context if any - readable attr _prev: ControlFlowContext + # Set effective static type of a given variable + # May be different from the declaration static type + meth stype=(v: Variable, t: nullable MMType) + do + _stypes[v] = t + end - # Is a control flow break met? (return, break, continue) - readable writable attr _unreash: Bool = false + # Variables by name (in the current context only) + attr _dico: Map[Symbol, Variable] - # Is a control flow already broken? - # Used to avoid repeating the same error message - readable writable attr _already_unreash: Bool = false + # All variables in all contextes + attr _all_variables: Set[Variable] - # Current controlable block (for or while) - readable writable attr _base_block: PNode = null + # Updated static type of variables + attr _stypes: Map[Variable, nullable MMType] = new HashMap[Variable, nullable MMType] - # Set of variable that are set (assigned) - readable attr _set_variables: HashSet[Variable] = new HashSet[Variable] - - # Is a variable set? - meth is_set(v: Variable): Bool - do - return _set_variables.has(v) or (_prev != null and _prev.is_set(v)) - end - - meth sub: ControlFlowContext + # Build a new VariableContext + meth sub(node: PNode): SubVariableContext do - return new ControlFlowContext.with_prev(self) - end - - init - do + return new SubVariableContext.with_prev(self, node) end - init with_prev(p: ControlFlowContext) + # Build a nested VariableContext with new variable information + meth sub_with(node: PNode, v: Variable, t: MMType): SubVariableContext do - _prev = p - _unreash = p.unreash - _already_unreash = p.already_unreash - _base_block = p.base_block + var ctx = sub(node) + ctx.stype(v) = t + return ctx end -end -############################################################################### + # The visitor of the context (used to display error) + attr _visitor: AbsSyntaxVisitor -redef class PNode - private meth accept_control_flow(v: ControlFlowVisitor) - do - accept_abs_syntax_visitor(v) - end -end + # The syntax node that introduced the context + readable attr _node: PNode -redef class AMethPropdef - redef meth accept_control_flow(v) + init(visitor: AbsSyntaxVisitor, node: PNode) do - v.control_flow_ctx = new ControlFlowContext - super + _visitor = visitor + _node = node + _dico = new HashMap[Symbol, Variable] end -end -redef class AConcreteMethPropdef - redef meth accept_control_flow(v) - do - super - if v.control_flow_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 + # Is a control flow break met? (return, break, continue) + readable writable attr _unreash: Bool = false -redef class AVardeclExpr - redef meth accept_control_flow(v) - do - super - if n_expr != null then v.mark_is_set(variable) - end -end + # Is a control flow already broken? + # Used to avoid repeating the same error message + readable writable attr _already_unreash: Bool = false -redef class ABlockExpr - redef meth accept_control_flow(v) - do - for e in n_expr do - if v.control_flow_ctx.unreash and not v.control_flow_ctx.already_unreash then - v.control_flow_ctx.already_unreash = true - v.warning(e, "Warning: unreachable statement.") - end - v.visit(e) - end - end -end + # Set of variable that are set (assigned) + readable attr _set_variables: HashSet[Variable] = new HashSet[Variable] -redef class AReturnExpr - redef meth accept_control_flow(v) + # Is a variable set? + meth is_set(v: Variable): Bool do - super - v.control_flow_ctx.unreash = true + return _set_variables.has(v) end -end -class ABlockControler -special PExpr - readable attr _block: PNode -end - -redef class ABreakExpr -special ABlockControler - redef meth accept_control_flow(v) + # Merge back one flow context information + meth merge(ctx: VariableContext) do - super - var block = v.control_flow_ctx.base_block - if block == null then - v.error(self, "Syntax Error: 'break' statment outside block.") + if ctx.unreash then + unreash = true + if ctx.already_unreash then already_unreash = true return end - _block = block - v.control_flow_ctx.unreash = true - end -end -redef class AContinueExpr -special ABlockControler - redef meth accept_control_flow(v) - do - super - var block = v.control_flow_ctx.base_block - if block == null then - v.error(self, "Syntax Error: 'continue' outside block.") - return + for v in _all_variables do + if not is_set(v) and ctx.is_set(v) then + mark_is_set(v) + end + var s = stype(v) + var s1 = ctx.stype(v) + if s1 != s then stype(v) = s1 end - _block = block - v.control_flow_ctx.unreash = true - end -end - -redef class AAbortExpr - redef meth accept_control_flow(v) - do - super - v.control_flow_ctx.unreash = true end -end -redef class AClosureCallExpr - redef meth accept_control_flow(v) + # Merge back two alternative flow context informations + meth merge2(ctx1, ctx2, basectx: VariableContext) do - super - if variable.closure.is_break then v.control_flow_ctx.unreash = true - end -end - -redef class AIfExpr - redef meth accept_control_flow(v) - do - v.visit(n_expr) - - var old_control_flow_ctx = v.control_flow_ctx - v.control_flow_ctx = v.control_flow_ctx.sub - - v.visit(n_then) - - if n_else == null then - # Restore control flow ctx since the 'then" block is optional - v.control_flow_ctx = old_control_flow_ctx - else - # Remember what appens in the 'then' - var then_control_flow_ctx = v.control_flow_ctx - # Reset to execute the 'else' - v.control_flow_ctx = old_control_flow_ctx.sub - - v.visit(n_else) - - # Merge then and else in the old control_flow - old_control_flow_ctx.unreash = v.control_flow_ctx.unreash and then_control_flow_ctx.unreash + if ctx1.unreash then + merge(ctx2) + else if ctx2.unreash then + merge(ctx1) + end + for v in _all_variables do + if not is_set(v) and ctx1.is_set(v) and ctx2.is_set(v) then + mark_is_set(v) + end - if v.control_flow_ctx.unreash then v.control_flow_ctx = then_control_flow_ctx - if then_control_flow_ctx.unreash then then_control_flow_ctx = v.control_flow_ctx - for variable in v.control_flow_ctx.set_variables do - if then_control_flow_ctx.is_set(variable) then - old_control_flow_ctx.set_variables.add(variable) - end + var s = stype(v) + var s1 = ctx1.stype(v) + var s2 = ctx2.stype(v) + if s1 == s and s2 == s then + # NOP + else if s1 == s2 then + stype(v) = s1 + else if s2 == null or s1 < s2 then + stype(v) = s2 + else if s1 == null or s2 < s1 then + stype(v) = s1 + else + stype(v) = basectx.stype(v) end - v.control_flow_ctx = old_control_flow_ctx end end -end -class AControlableBlock -special PExpr - redef meth accept_control_flow(v) + redef meth to_s do - # Store old control flow values - var old_control_flow_ctx = v.control_flow_ctx - v.control_flow_ctx = v.control_flow_ctx.sub - - # Register the block - v.control_flow_ctx.base_block = self - - super - - # Check control flow if any - check_control_flow(v) - - # Restore control flow value since all controlable blocks are optionnal - v.control_flow_ctx = old_control_flow_ctx + var s = new Buffer + s.append(node.locate) + for v in _all_variables do + var t = stype(v) + if t == null then continue + s.append(" {v}:{t}") + end + return s.to_s end - - private meth check_control_flow(v: ControlFlowVisitor) do end -end - -redef class AWhileExpr -special AControlableBlock -end - -redef class AForExpr -special AControlableBlock end -redef class AVarExpr - redef meth accept_control_flow(v) +class RootVariableContext +special VariableContext + init(visitor: AbsSyntaxVisitor, node: PNode) do - super - v.check_is_set(self, variable) + super(visitor, node) + _all_variables = new HashSet[Variable] end end -redef class AVarAssignExpr - redef meth accept_control_flow(v) - do - super - v.mark_is_set(variable) - end -end +class SubVariableContext +special VariableContext + readable attr _prev: VariableContext -redef class AVarReassignExpr - redef meth accept_control_flow(v) + redef meth [](s) do - super - v.check_is_set(self, variable) - v.mark_is_set(variable) + if _dico.has_key(s) then + return _dico[s] + else + return prev[s] + end end -end -redef class AClosureDecl - redef meth accept_control_flow(v) + redef meth stype(v) do - if n_expr != null then - var old_control_flow_ctx = v.control_flow_ctx - v.control_flow_ctx = v.control_flow_ctx.sub - # Register the block - v.control_flow_ctx.base_block = n_expr - - super - - if v.control_flow_ctx.unreash == false then - if variable.closure.signature.return_type != null then - v.error(self, "Control error: Reached end of bloc (a 'continue' with a value was expected).") - else if variable.closure.is_break then - v.error(self, "Control error: Reached end of break bloc (an 'abort' was expected).") - end - end - - v.control_flow_ctx = old_control_flow_ctx + if _stypes.has_key(v) then + return _stypes[v] + else + return prev.stype(v) end end -end -redef class AClosureDef -special AControlableBlock - redef meth accept_control_flow(v) + init with_prev(p: VariableContext, node: PNode) do - for va in variables do v.mark_is_set(va) - super + init(p._visitor, node) + _prev = p + _all_variables = p._all_variables end - redef meth check_control_flow(v) + redef meth is_set(v) do - if v.control_flow_ctx.unreash == false then - if closure.signature.return_type != null then - v.error(self, "Control error: Reached end of bloc (a 'continue' with a value was expected).") - else if closure.is_break then - v.error(self, "Control error: Reached end of break bloc (a 'break' was expected).") - end - end + return _set_variables.has(v) or _prev.is_set(v) end end -redef class AOnceExpr - redef meth accept_control_flow(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 +redef class Variable + # Is the variable must be set before being used ? + meth must_be_set: Bool do return false end +redef class VarVariable + redef meth must_be_set do return true +end