dsh-ptc-plus 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -0
- package/README.md +6 -0
- package/assets/ptc-plus-bindings-en.png +0 -0
- package/assets/ptc-plus-bindings-zh.png +0 -0
- package/client.js +43 -9
- package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +1 -1
- package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
- package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +7 -3
- package/docs/adr/0014-persist-cell-rewrite-policy.md +7 -5
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +5 -3
- package/docs/architecture.md +14 -6
- package/docs/client-ui.md +5 -5
- package/docs/durability-design.md +42 -20
- package/docs/program-data-plane.md +6 -0
- package/docs/runtime-reference.md +10 -3
- package/index.js +8 -3
- package/internal/cell-analysis.js +72 -12
- package/internal/cell-rewriter.js +128 -4
- package/internal/config-spec.js +10 -0
- package/internal/edit-transport-owner.js +1 -1
- package/internal/kernel-worker.js +11 -0
- package/internal/repl-convenience.js +94 -6
- package/internal/repl-rewrite-contract.js +15 -0
- package/internal/runtime-config.js +1 -0
- package/internal/session-cell-executor.js +65 -15
- package/internal/session-journal.js +328 -61
- package/internal/session-runtime.js +91 -7
- package/internal/session-state.js +36 -3
- package/package.json +7 -5
- package/src/client-activity.js +38 -9
|
@@ -3,6 +3,10 @@ import { parse } from '@babel/parser'
|
|
|
3
3
|
import traverseModule, { Hub, NodePath } from '@babel/traverse'
|
|
4
4
|
import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
|
|
5
5
|
import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
|
|
6
|
+
import {
|
|
7
|
+
LEGACY_DEFAULT_EXPORT_BINDING,
|
|
8
|
+
LIVE_DEFAULT_EXPORT_BINDING,
|
|
9
|
+
} from './repl-rewrite-contract.js'
|
|
6
10
|
|
|
7
11
|
export const STRIP_PREFIX = 'async function __ptc_cell__(){\n'
|
|
8
12
|
export const STRIP_SUFFIX = '\n}'
|
|
@@ -429,7 +433,96 @@ function exportAllEdits(node, code, edits, rewrites, moduleLoads) {
|
|
|
429
433
|
rewrites.push(record('export', `converted the re-export of ${JSON.stringify(source)} into a side-effect import`, node, source))
|
|
430
434
|
}
|
|
431
435
|
|
|
432
|
-
function exportDefaultEdits(
|
|
436
|
+
function exportDefaultEdits({
|
|
437
|
+
node,
|
|
438
|
+
code,
|
|
439
|
+
edits,
|
|
440
|
+
rewrites,
|
|
441
|
+
defaultNameAvailable,
|
|
442
|
+
exportDeclarations,
|
|
443
|
+
imports,
|
|
444
|
+
allocateNamespace,
|
|
445
|
+
commitSignal,
|
|
446
|
+
commitTargets,
|
|
447
|
+
}) {
|
|
448
|
+
const declaration = node.declaration
|
|
449
|
+
if (declaration.type === 'TSInterfaceDeclaration') {
|
|
450
|
+
editNode(edits, node, code)
|
|
451
|
+
rewrites.push(record('export', 'removed a type-only export declaration', node, 'interface'))
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
if (!defaultNameAvailable) {
|
|
455
|
+
throw new ModuleRewriteError(
|
|
456
|
+
'export default cannot be exposed as __default because that name is already declared',
|
|
457
|
+
{ line: node.loc.start.line, column: node.loc.start.column + 1 },
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
const namedDeclaration = (declaration.type === 'FunctionDeclaration'
|
|
461
|
+
|| declaration.type === 'ClassDeclaration') && declaration.id !== null
|
|
462
|
+
const retainedNamedDeclaration = namedDeclaration && declaration.id.name !== '__default'
|
|
463
|
+
const existingAlias = imports.get('__default')
|
|
464
|
+
const reuseAlias = existingAlias?.syntheticDefault === true
|
|
465
|
+
const namespace = reuseAlias ? existingAlias.namespace : allocateNamespace('default_namespace')
|
|
466
|
+
const member = importMember(namespace, 'default')
|
|
467
|
+
imports.set('__default', {
|
|
468
|
+
namespace,
|
|
469
|
+
imported: 'default',
|
|
470
|
+
syntheticDefault: true,
|
|
471
|
+
commitDependency: '__default',
|
|
472
|
+
})
|
|
473
|
+
commitTargets.add('__default')
|
|
474
|
+
if (declaration.type === 'ClassDeclaration' && declaration.id !== null) {
|
|
475
|
+
commitTargets.add(declaration.id.name)
|
|
476
|
+
}
|
|
477
|
+
exportDeclarations.push({
|
|
478
|
+
name: '__default',
|
|
479
|
+
kind: declaration.type === 'FunctionDeclaration'
|
|
480
|
+
? 'function'
|
|
481
|
+
: declaration.type === 'ClassDeclaration' ? 'class' : 'variable',
|
|
482
|
+
commitDependency: '__default',
|
|
483
|
+
span: {
|
|
484
|
+
line: node.loc.start.line,
|
|
485
|
+
column: node.loc.start.column + 1,
|
|
486
|
+
end: {
|
|
487
|
+
line: node.loc.end.line,
|
|
488
|
+
column: node.loc.end.column + 1,
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
definitionSpan: {
|
|
492
|
+
line: node.loc.start.line,
|
|
493
|
+
column: node.loc.start.column + 1,
|
|
494
|
+
end: {
|
|
495
|
+
line: node.loc.end.line,
|
|
496
|
+
column: node.loc.end.column + 1,
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
original: true,
|
|
500
|
+
})
|
|
501
|
+
const rewrite = record('export', 'converted the default export into a local __default binding', node)
|
|
502
|
+
const commit = [...new Set([...(declaration.type === 'ClassDeclaration' && declaration.id !== null
|
|
503
|
+
? [declaration.id.name]
|
|
504
|
+
: []), '__default'])]
|
|
505
|
+
.map(name => ` this[${JSON.stringify(commitSignal)}](${JSON.stringify(name)});`)
|
|
506
|
+
.join('')
|
|
507
|
+
const assignmentPrefix = reuseAlias ? `${member} = ` : `const ${namespace} = { default: `
|
|
508
|
+
const assignmentSuffix = reuseAlias ? '' : ' }'
|
|
509
|
+
if (retainedNamedDeclaration) {
|
|
510
|
+
edit(edits, node.start, declaration.start, preserveLines(code.slice(node.start, declaration.start)))
|
|
511
|
+
edit(edits, node.end, node.end,
|
|
512
|
+
`; ${assignmentPrefix}${declaration.id.name}${assignmentSuffix};${commit}`)
|
|
513
|
+
} else if (declaration.type === 'FunctionDeclaration' || declaration.type === 'ClassDeclaration') {
|
|
514
|
+
const prefix = code.slice(node.start, declaration.start)
|
|
515
|
+
edit(edits, node.start, declaration.start, preserveLines(prefix, assignmentPrefix))
|
|
516
|
+
edit(edits, node.end, node.end, `${assignmentSuffix};${commit}`)
|
|
517
|
+
} else {
|
|
518
|
+
const prefix = code.slice(node.start, declaration.start)
|
|
519
|
+
edit(edits, node.start, declaration.start, preserveLines(prefix, assignmentPrefix))
|
|
520
|
+
edit(edits, node.end, node.end, `${assignmentSuffix};${commit}`)
|
|
521
|
+
}
|
|
522
|
+
rewrites.push(rewrite)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function legacyExportDefaultEdits(node, code, edits, rewrites, defaultNameAvailable, exportDeclarations) {
|
|
433
526
|
const declaration = node.declaration
|
|
434
527
|
if (declaration.type === 'TSInterfaceDeclaration') {
|
|
435
528
|
editNode(edits, node, code)
|
|
@@ -490,7 +583,11 @@ function exportDefaultEdits(node, code, edits, rewrites, defaultNameAvailable, e
|
|
|
490
583
|
|
|
491
584
|
function ascending(left, right) { return left.at - right.at }
|
|
492
585
|
|
|
493
|
-
export function rewriteModuleImportsExports(program, enabled, existingImports = new Map(), existingNamespaces = new Set(), unavailableNames = new Set()) {
|
|
586
|
+
export function rewriteModuleImportsExports(program, enabled, existingImports = new Map(), existingNamespaces = new Set(), unavailableNames = new Set(), moduleSemantics = { defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING }) {
|
|
587
|
+
if (![LEGACY_DEFAULT_EXPORT_BINDING, LIVE_DEFAULT_EXPORT_BINDING]
|
|
588
|
+
.includes(moduleSemantics?.defaultExportBinding)) {
|
|
589
|
+
throw new TypeError('ptc-plus: unsupported default export binding semantics')
|
|
590
|
+
}
|
|
494
591
|
const tree = parseCell(program)
|
|
495
592
|
const edits = []
|
|
496
593
|
const rewrites = []
|
|
@@ -500,6 +597,7 @@ export function rewriteModuleImportsExports(program, enabled, existingImports =
|
|
|
500
597
|
const moduleLoads = []
|
|
501
598
|
const importNamespaces = new Set(existingNamespaces)
|
|
502
599
|
const allocateName = createGeneratedNameAllocator(tree, [...unavailableNames, ...importNamespaces])
|
|
600
|
+
const commitSignal = allocateName('redeclaration_commit')
|
|
503
601
|
const generatedNamespaces = new Set()
|
|
504
602
|
const allocateImportName = purpose => {
|
|
505
603
|
const name = allocateName(purpose)
|
|
@@ -508,7 +606,8 @@ export function rewriteModuleImportsExports(program, enabled, existingImports =
|
|
|
508
606
|
return name
|
|
509
607
|
}
|
|
510
608
|
const namespaceCaptures = []
|
|
511
|
-
const
|
|
609
|
+
const commitTargets = new Set()
|
|
610
|
+
const currentCellDefaultNameAvailable = !tree.body.some((node) => {
|
|
512
611
|
const declaration = node.type === 'ExportNamedDeclaration' ? node.declaration : node
|
|
513
612
|
if (declaration?.type === 'VariableDeclaration' && declaration.declare !== true) {
|
|
514
613
|
return declaration.declarations.some(entry => bindingNodes(entry.id).some(binding => binding.name === '__default'))
|
|
@@ -520,6 +619,12 @@ export function rewriteModuleImportsExports(program, enabled, existingImports =
|
|
|
520
619
|
specifier.importKind !== 'type' && specifier.local.name === '__default'
|
|
521
620
|
))
|
|
522
621
|
})
|
|
622
|
+
const existingDefault = existingImports.get('__default')
|
|
623
|
+
const persistentDefaultNameAvailable = !unavailableNames.has('__default')
|
|
624
|
+
|| existingDefault?.syntheticDefault === true
|
|
625
|
+
const defaultNameAvailable = currentCellDefaultNameAvailable
|
|
626
|
+
&& (moduleSemantics.defaultExportBinding === LEGACY_DEFAULT_EXPORT_BINDING
|
|
627
|
+
|| persistentDefaultNameAvailable)
|
|
523
628
|
for (const node of tree.body) {
|
|
524
629
|
if (node.type === 'ImportDeclaration' && enabled.autoRewriteImports) {
|
|
525
630
|
importEdits(node, program, edits, rewrites, imports, importDeclarations,
|
|
@@ -532,7 +637,24 @@ export function rewriteModuleImportsExports(program, enabled, existingImports =
|
|
|
532
637
|
exportAllEdits(node, program, edits, rewrites, moduleLoads)
|
|
533
638
|
}
|
|
534
639
|
else if (node.type === 'ExportDefaultDeclaration' && enabled.autoStripExports) {
|
|
535
|
-
|
|
640
|
+
if (moduleSemantics.defaultExportBinding === LEGACY_DEFAULT_EXPORT_BINDING) {
|
|
641
|
+
legacyExportDefaultEdits(
|
|
642
|
+
node, program, edits, rewrites, defaultNameAvailable, exportDeclarations,
|
|
643
|
+
)
|
|
644
|
+
} else {
|
|
645
|
+
exportDefaultEdits({
|
|
646
|
+
node,
|
|
647
|
+
code: program,
|
|
648
|
+
edits,
|
|
649
|
+
rewrites,
|
|
650
|
+
defaultNameAvailable,
|
|
651
|
+
exportDeclarations,
|
|
652
|
+
imports,
|
|
653
|
+
allocateNamespace: allocateImportName,
|
|
654
|
+
commitSignal,
|
|
655
|
+
commitTargets,
|
|
656
|
+
})
|
|
657
|
+
}
|
|
536
658
|
}
|
|
537
659
|
}
|
|
538
660
|
assertNoDynamicImportResolution(tree, imports)
|
|
@@ -549,6 +671,8 @@ export function rewriteModuleImportsExports(program, enabled, existingImports =
|
|
|
549
671
|
importDeclarations,
|
|
550
672
|
exportDeclarations,
|
|
551
673
|
moduleLoads,
|
|
674
|
+
commitSignal,
|
|
675
|
+
commitTargets,
|
|
552
676
|
rewrites: rewrites.map(({ at: _at, ...rest }) => rest),
|
|
553
677
|
}
|
|
554
678
|
}
|
package/internal/config-spec.js
CHANGED
|
@@ -65,6 +65,15 @@ export const CONFIG_FIELDS = Object.freeze([
|
|
|
65
65
|
description: '允许顶层 const 或 let 声明替换已有变量;关闭后,重声明会在执行前被拒绝。',
|
|
66
66
|
descriptionEn: 'Allows top-level const or let declarations to replace existing variables; disabling rejects redeclarations before execution.',
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
key: 'looseTopLevelFunctionClassRedeclarations',
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
default: false,
|
|
72
|
+
label: '允许顶层函数/类重声明',
|
|
73
|
+
labelEn: 'Allow top-level function/class redeclarations',
|
|
74
|
+
description: '允许顶层 function 或 class 声明替换已有的可写 binding;不可写或保留 binding 仍会在执行前被拒绝。',
|
|
75
|
+
descriptionEn: 'Allows top-level function or class declarations to replace existing writable bindings; immutable and reserved bindings are still rejected before execution.',
|
|
76
|
+
},
|
|
68
77
|
{
|
|
69
78
|
key: 'autoRewriteImports',
|
|
70
79
|
type: 'boolean',
|
|
@@ -253,6 +262,7 @@ export const CONFIG_GROUPS = Object.freeze([
|
|
|
253
262
|
labelEn: 'Advanced behavior',
|
|
254
263
|
fields: Object.freeze([
|
|
255
264
|
'looseTopLevelRedeclarations',
|
|
265
|
+
'looseTopLevelFunctionClassRedeclarations',
|
|
256
266
|
'autoRewriteImports',
|
|
257
267
|
'autoStripExports',
|
|
258
268
|
'autoSplitRedeclarations',
|
|
@@ -147,7 +147,7 @@ export function createEditTransportOwner(ctx, {
|
|
|
147
147
|
if (typeof ctx.tools.execute !== 'function') {
|
|
148
148
|
throw new Error('ptc-plus: DSH tools.execute is required for derived edit execution')
|
|
149
149
|
}
|
|
150
|
-
const persistedCallSeq =
|
|
150
|
+
const persistedCallSeq = persistedTargetCallSeq
|
|
151
151
|
const id = sessionId(agent)
|
|
152
152
|
let claims = editClaims.get(id)
|
|
153
153
|
if (claims === undefined) {
|
|
@@ -366,6 +366,7 @@ async function runCell(message) {
|
|
|
366
366
|
pendingVolatileReason = undefined
|
|
367
367
|
activeExecution = execution
|
|
368
368
|
const cellGlobals = []
|
|
369
|
+
const committedRedeclarations = new Set()
|
|
369
370
|
|
|
370
371
|
try {
|
|
371
372
|
let completion
|
|
@@ -376,6 +377,13 @@ async function runCell(message) {
|
|
|
376
377
|
value: CellReturn,
|
|
377
378
|
})
|
|
378
379
|
cellGlobals.push(message.returnSignal)
|
|
380
|
+
Object.defineProperty(context, message.commitSignal, {
|
|
381
|
+
configurable: true,
|
|
382
|
+
value(name) {
|
|
383
|
+
committedRedeclarations.add(name)
|
|
384
|
+
},
|
|
385
|
+
})
|
|
386
|
+
cellGlobals.push(message.commitSignal)
|
|
379
387
|
for (const load of message.moduleLoads ?? []) {
|
|
380
388
|
let namespace
|
|
381
389
|
try {
|
|
@@ -421,6 +429,7 @@ async function runCell(message) {
|
|
|
421
429
|
...(position === undefined ? {} : { position }),
|
|
422
430
|
...(detail.cause === undefined ? {} : { cause: detail.cause }),
|
|
423
431
|
...completionDurability(execution),
|
|
432
|
+
committedRedeclarations: [...committedRedeclarations],
|
|
424
433
|
})
|
|
425
434
|
return
|
|
426
435
|
}
|
|
@@ -437,6 +446,7 @@ async function runCell(message) {
|
|
|
437
446
|
hasValue: completion.hasValue,
|
|
438
447
|
...(encodedValue === undefined ? {} : { value: encodedValue }),
|
|
439
448
|
...completionDurability(execution),
|
|
449
|
+
committedRedeclarations: [...committedRedeclarations],
|
|
440
450
|
}
|
|
441
451
|
} catch (error) {
|
|
442
452
|
const detail = messageOf(error)
|
|
@@ -446,6 +456,7 @@ async function runCell(message) {
|
|
|
446
456
|
logs: execution.logs,
|
|
447
457
|
invalidOutput: detail,
|
|
448
458
|
...completionDurability(execution),
|
|
459
|
+
committedRedeclarations: [...committedRedeclarations],
|
|
449
460
|
}
|
|
450
461
|
}
|
|
451
462
|
channel.postMessage(response)
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { bindingNodes, createGeneratedNameAllocator } from './binding-pattern.js'
|
|
2
|
+
import {
|
|
3
|
+
CLASS_REDECLARATION_REWRITE,
|
|
4
|
+
FUNCTION_REDECLARATION_REWRITE,
|
|
5
|
+
MIXED_REDECLARATION_REWRITE,
|
|
6
|
+
redeclarationCommitTarget,
|
|
7
|
+
} from './repl-rewrite-contract.js'
|
|
2
8
|
import { applySourceEdits, createMappedTextBuilder, identitySourceMap } from './source-position-map.js'
|
|
3
9
|
import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
|
|
4
10
|
|
|
@@ -88,16 +94,41 @@ function bindingEntries(pattern, declarationSpan) {
|
|
|
88
94
|
}))
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
function existingFunctionText(statement, code, offset) {
|
|
98
|
+
const builder = createMappedTextBuilder(code, offset)
|
|
99
|
+
builder.append(';(')
|
|
100
|
+
builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
|
|
101
|
+
builder.append(' = ')
|
|
102
|
+
builder.appendSource(statement.start, statement.id.start)
|
|
103
|
+
builder.appendSource(statement.id.end, statement.end)
|
|
104
|
+
builder.append(');')
|
|
105
|
+
return builder.result()
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function existingClassText(statement, code, offset) {
|
|
109
|
+
const builder = createMappedTextBuilder(code, offset)
|
|
110
|
+
builder.append(';(')
|
|
111
|
+
builder.appendMapped(statement.id.name, statement.id.start, statement.id.end)
|
|
112
|
+
builder.append(' = ')
|
|
113
|
+
builder.appendSource(statement.start, statement.end)
|
|
114
|
+
builder.append(');')
|
|
115
|
+
return builder.result()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function rewriteReplRedeclarations({
|
|
92
119
|
code,
|
|
93
120
|
sourceMap = identitySourceMap(code.length),
|
|
94
121
|
body,
|
|
95
122
|
offset,
|
|
96
123
|
knownBindings,
|
|
124
|
+
variableRedeclarationBindings = knownBindings,
|
|
125
|
+
writableBindings,
|
|
97
126
|
declarations,
|
|
98
127
|
declarationSpan,
|
|
99
128
|
collisionFor,
|
|
129
|
+
bindingPolicy,
|
|
100
130
|
autoSplitRedeclarations,
|
|
131
|
+
commitSignal,
|
|
101
132
|
}) {
|
|
102
133
|
const redeclared = []
|
|
103
134
|
const rewrites = []
|
|
@@ -117,7 +148,47 @@ export function rewriteLooseRedeclarations({
|
|
|
117
148
|
if (statement.type !== 'VariableDeclaration') {
|
|
118
149
|
if ((statement.type === 'FunctionDeclaration' || statement.type === 'ClassDeclaration')
|
|
119
150
|
&& statement.id !== null && knownBindings.has(statement.id.name)) {
|
|
120
|
-
|
|
151
|
+
const kind = statement.type === 'ClassDeclaration' ? 'class' : 'function'
|
|
152
|
+
if (!writableBindings.has(statement.id.name)) {
|
|
153
|
+
rejected.push({
|
|
154
|
+
name: statement.id.name,
|
|
155
|
+
kind,
|
|
156
|
+
reason: 'binding-not-writable',
|
|
157
|
+
span: declarationSpan(statement.id),
|
|
158
|
+
})
|
|
159
|
+
} else if (!bindingPolicy.functionClassRedeclarations) {
|
|
160
|
+
rejected.push({
|
|
161
|
+
name: statement.id.name,
|
|
162
|
+
kind,
|
|
163
|
+
reason: 'function-class-redeclarations-disabled',
|
|
164
|
+
span: declarationSpan(statement.id),
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
const commitDependency = redeclarationCommitTarget(statement.id.name, statement.start)
|
|
168
|
+
const replacement = kind === 'class'
|
|
169
|
+
? existingClassText(statement, code, offset)
|
|
170
|
+
: existingFunctionText(statement, code, offset)
|
|
171
|
+
replacement.text += ` void 0; this[${JSON.stringify(commitSignal)}](${JSON.stringify(commitDependency)});`
|
|
172
|
+
replacements.push({
|
|
173
|
+
start: statement.start - offset,
|
|
174
|
+
end: statement.end - offset,
|
|
175
|
+
text: replacement.text,
|
|
176
|
+
mappings: replacement.mappings,
|
|
177
|
+
})
|
|
178
|
+
redeclared.push(collisionFor({
|
|
179
|
+
name: statement.id.name,
|
|
180
|
+
kind,
|
|
181
|
+
commitDependency,
|
|
182
|
+
span: declarationSpan(statement.id),
|
|
183
|
+
}))
|
|
184
|
+
rewrites.push({
|
|
185
|
+
kind: 'redeclaration',
|
|
186
|
+
description: kind === 'class'
|
|
187
|
+
? CLASS_REDECLARATION_REWRITE
|
|
188
|
+
: FUNCTION_REDECLARATION_REWRITE,
|
|
189
|
+
source: statement.id.name,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
121
192
|
}
|
|
122
193
|
continue
|
|
123
194
|
}
|
|
@@ -125,7 +196,24 @@ export function rewriteLooseRedeclarations({
|
|
|
125
196
|
let statementRejected = false
|
|
126
197
|
for (const declarator of statement.declarations) {
|
|
127
198
|
const bindings = bindingEntries(declarator.id, declarationSpan)
|
|
128
|
-
const existing = bindings.filter(binding =>
|
|
199
|
+
const existing = bindings.filter(binding => variableRedeclarationBindings.has(binding.name))
|
|
200
|
+
const immutable = existing.filter(binding => !writableBindings.has(binding.name))
|
|
201
|
+
if (immutable.length > 0) {
|
|
202
|
+
rejected.push(...immutable.map(binding => ({
|
|
203
|
+
...binding,
|
|
204
|
+
reason: 'binding-not-writable',
|
|
205
|
+
})))
|
|
206
|
+
statementRejected = true
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
if (existing.length > 0 && !bindingPolicy.variableRedeclarations) {
|
|
210
|
+
rejected.push(...existing.map(binding => ({
|
|
211
|
+
...binding,
|
|
212
|
+
reason: 'variable-redeclarations-disabled',
|
|
213
|
+
})))
|
|
214
|
+
statementRejected = true
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
129
217
|
if (existing.length > 0 && existing.length < bindings.length) {
|
|
130
218
|
if (autoSplitRedeclarations) entries.push({ declarator, bindings, existing })
|
|
131
219
|
else {
|
|
@@ -138,7 +226,7 @@ export function rewriteLooseRedeclarations({
|
|
|
138
226
|
}
|
|
139
227
|
if (statementRejected) continue
|
|
140
228
|
if (!entries.some(entry => entry.existing.length > 0)) {
|
|
141
|
-
if (statement.kind === 'const') {
|
|
229
|
+
if (statement.kind === 'const' && bindingPolicy.variableRedeclarations) {
|
|
142
230
|
replacements.push({
|
|
143
231
|
start: statement.start - offset,
|
|
144
232
|
end: statement.start - offset + statement.kind.length,
|
|
@@ -164,7 +252,7 @@ export function rewriteLooseRedeclarations({
|
|
|
164
252
|
appendPart(mixedDeclaratorText(
|
|
165
253
|
statement,
|
|
166
254
|
declarator,
|
|
167
|
-
bindings.map(binding => ({ ...binding, existing:
|
|
255
|
+
bindings.map(binding => ({ ...binding, existing: existing.includes(binding) })),
|
|
168
256
|
code,
|
|
169
257
|
offset,
|
|
170
258
|
allocateName,
|
|
@@ -172,7 +260,7 @@ export function rewriteLooseRedeclarations({
|
|
|
172
260
|
redeclared.push(...existing.map(collisionFor))
|
|
173
261
|
rewrites.push({
|
|
174
262
|
kind: 'redeclaration',
|
|
175
|
-
description:
|
|
263
|
+
description: MIXED_REDECLARATION_REWRITE,
|
|
176
264
|
source: existing.map(binding => binding.name).join(', '),
|
|
177
265
|
})
|
|
178
266
|
} else if (existing.length === bindings.length && bindings.length > 0) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const MIXED_REDECLARATION_REWRITE =
|
|
2
|
+
'split a mixed top-level declaration while preserving native pattern initialization'
|
|
3
|
+
|
|
4
|
+
export const FUNCTION_REDECLARATION_REWRITE =
|
|
5
|
+
'reassigned an existing top-level function declaration for REPL continuity'
|
|
6
|
+
|
|
7
|
+
export const CLASS_REDECLARATION_REWRITE =
|
|
8
|
+
'reassigned an existing top-level class declaration for REPL continuity'
|
|
9
|
+
|
|
10
|
+
export const LEGACY_DEFAULT_EXPORT_BINDING = 'legacy-variable'
|
|
11
|
+
export const LIVE_DEFAULT_EXPORT_BINDING = 'live-readonly'
|
|
12
|
+
|
|
13
|
+
export function redeclarationCommitTarget(name, statementStart) {
|
|
14
|
+
return `redeclaration:${statementStart}:${name}`
|
|
15
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from './failure-reporting.js'
|
|
19
19
|
import { assertStateName } from './session-journal.js'
|
|
20
20
|
import { PreflightError, prepareProgram } from './cell-analysis.js'
|
|
21
|
+
import { LIVE_DEFAULT_EXPORT_BINDING } from './repl-rewrite-contract.js'
|
|
21
22
|
import { ModuleRewriteError } from './cell-rewriter.js'
|
|
22
23
|
import { mapSourcePosition } from './source-position-map.js'
|
|
23
24
|
import { durabilityState, transitionDurability } from './session-state.js'
|
|
@@ -105,9 +106,47 @@ function preflightDiagnostic(error) {
|
|
|
105
106
|
})
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
function committedRedeclarationSet(message, prepared) {
|
|
110
|
+
if (!Array.isArray(message.committedRedeclarations)) {
|
|
111
|
+
throw new Error('kernel returned an invalid committed redeclaration set')
|
|
112
|
+
}
|
|
113
|
+
const allowed = prepared.commitTargets
|
|
114
|
+
const committed = new Set()
|
|
115
|
+
for (const name of message.committedRedeclarations) {
|
|
116
|
+
if (typeof name !== 'string' || committed.has(name) || !allowed.has(name)) {
|
|
117
|
+
throw new Error('kernel returned an invalid committed redeclaration set')
|
|
118
|
+
}
|
|
119
|
+
committed.add(name)
|
|
120
|
+
}
|
|
121
|
+
return committed
|
|
122
|
+
}
|
|
123
|
+
|
|
108
124
|
function collisionDiagnostic(collisions) {
|
|
109
125
|
const names = [...new Set(collisions.map(item => item.name))]
|
|
126
|
+
const reasons = new Set(collisions.map(item => item.reason).filter(Boolean))
|
|
127
|
+
const disabledKinds = new Set(collisions
|
|
128
|
+
.filter(item => item.reason === 'function-class-redeclarations-disabled')
|
|
129
|
+
.map(item => item.kind))
|
|
110
130
|
const first = collisions[0]
|
|
131
|
+
const alternatives = [
|
|
132
|
+
...(disabledKinds.size === 2
|
|
133
|
+
? ['assign function or class expressions to the existing writable bindings']
|
|
134
|
+
: disabledKinds.has('function')
|
|
135
|
+
? ['assign a function expression to the existing writable binding']
|
|
136
|
+
: disabledKinds.has('class')
|
|
137
|
+
? ['assign a class expression to the existing writable binding']
|
|
138
|
+
: []),
|
|
139
|
+
...((reasons.size === 0 || reasons.has('variable-redeclarations-disabled'))
|
|
140
|
+
? ['reuse the existing bindings']
|
|
141
|
+
: []),
|
|
142
|
+
]
|
|
143
|
+
const help = [
|
|
144
|
+
...(reasons.has('binding-not-writable')
|
|
145
|
+
? ['use a fresh name because the existing binding is immutable']
|
|
146
|
+
: []),
|
|
147
|
+
...(alternatives.length === 0 ? [] : [alternatives.join('; ')]),
|
|
148
|
+
'place one-off declarations inside a block',
|
|
149
|
+
]
|
|
111
150
|
return diagnostic({
|
|
112
151
|
code: 'PTC-N001',
|
|
113
152
|
severity: 'error',
|
|
@@ -115,10 +154,7 @@ function collisionDiagnostic(collisions) {
|
|
|
115
154
|
message: `top-level bindings already exist: ${names.join(', ')}. This cell was not executed; the REPL state is unchanged.`,
|
|
116
155
|
stateEffect: 'unchanged',
|
|
117
156
|
source: { cell: 'current', start: first.start, end: first.end },
|
|
118
|
-
help
|
|
119
|
-
'reuse the existing bindings',
|
|
120
|
-
'place one-off declarations inside a block',
|
|
121
|
-
],
|
|
157
|
+
help,
|
|
122
158
|
})
|
|
123
159
|
}
|
|
124
160
|
|
|
@@ -218,22 +254,28 @@ export class SessionCellExecutor {
|
|
|
218
254
|
}
|
|
219
255
|
|
|
220
256
|
const catalog = kernel.bindingCatalog.inputs()
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
:
|
|
257
|
+
const bindingPolicy = replayRecord === undefined ? {
|
|
258
|
+
variableRedeclarations: config.looseTopLevelRedeclarations,
|
|
259
|
+
functionClassRedeclarations: config.looseTopLevelFunctionClassRedeclarations,
|
|
260
|
+
} : replayRecord.bindingPolicy
|
|
224
261
|
const rewritesEnabled = replayRecord === undefined ? {
|
|
225
262
|
autoRewriteImports: config.autoRewriteImports,
|
|
226
263
|
autoStripExports: config.autoStripExports,
|
|
227
264
|
autoSplitRedeclarations: config.autoSplitRedeclarations,
|
|
228
265
|
} : replayRecord.rewritePolicy
|
|
266
|
+
const moduleSemantics = replayRecord === undefined ? {
|
|
267
|
+
defaultExportBinding: LIVE_DEFAULT_EXPORT_BINDING,
|
|
268
|
+
} : replayRecord.moduleSemantics
|
|
229
269
|
const prepareCell = program => prepareProgram(
|
|
230
270
|
program,
|
|
231
271
|
catalog.knownBindings,
|
|
232
|
-
|
|
272
|
+
bindingPolicy,
|
|
233
273
|
request.bindingDescriptors.reservedNames,
|
|
234
274
|
rewritesEnabled,
|
|
235
275
|
catalog.importBindings,
|
|
236
276
|
catalog.importNamespaces,
|
|
277
|
+
catalog.writableBindings,
|
|
278
|
+
moduleSemantics,
|
|
237
279
|
)
|
|
238
280
|
let prepared
|
|
239
281
|
try {
|
|
@@ -348,6 +390,7 @@ export class SessionCellExecutor {
|
|
|
348
390
|
type: 'run', id, program: prepared.code, namespaces: bindings.workerDescriptors,
|
|
349
391
|
moduleLoads: prepared.moduleLoads,
|
|
350
392
|
returnSignal: prepared.returnSignal,
|
|
393
|
+
commitSignal: prepared.commitSignal,
|
|
351
394
|
maxOutputBytes: config.maxOutputBytes,
|
|
352
395
|
valueLimits,
|
|
353
396
|
durability,
|
|
@@ -480,6 +523,20 @@ export class SessionCellExecutor {
|
|
|
480
523
|
}, true)
|
|
481
524
|
return
|
|
482
525
|
}
|
|
526
|
+
let committed
|
|
527
|
+
try {
|
|
528
|
+
committed = committedRedeclarationSet(message, active.prepared)
|
|
529
|
+
} catch (error) {
|
|
530
|
+
active.resolve(earlyResult('worker-exit', messageOf(error)), true)
|
|
531
|
+
return
|
|
532
|
+
}
|
|
533
|
+
active.appliedBindingCatalog = message.moduleLoadFailed === true
|
|
534
|
+
? active.priorBindingCatalog
|
|
535
|
+
: active.priorBindingCatalog.advance(
|
|
536
|
+
active.prepared,
|
|
537
|
+
active.request.program,
|
|
538
|
+
committed,
|
|
539
|
+
)
|
|
483
540
|
if (typeof message.error === 'string') {
|
|
484
541
|
const rawError = {
|
|
485
542
|
kind: 'exception',
|
|
@@ -501,9 +558,6 @@ export class SessionCellExecutor {
|
|
|
501
558
|
declared: message.moduleLoadFailed === true ? new Set() : active.prepared.declared,
|
|
502
559
|
longCellFailure: active.request.program.length >= LONG_CELL_CODE_UNITS,
|
|
503
560
|
})
|
|
504
|
-
active.appliedBindingCatalog = message.moduleLoadFailed === true
|
|
505
|
-
? active.priorBindingCatalog
|
|
506
|
-
: active.priorBindingCatalog.advance(active.prepared, active.request.program)
|
|
507
561
|
const recordedFailure = active.replay?.diagnostics?.find(item => item.code === 'PTC-X001')
|
|
508
562
|
const failure = recordedFailure?.message === actualFailure.message ? recordedFailure : actualFailure
|
|
509
563
|
const error = {
|
|
@@ -532,10 +586,6 @@ export class SessionCellExecutor {
|
|
|
532
586
|
hasValue: message.hasValue,
|
|
533
587
|
...(message.hasValue ? { value } : {}),
|
|
534
588
|
}
|
|
535
|
-
active.appliedBindingCatalog = active.priorBindingCatalog.advance(
|
|
536
|
-
active.prepared,
|
|
537
|
-
active.request.program,
|
|
538
|
-
)
|
|
539
589
|
if (active.replay?.completion?.kind === 'return'
|
|
540
590
|
&& (active.replay.completion.hasValue !== message.hasValue
|
|
541
591
|
|| (message.hasValue && !valueWiresEqual(active.replay.completion.value, value, active.valueLimits)))) {
|