diff --git a/lib/ast.js b/lib/ast.js index 3b65dcf0e..80b84e466 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -423,6 +423,7 @@ SourceNode::to-string = (...args) -> getCall: VOID, getDefault: VOID, getJump: VOID, + isNextUnreachable: NO, extractKeyRef: function(o, assign){ return this.maybeKey() || this.carp(assign ? "invalid assign" : "invalid property shorthand"); }, @@ -627,6 +628,16 @@ exports.Block = Block = (function(superclass){ } } }; + Block.prototype.isNextUnreachable = function(){ + var i$, ref$, len$, node; + for (i$ = 0, len$ = (ref$ = this.lines).length; i$ < len$; ++i$) { + node = ref$[i$]; + if (node.isNextUnreachable()) { + return true; + } + } + return false; + }; Block.prototype.makeReturn = function(){ var that, ref$, key$, ref1$; this.chomp(); @@ -3405,7 +3416,7 @@ exports.Parens = Parens = (function(superclass){ Parens.prototype.show = function(){ return this.string && '""'; }; - prototype.delegate(['isComplex', 'isCallable', 'isArray', 'isRegex'], function(it){ + prototype.delegate(['isComplex', 'isCallable', 'isArray', 'isRegex', 'isNextUnreachable'], function(it){ return this.it[it](); }); Parens.prototype.isString = function(){ @@ -3543,6 +3554,7 @@ exports.Jump = Jump = (function(superclass){ }; Jump.prototype.isStatement = YES; Jump.prototype.makeReturn = THIS; + Jump.prototype.isNextUnreachable = YES; Jump.prototype.getJump = function(ctx){ var that, ref$; ctx || (ctx = {}); @@ -3850,6 +3862,7 @@ exports.For = For = (function(superclass){ } return this; }; + For.prototype.isNextUnreachable = NO; For.prototype.compileNode = function(o){ var temps, idx, ref$, pvar, step, tvar, tail, fvar, vars, eq, cond, svar, srcPart, lvar, head, that, body; o.loop = true; @@ -4006,6 +4019,10 @@ exports.Try = Try = (function(superclass){ var ref$; return this.attempt.getJump(it) || ((ref$ = this.recovery) != null ? ref$.getJump(it) : void 8); }; + Try.prototype.isNextUnreachable = function(){ + var ref$, that; + return ((ref$ = this.ensure) != null ? ref$.isNextUnreachable() : void 8) || this.attempt.isNextUnreachable() && ((that = this.recovery) != null ? that.isNextUnreachable() : true); + }; Try.prototype.makeReturn = function(){ var ref$; this.attempt = (ref$ = this.attempt).makeReturn.apply(ref$, arguments); @@ -4086,6 +4103,16 @@ exports.Switch = Switch = (function(superclass){ } return (ref$ = this['default']) != null ? ref$.getJump(ctx) : void 8; }; + Switch.prototype.isNextUnreachable = function(){ + var i$, ref$, len$, c; + for (i$ = 0, len$ = (ref$ = this.cases).length; i$ < len$; ++i$) { + c = ref$[i$]; + if (!c.body.isNextUnreachable()) { + return false; + } + } + return (ref$ = this['default']) != null ? ref$.isNextUnreachable() : void 8; + }; Switch.prototype.makeReturn = function(){ var i$, ref$, len$, c; for (i$ = 0, len$ = (ref$ = this.cases).length; i$ < len$; ++i$) { @@ -4186,7 +4213,7 @@ exports.Case = Case = (function(superclass){ if (!snEmpty(bodyCode = this.body.compile(o, LEVEL_TOP))) { code.push(bodyCode, '\n'); } - if (!(nobr || ft || last instanceof Jump)) { + if (!(nobr || ft || (last != null && last.isNextUnreachable()))) { code.push(tab + 'break;\n'); } return sn.apply(null, [null].concat(slice$.call(code))); @@ -4209,7 +4236,7 @@ exports.If = If = (function(superclass){ return this.un && '!'; }; If.prototype.terminator = ''; - prototype.delegate(['isCallable', 'isArray', 'isString', 'isRegex'], function(it){ + prototype.delegate(['isCallable', 'isArray', 'isString', 'isRegex', 'isNextUnreachable'], function(it){ var ref$; return ((ref$ = this['else']) != null ? ref$[it]() : void 8) && this.then[it](); }); diff --git a/src/ast.ls b/src/ast.ls index 4880bcec9..ebc82e1d8 100644 --- a/src/ast.ls +++ b/src/ast.ls @@ -299,6 +299,7 @@ SourceNode::to-string = (...args) -> get-default : VOID # Digs up a statement that jumps out of this node. get-jump : VOID + is-next-unreachable : NO # If this node can be used as a property shorthand, finds the implied key. # If the key is dynamic, this node may be mutated so that it refers to a @@ -426,6 +427,10 @@ class exports.Block extends Node get-jump: -> for node in @lines then return that if node.get-jump it + is-next-unreachable: -> + for node in @lines then return true if node.is-next-unreachable! + false + # **Block** does not return its entire body, rather it # ensures that the final line is returned. make-return: -> @@ -2185,7 +2190,7 @@ class exports.Parens extends Node show: -> @string and '""' - ::delegate <[ isComplex isCallable isArray isRegex ]> -> @it[it]! + ::delegate <[ isComplex isCallable isArray isRegex isNextUnreachable ]> -> @it[it]! is-string: -> @string or @it.is-string! @@ -2280,6 +2285,7 @@ class exports.Jump extends Node is-statement : YES make-return : THIS + is-next-unreachable : YES get-jump: (ctx or {}) -> return this unless ctx[@verb] @@ -2470,6 +2476,8 @@ class exports.For extends While delete @item this + is-next-unreachable: NO + compile-node: (o) -> o.loop = true temps = @temps = [] @@ -2573,6 +2581,8 @@ class exports.Try extends Node get-jump: -> @attempt.get-jump it or @recovery?get-jump it + is-next-unreachable: -> @ensure?is-next-unreachable! or @attempt.is-next-unreachable! and (if @recovery? then that.is-next-unreachable! else true) + make-return: -> @attempt .=make-return ...& @recovery?=make-return ...& @@ -2620,6 +2630,10 @@ class exports.Switch extends Node for c in @cases then return that if c.body.get-jump ctx @default?get-jump ctx + is-next-unreachable: -> + for c in @cases then return false unless c.body.is-next-unreachable! + @default?is-next-unreachable! + make-return: -> for c in @cases then c.make-return ...& @default?make-return ...& @@ -2679,7 +2693,7 @@ class exports.Case extends Node lines[*-1] = JS '// fallthrough' if ft = last?value is \fallthrough o.indent = tab += TAB code.push bodyCode, \\n unless sn-empty(bodyCode = @body.compile o, LEVEL_TOP) - code.push tab + 'break;\n' unless nobr or ft or last instanceof Jump + code.push tab + 'break;\n' unless nobr or ft or last?is-next-unreachable! sn(null, ...code) #### If @@ -2695,7 +2709,7 @@ class exports.If extends Node terminator: '' - ::delegate <[ isCallable isArray isString isRegex ]> -> + ::delegate <[ isCallable isArray isString isRegex isNextUnreachable ]> -> @else?[it]! and @then[it]! get-jump: -> @then.get-jump it or @else?get-jump it diff --git a/test/switch.ls b/test/switch.ls index 2b284d0ca..5bbfd60a1 100644 --- a/test/switch.ls +++ b/test/switch.ls @@ -285,3 +285,33 @@ o = | (== 42) => true ok o.run!foo + +# [gkz/LiveScript#931](https://github.com/gkz/LiveScript/issues/931) +# Let's not produce unreachable `break` statements +test-cases = + ''' + foo = switch bar + | \\a => + if baz then 1 else 2 + | \\b => 3 + ''' + ''' + foo = switch bar + | \\a => + switch baz + | \\1 => 1 + | otherwise => 2 + | \\b => 3 + ''' + ''' + switch foo + | \\a => + if bar + return 1 + else throw new Error + | \\b => baz! + ''' + +for test-cases + compiled = LiveScript.compile .., {+bare,-header} + eq -1 compiled.index-of(\break), "no break in:\n#compiled"