From 2d20bd83a41672c6cf72c27ff1c099968ab3b9ca Mon Sep 17 00:00:00 2001 From: Caio Lima Date: Mon, 27 Apr 2026 11:57:22 -0300 Subject: [PATCH] [export-defer] Add interaction tests for `import defer * as ns` consumer Verifies the two layers of laziness when an `import defer * as ns` consumer points at an `export defer` barrel: any interaction with the namespace triggers barrel evaluation, while only [[Get]] of a deferred-reexported binding triggers deferred-dep evaluation. --- .../import-defer-consumer/barrel_FIXTURE.js | 7 +++ .../caching-on-second-deferred-get.js | 60 +++++++++++++++++++ .../import-defer-consumer/dep_FIXTURE.js | 6 ++ .../import-defer-consumer/setup_FIXTURE.js | 4 ++ .../trigger-barrel-and-dep-on-deferred-get.js | 31 ++++++++++ .../trigger-barrel-only-on-direct-get.js | 34 +++++++++++ .../trigger-barrel-only-on-hasProperty.js | 34 +++++++++++ 7 files changed, 176 insertions(+) create mode 100644 test/language/export/export-defer/import-defer-consumer/barrel_FIXTURE.js create mode 100644 test/language/export/export-defer/import-defer-consumer/caching-on-second-deferred-get.js create mode 100644 test/language/export/export-defer/import-defer-consumer/dep_FIXTURE.js create mode 100644 test/language/export/export-defer/import-defer-consumer/setup_FIXTURE.js create mode 100644 test/language/export/export-defer/import-defer-consumer/trigger-barrel-and-dep-on-deferred-get.js create mode 100644 test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-direct-get.js create mode 100644 test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-hasProperty.js diff --git a/test/language/export/export-defer/import-defer-consumer/barrel_FIXTURE.js b/test/language/export/export-defer/import-defer-consumer/barrel_FIXTURE.js new file mode 100644 index 00000000000..520f59567a2 --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/barrel_FIXTURE.js @@ -0,0 +1,7 @@ +// Copyright (C) 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +globalThis.evaluations.push("barrel"); + +export let direct = 1; +export defer { exported } from "./dep_FIXTURE.js"; diff --git a/test/language/export/export-defer/import-defer-consumer/caching-on-second-deferred-get.js b/test/language/export/export-defer/import-defer-consumer/caching-on-second-deferred-get.js new file mode 100644 index 00000000000..f84873cd6a1 --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/caching-on-second-deferred-get.js @@ -0,0 +1,60 @@ +// Copyright (C) 2026 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 +description: > + Repeated ns.x access does not re-evaluate barrel or deferred dep +info: | + [[Get]] ( _P_, _Receiver_ ) + 1. ... + 1. Let _m_ be _obj_.[[Module]]. + 1. If _m_ is a Cyclic Module Record and _m_.GetOptionalIndirectExportsModuleRequests(« _P_ ») + is not empty, then + 1. Perform ? EvaluateModuleSync(_m_, « _P_ »). + 1. Let _binding_ be _m_.ResolveExport(_P_). + 1. ... + 1. Let _targetEnv_ be _binding_.[[Module]].[[Environment]]. + 1. Return ? _targetEnv_.GetBindingValue(_binding_.[[BindingName]], *true*). + + EvaluateModuleSync ( _module_ [ , _importedNames_ ] ) + 1. If _importedNames_ is not present, set _importedNames_ to « ». + 1. If ReadyForSyncExecution(_module_, _importedNames_) is false, throw a TypeError exception. + 1. Let _promise_ be _module_.Evaluate(_importedNames_). + 1. Assert: _promise_.[[PromiseState]] is either ~fulfilled~ or ~rejected~. + 1. ... + + Evaluate ( _importedNames_ ) + 1. ... + 1. If _importedNames_ is not present, set _importedNames_ to « ». + 1. If _module_.[[Status]] is either ~evaluating-async~ or ~evaluated~, then + 1. Assert: _module_.[[CycleRoot]].[[TopLevelCapability]] is not ~empty~. + 1. Let _topLevelPromise_ be _module_.[[CycleRoot]].[[TopLevelCapability]].[[Promise]]. + 1. Else, + 1. ... + 1. ... + + Once a module reaches ~evaluated~, Evaluate takes the first branch + and reuses the existing TopLevelCapability promise rather than + re-running InnerModuleEvaluation. Repeated [[Get]] of the same + deferred-reexported name therefore re-enters EvaluateModuleSync but + never re-executes any module body; GetBindingValue returns the same + already-initialized value. +flags: [module] +features: [export-defer, import-defer] +includes: [compareArray.js] +---*/ + +import "./setup_FIXTURE.js"; +import defer * as ns from "./barrel_FIXTURE.js"; + +assert.compareArray(globalThis.evaluations, [], + "barrel and dep both deferred until first interaction with ns"); + +assert.sameValue(ns.exported, 3, "first read returns the binding's value"); +assert.compareArray(globalThis.evaluations, ["barrel", "dep"], + "first ns.exported triggers barrel then dep evaluation"); + +assert.sameValue(ns.exported, 3, "second read returns the same value"); +assert.compareArray(globalThis.evaluations, ["barrel", "dep"], + "second ns.exported does not re-evaluate either module"); diff --git a/test/language/export/export-defer/import-defer-consumer/dep_FIXTURE.js b/test/language/export/export-defer/import-defer-consumer/dep_FIXTURE.js new file mode 100644 index 00000000000..0be6376896b --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/dep_FIXTURE.js @@ -0,0 +1,6 @@ +// Copyright (C) 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +globalThis.evaluations.push("dep"); + +export let exported = 3; diff --git a/test/language/export/export-defer/import-defer-consumer/setup_FIXTURE.js b/test/language/export/export-defer/import-defer-consumer/setup_FIXTURE.js new file mode 100644 index 00000000000..c72aabeac21 --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/setup_FIXTURE.js @@ -0,0 +1,4 @@ +// Copyright (C) 2026 Igalia, S.L. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +globalThis.evaluations = []; diff --git a/test/language/export/export-defer/import-defer-consumer/trigger-barrel-and-dep-on-deferred-get.js b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-and-dep-on-deferred-get.js new file mode 100644 index 00000000000..b7eef2778ac --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-and-dep-on-deferred-get.js @@ -0,0 +1,31 @@ +// Copyright (C) 2026 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 +description: > + ns.x triggers both barrel and deferred-dep evaluation under `import defer` +info: | + [[Get]] ( _P_, _Receiver_ ) + 1. ... + 1. Let _m_ be _O_.[[Module]]. + 1. If _m_ is a Cyclic Module Record and _m_.GetOptionalIndirectExportsModuleRequests(« _P_ ») + is not empty, then + 1. Perform ? EvaluateModuleSync(_m_, « _P_ »). + 1. ... +flags: [module] +features: [export-defer, import-defer] +includes: [compareArray.js] +---*/ + +import "./setup_FIXTURE.js"; +import defer * as ns from "./barrel_FIXTURE.js"; + +assert.compareArray(globalThis.evaluations, [], + "barrel and dep both deferred until first interaction with ns"); + +assert.sameValue(ns.exported, 3, + "deferred-reexported binding resolves to the original value"); + +assert.compareArray(globalThis.evaluations, ["barrel", "dep"], + "ns.exported triggers barrel evaluation, then dep evaluation"); diff --git a/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-direct-get.js b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-direct-get.js new file mode 100644 index 00000000000..07fb336a8ef --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-direct-get.js @@ -0,0 +1,34 @@ +// Copyright (C) 2026 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 +description: > + ns.direct triggers barrel evaluation only under `import defer` +info: | + [[Get]] ( _P_, _Receiver_ ) + 1. ... + 1. Let _m_ be _O_.[[Module]]. + 1. If _m_ is a Cyclic Module Record and _m_.GetOptionalIndirectExportsModuleRequests(« _P_ ») + is not empty, then + 1. Perform ? EvaluateModuleSync(_m_, « _P_ »). + 1. ... + + For names that are NOT deferred-reexported, GetOptionalIndirectExportsModuleRequests + returns an empty list, so EvaluateModuleSync is not called for them. +flags: [module] +features: [export-defer, import-defer] +includes: [compareArray.js] +---*/ + +import "./setup_FIXTURE.js"; +import defer * as ns from "./barrel_FIXTURE.js"; + +assert.compareArray(globalThis.evaluations, [], + "barrel deferred until first interaction with ns"); + +assert.sameValue(ns.direct, 1, + "non-deferred binding resolves to the value defined in the barrel"); + +assert.compareArray(globalThis.evaluations, ["barrel"], + "ns.direct triggers barrel evaluation but not the deferred dep"); diff --git a/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-hasProperty.js b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-hasProperty.js new file mode 100644 index 00000000000..f098e89142d --- /dev/null +++ b/test/language/export/export-defer/import-defer-consumer/trigger-barrel-only-on-hasProperty.js @@ -0,0 +1,34 @@ +// Copyright (C) 2026 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-hasproperty-p +description: > + in operator triggers barrel evaluation only under `import defer` +info: | + [[HasProperty]] ( _P_ ) + 1. If _P_ is a Symbol, return ! OrdinaryHasProperty(_O_, _P_). + 1. Let _exports_ be _O_.[[Exports]]. + 1. If _exports_ contains _P_, return *true*. + 1. Return *false*. + + EvaluateModuleSync is only inserted into [[Get]]; [[HasProperty]] does + not route through [[Get]] and so does not trigger deferred-dep + evaluation, but it does interact with the deferred namespace and + therefore triggers barrel evaluation. +flags: [module] +features: [export-defer, import-defer] +includes: [compareArray.js] +---*/ + +import "./setup_FIXTURE.js"; +import defer * as ns from "./barrel_FIXTURE.js"; + +assert.compareArray(globalThis.evaluations, [], + "barrel deferred until first interaction with ns"); + +assert.sameValue("exported" in ns, true, + "deferred-reexported name is reported as a member of the namespace"); + +assert.compareArray(globalThis.evaluations, ["barrel"], + "`in` triggers barrel evaluation but not the deferred dep");