tla-ts 0.0.1 → 0.0.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.
Files changed (57) hide show
  1. package/README.md +34 -41
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +166 -30
  4. package/dist/index.d.ts +166 -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 +2 -1
  11. package/dist/run-cli.js +2 -1
  12. package/dist/transfer.cjs +388 -39
  13. package/dist/transfer.js +388 -39
  14. package/package.json +4 -2
  15. package/src/ast.ts +82 -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 +904 -230
  20. package/src/codegen.ts +52 -18
  21. package/src/config.ts +39 -11
  22. package/src/environment.ts +485 -140
  23. package/src/errors.ts +22 -0
  24. package/src/evaluate.ts +486 -136
  25. package/src/exit-codes.ts +75 -0
  26. package/src/fingerprints.ts +2 -2
  27. package/src/index.ts +9 -4
  28. package/src/instances.ts +221 -66
  29. package/src/java-overrides.ts +241 -0
  30. package/src/json.ts +153 -72
  31. package/src/levels.ts +850 -0
  32. package/src/lexer.ts +97 -27
  33. package/src/liveness.ts +47 -91
  34. package/src/parallel-worker.ts +57 -28
  35. package/src/parallel.ts +116 -62
  36. package/src/parser.ts +703 -183
  37. package/src/pretty-print.ts +259 -0
  38. package/src/randomization.ts +24 -113
  39. package/src/resolve.ts +472 -41
  40. package/src/run-cli.ts +142 -44
  41. package/src/runtime.ts +63 -28
  42. package/src/spec-description.ts +509 -0
  43. package/src/standard-levels.ts +105 -0
  44. package/src/standard-modules.ts +223 -92
  45. package/src/subexpressions.ts +21 -35
  46. package/src/substitution-levels.ts +184 -0
  47. package/src/successors.ts +261 -46
  48. package/src/symmetry.ts +65 -21
  49. package/src/tableau.ts +43 -37
  50. package/src/tlc-ext.ts +120 -33
  51. package/src/tlc-module.ts +71 -47
  52. package/src/tlc-modules.ts +1022 -0
  53. package/src/tlc-order.ts +497 -0
  54. package/src/tlc-random.ts +411 -0
  55. package/src/tlc-summary.ts +109 -0
  56. package/src/transfer.ts +4 -4
  57. package/src/values.ts +1186 -164
package/src/evaluate.ts CHANGED
@@ -1,35 +1,51 @@
1
1
  import type {
2
2
  Bound,
3
3
  BoundPattern,
4
+ DecimalExpression,
4
5
  Definition,
5
6
  Expression,
6
7
  FunctionDefinition,
8
+ NumberExpression,
7
9
  OperatorParam,
8
10
  RecordField,
11
+ SourceRange,
9
12
  } from "./ast";
13
+ import { getKindString } from "./builtins";
10
14
  import { TlaEvaluationError } from "./errors";
15
+ import { EXIT_STATUS, setExitCode } from "./exit-codes";
11
16
  import { generateEvaluator } from "./codegen";
12
17
  import type { CheckerRuntime } from "./runtime";
13
18
  import { getReadVariables } from "./dependencies";
14
- import { mapChildren, substitutedExpressions } from "./instances";
19
+ import { contextualApplications, mapChildren, substitutedExpressions } from "./instances";
15
20
  import { isEnabled } from "./successors";
16
21
  import {
17
22
  applyFunction,
18
- checkInteger,
23
+ combineHash,
24
+ compareValues,
19
25
  createFunction,
20
- createSet,
26
+ createEnumeratedSet,
21
27
  createSortedFunction,
22
28
  createSortedSet,
29
+ createWrittenRecord,
23
30
  createTuple,
24
31
  formatValue,
32
+ getDomain,
33
+ getFunctionDomain,
34
+ getIntervalElements,
25
35
  getSequenceElements,
26
36
  getValueHash,
27
37
  isEnumerable,
38
+ isFunctionValue,
39
+ isOverriddenSet,
28
40
  isSameValue,
41
+ markLambdaFunction,
29
42
  isSetValue,
43
+ MAXIMUM_INTEGER,
44
+ MINIMUM_INTEGER,
30
45
  toFiniteSet,
31
46
  updateFunction,
32
47
  valuesEqual,
48
+ type LazyFunctionSource,
33
49
  type LazyFunctionValue,
34
50
  type Value,
35
51
  } from "./values";
@@ -42,6 +58,7 @@ export interface OperatorValue {
42
58
  arity: number;
43
59
  call: (args: Value[]) => Value;
44
60
  expression?: Expression;
61
+ literalBody?: Value;
45
62
  }
46
63
 
47
64
  export type BuiltinArgument = Value | OperatorValue;
@@ -65,7 +82,7 @@ export interface Closure {
65
82
  scope: Scope | undefined;
66
83
  }
67
84
 
68
- export type Operator = Closure | Builtin | OperatorValue;
85
+ type Operator = Closure | Builtin | OperatorValue;
69
86
 
70
87
  export interface Thunk {
71
88
  kind: "Thunk";
@@ -76,10 +93,7 @@ export interface Thunk {
76
93
  cache: Value | undefined;
77
94
  }
78
95
 
79
- export type Binding =
80
- | { kind: "Value"; value: Value }
81
- | Thunk
82
- | { kind: "Operator"; operator: Operator };
96
+ type Binding = { kind: "Value"; value: Value } | Thunk | { kind: "Operator"; operator: Operator };
83
97
 
84
98
  export interface Scope {
85
99
  name: string;
@@ -111,6 +125,12 @@ export type Evaluator = (context: Context) => Value;
111
125
 
112
126
  type ArgumentBinder = (context: Context) => Binding;
113
127
 
128
+ interface CompiledStep {
129
+ kind: "Field" | "Index";
130
+ name: string;
131
+ args: Evaluator[];
132
+ }
133
+
114
134
  type GlobalName =
115
135
  | { kind: "Variable"; index: number }
116
136
  | { kind: "Value"; value: Value }
@@ -118,9 +138,87 @@ type GlobalName =
118
138
 
119
139
  const EXCEPT_AT = "@";
120
140
 
121
- const describeLocation = (expression: Expression) =>
141
+ export const describeLocation = (expression: Expression) =>
122
142
  `line ${expression.location.line}, column ${expression.location.column}`;
123
143
 
144
+ export const formatRange = (range: SourceRange) =>
145
+ `line ${range.start.line}, col ${range.start.column} to line ${range.end.line}, col ${range.end.column} of module ${range.module}`;
146
+
147
+ export const describeRange = (expression: Expression) => {
148
+ const range = expression.location.range;
149
+ return range ? formatRange(range) : describeLocation(expression);
150
+ };
151
+
152
+ export const getConditionError = (expression: Expression, value: Value) =>
153
+ new TlaEvaluationError(
154
+ `A non-boolean expression (${getKindString(value)}) was used as the condition of an IF.\n${describeRange(expression)}`,
155
+ );
156
+
157
+ // TLC parses an infix chain of /\ as nested binary conjunctions and a bulleted list as one
158
+ // conjunction list, and it words the error differently for each.
159
+ export const getConjunctError = (
160
+ expression: Expression & { kind: "And" | "Or" },
161
+ index: number,
162
+ value: Value,
163
+ ) => {
164
+ const range = expression.location.range;
165
+ const isBulleted =
166
+ range?.start.line === expression.location.line &&
167
+ range.start.column === expression.location.column;
168
+ if (isBulleted || !range) {
169
+ const operand = expression.operands[index];
170
+ // SANY prints a numeral as its value instead of its location.
171
+ const description = operand.kind === "Number" ? String(operand.value) : describeRange(operand);
172
+ return new TlaEvaluationError(
173
+ `A non-boolean expression (${getKindString(value)}) was used as a formula in a conjunction.\n${description}`,
174
+ );
175
+ }
176
+ const end = expression.operands[Math.max(index, 1)].location.range?.end ?? range.end;
177
+ return new TlaEvaluationError(
178
+ `Attempted to evaluate an expression of form P /\\ Q when ${index === 0 ? "P" : "Q"} was\n${getKindString(value)}.\n${describeRange({ ...expression, location: { ...expression.location, range: { ...range, end } } })}`,
179
+ );
180
+ };
181
+
182
+ export const getFieldError =
183
+ (expression: Expression & { kind: "FieldAccess" }) => (record: Value) =>
184
+ `Attempted to select nonexistent field "${expression.field}" from the record\n${formatValue(record)}\n${describeRange(expression)}`;
185
+
186
+ export const getDomainOperator = (expression: Expression) => (value: Value) => {
187
+ if (!isFunctionValue(value)) {
188
+ throw new TlaEvaluationError(
189
+ `Attempted to apply the operator DOMAIN to a non-function\n(${getKindString(value)})\n${describeRange(expression)}`,
190
+ );
191
+ }
192
+ return getDomain(value);
193
+ };
194
+
195
+ export const getSubsetOperator =
196
+ (expression: Expression, builtin: Builtin) => (left: Value, right: Value) => {
197
+ if (!isSetValue(left) || isOverriddenSet(left)) {
198
+ throw new TlaEvaluationError(
199
+ `Attempted to evaluate an expression of form S \\subseteq T, but S was not enumerable.\n${describeRange(expression)}`,
200
+ );
201
+ }
202
+ return builtin.evaluate([left, right]);
203
+ };
204
+
205
+ // TLC rejects a decimal, or an integer literal too big for it, while it processes the spec.
206
+ export const createRealNumberError = (expression: DecimalExpression) =>
207
+ setExitCode(
208
+ new TlaEvaluationError(
209
+ `TLC can't handle real numbers: ${expression.text} at ${describeLocation(expression)}`,
210
+ ),
211
+ EXIT_STATUS.configParseError,
212
+ );
213
+
214
+ export const createBigNumberError = (expression: NumberExpression) =>
215
+ setExitCode(
216
+ new TlaEvaluationError(
217
+ `TLC can't handle a number this big: ${expression.text ?? expression.value} at ${describeLocation(expression)}`,
218
+ ),
219
+ EXIT_STATUS.error,
220
+ );
221
+
124
222
  export const lookupScope = (scope: Scope | undefined, name: string): Binding | undefined => {
125
223
  for (let current = scope; current; current = current.parent) {
126
224
  if (current.name === name) return current.binding;
@@ -134,11 +232,7 @@ export const bindValue = (scope: Scope | undefined, name: string, value: Value):
134
232
  parent: scope,
135
233
  });
136
234
 
137
- export const bindPattern = (
138
- scope: Scope | undefined,
139
- pattern: BoundPattern,
140
- value: Value,
141
- ): Scope => {
235
+ const bindPattern = (scope: Scope | undefined, pattern: BoundPattern, value: Value) => {
142
236
  if (pattern.kind === "Name") return bindValue(scope, pattern.name, value);
143
237
  const elements = getSequenceElements(value);
144
238
  if (!elements || elements.length !== pattern.names.length) {
@@ -149,7 +243,7 @@ export const bindPattern = (
149
243
  return pattern.names.reduce<Scope | undefined>(
150
244
  (current, name, index) => bindValue(current, name, elements[index]),
151
245
  scope,
152
- ) as Scope;
246
+ );
153
247
  };
154
248
 
155
249
  const withScope = (context: Context, scope: Scope | undefined): Context => ({
@@ -171,6 +265,7 @@ export const expectBoolean = (value: Value, description: string) => {
171
265
  export const getChildren = (expression: Expression): Expression[] => {
172
266
  switch (expression.kind) {
173
267
  case "Number":
268
+ case "Decimal":
174
269
  case "String":
175
270
  case "Identifier":
176
271
  case "OperatorReference":
@@ -296,13 +391,37 @@ export const isConstantLevel = (definition: Definition, environment: Environment
296
391
  return isConstant;
297
392
  };
298
393
 
394
+ const getBoundExplanation = (set: Value, text: string) => {
395
+ const quantified = `universally (\\A) or existentially (\\E) quantify over ${text}, because this would require checking an\ninfinite number of cases.`;
396
+ if (typeof set === "object" && set.kind === "SequenceSet") {
397
+ return `In TLA+, Seq(S) represents the set of all finite sequences whose elements come from the set S. Even when S\nis a finite set, the number of possible sequences in Seq(S) is unbounded because sequences can have any\nfinite length (e.g., length 0, 1, 2, and so on). As a result, TLC cannot evaluate expressions that\n${quantified} Note that for a finite set of sequences s, TLC handles s \\subseteq Seq(S).\nSee https://explain.tlapl.us/seq-unenumerable for additional details.`;
398
+ }
399
+ if (
400
+ typeof set === "object" &&
401
+ set.kind === "InfiniteSet" &&
402
+ set.name !== "STRING" &&
403
+ set.name !== "Any"
404
+ ) {
405
+ return `The set ${set.name} contains infinitely many elements. As a result, TLC cannot evaluate expressions that\n${quantified} Note that TLC handles set membership like T \\subseteq ${set.name} for any finite set T.`;
406
+ }
407
+ return undefined;
408
+ };
409
+
410
+ // TLC enumerates every set except the ones it implements in Java, so any other set fails as its own
411
+ // class does.
299
412
  export const getEnumeratedElements = (set: Value, expression: Expression) => {
300
- if (!isEnumerable(set)) {
413
+ if (!isSetValue(set) || isOverriddenSet(set)) {
414
+ const text = formatValue(set);
415
+ const explanation = getBoundExplanation(set, text);
301
416
  throw new TlaEvaluationError(
302
- `TLC encountered a non-enumerable quantifier bound ${formatValue(set)} at ${describeLocation(expression)}`,
417
+ explanation === undefined
418
+ ? `TLC encountered a non-enumerable quantifier bound\n${text}.\n${describeRange(expression)}`
419
+ : `TLC encountered the non-enumerable quantifier bound\n${text}\n${describeRange(expression)}\n${explanation}`,
303
420
  );
304
421
  }
305
- return toFiniteSet(set).elements;
422
+ return typeof set === "object" && set.kind === "Interval"
423
+ ? getIntervalElements(set)
424
+ : toFiniteSet(set).elements;
306
425
  };
307
426
 
308
427
  export const forEachBinding = (
@@ -336,14 +455,66 @@ export const bindArgument = (bounds: Bound[], scope: Scope | undefined, argument
336
455
  );
337
456
  };
338
457
 
458
+ // TLC prints a function it can't enumerate as its parameters and the location of its body. It
459
+ // prints only the first name of a group such as `m, n \in S`.
460
+ interface LazyFunctionTemplate {
461
+ parameters: { index: number; name: string }[];
462
+ body: string;
463
+ }
464
+
465
+ const lazyFunctionTemplates = new WeakMap<Expression, LazyFunctionTemplate>();
466
+
467
+ const getLazyFunctionTemplate = (bounds: Bound[], body: Expression) => {
468
+ const cached = lazyFunctionTemplates.get(body);
469
+ if (cached) return cached;
470
+ const range = body.location.range;
471
+ if (!range) return undefined;
472
+ const template = {
473
+ parameters: bounds.flatMap((bound, index) =>
474
+ index > 0 && bounds[index - 1].set === bound.set
475
+ ? []
476
+ : [
477
+ {
478
+ index,
479
+ name:
480
+ bound.pattern.kind === "Name"
481
+ ? bound.pattern.name
482
+ : `<<${bound.pattern.names.join(", ")}>>`,
483
+ },
484
+ ],
485
+ ),
486
+ body: formatRange(range),
487
+ };
488
+ lazyFunctionTemplates.set(body, template);
489
+ return template;
490
+ };
491
+
492
+ const getLazyFunctionSource = (bounds: Bound[], body: Expression, domain: Value) => {
493
+ const template = getLazyFunctionTemplate(bounds, body);
494
+ if (!template) return undefined;
495
+ const domains =
496
+ bounds.length > 1 && typeof domain === "object" && domain.kind === "ProductSet"
497
+ ? domain.factors
498
+ : [domain];
499
+ return {
500
+ parameters: template.parameters.map((parameter) => ({
501
+ name: parameter.name,
502
+ domain: domains[parameter.index],
503
+ })),
504
+ body: template.body,
505
+ };
506
+ };
507
+
339
508
  const createLazyFunction = (
340
509
  domain: Value,
341
510
  compute: (argument: Value, self: LazyFunctionValue) => Value,
342
- ): LazyFunctionValue => {
511
+ source: LazyFunctionSource | undefined,
512
+ ) => {
343
513
  const memo = new Map<number, Value[]>();
344
514
  const self: LazyFunctionValue = {
345
515
  kind: "LazyFunction",
346
516
  domain,
517
+ source,
347
518
  apply: (argument) => {
348
519
  const hash = getValueHash(argument);
349
520
  const bucket = memo.get(hash);
@@ -362,18 +533,29 @@ const createLazyFunction = (
362
533
  return self;
363
534
  };
364
535
 
365
- const createRecursiveFunction = (definition: FunctionDefinition, context: Context) =>
366
- createLazyFunction(getBoundsDomain(definition.bounds, context), (argument, self) =>
367
- evaluate(
368
- definition.body,
369
- withScope(
370
- context,
371
- bindArgument(definition.bounds, bindValue(undefined, definition.name, self), argument),
536
+ const createRecursiveFunction = (definition: FunctionDefinition, context: Context) => {
537
+ const domain = getBoundsDomain(definition.bounds, context);
538
+ const functionValue = createLazyFunction(
539
+ domain,
540
+ (argument, self) =>
541
+ evaluate(
542
+ definition.body,
543
+ withScope(
544
+ context,
545
+ bindArgument(definition.bounds, bindValue(undefined, definition.name, self), argument),
546
+ ),
372
547
  ),
373
- ),
548
+ getLazyFunctionSource(definition.bounds, definition.body, domain),
374
549
  );
550
+ return definition.bounds.length === 1 ? markLambdaFunction(functionValue) : functionValue;
551
+ };
552
+
553
+ const evaluateDefinitionBody = (definition: Definition, context: Context) =>
554
+ definition.kind === "Function"
555
+ ? createRecursiveFunction(definition, context)
556
+ : evaluate(definition.body, context);
375
557
 
376
- const evaluateDefinitionValue = (definition: Definition, context: Context): Value => {
558
+ const evaluateDefinitionValue = (definition: Definition, context: Context) => {
377
559
  const isConstant = isConstantLevel(definition, context.environment);
378
560
  const cached = isConstant ? context.environment.definitionValues.get(definition) : undefined;
379
561
  if (cached !== undefined) return cached;
@@ -383,11 +565,11 @@ const evaluateDefinitionValue = (definition: Definition, context: Context): Valu
383
565
  );
384
566
  }
385
567
  const topLevel = withScope(context, undefined);
386
- const value =
387
- definition.kind === "Function"
388
- ? createRecursiveFunction(definition, topLevel)
389
- : evaluate(definition.body, topLevel);
390
- if (isConstant) context.environment.definitionValues.set(definition, value);
568
+ if (!isConstant) return evaluateDefinitionBody(definition, topLevel);
569
+ const value = context.environment.runtime.evaluateAtStartup(() =>
570
+ evaluateDefinitionBody(definition, topLevel),
571
+ );
572
+ context.environment.definitionValues.set(definition, value);
391
573
  return value;
392
574
  };
393
575
 
@@ -477,11 +659,7 @@ const evaluateOperatorArgument = (expression: Expression, context: Context): Ope
477
659
  return operator;
478
660
  };
479
661
 
480
- export const callOperatorWithValues = (
481
- operator: Operator,
482
- args: Value[],
483
- context: Context,
484
- ): Value => {
662
+ const callOperatorWithValues = (operator: Operator, args: Value[], context: Context): Value => {
485
663
  if (operator.kind === "Builtin") return operator.evaluate(args, context);
486
664
  if (operator.kind === "Operator") return operator.call(args);
487
665
  const scope = operator.params.reduce<Scope | undefined>(
@@ -499,6 +677,11 @@ const toOperatorValue = (operator: Operator, context: Context): OperatorValue =>
499
677
  name: operator.name,
500
678
  arity: getArity(operator),
501
679
  call: (args) => callOperatorWithValues(operator, args, context),
680
+ literalBody:
681
+ operator.kind === "Closure" &&
682
+ (operator.body.kind === "Number" || operator.body.kind === "String")
683
+ ? operator.body.value
684
+ : undefined,
502
685
  };
503
686
 
504
687
  const isOperatorArgument = (expression: Expression, context: Context) => {
@@ -590,7 +773,7 @@ export const bindOperatorArguments = (
590
773
  return scope;
591
774
  };
592
775
 
593
- const callOperator = (operator: Operator, args: Expression[], context: Context): Value => {
776
+ const callOperator = (operator: Operator, args: Expression[], context: Context) => {
594
777
  if (operator.kind === "Closure") {
595
778
  return evaluate(
596
779
  operator.body,
@@ -609,7 +792,11 @@ const callOperator = (operator: Operator, args: Expression[], context: Context):
609
792
  );
610
793
  };
611
794
 
612
- export const bindDefinitions = (definitions: Definition[], context: Context): Scope | undefined =>
795
+ export const bindDefinitions = (
796
+ definitions: Definition[],
797
+ context: Context,
798
+ reevaluated?: ReadonlySet<Definition>,
799
+ ): Scope | undefined =>
613
800
  definitions.reduce<Scope | undefined>((scope, definition) => {
614
801
  const node: Scope = {
615
802
  name: definition.name,
@@ -633,7 +820,7 @@ export const bindDefinitions = (definitions: Definition[], context: Context): Sc
633
820
  };
634
821
  return node;
635
822
  }
636
- if (definition.params.length > 0) {
823
+ if (definition.params.length > 0 || reevaluated?.has(definition)) {
637
824
  node.binding = {
638
825
  kind: "Operator",
639
826
  operator: {
@@ -684,10 +871,10 @@ const evaluateLocalIdentifier = (name: string, expression: Expression, context:
684
871
  throw new TlaEvaluationError(`Unknown identifier ${name} at ${describeLocation(expression)}`);
685
872
  };
686
873
 
687
- const isTlcInteger = (value: number) =>
688
- Number.isInteger(value) && value <= 2147483647 && value >= -2147483648;
874
+ export const isTlcInteger = (value: number) =>
875
+ Number.isInteger(value) && value <= MAXIMUM_INTEGER && value >= MINIMUM_INTEGER;
689
876
 
690
- export const IMPURE_BUILTINS = new Set([
877
+ const IMPURE_BUILTINS = new Set([
691
878
  "Print",
692
879
  "PrintT",
693
880
  "Assert",
@@ -700,6 +887,58 @@ export const IMPURE_BUILTINS = new Set([
700
887
  export const isImpureBuiltin = (name: string, environment: Environment) =>
701
888
  IMPURE_BUILTINS.has(name) || environment.builtins.get(name)?.isStateLevel === true;
702
889
 
890
+ const impureDefinitions = new WeakMap<Definition, boolean>();
891
+
892
+ const isEvaluatedOnce = (definition: Definition, environment: Environment) =>
893
+ definition.kind === "Operator" &&
894
+ definition.params.length === 0 &&
895
+ isConstantLevel(definition, environment);
896
+
897
+ const callsImpureBuiltin = (expression: Expression, environment: Environment): boolean => {
898
+ const name =
899
+ expression.kind === "Identifier"
900
+ ? expression.name
901
+ : expression.kind === "Apply" || expression.kind === "OperatorReference"
902
+ ? expression.operator
903
+ : undefined;
904
+ if (name !== undefined && !isLocallyResolved(expression)) {
905
+ const definition = getDefinition(name, environment);
906
+ if (!definition && isImpureBuiltin(name, environment)) return true;
907
+ if (
908
+ definition &&
909
+ !isEvaluatedOnce(definition, environment) &&
910
+ isImpureDefinition(definition, environment)
911
+ ) {
912
+ return true;
913
+ }
914
+ }
915
+ return getChildren(expression).some((child) => callsImpureBuiltin(child, environment));
916
+ };
917
+
918
+ // TLC evaluates a constant definition without parameters once, so only other definitions pass
919
+ // on the side effects and random choices of the builtins they call.
920
+ const isImpureDefinition = (definition: Definition, environment: Environment): boolean => {
921
+ const cached = impureDefinitions.get(definition);
922
+ if (cached !== undefined) return cached;
923
+ impureDefinitions.set(definition, false);
924
+ const isImpure =
925
+ callsImpureBuiltin(definition.body, environment) ||
926
+ (definition.kind === "Function" &&
927
+ definition.bounds.some((bound) => callsImpureBuiltin(bound.set, environment)));
928
+ impureDefinitions.set(definition, isImpure);
929
+ return isImpure;
930
+ };
931
+
932
+ // TLC's next-state generator does not bind LET definitions to values, so each reference to one
933
+ // that calls an impure builtin evaluates it again.
934
+ export const getReevaluatedDefinitions = (definitions: Definition[], environment: Environment) =>
935
+ new Set(definitions.filter((definition) => isImpureDefinition(definition, environment)));
936
+
937
+ export const isImpure = (environment: Environment) =>
938
+ [...environment.definitions.values()].some((definition) =>
939
+ isImpureDefinition(definition, environment),
940
+ );
941
+
703
942
  const closedConstants = new WeakMap<Expression, boolean>();
704
943
 
705
944
  export const isClosedConstant = (expression: Expression, environment: Environment): boolean => {
@@ -742,7 +981,7 @@ const isClosedIn = (
742
981
  expression: Expression,
743
982
  environment: Environment,
744
983
  bound: ReadonlySet<string>,
745
- ): boolean =>
984
+ ) =>
746
985
  bound.size === 0
747
986
  ? isClosedConstant(expression, environment)
748
987
  : computeIsClosedConstant(expression, environment, bound);
@@ -760,28 +999,24 @@ const computeIsClosedConstant = (
760
999
  if (expression.kind === "Identifier") {
761
1000
  const global = findGlobalName(expression.name, environment);
762
1001
  if (global?.kind === "Value") return true;
763
- return (
764
- global?.kind === "Definition" &&
765
- global.definition.kind === "Operator" &&
766
- global.definition.params.length === 0 &&
767
- isConstantLevel(global.definition, environment)
768
- );
1002
+ return global?.kind === "Definition" && isEvaluatedOnce(global.definition, environment);
769
1003
  }
770
1004
  if (expression.kind === "Apply") {
771
1005
  if (isImpureBuiltin(expression.operator, environment) || expression.operator === "~>") {
772
1006
  return false;
773
1007
  }
774
1008
  const operator = resolveGlobalOperator(expression.operator, environment);
775
- const isPure =
776
- operator?.kind === "Builtin" ||
777
- (operator?.kind === "Closure" &&
778
- (() => {
779
- const definition = environment.definitions.get(
780
- environment.substitutions.get(expression.operator) ?? expression.operator,
781
- );
782
- return definition !== undefined && isConstantLevel(definition, environment);
783
- })());
784
- if (!isPure || operator === undefined) return false;
1009
+ if (operator?.kind !== "Builtin" && operator?.kind !== "Closure") return false;
1010
+ if (operator.kind === "Closure") {
1011
+ const definition = getDefinition(expression.operator, environment);
1012
+ if (
1013
+ !definition ||
1014
+ !isConstantLevel(definition, environment) ||
1015
+ isImpureDefinition(definition, environment)
1016
+ ) {
1017
+ return false;
1018
+ }
1019
+ }
785
1020
  const params = operator.kind === "Closure" ? operator.params : [];
786
1021
  const hasOperatorArgument = params.some((param) => param.arity > 0);
787
1022
  if (hasOperatorArgument) return false;
@@ -855,7 +1090,7 @@ export const getEvaluator = (expression: Expression, environment: Environment):
855
1090
 
856
1091
  const entryEvaluators = new WeakMap<Expression, Evaluator>();
857
1092
 
858
- const getEntryEvaluator = (expression: Expression, environment: Environment): Evaluator => {
1093
+ const getEntryEvaluator = (expression: Expression, environment: Environment) => {
859
1094
  const cached = entryEvaluators.get(expression);
860
1095
  if (cached) return cached;
861
1096
  const evaluator =
@@ -1031,18 +1266,79 @@ export const compileArgumentScope = (
1031
1266
  };
1032
1267
  };
1033
1268
 
1269
+ interface MemoizedCall {
1270
+ args: Value[];
1271
+ value: Value;
1272
+ }
1273
+
1274
+ const MEMOIZED_CALL_LIMIT = 1 << 16;
1275
+
1276
+ const callMemos = new WeakMap<Closure, { calls: Map<number, MemoizedCall[]>; size: number }>();
1277
+
1278
+ const getCallMemo = (closure: Closure) => {
1279
+ const cached = callMemos.get(closure);
1280
+ if (cached) return cached;
1281
+ const memo = { calls: new Map<number, MemoizedCall[]>(), size: 0 };
1282
+ callMemos.set(closure, memo);
1283
+ return memo;
1284
+ };
1285
+
1286
+ const getArgumentValues = (scope: Scope | undefined, arity: number) => {
1287
+ const values: Value[] = [];
1288
+ for (let current = scope; values.length < arity; current = current?.parent) {
1289
+ if (current?.binding.kind !== "Value") return undefined;
1290
+ values.push(current.binding.value);
1291
+ }
1292
+ return values;
1293
+ };
1294
+
1295
+ const isSameArguments = (left: Value[], right: Value[]) =>
1296
+ left.every((value, index) => isSameValue(value, right[index]));
1297
+
1298
+ // A constant-level operator without side effects yields the same value for the same arguments,
1299
+ // so its calls with evaluated arguments share one result.
1300
+ const isMemoizable = (expression: Expression & { kind: "Apply" }, environment: Environment) => {
1301
+ const definition = getDefinition(expression.operator, environment);
1302
+ return (
1303
+ definition?.kind === "Operator" &&
1304
+ definition.params.length > 0 &&
1305
+ definition.params.every((param) => param.arity === 0) &&
1306
+ isConstantLevel(definition, environment) &&
1307
+ !isImpureDefinition(definition, environment)
1308
+ );
1309
+ };
1310
+
1034
1311
  const compileClosureCall = (
1035
1312
  closure: Closure,
1036
1313
  args: Expression[],
1037
1314
  environment: Environment,
1315
+ isMemoized: boolean,
1038
1316
  ): Evaluator => {
1039
1317
  const bindArguments = compileArgumentScope(closure, args, environment);
1040
1318
  const compiled = { body: undefined as Evaluator | undefined };
1041
- return (context) => {
1042
- const scope = bindArguments(context);
1319
+ const evaluateBody = (scope: Scope | undefined, context: Context) => {
1043
1320
  compiled.body ??= getEntryEvaluator(closure.body, environment);
1044
1321
  return compiled.body({ environment, scope, state: context.state, next: context.next });
1045
1322
  };
1323
+ if (!isMemoized) return (context) => evaluateBody(bindArguments(context), context);
1324
+ const memo = getCallMemo(closure);
1325
+ return (context) => {
1326
+ const scope = bindArguments(context);
1327
+ const values = getArgumentValues(scope, args.length);
1328
+ if (!values) return evaluateBody(scope, context);
1329
+ let hash = values.length;
1330
+ for (const value of values) hash = combineHash(hash, getValueHash(value));
1331
+ const bucket = memo.calls.get(hash);
1332
+ const known = bucket?.find((call) => isSameArguments(call.args, values));
1333
+ if (known) return known.value;
1334
+ const value = evaluateBody(scope, context);
1335
+ if (memo.size < MEMOIZED_CALL_LIMIT) {
1336
+ memo.size++;
1337
+ if (bucket) bucket.push({ args: values, value });
1338
+ else memo.calls.set(hash, [{ args: values, value }]);
1339
+ }
1340
+ return value;
1341
+ };
1046
1342
  };
1047
1343
 
1048
1344
  const compileApply = (
@@ -1065,7 +1361,29 @@ const compileApply = (
1065
1361
  const global = isLocallyResolved(expression)
1066
1362
  ? undefined
1067
1363
  : resolveGlobalOperator(expression.operator, environment);
1068
- if (global?.kind === "Closure") return compileClosureCall(global, args, environment);
1364
+ if (global?.kind === "Closure") {
1365
+ return compileClosureCall(global, args, environment, isMemoizable(expression, environment));
1366
+ }
1367
+ if (global?.kind === "Builtin" && global.name === "DOMAIN") {
1368
+ const operand = getEvaluator(args[0], environment);
1369
+ const domain = getDomainOperator(expression);
1370
+ return (context) => domain(operand(context));
1371
+ }
1372
+ // TLC sorts the value of a constant TLCEval argument only where no context binds a name.
1373
+ if (global?.kind === "Builtin" && global.name === "TLCEval" && args.length === 1) {
1374
+ const operand = getEvaluator(args[0], environment);
1375
+ if (contextualApplications.has(expression) || isStateDependent(args[0], environment)) {
1376
+ return operand;
1377
+ }
1378
+ return (context) =>
1379
+ context.scope === undefined ? global.evaluate([operand(context)]) : operand(context);
1380
+ }
1381
+ if (global?.kind === "Builtin" && global.name === "\\subseteq" && args.length === 2) {
1382
+ const left = getEvaluator(args[0], environment);
1383
+ const right = getEvaluator(args[1], environment);
1384
+ const subset = getSubsetOperator(expression, global);
1385
+ return (context) => subset(left(context), right(context));
1386
+ }
1069
1387
  if (global?.kind === "Builtin") return compileBuiltinCall(global, args, environment);
1070
1388
  return (context) => {
1071
1389
  const operator = resolveOperator(expression.operator, context);
@@ -1134,7 +1452,7 @@ const compileQuantifier = (
1134
1452
  };
1135
1453
  };
1136
1454
 
1137
- const lookupKey = (pattern: BoundPattern, scope: Scope | undefined): Value | undefined => {
1455
+ const lookupKey = (pattern: BoundPattern, scope: Scope | undefined) => {
1138
1456
  if (pattern.kind === "Tuple") {
1139
1457
  return createTuple(
1140
1458
  pattern.names.map((name) => {
@@ -1147,10 +1465,21 @@ const lookupKey = (pattern: BoundPattern, scope: Scope | undefined): Value | und
1147
1465
  return binding?.kind === "Value" ? binding.value : undefined;
1148
1466
  };
1149
1467
 
1468
+ // TLC keeps a function constructor lazy when it is the function of an application, or the base of
1469
+ // an EXCEPT that is, so it evaluates the body only at the arguments it is applied to.
1470
+ const keptLazyExpressions = new WeakSet<Expression>();
1471
+
1472
+ export const keepLazy = (expression: Expression) => {
1473
+ if (expression.kind === "FunctionConstruction" || expression.kind === "Except") {
1474
+ keptLazyExpressions.add(expression);
1475
+ }
1476
+ };
1477
+
1150
1478
  const compileFunctionConstruction = (
1151
1479
  expression: Expression & { kind: "FunctionConstruction" },
1152
1480
  environment: Environment,
1153
1481
  ): Evaluator => {
1482
+ const isKeptLazy = keptLazyExpressions.has(expression);
1154
1483
  const body = getEvaluator(expression.body, environment);
1155
1484
  const bounds = expression.bounds;
1156
1485
  const bound = bounds[0];
@@ -1158,17 +1487,22 @@ const compileFunctionConstruction = (
1158
1487
  const set = getEvaluator(bound.set, environment);
1159
1488
  return (context) => {
1160
1489
  const domain = isSingleName ? set(context) : getBoundsDomain(bounds, context);
1161
- if (expression.isRecursive || !isEnumerable(domain)) {
1162
- return createLazyFunction(domain, (argument) =>
1163
- body(withScope(context, bindArgument(bounds, context.scope, argument))),
1490
+ if (expression.isRecursive || isKeptLazy || !isEnumerable(domain)) {
1491
+ const functionValue = createLazyFunction(
1492
+ domain,
1493
+ (argument) => body(withScope(context, bindArgument(bounds, context.scope, argument))),
1494
+ getLazyFunctionSource(bounds, expression.body, domain),
1164
1495
  );
1496
+ return bounds.length === 1 ? markLambdaFunction(functionValue) : functionValue;
1165
1497
  }
1166
1498
  if (isSingleName && bound.pattern.kind === "Name") {
1167
1499
  const name = bound.pattern.name;
1168
- const keys = toFiniteSet(domain).elements;
1169
- return createSortedFunction(
1170
- keys,
1171
- keys.map((key) => body(withScope(context, bindValue(context.scope, name, key)))),
1500
+ const keys = getFunctionDomain(domain);
1501
+ return markLambdaFunction(
1502
+ createSortedFunction(
1503
+ keys,
1504
+ keys.map((key) => body(withScope(context, bindValue(context.scope, name, key)))),
1505
+ ),
1172
1506
  );
1173
1507
  }
1174
1508
  const pairs: [Value, Value][] = [];
@@ -1188,21 +1522,22 @@ const compileExcept = (
1188
1522
  expression: Expression & { kind: "Except" },
1189
1523
  environment: Environment,
1190
1524
  ): Evaluator => {
1191
- const fn = getEvaluator(expression.function, environment);
1525
+ const isKeptLazy = keptLazyExpressions.has(expression);
1526
+ if (isKeptLazy) keepLazy(expression.function);
1527
+ const evaluateFunction = getEvaluator(expression.function, environment);
1192
1528
  const updates = expression.updates.map((update) => ({
1193
- path: update.path.map((step) =>
1529
+ path: update.path.map((step): CompiledStep =>
1194
1530
  step.kind === "Field"
1195
- ? { kind: "Field" as const, name: step.name, args: [] as Evaluator[] }
1531
+ ? { kind: "Field", name: step.name, args: [] }
1196
1532
  : {
1197
- kind: "Index" as const,
1533
+ kind: "Index",
1198
1534
  name: "",
1199
1535
  args: step.args.map((arg) => getEvaluator(arg, environment)),
1200
1536
  },
1201
1537
  ),
1202
1538
  value: getEvaluator(update.value, environment),
1203
1539
  }));
1204
- type CompiledStep = (typeof updates)[number]["path"][number];
1205
- const getKey = (step: CompiledStep, context: Context): Value => {
1540
+ const getKey = (step: CompiledStep, context: Context) => {
1206
1541
  if (step.kind === "Field") return step.name;
1207
1542
  if (step.args.length === 1) return step.args[0](context);
1208
1543
  return createTuple(step.args.map((arg) => arg(context)));
@@ -1217,12 +1552,15 @@ const compileExcept = (
1217
1552
  if (depth === path.length) {
1218
1553
  return value(withScope(context, bindValue(context.scope, EXCEPT_AT, current)));
1219
1554
  }
1220
- return updateFunction(current, getKey(path[depth], context), (previous) =>
1221
- update(previous, path, depth + 1, context, value),
1555
+ return updateFunction(
1556
+ current,
1557
+ getKey(path[depth], context),
1558
+ (previous) => update(previous, path, depth + 1, context, value),
1559
+ isKeptLazy && depth === 0,
1222
1560
  );
1223
1561
  };
1224
1562
  return (context) => {
1225
- let current = fn(context);
1563
+ let current = evaluateFunction(context);
1226
1564
  for (const item of updates) current = update(current, item.path, 0, context, item.value);
1227
1565
  return current;
1228
1566
  };
@@ -1244,7 +1582,8 @@ const compileSetComprehension = (
1244
1582
  "a set filter predicate",
1245
1583
  );
1246
1584
  const isReducible =
1247
- typeof base === "object" && (base.kind === "Set" || base.kind === "Interval");
1585
+ typeof base === "object" &&
1586
+ (base.kind === "Set" || base.kind === "MixedSet" || base.kind === "Interval");
1248
1587
  if (!isReducible) {
1249
1588
  if (isSetValue(base)) return { kind: "FilteredSet", base, test };
1250
1589
  throw new TlaEvaluationError(
@@ -1262,7 +1601,7 @@ const compileSetComprehension = (
1262
1601
  elements.push(mapped(withScope(context, scope)));
1263
1602
  return false;
1264
1603
  });
1265
- return createSet(elements);
1604
+ return createEnumeratedSet(elements);
1266
1605
  };
1267
1606
  }
1268
1607
  const set = expression.set;
@@ -1284,7 +1623,7 @@ const compileSetComprehension = (
1284
1623
  });
1285
1624
  if (chosen.value === undefined) {
1286
1625
  throw new TlaEvaluationError(
1287
- `Attempted to compute CHOOSE x \\in S : P, but no element of S satisfies P at ${describeLocation(expression)}`,
1626
+ `Attempted to compute the value of an expression of form\nCHOOSE x \\in S: P, but no element of S satisfied P.\n${describeRange(expression)}`,
1288
1627
  );
1289
1628
  }
1290
1629
  return chosen.value;
@@ -1292,7 +1631,7 @@ const compileSetComprehension = (
1292
1631
  };
1293
1632
 
1294
1633
  const compileRecord = (recordFields: RecordField[], environment: Environment): Evaluator => {
1295
- const fields = [...recordFields].sort((left, right) => (left.name < right.name ? -1 : 1));
1634
+ const fields = [...recordFields].sort((left, right) => compareValues(left.name, right.name));
1296
1635
  const hasDuplicates = fields.some(
1297
1636
  (field, index) => index > 0 && fields[index - 1].name === field.name,
1298
1637
  );
@@ -1303,7 +1642,16 @@ const compileRecord = (recordFields: RecordField[], environment: Environment): E
1303
1642
  fields.map((field, index): [Value, Value] => [field.name, values[index](context)]),
1304
1643
  );
1305
1644
  }
1306
- const names: Value[] = fields.map((field) => field.name);
1645
+ const names = fields.map((field) => field.name);
1646
+ const writtenNames = recordFields.map((field) => field.name);
1647
+ if (writtenNames.some((name, index) => name !== names[index])) {
1648
+ return (context) =>
1649
+ createWrittenRecord(
1650
+ names,
1651
+ values.map((value) => value(context)),
1652
+ writtenNames,
1653
+ );
1654
+ }
1307
1655
  return (context) =>
1308
1656
  createSortedFunction(
1309
1657
  names,
@@ -1311,25 +1659,21 @@ const compileRecord = (recordFields: RecordField[], environment: Environment): E
1311
1659
  );
1312
1660
  };
1313
1661
 
1314
- const createUnchanged = (expression: Expression, location: Expression["location"]): Expression => ({
1315
- kind: "Unchanged",
1316
- expression,
1317
- location,
1318
- });
1319
-
1320
- const throwing =
1321
- (createError: () => Error): Evaluator =>
1322
- () => {
1323
- throw createError();
1324
- };
1325
-
1326
1662
  const compile = (expression: Expression, environment: Environment): Evaluator => {
1327
1663
  const child = (node: Expression) => getEvaluator(node, environment);
1328
1664
  switch (expression.kind) {
1329
1665
  case "Number": {
1330
1666
  const value = expression.value;
1331
- return isTlcInteger(value) ? () => value : () => checkInteger(value);
1667
+ return isTlcInteger(value)
1668
+ ? () => value
1669
+ : () => {
1670
+ throw createBigNumberError(expression);
1671
+ };
1332
1672
  }
1673
+ case "Decimal":
1674
+ return () => {
1675
+ throw createRealNumberError(expression);
1676
+ };
1333
1677
  case "String": {
1334
1678
  const value = expression.value;
1335
1679
  return () => value;
@@ -1340,17 +1684,18 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1340
1684
  return compileApply(expression, environment);
1341
1685
  case "OperatorReference":
1342
1686
  case "Lambda":
1343
- return throwing(
1344
- () =>
1345
- new TlaEvaluationError(
1346
- `An operator cannot be used as a value at ${describeLocation(expression)}`,
1347
- ),
1348
- );
1687
+ return () => {
1688
+ throw new TlaEvaluationError(
1689
+ `An operator cannot be used as a value at ${describeLocation(expression)}`,
1690
+ );
1691
+ };
1349
1692
  case "And": {
1350
1693
  const operands = expression.operands.map(child);
1351
1694
  return (context) => {
1352
1695
  for (let index = 0; index < operands.length; index++) {
1353
- if (!expectBoolean(operands[index](context), "a conjunct")) return false;
1696
+ const value = operands[index](context);
1697
+ if (typeof value !== "boolean") throw getConjunctError(expression, index, value);
1698
+ if (!value) return false;
1354
1699
  }
1355
1700
  return true;
1356
1701
  };
@@ -1393,10 +1738,11 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1393
1738
  const condition = child(expression.condition);
1394
1739
  const consequent = child(expression.consequent);
1395
1740
  const alternative = child(expression.alternative);
1396
- return (context) =>
1397
- expectBoolean(condition(context), "an IF condition")
1398
- ? consequent(context)
1399
- : alternative(context);
1741
+ return (context) => {
1742
+ const value = condition(context);
1743
+ if (typeof value !== "boolean") throw getConditionError(expression, value);
1744
+ return value ? consequent(context) : alternative(context);
1745
+ };
1400
1746
  }
1401
1747
  case "Case": {
1402
1748
  const arms = expression.arms.map((arm) => ({
@@ -1421,19 +1767,18 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1421
1767
  case "Exists":
1422
1768
  return compileQuantifier(expression, environment);
1423
1769
  case "UnboundedQuantifier":
1424
- return throwing(
1425
- () =>
1426
- new TlaEvaluationError(
1427
- `TLC cannot evaluate an unbounded quantifier at ${describeLocation(expression)}`,
1428
- ),
1429
- );
1770
+ return () => {
1771
+ throw new TlaEvaluationError(
1772
+ `TLC cannot evaluate an unbounded quantifier at ${describeLocation(expression)}`,
1773
+ );
1774
+ };
1430
1775
  case "Choose":
1431
1776
  case "SetFilter":
1432
1777
  case "SetMap":
1433
1778
  return compileSetComprehension(expression, environment);
1434
1779
  case "SetEnumeration": {
1435
1780
  const elements = expression.elements.map(child);
1436
- return (context) => createSet(elements.map((element) => element(context)));
1781
+ return (context) => createEnumeratedSet(elements.map((element) => element(context)));
1437
1782
  }
1438
1783
  case "Tuple": {
1439
1784
  const elements = expression.elements.map(child);
@@ -1450,7 +1795,7 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1450
1795
  return compileRecord(expression.fields, environment);
1451
1796
  case "RecordSet": {
1452
1797
  const fields = [...expression.fields].sort((left, right) =>
1453
- left.name < right.name ? -1 : 1,
1798
+ compareValues(left.name, right.name),
1454
1799
  );
1455
1800
  const names = fields.map((field) => field.name);
1456
1801
  const sets = fields.map((field) => child(field.value));
@@ -1469,18 +1814,21 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1469
1814
  return binding.value;
1470
1815
  };
1471
1816
  case "FunctionApplication": {
1472
- const fn = child(expression.function);
1817
+ keepLazy(expression.function);
1818
+ const evaluateFunction = child(expression.function);
1473
1819
  const args = expression.args.map(child);
1474
1820
  if (args.length === 1) {
1475
1821
  const argument = args[0];
1476
- return (context) => applyFunction(fn(context), argument(context));
1822
+ return (context) => applyFunction(evaluateFunction(context), argument(context));
1477
1823
  }
1478
- return (context) => applyFunction(fn(context), createTuple(args.map((arg) => arg(context))));
1824
+ return (context) =>
1825
+ applyFunction(evaluateFunction(context), createTuple(args.map((arg) => arg(context))));
1479
1826
  }
1480
1827
  case "FieldAccess": {
1481
1828
  const record = child(expression.record);
1482
1829
  const field = expression.field;
1483
- return (context) => applyFunction(record(context), field);
1830
+ const describeMissing = getFieldError(expression);
1831
+ return (context) => applyFunction(record(context), field, describeMissing);
1484
1832
  }
1485
1833
  case "Product": {
1486
1834
  const factors = expression.factors.map(child);
@@ -1492,7 +1840,11 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1492
1840
  case "BoxAction":
1493
1841
  case "AngleAction": {
1494
1842
  const action = child(expression.action);
1495
- const unchanged = child(createUnchanged(expression.subscript, expression.location));
1843
+ const unchanged = child({
1844
+ kind: "Unchanged",
1845
+ expression: expression.subscript,
1846
+ location: expression.location,
1847
+ });
1496
1848
  const isBox = expression.kind === "BoxAction";
1497
1849
  return (context) =>
1498
1850
  isBox
@@ -1506,21 +1858,19 @@ const compile = (expression: Expression, environment: Environment): Evaluator =>
1506
1858
  case "Label":
1507
1859
  return child(expression.body);
1508
1860
  case "Subexpression":
1509
- return throwing(
1510
- () =>
1511
- new TlaEvaluationError(
1512
- `The subexpression reference at ${describeLocation(expression)} does not name a subexpression`,
1513
- ),
1514
- );
1861
+ return () => {
1862
+ throw new TlaEvaluationError(
1863
+ `The subexpression reference at ${describeLocation(expression)} does not name a subexpression`,
1864
+ );
1865
+ };
1515
1866
  case "Always":
1516
1867
  case "Eventually":
1517
1868
  case "WeakFairness":
1518
1869
  case "StrongFairness":
1519
- return throwing(
1520
- () =>
1521
- new TlaEvaluationError(
1522
- `TLC cannot evaluate a temporal formula in a state at ${describeLocation(expression)}`,
1523
- ),
1524
- );
1870
+ return () => {
1871
+ throw new TlaEvaluationError(
1872
+ `TLC cannot evaluate a temporal formula in a state at ${describeLocation(expression)}`,
1873
+ );
1874
+ };
1525
1875
  }
1526
1876
  };