Skip to content

Commit 4064dfa

Browse files
authored
Merge pull request #3255 from apalache-mc/igor/variants3248
Make Variants compatible with TLC
2 parents f4bd670 + cd59dd1 commit 4064dfa

File tree

8 files changed

+321
-11
lines changed

8 files changed

+321
-11
lines changed

.unreleased/bug-fixes/tlc3255.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make Variants compatible with TLC (#3255)

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ test-coverage:
3939
# Run the integration tests
4040
integration: package
4141
test/mdx-test.py --debug "$(TEST_FILTER)"
42+
test/mdx-test.py --test_file=test/tla/tlc-integration-tests.md --debug "$(TEST_FILTER)"
4243

4344
# Generate fixtures needed to test quint integration
4445
quint-fixtures: tla-io/src/test/resources/tictactoe.json tla-io/src/test/resources/clockSync3.json test/tla/booleans.qnt.json

src/tla/Apalache.tla

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
* encoded inside Apalache. For the moment, these operators are mirrored in
99
* the class at.forsyte.apalache.tla.lir.oper.ApalacheOper.
1010
*
11-
* Igor Konnov, Jure Kukovec, Informal Systems 2020-2022
11+
* Igor Konnov, Jure Kukovec, Informal Systems 2020-2022
12+
* Igor Konnov, konnov.phd, 2026
1213
*)
1314

1415
(**
@@ -77,6 +78,9 @@ SetAsFun(__S) ==
7778
LOCAL INSTANCE Integers
7879
MkSeq(__N, __F(_)) ==
7980
\* This is the TLC implementation. Apalache does it differently.
81+
\* If __F is not defined on i \in 1..__N, TLC fails.
82+
\* Apalache evaluates symbolically. This is why definitions
83+
\* like `FunAsSeq` work.
8084
[ __i \in (1..__N) |-> __F(__i) ]
8185

8286
\* required by our default definition of FoldSeq and FunAsSeq

src/tla/Variants.tla

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* variants.
1010
*
1111
* Igor Konnov, Informal Systems, 2021-2022
12+
* Igor Konnov, konnov.phd, 2026
1213
*)
1314

1415
(**
@@ -32,8 +33,9 @@ UNIT == "U_OF_UNIT"
3233
* (Str, a) => Tag(a) | b
3334
*)
3435
Variant(__tagName, __value) ==
35-
\* default untyped implementation
36-
[ tag |-> __tagName, value |-> __value ]
36+
\* Default untyped implementation.
37+
\* See the discussion in https://github.com/tlaplus/tlaplus/pull/1284
38+
[ t \in { __tagName } |-> __value ]
3739

3840
(**
3941
* Filter a set of variants with the provided tag value.
@@ -48,7 +50,7 @@ Variant(__tagName, __value) ==
4850
*)
4951
VariantFilter(__tagName, __S) ==
5052
\* default untyped implementation
51-
{ __d.value : __d \in { __e \in __S: __e.tag = __tagName } }
53+
{ __f[__tagName]: __f \in { __e \in __S: __tagName \in DOMAIN __e } }
5254

5355
(**
5456
* Get the tag name that is associated with a variant.
@@ -62,7 +64,7 @@ VariantFilter(__tagName, __S) ==
6264
*)
6365
VariantTag(__variant) ==
6466
\* default untyped implementation
65-
__variant.tag
67+
CHOOSE __tag \in DOMAIN __variant: TRUE
6668

6769
(**
6870
* Return the value associated with the tag, when the tag equals to __tagName.
@@ -79,8 +81,8 @@ VariantTag(__variant) ==
7981
*)
8082
VariantGetOrElse(__tagName, __variant, __defaultValue) ==
8183
\* default untyped implementation
82-
IF __variant.tag = __tagName
83-
THEN __variant.value
84+
IF __tagName \in DOMAIN __variant
85+
THEN __variant[__tagName]
8486
ELSE __defaultValue
8587

8688

@@ -100,6 +102,6 @@ VariantGetOrElse(__tagName, __variant, __defaultValue) ==
100102
*)
101103
VariantGetUnsafe(__tagName, __variant) ==
102104
\* the default untyped implementation
103-
__variant.value
105+
__variant[__tagName]
104106

105107
===============================================================================

test/tla/TestApalache.tla

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---------------------------- MODULE TestApalache -----------------------------
2+
(*
3+
* Test the module Apalache against TLC.
4+
*
5+
* Igor Konnov, 2026
6+
*)
7+
8+
EXTENDS Sequences, Integers, Apalache
9+
10+
VARIABLE
11+
\* This is needed for TLC to work
12+
\* @type: Int;
13+
xxx
14+
15+
Init == xxx = 0
16+
Next == UNCHANGED xxx
17+
18+
(********************* DEFINITIONS ********************************)
19+
\* @type: Seq(Int);
20+
seq345 == <<3, 4, 5>>
21+
22+
\* @type: Seq(Int);
23+
seqEmpty == <<>>
24+
25+
\* @type: Seq(Int);
26+
seq26970 == <<2, 6, 9, 7, 0>>
27+
28+
Add(i, j) == i + j
29+
30+
IsOdd(i) == i % 2 = 1
31+
32+
Concat(s, t) == s \o t
33+
34+
\* The set { 1, 2, 3, 5, 6, 7 }
35+
\* that has duplicates in its internal representation
36+
Set123567 ==
37+
LET one == 1 IN
38+
LET two == 2 IN
39+
LET three == one + two IN
40+
\* 3 and three are equal but the set constructor will miss that
41+
{ one, two, 3, three, three, 5, 6, 7, 7 }
42+
43+
\* The set { 1, 3, 5, 7 }
44+
\* that has ghost cells 2, 6 in its internal representation
45+
Set1357 ==
46+
{ i \in Set123567: i % 2 /= 0 }
47+
48+
\* The set { 2, 6 }
49+
\* that has ghost cells 1, 3, three, 5 in its internal representation
50+
Set26 ==
51+
{ i \in Set123567: i % 2 = 0 }
52+
53+
\* The set { 2, 3, 6 }
54+
\* that has ghost cells 1, 3, three, 5 and non-ghost cells 2, 3, 6
55+
Set263 ==
56+
Set26 \union { 3 }
57+
58+
SetEmpty ==
59+
\* create an empty set yet avoiding constant propagation
60+
LET x == 1 IN
61+
{ 1 } \ { x }
62+
63+
(********************* TESTS ********************************)
64+
65+
TestFoldSeq26970 ==
66+
ApaFoldSeqLeft(Add, 0, seq26970) = 2 + 6 + 9 + 7
67+
68+
TestFoldSeq697 ==
69+
ApaFoldSeqLeft(Add, 0, SubSeq(seq26970, 2, 4)) = 6 + 9 + 7
70+
71+
TestFoldSeq345 ==
72+
ApaFoldSeqLeft(Add, 0, Tail(seq345)) = 4 + 5
73+
74+
TestFoldSeqEmpty ==
75+
ApaFoldSeqLeft(Add, 0, seqEmpty) = 0
76+
77+
TestFoldSeqFlatten ==
78+
ApaFoldSeqLeft(Concat, <<>>, <<seq345, seqEmpty, seq26970>>) = <<3, 4, 5, 2, 6, 9, 7, 0>>
79+
80+
TestMkSeqDouble ==
81+
LET Double(i) == 2 * i IN
82+
MkSeq(4, Double) = <<2, 4, 6, 8>>
83+
84+
TestMkSeqEmpty ==
85+
LET Double(i) == 2 * i IN
86+
MkSeq(0, Double) = << >>
87+
88+
TestMkSeqConcat ==
89+
LET Double(i) == 2 * i IN
90+
LET Triple(i) == 3 * i IN
91+
MkSeq(2, Double) \o MkSeq(3, Triple) = <<2, 4, 3, 6, 9>>
92+
93+
TestMkSeqSubSeq ==
94+
LET Triple(i) == 3 * i IN
95+
SubSeq(MkSeq(5, Triple), 2, 3) = <<6, 9>>
96+
97+
TestMkSeqLen ==
98+
LET Double(i) == 2 * i IN
99+
Len(MkSeq(4, Double)) = 4
100+
101+
TestMkSeqFold ==
102+
LET Double(i) == 2 * i IN
103+
ApaFoldSeqLeft(Add, 0, MkSeq(4, Double)) = 20
104+
105+
TestFoldSeqOverSelectSeq ==
106+
ApaFoldSeqLeft(Add, 0, SelectSeq(seq345, IsOdd)) = 3 + 5
107+
108+
TestFunAsSeq3 ==
109+
LET f == [ i \in 1..3 |-> i + 1 ] IN
110+
FunAsSeq(f, 3, 3) = <<2, 3, 4>>
111+
112+
TestFunAsSeqEmpty ==
113+
LET
114+
\* @type: Int -> Int;
115+
f == [ i \in {} |-> i ]
116+
IN
117+
FunAsSeq(f, 0, 0) = << >>
118+
119+
\* TLC fails here, so we do not include this test
120+
TestFunAsSeqLargerCapacity ==
121+
LET f == [ i \in 1..3 |-> i + 1 ] IN
122+
\* provide a larger upper bound
123+
FunAsSeq(f, 3, 10) = <<2, 3, 4>>
124+
125+
TestGuessSet ==
126+
LET S ==
127+
Guess({ T \in { SetEmpty, Set263, Set1357 }: 6 \in T })
128+
IN
129+
S = Set263
130+
131+
TestGuessSetFromTwo ==
132+
LET S ==
133+
Guess({ T \in { SetEmpty, Set263, Set1357 }: 3 \in T })
134+
IN
135+
S \in { Set263, Set1357 }
136+
137+
TestGuessInSingleton ==
138+
LET x == Guess({ 3 }) IN
139+
x = 3
140+
141+
TestGuessEmpty ==
142+
LET x == Guess(SetEmpty) IN
143+
\* This expression is equivalent to TRUE.
144+
\* We are using to avoid constant simplification of TRUE.
145+
x <= 0 \/ x > 0
146+
147+
TestApaFoldSet ==
148+
LET \* @type: (Set(Int), Int) => Set(Int);
149+
A(p,q) == p \union {q}
150+
IN
151+
ApaFoldSet( A, {4,4}, {1,1,2,2,3,3} ) = {1,2,3,4}
152+
153+
\* this test is a disjunction of all smaller tests
154+
AllTests ==
155+
/\ TestFoldSeq26970
156+
/\ TestFoldSeq697
157+
/\ TestFoldSeq345
158+
/\ TestFoldSeqEmpty
159+
/\ TestFoldSeqFlatten
160+
/\ TestFoldSeqOverSelectSeq
161+
/\ TestMkSeqDouble
162+
/\ TestMkSeqEmpty
163+
/\ TestMkSeqConcat
164+
/\ TestMkSeqSubSeq
165+
/\ TestMkSeqLen
166+
/\ TestMkSeqFold
167+
/\ TestFunAsSeq3
168+
/\ TestFunAsSeqEmpty
169+
/\ TestGuessSet
170+
/\ TestGuessSetFromTwo
171+
/\ TestGuessInSingleton
172+
/\ TestApaFoldSet
173+
\* TLC fails here
174+
\*/\ TestGuessEmpty
175+
\*/\ TestFunAsSeqLargerCapacity
176+
177+
===============================================================================

test/tla/TestVariants.tla

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22
(*
33
* Functional tests for operators over variants.
44
* We introduce a trivial state machine and write tests as state invariants.
5+
*
6+
* Igor Konnov, Informal Systems, 2022
7+
* Igor Konnov, konnov.phd, 2026
58
*)
69

710
EXTENDS Integers, FiniteSets, Apalache, Variants
811

9-
Init == TRUE
10-
Next == TRUE
12+
VARIABLE
13+
\* This is needed for Apalache to work
14+
\* @type: Int;
15+
x
16+
17+
Init == x = 0
18+
Next == UNCHANGED x
1119

1220
(* DEFINITIONS *)
1321

@@ -38,7 +46,11 @@ TestVariantTag ==
3846

3947
TestVariantGetUnsafe ==
4048
\* The unsafe version gives us only a type guarantee.
41-
VariantGetUnsafe("A", VarB) \in Int
49+
/\ VariantGetUnsafe("A", VarA) = 1
50+
\* TLC fails on the expression below.
51+
\* Apalache works, as the second condition holds true.
52+
\*/\ \/ VariantTag(VarB) = "A"
53+
\* \/ VariantGetUnsafe("B", VarB) \in Int
4254

4355
TestVariantGetOrElse ==
4456
\* When the tag name is different from the actual one, return the default value.

test/tla/test.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
INIT Init
2+
NEXT Next
3+
INVARIANT AllTests

0 commit comments

Comments
 (0)