1
0
Fork 0
mirror of https://github.com/jashkenas/coffeescript.git synced 2022-11-09 12:23:24 -05:00

Add Implicit Async Functions (#3757)

* changed jison acceptable versions

* added await support

* wrong function bug fix

* added tests for async/await

* invalid to have await, yield(from) in same function

* changed error handling and tests

* bug fix

* made error handling test more rigorous

* consolidated harmony test files

* added async constructor support and tests

* removed .orig files

* Fixed browser testing issue

* Minor cleanup

* Async test-suite and Cake support, simplified/removed funky tests

* Skip async/await tests when not supported in runtime

* cleanup

* Replaced polyfill with native JS async/await

* Oops

* Make 'async' reserved word

* Remove all async polyfills

* fix merge conflict

* make async testing opt-in

* restore test, remove confusing polyfill language

* Revert changes to test runners

* Only run async tests where async/await is supported (Node 7+ with --harmony, for now)

* remove 'async' from JS reserved words

* The async tests should use their own special async-capable version of `global.test`, which is only loaded for the async tests and only loaded by async-capable environments

* Reverting rename of `async`, it’s not a reserved word so there’s no longer a need for this change

* async test refactoring and additions

* oops

* sync

* better error reporting for `await`

* more stuff geoffrey wants

* fixed litcoffee tests

* change test title
This commit is contained in:
geebo 2016-11-02 08:51:26 -07:00 committed by Geoffrey Booth
parent a1bcf7f1d9
commit 496fd5d3d3
13 changed files with 541 additions and 213 deletions

View file

@ -228,10 +228,14 @@ task 'bench', 'quick benchmark of compilation time', ->
# Run the CoffeeScript test suite.
runTests = (CoffeeScript) ->
CoffeeScript.register()
startTime = Date.now()
currentFile = null
passedTests = 0
failures = []
startTime = Date.now()
# These are attached to `global` so that theyre accessible from within
# `test/async.coffee`, which has an async-capable version of
# `global.test`.
global.currentFile = null
global.passedTests = 0
global.failures = []
global[name] = func for name, func of require 'assert'
@ -288,10 +292,10 @@ runTests = (CoffeeScript) ->
# Run every test in the `test` folder, recording failures.
files = fs.readdirSync 'test'
# Ignore generators test file if generators are not available
generatorsAreAvailable = '--harmony' in process.execArgv or
'--harmony-generators' in process.execArgv
files.splice files.indexOf('generators.coffee'), 1 if not generatorsAreAvailable
# Ignore async test file if async/await is not available
asyncSupported = parseInt(process.versions.node.split('.')[0]) >= 7 and
('--harmony' in process.execArgv or '--harmony-async-await' in process.execArgv)
files.splice files.indexOf('async.coffee'), 1 unless asyncSupported
for file in files when helpers.isCoffee file
literate = helpers.isLiterate file

File diff suppressed because one or more lines are too long

View file

@ -41,7 +41,8 @@
return $1.push($3);
}), o('Body TERMINATOR')
],
Line: [o('Expression'), o('Statement'), o('YieldReturn')],
Line: [o('Expression'), o('Statement'), o('FuncDirective')],
FuncDirective: [o('YieldReturn'), o('AwaitReturn')],
Statement: [
o('Return'), o('Comment'), o('STATEMENT', function() {
return new StatementLiteral($1);
@ -154,6 +155,13 @@
return new YieldReturn;
})
],
AwaitReturn: [
o('AWAIT RETURN Expression', function() {
return new AwaitReturn($3);
}), o('AWAIT RETURN', function() {
return new AwaitReturn;
})
],
Comment: [
o('HERECOMMENT', function() {
return new Comment($1);
@ -702,6 +710,8 @@
return new Op('+', $2);
}), {
prec: 'UNARY_MATH'
}), o('AWAIT Expression', function() {
return new Op($1, $2);
}), o('-- SimpleAssignable', function() {
return new Op('--', $2);
}), o('++ SimpleAssignable', function() {
@ -754,7 +764,7 @@
]
};
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
operators = [['left', '.', '?.', '::', '?::'], ['left', 'CALL_START', 'CALL_END'], ['nonassoc', '++', '--'], ['left', '?'], ['right', 'UNARY'], ['right', 'AWAIT'], ['right', '**'], ['right', 'UNARY_MATH'], ['left', 'MATH'], ['left', '+', '-'], ['left', 'SHIFT'], ['left', 'RELATION'], ['left', 'COMPARE'], ['left', '&'], ['left', '^'], ['left', '|'], ['left', '&&'], ['left', '||'], ['left', 'BIN?'], ['nonassoc', 'INDENT', 'OUTDENT'], ['right', 'YIELD'], ['right', '=', ':', 'COMPOUND_ASSIGN', 'RETURN', 'THROW', 'EXTENDS'], ['right', 'FORIN', 'FOROF', 'BY', 'WHEN'], ['right', 'IF', 'ELSE', 'FOR', 'WHILE', 'UNTIL', 'LOOP', 'SUPER', 'CLASS', 'IMPORT', 'EXPORT'], ['left', 'POST_IF']];
tokens = [];

View file

@ -947,7 +947,7 @@
exports.isUnassignable = isUnassignable;
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default'];
JS_KEYWORDS = ['true', 'false', 'null', 'this', 'new', 'delete', 'typeof', 'in', 'instanceof', 'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'await', 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally', 'class', 'extends', 'super', 'import', 'export', 'default'];
COFFEE_KEYWORDS = ['undefined', 'Infinity', 'NaN', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when'];

View file

@ -1,6 +1,6 @@
// Generated by CoffeeScript 2.0.0-alpha
(function() {
var Access, Arr, Assign, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, ref1, ref2, some, starts, throwSyntaxError, unfoldSoak, utility,
var Access, Arr, Assign, AwaitReturn, Base, Block, BooleanLiteral, Call, Class, Code, CodeFragment, Comment, Existence, Expansion, ExportAllDeclaration, ExportDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExportSpecifierList, Extends, For, IdentifierLiteral, If, ImportClause, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, ImportSpecifierList, In, Index, InfinityLiteral, JS_FORBIDDEN, LEVEL_ACCESS, LEVEL_COND, LEVEL_LIST, LEVEL_OP, LEVEL_PAREN, LEVEL_TOP, Literal, ModuleDeclaration, ModuleSpecifier, ModuleSpecifierList, NEGATE, NO, NaNLiteral, NullLiteral, NumberLiteral, Obj, Op, Param, Parens, PassthroughLiteral, PropertyName, Range, RegexLiteral, RegexWithInterpolations, Return, SIMPLENUM, Scope, Slice, Splat, StatementLiteral, StringLiteral, StringWithInterpolations, SuperCall, Switch, TAB, THIS, ThisLiteral, Throw, Try, UTILITIES, UndefinedLiteral, Value, While, YES, YieldReturn, addLocationDataFn, compact, del, ends, extend, flatten, fragmentsToText, isComplexOrAssignable, isLiteralArguments, isLiteralThis, isUnassignable, locationDataToString, merge, multident, 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; },
@ -87,7 +87,7 @@
};
Base.prototype.compileClosure = function(o) {
var args, argumentsNode, func, jumpNode, meth, parts, ref3;
var args, argumentsNode, func, jumpNode, meth, parts, ref3, ref4;
if (jumpNode = this.jumps()) {
jumpNode.error('cannot use a pure statement in an expression');
}
@ -105,9 +105,14 @@
func = new Value(func, [new Access(new PropertyName(meth))]);
}
parts = (new Call(func, args)).compileNode(o);
if (func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)) {
parts.unshift(this.makeCode("(yield* "));
parts.push(this.makeCode(")"));
switch (false) {
case !(func.isGenerator || ((ref3 = func.base) != null ? ref3.isGenerator : void 0)):
parts.unshift(this.makeCode("(yield* "));
parts.push(this.makeCode(")"));
break;
case !(func.isAsync || ((ref4 = func.base) != null ? ref4.isAsync : void 0)):
parts.unshift(this.makeCode("(await "));
parts.push(this.makeCode(")"));
}
return parts;
};
@ -779,6 +784,24 @@
})(Return);
exports.AwaitReturn = AwaitReturn = (function(superClass1) {
extend1(AwaitReturn, superClass1);
function AwaitReturn() {
return AwaitReturn.__super__.constructor.apply(this, arguments);
}
AwaitReturn.prototype.compileNode = function(o) {
if (o.scope.parent == null) {
this.error('await can only occur inside functions');
}
return AwaitReturn.__super__.compileNode.apply(this, arguments);
};
return AwaitReturn;
})(Return);
exports.Value = Value = (function(superClass1) {
extend1(Value, superClass1);
@ -2368,8 +2391,18 @@
this.params = params || [];
this.body = body || new Block;
this.bound = tag === 'boundfunc';
this.isGenerator = !!this.body.contains(function(node) {
return (node instanceof Op && node.isYield()) || node instanceof YieldReturn;
this.isGenerator = false;
this.isAsync = false;
this.body.traverseChildren(false, (node) => {
if ((node instanceof Op && node.isYield()) || node instanceof YieldReturn) {
this.isGenerator = true;
}
if ((node instanceof Op && node.isAwait()) || node instanceof AwaitReturn) {
this.isAsync = true;
}
if (this.isGenerator && this.isAsync) {
return node.error("function can't contain both yield and await");
}
});
}
@ -2482,6 +2515,9 @@
this.body.makeReturn();
}
code = '';
if (this.isAsync) {
code += 'async ';
}
if (!this.bound) {
code += 'function';
if (this.isGenerator) {
@ -2849,6 +2885,10 @@
return this.isUnary() && ((ref3 = this.operator) === '+' || ref3 === '-') && this.first instanceof Value && this.first.isNumber();
};
Op.prototype.isAwait = function() {
return this.operator === 'await';
};
Op.prototype.isYield = function() {
var ref3;
return (ref3 = this.operator) === 'yield' || ref3 === 'yield*';
@ -2940,8 +2980,8 @@
this.first.error(message);
}
}
if (this.isYield()) {
return this.compileYield(o);
if (this.isYield() || this.isAwait()) {
return this.compileContinuation(o);
}
if (this.isUnary()) {
return this.compileUnary(o);
@ -3018,12 +3058,12 @@
return this.joinFragmentArrays(parts, '');
};
Op.prototype.compileYield = function(o) {
Op.prototype.compileContinuation = function(o) {
var op, parts, ref3, ref4;
parts = [];
op = this.operator;
if (o.scope.parent == null) {
this.error('yield can only occur inside functions');
this.error(this.operator + " can only occur inside functions");
}
if (((ref3 = o.scope.method) != null ? ref3.bound : void 0) && o.scope.method.isGenerator) {
this.error('yield cannot occur inside bound (fat arrow) functions');

File diff suppressed because one or more lines are too long

View file

@ -501,7 +501,7 @@
IMPLICIT_FUNC = ['IDENTIFIER', 'PROPERTY', 'SUPER', ')', 'CALL_END', ']', 'INDEX_END', '@', 'THIS'];
IMPLICIT_CALL = ['IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_CALL = ['IDENTIFIER', 'PROPERTY', 'NUMBER', 'INFINITY', 'NAN', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS', 'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS', 'UNDEFINED', 'NULL', 'BOOL', 'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW', '@', '->', '=>', '[', '(', '{', '--', '++'];
IMPLICIT_UNSPACED_CALL = ['+', '-'];

View file

@ -89,7 +89,12 @@ grammar =
Line: [
o 'Expression'
o 'Statement'
o 'FuncDirective'
]
FuncDirective: [
o 'YieldReturn'
o 'AwaitReturn'
]
# Pure statements which cannot be expressions.
@ -219,6 +224,12 @@ grammar =
o 'YIELD RETURN', -> new YieldReturn
]
AwaitReturn: [
o 'AWAIT RETURN Expression', -> new AwaitReturn $3
o 'AWAIT RETURN', -> new AwaitReturn
]
# A block comment.
Comment: [
o 'HERECOMMENT', -> new Comment $1
@ -644,6 +655,8 @@ grammar =
o '- Expression', (-> new Op '-', $2), prec: 'UNARY_MATH'
o '+ Expression', (-> new Op '+', $2), prec: 'UNARY_MATH'
o 'AWAIT Expression', -> new Op $1 , $2
o '-- SimpleAssignable', -> new Op '--', $2
o '++ SimpleAssignable', -> new Op '++', $2
o 'SimpleAssignable --', -> new Op '--', $1, null, true
@ -698,6 +711,7 @@ operators = [
['nonassoc', '++', '--']
['left', '?']
['right', 'UNARY']
['right', 'AWAIT']
['right', '**']
['right', 'UNARY_MATH']
['left', 'MATH']

View file

@ -834,7 +834,7 @@ exports.isUnassignable = isUnassignable
JS_KEYWORDS = [
'true', 'false', 'null', 'this'
'new', 'delete', 'typeof', 'in', 'instanceof'
'return', 'throw', 'break', 'continue', 'debugger', 'yield'
'return', 'throw', 'break', 'continue', 'debugger', 'yield', 'await'
'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
'class', 'extends', 'super'
'import', 'export', 'default'

View file

@ -90,9 +90,14 @@ exports.Base = class Base
meth = 'call'
func = new Value func, [new Access new PropertyName meth]
parts = (new Call func, args).compileNode o
if func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
parts.push @makeCode ")"
switch
when func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
parts.push @makeCode ")"
when func.isAsync or func.base?.isAsync
parts.unshift @makeCode "(await "
parts.push @makeCode ")"
parts
# If the code generation wishes to use the result of a complex expression
@ -491,6 +496,14 @@ exports.YieldReturn = class YieldReturn extends Return
@error 'yield can only occur inside functions'
super
exports.AwaitReturn = class AwaitReturn extends Return
compileNode: (o) ->
unless o.scope.parent?
@error 'await can only occur inside functions'
super
#### Value
# A value, variable or literal or parenthesized, indexed or dotted into,
@ -1589,8 +1602,16 @@ exports.Code = class Code extends Base
@params = params or []
@body = body or new Block
@bound = tag is 'boundfunc'
@isGenerator = !!@body.contains (node) ->
(node instanceof Op and node.isYield()) or node instanceof YieldReturn
@isGenerator = no
@isAsync = no
@body.traverseChildren no, (node) =>
if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
@isGenerator = yes
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
@isAsync = yes
if @isGenerator and @isAsync
node.error "function can't contain both yield and await"
children: ['params', 'body']
@ -1710,6 +1731,7 @@ exports.Code = class Code extends Base
# Assemble the output
code = ''
code += 'async ' if @isAsync
unless @bound
code += 'function'
code += '*' if @isGenerator # Arrow functions cant be generators
@ -1964,6 +1986,9 @@ exports.Op = class Op extends Base
@isUnary() and @operator in ['+', '-'] and
@first instanceof Value and @first.isNumber()
isAwait: ->
@operator is 'await'
isYield: ->
@operator in ['yield', 'yield*']
@ -2034,9 +2059,9 @@ exports.Op = class Op extends Base
if @operator in ['--', '++']
message = isUnassignable @first.unwrapAll().value
@first.error message if message
return @compileYield o if @isYield()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
return @compileContinuation o if @isYield() or @isAwait()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
switch @operator
when '?' then @compileExistence o
when '**' then @compilePower o
@ -2089,11 +2114,11 @@ exports.Op = class Op extends Base
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compileYield: (o) ->
compileContinuation: (o) ->
parts = []
op = @operator
unless o.scope.parent?
@error 'yield can only occur inside functions'
@error "#{@operator} can only occur inside functions"
if o.scope.method?.bound and o.scope.method.isGenerator
@error 'yield cannot occur inside bound (fat arrow) functions'
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)

View file

@ -503,7 +503,7 @@ IMPLICIT_CALL = [
'STRING', 'STRING_START', 'REGEX', 'REGEX_START', 'JS'
'NEW', 'PARAM_START', 'CLASS', 'IF', 'TRY', 'SWITCH', 'THIS'
'UNDEFINED', 'NULL', 'BOOL'
'UNARY', 'YIELD', 'UNARY_MATH', 'SUPER', 'THROW'
'UNARY', 'YIELD', 'AWAIT', 'UNARY_MATH', 'SUPER', 'THROW'
'@', '->', '=>', '[', '(', '{', '--', '++'
]

193
test/async.coffee Normal file
View file

@ -0,0 +1,193 @@
# Functions that contain the `await` keyword will compile into async
# functions, which currently only Node 7+ in harmony mode can even
# evaluate, much less run. Therefore we need to prevent runtimes
# which will choke on such code from even loading it. This file is
# only loaded by async-capable environments, so we redefine `test`
# here even though it is based on `test` defined in `Cakefile`.
# It replaces `test` for this file, and adds to the tracked
# `passedTests` and `failures` arrays which are global objects.
test = (description, fn) ->
try
fn.test = {description, currentFile}
await fn.call(fn)
++passedTests
catch e
failures.push
filename: currentFile
error: e
description: description if description?
source: fn.toString() if fn.toString?
# always fulfills
winning = (val) -> Promise.resolve val
# always is rejected
failing = (val) -> Promise.reject new Error val
test "async as argument", ->
ok ->
await winning()
test "explicit async", ->
a = do ->
await return 5
eq a.constructor, Promise
a.then (val) ->
eq val, 5
test "implicit async", ->
a = do ->
x = await winning(5)
y = await winning(4)
z = await winning(3)
[x, y, z]
eq a.constructor, Promise
test "async return value (implicit)", ->
out = null
a = ->
x = await winning(5)
y = await winning(4)
z = await winning(3)
[x, y, z]
b = do ->
out = await a()
b.then ->
arrayEq out, [5, 4, 3]
test "async return value (explicit)", ->
out = null
a = ->
await return [5, 2, 3]
b = do ->
out = await a()
b.then ->
arrayEq out, [5, 2, 3]
test "async parameters", ->
[out1, out2] = [null, null]
a = (a, [b, c])->
arr = [a]
arr.push b
arr.push c
await return arr
b = (a, b, c = 5)->
arr = [a]
arr.push b
arr.push c
await return arr
c = do ->
out1 = await a(5, [4, 3])
out2 = await b(4, 4)
c.then ->
arrayEq out1, [5, 4, 3]
arrayEq out2, [4, 4, 5]
test "async `this` scoping", ->
bnd = null
ubnd = null
nst = null
obj =
bound: ->
return do =>
await return this
unbound: ->
return do ->
await return this
nested: ->
return do =>
await do =>
await do =>
await return this
promise = do ->
bnd = await obj.bound()
ubnd = await obj.unbound()
nst = await obj.nested()
promise.then ->
eq bnd, obj
ok ubnd isnt obj
eq nst, obj
test "await precedence", ->
out = null
fn = (win, fail) ->
win(3)
promise = do ->
# assert precedence between unary (new) and power (**) operators
out = 1 + await new Promise(fn) ** 2
promise.then ->
eq out, 10
test "`await` inside IIFEs", ->
[x, y, z] = new Array(3)
a = do ->
x = switch (4) # switch 4
when 2
await winning(1)
when 4
await winning(5)
when 7
await winning(2)
y = try
text = "this should be caught"
throw new Error(text)
await winning(1)
catch e
await winning(4)
z = for i in [0..5]
a = i * i
await winning(a)
a.then ->
eq x, 5
eq y, 4
arrayEq z, [0, 1, 4, 9, 16, 25]
test "error handling", ->
res = null
val = 0
a = ->
try
await failing("fail")
catch e
val = 7 # to assure the catch block runs
return e
b = do ->
res = await a()
b.then ->
eq val, 7
ok res.message?
eq res.message, "fail"
test "await expression evaluates to argument if not A+", ->
eq(await 4, 4)
test "implicit call with `await`", ->
addOne = (arg) -> arg + 1
a = addOne await 3
eq a, 4

View file

@ -1169,3 +1169,34 @@ test "CoffeeScript keywords cannot be used as local names in import list aliases
import { bar as unless, baz as bar } from 'lib'
^^^^^^
'''
test "function cannot contain both `await` and `yield`", ->
assertErrorFormat '''
f = () ->
yield 5
await a
''', '''
[stdin]:3:3: error: function can't contain both yield and await
await a
^^^^^^^
'''
test "function cannot contain both `await` and `yield from`", ->
assertErrorFormat '''
f = () ->
yield from a
await b
''', '''
[stdin]:3:3: error: function can't contain both yield and await
await b
^^^^^^^
'''
test "Cannnot have `await` outside a function", ->
assertErrorFormat '''
await 1
''', '''
[stdin]:1:1: error: await can only occur inside functions
await 1
^^^^^^^
'''