Skip to content

Commit 1717dbc

Browse files
committed
[1.7>master] [MERGE #4105 @boingoing] Support for defer parsing lambda functions
Merge pull request #4105 from boingoing:DeferParseLambda_3 Fix up the parser to enable the defer parse methods to handle lambda functions. All lambdas are allowed to be deferred, even lambdas with compact parameter lists or function bodies. Should also allow lambda functions to be redeferred, which is really the main reason to do this work. One of the related changes is in `Parser::Parse`. I changed this function to skip calling `ParseStmtList` in the case where we are defer parsing a single function. In these cases we know that we are going to parse a function so we don't need to start parsing a set of statements, then one statement, then one expression, then one terminal, etc until we finally start parsing the function via `ParseFncDecl`. Instead we can just jump directly to `ParseFncDecl` after setting up the correct flags. By doing this, we can avoid reparsing the lambda parameter list (because we never assume it will be an ordinary expression list) which saves us the headache of bookkeeping the block and function ids. One other change of note is in `ScopeInfo::SaveSymbolInfo`. We now need to save symbol info for the `arguments` symbol since a lambda may be deferred in a function and lambdas capture the `arguments` value from their parent. I suppose we could move the `arguments` default binding into a regular special symbol at some point in the future.
2 parents 3825ee2 + 0bc7709 commit 1717dbc

File tree

12 files changed

+366
-109
lines changed

12 files changed

+366
-109
lines changed

lib/Parser/Parse.cpp

Lines changed: 213 additions & 92 deletions
Large diffs are not rendered by default.

lib/Parser/Parse.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,8 +811,8 @@ class Parser
811811
template<bool buildAST> void ParseExpressionLambdaBody(ParseNodePtr pnodeFnc);
812812
template<bool buildAST> void UpdateCurrentNodeFunc(ParseNodePtr pnodeFnc, bool fLambda);
813813
bool FncDeclAllowedWithoutContext(ushort flags);
814-
void FinishFncDecl(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, ParseNodePtr *lastNodeRef, bool skipCurlyBraces = false);
815-
void ParseTopLevelDeferredFunc(ParseNodePtr pnodeFnc, ParseNodePtr pnodeFncParent, LPCOLESTR pNameHint);
814+
void FinishFncDecl(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, ParseNodePtr *lastNodeRef, bool fLambda, bool skipCurlyBraces = false);
815+
void ParseTopLevelDeferredFunc(ParseNodePtr pnodeFnc, ParseNodePtr pnodeFncParent, LPCOLESTR pNameHint, bool fLambda, bool *pNeedScanRCurly = nullptr);
816816
void ParseNestedDeferredFunc(ParseNodePtr pnodeFnc, bool fLambda, bool *pNeedScanRCurly, bool *pStrictModeTurnedOn);
817817
void CheckStrictFormalParameters();
818818
ParseNodePtr AddArgumentsNodeToVars(ParseNodePtr pnodeFnc);

lib/Parser/Scan.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Scanner<EncodingPolicy>::Scanner(Parser* parser, HashTbl *phtbl, Token *ptoken,
9999
m_tempChBufSecondary.m_pscanner = this;
100100

101101
m_iecpLimTokPrevious = (size_t)-1;
102+
m_ichLimTokPrevious = (charcount_t)-1;
102103

103104
this->charClassifier = scriptContext->GetCharClassifier();
104105

@@ -1552,6 +1553,7 @@ tokens Scanner<EncodingPolicy>::ScanCore(bool identifyKwds)
15521553
// store the last token
15531554
m_tkPrevious = m_ptoken->tk;
15541555
m_iecpLimTokPrevious = IecpLimTok(); // Introduced for use by lambda parsing to find correct span of expression lambdas
1556+
m_ichLimTokPrevious = IchLimTok();
15551557

15561558
if (p >= last)
15571559
{

lib/Parser/Scan.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,12 @@ class Scanner : public IScanner, public EncodingPolicy
531531
return m_iecpLimTokPrevious;
532532
}
533533

534+
charcount_t IchLimTokPrevious() const
535+
{
536+
AssertMsg(m_ichLimTokPrevious != (charcount_t)-1, "IchLimTokPrevious() cannot be called before scanning a token");
537+
return m_ichLimTokPrevious;
538+
}
539+
534540
IdentPtr PidAt(size_t iecpMin, size_t iecpLim);
535541

536542
// Returns the character offset within the stream of the first character on the current line.
@@ -549,6 +555,7 @@ class Scanner : public IScanner, public EncodingPolicy
549555
void SetCurrentCharacter(charcount_t offset, ULONG lineNumber = 0)
550556
{
551557
DebugOnly(m_iecpLimTokPrevious = (size_t)-1);
558+
DebugOnly(m_ichLimTokPrevious = (charcount_t)-1);
552559
size_t length = m_pchLast - m_pchBase;
553560
if (offset > length) offset = static_cast< charcount_t >(length);
554561
size_t ibOffset = this->CharacterOffsetToUnitOffset(m_pchBase, m_currentCharacter, m_pchLast, offset);
@@ -713,6 +720,7 @@ class Scanner : public IScanner, public EncodingPolicy
713720

714721
tokens m_tkPrevious;
715722
size_t m_iecpLimTokPrevious;
723+
charcount_t m_ichLimTokPrevious;
716724

717725
Scanner(Parser* parser, HashTbl *phtbl, Token *ptoken, Js::ScriptContext *scriptContext);
718726
~Scanner(void);

lib/Runtime/Base/FunctionBody.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,7 @@ namespace Js
12271227
bool IsConstructor() const;
12281228
bool IsGenerator() const;
12291229
bool IsClassConstructor() const;
1230+
bool IsBaseClassConstructor() const;
12301231
bool IsClassMethod() const;
12311232
bool IsModule() const;
12321233
bool IsWasmFunction() const;
@@ -1533,6 +1534,13 @@ namespace Js
15331534
return GetFunctionInfo()->IsClassConstructor();
15341535
}
15351536

1537+
inline bool FunctionProxy::IsBaseClassConstructor() const
1538+
{
1539+
Assert(GetFunctionInfo());
1540+
Assert(GetFunctionInfo()->GetFunctionProxy() == this);
1541+
return GetFunctionInfo()->GetBaseConstructorKind();
1542+
}
1543+
15361544
inline bool FunctionProxy::IsClassMethod() const
15371545
{
15381546
Assert(GetFunctionInfo());

lib/Runtime/ByteCode/ByteCodeGenerator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,11 @@ FuncInfo * ByteCodeGenerator::StartBindFunction(const char16 *name, uint nameLen
13081308
if (pnode->sxFnc.IsClassConstructor())
13091309
{
13101310
attributes = (Js::FunctionInfo::Attributes)(attributes | Js::FunctionInfo::Attributes::ClassConstructor);
1311+
1312+
if (pnode->sxFnc.IsBaseClassConstructor())
1313+
{
1314+
attributes = (Js::FunctionInfo::Attributes)(attributes | Js::FunctionInfo::Attributes::BaseConstructorKind);
1315+
}
13111316
}
13121317
else
13131318
{

lib/Runtime/ByteCode/ByteCodeSerializer.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ enum FunctionFlags
223223
ffIsAnonymous = 0x100000,
224224
ffUsesArgumentsObject = 0x200000,
225225
ffDoScopeObjectCreation = 0x400000,
226-
ffIsParamAndBodyScopeMerged = 0x800000
226+
ffIsParamAndBodyScopeMerged = 0x800000,
227227
};
228228

229229
// Kinds of constant
@@ -2064,10 +2064,11 @@ class ByteCodeBufferBuilder
20642064
| FunctionInfo::Attributes::CapturesThis
20652065
| FunctionInfo::Attributes::Generator
20662066
| FunctionInfo::Attributes::ClassConstructor
2067+
| FunctionInfo::Attributes::BaseConstructorKind
20672068
| FunctionInfo::Attributes::ClassMethod
20682069
| FunctionInfo::Attributes::EnclosedByGlobalFunc
20692070
| FunctionInfo::Attributes::AllowDirectSuper)) == 0,
2070-
"Only the ErrorOnNew|SuperReference|Lambda|CapturesThis|Generator|ClassConstructor|Async|ClassMember|EnclosedByGlobalFunc|AllowDirectSuper attributes should be set on a serialized function");
2071+
"Only the ErrorOnNew|SuperReference|Lambda|CapturesThis|Generator|ClassConstructor|BaseConstructorKind|Async|ClassMember|EnclosedByGlobalFunc|AllowDirectSuper attributes should be set on a serialized function");
20712072
if (attributes != FunctionInfo::Attributes::None)
20722073
{
20732074
definedFields.has_attributes = true;

lib/Runtime/ByteCode/FuncInfo.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,22 @@ BOOL FuncInfo::HasDirectSuper() const
136136

137137
BOOL FuncInfo::IsClassMember() const
138138
{
139-
return root->sxFnc.IsClassMember();
139+
return this->byteCodeFunction->IsClassMethod();
140140
}
141141

142142
BOOL FuncInfo::IsLambda() const
143143
{
144-
return root->sxFnc.IsLambda();
144+
return this->byteCodeFunction->IsLambda();
145145
}
146146

147147
BOOL FuncInfo::IsClassConstructor() const
148148
{
149-
return root->sxFnc.IsClassConstructor();
149+
return this->byteCodeFunction->IsClassConstructor();
150150
}
151151

152152
BOOL FuncInfo::IsBaseClassConstructor() const
153153
{
154-
return root->sxFnc.IsBaseClassConstructor();
154+
return this->byteCodeFunction->IsBaseClassConstructor();
155155
}
156156

157157
BOOL FuncInfo::IsDerivedClassConstructor() const

lib/Runtime/ByteCode/ScopeInfo.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ namespace Js
1111
//
1212
void ScopeInfo::SaveSymbolInfo(Symbol* sym, MapSymbolData* mapSymbolData)
1313
{
14-
// We don't need to create slot for or save "arguments"
15-
bool needScopeSlot = !sym->IsArguments() && sym->GetHasNonLocalReference();
14+
bool needScopeSlot = sym->GetHasNonLocalReference();
1615
Js::PropertyId scopeSlot = Constants::NoSlot;
1716

1817
if (sym->GetIsModuleExportStorage())

test/AsmJs/lambda.baseline

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
Lambda functions are not supported.
1+
lambda functions are not allowed
22
Asm.js compilation failed.

test/es6/DeferParseLambda.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
7+
8+
var tests = [
9+
{
10+
name: "Simple lambda function deferral",
11+
body: function () {
12+
var a = () => { return 123 };
13+
assert.areEqual(123, a(), "Lambda with no args but empty parens and body surrounded with curly-braces");
14+
15+
var b = (arg) => { return arg; };
16+
assert.areEqual(123, b(123), "Lambda with an arg in parens");
17+
18+
var c = (arg1, arg2) => { return arg1 + arg2; };
19+
assert.areEqual(2, c(1, 1), "Lambda with two args in parens");
20+
21+
var d = () => 123
22+
assert.areEqual(123, d(), "Lambda with empty arg list and single expression-body");
23+
24+
var e = arg => arg
25+
assert.areEqual(123, e(123), "Lambda with single arg and single expression-body");
26+
27+
var f = arg => { return arg }
28+
assert.areEqual(123, f(123), "Lambda with single arg and body in curly-braces");
29+
30+
var g = (arg1, arg2) => arg1 + arg2
31+
assert.areEqual(2, g(1, 1), "Lambda with two args in parens and single expression body");
32+
}
33+
},
34+
{
35+
name: "Global lambda function deferral",
36+
body: function () {
37+
WScript.LoadScript(`
38+
var a = () => { return 123 };
39+
assert.areEqual(123, a(), "Lambda with no args but empty parens and body surrounded with curly-braces");
40+
41+
var b = (arg) => { return arg; };
42+
assert.areEqual(123, b(123), "Lambda with an arg in parens");
43+
44+
var c = (arg1, arg2) => { return arg1 + arg2; };
45+
assert.areEqual(2, c(1, 1), "Lambda with two args in parens");
46+
47+
var d = () => 123
48+
assert.areEqual(123, d(), "Lambda with empty arg list and single expression-body");
49+
50+
var e = arg => arg
51+
assert.areEqual(123, e(123), "Lambda with single arg and single expression-body");
52+
53+
var f = arg => { return arg }
54+
assert.areEqual(123, f(123), "Lambda with single arg and body in curly-braces");
55+
56+
var g = (arg1, arg2) => arg1 + arg2
57+
assert.areEqual(2, g(1, 1), "Lambda with two args in parens and single expression body");
58+
`);
59+
}
60+
},
61+
{
62+
name: "Async lambda function deferral",
63+
body: function () {
64+
var a = async () => { return 123 };
65+
assert.isTrue(a() instanceof Promise, "Lambda with no args but empty parens and body surrounded with curly-braces");
66+
67+
var b = async (arg) => { return arg; };
68+
assert.isTrue(b() instanceof Promise, "Lambda with an arg in parens");
69+
70+
var c = async (arg1, arg2) => { return arg1 + arg2; };
71+
assert.isTrue(c() instanceof Promise, "Lambda with two args in parens");
72+
73+
var d = async () => 123
74+
assert.isTrue(d() instanceof Promise, "Lambda with empty arg list and single expression-body");
75+
76+
var e = async arg => arg
77+
assert.isTrue(e() instanceof Promise, "Lambda with single arg and single expression-body");
78+
79+
var f = async arg => { return arg }
80+
assert.isTrue(f() instanceof Promise, "Lambda with single arg and body in curly-braces");
81+
82+
var g = async (arg1, arg2) => arg1 + arg2
83+
assert.isTrue(g() instanceof Promise, "Lambda with two args in parens and single expression body");
84+
}
85+
},
86+
{
87+
name: "Global async lambda function deferral",
88+
body: function () {
89+
WScript.LoadScript(`
90+
var a = async () => { return 123 };
91+
assert.isTrue(a() instanceof Promise, "Lambda with no args but empty parens and body surrounded with curly-braces");
92+
93+
var b = async (arg) => { return arg; };
94+
assert.isTrue(b() instanceof Promise, "Lambda with an arg in parens");
95+
96+
var c = async (arg1, arg2) => { return arg1 + arg2; };
97+
assert.isTrue(c() instanceof Promise, "Lambda with two args in parens");
98+
99+
var d = async () => 123
100+
assert.isTrue(d() instanceof Promise, "Lambda with empty arg list and single expression-body");
101+
102+
var e = async arg => arg
103+
assert.isTrue(e() instanceof Promise, "Lambda with single arg and single expression-body");
104+
105+
var f = async arg => { return arg }
106+
assert.isTrue(f() instanceof Promise, "Lambda with single arg and body in curly-braces");
107+
108+
var g = async (arg1, arg2) => arg1 + arg2
109+
assert.isTrue(g() instanceof Promise, "Lambda with two args in parens and single expression body");
110+
`);
111+
}
112+
},
113+
]
114+
115+
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

test/es6/module-syntax.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,10 @@ var tests = [
168168
{
169169
name: "Runtime error import statements",
170170
body: function () {
171-
testModuleScript('import foo from "ValidExportStatements.js"; assert.throws(()=>{ foo =12; }, TypeError, "assignment to const");', 'Imported default bindings are constant bindings', false);
172-
testModuleScript('import { foo } from "ValidExportStatements.js"; assert.throws(()=>{ foo = 12; }, TypeError, "assignment to const");', 'Imported named bindings are constant bindings', false);
173-
174-
testModuleScript('import * as foo from "ValidExportStatements.js"; assert.throws(()=>{ foo = 12; }, TypeError, "assignment to const");', 'Namespace import bindings are constant bindings', false);
175-
176-
testModuleScript('import { foo as foo22 } from "ValidExportStatements.js"; assert.throws(()=>{ foo22 = 12; }, TypeError, "assignment to const");', 'Renamed import bindings are constant bindings', false);
171+
testModuleScript('import foo from "ValidExportStatements.js"; try { (() => { foo = 12; })() } catch(e) { assert.areEqual("Assignment to const", e.message); }', 'Imported default bindings are constant bindings', false);
172+
testModuleScript('import { foo } from "ValidExportStatements.js"; try { (() => { foo = 12; })() } catch(e) { assert.areEqual("Assignment to const", e.message); }', 'Imported named bindings are constant bindings', false);
173+
testModuleScript('import * as foo from "ValidExportStatements.js"; try { (() => { foo = 12; })() } catch(e) { assert.areEqual("Assignment to const", e.message); }', 'Namespace import bindings are constant bindings', false);
174+
testModuleScript('import { foo as foo22 } from "ValidExportStatements.js"; try { (() => { foo22 = 12; })() } catch(e) { assert.areEqual("Assignment to const", e.message); }', 'Renamed import bindings are constant bindings', false);
177175
}
178176
},
179177
{

0 commit comments

Comments
 (0)