Sfoglia il codice sorgente

Add support for Optional Chaining (#881)

* Implement optional chaining

* finishing touches

* switch to new Esprima release on NuGet
Marko Lahma 4 anni fa
parent
commit
0dbcdb0395
62 ha cambiato i file con 1292 aggiunte e 101 eliminazioni
  1. 7 3
      Jint.Repl/Program.cs
  2. 15 0
      Jint.Tests.Test262/Language/Expressions/OptionalChaining.cs
  3. 23 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/call-expression-super-no-base.js
  4. 75 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/call-expression.js
  5. 25 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js
  6. 22 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js
  7. 25 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js
  8. 22 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js
  9. 27 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js
  10. 24 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js
  11. 27 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js
  12. 24 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js
  13. 39 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/eval-optional-call.js
  14. 18 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-do.js
  15. 35 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-await-of.js
  16. 22 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-in.js
  17. 28 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js
  18. 43 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for.js
  19. 18 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-while.js
  20. 32 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-identifier.js
  21. 19 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-literal.js
  22. 20 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-this.js
  23. 104 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression.js
  24. 30 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/new-target-optional-call.js
  25. 27 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js
  26. 28 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js
  27. 24 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js
  28. 20 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js
  29. 19 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-arguments.js
  30. 42 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-expression.js
  31. 38 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js
  32. 50 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain.js
  33. 27 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-expression.js
  34. 15 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js
  35. 18 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/runtime-semantics-evaluation.js
  36. 22 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/short-circuiting.js
  37. 23 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/static-semantics-simple-assignment.js
  38. 30 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/super-property-optional-call.js
  39. 23 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/update-expression-postfix.js
  40. 23 0
      Jint.Tests.Test262/test/language/expressions/optional-chaining/update-expression-prefix.js
  41. 4 0
      Jint.Tests.Test262/test/skipped.json
  42. 0 1
      Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs
  43. 1 4
      Jint.Tests/Runtime/ExtensionMethods/OverrideStringPrototypeExtensions.cs
  44. 1 1
      Jint/Engine.cs
  45. 13 0
      Jint/EsprimaExtensions.cs
  46. 1 1
      Jint/Jint.csproj
  47. 6 5
      Jint/Native/Function/EvalFunctionInstance.cs
  48. 0 1
      Jint/Native/JsValue.cs
  49. 0 5
      Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
  50. 0 6
      Jint/Runtime/Environments/EnvironmentRecord.cs
  51. 0 7
      Jint/Runtime/Environments/FunctionEnvironmentRecord.cs
  52. 0 5
      Jint/Runtime/Environments/GlobalEnvironmentRecord.cs
  53. 0 10
      Jint/Runtime/Environments/ObjectEnvironmentRecord.cs
  54. 0 2
      Jint/Runtime/EventLoop.cs
  55. 0 1
      Jint/Runtime/Interop/TypeReference.cs
  56. 2 3
      Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
  57. 88 39
      Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs
  58. 4 1
      Jint/Runtime/Interpreter/Expressions/JintExpression.cs
  59. 10 4
      Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs
  60. 9 0
      Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs
  61. 0 1
      Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs
  62. 0 1
      Jint/Runtime/TypeConverter.cs

+ 7 - 3
Jint.Repl/Program.cs

@@ -57,7 +57,7 @@ namespace Jint.Repl
                 Console.ForegroundColor = defaultColor;
                 Console.Write("jint> ");
                 var input = Console.ReadLine();
-                if (input == "exit")
+                if (input is "exit" or ".exit")
                 {
                     return;
                 }
@@ -65,10 +65,14 @@ namespace Jint.Repl
                 try
                 {
                     var result = engine.GetValue(engine.Execute(input, parserOptions).GetCompletionValue());
-                    if (result.Type != Types.None && result.Type != Types.Null && result.Type != Types.Undefined)
+                    if (!result.IsNull() && !result.IsUndefined())
                     {
                         var str = TypeConverter.ToString(engine.Json.Stringify(engine.Json, Arguments.From(result, Undefined.Instance, "  ")));
-                        Console.WriteLine("=> {0}", str);
+                        Console.WriteLine(str);
+                    }
+                    else
+                    {
+                        Console.WriteLine(result);
                     }
                 }
                 catch (JavaScriptException je)

+ 15 - 0
Jint.Tests.Test262/Language/Expressions/OptionalChaining.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.Language.Expressions
+{
+    public class OptionalChaining : Test262Test
+    {
+        [Theory(DisplayName = "language\\expressions\\optional-chaining")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\optional-chaining", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\optional-chaining", true, Skip = "Skipped")]
+        protected void Chaining(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 23 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/call-expression-super-no-base.js

@@ -0,0 +1,23 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  should not suppress error if super called on class with no base
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      SuperCall OptionalChain
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+class C {
+  constructor () {
+    super()?.a;
+  }
+}

+ 75 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/call-expression.js

@@ -0,0 +1,75 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on call expression
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      CallExpression OptionalChain
+features: [optional-chaining]
+---*/
+
+// CallExpression CoverCallExpressionAndAsyncArrowHead
+function fn () {
+  return {a: 33};
+};
+const obj = {
+  fn () {
+    return 44;
+  }
+}
+assert.sameValue(33, fn()?.a);
+assert.sameValue(undefined, fn()?.b);
+assert.sameValue(44, obj?.fn());
+
+// CallExpression SuperCall
+class A {}
+class B extends A {
+  constructor () {
+    assert.sameValue(undefined, super()?.a);
+  }
+}
+new B();
+
+// CallExpression Arguments
+function fn2 () {
+  return () => {
+    return {a: 66};
+  };
+}
+function fn3 () {
+  return () => {
+    return null;
+  };
+}
+assert.sameValue(66, fn2()()?.a);
+assert.sameValue(undefined, fn3()()?.a);
+
+// CallExpression [Expression]
+function fn4 () {
+  return [{a: 77}];
+}
+function fn5 () {
+  return [];
+}
+assert.sameValue(77, fn4()[0]?.a);
+assert.sameValue(undefined, fn5()[0]?.a);
+
+// CallExpression .IdentifierName
+function fn6 () {
+  return {
+    a: {
+      b: 88
+    }
+  };
+}
+assert.sameValue(88, fn6().a?.b);
+assert.sameValue(undefined, fn6().b?.c);
+
+// CallExpression TemplateLiteral
+function fn7 () {
+  return () => {};
+}
+assert.sameValue(undefined, fn7()`hello`?.a);

+ 25 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string-esi.js

@@ -0,0 +1,25 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+null?.
+  `hello`

+ 22 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-op-template-string.js

@@ -0,0 +1,22 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+null?.`hello`;

+ 25 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string-esi.js

@@ -0,0 +1,25 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+null?.fn
+  `hello`

+ 22 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-null-optchain-template-string.js

@@ -0,0 +1,22 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+null?.fn`hello`;

+ 27 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string-esi.js

@@ -0,0 +1,27 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+const a = function() {};
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+a?.
+  `hello`

+ 24 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-op-template-string.js

@@ -0,0 +1,24 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+const a = function() {};
+
+a?.`hello`;

+ 27 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string-esi.js

@@ -0,0 +1,27 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+const a = {fn() {}};
+
+// This production exists in order to prevent automatic semicolon
+// insertion rules.
+a?.fn
+  `hello`

+ 24 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/early-errors-tail-position-optchain-template-string.js

@@ -0,0 +1,24 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  template string passed to tail position of optional chain
+info: |
+  Static Semantics: Early Errors
+    OptionalChain:
+      ?.TemplateLiteral
+      OptionalChain TemplateLiteral
+
+  It is a Syntax Error if any code matches this production.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+const a = {fn() {}};
+
+a?.fn`hello`;

+ 39 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/eval-optional-call.js

@@ -0,0 +1,39 @@
+// Copyright 2020 Toru Nagashima.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-optional-chaining-chain-evaluation
+description: optional call invoked on eval function should be indirect eval.
+info: |
+  Runtime Semantics: ChainEvaluation
+    OptionalChain: ?. Arguments
+      1. Let thisChain be this OptionalChain.
+      2. Let tailCall be IsInTailPosition(thisChain).
+      3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall).
+
+  Runtime Semantics: EvaluateCall ( func, ref, arguments, tailPosition )
+
+  ...
+  7. Let result be Call(func, thisValue, argList).
+  ...
+
+  eval ( x )
+
+  ...
+  4. Return ? PerformEval(x, callerRealm, false, false).
+
+  Runtime Semantics: PerformEval ( x, callerRealm, strictCaller, direct )
+features: [optional-chaining]
+---*/
+
+const a = 'global';
+
+function fn() {
+  const a = 'local';
+  return eval?.('a');
+}
+
+assert.sameValue(fn(), 'global', 'fn() returns "global" value from indirect eval');
+
+const b = (a => eval?.('a'))('local');
+
+assert.sameValue(b, 'global', 'b is "global", from indirect eval not observing parameter');

+ 18 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-do.js

@@ -0,0 +1,18 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain in test portion of do while statement
+info: |
+  IterationStatement
+    do Statement while (OptionalExpression)
+features: [optional-chaining]
+---*/
+let count = 0;
+const obj = {a: true};
+do {
+  count++;
+  break;
+} while (obj?.a);
+assert.sameValue(1, count);

+ 35 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-await-of.js

@@ -0,0 +1,35 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain RHS of for await statement
+info: |
+  IterationStatement
+    for await (LeftHandSideExpression of AssignmentExpression) Statement
+features: [optional-chaining]
+flags: [async]
+---*/
+const obj = {
+  iterable: {
+    [Symbol.asyncIterator]() {
+      return {
+        i: 0,
+        next() {
+          if (this.i < 3) {
+            return Promise.resolve({ value: this.i++, done: false });
+          }
+          return Promise.resolve({ done: true });
+        }
+      };
+    }
+  }
+};
+async function checkAssertions() {
+  let count = 0;
+  for await (const num of obj?.iterable) {
+    count += num;
+  }
+  assert.sameValue(3, count);
+}
+checkAssertions().then($DONE, $DONE);

+ 22 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-in.js

@@ -0,0 +1,22 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain in test portion of do while statement
+info: |
+  IterationStatement
+    for (LeftHandSideExpression in Expression) Statement
+features: [optional-chaining]
+---*/
+const obj = {
+  inner: {
+    a: 1,
+    b: 2
+  }
+};
+let str = '';
+for (const key in obj?.inner) {
+  str += key;
+}
+assert.sameValue('ab', str);

+ 28 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for-of-type-error.js

@@ -0,0 +1,28 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain returning undefined in RHS of for of statement
+info: |
+  IterationStatement
+    for (LeftHandSideExpression of Expression) Statement
+features: [optional-chaining]
+---*/
+
+assert.throws(TypeError, function() {
+  for (const key of {}?.a) ;
+});
+
+assert.throws(TypeError, function() {
+  for (const key of {}?.a) {}
+});
+
+const obj = undefined;
+assert.throws(TypeError, function() {
+  for (const key of obj?.a) {}
+});
+
+assert.throws(TypeError, function() {
+  for (const key of obj?.a);
+});

+ 43 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-for.js

@@ -0,0 +1,43 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain in init/test/update of for statement
+info: |
+  IterationStatement
+    for (Expression; Expression; Expression) Statement
+features: [optional-chaining]
+---*/
+
+// OptionalExpression in test.
+let count;
+const obj = {a: true};
+for (count = 0; obj?.a; count++) {
+  if (count > 0) break;
+}
+assert.sameValue(count, 1);
+
+// OptionalExpression in init/test/update.
+let count2 = 0;
+const obj2 = undefined;
+
+for (obj?.a; obj2?.a; obj?.a) { count2++; }
+assert.sameValue(count2, 0);
+
+for (obj?.a; undefined?.a; obj?.a) { count2++; }
+assert.sameValue(count2, 0);
+
+// Short-circuiting
+let touched = 0;
+const obj3 = {
+  get a() {
+    count++;
+    return undefined; // explicit for clarity
+  }
+};
+for (count = 0; true; obj3?.a?.[touched++]) {
+  if (count > 0) { break; }
+}
+assert.sameValue(count, 1);
+assert.sameValue(touched, 0);

+ 18 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/iteration-statement-while.js

@@ -0,0 +1,18 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain in test portion of while statement
+info: |
+  IterationStatement
+    while (Expression) Statement
+features: [optional-chaining]
+---*/
+let count = 0;
+const obj = {a: true};
+while (obj?.a) {
+  count++;
+  break;
+}
+assert.sameValue(1, count);

+ 32 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-identifier.js

@@ -0,0 +1,32 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on member expression in async context
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression
+      MemberExpression [PrimaryExpression identifier] OptionalChain
+features: [optional-chaining]
+flags: [async]
+---*/
+
+const a = undefined;
+const c = {d: Promise.resolve(11)};
+async function checkAssertions() {
+  assert.sameValue(await a?.b, undefined);
+  assert.sameValue(await c?.d, 11);
+  
+  Promise.prototype.x = 42;
+  var res = await Promise.resolve(undefined)?.x;
+  assert.sameValue(res, 42, 'await unwraps the evaluation of the whole optional chaining expression #1');
+
+  Promise.prototype.y = 43;
+  var res = await Promise.reject(undefined)?.y;
+  assert.sameValue(res, 43, 'await unwraps the evaluation of the whole optional chaining expression #2');
+  
+  c.e = Promise.resolve(39);
+  assert.sameValue(await c?.e, 39, 'await unwraps the promise given after the evaluation of the OCE');
+}
+checkAssertions().then($DONE, $DONE);

+ 19 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-literal.js

@@ -0,0 +1,19 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on member expression in async context
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      MemberExpression [PrimaryExpression literal] OptionalChain
+features: [optional-chaining]
+flags: [async]
+---*/
+
+async function checkAssertions() {
+  assert.sameValue(await "hello"?.[0], 'h');
+  assert.sameValue(await null?.a, undefined);
+}
+checkAssertions().then($DONE, $DONE);

+ 20 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression-async-this.js

@@ -0,0 +1,20 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on member expression in async context
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      MemberExpression [PrimaryExpression this] OptionalChain
+features: [optional-chaining]
+flags: [async]
+---*/
+
+async function thisFn() {
+  return await this?.a
+}
+thisFn.call({a: Promise.resolve(33)}).then(function(arg) {
+  assert.sameValue(33, arg);
+}).then($DONE, $DONE);

+ 104 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/member-expression.js

@@ -0,0 +1,104 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on member expression
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      MemberExpression OptionalChain
+features: [optional-chaining]
+---*/
+
+// PrimaryExpression
+//   IdentifierReference
+const a = {b: 22};
+assert.sameValue(22, a?.b);
+//   this
+function fn () {
+  return this?.a
+}
+assert.sameValue(33, fn.call({a: 33}));
+//   Literal
+assert.sameValue(undefined, "hello"?.a);
+assert.sameValue(undefined, null?.a);
+//   ArrayLiteral
+assert.sameValue(2, [1, 2]?.[1]);
+//   ObjectLiteral
+assert.sameValue(44, {a: 44}?.a);
+//   FunctionExpression
+assert.sameValue('a', (function a () {}?.name));
+//   ClassExpression
+assert.sameValue('Foo', (class Foo {}?.name));
+//   GeneratorFunction
+assert.sameValue('a', (function * a () {}?.name));
+//   AsyncFunctionExpression
+assert.sameValue('a', (async function a () {}?.name));
+//   AsyncGeneratorExpression
+assert.sameValue('a', (async function * a () {}?.name));
+//   RegularExpressionLiteral
+assert.sameValue(true, /[a-z]/?.test('a'));
+//   TemplateLiteral
+assert.sameValue('h', `hello`?.[0]);
+//   CoverParenthesizedExpressionAndArrowParameterList
+assert.sameValue(undefined, ({a: 33}, null)?.a);
+assert.sameValue(33, (undefined, {a: 33})?.a);
+
+//  MemberExpression [ Expression ]
+const arr = [{a: 33}];
+assert.sameValue(33, arr[0]?.a);
+assert.sameValue(undefined, arr[1]?.a);
+
+//  MemberExpression .IdentifierName
+const obj = {a: {b: 44}};
+assert.sameValue(44, obj.a?.b);
+assert.sameValue(undefined, obj.c?.b);
+
+//  MemberExpression TemplateLiteral
+function f2 () {
+  return {a: 33};
+}
+function f3 () {}
+assert.sameValue(33, f2`hello world`?.a);
+assert.sameValue(undefined, f3`hello world`?.a);
+
+//  MemberExpression SuperProperty
+class A {
+  a () {}
+  undf () {
+    return super.a?.c;
+  }
+}
+class B extends A {
+  dot () {
+    return super.a?.name;
+  }
+  expr () {
+    return super['a']?.name;
+  }
+  undf2 () {
+    return super.b?.c;
+  }
+}
+const subcls = new B();
+assert.sameValue('a', subcls.dot());
+assert.sameValue('a', subcls.expr());
+assert.sameValue(undefined, subcls.undf2());
+assert.sameValue(undefined, (new A()).undf());
+
+// MemberExpression MetaProperty
+class C {
+  constructor () {
+    assert.sameValue(undefined, new.target?.a);
+  }
+}
+new C();
+
+// new MemberExpression Arguments
+class D {
+  constructor (val) {
+    this.a = val;
+  }
+}
+assert.sameValue(99, new D(99)?.a);

+ 30 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/new-target-optional-call.js

@@ -0,0 +1,30 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional call invoked on new.target should be equivalent to call
+info: |
+  OptionalExpression
+    MemberExpression OptionalChain
+      NewTarget OptionalChain
+features: [optional-chaining]
+---*/
+
+const newTargetContext = (function() { return this; })();
+
+let called = false;
+// should be set to 'undefined' or global context, depending on whether
+// mode is strict or sloppy.
+let context = null;
+function Base() {
+  called = true;
+  context = this;
+}
+function Foo(blerg) {
+  new.target?.();
+}
+
+Reflect.construct(Foo, [], Base);
+assert(context === newTargetContext);
+assert.sameValue(called, true);

+ 27 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-call-preserves-this.js

@@ -0,0 +1,27 @@
+// Copyright (C) 2019 Sony Interactive Entertainment Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-optional-chaining-chain-evaluation
+description: >
+  optional call must preserve this context, as with a non-optional call
+info: |
+  OptionalChain : ?. Arguments
+    1. Let thisChain be this OptionalChain.
+    2. Let tailCall be IsInTailPosition(thisChain).
+    3. Return ? EvaluateCall(baseValue, baseReference, Arguments, tailCall).
+features: [optional-chaining]
+---*/
+
+const a = {
+    b() { return this._b; },
+    _b: { c: 42 }
+};
+
+assert.sameValue(a?.b().c, 42);
+assert.sameValue((a?.b)().c, 42);
+
+assert.sameValue(a.b?.().c, 42);
+assert.sameValue((a.b)?.().c, 42);
+
+assert.sameValue(a?.b?.().c, 42);
+assert.sameValue((a?.b)?.().c, 42);

+ 28 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js

@@ -0,0 +1,28 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain expansions in an async context
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression
+      MemberExpression [PrimaryExpression Identifier] OptionalChain
+        OptionalChain OptionalChain ?.[Expression]
+features: [optional-chaining]
+flags: [async]
+---*/
+
+async function checkAssertions() {
+  assert.sameValue(await {a: [11]}?.a[0], 11);
+  const b = {c: [22, 33]};
+  assert.sameValue(b?.c[await Promise.resolve(1)], 33);
+  function e(val) {
+    return val;
+  }
+  assert.sameValue({d: e}?.d(await Promise.resolve([44, 55]))[1], 55);
+  assert.sameValue(undefined?.arr[
+    await Promise.reject(new Error('unreachable'))
+  ], undefined);
+}
+checkAssertions().then($DONE, $DONE);

+ 24 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-async-square-brackets.js

@@ -0,0 +1,24 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain expansions in an async context
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression
+      MemberExpression [PrimaryExpression Identifier] OptionalChain
+        OptionalChain ?.[Expression]
+features: [optional-chaining]
+flags: [async]
+---*/
+
+async function checkAssertions() {
+  assert.sameValue(await [11]?.[0], 11);
+  assert.sameValue([22, 33]?.[await Promise.resolve(1)], 33);
+  assert.sameValue([44, await Promise.resolve(55)]?.[1], 55);
+  assert.sameValue(undefined?.[
+    await Promise.reject(new Error('unreachable'))
+  ], undefined);
+}
+checkAssertions().then($DONE, $DONE);

+ 20 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-expression-optional-expression.js

@@ -0,0 +1,20 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain bracket notation containing optional expresion
+info: |
+  OptionalChain:
+    ?. [OptionalExpression]
+features: [optional-chaining]
+---*/
+const a = undefined;
+const b = {e: 0};
+const c = {};
+c[undefined] = 11;
+const d = [22];
+
+assert.sameValue(undefined, a?.[a?.b]);
+assert.sameValue(11, c?.[a?.b]);
+assert.sameValue(22, d?.[b?.e]);

+ 19 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-arguments.js

@@ -0,0 +1,19 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  Productions for ?. Arguments
+info: |
+  OptionalChain[Yield, Await]:
+    ?. Arguments
+features: [optional-chaining]
+---*/
+
+function fn(arg1, arg2, arg3 = 0) {
+  return arg1 + arg2 + arg3;
+}
+
+assert.sameValue(fn?.(10, 20), 30, 'regular');
+assert.sameValue(String?.(42), '42', 'built-in');
+assert.sameValue(fn ?. (...[10, 20, 40]), 70, 'spread');

+ 42 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-expression.js

@@ -0,0 +1,42 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  Productions for ?. [Expression]
+info: |
+  OptionalChain:
+    ?.[ Expression ]
+features: [optional-chaining]
+---*/
+
+const $ = 'x';
+const arr = [39, 42];
+
+arr.true = 'prop';
+arr[1.1] = 'other prop';
+
+const obj = {
+  a: 'hello',
+  undefined: 40,
+  $: 0,
+  NaN: 41,
+  null: 42,
+  x: 43,
+  true: 44
+};
+
+assert.sameValue(arr?.[0], 39, '[0]');
+assert.sameValue(arr?.[0, 1], 42, '[0, 1]');
+assert.sameValue(arr?.[1], 42, '[1]');
+assert.sameValue(arr?.[1, 0], 39, '[1, 0]');
+assert.sameValue(arr?.[{}, NaN, undefined, 2, 0, 10 / 10], 42, '[{}, NaN, undefined, 2, 0, 10 / 10]');
+assert.sameValue(arr?.[true], 'prop', '[true]');
+assert.sameValue(arr?.[1.1], 'other prop', '[1.1]');
+
+assert.sameValue(obj?.[undefined], 40, '[undefined]');
+assert.sameValue(obj?.[NaN], 41, '[NaN]');
+assert.sameValue(obj?.[null], 42, '[null]');
+assert.sameValue(obj?.['$'], 0, '["$"]');
+assert.sameValue(obj?.[$], 43, '[$]');
+assert.sameValue(obj?.[true], 44, '[true]');

+ 38 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain-prod-identifiername.js

@@ -0,0 +1,38 @@
+// Copyright 2020 Salesforce.com, Inc. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: prod-OptionalExpression
+description: >
+  Productions for ?. IdentifierName
+info: |
+  OptionalChain[Yield, Await]:
+    ?. IdentifierName
+features: [optional-chaining]
+---*/
+
+const arr = [10, 11];
+const obj = {
+  a: 'hello'
+};
+
+assert.sameValue(obj?.a, 'hello');
+assert.sameValue(obj?.\u0061, 'hello');
+assert.sameValue(obj?.\u{0061}, 'hello');
+
+assert.sameValue(obj?.\u0062, undefined);
+assert.sameValue(obj?.\u{0062}, undefined);
+
+assert.sameValue(arr ?. length, 2);
+assert.sameValue(arr ?. l\u0065ngth, 2);
+assert.sameValue(arr ?. l\u{0065}ngth, 2);
+
+assert.sameValue(obj?.$, undefined);
+
+obj.$ = 42;
+assert.sameValue(obj?.$, 42);
+
+assert.sameValue(obj?._, undefined);
+
+obj._ = 39;
+assert.sameValue(obj?._, 39);

+ 50 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-chain.js

@@ -0,0 +1,50 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  various optional chain expansions
+info: |
+  OptionalChain[Yield, Await]:
+    ?.[Expression]
+    ?.IdentifierName
+    ?.Arguments
+    ?.TemplateLiteral
+    OptionalChain [Expression]
+    OptionalChain .IdentifierName
+    OptionalChain Arguments[?Yield, ?Await]
+    OptionalChain TemplateLiteral
+features: [optional-chaining]
+---*/
+
+const arr = [10, 11];
+const obj = {
+  a: 'hello',
+  b: {val: 13},
+  c(arg1) {
+    return arg1 * 2;
+  },
+  arr: [11, 12]
+};
+const i = 0;
+
+// OptionalChain: ?.[Expression]
+assert.sameValue(11, arr?.[i + 1]);
+
+// OptionalChain: ?.IdentifierName
+assert.sameValue('hello', obj?.a);
+
+// OptionalChain: ?.Arguments
+const fn = (arg1, arg2) => {
+  return arg1 + arg2;
+}
+assert.sameValue(30, fn?.(10, 20));
+
+// OptionalChain: OptionalChain [Expression]
+assert.sameValue(12, obj?.arr[i + 1]);
+
+// OptionalChain: OptionalChain .IdentifierName
+assert.sameValue(13, obj?.b.val);
+
+// OptionalChain: OptionalChain Arguments
+assert.sameValue(20, obj?.c(10));

+ 27 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/optional-expression.js

@@ -0,0 +1,27 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chain on recursive optional expression
+info: |
+  Left-Hand-Side Expressions
+    OptionalExpression:
+      OptionalExpression OptionalChain
+features: [optional-chaining]
+---*/
+
+const obj = {
+  a: {
+    b: 22
+  }
+};
+
+function fn () {
+  return {};
+}
+
+// OptionalExpression (MemberExpression OptionalChain) OptionalChain
+assert.sameValue(22, obj?.a?.b);
+// OptionalExpression (CallExpression OptionalChain) OptionalChain
+assert.sameValue(undefined, fn()?.a?.b);

+ 15 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/punctuator-decimal-lookahead.js

@@ -0,0 +1,15 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  ternary operation with decimal does not evaluate as optional chain 
+info: |
+  Punctuators
+    OptionalChainingPunctuator::
+      ?.[lookahead ∉ DecimalDigit]
+features: [optional-chaining]
+---*/
+
+const value = true ?.30 : false;
+assert.sameValue(.30, value);

+ 18 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/runtime-semantics-evaluation.js

@@ -0,0 +1,18 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  accessing optional value on undefined or null returns undefined.
+info: |
+  If baseValue is undefined or null, then
+    Return undefined.
+features: [optional-chaining]
+---*/
+
+const nul = null;
+const undf = undefined;
+assert.sameValue(undefined, nul?.a);
+assert.sameValue(undefined, undf?.b);
+assert.sameValue(undefined, null?.a);
+assert.sameValue(undefined, undefined?.b);

+ 22 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/short-circuiting.js

@@ -0,0 +1,22 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  demonstrate syntax-based short-circuiting.
+info: |
+  If the expression on the LHS of ?. evaluates to null/undefined, the RHS is
+  not evaluated
+features: [optional-chaining]
+---*/
+
+const a = undefined;
+let x = 1;
+
+a?.[++x] // short-circuiting.
+a?.b.c(++x).d; // long short-circuiting.
+
+undefined?.[++x] // short-circuiting.
+undefined?.b.c(++x).d; // long short-circuiting.
+
+assert.sameValue(1, x);

+ 23 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/static-semantics-simple-assignment.js

@@ -0,0 +1,23 @@
+
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  an optional expression cannot be target of assignment
+info: |
+  Static Semantics: IsValidSimpleAssignmentTarget
+    LeftHandSideExpression:
+      OptionalExpression
+    Return false.
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+const obj = {};
+
+obj?.a = 33;

+ 30 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/super-property-optional-call.js

@@ -0,0 +1,30 @@
+// Copyright 2019 Google, LLC.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional call invoked on super method should be equivalent to call
+info: |
+  OptionalExpression
+    MemberExpression OptionalChain
+      SuperProperty OptionalChain
+features: [optional-chaining]
+---*/
+
+let called = false;
+let context;
+class Base {
+    method() {
+      called = true;
+      context = this;
+    }
+}
+class Foo extends Base {
+    method() {
+      super.method?.();
+    }
+}
+const foo = new Foo();
+foo.method();
+assert(foo === context);
+assert.sameValue(called, true);

+ 23 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/update-expression-postfix.js

@@ -0,0 +1,23 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chaining is forbidden in write contexts
+info: |
+  UpdateExpression[Yield, Await]:
+    LeftHandSideExpression++
+    LeftHandSideExpression--
+    ++UnaryExpression
+    --UnaryExpression
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+// LeftHandSideExpression ++
+const a = {};
+a?.b++;

+ 23 - 0
Jint.Tests.Test262/test/language/expressions/optional-chaining/update-expression-prefix.js

@@ -0,0 +1,23 @@
+// Copyright 2019 Google, Inc.  All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: prod-OptionalExpression
+description: >
+  optional chaining is forbidden in write contexts
+info: |
+  UpdateExpression[Yield, Await]:
+    LeftHandSideExpression++
+    LeftHandSideExpression--
+    ++UnaryExpression
+    --UnaryExpression
+features: [optional-chaining]
+negative:
+  type: SyntaxError
+  phase: parse
+---*/
+
+$DONOTEVALUATE();
+
+// --UnaryExpression
+const a = {};
+--a?.b;

+ 4 - 0
Jint.Tests.Test262/test/skipped.json

@@ -346,6 +346,10 @@
 
   // Esprima problems
 
+  {
+    "source": "language/expressions/optional-chaining/member-expression.js",
+    "reason": "Esprima problem"
+  },
   {
     "source": "language/statements/for-of/dstr-obj-id-init-let.js",
     "reason": "Esprima problem"

+ 0 - 1
Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs

@@ -1,6 +1,5 @@
 using Jint.Native;
 using Jint.Tests.Runtime.Domain;
-using System;
 using System.Collections.Generic;
 using System.Linq;
 using Xunit;

+ 1 - 4
Jint.Tests/Runtime/ExtensionMethods/OverrideStringPrototypeExtensions.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Dynamic;
-using System.Linq;
-using Newtonsoft.Json;
+using System.Linq;
 
 namespace Jint.Tests.Runtime.ExtensionMethods
 {

+ 1 - 1
Jint/Engine.cs

@@ -579,7 +579,7 @@ namespace Jint
                 }
             }
 
-            if (!(baseValue is EnvironmentRecord record))
+            if (baseValue is not EnvironmentRecord record)
             {
                 return ExceptionHelper.ThrowArgumentException<JsValue>();
             }

+ 13 - 0
Jint/EsprimaExtensions.cs

@@ -65,6 +65,19 @@ namespace Jint
                    || type == Nodes.ClassExpression;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsOptional<T>(this T node) where T : Expression
+        {
+            switch (node)
+            {
+                case MemberExpression { Optional: true }:
+                case CallExpression { Optional: true }:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static string LiteralKeyToString(Literal literal)
         {

+ 1 - 1
Jint/Jint.csproj

@@ -8,7 +8,7 @@
     <IsPackable>true</IsPackable>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="2.0.0-beta-1338" />
+    <PackageReference Include="Esprima" Version="2.0.0-beta-1339" />
     <PackageReference Include="IsExternalInit" Version="1.0.0">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

+ 6 - 5
Jint/Native/Function/EvalFunctionInstance.cs

@@ -21,17 +21,18 @@ namespace Jint.Native.Function
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
-            return PerformEval(arguments, false);
+            var callerRealm = (object) null;
+            return PerformEval(arguments.At(0), callerRealm, StrictModeScope.IsStrictModeCode, false);
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-performeval
         /// </summary>
-        public JsValue PerformEval(JsValue[] arguments, bool direct)
+        public JsValue PerformEval(JsValue x, object callerRealm, bool strictCaller, bool direct)
         {
-            if (!(arguments.At(0) is JsString x))
+            if (!x.IsString())
             {
-                return arguments.At(0);
+                return x;
             }
 
             var inFunction = false;
@@ -58,7 +59,7 @@ namespace Jint.Native.Function
             Script script;
             try
             {
-                script = parser.ParseScript(StrictModeScope.IsStrictModeCode);
+                script = parser.ParseScript(strictCaller);
             }
             catch (ParserException e)
             {

+ 0 - 1
Jint/Native/JsValue.cs

@@ -4,7 +4,6 @@ using System.Diagnostics;
 using System.Diagnostics.Contracts;
 using System.Runtime.CompilerServices;
 using System.Threading;
-using System.Threading.Tasks;
 using Jint.Native.Array;
 using Jint.Native.Date;
 using Jint.Native.Iterator;

+ 0 - 5
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -161,11 +161,6 @@ namespace Jint.Runtime.Environments
 
         public override JsValue WithBaseObject() => Undefined;
 
-        public sealed override JsValue ImplicitThisValue()
-        {
-            return Undefined;
-        }
-
         /// <inheritdoc />
         internal sealed override string[] GetAllBindingNames()
         {

+ 0 - 6
Jint/Runtime/Environments/EnvironmentRecord.cs

@@ -80,12 +80,6 @@ namespace Jint.Runtime.Environments
 
         public abstract JsValue WithBaseObject();
 
-        /// <summary>
-        /// Returns the value to use as the <c>this</c> value on calls to function objects that are obtained as binding values from this environment record.
-        /// </summary>
-        /// <returns>The value to use as <c>this</c>.</returns>
-        public abstract JsValue ImplicitThisValue();
-
         /// <summary>
         /// Returns an array of all the defined binding names
         /// </summary>

+ 0 - 7
Jint/Runtime/Environments/FunctionEnvironmentRecord.cs

@@ -49,13 +49,6 @@ namespace Jint.Runtime.Environments
         public override bool HasSuperBinding() => 
             _thisBindingStatus != ThisBindingStatus.Lexical && !_functionObject._homeObject.IsUndefined();
 
-        public override JsValue WithBaseObject()
-        {
-            return _thisBindingStatus == ThisBindingStatus.Uninitialized
-                ? ExceptionHelper.ThrowReferenceError<JsValue>(_engine)
-                : _thisValue;
-        }
-
         public JsValue BindThisValue(JsValue value)
         {
             if (_thisBindingStatus == ThisBindingStatus.Initialized)

+ 0 - 5
Jint/Runtime/Environments/GlobalEnvironmentRecord.cs

@@ -284,11 +284,6 @@ namespace Jint.Runtime.Environments
             _varNames.Add(name);
         }
 
-        public override JsValue ImplicitThisValue()
-        {
-            return Undefined;
-        }
-
         internal override string[] GetAllBindingNames()
         {
             // JT: Rather than introduce a new method for the debugger, I'm reusing this one,

+ 0 - 10
Jint/Runtime/Environments/ObjectEnvironmentRecord.cs

@@ -166,16 +166,6 @@ namespace Jint.Runtime.Environments
 
         public override JsValue WithBaseObject() => _withEnvironment ? _bindingObject : Undefined;
 
-        
-        public override JsValue ImplicitThisValue()
-        {
-            if (_provideThis)
-            {
-                return _bindingObject;
-            }
-
-            return Undefined;
-        }
 
         internal override string[] GetAllBindingNames()
         {

+ 0 - 2
Jint/Runtime/EventLoop.cs

@@ -1,7 +1,5 @@
 using System;
 using System.Collections.Generic;
-using Jint.Native;
-using Jint.Native.Promise;
 
 namespace Jint.Runtime
 {

+ 0 - 1
Jint/Runtime/Interop/TypeReference.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Reflection;
 using Jint.Collections;
 using Jint.Native;

+ 2 - 3
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Concurrent;
 using System.Linq;
-using System.Reflection;
 using Esprima.Ast;
 using Jint.Extensions;
 using Jint.Native;
@@ -24,8 +23,8 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         private JintBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
         {
-            _left = Build(engine, expression.Left);
-            _right = Build(engine, expression.Right);
+            _left = Build(_engine, expression.Left);
+            _right = Build(_engine, expression.Right);
         }
 
         protected bool TryOperatorOverloading(string clrName, out object result)

+ 88 - 39
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -93,7 +93,7 @@ namespace Jint.Runtime.Interpreter.Expressions
         /// <summary>
         /// https://tc39.es/ecma262/#sec-getsuperconstructor
         /// </summary>
-        private ObjectInstance GetSuperConstructor(FunctionEnvironmentRecord thisEnvironment)
+        private static ObjectInstance GetSuperConstructor(FunctionEnvironmentRecord thisEnvironment)
         {
             var envRec = thisEnvironment;
             var activeFunction = envRec._functionObject;
@@ -101,72 +101,121 @@ namespace Jint.Runtime.Interpreter.Expressions
             return superConstructor;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-function-calls
+        /// </summary>
         private object Call()
         {
-            var callee = _calleeExpression.Evaluate();
-            var expression = (CallExpression) _expression;
-
-            // todo: implement as in http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4
-
-            var arguments = ArgumentListEvaluation();
+            var reference = _calleeExpression.Evaluate();
 
-            var func = _engine.GetValue(callee, false);
-            var r = callee as Reference;
-
-            if (func._type == InternalTypes.Undefined)
+            if (ReferenceEquals(reference, Undefined.Instance))
             {
-                ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'");
+                return Undefined.Instance;
             }
+            
+            var func = _engine.GetValue(reference, false);
 
-            if (!func.IsObject())
+            if (reference is Reference referenceRecord 
+                && !referenceRecord.IsPropertyReference()
+                && referenceRecord.GetReferencedName() == CommonProperties.Eval
+                && func is EvalFunctionInstance eval)
             {
-                if (!_engine._referenceResolver.TryGetCallable(_engine, callee, out func))
+                var argList = ArgumentListEvaluation();
+                if (argList.Length == 0)
                 {
-                    ExceptionHelper.ThrowTypeError(_engine,
-                        r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function");
+                    return Undefined.Instance;
                 }
-            }
 
-            if (!(func is ICallable callable))
-            {
-                var message = $"{r?.GetReferencedName() ?? ""} is not a function";
-                return ExceptionHelper.ThrowTypeError<object>(_engine, message);
+                var evalArg = argList[0];
+                var strictCaller = StrictModeScope.IsStrictModeCode;
+                // TODO Let evalRealm be the current Realm Record.
+                var evalRealm = (object) null;
+                var direct = !((CallExpression) _expression).Optional;
+                var value = eval.PerformEval(evalArg, evalRealm, strictCaller, direct);
+                _engine._referencePool.Return(referenceRecord);
+                return value;
             }
 
-            var thisObject = Undefined.Instance;
-            if (r != null)
+            var thisCall = (CallExpression) _expression;
+            var tailCall = IsInTailPosition(thisCall);
+            return EvaluateCall(func, reference, thisCall.Arguments, tailCall);
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-evaluatecall
+        /// </summary>
+        private object EvaluateCall(JsValue func, object reference, in NodeList<Expression> arguments, bool tailPosition)
+        {
+            JsValue thisValue;
+            var referenceRecord = reference as Reference;
+            if (referenceRecord is not null)
             {
-                var baseValue = r.GetBase();
-                if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0)
+                if (referenceRecord.IsPropertyReference())
                 {
-                    thisObject = r.GetThisValue();
+                    thisValue = referenceRecord.GetThisValue();
                 }
                 else
                 {
-                    var env = (EnvironmentRecord) baseValue;
-                    thisObject = env.ImplicitThisValue();
+                    var baseValue = referenceRecord.GetBase();
+                    
+                    // deviation from the spec to support null-propagation helper
+                    if (baseValue.IsNullOrUndefined() 
+                        && _engine._referenceResolver.TryUnresolvableReference(_engine, referenceRecord, out var value))
+                    {
+                        return value;
+                    }
+                    
+                    var refEnv = (EnvironmentRecord) baseValue;
+                    thisValue = refEnv.WithBaseObject();
                 }
+            }
+            else
+            {
+                thisValue = Undefined.Instance;
+            }
+            
+            var argList = ArgumentListEvaluation();
 
-                // is it a direct call to eval ? http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.1.1
-                if (r.GetReferencedName() == CommonProperties.Eval && callable is EvalFunctionInstance instance)
-                {
-                    var value = instance.PerformEval(arguments, true);
-                    _engine._referencePool.Return(r);
-                    return value;
-                }
+            if (!func.IsObject() && !_engine._referenceResolver.TryGetCallable(_engine, reference, out func))
+            {
+                var message = referenceRecord == null 
+                    ? reference + " is not a function" 
+                    : $"Property '{referenceRecord.GetReferencedName()}' of object is not a function";
+                ExceptionHelper.ThrowTypeError(_engine, message);
+            }
+
+            if (func is not ICallable callable)
+            {
+                var message = $"{referenceRecord?.GetReferencedName() ?? reference} is not a function";
+                return ExceptionHelper.ThrowTypeError<object>(_engine, message);
             }
 
-            var result = _engine.Call(callable, thisObject, arguments, _calleeExpression);
+            if (tailPosition)
+            {
+                // TODO tail call
+                // PrepareForTailCall();
+            }
 
-            if (!_cached && arguments.Length > 0)
+            var result = _engine.Call(callable, thisValue, argList, _calleeExpression);
+
+            if (!_cached && argList.Length > 0)
             {
-                _engine._jsValueArrayPool.ReturnArray(arguments);
+                _engine._jsValueArrayPool.ReturnArray(argList);
             }
 
-            _engine._referencePool.Return(r);
+            _engine._referencePool.Return(referenceRecord);
             return result;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-isintailposition
+        /// </summary>
+        private static bool IsInTailPosition(CallExpression call)
+        {
+            // TODO tail calls
+            return false;
+        }
+
         private JsValue[] ArgumentListEvaluation()
         {
             var cachedArguments = _cachedArguments;

+ 4 - 1
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -122,6 +122,9 @@ namespace Jint.Runtime.Interpreter.Expressions
                 Nodes.ClassExpression => new JintClassExpression(engine, (ClassExpression) expression),
                 Nodes.Super => new JintSuperExpression(engine, (Super) expression),
                 Nodes.MetaProperty => new JintMetaPropertyExpression(engine, (MetaProperty) expression),
+                Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression
+                    ? new JintCallExpression(engine, (CallExpression) ((ChainExpression) expression).Expression)
+                    : new JintMemberExpression(engine, (MemberExpression) ((ChainExpression) expression).Expression),
                 _ => ExceptionHelper.ThrowArgumentOutOfRangeException<JintExpression>(nameof(expression), $"unsupported expression type '{expression.Type}'")
             };
         }
@@ -362,7 +365,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 {
                     jse.GetValueAndCheckIterator(out var objectInstance, out var iterator);
                     // optimize for array unless someone has touched the iterator
-                    if (objectInstance is ArrayInstance ai 
+                    if (objectInstance is ArrayInstance ai
                         && ReferenceEquals(ai.Get(GlobalSymbolRegistry.Iterator), _engine.Array.PrototypeObject._originalIteratorFunction))
                     {
                         var length = ai.GetLength();

+ 10 - 4
Jint/Runtime/Interpreter/Expressions/JintLogicalAndExpression.cs

@@ -5,13 +5,19 @@ namespace Jint.Runtime.Interpreter.Expressions
 {
     internal sealed class JintLogicalAndExpression : JintExpression
     {
-        private readonly JintExpression _left;
-        private readonly JintExpression _right;
+        private JintExpression _left;
+        private JintExpression _right;
 
         public JintLogicalAndExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
         {
-            _left = Build(engine, expression.Left);
-            _right = Build(engine, expression.Right);
+            _initialized = false;
+        }
+
+        protected override void Initialize()
+        {
+            var expression = (BinaryExpression) _expression;
+            _left = Build(_engine, expression.Left);
+            _right = Build(_engine, expression.Right);
         }
 
         protected override object EvaluateInternal()

+ 9 - 0
Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs

@@ -74,6 +74,10 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 // fast checks failed
                 var baseReference = _objectExpression.Evaluate();
+                if (ReferenceEquals(Undefined.Instance, baseReference))
+                {
+                    return Undefined.Instance;
+                }
                 if (baseReference is Reference reference)
                 {
                     baseReferenceName = reference.GetReferencedName().ToString();
@@ -86,6 +90,11 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
+            if (baseValue.IsNullOrUndefined() && (_memberExpression.Optional || _objectExpression._expression.IsOptional()))
+            {
+                return Undefined.Instance;
+            }
+
             var property = _determinedProperty ?? _propertyExpression.GetValue();
             if (baseValue.IsNullOrUndefined())
             {

+ 0 - 1
Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs

@@ -6,7 +6,6 @@ using Jint.Runtime.Interop;
 using Jint.Runtime.References;
 using System.Collections.Concurrent;
 using System.Linq;
-using System.Reflection;
 
 namespace Jint.Runtime.Interpreter.Expressions
 {

+ 0 - 1
Jint/Runtime/TypeConverter.cs

@@ -2,7 +2,6 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Reflection;
 using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Extensions;