Skip to content

Commit 387ab4d

Browse files
authored
update: scope ceiling (#20)
1 parent 50a6bf6 commit 387ab4d

7 files changed

Lines changed: 459 additions & 44 deletions

File tree

README.md

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ Champions:
1414
A way to evaluate a module and its dependencies in the context of a new global scope within the same Realm
1515

1616

17+
Sever the tie to the `GlobalEnvironmentRecord` in `ModuleEnvironmentRecord` instances and replace with `ScopeCeiling` whenever provided.
18+
19+
1720

1821
> This proposal picks up from the previous proposal for
1922
> [Evaluators](https://github.com/tc39/proposal-compartments/blob/7e60fdbce66ef2d97370007afeb807192c653333/3-evaluator.md)
@@ -91,57 +94,77 @@ That includes REPLs, inline code execution results in editors (eg. [Quokka.js](h
9194

9295
Maintaining the global state between executions of user-provided code snippets would benefit from the ability to control scope
9396

94-
## Intersection Semantics
97+
## Proposal
9598

96-
TBD
99+
Allow evaluating a module without access to global context by severing the tie to the `GlobalEnvironmentRecord` in `ModuleEnvironmentRecord` instances
100+
by setting `[[OuterEnv]]` to a different record than `module.[[Realm]].[[GlobalEnv]]`, replacing it with user-defined emulation of a global we will refer to as **Scope Ceiling**
97101

98-
## Design Questions
102+
### Scope Ceiling
99103

100-
### Prototype chain in the browser
104+
[16.2.1.7.3.1 InitializeEnvironment ( )](https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-source-text-module-record-initialize-environment)
101105

102-
`globalThis` in the browser has a non-trivial prototype chain for some Window
103-
API functionality and events.
104-
105-
```js
106-
let pro = globalThis;
107-
while (pro = Object.getPrototypeOf(pro)) {
108-
console.log(pro.toString())
109-
}
110-
```
111-
```
112-
// browsers
113-
[object Window]
114-
[object WindowProperties]
115-
[object EventTarget]
116-
[object Object]
117-
null
118-
```
119-
```
120-
// web extension contentscript
121-
[object Window]
122-
[object WindowProperties]
123-
null
124-
```
106+
```scheme
107+
3. Let realm be module.[[Realm]].
108+
4. Assert: realm is not undefined.
109+
+ if module.[[ScopeCeiling]] is not EMPTY, then
110+
+ Let outer be NewObjectEnvironment(module.[[ScopeCeiling]], false, null)
111+
+ Let env be NewModuleEnvironment(outer)
112+
+ Else
113+
Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
114+
5. Set module.[[Environment]] to env.
125115
```
126-
// Node.js
127-
[object Object]
128-
[object Object]
129-
null
130-
```
131-
```
132-
// Deno
133-
[object Window]
134-
[object EventTarget]
135-
[object Object]
136-
null
137-
```
138-
```
139-
// Hermes
140-
[object Object]
141-
undefined
116+
> note:
117+
> - `module` is Source Text Module Record
118+
> - NewObjectEnvironment could be called earlier
119+
> - `[[ScopeCeiling]]` will likely be accessed indirectly
120+
121+
122+
The association between `ModuleRecord` and `ScopeCeiling` ultimately needs to be introduced by an intermediary - potentially the same intermediary that introduces a Module Map or an `importHook`. (e.g. Compartment)
123+
124+
> We initially considered associating `ScopeCeiling` with `ModuleSource` in its constructor. It doesn't compose well with the esm-phase-imports proposal anymore.
125+
126+
---
127+
128+
#### Execution Context interactions
129+
130+
131+
> Note: `module.[[Environment]]` becomes `LexicalEnvironment` of the `moduleContext`
132+
133+
```scheme
134+
11. Set the Realm of moduleContext to module.[[Realm]].
135+
12. Set the ScriptOrModule of moduleContext to module.
136+
13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
137+
14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
138+
15. Set the PrivateEnvironment of moduleContext to null.
139+
16. Set module.[[Context]] to moduleContext.
140+
17. Push moduleContext onto the execution context stack;
141+
moduleContext is now the running execution context.
142142
```
143143

144144

145+
1. The `x` IdentifierReference is evaluated. Per [§13.1.3](https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-identifiers-runtime-semantics-evaluation) (Runtime Semantics: Evaluation of IdentifierReference), calls [`ResolveBinding("x")`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-resolvebinding).
146+
147+
2. [`ResolveBinding`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-resolvebinding) reads `env` from the **running execution context's `LexicalEnvironment`**. The running execution context is `module.[[Context]]`, and its `LexicalEnvironment` is the module's `ModuleEnvironmentRecord`. Since module code is always strict, `strict` = **true**. Calls `GetIdentifierReference(moduleEnv, "x", true)`.
148+
149+
150+
3. [`GetIdentifierReference`](https://tc39.es/ecma262/multipage/executable-code-and-execution-contexts.html#sec-getidentifierreference) calls **`moduleEnv.HasBinding("x")`** on the `ModuleEnvironmentRecord`. The module environment holds only the module's own top-level `var`/`let`/`const`/`class` declarations and imported bindings. `x` is none of these, so `HasBinding` returns **false**.
151+
152+
4. `GetIdentifierReference` reads `outer` = **`moduleEnv.[[OuterEnv]]`** reaching `ScopeCeiling`
153+
154+
5. `ScopeCeiling.[[OuterEnv]]` is null, so it cannot progress to Realm global for lookup
155+
156+
157+
The change would result in an option to run a module in a context that does not have the means to reach globals lexically.
158+
The *undeniables* would come from the shared Realm, convenience of accessing intrinsics lexically via their global name
159+
would depend on the provider of `ScopeCeiling` adding them.
160+
161+
---
162+
163+
## Intersection Semantics
164+
165+
Depends on an umbrella proposal to have an entrypoint to defining a `ScopeCeiling`, so likely will be folded into a Compartment proposal or its subset.
166+
167+
145168
[proposal-source-phase-imports]: https://github.com/tc39/proposal-source-phase-imports
146169
[proposal-esm-phase-imports]: https://github.com/tc39/proposal-esm-phase-imports
147170
[proposal-compartments]: https://github.com/tc39/proposal-compartments

module.html

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>ModuleRecord and friends
8+
9+
###################################################################################################
10+
11+
12+
Open this file in the browser to see the diagram rendered with working links and color coding
13+
14+
15+
(CMD/Ctrl)+Scroll to zoom in and out works
16+
17+
###################################################################################################
18+
19+
20+
21+
</title>
22+
<base target="_blank">
23+
</head>
24+
25+
<body>
26+
<div id="l">
27+
<span style="background: #ffe"><a href="https://tc39.es/proposal-source-phase-imports">proposal-source-phase-imports</a> <a href="https://github.com/tc39/proposal-source-phase-imports">[repo]</a></span>
28+
<span style="background: #fef"><a href="https://tc39.es/proposal-esm-phase-imports">proposal-esm-phase-imports</a> <a href="https://github.com/tc39/proposal-esm-phase-imports">[repo]</a></span>
29+
<span style="background: #afa">new variant A</span>
30+
<span style="background: #9da">new variant B</span>
31+
</div>
32+
<pre class="mermaid">
33+
34+
classDiagram
35+
class ModuleRecord {
36+
&lt;&lt;abstract>>
37+
[[Status]]
38+
[[Realm]] : Realm Record
39+
[[Environment]] : Module Environment Record | EMPTY
40+
[[Namespace]] : Object | EMPTY
41+
[[SourceRecord]] : Module Source Record
42+
-[[ModuleSource]] : replaced by SourceRecord in esm-phase
43+
[[HostDefined]] : anything
44+
+[[Compartment]] : Compartment | EMPTY
45+
}
46+
link ModuleRecord "https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-abstract-module-records"
47+
48+
class CyclicModuleRecord {
49+
&lt;&lt;abstract>>
50+
[[Status]]
51+
[[EvaluationError]]
52+
[[DFSAncestorIndex]]
53+
[[RequestedModules]] : List~ModuleRequest Records~
54+
[[LoadedModules]] : List~LoadedModuleRequest Records~
55+
[[CycleRoot]]
56+
[[HasTLA]] : Boolean
57+
[[AsyncEvaluationOrder]]
58+
[[TopLevelCapability]]
59+
[[AsyncParentModules]]
60+
[[PendingAsyncDependencies]]
61+
}
62+
link CyclicModuleRecord "https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#cyclic-module-record"
63+
64+
65+
class SourceTextModuleRecord {
66+
[[ECMAScriptCode]] : Parse Node
67+
[[Context]] : Execution Context | EMPTY
68+
[[ImportMeta]] : Object | EMPTY
69+
[[ImportEntries]] : List~ImportEntry Records~
70+
[[LocalExportEntries]] : List~ExportEntry Records~
71+
[[IndirectExportEntries]] : List~ExportEntry Records~
72+
[[StarExportEntries]] : List~ExportEntry Records~
73+
}
74+
link SourceTextModuleRecord "https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sourctextmodule-record"
75+
note for SourceTextModuleRecord "<a href=https://tc39.es/ecma262/multipage/ecmascript-language-scripts-and-modules.html#sec-source-text-module-record-initialize-environment>InitializeEnvironment()</a><br>to use [[ScopeCeiling]]<br>instead of realm.[[GlobalEnv]]<br>for NewModuleEnvironment(OuterEnv)<br>if [[ScopeCeiling]] not EMPTY"
76+
77+
class ScopeCeiling:::added {
78+
[[OuterEnv]] = null
79+
[[BindingObject]] handles the globalThis binding and var
80+
:
81+
inheriting from ObjectEnvironmentRecord is sufficient,
82+
but all bindings are dynamic andcould be slower than
83+
a mix of ObjectRecord and DeclarativeRecord
84+
}
85+
86+
class EnvironmentRecord {
87+
&lt;&lt;abstract>>
88+
[[OuterEnv]] : Environment Record | null
89+
}
90+
link EnvironmentRecord "https://tc39.es/ecma262/#sec-environment-records"
91+
92+
93+
94+
class DeclarativeEnvironmentRecord {
95+
}
96+
link DeclarativeEnvironmentRecord "https://tc39.es/ecma262/#sec-declarative-environment-records"
97+
98+
class ObjectEnvironmentRecord {
99+
[[BindingObject]] : Object
100+
[[IsWithEnvironment]] : Boolean
101+
}
102+
103+
class GlobalEnvironmentRecord {
104+
[[ObjectRecord]] : Object Environment Record
105+
[[GlobalThisValue]] : Object
106+
[[DeclarativeRecord]] : Declarative Environment Record
107+
[[OuterEnv]] = null
108+
}
109+
110+
class ModuleEnvironmentRecord {
111+
[[OuterEnv]] : Environment Record
112+
}
113+
link ModuleEnvironmentRecord "https://tc39.es/ecma262/#sec-module-environment-records"
114+
115+
class RealmRecord {
116+
[[AgentSignifier]]
117+
[[Intrinsics]]
118+
[[GlobalObject]] : Object
119+
[[GlobalEnv]] : Global Environment Record
120+
[[TemplateMap]]
121+
[[LoadedModules]]
122+
[[HostDefined]]
123+
}
124+
125+
ModuleRecord <|-- CyclicModuleRecord
126+
CyclicModuleRecord <|-- SourceTextModuleRecord
127+
EnvironmentRecord <|-- DeclarativeEnvironmentRecord
128+
EnvironmentRecord <|-- ObjectEnvironmentRecord
129+
EnvironmentRecord <|-- GlobalEnvironmentRecord
130+
DeclarativeEnvironmentRecord <|-- ModuleEnvironmentRecord
131+
132+
ModuleRecord --> RealmRecord : [[Realm]]
133+
ModuleRecord --> ModuleEnvironmentRecord : [[Environment]]
134+
RealmRecord --> GlobalEnvironmentRecord : [[GlobalEnv]]
135+
ModuleEnvironmentRecord ..> GlobalEnvironmentRecord : [[OuterEnv]]
136+
ModuleEnvironmentRecord ..> ScopeCeiling : +[[OuterEnv]]
137+
GlobalEnvironmentRecord --> ObjectEnvironmentRecord : [[ObjectRecord]]
138+
GlobalEnvironmentRecord --> DeclarativeEnvironmentRecord : [[DeclarativeRecord]]
139+
140+
ObjectEnvironmentRecord <|-- ScopeCeiling : would this suffice?
141+
142+
class AbstractModuleSource:::sourceproposal {
143+
&lt;&lt;abstract>>
144+
}
145+
link AbstractModuleSource "https://tc39.es/proposal-source-phase-imports/#sec-abstract-module-source-constructor"
146+
147+
class HostDefinedModuleSource:::sourceproposal {
148+
&lt;&lt;host-defined>>
149+
e.g. WebAssembly.Module
150+
}
151+
152+
class ModuleRequestRecord:::sourceproposal {
153+
[[Specifier]] : String
154+
[[Phase]] : source | evaluation
155+
[[Attributes]]
156+
}
157+
link ModuleRequestRecord "https://tc39.es/proposal-source-phase-imports/#sec-modulerequest-records"
158+
note for ModuleRequestRecord "<a href=https://tc39.es/proposal-esm-phase-imports/#sec-modulesourcesequal>ModuleSourcesEqual(a,b)</a> <br> compares [[HostDefined]] too. <br> could consider Compartment"
159+
160+
class ModuleSourceRecord:::esmphaseproposal {
161+
[[HostDefined]] : anything
162+
[[SourceText]] : source text | NONE
163+
[[ModuleSourceKind]] : String
164+
[[ModuleSource]] : Object | EMPTY
165+
}
166+
link ModuleSourceRecord "https://tc39.es/proposal-esm-phase-imports/#module-source-record"
167+
168+
class ModuleSource:::esmphaseproposal {
169+
[[ModuleSourceRecord]] : Module Source Record
170+
}
171+
link ModuleSource "https://tc39.es/proposal-esm-phase-imports/#sec-module-source-object"
172+
173+
174+
class Compartment:::added {
175+
[[moduleMap]]
176+
+[[ScopeCeiling]] : Object | EMPTY
177+
}
178+
179+
Compartment --> ScopeCeiling : [[ScopeCeiling]]
180+
ModuleRecord --> Compartment : +[[Compartment]]
181+
182+
AbstractModuleSource <|-- HostDefinedModuleSource
183+
AbstractModuleSource <|-- ModuleSource
184+
ModuleRecord --> ModuleSourceRecord : [[SourceRecord]]
185+
ModuleSourceRecord --> ModuleSource : [[ModuleSource]]
186+
ModuleSource --> ModuleSourceRecord : [[ModuleSourceRecord]]
187+
CyclicModuleRecord --> ModuleRequestRecord : [[RequestedModules]]
188+
189+
classDef sourceproposal fill:#ffe
190+
classDef esmphaseproposal fill:#fef
191+
classDef importhookproposal fill:#eff
192+
193+
classDef added fill:#afa
194+
195+
</pre>
196+
<script type="module">
197+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
198+
mermaid.initialize({ startOnLoad: true, securityLevel: 'loose' });
199+
200+
setTimeout(() => {
201+
document.querySelectorAll('.mermaid svg p').forEach(p => {
202+
if (p.textContent.startsWith('+')) {
203+
p.style.background = '#afa';
204+
}
205+
if (p.textContent.startsWith('+B')) {
206+
p.style.background = '#9da';
207+
}
208+
if (p.textContent.startsWith('-')) {
209+
p.style.color = '#aaa';
210+
}
211+
})
212+
213+
document.querySelectorAll('.mermaid svg .edgePaths path').forEach(path => {
214+
path.style.strokeWidth = '2px';
215+
path.addEventListener('mouseover', () => {
216+
path.style.stroke = '#f00';
217+
});
218+
path.addEventListener('mouseout', () => {
219+
path.style.stroke = '';
220+
});
221+
});
222+
223+
}, 1000);
224+
</script>
225+
<style>
226+
.mermaid {
227+
/* makes zooming in and out work */
228+
width: 200vw
229+
}
230+
#l {
231+
position: fixed;
232+
top: 0;
233+
left: 0;
234+
background: #fff8;
235+
padding: 10px;
236+
z-index: 1000;
237+
span {
238+
padding: 10px;
239+
}
240+
}
241+
a, a:visited {
242+
color: #000;
243+
}
244+
</style>
245+
246+
</body>
247+
248+
</html>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"start": "npm run build-loose -- --watch",
77
"build": "npm run build-loose -- --strict",
88
"build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec",
9-
"slides": "npx @marp-team/marp-cli slides/2025-09-stage1-update.md --allow-local-files --pdf"
9+
"slides": "npx @marp-team/marp-cli slides/*.md --allow-local-files --pdf"
1010
},
1111
"homepage": "https://github.com/tc39/template-for-proposals#readme",
1212
"repository": {
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)