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.
@@ -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(node, code, edits, rewrites, defaultNameAvailable, exportDeclarations) {
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 defaultNameAvailable = !tree.body.some((node) => {
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
- exportDefaultEdits(node, program, edits, rewrites, defaultNameAvailable, exportDeclarations)
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
  }
@@ -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 = currentDurableReplay ? persistedTargetCallSeq : undefined
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
- export function rewriteLooseRedeclarations({
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
- rejected.push({ name: statement.id.name, span: declarationSpan(statement.id) })
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 => knownBindings.has(binding.name))
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: knownBindings.has(binding.name) })),
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: 'split a mixed top-level declaration while preserving native pattern initialization',
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
+ }
@@ -23,6 +23,7 @@ const BOOLEAN_KEYS = Object.freeze([
23
23
  'cordisToolsEnabled',
24
24
  'canonicalizeToolCalls',
25
25
  'looseTopLevelRedeclarations',
26
+ 'looseTopLevelFunctionClassRedeclarations',
26
27
  'durableReplay',
27
28
  'autoRewriteImports',
28
29
  'autoStripExports',
@@ -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 looseTopLevelRedeclarations = replayRecord === undefined
222
- ? config.looseTopLevelRedeclarations
223
- : replayRecord.bindingMode === 'loose'
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
- looseTopLevelRedeclarations,
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)))) {