-
-
Notifications
You must be signed in to change notification settings - Fork 754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optional chaining #2221
Optional chaining #2221
Changes from all commits
98760f4
7c2e481
c71dded
6b3e1e9
0b5f63a
8b1c7eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -783,10 +783,10 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t | |
bool hadDebuggerNextLineOnly = false; | ||
|
||
if (execInfo.execute&EXEC_DEBUGGER_STEP_INTO) { | ||
if (functionName) | ||
jsiConsolePrintf("Stepping into %v\n", functionName); | ||
else | ||
jsiConsolePrintf("Stepping into function\n", functionName); | ||
if (functionName) | ||
jsiConsolePrintf("Stepping into %v\n", functionName); | ||
else | ||
jsiConsolePrintf("Stepping into function\n", functionName); | ||
} else { | ||
hadDebuggerNextLineOnly = execInfo.execute&EXEC_DEBUGGER_NEXT_LINE; | ||
if (hadDebuggerNextLineOnly) | ||
|
@@ -1055,13 +1055,15 @@ JsVar *jspGetVarNamedField(JsVar *object, JsVar *nameVar, bool returnName) { | |
else return jsvSkipNameAndUnLock(child); | ||
} | ||
|
||
NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { | ||
NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult, bool *isOptional) { | ||
/* The parent if we're executing a method call */ | ||
JsVar *parent = 0; | ||
|
||
while (lex->tk=='.' || lex->tk=='[') { | ||
if (lex->tk == '.') { // ------------------------------------- Record Access | ||
JSP_ASSERT_MATCH('.'); | ||
while (lex->tk==LEX_OPTIONAL_CHAINING || lex->tk=='.' || lex->tk=='[') { | ||
bool optionalTk = lex->tk == LEX_OPTIONAL_CHAINING; | ||
*isOptional |= optionalTk; | ||
if (lex->tk == '.' || optionalTk) { // ------------------------------------- Record Access | ||
jslGetNextToken(); | ||
if (jslIsIDOrReservedWord()) { | ||
if (JSP_SHOULD_EXECUTE) { | ||
// Note: name will go away when we parse something else! | ||
|
@@ -1078,6 +1080,8 @@ NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { | |
JsVar *nameVar = jslGetTokenValueAsVar(); | ||
child = jsvCreateNewChild(aVar, nameVar, 0); | ||
jsvUnLock(nameVar); | ||
} else if (*isOptional) { | ||
child = 0; // undefined | ||
} else { | ||
// could have been a string... | ||
jsExceptionHere(JSET_ERROR, "Cannot read property '%s' of %s", name, jsvIsUndefined(aVar) ? "undefined" : "null"); | ||
|
@@ -1090,6 +1094,20 @@ NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { | |
} | ||
// skip over current token (we checked above that it was an ID or reserved word) | ||
jslGetNextToken(); | ||
#ifndef ESPR_NO_OPTIONAL_CHAINING | ||
} else if ((lex->tk == '(' || lex->tk == '[') && optionalTk) { | ||
// handle a?.() and a?.[0] | ||
|
||
JsVar *aVar = jsvSkipNameWithParent(a, true, parent); | ||
|
||
jsvUnLock(a); | ||
if (jsvIsNullish(aVar)) { | ||
a = 0; // undefined | ||
} else { | ||
a = aVar; | ||
} | ||
continue; | ||
#endif | ||
} else { | ||
// incorrect token - force a match fail by asking for an ID | ||
JSP_MATCH_WITH_RETURN(LEX_ID, a); | ||
|
@@ -1098,6 +1116,15 @@ NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { | |
JsVar *index; | ||
JSP_ASSERT_MATCH('['); | ||
if (!jspCheckStackPosition()) return parent; | ||
|
||
#ifndef ESPR_NO_OPTIONAL_CHAINING | ||
JSP_SAVE_EXECUTE(); | ||
if (jsvIsUndefined(a) && *isOptional) { | ||
// there was a previous a?.b where a was undefined | ||
jspSetNoExecute(); | ||
} | ||
#endif | ||
|
||
index = jsvSkipNameAndUnLock(jspeAssignmentExpression()); | ||
JSP_MATCH_WITH_CLEANUP_AND_RETURN(']', jsvUnLock2(parent, index);, a); | ||
if (JSP_SHOULD_EXECUTE) { | ||
|
@@ -1123,6 +1150,10 @@ NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) { | |
jsvUnLock(aVar); | ||
} | ||
jsvUnLock(index); | ||
|
||
#ifndef ESPR_NO_OPTIONAL_CHAINING | ||
JSP_RESTORE_EXECUTE(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid this breaks stuff. |
||
#endif | ||
} else { | ||
assert(0); | ||
} | ||
|
@@ -1185,7 +1216,8 @@ NO_INLINE JsVar *jspeFactorFunctionCall() { | |
#ifndef SAVE_ON_FLASH | ||
bool wasSuper = lex->tk==LEX_R_SUPER; | ||
#endif | ||
JsVar *a = jspeFactorMember(jspeFactor(), &parent); | ||
bool optional; | ||
JsVar *a = jspeFactorMember(jspeFactor(), &parent, &optional); | ||
#ifndef SAVE_ON_FLASH | ||
if (wasSuper) { | ||
/* if this was 'super.something' then we need | ||
|
@@ -1196,24 +1228,44 @@ NO_INLINE JsVar *jspeFactorFunctionCall() { | |
parent = jsvLockAgainSafe(execInfo.thisVar); | ||
} | ||
#endif | ||
|
||
while ((lex->tk=='(' || (isConstructor && JSP_SHOULD_EXECUTE)) && !jspIsInterrupted()) { | ||
JsVar *funcName = a; | ||
JsVar *func = jsvSkipName(funcName); | ||
|
||
/* The constructor function doesn't change parsing, so if we're | ||
* not executing, just short-cut it. */ | ||
if (isConstructor && JSP_SHOULD_EXECUTE) { | ||
// If we have '(' parse an argument list, otherwise don't look for any args | ||
bool parseArgs = lex->tk=='('; | ||
a = jspeConstruct(func, funcName, parseArgs); | ||
isConstructor = false; // don't treat subsequent brackets as constructors | ||
} else | ||
#ifndef ESPR_NO_OPTIONAL_CHAINING | ||
if (jsvIsUndefined(a) && optional) { | ||
JSP_SAVE_EXECUTE(); | ||
jspSetNoExecute(); | ||
|
||
JsVar *funcName = a; | ||
JsVar *func = jsvSkipName(funcName); | ||
|
||
a = jspeFunctionCall(func, funcName, parent, true, 0, 0); | ||
|
||
jsvUnLock3(funcName, func, parent); | ||
jsvUnLock3(funcName, func, parent); | ||
|
||
JSP_RESTORE_EXECUTE(); | ||
} else if (jsvIsUndefined(a)) { | ||
break; | ||
} else { | ||
#endif | ||
JsVar *funcName = a; | ||
JsVar *func = jsvSkipName(funcName); | ||
|
||
/* The constructor function doesn't change parsing, so if we're | ||
* not executing, just short-cut it. */ | ||
if (isConstructor && JSP_SHOULD_EXECUTE) { | ||
// If we have '(' parse an argument list, otherwise don't look for any args | ||
bool parseArgs = lex->tk=='('; | ||
a = jspeConstruct(func, funcName, parseArgs); | ||
isConstructor = false; // don't treat subsequent brackets as constructors | ||
} else | ||
a = jspeFunctionCall(func, funcName, parent, true, 0, 0); | ||
|
||
jsvUnLock3(funcName, func, parent); | ||
#ifndef ESPR_NO_OPTIONAL_CHAINING | ||
} | ||
#endif | ||
parent=0; | ||
a = jspeFactorMember(a, &parent); | ||
optional=false; | ||
a = jspeFactorMember(a, &parent, &optional); | ||
} | ||
#ifndef SAVE_ON_FLASH | ||
/* If we've got something that we care about the parent of (eg. a getter/setter) | ||
|
@@ -1403,7 +1455,8 @@ NO_INLINE JsVar *jspeFactorTypeOf() { | |
NO_INLINE JsVar *jspeFactorDelete() { | ||
JSP_ASSERT_MATCH(LEX_R_DELETE); | ||
JsVar *parent = 0; | ||
JsVar *a = jspeFactorMember(jspeFactor(), &parent); | ||
bool optional; | ||
JsVar *a = jspeFactorMember(jspeFactor(), &parent, &optional); | ||
JsVar *result = 0; | ||
if (JSP_SHOULD_EXECUTE) { | ||
bool ok = false; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Testing optional chaining | ||
|
||
var a; | ||
|
||
result = a?.b ?? true; | ||
result &= a?.b.c ?? true; | ||
result &= a?.b() ?? true; | ||
|
||
a = null; | ||
|
||
result &= a?.b ?? true; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Tests optional chaining with array access | ||
|
||
var a = undefined; | ||
|
||
result = a?.b[0] ?? true; | ||
|
||
result |= a?.[0] ?? true; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Testing optional chaining method | ||
|
||
// when a is undefined | ||
var a; | ||
result = a?.() ?? true; | ||
|
||
// when a is a function | ||
a = () => true; | ||
result &= a?.() ?? false; | ||
|
||
// when a.b is undefined | ||
a = {}; | ||
result &= a.b?.() ?? true; | ||
|
||
// when a.b is a function | ||
a = { b: () => true }; | ||
result &= a.b?.() ?? false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just noticed these spaces got replaced with tabs. Please can you try and tweak these (and change your editor so tabs don't get added?). It really screws up the layout if someone views the code and doesn't have 8 space tabs set