tla-ts 0.0.1 → 0.0.3-dev.a50fc89

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.
Files changed (71) hide show
  1. package/README.md +81 -42
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +215 -30
  4. package/dist/index.d.ts +215 -30
  5. package/dist/index.js +1 -1
  6. package/dist/parallel-worker.cjs +2 -2
  7. package/dist/parallel-worker.js +2 -2
  8. package/dist/parallel.cjs +1 -1
  9. package/dist/parallel.js +2 -2
  10. package/dist/run-cli.cjs +20 -1
  11. package/dist/run-cli.js +20 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +6 -2
  15. package/src/ast.ts +122 -5
  16. package/src/bags.ts +161 -71
  17. package/src/builtins.ts +203 -23
  18. package/src/bundled-modules.ts +63 -32
  19. package/src/check.ts +945 -238
  20. package/src/cli-options.ts +58 -0
  21. package/src/codegen.ts +52 -18
  22. package/src/config.ts +39 -11
  23. package/src/environment.ts +485 -140
  24. package/src/errors.ts +59 -1
  25. package/src/evaluate.ts +489 -137
  26. package/src/exit-codes.ts +75 -0
  27. package/src/fingerprints.ts +2 -2
  28. package/src/index.ts +9 -4
  29. package/src/instances.ts +221 -66
  30. package/src/java-overrides.ts +241 -0
  31. package/src/json.ts +153 -72
  32. package/src/levels.ts +850 -0
  33. package/src/lexer.ts +103 -28
  34. package/src/liveness.ts +47 -91
  35. package/src/obligation-action.ts +384 -0
  36. package/src/obligation-anon.ts +575 -0
  37. package/src/obligation-deref.ts +637 -0
  38. package/src/obligation-expression.ts +1060 -0
  39. package/src/obligation-format.ts +406 -0
  40. package/src/obligation-generate.ts +1074 -0
  41. package/src/obligation-levels.ts +329 -0
  42. package/src/obligation-module.ts +1042 -0
  43. package/src/obligation-printer.ts +889 -0
  44. package/src/obligation-proof.ts +76 -0
  45. package/src/obligation-source.ts +1084 -0
  46. package/src/obligation-tuples.ts +184 -0
  47. package/src/obligations.ts +316 -0
  48. package/src/parallel-worker.ts +57 -28
  49. package/src/parallel.ts +116 -62
  50. package/src/parser.ts +958 -205
  51. package/src/pretty-print.ts +259 -0
  52. package/src/randomization.ts +24 -113
  53. package/src/resolve.ts +472 -41
  54. package/src/run-cli.ts +427 -61
  55. package/src/runtime.ts +63 -28
  56. package/src/spec-description.ts +509 -0
  57. package/src/standard-levels.ts +105 -0
  58. package/src/standard-modules.ts +223 -92
  59. package/src/subexpressions.ts +21 -35
  60. package/src/substitution-levels.ts +184 -0
  61. package/src/successors.ts +261 -46
  62. package/src/symmetry.ts +65 -21
  63. package/src/tableau.ts +43 -37
  64. package/src/tlc-ext.ts +120 -33
  65. package/src/tlc-module.ts +71 -47
  66. package/src/tlc-modules.ts +1022 -0
  67. package/src/tlc-order.ts +497 -0
  68. package/src/tlc-random.ts +411 -0
  69. package/src/tlc-summary.ts +109 -0
  70. package/src/transfer.ts +26 -5
  71. package/src/values.ts +1231 -176
@@ -0,0 +1,75 @@
1
+ import type { LivenessChecker } from "./liveness";
2
+
3
+ // TLC's exit statuses, from EC.ExitStatus.
4
+ export const EXIT_STATUS = {
5
+ success: 0,
6
+ assumptionViolation: 10,
7
+ deadlock: 11,
8
+ safetyViolation: 12,
9
+ livenessViolation: 13,
10
+ assertViolation: 14,
11
+ specEvaluationFailure: 75,
12
+ safetyEvaluationFailure: 76,
13
+ livenessEvaluationFailure: 77,
14
+ specParseError: 150,
15
+ configParseError: 151,
16
+ error: 255,
17
+ } as const;
18
+
19
+ const exitCodes = new WeakMap<Error, number>();
20
+ const moduleExitCodes = new WeakMap<Error, number>();
21
+ const phase = { isNextState: false };
22
+
23
+ export const setNextStatePhase = (isNextState: boolean) => {
24
+ phase.isNextState = isNextState;
25
+ };
26
+
27
+ // The innermost site that knows TLC's exit status for an error sets it first and wins.
28
+ export const setExitCode = <Thrown>(error: Thrown, code: number) => {
29
+ if (error instanceof Error && !exitCodes.has(error)) exitCodes.set(error, code);
30
+ return error;
31
+ };
32
+
33
+ // TLC exits with the code of an exception from a standard module's Java code only when the exception
34
+ // escapes the computation of next states. In every other phase, the phase decides the code.
35
+ export const setModuleExitCode = (error: Error, code: number) => {
36
+ if (phase.isNextState) moduleExitCodes.set(error, code);
37
+ };
38
+
39
+ export const getExitCode = (error: Error) => exitCodes.get(error) ?? moduleExitCodes.get(error);
40
+
41
+ // TLC exits with 255 when it fails to process or evaluate a liveness property, in any phase.
42
+ export const withLivenessExitCode = (
43
+ create: () => LivenessChecker | undefined,
44
+ ): LivenessChecker | undefined => {
45
+ try {
46
+ const checker = create();
47
+ return (
48
+ checker && {
49
+ addInitialState: (index, state) => {
50
+ try {
51
+ checker.addInitialState(index, state);
52
+ } catch (error) {
53
+ throw setExitCode(error, EXIT_STATUS.error);
54
+ }
55
+ },
56
+ addState: (index, state, successors) => {
57
+ try {
58
+ return checker.addState(index, state, successors);
59
+ } catch (error) {
60
+ throw setExitCode(error, EXIT_STATUS.error);
61
+ }
62
+ },
63
+ findViolation: () => {
64
+ try {
65
+ return checker.findViolation();
66
+ } catch (error) {
67
+ throw setExitCode(error, EXIT_STATUS.error);
68
+ }
69
+ },
70
+ }
71
+ );
72
+ } catch (error) {
73
+ throw setExitCode(error, EXIT_STATUS.error);
74
+ }
75
+ };
@@ -4,7 +4,7 @@ const EMPTY = 0;
4
4
  const TOMBSTONE = 1;
5
5
  const INITIAL_CAPACITY = 1 << 12;
6
6
 
7
- export interface FingerprintTable {
7
+ interface FingerprintTable {
8
8
  insert: (first: number, second: number) => boolean;
9
9
  remove: (first: number, second: number) => void;
10
10
  }
@@ -15,7 +15,7 @@ const mix = (hash: number) => {
15
15
  return second ^ (second >>> 16);
16
16
  };
17
17
 
18
- export const getStateFingerprint = (state: Value[], fingerprint: Int32Array) => {
18
+ export const writeStateFingerprint = (state: Value[], fingerprint: Int32Array) => {
19
19
  let first = 0x811c9dc5;
20
20
  let second = 0x6a09e667;
21
21
  for (let index = 0; index < state.length; index++) {
package/src/index.ts CHANGED
@@ -1,5 +1,11 @@
1
- export { check } from "./check";
2
- export type { CheckOptions, CheckResult, CheckStatus, TraceStep, Violation } from "./check";
1
+ export type {
2
+ CheckOptions,
3
+ CheckResult,
4
+ CheckStatus,
5
+ EvaluationFailure,
6
+ TraceStep,
7
+ Violation,
8
+ } from "./check";
3
9
  export { parseConfig } from "./config";
4
10
  export type { ConfigValue, ConstantAssignment, ModelConfig, Substitution } from "./config";
5
11
  export {
@@ -9,8 +15,7 @@ export {
9
15
  TlaSyntaxError,
10
16
  TlaUnsupportedError,
11
17
  } from "./errors";
12
- export { checkInParallel } from "./parallel";
13
- export type { ParallelCheckOptions } from "./parallel";
18
+ export { check } from "./parallel";
14
19
  export { parseModule } from "./parser";
15
20
  export type { Module } from "./ast";
16
21
  export { formatValue } from "./values";
package/src/instances.ts CHANGED
@@ -7,19 +7,18 @@ import type {
7
7
  Module,
8
8
  OperatorParam,
9
9
  SourceLocation,
10
+ Theorem,
10
11
  } from "./ast";
11
12
  import { TlaSyntaxError } from "./errors";
12
13
  import { getChildren, isLocallyResolved } from "./evaluate";
13
- import { isBefore } from "./resolve";
14
-
15
- const getPatternNames = (pattern: BoundPattern) =>
16
- pattern.kind === "Name" ? [pattern.name] : pattern.names;
17
-
18
- const getBoundNames = (bounds: Bound[]) =>
19
- bounds.flatMap((bound) => getPatternNames(bound.pattern));
14
+ import { getArity, getBoundNames, getPatternNames, isBefore } from "./resolve";
20
15
 
21
16
  export const substitutedExpressions = new WeakSet<Expression>();
22
17
 
18
+ // Applications inside an instance with substitutions or parameters, which TLC evaluates in a
19
+ // context that binds them.
20
+ export const contextualApplications = new WeakSet<Expression>();
21
+
23
22
  export const renameLocals = (
24
23
  expression: Expression,
25
24
  names: Map<string, string>,
@@ -41,26 +40,39 @@ export const renameLocals = (
41
40
  return mapped;
42
41
  };
43
42
 
44
- const mapBounds = (bounds: Bound[], map: (child: Expression) => Expression): Bound[] =>
43
+ const mapBounds = (bounds: Bound[], map: (child: Expression) => Expression) =>
45
44
  bounds.map((bound) => ({ pattern: bound.pattern, set: map(bound.set) }));
46
45
 
46
+ const sourceNames = new WeakMap<Definition, string>();
47
+
48
+ export const getSourceName = (definition: Definition) =>
49
+ sourceNames.get(definition) ?? definition.name;
50
+
51
+ const withSourceName = (definition: Definition, source: Definition) => {
52
+ if (getSourceName(source) !== definition.name) sourceNames.set(definition, getSourceName(source));
53
+ return definition;
54
+ };
55
+
47
56
  export const mapDefinition = (
48
57
  definition: Definition,
49
58
  map: (child: Expression, bound: string[]) => Expression,
50
59
  ): Definition =>
51
- definition.kind === "Function"
52
- ? {
53
- ...definition,
54
- bounds: mapBounds(definition.bounds, (child) => map(child, [])),
55
- body: map(definition.body, getBoundNames(definition.bounds)),
56
- }
57
- : {
58
- ...definition,
59
- body: map(
60
- definition.body,
61
- definition.params.map((param) => param.name),
62
- ),
63
- };
60
+ withSourceName(
61
+ definition.kind === "Function"
62
+ ? {
63
+ ...definition,
64
+ bounds: mapBounds(definition.bounds, (child) => map(child, [])),
65
+ body: map(definition.body, getBoundNames(definition.bounds)),
66
+ }
67
+ : {
68
+ ...definition,
69
+ body: map(
70
+ definition.body,
71
+ definition.params.map((param) => param.name),
72
+ ),
73
+ },
74
+ definition,
75
+ );
64
76
 
65
77
  export const mapChildren = (
66
78
  expression: Expression,
@@ -69,6 +81,7 @@ export const mapChildren = (
69
81
  const plain = (child: Expression) => map(child, []);
70
82
  switch (expression.kind) {
71
83
  case "Number":
84
+ case "Decimal":
72
85
  case "String":
73
86
  case "Identifier":
74
87
  case "OperatorReference":
@@ -199,29 +212,131 @@ export const mapChildren = (
199
212
  }
200
213
  };
201
214
 
202
- export interface Instantiation {
215
+ interface Instantiation {
203
216
  params: OperatorParam[];
204
217
  substitutions: Map<string, Expression>;
205
218
  variables: Set<string>;
206
219
  getDefinition: (key: string) => Definition | undefined;
207
220
  isParameterized: (key: string) => boolean;
208
221
  addDefinition: (name: string, create: (key: string) => Definition) => string;
222
+ createName: (name: string) => string;
209
223
  }
210
224
 
225
+ // LAMBDA is a keyword, so no name bound inside the instance can capture it.
226
+ const LAMBDA_NAME = "LAMBDA";
227
+
211
228
  type RewriteMode = "Normal" | "Enabled" | "Primed";
212
229
 
213
230
  interface Rewrite {
214
231
  instantiation: Instantiation;
232
+ substitutedNames: Set<string>;
215
233
  copies: Map<string, string>;
216
234
  mode: RewriteMode;
217
235
  modes: Map<RewriteMode, Rewrite>;
218
236
  }
219
237
 
238
+ const getFreeNames = (expression: Expression, bound = new Set<string>()): string[] => {
239
+ const name =
240
+ expression.kind === "Identifier"
241
+ ? expression.name
242
+ : expression.kind === "Apply" || expression.kind === "OperatorReference"
243
+ ? expression.operator
244
+ : undefined;
245
+ const names = name === undefined || bound.has(name) ? [] : [name];
246
+ mapChildren(expression, (child, childBound) => {
247
+ names.push(
248
+ ...getFreeNames(child, childBound.length === 0 ? bound : new Set([...bound, ...childBound])),
249
+ );
250
+ return child;
251
+ });
252
+ return names;
253
+ };
254
+
255
+ const createRenamer = (rewrite: Rewrite) => {
256
+ const renamed = new Map<string, string>();
257
+ return (name: string) => {
258
+ if (!rewrite.substitutedNames.has(name)) return name;
259
+ const fresh = renamed.get(name) ?? rewrite.instantiation.createName(name);
260
+ renamed.set(name, fresh);
261
+ return fresh;
262
+ };
263
+ };
264
+
265
+ const bindNames = (bound: Map<string, string>, names: string[], rename: (name: string) => string) =>
266
+ new Map([...bound, ...names.map((name): [string, string] => [name, rename(name)])]);
267
+
268
+ const renamePattern = (pattern: BoundPattern, rename: (name: string) => string): BoundPattern =>
269
+ pattern.kind === "Name"
270
+ ? { kind: "Name", name: rename(pattern.name) }
271
+ : { kind: "Tuple", names: pattern.names.map(rename) };
272
+
273
+ const renameBounds = (bounds: Bound[], rename: (name: string) => string) =>
274
+ bounds.map((bound) => ({ pattern: renamePattern(bound.pattern, rename), set: bound.set }));
275
+
276
+ const renameParams = (definition: Definition, rename: (name: string) => string): Definition =>
277
+ definition.kind === "Function"
278
+ ? { ...definition, bounds: renameBounds(definition.bounds, rename) }
279
+ : {
280
+ ...definition,
281
+ params: definition.params.map((param) => ({
282
+ name: rename(param.name),
283
+ arity: param.arity,
284
+ })),
285
+ };
286
+
287
+ const renameNames = (
288
+ expression: Expression,
289
+ bound: Map<string, string>,
290
+ rename: (name: string) => string,
291
+ ): Expression => {
292
+ const getBoundName = (name: string) => bound.get(name) ?? name;
293
+ switch (expression.kind) {
294
+ case "Identifier":
295
+ return { ...expression, name: getBoundName(expression.name) };
296
+ case "Apply":
297
+ case "OperatorReference":
298
+ return { ...expression, operator: getBoundName(expression.operator) };
299
+ case "Label":
300
+ return { ...expression, params: expression.params.map(getBoundName) };
301
+ case "Lambda":
302
+ return { ...expression, params: expression.params.map(rename) };
303
+ case "Let":
304
+ return {
305
+ ...expression,
306
+ definitions: expression.definitions.map((definition) =>
307
+ withSourceName(
308
+ { ...renameParams(definition, rename), name: rename(definition.name) },
309
+ definition,
310
+ ),
311
+ ),
312
+ };
313
+ case "Forall":
314
+ case "Exists":
315
+ case "FunctionConstruction":
316
+ case "SetMap":
317
+ return { ...expression, bounds: renameBounds(expression.bounds, rename) };
318
+ case "UnboundedQuantifier":
319
+ return { ...expression, names: expression.names.map(rename) };
320
+ case "Choose":
321
+ return { ...expression, pattern: renamePattern(expression.pattern, rename) };
322
+ case "SetFilter":
323
+ return {
324
+ ...expression,
325
+ bound: {
326
+ pattern: renamePattern(expression.bound.pattern, rename),
327
+ set: expression.bound.set,
328
+ },
329
+ };
330
+ default:
331
+ return expression;
332
+ }
333
+ };
334
+
220
335
  export const getEnabledVariableName = (name: string) => `${name}@ENABLED`;
221
336
 
222
337
  const getMode = (rewrite: Rewrite, mode: RewriteMode) => rewrite.modes.get(mode) ?? rewrite;
223
338
 
224
- const isVariable = (expression: Expression, rewrite: Rewrite, bound: Set<string>) =>
339
+ const isVariable = (expression: Expression, rewrite: Rewrite, bound: Map<string, string>) =>
225
340
  expression.kind === "Identifier" &&
226
341
  !bound.has(expression.name) &&
227
342
  !isLocallyResolved(expression) &&
@@ -230,7 +345,7 @@ const isVariable = (expression: Expression, rewrite: Rewrite, bound: Set<string>
230
345
  const getUnchangedVariables = (
231
346
  expression: Expression,
232
347
  rewrite: Rewrite,
233
- bound: Set<string>,
348
+ bound: Map<string, string>,
234
349
  ): string[] | undefined => {
235
350
  if (expression.kind === "Tuple") {
236
351
  const names = expression.elements.map((element) =>
@@ -244,14 +359,14 @@ const getUnchangedVariables = (
244
359
  ? undefined
245
360
  : rewrite.instantiation.getDefinition(expression.name);
246
361
  return definition?.kind === "Operator" && definition.params.length === 0
247
- ? getUnchangedVariables(definition.body, rewrite, new Set())
362
+ ? getUnchangedVariables(definition.body, rewrite, new Map())
248
363
  : undefined;
249
364
  };
250
365
 
251
366
  const substituteInEnabled = (
252
367
  expression: Expression,
253
368
  rewrite: Rewrite,
254
- bound: Set<string>,
369
+ bound: Map<string, string>,
255
370
  ): Expression | undefined => {
256
371
  if (rewrite.mode === "Normal") return undefined;
257
372
  if (rewrite.mode === "Primed") {
@@ -318,16 +433,15 @@ const substituteInEnabled = (
318
433
  };
319
434
  };
320
435
 
321
- const getParamArguments = (params: OperatorParam[], location: SourceLocation): Expression[] =>
322
- params.map((param) => ({
323
- kind: "Identifier",
324
- name: param.name,
325
- location,
326
- resolution: { kind: "Local" },
327
- }));
436
+ const createLocalIdentifier = (name: string, location: SourceLocation): Expression => ({
437
+ kind: "Identifier",
438
+ name,
439
+ location,
440
+ resolution: { kind: "Local" },
441
+ });
328
442
 
329
- const getArity = (definition: Definition) =>
330
- definition.kind === "Operator" ? definition.params.length : 0;
443
+ const getParamArguments = (params: OperatorParam[], location: SourceLocation) =>
444
+ params.map((param) => createLocalIdentifier(param.name, location));
331
445
 
332
446
  const getInstanceOperator = (
333
447
  key: string,
@@ -344,12 +458,7 @@ const getInstanceOperator = (
344
458
  operator: key,
345
459
  args: [
346
460
  ...getParamArguments(params, location),
347
- ...names.map((name): Expression => ({
348
- kind: "Identifier",
349
- name,
350
- location,
351
- resolution: { kind: "Local" },
352
- })),
461
+ ...names.map((name) => createLocalIdentifier(name, location)),
353
462
  ],
354
463
  location,
355
464
  },
@@ -360,7 +469,11 @@ const getInstanceOperator = (
360
469
  const getCopy = (rewrite: Rewrite, key: string) =>
361
470
  rewrite.instantiation.isParameterized(key) ? copyDefinition(rewrite, key) : undefined;
362
471
 
363
- const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>): Expression => {
472
+ const substitute = (
473
+ expression: Expression,
474
+ rewrite: Rewrite,
475
+ bound: Map<string, string>,
476
+ ): Expression => {
364
477
  const recurse = (node: Expression) => substitute(node, rewrite, bound);
365
478
  const instantiation = rewrite.instantiation;
366
479
  const params = instantiation.params;
@@ -375,7 +488,6 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
375
488
  (expression.kind === "WeakFairness" || expression.kind === "StrongFairness") &&
376
489
  rewrite.mode === "Normal"
377
490
  ) {
378
- const enabled = getMode(rewrite, "Enabled");
379
491
  return {
380
492
  ...expression,
381
493
  subscript: recurse(expression.subscript),
@@ -387,7 +499,7 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
387
499
  subscript: expression.subscript,
388
500
  location: expression.location,
389
501
  },
390
- enabled,
502
+ getMode(rewrite, "Enabled"),
391
503
  bound,
392
504
  ),
393
505
  };
@@ -417,22 +529,30 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
417
529
  if (expression.kind === "Apply" && isFree(expression.operator)) {
418
530
  const args = expression.args.map(recurse);
419
531
  const replacement = instantiation.substitutions.get(expression.operator);
532
+ const isContextual =
533
+ instantiation.substitutions.size > 0 ||
534
+ params.length > 0 ||
535
+ contextualApplications.has(expression);
536
+ const mark = (rewritten: Expression) => {
537
+ if (isContextual) contextualApplications.add(rewritten);
538
+ return rewritten;
539
+ };
420
540
  if (replacement?.kind === "Identifier" || replacement?.kind === "OperatorReference") {
421
541
  const operator = replacement.kind === "Identifier" ? replacement.name : replacement.operator;
422
- return {
542
+ return mark({
423
543
  ...expression,
424
544
  operator,
425
545
  args,
426
546
  resolution: replacement.kind === "Identifier" ? replacement.resolution : undefined,
427
- };
547
+ });
428
548
  }
429
549
  if (replacement?.kind === "Apply") {
430
- return {
550
+ return mark({
431
551
  ...expression,
432
552
  operator: replacement.operator,
433
553
  args: [...replacement.args, ...args],
434
554
  resolution: replacement.resolution,
435
- };
555
+ });
436
556
  }
437
557
  if (replacement?.kind === "Lambda") {
438
558
  return {
@@ -440,7 +560,7 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
440
560
  definitions: [
441
561
  {
442
562
  kind: "Operator",
443
- name: replacement.kind,
563
+ name: LAMBDA_NAME,
444
564
  params: replacement.params.map((name) => ({ name, arity: 0 })),
445
565
  body: replacement.body,
446
566
  isLocal: true,
@@ -450,7 +570,7 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
450
570
  ],
451
571
  body: {
452
572
  ...expression,
453
- operator: replacement.kind,
573
+ operator: LAMBDA_NAME,
454
574
  args,
455
575
  resolution: { kind: "Local" },
456
576
  },
@@ -463,7 +583,7 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
463
583
  );
464
584
  }
465
585
  const copy = getCopy(rewrite, expression.operator);
466
- if (copy === undefined) return { ...expression, args };
586
+ if (copy === undefined) return mark({ ...expression, args });
467
587
  return {
468
588
  ...expression,
469
589
  operator: copy,
@@ -479,9 +599,13 @@ const substitute = (expression: Expression, rewrite: Rewrite, bound: Set<string>
479
599
  }
480
600
  return { ...expression, operator: copy };
481
601
  }
482
- return mapChildren(expression, (child, names) =>
483
- names.length === 0 ? recurse(child) : substitute(child, rewrite, new Set([...bound, ...names])),
602
+ const rename = createRenamer(rewrite);
603
+ const mapped = mapChildren(expression, (child, names) =>
604
+ names.length === 0
605
+ ? recurse(child)
606
+ : substitute(child, rewrite, bindNames(bound, names, rename)),
484
607
  );
608
+ return renameNames(mapped, bound, rename);
485
609
  };
486
610
 
487
611
  const withInstanceParams = (
@@ -500,7 +624,7 @@ const withInstanceParams = (
500
624
  body: {
501
625
  kind: "Let",
502
626
  definitions: [definition],
503
- body: { kind: "Identifier", name: definition.name, location, resolution: { kind: "Local" } },
627
+ body: createLocalIdentifier(definition.name, location),
504
628
  location,
505
629
  },
506
630
  isLocal: definition.isLocal,
@@ -517,8 +641,12 @@ const copyDefinition = (rewrite: Rewrite, key: string): string => {
517
641
  if (!definition) return key;
518
642
  return instantiation.addDefinition(definition.name, (copyKey) => {
519
643
  rewrite.copies.set(key, copyKey);
520
- const mapped = mapDefinition(definition, (child, bound) =>
521
- substitute(child, rewrite, new Set(bound)),
644
+ const rename = createRenamer(rewrite);
645
+ const mapped = renameParams(
646
+ mapDefinition(definition, (child, bound) =>
647
+ substitute(child, rewrite, bindNames(new Map(), bound, rename)),
648
+ ),
649
+ rename,
522
650
  );
523
651
  return withInstanceParams(mapped, instantiation.params, definition.location);
524
652
  });
@@ -526,8 +654,17 @@ const copyDefinition = (rewrite: Rewrite, key: string): string => {
526
654
 
527
655
  export const createInstantiation = (instantiation: Instantiation) => {
528
656
  const modes = new Map<RewriteMode, Rewrite>();
657
+ const substitutedNames = new Set(
658
+ [...instantiation.substitutions.values()].flatMap((expression) => getFreeNames(expression)),
659
+ );
529
660
  const createRewrite = (mode: RewriteMode): Rewrite => {
530
- const created = { instantiation, copies: new Map(), mode, modes };
661
+ const created = {
662
+ instantiation,
663
+ substitutedNames,
664
+ copies: new Map<string, string>(),
665
+ mode,
666
+ modes,
667
+ };
531
668
  modes.set(mode, created);
532
669
  return created;
533
670
  };
@@ -554,36 +691,54 @@ const hasLetInstance = (expression: Expression): boolean =>
554
691
 
555
692
  const expandLetInstances = (
556
693
  expression: Expression,
557
- instantiate: (instance: Instance) => Definition[],
694
+ instantiate: (instance: Instance, lets: Definition[]) => Definition[],
695
+ lets: Definition[],
558
696
  ): Expression => {
559
- const mapped = mapChildren(expression, (child) => expandLetInstances(child, instantiate));
697
+ const inner = expression.kind === "Let" ? [...lets, ...expression.definitions] : lets;
698
+ const mapped = mapChildren(expression, (child) => expandLetInstances(child, instantiate, inner));
560
699
  if (mapped.kind !== "Let" || !mapped.instances) return mapped;
561
700
  const definitions = mapped.instances.reduce((current, instance) => {
562
701
  const position = current.findIndex((definition) =>
563
702
  isBefore(instance.location, definition.location),
564
703
  );
565
704
  const index = position === -1 ? current.length : position;
566
- return [...current.slice(0, index), ...instantiate(instance), ...current.slice(index)];
705
+ return [...current.slice(0, index), ...instantiate(instance, inner), ...current.slice(index)];
567
706
  }, mapped.definitions);
568
707
  return { kind: "Let", definitions, body: mapped.body, location: mapped.location };
569
708
  };
570
709
 
710
+ // A named theorem's statement is linked as its definition.
711
+ const getUnnamedStatement = (theorem: Theorem) =>
712
+ theorem.name === undefined && theorem.statement.kind !== "AssumeProve"
713
+ ? theorem.statement
714
+ : undefined;
715
+
571
716
  export const withLetInstances = (
572
717
  module: Module,
573
- instantiate: (instance: Instance) => Definition[],
718
+ instantiate: (instance: Instance, lets: Definition[]) => Definition[],
574
719
  ): Module => {
575
- const isExpanded =
720
+ const hasLetInstances =
576
721
  module.definitions.some((definition) => hasLetInstance(definition.body)) ||
577
- module.assumptions.some((assumption) => hasLetInstance(assumption.expression));
578
- if (!isExpanded) return module;
722
+ module.assumptions.some((assumption) => hasLetInstance(assumption.expression)) ||
723
+ module.theorems.some((theorem) => {
724
+ const statement = getUnnamedStatement(theorem);
725
+ return statement !== undefined && hasLetInstance(statement);
726
+ });
727
+ if (!hasLetInstances) return module;
579
728
  return {
580
729
  ...module,
581
730
  definitions: module.definitions.map((definition) =>
582
- mapDefinition(definition, (child) => expandLetInstances(child, instantiate)),
731
+ mapDefinition(definition, (child) => expandLetInstances(child, instantiate, [])),
583
732
  ),
584
733
  assumptions: module.assumptions.map((assumption) => ({
585
734
  ...assumption,
586
- expression: expandLetInstances(assumption.expression, instantiate),
735
+ expression: expandLetInstances(assumption.expression, instantiate, []),
587
736
  })),
737
+ theorems: module.theorems.map((theorem) => {
738
+ const statement = getUnnamedStatement(theorem);
739
+ return statement === undefined
740
+ ? theorem
741
+ : { ...theorem, statement: expandLetInstances(statement, instantiate, []) };
742
+ }),
588
743
  };
589
744
  };