STUDIO HAZE · topaz
Topaz source
Open by house policy. Topaz is our own language.
Topaz 5.20 · 56 files
compiler/crates/topaz_self_frontend/topaz/checker/members_capabilities.tpz
Topaz · 58903 B · Original file
1
// Member catalogs and capability rules for nominal and builtin types.2
// Formation supplies declared shapes; expression and call inference consume the3
// resolved member and Eq, Order, JSON, iterable, and key decisions.5
import types { SyntaxNode }6
import checker_types { TypeValue }7
import checker.model {8
CheckerService,9
CheckerState,10
FunctionDefinition,11
TypeBinding,12
constructor,13
emptyType,14
functionType,15
inference,16
literal,17
nominal,18
primitive,19
rigid,20
serviceBuiltinNamespaceObject,21
structural,22
}23
import checker.type_algebra {24
hasHole,25
transformTypeComponents,26
typeContains,27
typeEqual,28
typeListText,29
typeText,30
unionType,31
widen,32
}33
import checker.syntax_index {34
addDiagnostic,35
child,36
children,37
containsString,38
text,39
}40
import checker.nominals { substitute }41
import checker.protocols_methods {42
conformanceTypeKey,43
enumDefinitionForType,44
newtypeDefinitionForType,45
protocolConformanceIndex,46
receiverMethodType,47
recordDefinitionForType,48
}50
export function iterableElement(value: TypeValue) -> TypeValue {51
if value.kind == "constructor" &&52
(value.name == "Array" || value.name == "Set" || value.name == "Range") {53
return match value.arguments.get(0) {54
case Some(found) => found55
case None => emptyType("unknown")56
}57
}58
if value.kind == "rigid" || value.kind == "foreign" {59
return rigid("ElemOf<{typeText(value)}>", "builtin::iterable")60
}61
emptyType("unknown")62
}64
export record ArraySpreadElement {65
status: string,66
element: TypeValue,67
}69
export function arraySpreadElement(value: TypeValue) -> ArraySpreadElement {70
if value.kind == "constructor" && value.name == "Array" {71
return ArraySpreadElement {72
status: "element",73
element: match value.arguments.get(0) {74
case Some(found) => found75
case None => emptyType("unknown")76
},77
}78
}79
if value.kind == "rigid" || value.kind == "foreign" {80
return ArraySpreadElement {81
status: "element",82
element: rigid("ElemOf<{typeText(value)}>", "builtin::array-spread"),83
}84
}85
if value.kind == "union" {86
let mut elements: Array<TypeValue> = []87
let mut staged = false88
for member in value.members {89
let spread = arraySpreadElement(member)90
if spread.status == "invalid" {91
return spread92
}93
if spread.status == "stage" {94
staged = true95
} else {96
elements.push(spread.element)97
}98
}99
return if staged {100
ArraySpreadElement {101
status: "stage",102
element: emptyType("unknown"),103
}104
} else {105
ArraySpreadElement {106
status: "element",107
element: unionType(elements),108
}109
}110
}111
if hasHole(value) {112
ArraySpreadElement {113
status: "stage",114
element: emptyType("unknown"),115
}116
} else {117
ArraySpreadElement {118
status: "invalid",119
element: emptyType("unknown"),120
}121
}122
}124
record KeyCapabilityFailure {125
subject: string,126
kind: string,127
}129
export function capabilityPath(seen: Array<string>, key: string) -> Array<string> {130
let mut nested: Array<string> = []131
for item in seen {132
nested.push(item)133
}134
nested.push(key)135
nested136
}138
export function nominalCapabilityKey(value: TypeValue) -> string {139
"{value.kind}:{value.origin}:{value.identity}<{typeListText(value.arguments)}>"140
}142
record NominalCapabilityProjection {143
moduleIndex: int,144
children: Array<TypeValue>,145
}147
function nominalCapabilityType(value: TypeValue) -> bool {148
value.kind == "nominal-record" || value.kind == "enum" ||149
value.kind == "newtype"150
}152
function nominalCapabilityProjection(153
state: CheckerState,154
moduleIndex: int,155
value: TypeValue,156
) -> NominalCapabilityProjection {157
let mut children: Array<TypeValue> = []158
if value.kind == "nominal-record" {159
return match recordDefinitionForType(state, moduleIndex, value) {160
case Some(definition) => {161
for field in definition.fields {162
children.push(substitute(163
field.typeValue,164
definition.typeParameters,165
value.arguments,166
))167
}168
NominalCapabilityProjection {169
moduleIndex: definition.moduleIndex,170
children: children,171
}172
}173
case None => NominalCapabilityProjection {174
moduleIndex: moduleIndex,175
children: children,176
}177
}178
}179
if value.kind == "enum" {180
return match enumDefinitionForType(state, moduleIndex, value) {181
case Some(definition) => {182
for variant in definition.variants {183
for payload in variant.payloads {184
children.push(substitute(185
payload,186
definition.typeParameters,187
value.arguments,188
))189
}190
}191
NominalCapabilityProjection {192
moduleIndex: definition.moduleIndex,193
children: children,194
}195
}196
case None => NominalCapabilityProjection {197
moduleIndex: moduleIndex,198
children: children,199
}200
}201
}202
match newtypeDefinitionForType(state, moduleIndex, value) {203
case Some(definition) => {204
children.push(substitute(205
definition.base,206
definition.typeParameters,207
value.arguments,208
))209
NominalCapabilityProjection {210
moduleIndex: definition.moduleIndex,211
children: children,212
}213
}214
case None => NominalCapabilityProjection {215
moduleIndex: moduleIndex,216
children: children,217
}218
}219
}221
export function containsByteBuffer(222
state: CheckerState,223
moduleIndex: int,224
value: TypeValue,225
seen: Array<string>,226
) -> bool {227
let widened = widen(value)228
if widened.kind == "byte-buffer" {229
return true230
}231
if widened.kind == "function" {232
return false233
}234
if widened.kind == "constructor" || widened.kind == "foreign" {235
for argument in widened.arguments {236
if containsByteBuffer(state, moduleIndex, argument, seen) {237
return true238
}239
}240
return false241
}242
if widened.kind == "record" {243
for field in widened.fields {244
if containsByteBuffer(state, moduleIndex, field, seen) {245
return true246
}247
}248
return false249
}250
if widened.kind == "union" {251
for member in widened.members {252
if containsByteBuffer(state, moduleIndex, member, seen) {253
return true254
}255
}256
return false257
}258
if nominalCapabilityType(widened) {259
for argument in widened.arguments {260
if containsByteBuffer(state, moduleIndex, argument, seen) {261
return true262
}263
}264
let key = nominalCapabilityKey(widened)265
if containsString(seen, key) {266
return false267
}268
let projection = nominalCapabilityProjection(state, moduleIndex, widened)269
let nested = capabilityPath(seen, key)270
for child in projection.children {271
if containsByteBuffer(272
state,273
projection.moduleIndex,274
child,275
nested,276
) {277
return true278
}279
}280
return false281
}282
false283
}285
export function comparableType(286
state: CheckerState,287
moduleIndex: int,288
value: TypeValue,289
seen: Array<string>,290
) -> bool {291
let widened = widen(value)292
if widened.kind == "template" || widened.kind == "file" ||293
widened.kind == "json-value" || widened.kind == "toml-value" ||294
widened.kind == "function" || widened.kind == "byte-buffer" ||295
widened.kind == "regex" {296
return false297
}298
if widened.kind == "constructor" {299
if widened.name == "Map" || widened.name == "Set" {300
return false301
}302
for argument in widened.arguments {303
if !comparableType(state, moduleIndex, argument, seen) {304
return false305
}306
}307
return true308
}309
if widened.kind == "record" {310
for field in widened.fields {311
if !comparableType(state, moduleIndex, field, seen) {312
return false313
}314
}315
return true316
}317
if widened.kind == "union" {318
for member in widened.members {319
if !comparableType(state, moduleIndex, member, seen) {320
return false321
}322
}323
return true324
}325
if nominalCapabilityType(widened) {326
let key = nominalCapabilityKey(widened)327
if containsString(seen, key) {328
return true329
}330
let projection = nominalCapabilityProjection(state, moduleIndex, widened)331
let nested = capabilityPath(seen, key)332
for child in projection.children {333
if !comparableType(334
state,335
projection.moduleIndex,336
child,337
nested,338
) {339
return false340
}341
}342
return true343
}344
true345
}347
function capabilityAnd(left: string, right: string) -> string {348
if left == "reject" || right == "reject" {349
"reject"350
} else if left == "defer" || right == "defer" {351
"defer"352
} else {353
"accept"354
}355
}357
export function orderCapability(358
state: CheckerState,359
moduleIndex: int,360
value: TypeValue,361
seen: Array<string>,362
) -> string {363
let widened = widen(value)364
if widened.kind == "primitive" {365
return if widened.name == "int" || widened.name == "float" ||366
widened.name == "string" {367
"accept"368
} else {369
"reject"370
}371
}372
if widened.kind == "literal" {373
return if widened.name == "int" || widened.name == "float" ||374
widened.name == "string" {375
"accept"376
} else {377
"reject"378
}379
}380
if widened.kind == "bytes" || widened.kind == "path" ||381
widened.kind == "url" || widened.kind == "date" ||382
widened.kind == "big-int" || widened.kind == "decimal" {383
return "accept"384
}385
if widened.kind == "inference-variable" {386
return "defer"387
}388
if nominalCapabilityType(widened) {389
let key = nominalCapabilityKey(widened)390
if containsString(seen, key) {391
return "accept"392
}393
let mut status = "accept"394
let projection = nominalCapabilityProjection(state, moduleIndex, widened)395
let nested = capabilityPath(seen, key)396
for child in projection.children {397
status = capabilityAnd(status, orderCapability(398
state,399
projection.moduleIndex,400
child,401
nested,402
))403
}404
return status405
}406
"reject"407
}409
export function jsonCapability(410
state: CheckerState,411
moduleIndex: int,412
value: TypeValue,413
seen: Array<string>,414
decoding: bool,415
) -> string {416
let widened = widen(value)417
if widened.kind == "primitive" {418
return if widened.name == "int" || widened.name == "bool" ||419
widened.name == "string" || widened.name == "unit" {420
"accept"421
} else {422
"reject"423
}424
}425
if widened.kind == "literal" {426
return if widened.name == "float" { "reject" } else { "accept" }427
}428
if widened.kind == "json-value" {429
return "accept"430
}431
if widened.kind == "match" {432
return if decoding { "reject" } else { "accept" }433
}434
if widened.kind == "inference-variable" {435
return "defer"436
}437
if widened.kind == "constructor" {438
if widened.name == "Option" || widened.name == "Array" {439
return match widened.arguments.get(0) {440
case Some(argument) => jsonCapability(441
state,442
moduleIndex,443
argument,444
seen,445
decoding,446
)447
case None => "reject"448
}449
}450
if widened.name == "Map" {451
let key = match widened.arguments.get(0) {452
case Some(argument) => {453
let shown = widen(argument)454
if (shown.kind == "primitive" || shown.kind == "literal") &&455
shown.name == "string" {456
"accept"457
} else if shown.kind == "inference-variable" {458
"defer"459
} else {460
"reject"461
}462
}463
case None => "reject"464
}465
return capabilityAnd(key, match widened.arguments.get(1) {466
case Some(argument) => jsonCapability(467
state,468
moduleIndex,469
argument,470
seen,471
decoding,472
)473
case None => "reject"474
})475
}476
return "reject"477
}478
if widened.kind == "record" {479
let mut status = "accept"480
for field in widened.fields {481
status = capabilityAnd(status, jsonCapability(482
state,483
moduleIndex,484
field,485
seen,486
decoding,487
))488
}489
return status490
}491
if nominalCapabilityType(widened) {492
let key = nominalCapabilityKey(widened)493
if containsString(seen, key) {494
return if decoding { "reject" } else { "accept" }495
}496
let mut status = "accept"497
let projection = nominalCapabilityProjection(state, moduleIndex, widened)498
let nested = capabilityPath(seen, key)499
for child in projection.children {500
status = capabilityAnd(status, jsonCapability(501
state,502
projection.moduleIndex,503
child,504
nested,505
decoding,506
))507
}508
return status509
}510
"reject"511
}513
function nonKeyableType(514
state: CheckerState,515
moduleIndex: int,516
value: TypeValue,517
seen: Array<string>,518
) -> Option<KeyCapabilityFailure> {519
let widened = widen(value)520
if widened.kind == "byte-buffer" {521
return Some(KeyCapabilityFailure {522
subject: "ByteBuffer",523
kind: "mutable byte buffer",524
})525
}526
if widened.kind == "constructor" {527
if widened.name == "Map" {528
return Some(KeyCapabilityFailure {529
subject: "`Map` value",530
kind: "Map",531
})532
}533
if widened.name == "Set" {534
return Some(KeyCapabilityFailure {535
subject: "`Set` value",536
kind: "Set",537
})538
}539
if widened.name == "Range" {540
return Some(KeyCapabilityFailure {541
subject: "`range` value",542
kind: "range",543
})544
}545
for argument in widened.arguments {546
match nonKeyableType(state, moduleIndex, argument, seen) {547
case Some(failure) => return Some(failure)548
case None => ()549
}550
}551
return None552
}553
if widened.kind == "function" {554
return Some(KeyCapabilityFailure {555
subject: "function value",556
kind: "function",557
})558
}559
let specialKind = match widened.kind {560
case "template" => "template"561
case "file" => "File"562
case "json-value" => "JSONValue"563
case "regex" => "Regex"564
case "rounding-mode" => "RoundingMode"565
case "match" => "Match"566
case "toml-value" => "TOMLValue"567
case _ => ""568
}569
if specialKind != "" {570
return Some(KeyCapabilityFailure {571
subject: if specialKind == "template" {572
"template value"573
} else {574
"`{specialKind}` value"575
},576
kind: specialKind,577
})578
}579
if widened.kind == "union" {580
for member in widened.members {581
match nonKeyableType(state, moduleIndex, member, seen) {582
case Some(failure) => return Some(failure)583
case None => ()584
}585
}586
return None587
}588
if widened.kind == "record" {589
for field in widened.fields {590
match nonKeyableType(state, moduleIndex, field, seen) {591
case Some(failure) => return Some(failure)592
case None => ()593
}594
}595
return None596
}597
if nominalCapabilityType(widened) {598
let key = nominalCapabilityKey(widened)599
if containsString(seen, key) {600
return None601
}602
let projection = nominalCapabilityProjection(state, moduleIndex, widened)603
let nested = capabilityPath(seen, key)604
for child in projection.children {605
match nonKeyableType(606
state,607
projection.moduleIndex,608
child,609
nested,610
) {611
case Some(failure) => return Some(failure)612
case None => ()613
}614
}615
return None616
}617
None618
}620
export function gateCollectionKey(621
service: CheckerService,622
state: CheckerState,623
moduleIndex: int,624
node: SyntaxNode,625
value: TypeValue,626
) -> () {627
match nonKeyableType(state, moduleIndex, value, []) {628
case Some(failure) => addDiagnostic(629
state,630
moduleIndex,631
"TPZ5007",632
"{failure.subject} is not a valid Map/Set key ({failure.kind} keys are not supported yet)",633
"",634
node,635
)636
case None => ()637
}638
()639
}641
function declaredMemberType(642
state: CheckerState,643
moduleIndex: int,644
object: TypeValue,645
name: string,646
) -> Option<TypeValue> {647
match recordDefinitionForType(state, moduleIndex, object) {648
case Some(definition) => for field in definition.fields {649
if field.name == name {650
return Some(substitute(651
field.typeValue,652
definition.typeParameters,653
object.arguments,654
))655
}656
}657
case None => ()658
}659
match enumDefinitionForType(state, moduleIndex, object) {660
case Some(definition) => for variant in definition.variants {661
if variant.name == name {662
if variant.payloads.length == 0 {663
return Some(object)664
}665
let mut payloads: Array<TypeValue> = []666
for payload in variant.payloads {667
payloads.push(substitute(668
payload,669
definition.typeParameters,670
object.arguments,671
))672
}673
return Some(functionType(payloads, [], object))674
}675
}676
case None => ()677
}678
match newtypeDefinitionForType(state, moduleIndex, object) {679
case Some(definition) => if name == "value" {680
return Some(functionType([], [], substitute(681
definition.base,682
definition.typeParameters,683
object.arguments,684
)))685
}686
case None => ()687
}688
if object.kind == "record" {689
let mut index = 0690
for fieldName in object.fieldNames {691
if fieldName == name {692
return Some(match object.fields.get(index) {693
case Some(found) => found694
case None => emptyType("unknown")695
})696
}697
index += 1698
}699
}700
None701
}703
export function typeArgument(object: TypeValue, index: int) -> TypeValue {704
match object.arguments.get(index) {705
case Some(found) => found706
case None => emptyType("unknown")707
}708
}710
function optionMemberType(object: TypeValue, name: string) -> TypeValue {711
let value = typeArgument(object, 0)712
let output = rigid("U", "builtin::Option.{name}")713
match name {714
case "okOr" => functionType(715
[output],716
[],717
constructor("Result", [value, output]),718
)719
case "okOrElse" => functionType(720
[functionType([], [], output)],721
[],722
constructor("Result", [value, output]),723
)724
case "map" => functionType(725
[functionType([value], [], output)],726
[],727
constructor("Option", [output]),728
)729
case "flatMap" => functionType(730
[functionType([value], [], constructor("Option", [output]))],731
[],732
constructor("Option", [output]),733
)734
case _ => emptyType("unknown")735
}736
}738
function resultMemberType(object: TypeValue, name: string) -> TypeValue {739
let value = typeArgument(object, 0)740
let error = typeArgument(object, 1)741
let output = rigid("U", "builtin::Result.{name}")742
match name {743
case "map" => functionType(744
[functionType([value], [], output)],745
[],746
constructor("Result", [output, error]),747
)748
case "flatMap" => functionType(749
[functionType(750
[value],751
[],752
constructor("Result", [output, error]),753
)],754
[],755
constructor("Result", [output, error]),756
)757
case _ => emptyType("unknown")758
}759
}761
function arrayMemberType(object: TypeValue, name: string) -> TypeValue {762
let element = iterableElement(object)763
match name {764
case "length" => primitive("int")765
case "get" => functionType(766
[primitive("int")],767
[],768
constructor("Option", [element]),769
)770
case "slice" => functionType(771
[primitive("int"), primitive("int")],772
[],773
object,774
)775
case "map" => {776
let output = rigid("U", "builtin::Array.map")777
functionType(778
[functionType([element], [], output)],779
[],780
constructor("Array", [output]),781
)782
}783
case "join" => functionType([primitive("string")], [], primitive("string"))784
case "sorted" => functionType([], [], object)785
case "sortedBy" => {786
let key = rigid("K", "builtin::Array.sortedBy")787
functionType([functionType([element], [], key)], [], object)788
}789
case "filter" => functionType(790
[functionType([element], [], primitive("bool"))],791
[],792
object,793
)794
case "reduce" => {795
let output = rigid("U", "builtin::Array.reduce")796
functionType(797
[output, functionType([output, element], [], output)],798
[],799
output,800
)801
}802
case "indexOf" => functionType(803
[element],804
[],805
constructor("Option", [primitive("int")]),806
)807
case "push" => functionType([element], [], primitive("unit"))808
case "pop" => functionType([], [], constructor("Option", [element]))809
case "clear" | "reverse" | "sort" =>810
functionType([], [], primitive("unit"))811
case "sortBy" => {812
let key = rigid("K", "builtin::Array.sortBy")813
functionType(814
[functionType([element], [], key)],815
[],816
primitive("unit"),817
)818
}819
case "retain" => functionType(820
[functionType([element], [], primitive("bool"))],821
[],822
primitive("unit"),823
)824
case "insert" => functionType(825
[primitive("int"), element],826
[],827
primitive("unit"),828
)829
case "removeAt" => functionType(830
[primitive("int")],831
[],832
constructor("Option", [element]),833
)834
case _ => emptyType("unknown")835
}836
}838
function setMemberType(object: TypeValue, name: string) -> TypeValue {839
let element = iterableElement(object)840
match name {841
case "length" => primitive("int")842
case "toArray" => functionType([], [], constructor("Array", [element]))843
case "contains" => functionType([element], [], primitive("bool"))844
case "add" => functionType([element], [], primitive("unit"))845
case "remove" => functionType([element], [], primitive("bool"))846
case "isEmpty" => functionType([], [], primitive("bool"))847
case "union" | "intersection" | "difference" =>848
functionType([object], [], object)849
case "clear" => functionType([], [], primitive("unit"))850
case _ => emptyType("unknown")851
}852
}854
function mapMemberType(object: TypeValue, name: string) -> TypeValue {855
let key = typeArgument(object, 0)856
let item = typeArgument(object, 1)857
match name {858
case "length" => primitive("int")859
case "keys" => constructor("Array", [key])860
case "values" => constructor("Array", [item])861
case "entries" => constructor("Array", [structural(862
["key", "value"],863
[key, item],864
)])865
case "get" => functionType([key], [], constructor("Option", [item]))866
case "getOr" => functionType([key, item], [], item)867
case "containsKey" => functionType([key], [], primitive("bool"))868
case "insert" => functionType([key, item], [], primitive("unit"))869
case "remove" => functionType([key], [], constructor("Option", [item]))870
case "isEmpty" => functionType([], [], primitive("bool"))871
case "clear" => functionType([], [], primitive("unit"))872
case "update" => functionType(873
[key, item, functionType([item], [], item)],874
[],875
primitive("unit"),876
)877
case "mapValues" => {878
let output = rigid("W", "builtin::Map.mapValues")879
functionType(880
[functionType([item], [], output)],881
[],882
constructor("Map", [key, output]),883
)884
}885
case "filter" => functionType(886
[functionType([key, item], [], primitive("bool"))],887
[],888
object,889
)890
case _ => emptyType("unknown")891
}892
}894
function stringMemberType(name: string) -> TypeValue {895
match name {896
case "byteLength" => functionType([], [], primitive("int"))897
case "trim" | "trimStart" | "trimEnd" =>898
functionType([], [], primitive("string"))899
case "contains" | "startsWith" | "endsWith" =>900
functionType([primitive("string")], [], primitive("bool"))901
case "replace" => functionType(902
[primitive("string"), primitive("string")],903
[],904
primitive("string"),905
)906
case "indexOf" | "lastIndexOf" => functionType(907
[primitive("string")],908
[],909
constructor("Option", [primitive("int")]),910
)911
case "split" => functionType(912
[primitive("string")],913
[],914
constructor("Array", [primitive("string")]),915
)916
case "slice" => functionType(917
[primitive("int"), primitive("int")],918
[],919
primitive("string"),920
)921
case "codePointAt" => functionType(922
[primitive("int")],923
[],924
constructor("Option", [primitive("int")]),925
)926
case "scalars" => functionType(927
[],928
[],929
constructor("Array", [primitive("string")]),930
)931
case _ => emptyType("unknown")932
}933
}935
function bytesMemberType(name: string) -> TypeValue {936
match name {937
case "length" => functionType([], [], primitive("int"))938
case "isEmpty" => functionType([], [], primitive("bool"))939
case "get" => functionType(940
[primitive("int")],941
[],942
constructor("Option", [primitive("int")]),943
)944
case "slice" => functionType(945
[primitive("int"), primitive("int")],946
[],947
emptyType("bytes"),948
)949
case "decodeUtf8" => functionType(950
[],951
[],952
constructor("Result", [primitive("string"), primitive("string")]),953
)954
case "toArray" => functionType(955
[],956
[],957
constructor("Array", [primitive("int")]),958
)959
case "toHex" | "toBase64" => functionType([], [], primitive("string"))960
case _ => emptyType("unknown")961
}962
}964
function byteBufferMemberType(name: string) -> TypeValue {965
match name {966
case "length" => functionType([], [], primitive("int"))967
case "get" => functionType([primitive("int")], [], primitive("int"))968
case "set" => functionType(969
[primitive("int"), primitive("int")],970
[],971
primitive("unit"),972
)973
case "fill" => functionType(974
[primitive("int"), primitive("int"), primitive("int")],975
[],976
primitive("unit"),977
)978
case "copy" => functionType(979
[980
emptyType("byte-buffer"),981
primitive("int"),982
primitive("int"),983
primitive("int"),984
],985
[],986
primitive("unit"),987
)988
case "toBytes" => functionType([], [], emptyType("bytes"))989
case _ => emptyType("unknown")990
}991
}993
function fileMemberType(name: string) -> TypeValue {994
match name {995
case "read" => functionType(996
[],997
[],998
constructor("Result", [primitive("string"), primitive("string")]),999
)1000
case "write" => functionType(SHA256 9fba93e664b71aa80f1f016d13ac92225fa35e8ed226404ea798e79312370489