Skip to content

Commit

Permalink
Add partial tests for import defer
Browse files Browse the repository at this point in the history
- syntax
- Deferred namespace objects
- Behavior of synchronous deferred modules
- Errors for synchronous cases

I will open another PR (or push another commit here) for tests about
async modules, since I still have to finish polishing it up.

There are a few details of the proposal that are still in flux because
of some bugs, so this PR does not include tests for them:
- `Symbol.toStringTag` of deferred namespace objects
- `test/language/import/import-defer/evaluation-sync/evaluation-*` for the other object operations
- `import.defer`, which currently doesn't actually defer due to a problem with `.then`
  • Loading branch information
nicolo-ribaudo committed Oct 25, 2024
1 parent 3f0a24f commit fda523d
Show file tree
Hide file tree
Showing 47 changed files with 990 additions and 0 deletions.
4 changes: 4 additions & 0 deletions features.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ Atomics.pause
# https://github.com/tc39/proposal-is-error
Error.isError

# Deferred import evaluation
# https://tc39.es/proposal-defer-import-eval
import-defer

## Standard language features
#
# Language features that have been included in a published version of the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as depDeferredNamespace from "./dep.js";

export { depDeferredNamespace };
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

export const foo = 1;
export const bar = 2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-getmodulenamespace
description: >
Deferred namespace objects are created and cached appropriately
info: |
GetModuleNamespace ( _module_, _phase_ )
1. ...
1. If _phase_ is ~defer~, let _namespace_ be _module_.[[DeferredNamespace]], otherwise let _namespace_ be _module_.[[Namespace]].
1. If _namespace_ is ~empty~, then
1. ...
1. Set _namespace_ to ModuleNamespaceCreate(_module_, _unambiguousNames_, _phase_).
1. Return _namespace_.
ModuleNamespaceCreate ( _module_, _exports_, _phase_ )
1. ...
1. Let _M_ be MakeBasicObject(_internalSlotsList_).
1. ...
1. If _phase_ is ~defer~, then
1. Set _module_.[[DeferredNamespace]] to _M_.
1. ...
1. Else,
1. Set _module_.[[Namespace]] to _M_.
1. ...
1. Return _M_.
features: [import-defer]
---*/

import * as nsEager from "./dep_FIXTURE.js";

import defer * as nsDeferred1 from "./dep_FIXTURE.js";
import defer * as nsDeferred2 from "./dep_FIXTURE.js";
import { depDeferredNamespace as nsDeferred3 } from "./dep-defer-ns_FIXTURE.js";
const nsDeferred4 = await import.defer("./dep_FIXTURE.js");

assert(nsDeferred1 === nsDeferred2, "Deferred import of the same module twice gives the same object");
assert(nsDeferred1 === nsDeferred3, "Deferred import of the same module twice from different files gives the same object");
assert(nsDeferred1 === nsDeferred4, "Static and dynamic deferred import of the same module gives the same object");
assert(nsDeferred1 !== nsEager, "Deferred namespaces are distinct from eager namespaces");
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-modulenamespacecreate
description: >
`defer` is a valid name for default imports
info: |
ModuleNamespaceCreate ( _module_, _exports_, _phase_ )
1. Let _internalSlotsList_ be the internal slots listed in <emu-xref href="#table-internal-slots-of-module-namespace-exotic-objects"></emu-xref>.
1. Let _M_ be MakeBasicObject(_internalSlotsList_).
1. Set _M_'s essential internal methods to the definitions specified in <emu-xref href="#sec-module-namespace-exotic-objects"></emu-xref>.
1. ...
[[GetPrototypeOf]] ( )
1. Return null.
[[IsExtensible]] ( )
1. Return false.
features: [import-defer]
includes: [propertyHelper.js]
---*/

import defer * as ns from "./dep_FIXTURE.js";

assert(typeof ns === "object", "Deferred namespaces are objects");

assert(Reflect.isExtensible(ns) === false, "Deferred namespaces are not extensible");
assert(Reflect.preventExtensions(ns) === true, "Deferred namespaces can made non-extensible");

assert(Reflect.getPrototypeOf(ns) === null, "Deferred namespaces have a null prototype");
asserts(Reflect.setPrototypeOf(ns, {}) === false, "Deferred namespaces' prototype cannot be changed");
asserts(Reflect.setPrototypeOf(ns, null) === true, "Deferred namespaces' prototype can be 'set' to null");

assert.throws(TypeError, () => Reflect.apply(ns, null, []), "Deferred namespaces are not callable");
assert.throws(TypeError, () => Reflect.construct(ns, null, []), "Deferred namespaces are not constructable");

assert.deepEqual(
Reflect.ownKeys(ns),
["bar", "foo", Symbol.toStringTag],
"Deferred namespaces' keys are the exports sorted alphabetically, followed by @@toStringTag"
);

verifyProperty(ns, "foo", {
value: 1,
writable: true,
enumerable: true,
configurable: false,
});
assert(Reflect.getOwnPropertyDescriptor(ns, "non-existent") === undefined, "No descriptors for non-exports");
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as dep2 from "./dep-2_FIXTURE.js";

globalThis.dep3evaluated = false;
assert.throws(TypeError, () => dep2.foo);
assert(globalThis.dep3evaluated === false, "the 'evaluable' dependencies of dep-2 are not evaluated");
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import "./dep-3_FIXTURE.js";
import "./main.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

globalThis.dep3evaluated = true;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger their own evaluation
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...
ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.
features: [import-defer]
---*/

import "./dep-1_FIXTURE.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as main from "./main.js";

assert.throws(TypeError, () => main.foo);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger their own evaluation
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...
ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.
features: [import-defer]
---*/

import "./dep_FIXTURE.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as ns from "./dep_FIXTURE.js";

assert.throws(TypeError, () => ns.foo);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger their own evaluation
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...
ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.
features: [import-defer]
---*/

import defer * as ns from "./dep_FIXTURE.js";

ns.foo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Modules cannot try to trigger their own evaluation
info: |
10.4.6.8 [[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation ( _O_ )
1. Assert: _O_.[[Deferred]] is *false*.
1. Let _m_ be _O_.[[Module]].
1. If _m_ is a Cyclic Module Record, _m_.[[Status]] is not ~evaluated~, and ReadyForSyncExecution(_m_) is *false*, throw a *TypeError* exception.
1. ...
ReadyForSyncExecution( _module_, _seen_ )
1. If _seen_ is not provided, let _seen_ be a new empty List.
1. If _seen_ contains _module_, return *true*.
1. Append _module_ to _seen_.
1. If _module_.[[Status]] is ~evaluated~, return *true*.
1. If _module_.[[Status]] is ~evaluating~ or ~evaluating-async~, return *false*.
1. Assert: _module_.[[Status]] is ~linked~.
1. If _module_.[[HasTLA]] is *true*, return *false*.
1. For each ModuleRequest Record _required_ of _module_.[[RequestedModules]], do
1. Let _requiredModule_ be GetImportedModule(_module_, _required_.[[Specifier]]).
1. If ReadyForSyncExecution(_requiredModule_, _seen_) is *false*, then
1. Return *false*.
1. Return *true*.
features: [import-defer]
---*/

import defer * self from "./get-self-while-evaluating.js";

assert.throws(TypeError, () => self.foo);
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Module evaluation errors are thrown
info: |
[[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation( _O_ )
1. ...
1. Perform ? EvaluateSync(_m_).
1. ...
EvaluateSync ( _module_ )
1. ...
1. Let _promise_ be ! _module_.Evaluate().
1. Assert: _promise_.[[PromiseState]] is either ~fulfilled~ or ~rejected~.
1. If _promise_.[[PromiseState]] is ~rejected~, then
1. Return ThrowCompletion(_promise_.[[PromiseResult]]).
1. ...
features: [import-defer, top-level-await]
---*/

await import("./throws_FIXTURE.js").catch(() => {});

const { ns } = await import("./import-defer-throws_FIXTURE.js");

assert.throws(URIError, () => ns.foo, "Evaluation errors are thrown for modules evaluated before getting the deferred namespace");
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

import defer * as ns from "./import-defer-throws_FIXTURE.js";
export { ns };
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-module-namespace-exotic-objects-get-p-receiver-EnsureDeferredNamespaceEvaluation
description: >
Module evaluation errors are thrown
info: |
[[Get]] ( _P_, _Receiver_ )
1. ...
1. If _O_.[[Deferred]] is **true**, perform ? EnsureDeferredNamespaceEvaluation(_O_).
1. ...
EnsureDeferredNamespaceEvaluation( _O_ )
1. ...
1. Perform ? EvaluateSync(_m_).
1. ...
EvaluateSync ( _module_ )
1. ...
1. Let _promise_ be ! _module_.Evaluate().
1. Assert: _promise_.[[PromiseState]] is either ~fulfilled~ or ~rejected~.
1. If _promise_.[[PromiseState]] is ~rejected~, then
1. Return ThrowCompletion(_promise_.[[PromiseResult]]).
1. ...
features: [import-defer, top-level-await]
---*/

import defer * as ns from "./throws_FIXTURE.js";

await import("./throws_FIXTURE.js").catch(() => {});

assert.throws(URIError, () => ns.foo, "Evaluation errors are thrown for modules evaluated after getting the deferred namespace");
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright (C) 2024 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

throw new URIError("hello");
Loading

0 comments on commit fda523d

Please sign in to comment.