Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/tests/Normalize.test.cpp
2723 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
3
#include "Fixture.h"
4
5
#include "Luau/AstQuery.h"
6
#include "Luau/Common.h"
7
#include "Luau/Type.h"
8
#include "ScopedFlags.h"
9
#include "doctest.h"
10
11
#include "Luau/Normalize.h"
12
#include <memory>
13
14
LUAU_FASTINT(LuauTypeInferRecursionLimit)
15
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
16
LUAU_FASTINT(LuauNormalizeUnionLimit)
17
LUAU_FASTFLAG(LuauIntegerType)
18
LUAU_FASTFLAG(DebugLuauForceOldSolver)
19
LUAU_FASTFLAG(LuauOverloadGetsInstantiated)
20
LUAU_FASTFLAG(LuauReplacerRespectsReboundGenerics)
21
LUAU_FASTFLAG(LuauUnifier2HandleMismatchedPacks2)
22
23
using namespace Luau;
24
25
TEST_SUITE_BEGIN("isSubtype");
26
27
TEST_CASE_FIXTURE(IsSubtypeFixture, "primitives")
28
{
29
check(R"(
30
local a = 41
31
local b = 32
32
33
local c = "hello"
34
local d = "world"
35
)");
36
37
TypeId a = requireType("a");
38
TypeId b = requireType("b");
39
TypeId c = requireType("c");
40
TypeId d = requireType("d");
41
42
CHECK(isSubtype(b, a));
43
CHECK(isSubtype(d, c));
44
CHECK(!isSubtype(d, a));
45
}
46
47
TEST_CASE_FIXTURE(IsSubtypeFixture, "functions")
48
{
49
check(R"(
50
function a(x: number): number return x end
51
function b(x: number): number return x end
52
53
function c(x: number?): number return x end
54
function d(x: number): number? return x end
55
)");
56
57
TypeId a = requireType("a");
58
TypeId b = requireType("b");
59
TypeId c = requireType("c");
60
TypeId d = requireType("d");
61
62
CHECK(isSubtype(b, a));
63
CHECK(isSubtype(c, a));
64
CHECK(!isSubtype(d, a));
65
CHECK(isSubtype(a, d));
66
}
67
68
TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_functions_with_no_head")
69
{
70
check(R"(
71
local a: (...number) -> ()
72
local b: (...number?) -> ()
73
)");
74
75
TypeId a = requireType("a");
76
TypeId b = requireType("b");
77
78
CHECK(isSubtype(b, a));
79
CHECK(!isSubtype(a, b));
80
}
81
82
TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_function_with_head")
83
{
84
ScopedFastFlag _{FFlag::DebugLuauForceOldSolver, false};
85
86
check(R"(
87
local a: (...number) -> ()
88
local b: (number, number) -> ()
89
)");
90
91
TypeId a = requireType("a");
92
TypeId b = requireType("b");
93
94
CHECK(!isSubtype(b, a));
95
CHECK(isSubtype(a, b));
96
}
97
98
TEST_CASE_FIXTURE(IsSubtypeFixture, "union")
99
{
100
check(R"(
101
local a: number | string
102
local b: number
103
local c: string
104
local d: number?
105
)");
106
107
TypeId a = requireType("a");
108
TypeId b = requireType("b");
109
TypeId c = requireType("c");
110
TypeId d = requireType("d");
111
112
CHECK(isSubtype(b, a));
113
CHECK(!isSubtype(a, b));
114
115
CHECK(isSubtype(c, a));
116
CHECK(!isSubtype(a, c));
117
118
CHECK(!isSubtype(d, a));
119
CHECK(!isSubtype(a, d));
120
121
CHECK(isSubtype(b, d));
122
CHECK(!isSubtype(d, b));
123
}
124
125
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_union_prop")
126
{
127
check(R"(
128
local a: {x: number}
129
local b: {x: number?}
130
)");
131
132
TypeId a = requireType("a");
133
TypeId b = requireType("b");
134
135
if (!FFlag::DebugLuauForceOldSolver)
136
CHECK(!isSubtype(a, b)); // table properties are invariant
137
else
138
CHECK(isSubtype(a, b));
139
CHECK(!isSubtype(b, a));
140
}
141
142
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_with_any_prop")
143
{
144
check(R"(
145
local a: {x: number}
146
local b: {x: any}
147
)");
148
149
TypeId a = requireType("a");
150
TypeId b = requireType("b");
151
152
if (!FFlag::DebugLuauForceOldSolver)
153
CHECK(!isSubtype(a, b)); // table properties are invariant
154
else
155
CHECK(isSubtype(a, b));
156
CHECK(!isSubtype(b, a));
157
}
158
159
TEST_CASE_FIXTURE(IsSubtypeFixture, "intersection")
160
{
161
check(R"(
162
local a: number & string
163
local b: number
164
local c: string
165
local d: number & nil
166
)");
167
168
TypeId a = requireType("a");
169
TypeId b = requireType("b");
170
TypeId c = requireType("c");
171
TypeId d = requireType("d");
172
173
CHECK(!isSubtype(b, a));
174
CHECK(isSubtype(a, b));
175
176
CHECK(!isSubtype(c, a));
177
CHECK(isSubtype(a, c));
178
179
// These types are both equivalent to never
180
CHECK(isSubtype(d, a));
181
CHECK(isSubtype(a, d));
182
}
183
184
TEST_CASE_FIXTURE(IsSubtypeFixture, "union_and_intersection")
185
{
186
check(R"(
187
local a: number & string
188
local b: number | nil
189
)");
190
191
TypeId a = requireType("a");
192
TypeId b = requireType("b");
193
194
CHECK(!isSubtype(b, a));
195
CHECK(isSubtype(a, b));
196
}
197
198
TEST_CASE_FIXTURE(IsSubtypeFixture, "tables")
199
{
200
check(R"(
201
local a: {x: number}
202
local b: {x: any}
203
local c: {y: number}
204
local d: {x: number, y: number}
205
)");
206
207
TypeId a = requireType("a");
208
TypeId b = requireType("b");
209
TypeId c = requireType("c");
210
TypeId d = requireType("d");
211
212
if (!FFlag::DebugLuauForceOldSolver)
213
CHECK(!isSubtype(a, b)); // table properties are invariant
214
else
215
CHECK(isSubtype(a, b));
216
CHECK(!isSubtype(b, a));
217
218
CHECK(!isSubtype(c, a));
219
CHECK(!isSubtype(a, c));
220
221
CHECK(isSubtype(d, a));
222
CHECK(!isSubtype(a, d));
223
224
if (!FFlag::DebugLuauForceOldSolver)
225
CHECK(!isSubtype(d, b)); // table properties are invariant
226
else
227
CHECK(isSubtype(d, b));
228
CHECK(!isSubtype(b, d));
229
}
230
231
TEST_CASE_FIXTURE(IsSubtypeFixture, "table_indexers_are_invariant")
232
{
233
ScopedFastFlag _{FFlag::DebugLuauForceOldSolver, false};
234
235
check(R"(
236
local a: {[string]: number}
237
local b: {[string]: any}
238
local c: {[string]: number}
239
)");
240
241
TypeId a = requireType("a");
242
TypeId b = requireType("b");
243
TypeId c = requireType("c");
244
245
CHECK(!isSubtype(b, a));
246
CHECK(!isSubtype(a, b));
247
248
CHECK(isSubtype(c, a));
249
CHECK(isSubtype(a, c));
250
}
251
252
TEST_CASE_FIXTURE(IsSubtypeFixture, "mismatched_indexers")
253
{
254
ScopedFastFlag _{FFlag::DebugLuauForceOldSolver, false};
255
256
check(R"(
257
local a: {x: number}
258
local b: {[string]: number}
259
local c: {}
260
)");
261
262
TypeId a = requireType("a");
263
TypeId b = requireType("b");
264
TypeId c = requireType("c");
265
266
CHECK(isSubtype(b, a));
267
CHECK(!isSubtype(a, b));
268
269
CHECK(!isSubtype(c, b));
270
CHECK(isSubtype(b, c));
271
}
272
273
#if 0
274
TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table")
275
{
276
check(R"(
277
type A = {method: (A) -> ()}
278
local a: A
279
280
type B = {method: (any) -> ()}
281
local b: B
282
283
type C = {method: (C) -> ()}
284
local c: C
285
286
type D = {method: (D) -> (), another: (D) -> ()}
287
local d: D
288
289
type E = {method: (A) -> (), another: (E) -> ()}
290
local e: E
291
)");
292
293
TypeId a = requireType("a");
294
TypeId b = requireType("b");
295
TypeId c = requireType("c");
296
TypeId d = requireType("d");
297
TypeId e = requireType("e");
298
299
CHECK(isSubtype(b, a));
300
CHECK(!isSubtype(a, b));
301
302
CHECK(isSubtype(c, a));
303
CHECK(isSubtype(a, c));
304
305
CHECK(!isSubtype(d, a));
306
CHECK(!isSubtype(a, d));
307
308
CHECK(isSubtype(e, a));
309
CHECK(!isSubtype(a, e));
310
}
311
#endif
312
313
TEST_CASE_FIXTURE(IsSubtypeFixture, "extern_types")
314
{
315
createSomeExternTypes(getFrontend());
316
317
check(""); // Ensure that we have a main Module.
318
319
TypeId p = getFrontend().globals.globalScope->lookupType("Parent")->type;
320
TypeId c = getFrontend().globals.globalScope->lookupType("Child")->type;
321
TypeId u = getFrontend().globals.globalScope->lookupType("Unrelated")->type;
322
323
CHECK(isSubtype(c, p));
324
CHECK(!isSubtype(p, c));
325
CHECK(!isSubtype(u, p));
326
CHECK(!isSubtype(p, u));
327
}
328
329
#if 0
330
TEST_CASE_FIXTURE(IsSubtypeFixture, "metatable" * doctest::expected_failures{1})
331
{
332
check(R"(
333
local T = {}
334
T.__index = T
335
function T.new()
336
return setmetatable({}, T)
337
end
338
339
function T:method() end
340
341
local a: typeof(T.new)
342
local b: {method: (any) -> ()}
343
)");
344
345
TypeId a = requireType("a");
346
TypeId b = requireType("b");
347
348
CHECK(isSubtype(a, b));
349
}
350
#endif
351
352
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_is_unknown_union_error")
353
{
354
check(R"(
355
local err = 5.nope.nope -- err is now an error type
356
local a : any
357
local b : (unknown | typeof(err))
358
)");
359
360
TypeId a = requireType("a");
361
TypeId b = requireType("b");
362
363
CHECK(isSubtype(a, b));
364
CHECK(isSubtype(b, a));
365
CHECK_EQ("*error-type*", toString(requireType("err")));
366
}
367
368
TEST_CASE_FIXTURE(IsSubtypeFixture, "any_intersect_T_is_T")
369
{
370
check(R"(
371
local a : (any & string)
372
local b : string
373
local c : number
374
)");
375
376
TypeId a = requireType("a");
377
TypeId b = requireType("b");
378
TypeId c = requireType("c");
379
380
CHECK(isSubtype(a, b));
381
CHECK(isSubtype(b, a));
382
CHECK(!isSubtype(a, c));
383
CHECK(!isSubtype(c, a));
384
}
385
386
TEST_CASE_FIXTURE(IsSubtypeFixture, "error_suppression")
387
{
388
check("");
389
390
TypeId any = getBuiltins()->anyType;
391
TypeId err = getBuiltins()->errorType;
392
TypeId str = getBuiltins()->stringType;
393
TypeId unk = getBuiltins()->unknownType;
394
395
CHECK(!isSubtype(any, err));
396
CHECK(isSubtype(err, any));
397
398
CHECK(!isSubtype(any, str));
399
CHECK(isSubtype(str, any));
400
401
// We have added this as an exception - the set of inhabitants of any is exactly the set of inhabitants of unknown (since error has no
402
// inhabitants). any = err | unknown, so under semantic subtyping, {} U unknown = unknown
403
if (!FFlag::DebugLuauForceOldSolver)
404
{
405
CHECK(isSubtype(any, unk));
406
}
407
else
408
{
409
CHECK(!isSubtype(any, unk));
410
}
411
412
if (!FFlag::DebugLuauForceOldSolver)
413
{
414
CHECK(isSubtype(err, str));
415
}
416
else
417
{
418
CHECK(!isSubtype(err, str));
419
}
420
421
CHECK(!isSubtype(str, err));
422
423
CHECK(!isSubtype(err, unk));
424
CHECK(!isSubtype(unk, err));
425
426
CHECK(isSubtype(str, unk));
427
CHECK(!isSubtype(unk, str));
428
}
429
430
TEST_SUITE_END();
431
432
struct NormalizeFixture : Fixture
433
{
434
TypeArena arena;
435
InternalErrorReporter iceHandler;
436
UnifierSharedState unifierState{&iceHandler};
437
438
NormalizeFixture() {}
439
440
std::shared_ptr<const NormalizedType> toNormalizedType(const std::string& annotation, int expectedErrors = 0)
441
{
442
getFrontend();
443
normalizer->clearCaches();
444
CheckResult result = check("type _Res = " + annotation);
445
LUAU_REQUIRE_ERROR_COUNT(expectedErrors, result);
446
447
if (!FFlag::DebugLuauForceOldSolver)
448
{
449
SourceModule* sourceModule = getMainSourceModule();
450
REQUIRE(sourceModule);
451
AstNode* node = findNodeAtPosition(*sourceModule, {0, 5});
452
REQUIRE(node);
453
AstStatTypeAlias* alias = node->as<AstStatTypeAlias>();
454
REQUIRE(alias);
455
TypeId* originalTy = getMainModule()->astResolvedTypes.find(alias->type);
456
REQUIRE(originalTy);
457
return normalizer->normalize(*originalTy);
458
}
459
else
460
{
461
std::optional<TypeId> ty = lookupType("_Res");
462
REQUIRE(ty);
463
return normalizer->normalize(*ty);
464
}
465
}
466
467
std::shared_ptr<const NormalizedType> normalize(TypeId ty)
468
{
469
// Force the frontend;
470
getFrontend();
471
return normalizer->normalize(ty);
472
}
473
474
TypeId typeFromNormal(const NormalizedType& norm)
475
{
476
// Force the fontend
477
getFrontend();
478
return normalizer->typeFromNormal(norm);
479
}
480
481
bool isInhabited(const NormalizedType* norm)
482
{
483
return normalizer->isInhabited(norm) == NormalizationResult::True;
484
}
485
486
TypeId normal(const std::string& annotation)
487
{
488
// Force the frontend;
489
getFrontend();
490
std::shared_ptr<const NormalizedType> norm = toNormalizedType(annotation);
491
REQUIRE(norm);
492
return normalizer->typeFromNormal(*norm);
493
}
494
495
Frontend& getFrontend() override
496
{
497
if (frontend)
498
return *frontend;
499
500
Frontend& f = Fixture::getFrontend();
501
globalScope = std::make_unique<Scope>(f.builtinTypes->anyTypePack);
502
normalizer = std::make_unique<Normalizer>(&arena, f.builtinTypes, NotNull{&unifierState}, f.getLuauSolverMode());
503
registerHiddenTypes(f);
504
505
return *frontend;
506
}
507
508
Scope* getGlobalScope()
509
{
510
return globalScope.get();
511
}
512
513
private:
514
std::unique_ptr<Normalizer> normalizer = nullptr;
515
std::unique_ptr<Scope> globalScope = nullptr;
516
};
517
518
TEST_SUITE_BEGIN("Normalize");
519
520
TEST_CASE_FIXTURE(NormalizeFixture, "string_intersection_is_commutative")
521
{
522
auto c4 = toString(normal(R"(
523
string & (string & Not<"a"> & Not<"b">)
524
)"));
525
auto c4Reverse = toString(normal(R"(
526
(string & Not<"a"> & Not<"b">) & string
527
)"));
528
CHECK(c4 == c4Reverse);
529
CHECK_EQ("string & ~\"a\" & ~\"b\"", c4);
530
531
auto c5 = toString(normal(R"(
532
(string & Not<"a"> & Not<"b">) & (string & Not<"b"> & Not<"c">)
533
)"));
534
auto c5Reverse = toString(normal(R"(
535
(string & Not<"b"> & Not<"c">) & (string & Not<"a"> & Not<"c">)
536
)"));
537
CHECK(c5 == c5Reverse);
538
CHECK_EQ("string & ~\"a\" & ~\"b\" & ~\"c\"", c5);
539
540
auto c6 = toString(normal(R"(
541
("a" | "b") & (string & Not<"b"> & Not<"c">)
542
)"));
543
auto c6Reverse = toString(normal(R"(
544
(string & Not<"b"> & Not<"c">) & ("a" | "b")
545
)"));
546
CHECK(c6 == c6Reverse);
547
CHECK_EQ("\"a\"", c6);
548
549
auto c7 = toString(normal(R"(
550
string & ("b" | "c")
551
)"));
552
auto c7Reverse = toString(normal(R"(
553
("b" | "c") & string
554
)"));
555
CHECK(c7 == c7Reverse);
556
CHECK_EQ("\"b\" | \"c\"", c7);
557
558
auto c8 = toString(normal(R"(
559
(string & Not<"a"> & Not<"b">) & ("b" | "c")
560
)"));
561
auto c8Reverse = toString(normal(R"(
562
("b" | "c") & (string & Not<"a"> & Not<"b">)
563
)"));
564
CHECK(c8 == c8Reverse);
565
CHECK_EQ("\"c\"", c8);
566
auto c9 = toString(normal(R"(
567
("a" | "b") & ("b" | "c")
568
)"));
569
auto c9Reverse = toString(normal(R"(
570
("b" | "c") & ("a" | "b")
571
)"));
572
CHECK(c9 == c9Reverse);
573
CHECK_EQ("\"b\"", c9);
574
575
auto l = toString(normal(R"(
576
(string | number) & ("a" | true)
577
)"));
578
auto r = toString(normal(R"(
579
("a" | true) & (string | number)
580
)"));
581
CHECK(l == r);
582
CHECK_EQ("\"a\"", l);
583
}
584
585
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string")
586
{
587
CHECK("number" == toString(normal(R"(
588
(number | string) & Not<string>
589
)")));
590
}
591
592
TEST_CASE_FIXTURE(NormalizeFixture, "negate_string_from_cofinite_string_intersection")
593
{
594
CHECK("number" == toString(normal(R"(
595
(number | (string & Not<"hello"> & Not<"world">)) & Not<string>
596
)")));
597
}
598
599
TEST_CASE_FIXTURE(NormalizeFixture, "no_op_negation_is_dropped")
600
{
601
CHECK("number" == toString(normal(R"(
602
number & Not<string>
603
)")));
604
}
605
606
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_negation")
607
{
608
CHECK("string" == toString(normal(R"(
609
(string & Not<"hello">) | "hello"
610
)")));
611
}
612
613
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy")
614
{
615
CHECK("number | string | true" == toString(normal(R"(
616
(string | number | boolean | nil) & Not<false | nil>
617
)")));
618
}
619
620
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_truthy_expressed_as_intersection")
621
{
622
CHECK("number | string | true" == toString(normal(R"(
623
(string | number | boolean | nil) & Not<false> & Not<nil>
624
)")));
625
}
626
627
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error")
628
{
629
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & AAA)", 1);
630
REQUIRE(norm);
631
CHECK("*error-type*" == toString(typeFromNormal(*norm)));
632
}
633
634
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_not_error")
635
{
636
std::shared_ptr<const NormalizedType> norm = toNormalizedType(R"(string & Not<)", 1);
637
REQUIRE(norm);
638
CHECK("*error-type*" == toString(typeFromNormal(*norm)));
639
}
640
641
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union")
642
{
643
CHECK(R"("alpha" | "beta" | "gamma")" == toString(normal(R"(
644
("alpha" | "beta") | "gamma"
645
)")));
646
}
647
648
TEST_CASE_FIXTURE(NormalizeFixture, "union_of_negations")
649
{
650
CHECK(R"(string & ~"world")" == toString(normal(R"(
651
(string & Not<"hello"> & Not<"world">) | (string & Not<"goodbye"> & Not<"world">)
652
)")));
653
}
654
655
TEST_CASE_FIXTURE(NormalizeFixture, "disjoint_negations_normalize_to_string")
656
{
657
CHECK(R"(string)" == toString(normal(R"(
658
(string & Not<"hello"> & Not<"world">) | (string & Not<"goodbye">)
659
)")));
660
}
661
662
TEST_CASE_FIXTURE(NormalizeFixture, "negate_boolean")
663
{
664
CHECK("true" == toString(normal(R"(
665
boolean & Not<false>
666
)")));
667
}
668
669
TEST_CASE_FIXTURE(NormalizeFixture, "negate_boolean_2")
670
{
671
CHECK("never" == toString(normal(R"(
672
true & Not<true>
673
)")));
674
}
675
676
TEST_CASE_FIXTURE(NormalizeFixture, "double_negation")
677
{
678
CHECK("number" == toString(normal(R"(
679
number & Not<Not<any>>
680
)")));
681
}
682
683
TEST_CASE_FIXTURE(NormalizeFixture, "negate_any")
684
{
685
CHECK("number" == toString(normal(R"(
686
number & Not<any>
687
)")));
688
}
689
690
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_function_and_top_function")
691
{
692
CHECK("() -> ()" == toString(normal(R"(
693
fun & (() -> ())
694
)")));
695
}
696
697
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_function_and_top_function_reverse")
698
{
699
CHECK("() -> ()" == toString(normal(R"(
700
(() -> ()) & fun
701
)")));
702
}
703
704
TEST_CASE_FIXTURE(NormalizeFixture, "union_function_and_top_function")
705
{
706
CHECK("function" == toString(normal(R"(
707
fun | (() -> ())
708
)")));
709
}
710
711
TEST_CASE_FIXTURE(NormalizeFixture, "negated_function_is_anything_except_a_function")
712
{
713
if (FFlag::LuauIntegerType)
714
{
715
CHECK("(boolean | buffer | integer | number | string | table | thread | userdata)?" == toString(normal(R"(
716
Not<fun>
717
)")));
718
}
719
else
720
{
721
CHECK("(boolean | buffer | number | string | table | thread | userdata)?" == toString(normal(R"(
722
Not<fun>
723
)")));
724
}
725
}
726
727
TEST_CASE_FIXTURE(NormalizeFixture, "specific_functions_cannot_be_negated")
728
{
729
CHECK(nullptr == toNormalizedType("Not<(boolean) -> boolean>", !FFlag::DebugLuauForceOldSolver ? 1 : 0));
730
}
731
732
TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited")
733
{
734
// this test was used to fix a bug in normalization when working with intersections/unions of the same type.
735
736
TypeId a = arena.addType(FunctionType{getBuiltins()->emptyTypePack, getBuiltins()->anyTypePack, std::nullopt, false});
737
TypeId c = arena.addType(IntersectionType{{a, a}});
738
739
std::shared_ptr<const NormalizedType> n = normalize(c);
740
REQUIRE(n);
741
742
CHECK(isInhabited(n.get()));
743
}
744
745
TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean")
746
{
747
if (FFlag::LuauIntegerType)
748
{
749
CHECK("(buffer | function | integer | number | string | table | thread | userdata)?" == toString(normal(R"(
750
Not<boolean>
751
)")));
752
}
753
else
754
{
755
CHECK("(buffer | function | number | string | table | thread | userdata)?" == toString(normal(R"(
756
Not<boolean>
757
)")));
758
}
759
}
760
761
TEST_CASE_FIXTURE(Fixture, "higher_order_function_normalization")
762
{
763
check(R"(
764
function apply(f, x)
765
return f(x)
766
end
767
768
local a = apply(function(x: number) return x + x end, 5)
769
)");
770
771
TypeId aType = requireType("a");
772
CHECK_MESSAGE(isNumber(follow(aType)), "Expected a number but got ", toString(aType));
773
}
774
775
TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation")
776
{
777
// CLI-117088 - Inferring the type of a higher order function with an annotation sometimes doesn't fully constrain the type (there are free types
778
// left over).
779
if (!FFlag::DebugLuauForceOldSolver)
780
return;
781
check(R"(
782
function apply<a, b>(f: (a) -> b, x)
783
return f(x)
784
end
785
)");
786
787
CHECK_EQ("<a, b>((a) -> b, a) -> b", toString(requireType("apply")));
788
}
789
790
TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly")
791
{
792
CheckResult result = check(R"(
793
local Cyclic = {}
794
function Cyclic.get()
795
return Cyclic
796
end
797
)");
798
799
LUAU_REQUIRE_NO_ERRORS(result);
800
801
TypeId ty = requireType("Cyclic");
802
if (!FFlag::DebugLuauForceOldSolver)
803
CHECK_EQ("t1 where t1 = { get: () -> t1 }", toString(ty, {true}));
804
else
805
CHECK_EQ("t1 where t1 = {| get: () -> t1 |}", toString(ty, {true}));
806
}
807
808
TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types")
809
{
810
createSomeExternTypes(getFrontend());
811
812
CheckResult result = check(R"(
813
export type t0 = { a: Child }
814
export type t1 = { a: typeof(string.byte) }
815
)");
816
817
LUAU_REQUIRE_NO_ERRORS(result);
818
}
819
820
TEST_CASE_FIXTURE(Fixture, "intersection_combine_on_bound_self")
821
{
822
CheckResult result = check(R"(
823
export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))
824
)");
825
826
LUAU_REQUIRE_ERRORS(result);
827
}
828
829
TEST_CASE_FIXTURE(NormalizeFixture, "unions_of_extern_types")
830
{
831
createSomeExternTypes(getFrontend());
832
CHECK("Parent | Unrelated" == toString(normal("Parent | Unrelated")));
833
CHECK("Parent" == toString(normal("Parent | Child")));
834
CHECK("Parent | Unrelated" == toString(normal("Parent | Child | Unrelated")));
835
}
836
837
TEST_CASE_FIXTURE(NormalizeFixture, "intersections_of_extern_types")
838
{
839
createSomeExternTypes(getFrontend());
840
CHECK("Child" == toString(normal("Parent & Child")));
841
CHECK("never" == toString(normal("Child & Unrelated")));
842
}
843
844
TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_extern_types_with_intersection")
845
{
846
createSomeExternTypes(getFrontend());
847
CHECK("Child" == toString(normal("(Child | Unrelated) & Child")));
848
}
849
850
TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom")
851
{
852
CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>")));
853
}
854
855
TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection")
856
{
857
CheckResult result = check(R"(
858
type A = any?
859
type B = A & A
860
)");
861
862
std::optional<TypeId> t = lookupType("B");
863
REQUIRE(t);
864
865
std::shared_ptr<const NormalizedType> nt = normalize(*t);
866
REQUIRE(nt);
867
868
CHECK("any" == toString(typeFromNormal(*nt)));
869
}
870
871
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union")
872
{
873
// T where T = any & (number | T)
874
TypeId t = arena.addType(BlockedType{});
875
TypeId u = arena.addType(UnionType{{getBuiltins()->numberType, t}});
876
asMutable(t)->ty.emplace<IntersectionType>(IntersectionType{{getBuiltins()->anyType, u}});
877
878
std::shared_ptr<const NormalizedType> nt = normalize(t);
879
REQUIRE(nt);
880
881
CHECK("number" == toString(typeFromNormal(*nt)));
882
}
883
884
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection")
885
{
886
// t1 where t1 = (string & t1) | string
887
TypeId boundTy = arena.addType(BlockedType{});
888
TypeId intersectTy = arena.addType(IntersectionType{{getBuiltins()->stringType, boundTy}});
889
TypeId unionTy = arena.addType(UnionType{{getBuiltins()->stringType, intersectTy}});
890
asMutable(boundTy)->reassign(Type{BoundType{unionTy}});
891
892
std::shared_ptr<const NormalizedType> nt = normalize(unionTy);
893
894
CHECK("string" == toString(typeFromNormal(*nt)));
895
}
896
897
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions")
898
{
899
// t1 where t1 = (string & t1) | string
900
TypeId boundTy = arena.addType(BlockedType{});
901
TypeId unionTy = arena.addType(UnionType{{getBuiltins()->stringType, boundTy}});
902
TypeId intersectionTy = arena.addType(IntersectionType{{getBuiltins()->stringType, unionTy}});
903
asMutable(boundTy)->reassign(Type{BoundType{intersectionTy}});
904
905
std::shared_ptr<const NormalizedType> nt = normalize(intersectionTy);
906
907
CHECK("string" == toString(typeFromNormal(*nt)));
908
}
909
910
TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable")
911
{
912
CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>")));
913
}
914
915
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_extern_types")
916
{
917
createSomeExternTypes(getFrontend());
918
CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not<Child>) | Unrelated")));
919
920
if (FFlag::LuauIntegerType)
921
{
922
CHECK("((userdata & ~Child) | boolean | buffer | function | integer | number | string | table | thread)?" == toString(normal("Not<Child>")));
923
CHECK("never" == toString(normal("Not<Parent> & Child")));
924
CHECK(
925
"((userdata & ~Parent) | Child | boolean | buffer | function | integer | number | string | table | thread)?" ==
926
toString(normal("Not<Parent> | Child"))
927
);
928
CHECK("(boolean | buffer | function | integer | number | string | table | thread)?" == toString(normal("Not<cls>")));
929
CHECK(
930
"(Parent | Unrelated | boolean | buffer | function | integer | number | string | table | thread)?" ==
931
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>"))
932
);
933
}
934
else
935
{
936
CHECK("((userdata & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<Child>")));
937
CHECK("never" == toString(normal("Not<Parent> & Child")));
938
CHECK(
939
"((userdata & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" ==
940
toString(normal("Not<Parent> | Child"))
941
);
942
CHECK("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not<cls>")));
943
CHECK(
944
"(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" ==
945
toString(normal("Not<cls & Not<Parent> & Not<Child> & Not<Unrelated>>"))
946
);
947
}
948
CHECK("Child" == toString(normal("(Child | Unrelated) & Not<Unrelated>")));
949
}
950
951
TEST_CASE_FIXTURE(NormalizeFixture, "extern_types_and_unknown")
952
{
953
createSomeExternTypes(getFrontend());
954
CHECK("Parent" == toString(normal("Parent & unknown")));
955
}
956
957
TEST_CASE_FIXTURE(NormalizeFixture, "extern_types_and_never")
958
{
959
createSomeExternTypes(getFrontend());
960
CHECK("never" == toString(normal("Parent & never")));
961
}
962
963
TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type")
964
{
965
CHECK("table" == toString(normal("{} | tbl")));
966
CHECK("{ }" == toString(normal("{} & tbl")));
967
CHECK("never" == toString(normal("number & tbl")));
968
}
969
970
TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables")
971
{
972
CHECK(nullptr == toNormalizedType("Not<{}>", !FFlag::DebugLuauForceOldSolver ? 1 : 0));
973
if (FFlag::LuauIntegerType)
974
CHECK("(boolean | buffer | function | integer | number | string | thread | userdata)?" == toString(normal("Not<tbl>")));
975
else
976
CHECK("(boolean | buffer | function | number | string | thread | userdata)?" == toString(normal("Not<tbl>")));
977
CHECK("table" == toString(normal("Not<Not<tbl>>")));
978
}
979
980
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_blocked_types")
981
{
982
Type blocked{BlockedType{}};
983
984
std::shared_ptr<const NormalizedType> norm = normalize(&blocked);
985
986
CHECK_EQ(typeFromNormal(*norm), &blocked);
987
}
988
989
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_is_exactly_number")
990
{
991
std::shared_ptr<const NormalizedType> number = normalize(getBuiltins()->numberType);
992
// 1. all types for which Types::number say true for, NormalizedType::isExactlyNumber should say true as well
993
CHECK(Luau::isNumber(getBuiltins()->numberType) == number->isExactlyNumber());
994
// 2. isExactlyNumber should handle cases like `number & number`
995
TypeId intersection = arena.addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->numberType}});
996
std::shared_ptr<const NormalizedType> normIntersection = normalize(intersection);
997
CHECK(normIntersection->isExactlyNumber());
998
999
// 3. isExactlyNumber should reject things that are definitely not precisely numbers `number | any`
1000
1001
TypeId yoonion = arena.addType(UnionType{{getBuiltins()->anyType, getBuiltins()->numberType}});
1002
std::shared_ptr<const NormalizedType> unionIntersection = normalize(yoonion);
1003
CHECK(!unionIntersection->isExactlyNumber());
1004
}
1005
1006
TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown")
1007
{
1008
auto nt = toNormalizedType("Not<string> | Not<number>");
1009
CHECK(nt);
1010
CHECK(nt->isUnknown());
1011
CHECK(toString(typeFromNormal(*nt)) == "unknown");
1012
}
1013
1014
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props")
1015
{
1016
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1017
1018
CHECK("{ x: string }" == toString(normal("{ read x: string } & { x: string }"), {true}));
1019
CHECK("{ x: string }" == toString(normal("{ x: string } & { read x: string }"), {true}));
1020
}
1021
1022
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_2")
1023
{
1024
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1025
1026
CHECK(R"({ x: "hello" })" == toString(normal(R"({ x: "hello" } & { x: string })"), {true}));
1027
CHECK(R"(never)" == toString(normal(R"({ x: "hello" } & { x: "world" })"), {true}));
1028
}
1029
1030
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_3")
1031
{
1032
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1033
1034
CHECK(R"({ read x: "hello" })" == toString(normal(R"({ read x: "hello" } & { read x: string })"), {true}));
1035
CHECK("never" == toString(normal(R"({ read x: "hello" } & { read x: "world" })"), {true}));
1036
}
1037
1038
TEST_CASE_FIXTURE(NormalizeFixture, "final_types_are_cached")
1039
{
1040
std::shared_ptr<const NormalizedType> na1 = normalize(getBuiltins()->numberType);
1041
std::shared_ptr<const NormalizedType> na2 = normalize(getBuiltins()->numberType);
1042
1043
CHECK(na1 == na2);
1044
}
1045
1046
TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_not_cached")
1047
{
1048
TypeId a = arena.freshType(getBuiltins(), getGlobalScope());
1049
1050
std::shared_ptr<const NormalizedType> na1 = normalize(a);
1051
std::shared_ptr<const NormalizedType> na2 = normalize(a);
1052
1053
CHECK(na1 != na2);
1054
}
1055
1056
TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown")
1057
{
1058
TypeId notUnknown = arena.addType(NegationType{getBuiltins()->unknownType});
1059
TypeId type = arena.addType(IntersectionType{{getBuiltins()->numberType, notUnknown}});
1060
std::shared_ptr<const NormalizedType> normalized = normalize(type);
1061
1062
CHECK("never" == toString(typeFromNormal(*normalized.get())));
1063
}
1064
1065
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_1")
1066
{
1067
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 165};
1068
this->unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
1069
TypeId t1 = arena.addType(TableType{});
1070
TypeId t2 = arena.addType(TableType{});
1071
TypeId t3 = arena.addType(IntersectionType{{t1, t2}});
1072
asMutable(t1)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t2)}};
1073
asMutable(t2)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t1)}};
1074
1075
std::shared_ptr<const NormalizedType> normalized = normalize(t3);
1076
CHECK(normalized);
1077
}
1078
1079
TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_2")
1080
{
1081
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 165};
1082
this->unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit;
1083
TypeId t1 = arena.addType(TableType{});
1084
TypeId t2 = arena.addType(TableType{});
1085
TypeId t3 = arena.addType(IntersectionType{{t1, t2}});
1086
asMutable(t1)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t3)}};
1087
asMutable(t2)->ty.get_if<TableType>()->props = {{"foo", Property::readonly(t1)}};
1088
1089
std::shared_ptr<const NormalizedType> normalized = normalize(t3);
1090
CHECK(normalized);
1091
}
1092
1093
TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_with_optional_prop")
1094
{
1095
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1096
1097
// { x: ~(false?) }
1098
TypeId t1 = arena.addType(TableType{TableType::Props{{"x", getBuiltins()->truthyType}}, std::nullopt, TypeLevel{}, TableState::Sealed});
1099
1100
// { x: number? }?
1101
TypeId t2 = arena.addType(
1102
UnionType{
1103
{arena.addType(TableType{TableType::Props{{"x", getBuiltins()->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed}),
1104
getBuiltins()->nilType}
1105
}
1106
);
1107
1108
TypeId intersection = arena.addType(IntersectionType{{t2, t1}});
1109
1110
auto norm = normalize(intersection);
1111
REQUIRE(norm);
1112
1113
TypeId ty = typeFromNormal(*norm);
1114
CHECK("{ x: number }" == toString(ty));
1115
}
1116
1117
TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy")
1118
{
1119
ScopedFastFlag sff[] = {
1120
{FFlag::DebugLuauForceOldSolver, false}, // Only because it affects the stringification of free types
1121
};
1122
1123
TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope());
1124
TypeId notTruthy = arena.addType(NegationType{getBuiltins()->truthyType}); // ~~(false?)
1125
1126
TypeId intersectionTy = arena.addType(IntersectionType{{freeTy, notTruthy}}); // 'a & ~~(false?)
1127
1128
auto norm = normalize(intersectionTy);
1129
REQUIRE(norm);
1130
1131
TypeId result = typeFromNormal(*norm);
1132
1133
CHECK("'a & (false?)" == toString(result));
1134
}
1135
1136
TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering")
1137
{
1138
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false}; // Affects stringification of free types.
1139
1140
TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope());
1141
TypeId orderA = arena.addType(IntersectionType{{freeTy, getBuiltins()->stringType}});
1142
auto normA = normalize(orderA);
1143
REQUIRE(normA);
1144
CHECK_EQ("'a & string", toString(typeFromNormal(*normA)));
1145
1146
TypeId orderB = arena.addType(IntersectionType{{getBuiltins()->stringType, freeTy}});
1147
auto normB = normalize(orderB);
1148
REQUIRE(normB);
1149
CHECK_EQ("'a & string", toString(typeFromNormal(*normB)));
1150
}
1151
1152
TEST_CASE_FIXTURE(NormalizeFixture, "tyvar_limit_one_sided_intersection" * doctest::timeout(0.5))
1153
{
1154
std::vector<TypeId> options;
1155
for (auto i = 0; i < 120; i++)
1156
options.push_back(arena.freshType(getBuiltins(), getGlobalScope()));
1157
1158
TypeId target = arena.addType(
1159
IntersectionType{
1160
{getBuiltins()->unknownType, arena.addType(UnionType{std::move(options)})},
1161
}
1162
);
1163
1164
// If we try to normalize 120 free variables against `unknown`, then exit
1165
// early and claim we've hit the limit.
1166
auto norm = normalize(target);
1167
REQUIRE(!norm);
1168
}
1169
1170
TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow")
1171
{
1172
if (FFlag::DebugLuauForceOldSolver)
1173
return;
1174
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
1175
1176
CheckResult result = check(R"(
1177
--!strict
1178
1179
type Array<T> = { [number] : T}
1180
type Object = { [number] : any}
1181
1182
type Set<T> = typeof(setmetatable(
1183
{} :: {
1184
size: number,
1185
-- method definitions
1186
add: (self: Set<T>, T) -> Set<T>,
1187
clear: (self: Set<T>) -> (),
1188
delete: (self: Set<T>, T) -> boolean,
1189
has: (self: Set<T>, T) -> boolean,
1190
ipairs: (self: Set<T>) -> any,
1191
},
1192
{} :: {
1193
__index: Set<T>,
1194
__iter: (self: Set<T>) -> (<K, V>({ [K]: V }, K?) -> (K, V), T),
1195
}
1196
))
1197
1198
type Map<K, V> = typeof(setmetatable(
1199
{} :: {
1200
size: number,
1201
-- method definitions
1202
set: (self: Map<K, V>, K, V) -> Map<K, V>,
1203
get: (self: Map<K, V>, K) -> V | nil,
1204
clear: (self: Map<K, V>) -> (),
1205
delete: (self: Map<K, V>, K) -> boolean,
1206
[K]: V,
1207
has: (self: Map<K, V>, K) -> boolean,
1208
keys: (self: Map<K, V>) -> Array<K>,
1209
values: (self: Map<K, V>) -> Array<V>,
1210
entries: (self: Map<K, V>) -> Array<Tuple<K, V>>,
1211
ipairs: (self: Map<K, V>) -> any,
1212
_map: { [K]: V },
1213
_array: { [number]: K },
1214
__index: (self: Map<K, V>, key: K) -> V,
1215
__iter: (self: Map<K, V>) -> (<K, V>({ [K]: V }, K?) -> (K?, V), V),
1216
__newindex: (self: Map<K, V>, key: K, value: V) -> (),
1217
},
1218
{} :: {
1219
__index: Map<K, V>,
1220
__iter: (self: Map<K, V>) -> (<K, V>({ [K]: V }, K?) -> (K, V), V),
1221
__newindex: (self: Map<K, V>, key: K, value: V) -> (),
1222
}
1223
))
1224
type mapFn<T, U> = (element: T, index: number) -> U
1225
type mapFnWithThisArg<T, U> = (thisArg: any, element: T, index: number) -> U
1226
1227
function fromSet<T, U>(
1228
value: Set<T>,
1229
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
1230
thisArg: Object?
1231
-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
1232
): Array<U> | Array<T> | Array<string>
1233
1234
local array : { [number] : string} = {"foo"}
1235
return array
1236
end
1237
1238
function instanceof(tbl: any, class: any): boolean
1239
return true
1240
end
1241
1242
function fromArray<T, U>(
1243
value: Array<T>,
1244
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
1245
thisArg: Object?
1246
-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
1247
): Array<U> | Array<T> | Array<string>
1248
local array : {[number] : string} = {}
1249
return array
1250
end
1251
1252
return function<T, U>(
1253
value: string | Array<T> | Set<T> | Map<any, any>,
1254
mapFn: (mapFn<T, U> | mapFnWithThisArg<T, U>)?,
1255
thisArg: Object?
1256
-- FIXME Luau: need overloading so the return type on this is more sane and doesn't require manual casts
1257
): Array<U> | Array<T> | Array<string>
1258
if value == nil then
1259
error("cannot create array from a nil value")
1260
end
1261
local array: Array<U> | Array<T> | Array<string>
1262
1263
if instanceof(value, Set) then
1264
array = fromSet(value :: Set<T>, mapFn, thisArg)
1265
else
1266
array = {}
1267
end
1268
1269
1270
return array
1271
end
1272
)");
1273
}
1274
1275
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_flatten_type_pack_cycle")
1276
{
1277
ScopedFastFlag sff[] = {
1278
{FFlag::DebugLuauForceOldSolver, false},
1279
{FFlag::LuauReplacerRespectsReboundGenerics, true},
1280
{FFlag::LuauOverloadGetsInstantiated, true},
1281
{FFlag::LuauUnifier2HandleMismatchedPacks2, true},
1282
};
1283
1284
LUAU_REQUIRE_ERRORS(check(R"(
1285
function _(_).readu32<t0...>()
1286
repeat
1287
until function<t4>()
1288
end
1289
return if _ then _,_(_)
1290
end
1291
_(_(_(_)),``)
1292
do end
1293
)"));
1294
}
1295
1296
#if 0
1297
TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
1298
{
1299
ScopedFastFlag sff{FFlag::DebugLuauForceOldSolver, false};
1300
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
1301
1302
// FIXME CLI-153131: This is constructing a cyclic type pack
1303
CHECK_THROWS_AS(
1304
check(R"(
1305
function _(_).n0(l32,...)
1306
return ({n0=_,[_(if _ then _,nil)]=- _,[_(_(_))]=_,})[_],_(_)
1307
end
1308
_[_] ^= _(_(_))
1309
)"),
1310
InternalCompilerError
1311
);
1312
}
1313
#endif
1314
1315
TEST_SUITE_END();
1316
1317