• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

osyrisrblx / t / 13446522360

20 Feb 2025 11:17PM UTC coverage: 99.73% (-0.3%) from 100.0%
13446522360

push

github

osyrisrblx
Optimize `t.literalList` for primative types

5 of 5 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

370 of 371 relevant lines covered (99.73%)

13.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.73
/lib/init.lua
1
-- t: a runtime typechecker for Roblox
2

3
local t = {}
1✔
4

5
function t.type(typeName)
1✔
6
        return function(value)
7
                local valueType = type(value)
16✔
8
                if valueType == typeName then
16✔
9
                        return true
8✔
10
                else
11
                        return false, string.format("%s expected, got %s", typeName, valueType)
8✔
12
                end
13
        end
14
end
15

16
function t.typeof(typeName)
1✔
17
        return function(value)
18
                local valueType = typeof(value)
340✔
19
                if valueType == typeName then
340✔
20
                        return true
301✔
21
                else
22
                        return false, string.format("%s expected, got %s", typeName, valueType)
39✔
23
                end
24
        end
25
end
26

27
--[[**
28
        matches any type except nil
29

30
        @param value The value to check against
31

32
        @returns True iff the condition is satisfied, false otherwise
33
**--]]
34
function t.any(value)
1✔
35
        if value ~= nil then
25✔
36
                return true
24✔
37
        else
38
                return false, "any expected, got nil"
1✔
39
        end
40
end
41

42
--Lua primitives
43

44
--[[**
45
        ensures Lua primitive boolean type
46

47
        @param value The value to check against
48

49
        @returns True iff the condition is satisfied, false otherwise
50
**--]]
51
t.boolean = t.typeof("boolean")
1✔
52

53
--[[**
54
        ensures Lua primitive buffer type
55

56
        @param value The value to check against
57

58
        @returns True iff the condition is satisfied, false otherwise
59
**--]]
60
t.buffer = t.typeof("buffer")
1✔
61

62
--[[**
63
        ensures Lua primitive thread type
64

65
        @param value The value to check against
66

67
        @returns True iff the condition is satisfied, false otherwise
68
**--]]
69
t.thread = t.typeof("thread")
1✔
70

71
--[[**
72
        ensures Lua primitive callback type
73

74
        @param value The value to check against
75

76
        @returns True iff the condition is satisfied, false otherwise
77
**--]]
78
t.callback = t.typeof("function")
1✔
79
t["function"] = t.callback
1✔
80

81
--[[**
82
        ensures Lua primitive none type
83

84
        @param value The value to check against
85

86
        @returns True iff the condition is satisfied, false otherwise
87
**--]]
88
t.none = t.typeof("nil")
1✔
89
t["nil"] = t.none
1✔
90

91
--[[**
92
        ensures Lua primitive string type
93

94
        @param value The value to check against
95

96
        @returns True iff the condition is satisfied, false otherwise
97
**--]]
98
t.string = t.typeof("string")
1✔
99

100
--[[**
101
        ensures Lua primitive table type
102

103
        @param value The value to check against
104

105
        @returns True iff the condition is satisfied, false otherwise
106
**--]]
107
t.table = t.typeof("table")
1✔
108

109
--[[**
110
        ensures Lua primitive userdata type
111

112
        @param value The value to check against
113

114
        @returns True iff the condition is satisfied, false otherwise
115
**--]]
116
t.userdata = t.type("userdata")
1✔
117

118
--[[**
119
        ensures Lua primitive vector type
120

121
        @param value The value to check against
122

123
        @returns True iff the condition is satisfied, false otherwise
124
**--]]
125
t.vector = t.type("vector")
1✔
126

127
--[[**
128
        ensures value is a number and non-NaN
129

130
        @param value The value to check against
131

132
        @returns True iff the condition is satisfied, false otherwise
133
**--]]
134
function t.number(value)
1✔
135
        local valueType = typeof(value)
169✔
136
        if valueType == "number" then
169✔
137
                if value == value then
141✔
138
                        return true
139✔
139
                else
140
                        return false, "unexpected NaN value"
2✔
141
                end
142
        else
143
                return false, string.format("number expected, got %s", valueType)
28✔
144
        end
145
end
146

147
--[[**
148
        ensures value is NaN
149

150
        @param value The value to check against
151

152
        @returns True iff the condition is satisfied, false otherwise
153
**--]]
154
function t.nan(value)
1✔
155
        local valueType = typeof(value)
5✔
156
        if valueType == "number" then
5✔
157
                if value ~= value then
3✔
158
                        return true
2✔
159
                else
160
                        return false, "unexpected non-NaN value"
1✔
161
                end
162
        else
163
                return false, string.format("number expected, got %s", valueType)
2✔
164
        end
165
end
166

167
-- roblox types
168

169
--[[**
170
        ensures Roblox Axes type
171

172
        @param value The value to check against
173

174
        @returns True iff the condition is satisfied, false otherwise
175
**--]]
176
t.Axes = t.typeof("Axes")
1✔
177

178
--[[**
179
        ensures Roblox BrickColor type
180

181
        @param value The value to check against
182

183
        @returns True iff the condition is satisfied, false otherwise
184
**--]]
185
t.BrickColor = t.typeof("BrickColor")
1✔
186

187
--[[**
188
        ensures Roblox CatalogSearchParams type
189

190
        @param value The value to check against
191

192
        @returns True iff the condition is satisfied, false otherwise
193
**--]]
194
t.CatalogSearchParams = t.typeof("CatalogSearchParams")
1✔
195

196
--[[**
197
        ensures Roblox CFrame type
198

199
        @param value The value to check against
200

201
        @returns True iff the condition is satisfied, false otherwise
202
**--]]
203
t.CFrame = t.typeof("CFrame")
1✔
204

205
--[[**
206
        ensures Roblox Color3 type
207

208
        @param value The value to check against
209

210
        @returns True iff the condition is satisfied, false otherwise
211
**--]]
212
t.Color3 = t.typeof("Color3")
1✔
213

214
--[[**
215
        ensures Roblox ColorSequence type
216

217
        @param value The value to check against
218

219
        @returns True iff the condition is satisfied, false otherwise
220
**--]]
221
t.ColorSequence = t.typeof("ColorSequence")
1✔
222

223
--[[**
224
        ensures Roblox ColorSequenceKeypoint type
225

226
        @param value The value to check against
227

228
        @returns True iff the condition is satisfied, false otherwise
229
**--]]
230
t.ColorSequenceKeypoint = t.typeof("ColorSequenceKeypoint")
1✔
231

232
--[[**
233
        ensures Roblox DateTime type
234

235
        @param value The value to check against
236

237
        @returns True iff the condition is satisfied, false otherwise
238
**--]]
239
t.DateTime = t.typeof("DateTime")
1✔
240

241
--[[**
242
        ensures Roblox DockWidgetPluginGuiInfo type
243

244
        @param value The value to check against
245

246
        @returns True iff the condition is satisfied, false otherwise
247
**--]]
248
t.DockWidgetPluginGuiInfo = t.typeof("DockWidgetPluginGuiInfo")
1✔
249

250
--[[**
251
        ensures Roblox Enum type
252

253
        @param value The value to check against
254

255
        @returns True iff the condition is satisfied, false otherwise
256
**--]]
257
t.Enum = t.typeof("Enum")
1✔
258

259
--[[**
260
        ensures Roblox EnumItem type
261

262
        @param value The value to check against
263

264
        @returns True iff the condition is satisfied, false otherwise
265
**--]]
266
t.EnumItem = t.typeof("EnumItem")
1✔
267

268
--[[**
269
        ensures Roblox Enums type
270

271
        @param value The value to check against
272

273
        @returns True iff the condition is satisfied, false otherwise
274
**--]]
275
t.Enums = t.typeof("Enums")
1✔
276

277
--[[**
278
        ensures Roblox Faces type
279

280
        @param value The value to check against
281

282
        @returns True iff the condition is satisfied, false otherwise
283
**--]]
284
t.Faces = t.typeof("Faces")
1✔
285

286
--[[**
287
        ensures Roblox FloatCurveKey type
288

289
        @param value The value to check against
290

291
        @returns True iff the condition is satisfied, false otherwise
292
**--]]
293
t.FloatCurveKey = t.typeof("FloatCurveKey")
1✔
294

295
--[[**
296
        ensures Roblox Font type
297

298
        @param value The value to check against
299

300
        @returns True iff the condition is satisfied, false otherwise
301
**--]]
302
t.Font = t.typeof("Font")
1✔
303

304
--[[**
305
        ensures Roblox Instance type
306

307
        @param value The value to check against
308

309
        @returns True iff the condition is satisfied, false otherwise
310
**--]]
311
t.Instance = t.typeof("Instance")
1✔
312

313
--[[**
314
        ensures Roblox NumberRange type
315

316
        @param value The value to check against
317

318
        @returns True iff the condition is satisfied, false otherwise
319
**--]]
320
t.NumberRange = t.typeof("NumberRange")
1✔
321

322
--[[**
323
        ensures Roblox NumberSequence type
324

325
        @param value The value to check against
326

327
        @returns True iff the condition is satisfied, false otherwise
328
**--]]
329
t.NumberSequence = t.typeof("NumberSequence")
1✔
330

331
--[[**
332
        ensures Roblox NumberSequenceKeypoint type
333

334
        @param value The value to check against
335

336
        @returns True iff the condition is satisfied, false otherwise
337
**--]]
338
t.NumberSequenceKeypoint = t.typeof("NumberSequenceKeypoint")
1✔
339

340
--[[**
341
        ensures Roblox OverlapParams type
342

343
        @param value The value to check against
344

345
        @returns True iff the condition is satisfied, false otherwise
346
**--]]
347
t.OverlapParams = t.typeof("OverlapParams")
1✔
348

349
--[[**
350
        ensures Roblox PathWaypoint type
351

352
        @param value The value to check against
353

354
        @returns True iff the condition is satisfied, false otherwise
355
**--]]
356
t.PathWaypoint = t.typeof("PathWaypoint")
1✔
357

358
--[[**
359
        ensures Roblox PhysicalProperties type
360

361
        @param value The value to check against
362

363
        @returns True iff the condition is satisfied, false otherwise
364
**--]]
365
t.PhysicalProperties = t.typeof("PhysicalProperties")
1✔
366

367
--[[**
368
        ensures Roblox Random type
369

370
        @param value The value to check against
371

372
        @returns True iff the condition is satisfied, false otherwise
373
**--]]
374
t.Random = t.typeof("Random")
1✔
375

376
--[[**
377
        ensures Roblox Ray type
378

379
        @param value The value to check against
380

381
        @returns True iff the condition is satisfied, false otherwise
382
**--]]
383
t.Ray = t.typeof("Ray")
1✔
384

385
--[[**
386
        ensures Roblox RaycastParams type
387

388
        @param value The value to check against
389

390
        @returns True iff the condition is satisfied, false otherwise
391
**--]]
392
t.RaycastParams = t.typeof("RaycastParams")
1✔
393

394
--[[**
395
        ensures Roblox RaycastResult type
396

397
        @param value The value to check against
398

399
        @returns True iff the condition is satisfied, false otherwise
400
**--]]
401
t.RaycastResult = t.typeof("RaycastResult")
1✔
402

403
--[[**
404
        ensures Roblox RBXScriptConnection type
405

406
        @param value The value to check against
407

408
        @returns True iff the condition is satisfied, false otherwise
409
**--]]
410
t.RBXScriptConnection = t.typeof("RBXScriptConnection")
1✔
411

412
--[[**
413
        ensures Roblox RBXScriptSignal type
414

415
        @param value The value to check against
416

417
        @returns True iff the condition is satisfied, false otherwise
418
**--]]
419
t.RBXScriptSignal = t.typeof("RBXScriptSignal")
1✔
420

421
--[[**
422
        ensures Roblox Rect type
423

424
        @param value The value to check against
425

426
        @returns True iff the condition is satisfied, false otherwise
427
**--]]
428
t.Rect = t.typeof("Rect")
1✔
429

430
--[[**
431
        ensures Roblox Region3 type
432

433
        @param value The value to check against
434

435
        @returns True iff the condition is satisfied, false otherwise
436
**--]]
437
t.Region3 = t.typeof("Region3")
1✔
438

439
--[[**
440
        ensures Roblox Region3int16 type
441

442
        @param value The value to check against
443

444
        @returns True iff the condition is satisfied, false otherwise
445
**--]]
446
t.Region3int16 = t.typeof("Region3int16")
1✔
447

448
--[[**
449
        ensures Roblox TweenInfo type
450

451
        @param value The value to check against
452

453
        @returns True iff the condition is satisfied, false otherwise
454
**--]]
455
t.TweenInfo = t.typeof("TweenInfo")
1✔
456

457
--[[**
458
        ensures Roblox UDim type
459

460
        @param value The value to check against
461

462
        @returns True iff the condition is satisfied, false otherwise
463
**--]]
464
t.UDim = t.typeof("UDim")
1✔
465

466
--[[**
467
        ensures Roblox UDim2 type
468

469
        @param value The value to check against
470

471
        @returns True iff the condition is satisfied, false otherwise
472
**--]]
473
t.UDim2 = t.typeof("UDim2")
1✔
474

475
--[[**
476
        ensures Roblox Vector2 type
477

478
        @param value The value to check against
479

480
        @returns True iff the condition is satisfied, false otherwise
481
**--]]
482
t.Vector2 = t.typeof("Vector2")
1✔
483

484
--[[**
485
        ensures Roblox Vector2int16 type
486

487
        @param value The value to check against
488

489
        @returns True iff the condition is satisfied, false otherwise
490
**--]]
491
t.Vector2int16 = t.typeof("Vector2int16")
1✔
492

493
--[[**
494
        ensures Roblox Vector3 type
495

496
        @param value The value to check against
497

498
        @returns True iff the condition is satisfied, false otherwise
499
**--]]
500
t.Vector3 = t.typeof("Vector3")
1✔
501

502
--[[**
503
        ensures Roblox Vector3int16 type
504

505
        @param value The value to check against
506

507
        @returns True iff the condition is satisfied, false otherwise
508
**--]]
509
t.Vector3int16 = t.typeof("Vector3int16")
1✔
510

511
--[[**
512
        ensures value is any of the given literal values
513

514
        @param literals The literals to check against
515

516
        @returns A function that will return true if the condition is passed
517
**--]]
518
function t.literalList(literals)
1✔
519
        -- optimization for primitive types
520
        local set = {}
1✔
521
        for _, literal in ipairs(literals) do
4✔
522
                set[literal] = true
3✔
523
        end
524
        return function(value)
525
                if set[value] then
6✔
526
                        return true
3✔
527
                end
528
                for _, literal in ipairs(literals) do
12✔
529
                        if literal == value then
9✔
UNCOV
530
                                return true
×
531
                        end
532
                end
533

534
                return false, "bad type for literal list"
3✔
535
        end
536
end
537

538
--[[**
539
        ensures value is a given literal value
540

541
        @param literal The literal to use
542

543
        @returns A function that will return true iff the condition is passed
544
**--]]
545
function t.literal(...)
1✔
546
        local size = select("#", ...)
16✔
547
        if size == 1 then
16✔
548
                local literal = ...
13✔
549
                return function(value)
550
                        if value ~= literal then
43✔
551
                                return false, string.format("expected %s, got %s", tostring(literal), tostring(value))
32✔
552
                        end
553

554
                        return true
11✔
555
                end
556
        else
557
                local literals = {}
3✔
558
                for i = 1, size do
10✔
559
                        local value = select(i, ...)
7✔
560
                        literals[i] = t.literal(value)
7✔
561
                end
562

563
                return t.unionList(literals)
3✔
564
        end
565
end
566

567
--[[**
568
        DEPRECATED
569
        Please use t.literal
570
**--]]
571
t.exactly = t.literal
1✔
572

573
--[[**
574
        Returns a t.union of each key in the table as a t.literal
575

576
        @param keyTable The table to get keys from
577

578
        @returns True iff the condition is satisfied, false otherwise
579
**--]]
580
function t.keyOf(keyTable)
1✔
581
        local keys = {}
1✔
582
        local length = 0
1✔
583
        for key in pairs(keyTable) do
3✔
584
                length = length + 1
2✔
585
                keys[length] = key
2✔
586
        end
587

588
        return t.literal(table.unpack(keys, 1, length))
1✔
589
end
590

591
--[[**
592
        Returns a t.union of each value in the table as a t.literal
593

594
        @param valueTable The table to get values from
595

596
        @returns True iff the condition is satisfied, false otherwise
597
**--]]
598
function t.valueOf(valueTable)
1✔
599
        local values = {}
1✔
600
        local length = 0
1✔
601
        for _, value in pairs(valueTable) do
3✔
602
                length = length + 1
2✔
603
                values[length] = value
2✔
604
        end
605

606
        return t.literal(table.unpack(values, 1, length))
1✔
607
end
608

609
--[[**
610
        ensures value is an integer
611

612
        @param value The value to check against
613

614
        @returns True iff the condition is satisfied, false otherwise
615
**--]]
616
function t.integer(value)
1✔
617
        local success, errMsg = t.number(value)
67✔
618
        if not success then
67✔
619
                return false, errMsg or ""
4✔
620
        end
621

622
        if value % 1 == 0 then
63✔
623
                return true
61✔
624
        else
625
                return false, string.format("integer expected, got %s", value)
2✔
626
        end
627
end
628

629
--[[**
630
        ensures value is a number where min <= value
631

632
        @param min The minimum to use
633

634
        @returns A function that will return true iff the condition is passed
635
**--]]
636
function t.numberMin(min)
1✔
637
        return function(value)
638
                local success, errMsg = t.number(value)
10✔
639
                if not success then
10✔
640
                        return false, errMsg or ""
2✔
641
                end
642

643
                if value >= min then
8✔
644
                        return true
6✔
645
                else
646
                        return false, string.format("number >= %s expected, got %s", min, value)
2✔
647
                end
648
        end
649
end
650

651
--[[**
652
        ensures value is a number where value <= max
653

654
        @param max The maximum to use
655

656
        @returns A function that will return true iff the condition is passed
657
**--]]
658
function t.numberMax(max)
1✔
659
        return function(value)
660
                local success, errMsg = t.number(value)
12✔
661
                if not success then
12✔
662
                        return false, errMsg
1✔
663
                end
664

665
                if value <= max then
11✔
666
                        return true
7✔
667
                else
668
                        return false, string.format("number <= %s expected, got %s", max, value)
4✔
669
                end
670
        end
671
end
672

673
--[[**
674
        ensures value is a number where min < value
675

676
        @param min The minimum to use
677

678
        @returns A function that will return true iff the condition is passed
679
**--]]
680
function t.numberMinExclusive(min)
1✔
681
        return function(value)
682
                local success, errMsg = t.number(value)
10✔
683
                if not success then
10✔
684
                        return false, errMsg or ""
2✔
685
                end
686

687
                if min < value then
8✔
688
                        return true
5✔
689
                else
690
                        return false, string.format("number > %s expected, got %s", min, value)
3✔
691
                end
692
        end
693
end
694

695
--[[**
696
        ensures value is a number where value < max
697

698
        @param max The maximum to use
699

700
        @returns A function that will return true iff the condition is passed
701
**--]]
702
function t.numberMaxExclusive(max)
1✔
703
        return function(value)
704
                local success, errMsg = t.number(value)
7✔
705
                if not success then
7✔
706
                        return false, errMsg or ""
1✔
707
                end
708

709
                if value < max then
6✔
710
                        return true
3✔
711
                else
712
                        return false, string.format("number < %s expected, got %s", max, value)
3✔
713
                end
714
        end
715
end
716

717
--[[**
718
        ensures value is a number where value > 0
719

720
        @returns A function that will return true iff the condition is passed
721
**--]]
722
t.numberPositive = t.numberMinExclusive(0)
1✔
723

724
--[[**
725
        ensures value is a number where value < 0
726

727
        @returns A function that will return true iff the condition is passed
728
**--]]
729
t.numberNegative = t.numberMaxExclusive(0)
1✔
730

731
--[[**
732
        ensures value is a number where min <= value <= max
733

734
        @param min The minimum to use
735
        @param max The maximum to use
736

737
        @returns A function that will return true iff the condition is passed
738
**--]]
739
function t.numberConstrained(min, max)
1✔
740
        assert(t.number(min))
1✔
741
        assert(t.number(max))
1✔
742
        local minCheck = t.numberMin(min)
1✔
743
        local maxCheck = t.numberMax(max)
1✔
744

745
        return function(value)
746
                local minSuccess, minErrMsg = minCheck(value)
6✔
747
                if not minSuccess then
6✔
748
                        return false, minErrMsg or ""
2✔
749
                end
750

751
                local maxSuccess, maxErrMsg = maxCheck(value)
4✔
752
                if not maxSuccess then
4✔
753
                        return false, maxErrMsg or ""
1✔
754
                end
755

756
                return true
3✔
757
        end
758
end
759

760
--[[**
761
        ensures value is a number where min < value < max
762

763
        @param min The minimum to use
764
        @param max The maximum to use
765

766
        @returns A function that will return true iff the condition is passed
767
**--]]
768
function t.numberConstrainedExclusive(min, max)
1✔
769
        assert(t.number(min))
1✔
770
        assert(t.number(max))
1✔
771
        local minCheck = t.numberMinExclusive(min)
1✔
772
        local maxCheck = t.numberMaxExclusive(max)
1✔
773

774
        return function(value)
775
                local minSuccess, minErrMsg = minCheck(value)
6✔
776
                if not minSuccess then
6✔
777
                        return false, minErrMsg or ""
3✔
778
                end
779

780
                local maxSuccess, maxErrMsg = maxCheck(value)
3✔
781
                if not maxSuccess then
3✔
782
                        return false, maxErrMsg or ""
2✔
783
                end
784

785
                return true
1✔
786
        end
787
end
788

789
--[[**
790
        ensures value matches string pattern
791

792
        @param string pattern to check against
793

794
        @returns A function that will return true iff the condition is passed
795
**--]]
796
function t.match(pattern)
1✔
797
        assert(t.string(pattern))
1✔
798
        return function(value)
799
                local stringSuccess, stringErrMsg = t.string(value)
3✔
800
                if not stringSuccess then
3✔
801
                        return false, stringErrMsg
1✔
802
                end
803

804
                if string.match(value, pattern) == nil then
2✔
805
                        return false, string.format("%q failed to match pattern %q", value, pattern)
1✔
806
                end
807

808
                return true
1✔
809
        end
810
end
811

812
--[[**
813
        ensures value is either nil or passes check
814

815
        @param check The check to use
816

817
        @returns A function that will return true iff the condition is passed
818
**--]]
819
function t.optional(check)
1✔
820
        assert(t.callback(check))
5✔
821
        return function(value)
822
                if value == nil then
14✔
823
                        return true
5✔
824
                end
825

826
                local success, errMsg = check(value)
9✔
827
                if success then
9✔
828
                        return true
5✔
829
                else
830
                        return false, string.format("(optional) %s", errMsg or "")
4✔
831
                end
832
        end
833
end
834

835
--[[**
836
        matches given tuple against tuple type definition
837

838
        @param ... The type definition for the tuples
839

840
        @returns A function that will return true iff the condition is passed
841
**--]]
842
function t.tuple(...)
1✔
843
        local checks = { ... }
4✔
844
        return function(...)
845
                local args = { ... }
11✔
846
                for i, check in ipairs(checks) do
31✔
847
                        local success, errMsg = check(args[i])
25✔
848
                        if success == false then
25✔
849
                                return false, string.format("Bad tuple index #%s:\n\t%s", i, errMsg or "")
5✔
850
                        end
851
                end
852

853
                return true
6✔
854
        end
855
end
856

857
--[[**
858
        ensures all keys in given table pass check
859

860
        @param check The function to use to check the keys
861

862
        @returns A function that will return true iff the condition is passed
863
**--]]
864
function t.keys(check)
1✔
865
        assert(t.callback(check))
5✔
866
        return function(value)
867
                local tableSuccess, tableErrMsg = t.table(value)
56✔
868
                if tableSuccess == false then
56✔
869
                        return false, tableErrMsg or ""
4✔
870
                end
871

872
                for key in pairs(value) do
134✔
873
                        local success, errMsg = check(key)
86✔
874
                        if success == false then
86✔
875
                                return false, string.format("bad key %s:\n\t%s", tostring(key), errMsg or "")
4✔
876
                        end
877
                end
878

879
                return true
48✔
880
        end
881
end
882

883
--[[**
884
        ensures all values in given table pass check
885

886
        @param check The function to use to check the values
887

888
        @returns A function that will return true iff the condition is passed
889
**--]]
890
function t.values(check)
1✔
891
        assert(t.callback(check))
11✔
892
        return function(value)
893
                local tableSuccess, tableErrMsg = t.table(value)
40✔
894
                if tableSuccess == false then
40✔
895
                        return false, tableErrMsg or ""
1✔
896
                end
897

898
                for key, val in pairs(value) do
94✔
899
                        local success, errMsg = check(val)
59✔
900
                        if success == false then
59✔
901
                                return false, string.format("bad value for key %s:\n\t%s", tostring(key), errMsg or "")
4✔
902
                        end
903
                end
904

905
                return true
35✔
906
        end
907
end
908

909
--[[**
910
        ensures value is a table and all keys pass keyCheck and all values pass valueCheck
911

912
        @param keyCheck The function to use to check the keys
913
        @param valueCheck The function to use to check the values
914

915
        @returns A function that will return true iff the condition is passed
916
**--]]
917
function t.map(keyCheck, valueCheck)
1✔
918
        assert(t.callback(keyCheck))
4✔
919
        assert(t.callback(valueCheck))
4✔
920
        local keyChecker = t.keys(keyCheck)
4✔
921
        local valueChecker = t.values(valueCheck)
4✔
922

923
        return function(value)
924
                local keySuccess, keyErr = keyChecker(value)
28✔
925
                if not keySuccess then
28✔
926
                        return false, keyErr or ""
4✔
927
                end
928

929
                local valueSuccess, valueErr = valueChecker(value)
24✔
930
                if not valueSuccess then
24✔
931
                        return false, valueErr or ""
3✔
932
                end
933

934
                return true
21✔
935
        end
936
end
937

938
--[[**
939
        ensures value is a table and all keys pass valueCheck and all values are true
940

941
        @param valueCheck The function to use to check the values
942

943
        @returns A function that will return true iff the condition is passed
944
**--]]
945
function t.set(valueCheck)
1✔
946
        return t.map(valueCheck, t.literal(true))
1✔
947
end
948

949
do
950
        local arrayKeysCheck = t.keys(t.integer)
1✔
951
--[[**
952
                ensures value is an array and all values of the array match check
953

954
                @param check The check to compare all values with
955

956
                @returns A function that will return true iff the condition is passed
957
        **--]]
958
        function t.array(check)
1✔
959
                assert(t.callback(check))
6✔
960
                local valuesCheck = t.values(check)
6✔
961

962
                return function(value)
963
                        local keySuccess, keyErrMsg = arrayKeysCheck(value)
20✔
964
                        if keySuccess == false then
20✔
965
                                return false, string.format("[array] %s", keyErrMsg or "")
3✔
966
                        end
967

968
                        -- # is unreliable for sparse arrays
969
                        -- Count upwards using ipairs to avoid false positives from the behavior of #
970
                        local arraySize = 0
17✔
971

972
                        for _ in ipairs(value) do
56✔
973
                                arraySize = arraySize + 1
39✔
974
                        end
975

976
                        for key in pairs(value) do
56✔
977
                                if key < 1 or key > arraySize then
41✔
978
                                        return false, string.format("[array] key %s must be sequential", tostring(key))
2✔
979
                                end
980
                        end
981

982
                        local valueSuccess, valueErrMsg = valuesCheck(value)
15✔
983
                        if not valueSuccess then
15✔
984
                                return false, string.format("[array] %s", valueErrMsg or "")
1✔
985
                        end
986

987
                        return true
14✔
988
                end
989
        end
990

991
--[[**
992
                ensures value is an array of a strict makeup and size
993

994
                @param check The check to compare all values with
995

996
                @returns A function that will return true iff the condition is passed
997
        **--]]
998
        function t.strictArray(...)
1✔
999
                local valueTypes = { ... }
2✔
1000
                assert(t.array(t.callback)(valueTypes))
2✔
1001

1002
                return function(value)
1003
                        local keySuccess, keyErrMsg = arrayKeysCheck(value)
8✔
1004
                        if keySuccess == false then
8✔
1005
                                return false, string.format("[strictArray] %s", keyErrMsg or "")
1✔
1006
                        end
1007

1008
                        -- If there's more than the set array size, disallow
1009
                        if #valueTypes < #value then
7✔
1010
                                return false, string.format("[strictArray] Array size exceeds limit of %d", #valueTypes)
1✔
1011
                        end
1012

1013
                        for idx, typeFn in pairs(valueTypes) do
17✔
1014
                                local typeSuccess, typeErrMsg = typeFn(value[idx])
14✔
1015
                                if not typeSuccess then
14✔
1016
                                        return false, string.format("[strictArray] Array index #%d - %s", idx, typeErrMsg)
3✔
1017
                                end
1018
                        end
1019

1020
                        return true
3✔
1021
                end
1022
        end
1023
end
1024

1025
do
1026
        local callbackArray = t.array(t.callback)
1✔
1027
--[[**
1028
                creates a union type
1029

1030
                @param checks The checks to union
1031

1032
                @returns A function that will return true iff the condition is passed
1033
        **--]]
1034
        function t.unionList(checks)
1✔
1035
                assert(callbackArray(checks))
7✔
1036

1037
                return function(value)
1038
                        for _, check in ipairs(checks) do
62✔
1039
                                if check(value) then
51✔
1040
                                        return true
14✔
1041
                                end
1042
                        end
1043

1044
                        return false, "bad type for union"
11✔
1045
                end
1046
        end
1047

1048
--[[**
1049
                creates a union type
1050

1051
                @param ... The checks to union
1052

1053
                @returns A function that will return true iff the condition is passed
1054
        **--]]
1055
        function t.union(...)
1✔
1056
                return t.unionList({ ... })
3✔
1057
        end
1058

1059
--[[**
1060
                Alias for t.union
1061
        **--]]
1062
        t.some = t.union
1✔
1063

1064
--[[**
1065
                creates an intersection type
1066

1067
                @param checks The checks to intersect
1068

1069
                @returns A function that will return true iff the condition is passed
1070
        **--]]
1071
        function t.intersectionList(checks)
1✔
1072
                assert(callbackArray(checks))
3✔
1073

1074
                return function(value)
1075
                        for _, check in ipairs(checks) do
25✔
1076
                                local success, errMsg = check(value)
22✔
1077
                                if not success then
22✔
1078
                                        return false, errMsg or ""
10✔
1079
                                end
1080
                        end
1081

1082
                        return true
3✔
1083
                end
1084
        end
1085

1086
--[[**
1087
                creates an intersection type
1088

1089
                @param ... The checks to intersect
1090

1091
                @returns A function that will return true iff the condition is passed
1092
        **--]]
1093
        function t.intersection(...)
1✔
1094
                return t.intersectionList({ ... })
2✔
1095
        end
1096

1097
--[[**
1098
                Alias for t.intersection
1099
        **--]]
1100
        t.every = t.intersection
1✔
1101
end
1102

1103
do
1104
        local checkInterface = t.map(t.any, t.callback)
1✔
1105
--[[**
1106
                ensures value matches given interface definition
1107

1108
                @param checkTable The interface definition
1109

1110
                @returns A function that will return true iff the condition is passed
1111
        **--]]
1112
        function t.interface(checkTable)
1✔
1113
                assert(checkInterface(checkTable))
9✔
1114
                return function(value)
1115
                        local tableSuccess, tableErrMsg = t.table(value)
31✔
1116
                        if tableSuccess == false then
31✔
1117
                                return false, tableErrMsg or ""
1✔
1118
                        end
1119

1120
                        for key, check in pairs(checkTable) do
50✔
1121
                                local success, errMsg = check(value[key])
40✔
1122
                                if success == false then
40✔
1123
                                        return false, string.format("[interface] bad value for %s:\n\t%s", tostring(key), errMsg or "")
20✔
1124
                                end
1125
                        end
1126

1127
                        return true
10✔
1128
                end
1129
        end
1130

1131
--[[**
1132
                ensures value matches given interface definition strictly
1133

1134
                @param checkTable The interface definition
1135

1136
                @returns A function that will return true iff the condition is passed
1137
        **--]]
1138
        function t.strictInterface(checkTable)
1✔
1139
                assert(checkInterface(checkTable))
2✔
1140
                return function(value)
1141
                        local tableSuccess, tableErrMsg = t.table(value)
5✔
1142
                        if tableSuccess == false then
5✔
1143
                                return false, tableErrMsg or ""
1✔
1144
                        end
1145

1146
                        for key, check in pairs(checkTable) do
13✔
1147
                                local success, errMsg = check(value[key])
10✔
1148
                                if success == false then
10✔
1149
                                        return false, string.format("[interface] bad value for %s:\n\t%s", tostring(key), errMsg or "")
1✔
1150
                                end
1151
                        end
1152

1153
                        for key in pairs(value) do
9✔
1154
                                if not checkTable[key] then
8✔
1155
                                        return false, string.format("[interface] unexpected field %q", tostring(key))
2✔
1156
                                end
1157
                        end
1158

1159
                        return true
1✔
1160
                end
1161
        end
1162
end
1163

1164
--[[**
1165
        ensure value is an Instance and it's ClassName matches the given ClassName
1166

1167
        @param className The class name to check for
1168

1169
        @returns A function that will return true iff the condition is passed
1170
**--]]
1171
function t.instanceOf(className, childTable)
1✔
1172
        assert(t.string(className))
5✔
1173

1174
        local childrenCheck
1175
        if childTable ~= nil then
5✔
1176
                childrenCheck = t.children(childTable)
1✔
1177
        end
1178

1179
        return function(value)
1180
                local instanceSuccess, instanceErrMsg = t.Instance(value)
21✔
1181
                if not instanceSuccess then
21✔
1182
                        return false, instanceErrMsg or ""
5✔
1183
                end
1184

1185
                if value.ClassName ~= className then
16✔
1186
                        return false, string.format("%s expected, got %s", className, value.ClassName)
3✔
1187
                end
1188

1189
                if childrenCheck then
13✔
1190
                        local childrenSuccess, childrenErrMsg = childrenCheck(value)
5✔
1191
                        if not childrenSuccess then
5✔
1192
                                return false, childrenErrMsg
4✔
1193
                        end
1194
                end
1195

1196
                return true
9✔
1197
        end
1198
end
1199

1200
t.instance = t.instanceOf
1✔
1201

1202
--[[**
1203
        ensure value is an Instance and it's ClassName matches the given ClassName by an IsA comparison
1204

1205
        @param className The class name to check for
1206

1207
        @returns A function that will return true iff the condition is passed
1208
**--]]
1209
function t.instanceIsA(className, childTable)
1✔
1210
        assert(t.string(className))
3✔
1211

1212
        local childrenCheck
1213
        if childTable ~= nil then
3✔
1214
                childrenCheck = t.children(childTable)
1✔
1215
        end
1216

1217
        return function(value)
1218
                local instanceSuccess, instanceErrMsg = t.Instance(value)
13✔
1219
                if not instanceSuccess then
13✔
1220
                        return false, instanceErrMsg or ""
3✔
1221
                end
1222

1223
                if not value:IsA(className) then
10✔
1224
                        return false, string.format("%s expected, got %s", className, value.ClassName)
2✔
1225
                end
1226

1227
                if childrenCheck then
8✔
1228
                        local childrenSuccess, childrenErrMsg = childrenCheck(value)
5✔
1229
                        if not childrenSuccess then
5✔
1230
                                return false, childrenErrMsg
4✔
1231
                        end
1232
                end
1233

1234
                return true
4✔
1235
        end
1236
end
1237

1238
--[[**
1239
        ensures value is an enum of the correct type
1240

1241
        @param enum The enum to check
1242

1243
        @returns A function that will return true iff the condition is passed
1244
**--]]
1245
function t.enum(enum)
1✔
1246
        assert(t.Enum(enum))
1✔
1247
        return function(value)
1248
                local enumItemSuccess, enumItemErrMsg = t.EnumItem(value)
4✔
1249
                if not enumItemSuccess then
4✔
1250
                        return false, enumItemErrMsg
1✔
1251
                end
1252

1253
                if value.EnumType == enum then
3✔
1254
                        return true
2✔
1255
                else
1256
                        return false, string.format("enum of %s expected, got enum of %s", tostring(enum), tostring(value.EnumType))
1✔
1257
                end
1258
        end
1259
end
1260

1261
do
1262
        local checkWrap = t.tuple(t.callback, t.callback)
1✔
1263

1264
--[[**
1265
                wraps a callback in an assert with checkArgs
1266

1267
                @param callback The function to wrap
1268
                @param checkArgs The function to use to check arguments in the assert
1269

1270
                @returns A function that first asserts using checkArgs and then calls callback
1271
        **--]]
1272
        function t.wrap(callback, checkArgs)
1✔
1273
                assert(checkWrap(callback, checkArgs))
1✔
1274
                return function(...)
1275
                        assert(checkArgs(...))
5✔
1276
                        return callback(...)
2✔
1277
                end
1278
        end
1279
end
1280

1281
--[[**
1282
        asserts a given check
1283

1284
        @param check The function to wrap with an assert
1285

1286
        @returns A function that simply wraps the given check in an assert
1287
**--]]
1288
function t.strict(check)
1✔
1289
        return function(...)
1290
                assert(check(...))
2✔
1291
        end
1292
end
1293

1294
do
1295
        local checkChildren = t.map(t.string, t.callback)
1✔
1296

1297
--[[**
1298
                Takes a table where keys are child names and values are functions to check the children against.
1299
                Pass an instance tree into the function.
1300
                If at least one child passes each check, the overall check passes.
1301

1302
                Warning! If you pass in a tree with more than one child of the same name, this function will always return false
1303

1304
                @param checkTable The table to check against
1305

1306
                @returns A function that checks an instance tree
1307
        **--]]
1308
        function t.children(checkTable)
1✔
1309
                assert(checkChildren(checkTable))
6✔
1310

1311
                return function(value)
1312
                        local instanceSuccess, instanceErrMsg = t.Instance(value)
18✔
1313
                        if not instanceSuccess then
18✔
1314
                                return false, instanceErrMsg or ""
3✔
1315
                        end
1316

1317
                        local childrenByName = {}
15✔
1318
                        for _, child in ipairs(value:GetChildren()) do
27✔
1319
                                local name = child.Name
15✔
1320
                                if checkTable[name] then
15✔
1321
                                        if childrenByName[name] then
12✔
1322
                                                return false, string.format("Cannot process multiple children with the same name %q", name)
3✔
1323
                                        end
1324

1325
                                        childrenByName[name] = child
9✔
1326
                                end
1327
                        end
1328

1329
                        for name, check in pairs(checkTable) do
15✔
1330
                                local success, errMsg = check(childrenByName[name])
12✔
1331
                                if not success then
12✔
1332
                                        return false, string.format("[%s.%s] %s", value:GetFullName(), name, errMsg or "")
9✔
1333
                                end
1334
                        end
1335

1336
                        return true
3✔
1337
                end
1338
        end
1339
end
1340

1341
return t
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc