ReviewOS

also looking at this

home-lang/home

fix(interpreter): keep the return value across deferred user calls

#929
Open mkantautas wants to merge fix/interpreter-defer-return-value into main
2 files +203 -84
packages/interpreter/tests/program_eval_test.zigmodified+173-0
Changes to packages/interpreter/tests/program_eval_test.zig
@@ -99,3 +99,176 @@ test "eval: runaway user recursion returns a runtime error" {
9999 \\}
100100 ));
101101}
102
103test "eval: deferred user function calls keep the pending return value" {
104 try runProgram(
105 \\struct Box {
106 \\ v: int,
107 \\}
108 \\
109 \\impl Box {
110 \\ fn get(self): int {
111 \\ defer cleanup()
112 \\ return self.v
113 \\ }
114 \\}
115 \\
116 \\fn cleanup() {
117 \\}
118 \\
119 \\fn direct(): int {
120 \\ defer cleanup()
121 \\ return 7
122 \\}
123 \\
124 \\fn in_if(): int {
125 \\ if true {
126 \\ defer cleanup()
127 \\ return 8
128 \\ }
129 \\ return 0
130 \\}
131 \\
132 \\fn in_else(): int {
133 \\ if false {
134 \\ return 0
135 \\ } else {
136 \\ defer cleanup()
137 \\ return 9
138 \\ }
139 \\ return 0
140 \\}
141 \\
142 \\fn in_while(): int {
143 \\ while true {
144 \\ defer cleanup()
145 \\ return 10
146 \\ }
147 \\ return 0
148 \\}
149 \\
150 \\fn in_range_loop(): int {
151 \\ for i in 0..3 {
152 \\ defer cleanup()
153 \\ return i + 11
154 \\ }
155 \\ return 0
156 \\}
157 \\
158 \\fn in_array_loop(): int {
159 \\ for x in [12] {
160 \\ defer cleanup()
161 \\ return x
162 \\ }
163 \\ return 0
164 \\}
165 \\
166 \\fn in_block(): int {
167 \\ {
168 \\ defer cleanup()
169 \\ return 13
170 \\ }
171 \\ return 0
172 \\}
173 \\
174 \\fn main() {
175 \\ let closure = fn(): int {
176 \\ defer cleanup()
177 \\ return 14
178 \\ }
179 \\ let box = Box { v: 15 }
180 \\ assert(direct() == 7)
181 \\ assert(in_if() == 8)
182 \\ assert(in_else() == 9)
183 \\ assert(in_while() == 10)
184 \\ assert(in_range_loop() == 11)
185 \\ assert(in_array_loop() == 12)
186 \\ assert(in_block() == 13)
187 \\ assert(closure() == 14)
188 \\ assert(box.get() == 15)
189 \\}
190 );
191}
192
193test "eval: a value left behind by a call inside a defer does not replace the pending return" {
194 try runProgram(
195 \\fn noop() {
196 \\}
197 \\
198 \\fn cleanup() {
199 \\ let c = fn(): int {
200 \\ return 99
201 \\ }
202 \\ c()
203 \\ defer noop()
204 \\}
205 \\
206 \\fn outer(): int {
207 \\ defer cleanup()
208 \\ return 7
209 \\}
210 \\
211 \\fn main() {
212 \\ assert(outer() == 7)
213 \\}
214 );
215}
216
217test "eval: a return inside a deferred block still sets the returned value" {
218 try runProgram(
219 \\fn final_return(): int {
220 \\ defer {
221 \\ return 5
222 \\ }
223 \\ return 7
224 \\}
225 \\
226 \\fn earlier_return(): int {
227 \\ defer {
228 \\ return 6
229 \\ 0
230 \\ }
231 \\ return 7
232 \\}
233 \\
234 \\fn main() {
235 \\ assert(final_return() == 5)
236 \\ assert(earlier_return() == 6)
237 \\}
238 );
239}
240
241test "eval: deferred calls run in reverse order before the return value is used" {
242 try runProgram(
243 \\fn inner(): int {
244 \\ defer note(1)
245 \\ return 99
246 \\}
247 \\
248 \\let mut order = 0
249 \\
250 \\fn note(n: int) {
251 \\ order = order * 10 + n
252 \\}
253 \\
254 \\fn first() {
255 \\ note(2)
256 \\}
257 \\
258 \\fn second() {
259 \\ assert(inner() == 99)
260 \\ note(3)
261 \\}
262 \\
263 \\fn outer(): int {
264 \\ defer first()
265 \\ defer second()
266 \\ return 7
267 \\}
268 \\
269 \\fn main() {
270 \\ assert(outer() == 7)
271 \\ assert(order == 132)
272 \\}
273 );
274}