0acdf36510
This change adds support for `const` redeclaration on REPL mode with the semantincs recommended in the design doc: 1) REPL scripts should not be able to reassign bindings to `const` variables. 2) Re-declaring `const` variables of page scripts is not allowed in REPL scripts. 3) Re-declearing `const` variables is not allowed in the same REPL script. 4) `const` re-declaration is allowed across separate REPL scripts. 5) Old references to previously declared variables get updated with the new value, even those references from within optimized functions. Design doc: https://goo.gle/devtools-const-repl Bug: chromium:1076427 Change-Id: Ic73d2ae7fcfbfc1f5b58f61e0c3c69e9c4d85d77 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2865721 Reviewed-by: Toon Verwaest <verwaest@chromium.org> Reviewed-by: Mythri Alle <mythria@chromium.org> Commit-Queue: Luis Fernando Pardo Sixtos <lpardosixtos@microsoft.com> Cr-Commit-Position: refs/heads/master@{#74510}
417 lines
14 KiB
JavaScript
417 lines
14 KiB
JavaScript
// 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;");
|
|
|
|
// Declare const and get value
|
|
result = await evaluate("const c = 7;");
|
|
result = await evaluate("c;");
|
|
assertEquals(7, result);
|
|
|
|
// Re-declare in the same script after declaration in another script.
|
|
assertThrows(() => evaluate("let c = 8; let c = 9;"));
|
|
result = await evaluate("c;");
|
|
assertEquals(7, result);
|
|
|
|
// Re-declare const as const
|
|
result = await evaluate("const c = 8;");
|
|
result = await evaluate("c;");
|
|
assertEquals(8, result);
|
|
|
|
// Assign to const
|
|
assertThrowsAsync(evaluate("c = 11;"),
|
|
TypeError, "Assignment to constant variable.");
|
|
result = await evaluate("c;");
|
|
assertEquals(8, result);
|
|
|
|
await evaluate("const c = 8;");
|
|
|
|
// Close over const. Inner function is only pre-parsed.
|
|
result = await evaluate("function getter() { return c; }");
|
|
assertEquals(undefined, result);
|
|
result = await evaluate("getter();");
|
|
assertEquals(8, result);
|
|
// Modifies the original c; does not create a new one/shadow.
|
|
result = await evaluate("const c = 10;");
|
|
assertEquals(undefined, result);
|
|
result = await evaluate("c;");
|
|
assertEquals(10, result);
|
|
result = await evaluate("getter();");
|
|
assertEquals(10, result);
|
|
|
|
await evaluate("const c = 10");
|
|
|
|
// Check store from an inner scope throws error.
|
|
assertThrowsAsync(evaluate("{ let z; c = 11; };"),
|
|
TypeError, "Assignment to constant variable.");
|
|
result = await evaluate("c;");
|
|
assertEquals(10, result);
|
|
|
|
// Check re-declare from an inner scope does nothing.
|
|
result = await evaluate("{ let z; const c = 12; } c;");
|
|
assertEquals(10, result);
|
|
|
|
assertThrowsAsync(evaluate("{ const qq = 10; } qq;"),
|
|
ReferenceError, "qq is not defined");
|
|
|
|
// Const vs. const in same script.
|
|
assertThrows(() => result = evaluate("const d = 9; const d = 10;"),
|
|
SyntaxError, "Identifier 'd' has already been declared");
|
|
|
|
// Check TDZ; const use before initialization.
|
|
assertThrowsAsync(evaluate("e; const e = 7;"), ReferenceError);
|
|
assertThrowsAsync(evaluate("e;"), ReferenceError);
|
|
|
|
result = await evaluate("const e = 8;");
|
|
assertEquals(undefined, result);
|
|
result = await evaluate("e;")
|
|
assertEquals(8, result);
|
|
|
|
// f is marked as constant in TDZ
|
|
assertThrowsAsync(evaluate("f = 10; const f = 7;"),
|
|
TypeError, ("Assignment to constant variable."));
|
|
result = await evaluate("const f = 11;");
|
|
assertEquals(undefined, result);
|
|
// We fixed 'f'!
|
|
result = await evaluate("f;");
|
|
assertEquals(11, result);
|
|
|
|
// 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 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);
|
|
|
|
// Non-configurable properties of the global object (also created by plain old
|
|
// top-level var declarations) cannot be re-declared as const.
|
|
result = await evaluate(`Object.defineProperty(globalThis, 'k12', {
|
|
value: 1,
|
|
configurable: false
|
|
});`);
|
|
result = await evaluate("k12;");
|
|
assertEquals(1, result);
|
|
assertThrows(() => result = evaluate("const k12 = 2;"),
|
|
SyntaxError, "Identifier 'k12' has already been declared");
|
|
result = await evaluate("k12;");
|
|
assertEquals(1, result);
|
|
|
|
// ... Except if you do it in the same script.
|
|
result = await evaluate(`Object.defineProperty(globalThis, 'k13', {
|
|
value: 1,
|
|
configurable: false
|
|
});
|
|
const k13 = 2;`);
|
|
result = await evaluate("k13;");
|
|
assertEquals(2, result);
|
|
result = await evaluate("globalThis.k13;");
|
|
assertEquals(1, result);
|
|
|
|
// But if the property is configurable then both versions are allowed.
|
|
result = await evaluate(`Object.defineProperty(globalThis, 'k14', {
|
|
value: 1,
|
|
configurable: true
|
|
});`);
|
|
result = await evaluate("k14;");
|
|
assertEquals(1, result);
|
|
result = await evaluate("const k14 = 2;");
|
|
result = await evaluate("k14;");
|
|
assertEquals(2, result);
|
|
result = await evaluate("globalThis.k14;");
|
|
assertEquals(1, result);
|
|
|
|
result = await evaluate(`Object.defineProperty(globalThis, 'k15', {
|
|
value: 1,
|
|
configurable: true
|
|
});
|
|
const k15 = 2;`);
|
|
result = await evaluate("k15;");
|
|
assertEquals(2, result);
|
|
result = await evaluate("globalThis.k15;");
|
|
assertEquals(1, 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());
|
|
|
|
// Check that binding and re-declaring a function via const works.
|
|
result = evaluate("const fn3 = function() { return 21; }");
|
|
assertEquals(21, fn3());
|
|
result = evaluate("const fn3 = function() { return 42; }");
|
|
assertEquals(42, fn3());
|
|
|
|
// Check that lazily parsed functions that bind a REPL-const variable work.
|
|
evaluate("const l11 = 21;");
|
|
evaluate("const l11 = 42; function fn4() { return l11; }");
|
|
evaluate("const l11 = 'foo1';");
|
|
assertEquals("foo1", fn4());
|
|
|
|
})().catch(e => {
|
|
print(e);
|
|
print(e.stack);
|
|
%AbortJS("Async test is failing");
|
|
});
|