| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| Debug = debug.Debug |
| |
| const evaluate = Debug.evaluateGlobalREPL; |
| const evaluateNonREPL = (source) => Debug.evaluateGlobal(source, false).value(); |
| |
| (async () => { |
| |
| // Declare let and get value |
| let result; |
| result = await evaluate("let x = 7;"); |
| result = await evaluate("x;"); |
| assertEquals(7, result); |
| |
| // Re-declare in the same script after declaration in another script. |
| assertThrows(() => evaluate("let x = 8; let x = 9;")); |
| result = await evaluate("x;"); |
| assertEquals(7, result); |
| |
| // Re-declare let as let |
| assertDoesNotThrow(async () => result = await evaluate("let x = 8;")); |
| result = await evaluate("x;"); |
| assertEquals(8, result); |
| |
| await evaluate("let x = 8;"); |
| |
| // Close over let. Inner function is only pre-parsed. |
| result = await evaluate("function getter() { return x; }"); |
| assertEquals(undefined, result); |
| result = await evaluate("getter();"); |
| assertEquals(8, result); |
| result = await evaluate("x = 9;"); |
| assertEquals(9, result); |
| result = await evaluate("x;"); |
| assertEquals(9, result); |
| result = await evaluate("getter();"); |
| assertEquals(9, result); |
| // Modifies the original x; does not create a new one/shadow. |
| result = await evaluate("let x = 10;"); |
| assertEquals(undefined, result); |
| result = await evaluate("x;"); |
| assertEquals(10, result); |
| result = await evaluate("getter();"); |
| assertEquals(10, result); |
| |
| await evaluate("let x = 10"); |
| |
| // Check store from an inner scope. |
| result = await evaluate("{ let z; x = 11; } x;"); |
| assertEquals(11, result); |
| |
| // Check re-declare from an inner scope does nothing. |
| result = await evaluate("{ let z; let x = 12; } x;"); |
| assertEquals(11, result); |
| |
| assertThrowsAsync(evaluate("{ let qq = 10; } qq;"), |
| ReferenceError, "qq is not defined"); |
| |
| // Re-declare in the same script (no previous declaration). |
| assertThrows(() => result = evaluate("let y = 7; let y = 8;"), |
| SyntaxError, "Identifier 'y' has already been declared"); |
| |
| // Check TDZ; use before initialization. |
| // Do not check exact error message, it depends on the path taken through |
| // the IC machinery and changes sometimes, causing the test to be flaky. |
| assertThrowsAsync(evaluate("a; let a = 7;"), ReferenceError); |
| assertThrowsAsync(evaluate("a;"), ReferenceError); |
| // This is different to non-REPL mode, which throws the kUndefined error here. |
| assertThrowsAsync(evaluate("a = 7;"), |
| ReferenceError, "Cannot access 'a' before initialization"); |
| |
| result = await evaluate("let a = 8;"); |
| assertEquals(undefined, result); |
| result = await evaluate("a;") |
| assertEquals(8, result); |
| |
| // Check TDZ; store before initialization. |
| assertThrowsAsync(evaluate("b = 10; let b;"), |
| ReferenceError, "Cannot access 'b' before initialization"); |
| // Check that b is still broken. |
| assertThrowsAsync(evaluate("b = 10; let b;"), |
| ReferenceError, "Cannot access 'b' before initialization"); |
| // Check that b is still broken when the let defines a value. |
| assertThrowsAsync(evaluate("b = 10; let b = 7;"), |
| ReferenceError, "Cannot access 'b' before initialization"); |
| result = await evaluate("let b = 11;"); |
| assertEquals(undefined, result); |
| // We fixed 'b'! |
| result = await evaluate("b;"); |
| assertEquals(11, result); |
| |
| // Check that class works the same. Internally there is no difference between |
| // class and let so we don't test as extensively as for let. |
| result = evaluate("class K {};"); |
| assertDoesNotThrow(() => result = evaluate("class K {};")); |
| |
| // many tests for normal/repl script interactions. |
| |
| // tests with let x = await |
| |
| // result = evaluate("toString;"); |
| |
| // Re-declare let as const |
| evaluate("let z = 10;"); |
| assertThrows(() => result = evaluate("const z = 9;"), |
| SyntaxError, "Identifier 'z' has already been declared"); |
| result = await evaluate("z;"); |
| assertEquals(10, result); |
| |
| // Re-declare const as const |
| result = await evaluate("const c = 10;"); |
| assertThrows(() => result = evaluate("const c = 11;"), |
| SyntaxError, "Identifier 'c' has already been declared"); |
| result = await evaluate("c;"); |
| assertEquals(10, result); |
| |
| // Const vs. const in same script. |
| assertThrows(() => result = evaluate("const d = 9; const d = 10;"), |
| SyntaxError, "Identifier 'd' has already been declared"); |
| |
| // Close over const |
| result = await evaluate("const e = 10; function closure() { return e; }"); |
| result = await evaluate("e;"); |
| assertEquals(10, result); |
| |
| // Assign to const |
| assertThrowsAsync(evaluate("e = 11;"), |
| TypeError, "Assignment to constant variable."); |
| result = await evaluate("e;"); |
| assertEquals(10, result); |
| result = await evaluate("closure();"); |
| assertEquals(10, result); |
| |
| // Assign to const in TDZ |
| assertThrowsAsync(evaluate("f; const f = 11;"), |
| ReferenceError, "Cannot access 'f' before initialization"); |
| assertThrowsAsync(evaluate("f = 12;"), |
| TypeError, "Assignment to constant variable."); |
| |
| // Re-declare const as let |
| result = await evaluate("const g = 12;"); |
| assertThrows(() => result = evaluate("let g = 13;"), |
| SyntaxError, "Identifier 'g' has already been declared"); |
| result = await evaluate("g;"); |
| assertEquals(12, result); |
| |
| // Let vs. const in the same script |
| assertThrows(() => result = evaluate("let h = 13; const h = 14;"), |
| SyntaxError, "Identifier 'h' has already been declared"); |
| assertThrows(() => result = evaluate("const i = 13; let i = 14;"), |
| SyntaxError, "Identifier 'i' has already been declared"); |
| |
| // Configurable properties of the global object can be re-declared as let. |
| result = await evaluate(`Object.defineProperty(globalThis, 'j', { |
| value: 1, |
| configurable: true |
| });`); |
| result = await evaluate("j;"); |
| assertEquals(1, result); |
| result = await evaluate("let j = 2;"); |
| result = await evaluate("j;"); |
| assertEquals(2, result); |
| |
| // Non-configurable properties of the global object (also created by plain old |
| // top-level var declarations) cannot be re-declared as let. |
| result = await evaluate(`Object.defineProperty(globalThis, 'k', { |
| value: 1, |
| configurable: false |
| });`); |
| result = await evaluate("k;"); |
| assertEquals(1, result); |
| assertThrows(() => result = evaluate("let k = 2;"), |
| SyntaxError, "Identifier 'k' has already been declared"); |
| result = await evaluate("k;"); |
| assertEquals(1, result); |
| |
| // ... Except if you do it in the same script. |
| result = await evaluate(`Object.defineProperty(globalThis, 'k2', { |
| value: 1, |
| configurable: false |
| }); |
| let k2 = 2;`); |
| result = await evaluate("k2;"); |
| assertEquals(2, result); |
| result = await evaluate("globalThis.k2;"); |
| assertEquals(1, result); |
| |
| // But if the property is configurable then both versions are allowed. |
| result = await evaluate(`Object.defineProperty(globalThis, 'k3', { |
| value: 1, |
| configurable: true |
| });`); |
| result = await evaluate("k3;"); |
| assertEquals(1, result); |
| result = await evaluate("let k3 = 2;"); |
| result = await evaluate("k3;"); |
| assertEquals(2, result); |
| result = await evaluate("globalThis.k3;"); |
| assertEquals(1, result); |
| |
| result = await evaluate(`Object.defineProperty(globalThis, 'k4', { |
| value: 1, |
| configurable: true |
| }); |
| let k4 = 2;`); |
| result = await evaluate("k4;"); |
| assertEquals(2, result); |
| result = await evaluate("globalThis.k4;"); |
| assertEquals(1, result); |
| |
| // Check var followed by let in the same script. |
| assertThrows(() => result = evaluate("var k5 = 1; let k5 = 2;"), |
| SyntaxError, "Identifier 'k5' has already been declared"); |
| |
| // In different scripts. |
| result = await evaluate("var k6 = 1;"); |
| assertThrows(() => result = evaluate("let k6 = 2;"), |
| SyntaxError, "Identifier 'k6' has already been declared"); |
| |
| // Check let followed by var in the same script. |
| assertThrows(() => result = evaluate("let k7 = 1; var k7 = 2;"), |
| SyntaxError, "Identifier 'k7' has already been declared"); |
| |
| // In different scripts. |
| result = evaluate("let k8 = 1;"); |
| assertThrows(() => result = evaluate("var k8 = 2;"), |
| SyntaxError, "Identifier 'k8' has already been declared"); |
| |
| // Check var followed by var in the same script. |
| result = await evaluate("var k9 = 1; var k9 = 2;"); |
| result = await evaluate("k9;"); |
| assertEquals(2, result); |
| |
| // In different scripts. |
| result = await evaluate("var k10 = 1;"); |
| result = await evaluate("var k10 = 2;"); |
| result = await evaluate("k10;"); |
| assertEquals(2, result); |
| result = await evaluate("globalThis.k10;"); |
| assertEquals(2, result); |
| |
| // typeof should not throw for undeclared variables |
| result = await evaluate("typeof k11"); |
| assertEquals("undefined", result); |
| |
| // Test lets with names on the object prototype e.g. toString to make sure |
| // it only works for own properties. |
| // result = evaluate("let valueOf;"); |
| |
| // REPL vs. non-REPL scripts |
| |
| // We can still read let values cross-mode. |
| result = evaluateNonREPL("let l1 = 1; let l2 = 2; let l3 = 3;"); |
| result = await evaluate("l1;"); |
| assertEquals(1, result); |
| |
| // But we can't re-declare page script lets in a REPL script. We might want to |
| // later. |
| assertThrows(() => result = evaluate("let l1 = 2;"), |
| SyntaxError, "Identifier 'l1' has already been declared"); |
| |
| assertThrows(() => result = evaluate("var l2 = 3;"), |
| SyntaxError, "Identifier 'l2' has already been declared"); |
| |
| assertThrows(() => result = evaluate("const l3 = 4;"), |
| SyntaxError, "Identifier 'l3' has already been declared"); |
| |
| // Re-declaring var across modes works. |
| result = evaluateNonREPL("var l4 = 1; const l5 = 1;"); |
| result = await evaluate("var l4 = 2;"); |
| result = await evaluate("l4;"); |
| assertEquals(2, result); |
| |
| // Const doesn't. |
| assertThrows(() => result = evaluate("const l5 = 2;"), |
| SyntaxError, "Identifier 'l5' has already been declared") ; |
| result = await evaluate("l5;"); |
| assertEquals(1, result); |
| |
| // Now REPL followed by non-REPL |
| result = await evaluate("let l6 = 1; const l7 = 2; let l8 = 3;"); |
| result = evaluateNonREPL("l7;"); |
| assertEquals(2, result); |
| result = evaluateNonREPL("l6;"); |
| assertEquals(1, result); |
| |
| // Check that the pattern of `l9; let l9;` does not throw for REPL scripts. |
| // If REPL scripts behaved the same as normal scripts, this case would |
| // re-introduce the hole in 'l9's script context slot, causing the IC and feedback |
| // to 'lie' about the current state. |
| result = await evaluate("let l9;"); |
| result = await evaluate("l9; let l9;"), |
| assertEquals(undefined, await evaluate('l9;')); |
| |
| // Check that binding and re-declaring a function via let works. |
| result = evaluate("let fn1 = function() { return 21; }"); |
| assertEquals(21, fn1()); |
| result = evaluate("let fn1 = function() { return 42; }"); |
| assertEquals(42, fn1()); |
| |
| // Check that lazily parsed functions that bind a REPL-let variable work. |
| evaluate("let l10 = 21;"); |
| evaluate("let l10 = 42; function fn2() { return l10; }"); |
| evaluate("let l10 = 'foo';"); |
| assertEquals("foo", fn2()); |
| |
| })().catch(e => { |
| print(e); |
| print(e.stack); |
| %AbortJS("Async test is failing"); |
| }); |