tla-ts 0.0.1 → 0.0.3-dev.763cc71

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 +78 -30
  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
package/src/resolve.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import type {
2
+ AssumeProve,
2
3
  Bound,
3
4
  BoundPattern,
4
5
  Definition,
5
6
  Expression,
6
7
  Instance,
7
8
  Module,
9
+ NewSymbol,
8
10
  OperatorParam,
11
+ Proof,
12
+ ProofStep,
9
13
  SourceLocation,
14
+ Theorem,
10
15
  } from "./ast";
11
16
  import { TlaSyntaxError } from "./errors";
12
17
  import { getChildren, isLocallyResolved } from "./evaluate";
@@ -14,6 +19,7 @@ import { getChildren, isLocallyResolved } from "./evaluate";
14
19
  export interface ModuleSymbol {
15
20
  key: string;
16
21
  arity: number;
22
+ declaration?: "Constant" | "Variable";
17
23
  }
18
24
 
19
25
  export type Symbols = Map<string, ModuleSymbol>;
@@ -30,14 +36,21 @@ interface Unit {
30
36
  getKey: (definition: Definition) => string;
31
37
  location: SourceLocation;
32
38
  self: Definition | undefined;
39
+ facts: Set<string>;
33
40
  }
34
41
 
35
- const LOCAL_RESOLUTION = { kind: "Local" } as const;
42
+ export const LOCAL_RESOLUTION = { kind: "Local" } as const;
36
43
 
37
44
  const renamedArities = new WeakMap<Expression, number>();
38
45
 
39
46
  export const KEY_SEPARATOR = "@";
40
47
 
48
+ // A key is a name, the separator, and an id, so the operator @@ is not one.
49
+ const isKey = (name: string) => {
50
+ const index = name.lastIndexOf(KEY_SEPARATOR);
51
+ return index > 0 && index < name.length - 1;
52
+ };
53
+
41
54
  const CORE_SYMBOLS: Symbols = new Map(
42
55
  ["~>", "-+->", "\\cdot"].map((name) => [name, { key: name, arity: 2 }]),
43
56
  );
@@ -45,7 +58,7 @@ const CORE_SYMBOLS: Symbols = new Map(
45
58
  export const isBefore = (left: SourceLocation, right: SourceLocation) =>
46
59
  left.line < right.line || (left.line === right.line && left.column < right.column);
47
60
 
48
- const getArity = (definition: Definition) =>
61
+ export const getArity = (definition: Definition) =>
49
62
  definition.kind === "Operator" ? definition.params.length : 0;
50
63
 
51
64
  const isInstantiated = (definition: Definition) => definition.name.includes("!");
@@ -60,9 +73,44 @@ const lookupLocal = (scope: LocalName | undefined, name: string) => {
60
73
  const bindNames = (scope: LocalName | undefined, names: string[], arity = 0) =>
61
74
  names.reduce<LocalName | undefined>((parent, name) => ({ name, arity, parent }), scope);
62
75
 
63
- const getPatternNames = (pattern: BoundPattern) =>
76
+ const bindParams = (scope: LocalName | undefined, params: OperatorParam[]) =>
77
+ params.reduce<LocalName | undefined>(
78
+ (parent, param) => ({ name: param.name, arity: param.arity, parent }),
79
+ scope,
80
+ );
81
+
82
+ export const getPatternNames = (pattern: BoundPattern) =>
64
83
  pattern.kind === "Name" ? [pattern.name] : pattern.names;
65
84
 
85
+ export const getBoundNames = (bounds: Bound[]) =>
86
+ bounds.flatMap((bound) => getPatternNames(bound.pattern));
87
+
88
+ const getVisibleDefinition = (name: string, unit: Unit) => {
89
+ const definition = unit.definitions.get(name);
90
+ const isVisible =
91
+ definition !== undefined &&
92
+ (definition === unit.self
93
+ ? definition.kind === "Function" || definition.isRecursive
94
+ : definition.isRecursive || isBefore(definition.location, unit.location));
95
+ return isVisible ? definition : undefined;
96
+ };
97
+
98
+ const findSymbol = (name: string, unit: Unit): ModuleSymbol | undefined => {
99
+ const definition = getVisibleDefinition(name, unit);
100
+ return definition
101
+ ? { key: unit.getKey(definition), arity: getArity(definition) }
102
+ : unit.imported.get(name);
103
+ };
104
+
105
+ // SANY names an undefined operator symbol such as + differently from an undefined identifier.
106
+ const isOperatorSymbol = (name: string) =>
107
+ name[0].toLowerCase() === name[0].toUpperCase() &&
108
+ name[0] !== "_" &&
109
+ (name[0] < "0" || name[0] > "9");
110
+
111
+ const isStepName = (name: string) =>
112
+ name.startsWith("<") && ((name[1] >= "0" && name[1] <= "9") || name[1] === "*");
113
+
66
114
  const getSymbolArity = (
67
115
  expression: Expression & { kind: "Identifier" | "Apply" | "OperatorReference" },
68
116
  name: string,
@@ -72,28 +120,34 @@ const getSymbolArity = (
72
120
  const location = expression.location;
73
121
  const renamed = renamedArities.get(expression);
74
122
  if (renamed !== undefined) return renamed;
75
- if (name.includes(KEY_SEPARATOR)) return expression.kind === "Apply" ? expression.args.length : 0;
123
+ if (isKey(name)) return expression.kind === "Apply" ? expression.args.length : 0;
124
+ if (isStepName(name)) return 0;
76
125
  const local = lookupLocal(scope, name);
77
126
  if (local && expression.kind !== "OperatorReference") expression.resolution = LOCAL_RESOLUTION;
78
127
  if (local) return local.arity;
79
- const definition = unit.definitions.get(name);
80
- const isVisible =
81
- definition !== undefined &&
82
- (definition === unit.self
83
- ? definition.kind === "Function" || definition.isRecursive
84
- : definition.isRecursive || isBefore(definition.location, unit.location));
85
- const symbol =
86
- definition && isVisible
87
- ? { key: unit.getKey(definition), arity: getArity(definition) }
88
- : unit.imported.get(name);
128
+ const symbol = findSymbol(name, unit);
89
129
  if (symbol) {
90
130
  if (expression.kind === "Identifier") expression.name = symbol.key;
91
131
  else expression.operator = symbol.key;
92
132
  renamedArities.set(expression, symbol.arity);
93
133
  return symbol.arity;
94
134
  }
135
+ // A name such as Inv!P1 selects the label P1 of a definition or theorem, which takes its own
136
+ // arguments.
137
+ const prefix = name.slice(0, name.indexOf("!"));
138
+ const isSelected =
139
+ prefix !== "" &&
140
+ (lookupLocal(scope, prefix) !== undefined ||
141
+ findSymbol(prefix, unit) !== undefined ||
142
+ unit.facts.has(prefix));
143
+ if (isSelected) {
144
+ return expression.kind === "Apply" ? expression.args.length : 0;
145
+ }
146
+ const position = `at line ${location.line}, column ${location.column}`;
95
147
  throw new TlaSyntaxError(
96
- `Could not find declaration or definition of symbol ${name} at line ${location.line}, column ${location.column}`,
148
+ isOperatorSymbol(name)
149
+ ? `Could not find declaration or definition of symbol '${name}' ${position}`
150
+ : `Unknown operator: \`${name}' ${position}`,
97
151
  );
98
152
  };
99
153
 
@@ -123,11 +177,7 @@ const resolveDefinition = (definition: Definition, scope: LocalName | undefined,
123
177
  markRecursiveConstructions(definition.body, definition.name);
124
178
  return;
125
179
  }
126
- const params = definition.params.reduce<LocalName | undefined>(
127
- (parent, param) => ({ name: param.name, arity: param.arity, parent }),
128
- self,
129
- );
130
- resolveExpression(definition.body, params, unit);
180
+ resolveExpression(definition.body, bindParams(self, definition.params), unit);
131
181
  };
132
182
 
133
183
  const resolveDefinitions = (
@@ -147,22 +197,67 @@ const resolveDefinitions = (
147
197
  }, recursiveScope);
148
198
  };
149
199
 
200
+ // OpDefNode#match: a parameter of arity 0 takes an expression, and a parameter of arity n takes an
201
+ // operator of arity n whose own parameters take expressions.
202
+ const checkArgument = (
203
+ definition: Definition,
204
+ index: number,
205
+ arg: Expression,
206
+ argArity: number,
207
+ argDefinition: Definition | undefined,
208
+ ) => {
209
+ const param = definition.kind === "Operator" ? definition.params[index] : undefined;
210
+ const location = arg.location;
211
+ if (param?.arity === 0 && argArity > 0) {
212
+ throw new TlaSyntaxError(
213
+ `Operator used in argument number ${index + 1} has incorrect number of arguments at line ${location.line}, column ${location.column}`,
214
+ );
215
+ }
216
+ const isHigherOrder =
217
+ argDefinition?.kind === "Operator" && argDefinition.params.some((inner) => inner.arity > 0);
218
+ if (param !== undefined && param.arity > 0 && (argArity !== param.arity || isHigherOrder)) {
219
+ throw new TlaSyntaxError(
220
+ `Argument number ${index + 1} to operator '${definition.name}' should be a ${param.arity}-parameter operator at line ${location.line}, column ${location.column}`,
221
+ );
222
+ }
223
+ };
224
+
225
+ const getArgumentName = (arg: Expression) =>
226
+ arg.kind === "Identifier"
227
+ ? arg.name
228
+ : arg.kind === "OperatorReference"
229
+ ? arg.operator
230
+ : undefined;
231
+
150
232
  const resolveApply = (
151
233
  expression: Expression & { kind: "Apply" },
152
234
  scope: LocalName | undefined,
153
235
  unit: Unit,
154
236
  ) => {
237
+ const callee = lookupLocal(scope, expression.operator)
238
+ ? undefined
239
+ : getVisibleDefinition(expression.operator, unit);
155
240
  const arity = getSymbolArity(expression, expression.operator, scope, unit);
156
241
  if (arity !== expression.args.length) {
157
242
  throw new TlaSyntaxError(
158
243
  `The operator ${expression.operator} requires ${arity} arguments at line ${expression.location.line}, column ${expression.location.column}`,
159
244
  );
160
245
  }
161
- expression.args.forEach((arg) =>
162
- arg.kind === "Identifier"
163
- ? getSymbolArity(arg, arg.name, scope, unit)
164
- : resolveExpression(arg, scope, unit),
165
- );
246
+ expression.args.forEach((arg, index) => {
247
+ const name = getArgumentName(arg);
248
+ const argDefinition =
249
+ name === undefined || lookupLocal(scope, name) ? undefined : getVisibleDefinition(name, unit);
250
+ const argArity = resolveArgument(arg, scope, unit);
251
+ if (callee) checkArgument(callee, index, arg, argArity, argDefinition);
252
+ });
253
+ };
254
+
255
+ // An argument may name an operator that takes arguments of its own.
256
+ const resolveArgument = (arg: Expression, scope: LocalName | undefined, unit: Unit) => {
257
+ if (arg.kind === "Identifier") return getSymbolArity(arg, arg.name, scope, unit);
258
+ if (arg.kind === "OperatorReference") return getSymbolArity(arg, arg.operator, scope, unit);
259
+ resolveExpression(arg, scope, unit);
260
+ return arg.kind === "Lambda" ? arg.params.length : 0;
166
261
  };
167
262
 
168
263
  const resolveExpression = (
@@ -174,6 +269,7 @@ const resolveExpression = (
174
269
  expressions.forEach((child) => resolveExpression(child, scope, unit));
175
270
  switch (expression.kind) {
176
271
  case "Number":
272
+ case "Decimal":
177
273
  case "String":
178
274
  case "ExceptAt":
179
275
  return;
@@ -288,24 +384,338 @@ const resolveExpression = (
288
384
  resolveExpression(expression.body, scope, unit);
289
385
  return;
290
386
  case "Subexpression":
291
- resolveAll(
292
- expression.selectors.flatMap((selector) => ("args" in selector ? selector.args : [])),
387
+ expression.selectors
388
+ .flatMap((selector) => ("args" in selector ? selector.args : []))
389
+ .forEach((arg) => resolveArgument(arg, scope, unit));
390
+ }
391
+ };
392
+
393
+ const resolveNewSymbol = (symbol: NewSymbol, scope: LocalName | undefined, unit: Unit) => {
394
+ if (symbol.set) resolveExpression(symbol.set, scope, unit);
395
+ return { name: symbol.name, arity: symbol.arity, parent: scope };
396
+ };
397
+
398
+ // The NEW symbols of an ASSUME stay in scope for its conclusion and the proof that follows.
399
+ const resolveAssumeProve = (
400
+ assumeProve: AssumeProve,
401
+ scope: LocalName | undefined,
402
+ unit: Unit,
403
+ ): LocalName | undefined => {
404
+ const assumed = assumeProve.assumptions.reduce((current, assumption) => {
405
+ if (assumption.kind === "NewSymbol") return resolveNewSymbol(assumption, current, unit);
406
+ if (assumption.kind === "AssumeProve") resolveAssumeProve(assumption, current, unit);
407
+ else resolveExpression(assumption, current, unit);
408
+ return current;
409
+ }, scope);
410
+ resolveExpression(assumeProve.conclusion, assumed, unit);
411
+ return assumed;
412
+ };
413
+
414
+ const resolveStatement = (
415
+ statement: Expression | AssumeProve,
416
+ scope: LocalName | undefined,
417
+ unit: Unit,
418
+ ) => {
419
+ if (statement.kind === "AssumeProve") return resolveAssumeProve(statement, scope, unit);
420
+ resolveExpression(statement, scope, unit);
421
+ return scope;
422
+ };
423
+
424
+ // SANY resolves the expressions of a proof, while its facts and DEF clauses may also name
425
+ // theorems, assumptions, and modules.
426
+ const resolveProof = (proof: Proof | undefined, scope: LocalName | undefined, unit: Unit) => {
427
+ if (proof?.kind !== "Steps") return;
428
+ proof.steps.reduce((current, step) => resolveStep(step, current, unit), scope);
429
+ };
430
+
431
+ const resolveStep = (
432
+ step: ProofStep,
433
+ scope: LocalName | undefined,
434
+ unit: Unit,
435
+ ): LocalName | undefined => {
436
+ const body = step.body;
437
+ switch (body.kind) {
438
+ case "Assert": {
439
+ const assumed = resolveStatement(body.statement, scope, unit);
440
+ resolveProof(step.proof, assumed, unit);
441
+ return body.isSuffices ? assumed : scope;
442
+ }
443
+ case "Define":
444
+ return resolveDefinitions(
445
+ body.definitions.flatMap((definition) =>
446
+ definition.kind === "Instance" ? [] : [definition],
447
+ ),
448
+ scope,
449
+ unit,
293
450
  );
451
+ case "Have":
452
+ case "Case":
453
+ resolveExpression(body.expression, scope, unit);
454
+ resolveProof(step.proof, scope, unit);
455
+ return scope;
456
+ case "Witness":
457
+ body.expressions.forEach((expression) => resolveExpression(expression, scope, unit));
458
+ return scope;
459
+ case "Take":
460
+ return bindNames(resolveBounds(body.bounds, scope, unit), body.names);
461
+ case "Pick": {
462
+ const picked = bindNames(resolveBounds(body.bounds, scope, unit), body.names);
463
+ resolveExpression(body.body, picked, unit);
464
+ resolveProof(step.proof, scope, unit);
465
+ return picked;
466
+ }
467
+ default:
468
+ resolveProof(step.proof, scope, unit);
469
+ return scope;
294
470
  }
295
471
  };
296
472
 
473
+ const resolveTheorem = (theorem: Theorem, unit: Unit) => {
474
+ const isDefinition = theorem.name !== undefined && theorem.statement.kind !== "AssumeProve";
475
+ const scope = isDefinition ? undefined : resolveStatement(theorem.statement, undefined, unit);
476
+ resolveProof(theorem.proof, scope, unit);
477
+ };
478
+
297
479
  interface ModuleUnit {
298
480
  imported: Symbols;
481
+ importedAt: Map<string, SourceLocation>;
299
482
  definitions: Map<string, Definition>;
300
483
  getKey: (definition: Definition) => string;
301
484
  }
302
485
 
486
+ interface ModuleName {
487
+ name: string;
488
+ kind: "Definition" | "Constant" | "Variable";
489
+ location: SourceLocation;
490
+ isRecursive: boolean;
491
+ }
492
+
493
+ type IsDefined = (name: string) => boolean;
494
+
495
+ const createRedefinitionError = (name: string, location: SourceLocation) =>
496
+ new TlaSyntaxError(
497
+ `Multiply-defined symbol '${name}' at line ${location.line}, column ${location.column}`,
498
+ );
499
+
500
+ const bindUnique = (
501
+ scope: LocalName | undefined,
502
+ names: string[],
503
+ isDefined: IsDefined,
504
+ location: SourceLocation,
505
+ ) =>
506
+ names.reduce<LocalName | undefined>((parent, name) => {
507
+ if (lookupLocal(parent, name) || isDefined(name)) throw createRedefinitionError(name, location);
508
+ return { name, arity: 0, parent };
509
+ }, scope);
510
+
511
+ const checkBoundNames = (
512
+ bounds: Bound[],
513
+ scope: LocalName | undefined,
514
+ isDefined: IsDefined,
515
+ location: SourceLocation,
516
+ ) => {
517
+ bounds.forEach((bound) => checkNames(bound.set, scope, isDefined));
518
+ return bounds.reduce(
519
+ (current, bound) => bindUnique(current, getPatternNames(bound.pattern), isDefined, location),
520
+ scope,
521
+ );
522
+ };
523
+
524
+ const checkDefinitionNames = (
525
+ definition: Definition,
526
+ scope: LocalName | undefined,
527
+ isDefined: IsDefined,
528
+ ) => {
529
+ if (definition.kind === "Function") {
530
+ const self = bindNames(scope, [definition.name]);
531
+ const bound = checkBoundNames(definition.bounds, self, isDefined, definition.location);
532
+ checkNames(definition.body, bound, isDefined);
533
+ return;
534
+ }
535
+ const params = bindUnique(
536
+ scope,
537
+ definition.params.map((param) => param.name),
538
+ isDefined,
539
+ definition.location,
540
+ );
541
+ checkNames(definition.body, params, isDefined);
542
+ };
543
+
544
+ const checkNames = (
545
+ expression: Expression,
546
+ scope: LocalName | undefined,
547
+ isDefined: IsDefined,
548
+ ): void => {
549
+ const location = expression.location;
550
+ switch (expression.kind) {
551
+ case "Lambda":
552
+ checkNames(
553
+ expression.body,
554
+ bindUnique(scope, expression.params, isDefined, location),
555
+ isDefined,
556
+ );
557
+ return;
558
+ case "Let": {
559
+ const recursive = expression.definitions
560
+ .filter((definition) => definition.isRecursive)
561
+ .map((definition) => definition.name);
562
+ const inner = expression.definitions.reduce((current, definition) => {
563
+ if (lookupLocal(current, definition.name) || isDefined(definition.name)) {
564
+ throw createRedefinitionError(definition.name, definition.location);
565
+ }
566
+ checkDefinitionNames(definition, bindNames(current, recursive), isDefined);
567
+ return bindNames(current, [definition.name]);
568
+ }, scope);
569
+ checkNames(
570
+ expression.body,
571
+ bindUnique(
572
+ inner,
573
+ (expression.instances ?? []).flatMap((instance) =>
574
+ instance.name === undefined ? [] : [instance.name],
575
+ ),
576
+ isDefined,
577
+ location,
578
+ ),
579
+ isDefined,
580
+ );
581
+ return;
582
+ }
583
+ case "Forall":
584
+ case "Exists":
585
+ case "FunctionConstruction":
586
+ checkNames(
587
+ expression.body,
588
+ checkBoundNames(expression.bounds, scope, isDefined, location),
589
+ isDefined,
590
+ );
591
+ return;
592
+ case "SetFilter":
593
+ checkNames(
594
+ expression.predicate,
595
+ checkBoundNames([expression.bound], scope, isDefined, location),
596
+ isDefined,
597
+ );
598
+ return;
599
+ case "SetMap":
600
+ checkNames(
601
+ expression.expression,
602
+ checkBoundNames(expression.bounds, scope, isDefined, location),
603
+ isDefined,
604
+ );
605
+ return;
606
+ case "UnboundedQuantifier":
607
+ checkNames(
608
+ expression.body,
609
+ bindUnique(scope, expression.names, isDefined, location),
610
+ isDefined,
611
+ );
612
+ return;
613
+ case "Choose":
614
+ if (expression.set) checkNames(expression.set, scope, isDefined);
615
+ checkNames(
616
+ expression.body,
617
+ bindUnique(scope, getPatternNames(expression.pattern), isDefined, location),
618
+ isDefined,
619
+ );
620
+ return;
621
+ default:
622
+ getChildren(expression).forEach((child) => checkNames(child, scope, isDefined));
623
+ }
624
+ };
625
+
626
+ const getModuleNames = (module: Module) => [
627
+ ...module.definitions.map((definition): ModuleName => ({
628
+ name: definition.name,
629
+ kind: "Definition",
630
+ location: definition.location,
631
+ isRecursive: definition.isRecursive,
632
+ })),
633
+ ...module.instances.flatMap((instance): ModuleName[] =>
634
+ instance.name === undefined
635
+ ? []
636
+ : [
637
+ {
638
+ name: instance.name,
639
+ kind: "Definition",
640
+ location: instance.location,
641
+ isRecursive: false,
642
+ },
643
+ ],
644
+ ),
645
+ ...module.constants.map((constant): ModuleName => ({
646
+ name: constant.name,
647
+ kind: "Constant",
648
+ location: constant.location,
649
+ isRecursive: false,
650
+ })),
651
+ ...module.variables.map((variable): ModuleName => ({
652
+ name: variable.name,
653
+ kind: "Variable",
654
+ location: variable.location,
655
+ isRecursive: false,
656
+ })),
657
+ ];
658
+
659
+ export const checkRedefinitions = (module: Module, unit: ModuleUnit) => {
660
+ const byName = new Map<string, ModuleName[]>();
661
+ getModuleNames(module)
662
+ .sort((left, right) => (isBefore(left.location, right.location) ? -1 : 1))
663
+ .forEach((entry) => byName.set(entry.name, [...(byName.get(entry.name) ?? []), entry]));
664
+ const isImportedBefore = (name: string, location: SourceLocation) => {
665
+ if (!unit.imported.has(name) && !CORE_SYMBOLS.has(name)) return false;
666
+ const importedAt = unit.importedAt.get(name);
667
+ return importedAt === undefined || isBefore(importedAt, location);
668
+ };
669
+ byName.forEach((entries) =>
670
+ entries.forEach((entry, index) => {
671
+ const isDuplicate = entries
672
+ .slice(0, index)
673
+ .some(
674
+ (earlier) =>
675
+ earlier.kind === "Definition" ||
676
+ entry.kind === "Definition" ||
677
+ earlier.kind !== entry.kind,
678
+ );
679
+ // A module may declare again a constant or variable that it extends or that encloses it.
680
+ const isRedeclared = unit.imported.get(entry.name)?.declaration === entry.kind;
681
+ if (isDuplicate || (!isRedeclared && isImportedBefore(entry.name, entry.location)))
682
+ throw createRedefinitionError(entry.name, entry.location);
683
+ }),
684
+ );
685
+ const getIsDefined =
686
+ (location: SourceLocation): IsDefined =>
687
+ (name) =>
688
+ (byName.get(name) ?? []).some(
689
+ (entry) => entry.isRecursive || isBefore(entry.location, location),
690
+ ) || isImportedBefore(name, location);
691
+ module.definitions.forEach((definition) =>
692
+ checkDefinitionNames(definition, undefined, getIsDefined(definition.location)),
693
+ );
694
+ module.assumptions.forEach((assumption) =>
695
+ checkNames(assumption.expression, undefined, getIsDefined(assumption.location)),
696
+ );
697
+ module.instances.forEach((instance) => {
698
+ const isDefined = getIsDefined(instance.location);
699
+ const params = bindUnique(
700
+ undefined,
701
+ instance.params.map((param) => param.name),
702
+ isDefined,
703
+ instance.location,
704
+ );
705
+ instance.substitutions.forEach((substitution) =>
706
+ checkNames(substitution.expression, params, isDefined),
707
+ );
708
+ });
709
+ };
710
+
303
711
  const createSymbols = (module: Module, imported: Symbols) => {
304
712
  const symbols = new Map([...CORE_SYMBOLS, ...imported]);
305
713
  module.constants.forEach((constant) =>
306
714
  symbols.set(constant.name, { key: constant.name, arity: constant.arity }),
307
715
  );
308
- module.variables.forEach((variable) => symbols.set(variable, { key: variable, arity: 0 }));
716
+ module.variables.forEach((variable) =>
717
+ symbols.set(variable.name, { key: variable.name, arity: 0 }),
718
+ );
309
719
  return symbols;
310
720
  };
311
721
 
@@ -315,48 +725,69 @@ export const resolveSubstitutions = (
315
725
  module: Module,
316
726
  unit: ModuleUnit,
317
727
  ) => {
318
- const scope = bindNames(
319
- undefined,
320
- instance.params.map((param) => param.name),
321
- );
728
+ const scope = bindParams(undefined, instance.params);
322
729
  const context: Unit = {
323
730
  imported: createSymbols(module, unit.imported),
324
731
  definitions: unit.definitions,
325
732
  getKey: unit.getKey,
326
733
  location: instance.location,
327
734
  self: undefined,
735
+ facts: new Set(),
328
736
  };
329
737
  const explicit = new Map(instance.substitutions.map((item) => [item.name, item.expression]));
738
+ const position = `in the instance of ${instance.module} at line ${instance.location.line}, column ${instance.location.column}`;
739
+ explicit.forEach((_, name) => {
740
+ if (declarations.every((declaration) => declaration.name !== name)) {
741
+ throw new TlaSyntaxError(
742
+ `Identifier '${name}' is not a legal target of a substitution ${position}`,
743
+ );
744
+ }
745
+ });
330
746
  return new Map(
331
747
  declarations.map((declaration): [string, Expression] => {
332
- const expression: Expression = explicit.get(declaration.name) ?? {
748
+ const name = declaration.name;
749
+ if (!explicit.has(name) && !lookupLocal(scope, name) && !findSymbol(name, context)) {
750
+ throw new TlaSyntaxError(`Substitution missing for symbol ${name} ${position}`);
751
+ }
752
+ const expression: Expression = explicit.get(name) ?? {
333
753
  kind: "Identifier",
334
- name: declaration.name,
754
+ name,
335
755
  location: instance.location,
336
756
  };
337
- if (declaration.arity > 0 && expression.kind === "Identifier") {
338
- getSymbolArity(expression, expression.name, scope, context);
339
- } else {
757
+ if (declaration.arity === 0 || expression.kind !== "Identifier") {
340
758
  resolveExpression(expression, scope, context);
759
+ return [name, expression];
341
760
  }
342
- return [declaration.name, expression];
761
+ if (getSymbolArity(expression, expression.name, scope, context) !== declaration.arity) {
762
+ throw new TlaSyntaxError(
763
+ `An operator must be substituted for symbol '${name}', and it must have arity ${declaration.arity} ${position}`,
764
+ );
765
+ }
766
+ return [name, expression];
343
767
  }),
344
768
  );
345
769
  };
346
770
 
347
771
  export const resolveModule = (module: Module, unit: ModuleUnit) => {
348
772
  const symbols = createSymbols(module, unit.imported);
349
- const resolveIn = (location: SourceLocation, self: Definition | undefined) => ({
773
+ const facts = new Set(
774
+ [...module.theorems, ...module.assumptions].flatMap((fact) => fact.name ?? []),
775
+ );
776
+ const createUnit = (location: SourceLocation, self: Definition | undefined) => ({
350
777
  imported: symbols,
351
778
  definitions: unit.definitions,
352
779
  getKey: unit.getKey,
353
780
  location,
354
781
  self,
782
+ facts,
355
783
  });
356
784
  module.definitions.forEach((definition) =>
357
- resolveDefinition(definition, undefined, resolveIn(definition.location, definition)),
785
+ resolveDefinition(definition, undefined, createUnit(definition.location, definition)),
358
786
  );
359
787
  module.assumptions.forEach((assumption) =>
360
- resolveExpression(assumption.expression, undefined, resolveIn(assumption.location, undefined)),
788
+ resolveExpression(assumption.expression, undefined, createUnit(assumption.location, undefined)),
789
+ );
790
+ module.theorems.forEach((theorem) =>
791
+ resolveTheorem(theorem, createUnit(theorem.location, undefined)),
361
792
  );
362
793
  };