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

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript 1.10.0
(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; },
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; },
@ -652,18 +652,13 @@
};
Return.prototype.compileNode = function(o) {
var answer, exprIsYieldReturn, ref3;
var answer;
answer = [];
exprIsYieldReturn = (ref3 = this.expression) != null ? typeof ref3.isYieldReturn === "function" ? ref3.isYieldReturn() : void 0 : void 0;
if (!exprIsYieldReturn) {
answer.push(this.makeCode(this.tab + ("return" + (this.expression ? " " : ""))));
}
answer.push(this.makeCode(this.tab + ("return" + (this.expression ? " " : ""))));
if (this.expression) {
answer = answer.concat(this.expression.compileToFragments(o, LEVEL_PAREN));
}
if (!exprIsYieldReturn) {
answer.push(this.makeCode(";"));
}
answer.push(this.makeCode(";"));
return answer;
};
@ -671,6 +666,24 @@
})(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) {
extend1(Value, superClass1);
@ -1921,8 +1934,7 @@
this.body = body || new Block;
this.bound = tag === 'boundfunc';
this.isGenerator = !!this.body.contains(function(node) {
var ref3;
return node instanceof Op && ((ref3 = node.operator) === 'yield' || ref3 === 'yield*');
return (node instanceof Op && node.isYield()) || node instanceof YieldReturn;
});
}
@ -2400,10 +2412,6 @@
return (ref3 = this.operator) === 'yield' || ref3 === 'yield*';
};
Op.prototype.isYieldReturn = function() {
return this.isYield() && this.first instanceof Return;
};
Op.prototype.isUnary = function() {
return !this.second;
};
@ -2571,19 +2579,17 @@
parts = [];
op = this.operator;
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 (this.isYieldReturn()) {
parts.push(this.first.compileToFragments(o, LEVEL_TOP));
} else if (this.first.expression != null) {
if (this.first.expression != null) {
parts.push(this.first.expression.compileToFragments(o, LEVEL_OP));
}
} else {
if (o.level >= LEVEL_PAREN) {
parts.push([this.makeCode("(")]);
}
parts.push([this.makeCode("" + op)]);
parts.push([this.makeCode(op)]);
if (((ref3 = this.first.base) != null ? ref3.value : void 0) !== '') {
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'
]
# 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: [
o 'Expression'
o 'Statement'
o 'YieldReturn'
]
# Pure statements which cannot be expressions.
@ -118,7 +121,6 @@ grammar =
Yield: [
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 FROM Expression', -> new Op $1.concat($2), $3
]
@ -205,6 +207,11 @@ grammar =
o 'RETURN', -> new Return
]
YieldReturn: [
o 'YIELD RETURN Expression', -> new YieldReturn $3
o 'YIELD RETURN', -> new YieldReturn
]
# A block comment.
Comment: [
o 'HERECOMMENT', -> new Comment $1

View File

@ -453,15 +453,21 @@ exports.Return = class Return extends Base
compileNode: (o) ->
answer = []
exprIsYieldReturn = @expression?.isYieldReturn?()
# 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
answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
answer.push @makeCode ";" unless exprIsYieldReturn
answer.push @makeCode ";"
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
# 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
@bound = tag is 'boundfunc'
@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']
@ -1701,9 +1707,6 @@ exports.Op = class Op extends Base
isYield: ->
@operator in ['yield', 'yield*']
isYieldReturn: ->
@isYield() and @first instanceof Return
isUnary: ->
not @second
@ -1829,13 +1832,10 @@ exports.Op = class Op extends Base
compileYield: (o) ->
parts = []
op = @operator
if not o.scope.parent?
@error 'yield statements must occur within a function generator.'
unless o.scope.parent?
@error 'yield can only occur inside functions'
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
if @isYieldReturn()
parts.push @first.compileToFragments o, LEVEL_TOP
else if @first.expression?
parts.push @first.expression.compileToFragments o, LEVEL_OP
parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
else
parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
parts.push [@makeCode op]

View File

@ -824,3 +824,28 @@ test "#3926: implicit object in parameter list", ->
(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
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", ->
x = do ->
yield from do ->