Only allow `yield return` as a statement

Fixes #4097. Also happens to fix #4096. I also took the liberty to simplify the
error message for invalid use of `yield`.
This commit is contained in:
Simon Lydell 2015-09-16 07:51:17 +02:00
parent d6ff91a454
commit 4ceb6a6818
7 changed files with 222 additions and 176 deletions

View File

@ -41,7 +41,7 @@
return $1.push($3); return $1.push($3);
}), o('Body TERMINATOR') }), o('Body TERMINATOR')
], ],
Line: [o('Expression'), o('Statement')], Line: [o('Expression'), o('Statement'), o('YieldReturn')],
Statement: [ Statement: [
o('Return'), o('Comment'), o('STATEMENT', function() { o('Return'), o('Comment'), o('STATEMENT', function() {
return new Literal($1); return new Literal($1);
@ -51,8 +51,6 @@
Yield: [ Yield: [
o('YIELD', function() { o('YIELD', function() {
return new Op($1, new Value(new Literal(''))); return new Op($1, new Value(new Literal('')));
}), o('YIELD Statement', function() {
return new Op($1, $2);
}), o('YIELD Expression', function() { }), o('YIELD Expression', function() {
return new Op($1, $2); return new Op($1, $2);
}), o('YIELD FROM Expression', function() { }), o('YIELD FROM Expression', function() {
@ -142,6 +140,13 @@
return new Return; return new Return;
}) })
], ],
YieldReturn: [
o('YIELD RETURN Expression', function() {
return new YieldReturn($3);
}), o('YIELD RETURN', function() {
return new YieldReturn;
})
],
Comment: [ Comment: [
o('HERECOMMENT', function() { o('HERECOMMENT', function() {
return new Comment($1); return new Comment($1);

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.10.0 // Generated by CoffeeScript 1.10.0
(function() { (function() {
var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, locationDataToString, merge, multident, parseNum, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility, var Access, Arr, Assign, Base, Block, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, Extends, For, HEXNUM, IDENTIFIER, IS_REGEX, IS_STRING, If, In, Index, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, NEGATE, NO, NUMBER, Obj, Op, Param, Parens, RESERVED, Range, Return, SIMPLENUM, STRICT_PROSCRIBED, Scope, Slice, Splat, Switch, TAB, THIS, Throw, Try, UTILITIES, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, locationDataToString, merge, multident, parseNum, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, extend1 = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty, hasProp = {}.hasOwnProperty,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
@ -652,18 +652,13 @@
}; };
Return.prototype.compileNode = function(o) { Return.prototype.compileNode = function(o) {
var answer, exprIsYieldReturn, ref3; var answer;
answer = []; answer = [];
exprIsYieldReturn = (ref3 = this.expression) != null ? typeof ref3.isYieldReturn === "function" ? ref3.isYieldReturn() : void 0 : void 0; answer.push(this.makeCode(this.tab + ("return" + (this.expression ? " " : ""))));
if (!exprIsYieldReturn) {
answer.push(this.makeCode(this.tab + ("return" + (this.expression ? " " : ""))));
}
if (this.expression) { if (this.expression) {
answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN)); answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN));
} }
if (!exprIsYieldReturn) { answer.push(this.makeCode(";"));
answer.push(this.makeCode(";"));
}
return answer; return answer;
}; };
@ -671,6 +666,24 @@
})(Base); })(Base);
exports.YieldReturn = YieldReturn = (function(superClass1) {
extend1(YieldReturn, superClass1);
function YieldReturn() {
return YieldReturn.__super__.constructor.apply(this, arguments);
}
YieldReturn.prototype.compileNode = function(o) {
if (o.scope.parent == null) {
this.error('yield can only occur inside functions');
}
return YieldReturn.__super__.compileNode.apply(this, arguments);
};
return YieldReturn;
})(Return);
exports.Value = Value = (function(superClass1) { exports.Value = Value = (function(superClass1) {
extend1(Value, superClass1); extend1(Value, superClass1);
@ -1921,8 +1934,7 @@
this.body = body || new Block; this.body = body || new Block;
this.bound = tag === 'boundfunc'; this.bound = tag === 'boundfunc';
this.isGenerator = !!this.body.contains(function(node) { this.isGenerator = !!this.body.contains(function(node) {
var ref3; return (node instanceof Op && node.isYield()) || node instanceof YieldReturn;
return node instanceof Op && ((ref3 = node.operator) === 'yield' || ref3 === 'yield*');
}); });
} }
@ -2400,10 +2412,6 @@
return (ref3 = this.operator) === 'yield' || ref3 === 'yield*'; return (ref3 = this.operator) === 'yield' || ref3 === 'yield*';
}; };
Op.prototype.isYieldReturn = function() {
return this.isYield() && this.first instanceof Return;
};
Op.prototype.isUnary = function() { Op.prototype.isUnary = function() {
return !this.second; return !this.second;
}; };
@ -2571,19 +2579,17 @@
parts = []; parts = [];
op = this.operator; op = this.operator;
if (o.scope.parent == null) { if (o.scope.parent == null) {
this.error('yield statements must occur within a function generator.'); this.error('yield can only occur inside functions');
} }
if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) { if (indexOf.call(Object.keys(this.first), 'expression') >= 0 && !(this.first instanceof Throw)) {
if (this.isYieldReturn()) { if (this.first.expression != null) {
parts.push(this.first.compileToFragments(o, LEVEL_TOP));
} else if (this.first.expression != null) {
parts.push(this.first.expression.compileToFragments(o, LEVEL_OP)); parts.push(this.first.expression.compileToFragments(o, LEVEL_OP));
} }
} else { } else {
if (o.level >= LEVEL_PAREN) { if (o.level >= LEVEL_PAREN) {
parts.push([this.makeCode("(")]); parts.push([this.makeCode("(")]);
} }
parts.push([this.makeCode("" + op)]); parts.push([this.makeCode(op)]);
if (((ref3 = this.first.base) != null ? ref3.value : void 0) !== '') { if (((ref3 = this.first.base) != null ? ref3.value : void 0) !== '') {
parts.push([this.makeCode(" ")]); parts.push([this.makeCode(" ")]);
} }

File diff suppressed because one or more lines are too long

View File

@ -83,10 +83,13 @@ grammar =
o 'Body TERMINATOR' o 'Body TERMINATOR'
] ]
# Block and statements, which make up a line in a body. # Block and statements, which make up a line in a body. YieldReturn is a
# statement, but not included in Statement because that results in an ambigous
# grammar.
Line: [ Line: [
o 'Expression' o 'Expression'
o 'Statement' o 'Statement'
o 'YieldReturn'
] ]
# Pure statements which cannot be expressions. # Pure statements which cannot be expressions.
@ -118,7 +121,6 @@ grammar =
Yield: [ Yield: [
o 'YIELD', -> new Op $1, new Value new Literal '' o 'YIELD', -> new Op $1, new Value new Literal ''
o 'YIELD Statement', -> new Op $1, $2
o 'YIELD Expression', -> new Op $1, $2 o 'YIELD Expression', -> new Op $1, $2
o 'YIELD FROM Expression', -> new Op $1.concat($2), $3 o 'YIELD FROM Expression', -> new Op $1.concat($2), $3
] ]
@ -205,6 +207,11 @@ grammar =
o 'RETURN', -> new Return o 'RETURN', -> new Return
] ]
YieldReturn: [
o 'YIELD RETURN Expression', -> new YieldReturn $3
o 'YIELD RETURN', -> new YieldReturn
]
# A block comment. # A block comment.
Comment: [ Comment: [
o 'HERECOMMENT', -> new Comment $1 o 'HERECOMMENT', -> new Comment $1

View File

@ -453,15 +453,21 @@ exports.Return = class Return extends Base
compileNode: (o) -> compileNode: (o) ->
answer = [] answer = []
exprIsYieldReturn = @expression?.isYieldReturn?()
# TODO: If we call expression.compile() here twice, we'll sometimes get back different results! # TODO: If we call expression.compile() here twice, we'll sometimes get back different results!
unless exprIsYieldReturn answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
if @expression if @expression
answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
answer.push @makeCode ";" unless exprIsYieldReturn answer.push @makeCode ";"
return answer return answer
# `yield return` works exactly like `return`, except that it turns the function
# into a generator.
exports.YieldReturn = class YieldReturn extends Return
compileNode: (o) ->
unless o.scope.parent?
@error 'yield can only occur inside functions'
super
#### Value #### Value
# A value, variable or literal or parenthesized, indexed or dotted into, # A value, variable or literal or parenthesized, indexed or dotted into,
@ -1384,7 +1390,7 @@ exports.Code = class Code extends Base
@body = body or new Block @body = body or new Block
@bound = tag is 'boundfunc' @bound = tag is 'boundfunc'
@isGenerator = !!@body.contains (node) -> @isGenerator = !!@body.contains (node) ->
node instanceof Op and node.operator in ['yield', 'yield*'] (node instanceof Op and node.isYield()) or node instanceof YieldReturn
children: ['params', 'body'] children: ['params', 'body']
@ -1701,9 +1707,6 @@ exports.Op = class Op extends Base
isYield: -> isYield: ->
@operator in ['yield', 'yield*'] @operator in ['yield', 'yield*']
isYieldReturn: ->
@isYield() and @first instanceof Return
isUnary: -> isUnary: ->
not @second not @second
@ -1829,13 +1832,10 @@ exports.Op = class Op extends Base
compileYield: (o) -> compileYield: (o) ->
parts = [] parts = []
op = @operator op = @operator
if not o.scope.parent? unless o.scope.parent?
@error 'yield statements must occur within a function generator.' @error 'yield can only occur inside functions'
if 'expression' in Object.keys(@first) and not (@first instanceof Throw) if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
if @isYieldReturn() parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
parts.push @first.compileToFragments o, LEVEL_TOP
else if @first.expression?
parts.push @first.expression.compileToFragments o, LEVEL_OP
else else
parts.push [@makeCode "("] if o.level >= LEVEL_PAREN parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
parts.push [@makeCode op] parts.push [@makeCode op]

View File

@ -824,3 +824,28 @@ test "#3926: implicit object in parameter list", ->
(one, two, {three, four: five}, key: value) -> (one, two, {three, four: five}, key: value) ->
^ ^
''' '''
test "`yield` outside of a function", ->
assertErrorFormat '''
yield 1
''', '''
[stdin]:1:1: error: yield can only occur inside functions
yield 1
^^^^^^^
'''
assertErrorFormat '''
yield return
''', '''
[stdin]:1:1: error: yield can only occur inside functions
yield return
^^^^^^^^^^^^
'''
test "#4097: `yield return` as an expression", ->
assertErrorFormat '''
-> (yield return)
''', '''
[stdin]:1:5: error: cannot use a pure statement in an expression
-> (yield return)
^^^^^^^^^^^^
'''

View File

@ -70,9 +70,6 @@ test "bound generator", ->
ok obj isnt obj.unbound().next().value ok obj isnt obj.unbound().next().value
eq obj, obj.nested().next().value.next().value.next().value eq obj, obj.nested().next().value.next().value.next().value
test "error if `yield` occurs outside of a function", ->
throws -> CoffeeScript.compile 'yield 1'
test "`yield from` support", -> test "`yield from` support", ->
x = do -> x = do ->
yield from do -> yield from do ->