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:
parent
d6ff91a454
commit
4ceb6a6818
|
@ -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);
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
'''
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
Loading…
Reference in New Issue