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/nominals.tpz
Topaz · 33013 B · Original file
1
// Predeclaration and filling of record, enum, newtype, and alias definitions.2
// Public type identity remains in shared exchange types; this leaf populates the3
// checker-private nominal catalogs consumed by later phases.5
import types {6
PreviewResolvedDeclaration,7
SyntaxNode,8
}9
import checker_types { TypeValue }10
import checker.model {11
AliasDefinition,12
CheckerService,13
CheckerState,14
EnumDefinition,15
EnumVariant,16
ModuleSyntax,17
NewtypeDefinition,18
ProtocolDefinition,19
ProtocolMethodDefinition,20
RecordDefinition,21
RecordField,22
TypeTarget,23
constructor,24
emptyType,25
functionType,26
literal,27
nominal,28
nominalInModule,29
primitive,30
rigid,31
serviceGateCollectionKey,32
structural,33
}34
import checker.type_algebra {35
transformTypeComponents,36
typeEqual,37
typeText,38
unionType,39
}40
import checker.syntax_index {41
activeTypeParameter,42
addDiagnostic,43
addNode,44
aliasDefinitionIndex,45
boolAttribute,46
child,47
children,48
containsString,49
declarationFor,50
definitionNameKey,51
enumDefinitionIndex,52
moduleAt,53
moduleIdentityAt,54
moduleIndexByIdentity,55
namedNominalTarget,56
newtypeDefinitionIndex,57
protocolDefinitionIndex,58
recordDefinitionIndex,59
referenceFor,60
registerTypeParameters,61
replaceEnumDefinition,62
replaceNewtypeDefinition,63
replaceRecordDefinition,64
text,65
typeParameterNames,66
typeParameterOrigin,67
unwrapExport,68
}70
function primitiveNamedType(name: string) -> Option<TypeValue> {71
match name {72
case "int" => Some(primitive("int"))73
case "float" => Some(primitive("float"))74
case "string" => Some(primitive("string"))75
case "bool" => Some(primitive("bool"))76
case _ => None77
}78
}80
function terminalNamedType(name: string) -> Option<TypeValue> {81
match name {82
case "template" => Some(emptyType("template"))83
case "JSONValue" => Some(emptyType("json-value"))84
case "File" => Some(emptyType("file"))85
case "Bytes" => Some(emptyType("bytes"))86
case "ByteBuffer" => Some(emptyType("byte-buffer"))87
case "Path" => Some(emptyType("path"))88
case "Regex" => Some(emptyType("regex"))89
case "Match" => Some(emptyType("match"))90
case "TOMLValue" => Some(emptyType("toml-value"))91
case "URL" => Some(emptyType("url"))92
case "Date" => Some(emptyType("date"))93
case "BigInt" => Some(emptyType("big-int"))94
case "Decimal" => Some(emptyType("decimal"))95
case "RoundingMode" => Some(emptyType("rounding-mode"))96
case _ => None97
}98
}100
function builtinTypeConstructorArity(name: string) -> int {101
match name {102
case "Array" | "Set" | "Option" => 1103
case "Map" | "Result" => 2104
case _ => -1105
}106
}108
export function addTypeArgumentArityDiagnostic(109
state: CheckerState,110
moduleIndex: int,111
subject: string,112
expected: int,113
found: int,114
node: SyntaxNode,115
) -> () {116
let suffix = if expected == 1 { "" } else { "s" }117
addDiagnostic(118
state,119
moduleIndex,120
"TPZ5022",121
"{subject} takes {expected} type argument{suffix}, found {found}",122
"",123
node,124
)125
}127
function typeParameterComponentReplacement(128
value: TypeValue,129
names: Array<string>,130
arguments: Array<TypeValue>,131
) -> Option<TypeValue> {132
if value.kind != "rigid" {133
return None134
}135
let mut index = 0136
for name in names {137
if value.name == name {138
return Some(match arguments.get(index) {139
case Some(found) => found140
case None => emptyType("unknown")141
})142
}143
index += 1144
}145
Some(value)146
}148
export function substitute(149
value: TypeValue,150
names: Array<string>,151
arguments: Array<TypeValue>,152
) -> TypeValue {153
transformTypeComponents(154
value,155
(component: TypeValue) => typeParameterComponentReplacement(156
component,157
names,158
arguments,159
),160
)161
}163
function typeDeclarationForName(164
state: CheckerState,165
moduleIndex: int,166
name: string,167
) -> Option<PreviewResolvedDeclaration> {168
for declaration in state.resolved.declarations {169
if declaration.moduleIndex == moduleIndex &&170
declaration.namespace == "type" && declaration.name == name {171
return Some(declaration)172
}173
}174
None175
}177
export function targetType(178
state: CheckerState,179
moduleIndex: int,180
node: SyntaxNode,181
) -> TypeTarget {182
let sourceName = match moduleAt(state, moduleIndex) {183
case Some(module) => text(module.source, node)184
case None => ""185
}186
match referenceFor(state, moduleIndex, node.lo, node.hi) {187
case Some(reference) => {188
let mut target = TypeTarget {189
moduleIndex: if reference.targetModuleIndex >= 0 {190
reference.targetModuleIndex191
} else {192
moduleIndex193
},194
name: if reference.targetName != "" {195
reference.targetName196
} else {197
sourceName198
},199
}200
match declarationFor(201
state,202
reference.targetModuleIndex,203
reference.targetLo,204
reference.targetHi,205
) {206
case Some(declaration) => {207
if declaration.targetModule != "" {208
target = TypeTarget {209
moduleIndex: moduleIndexByIdentity(210
state,211
declaration.targetModule,212
),213
name: target.name,214
}215
}216
if declaration.targetName != "" {217
target = TypeTarget {218
moduleIndex: target.moduleIndex,219
name: declaration.targetName,220
}221
}222
}223
case None => ()224
}225
target226
}227
case None => match typeDeclarationForName(state, moduleIndex, sourceName) {228
case Some(declaration) => TypeTarget {229
moduleIndex: if declaration.targetModule != "" {230
moduleIndexByIdentity(state, declaration.targetModule)231
} else {232
moduleIndex233
},234
name: if declaration.targetName != "" {235
declaration.targetName236
} else {237
sourceName238
},239
}240
case None => TypeTarget {241
moduleIndex: moduleIndex,242
name: sourceName,243
}244
}245
}246
}248
function formLiteralType(source: Bytes, node: SyntaxNode) -> TypeValue {249
let spelling = text(source, node)250
if spelling == "true" || spelling == "false" {251
return literal("bool", spelling)252
}253
if spelling == "null" {254
return literal("null", "")255
}256
if spelling.startsWith("\"") {257
let bytes = Bytes.encodeUtf8(spelling)258
let decoded = if bytes.length() >= 2 {259
match bytes.slice(1, bytes.length() - 1).decodeUtf8() {260
case Ok(found) => found261
case Err(_) => ""262
}263
} else {264
""265
}266
return literal("string", decoded)267
}268
if spelling.contains(".") {269
literal("float", spelling)270
} else {271
literal("int", spelling)272
}273
}275
function formUnionType(276
service: CheckerService,277
state: CheckerState,278
moduleIndex: int,279
source: Bytes,280
node: SyntaxNode,281
parameters: Array<string>,282
origin: string,283
) -> TypeValue {284
let mut members: Array<TypeValue> = []285
for member in children(node, "members") {286
members.push(formType(287
service,288
state,289
moduleIndex,290
source,291
member,292
parameters,293
origin,294
))295
}296
unionType(members)297
}299
function formRecordType(300
service: CheckerService,301
state: CheckerState,302
moduleIndex: int,303
source: Bytes,304
node: SyntaxNode,305
parameters: Array<string>,306
origin: string,307
) -> TypeValue {308
let mut names: Array<string> = []309
let mut values: Array<TypeValue> = []310
for field in children(node, "fields") {311
let name = match child(field, "name") {312
case Some(found) => text(source, found)313
case None => ""314
}315
if containsString(names, name) {316
addDiagnostic(317
state,318
moduleIndex,319
"TPZ5022",320
"record type declares field `{name}` twice",321
"",322
field,323
)324
continue325
}326
let value = match child(field, "type") {327
case Some(typeNode) => formType(328
service,329
state,330
moduleIndex,331
source,332
typeNode,333
parameters,334
origin,335
)336
case None => emptyType("unknown")337
}338
names.push(name)339
values.push(value)340
}341
structural(names, values)342
}344
function formFunctionType(345
service: CheckerService,346
state: CheckerState,347
moduleIndex: int,348
source: Bytes,349
node: SyntaxNode,350
parameters: Array<string>,351
origin: string,352
) -> TypeValue {353
let mut fixed: Array<TypeValue> = []354
let mut variadic: Array<TypeValue> = []355
for parameter in children(node, "parameters") {356
let formed = match child(parameter, "type") {357
case Some(typeNode) => formType(358
service,359
state,360
moduleIndex,361
source,362
typeNode,363
parameters,364
origin,365
)366
case None => emptyType("unknown")367
}368
if boolAttribute(parameter, "variadic") {369
variadic.push(formed)370
} else {371
fixed.push(formed)372
}373
}374
let result = match child(node, "returnType") {375
case Some(typeNode) => formType(376
service,377
state,378
moduleIndex,379
source,380
typeNode,381
parameters,382
origin,383
)384
case None => primitive("unit")385
}386
functionType(fixed, variadic, result)387
}389
function formNamedType(390
service: CheckerService,391
state: CheckerState,392
moduleIndex: int,393
source: Bytes,394
node: SyntaxNode,395
parameters: Array<string>,396
origin: string,397
) -> TypeValue {398
let nameNode = match child(node, "name") {399
case Some(found) => found400
case None => node401
}402
let name = text(source, nameNode)403
let argumentNodes = children(node, "arguments")404
let argumentCount = argumentNodes.length405
if containsString(parameters, name) {406
if argumentCount > 0 {407
addDiagnostic(408
state,409
moduleIndex,410
"TPZ5022",411
"type parameter `{name}` takes no type arguments",412
"",413
nameNode,414
)415
}416
return rigid(name, typeParameterOrigin(state, moduleIndex, name, origin))417
}418
match activeTypeParameter(state, moduleIndex, name) {419
case Some(value) => {420
if argumentCount > 0 {421
addDiagnostic(422
state,423
moduleIndex,424
"TPZ5022",425
"type parameter `{name}` takes no type arguments",426
"",427
nameNode,428
)429
}430
return value431
}432
case None => ()433
}434
match primitiveNamedType(name) {435
case Some(value) => {436
if argumentCount > 0 {437
addDiagnostic(438
state,439
moduleIndex,440
"TPZ5022",441
"primitive type `{name}` takes no type arguments",442
"",443
nameNode,444
)445
}446
return value447
}448
case None => ()449
}450
match terminalNamedType(name) {451
case Some(value) => {452
if argumentCount > 0 {453
addDiagnostic(454
state,455
moduleIndex,456
"TPZ5022",457
"`{name}` takes no type arguments",458
"",459
nameNode,460
)461
}462
return value463
}464
case None => ()465
}466
let constructorArity = builtinTypeConstructorArity(name)467
if constructorArity >= 0 && argumentCount != constructorArity {468
addTypeArgumentArityDiagnostic(469
state,470
moduleIndex,471
"`{name}`",472
constructorArity,473
argumentCount,474
nameNode,475
)476
return emptyType("unknown")477
}478
if constructorArity >= 0 {479
let mut arguments: Array<TypeValue> = []480
for argument in argumentNodes {481
arguments.push(formType(482
service,483
state,484
moduleIndex,485
source,486
argument,487
parameters,488
origin,489
))490
}491
let formed = constructor(name, arguments)492
if (name == "Map" || name == "Set") &&493
state.typeCapabilitySuppressions.length == 0 {494
match arguments.get(0) {495
case Some(key) => serviceGateCollectionKey(496
service,497
state,498
moduleIndex,499
nameNode,500
key,501
)502
case None => ()503
}504
}505
return formed506
}507
let target = targetType(state, moduleIndex, nameNode)508
let targetModule = target.moduleIndex509
let targetName = target.name510
let displayName = if node.kind == "type/qualified" {511
let namespace = match child(node, "namespace") {512
case Some(found) => text(source, found)513
case None => ""514
}515
"{namespace}.{name}"516
} else {517
name518
}519
let aliasIndex = aliasDefinitionIndex(state, targetModule, targetName)520
if aliasIndex >= 0 && targetModule == moduleIndex &&521
node.kind != "type/qualified" {522
match state.aliases.get(aliasIndex) {523
case Some(alias) => if argumentCount != alias.typeParameters.length {524
addTypeArgumentArityDiagnostic(525
state,526
moduleIndex,527
"type alias `{displayName}`",528
alias.typeParameters.length,529
argumentCount,530
nameNode,531
)532
return emptyType("unknown")533
}534
case None => ()535
}536
}537
let mut arguments: Array<TypeValue> = []538
for argument in argumentNodes {539
arguments.push(formType(540
service,541
state,542
moduleIndex,543
source,544
argument,545
parameters,546
origin,547
))548
}549
match namedNominalTarget(state, targetModule, targetName) {550
case Some(target) => {551
if arguments.length != target.typeArgumentCount {552
addTypeArgumentArityDiagnostic(553
state,554
moduleIndex,555
"{target.diagnosticKind} `{displayName}`",556
target.typeArgumentCount,557
arguments.length,558
nameNode,559
)560
return emptyType("unknown")561
}562
return nominalInModule(563
target.semanticKind,564
targetName,565
arguments,566
moduleIdentityAt(state, targetModule),567
)568
}569
case None => ()570
}571
if aliasIndex >= 0 {572
return match state.aliases.get(aliasIndex) {573
case Some(alias) => {574
if arguments.length != alias.typeParameters.length {575
let subject = if targetModule == moduleIndex &&576
node.kind != "type/qualified" {577
"type alias `{displayName}`"578
} else {579
"`{displayName}`"580
}581
addTypeArgumentArityDiagnostic(582
state,583
moduleIndex,584
subject,585
alias.typeParameters.length,586
arguments.length,587
nameNode,588
)589
emptyType("unknown")590
} else {591
substitute(alias.body, alias.typeParameters, arguments)592
}593
}594
case None => emptyType("unknown")595
}596
}597
if node.kind == "type/qualified" {598
let namespace = match child(node, "namespace") {599
case Some(found) => text(source, found)600
case None => ""601
}602
return nominal("foreign", "{namespace}.{name}", arguments)603
}604
nominal("foreign", name, arguments)605
}607
export function formType(608
service: CheckerService,609
state: CheckerState,610
moduleIndex: int,611
source: Bytes,612
node: SyntaxNode,613
parameters: Array<string>,614
origin: string,615
) -> TypeValue {616
match node.kind {617
case "type/unit" => primitive("unit")618
case "type/literal" => formLiteralType(source, node)619
case "type/union" =>620
formUnionType(service, state, moduleIndex, source, node, parameters, origin)621
case "type/record" =>622
formRecordType(service, state, moduleIndex, source, node, parameters, origin)623
case "type/function" =>624
formFunctionType(service, state, moduleIndex, source, node, parameters, origin)625
case "type/named" | "type/qualified" =>626
formNamedType(service, state, moduleIndex, source, node, parameters, origin)627
case _ => emptyType("unknown")628
}629
}631
export function predeclareNominals(state: CheckerState) -> () {632
let mut records = state.records633
let mut enums = state.enums634
let mut newtypes = state.newtypes635
let mut aliases = state.aliases636
let mut recordIndexes = state.recordIndexes637
let mut enumIndexes = state.enumIndexes638
let mut newtypeIndexes = state.newtypeIndexes639
let mut aliasIndexes = state.aliasIndexes640
for module in state.modules {641
for statement in module.root.children {642
let item = unwrapExport(statement)643
let nameNode = child(item, "name")644
let name = match nameNode {645
case Some(found) => text(module.source, found)646
case None => ""647
}648
let parameters = typeParameterNames(module.source, item)649
let nominalKind = match item.kind {650
case "statement/record" => "record"651
case "statement/enum" => "enum"652
case "statement/newtype" => "newtype"653
case _ => ""654
}655
let reservedNominal = nominalKind != "" && name == "template"656
if reservedNominal {657
addDiagnostic(658
state,659
module.moduleIndex,660
"TPZ5022",661
"{nominalKind} name `template` collides with the builtin `template` type; choose another name",662
"",663
match nameNode {664
case Some(found) => found665
case None => item666
},667
)668
}669
if !reservedNominal &&670
(item.kind == "statement/record" || item.kind == "statement/enum" ||671
item.kind == "statement/newtype" ||672
item.kind == "statement/type-alias") {673
registerTypeParameters(674
state,675
module.moduleIndex,676
module.source,677
item,678
)679
}680
if !reservedNominal && item.kind == "statement/record" {681
recordIndexes.insert(682
definitionNameKey(module.moduleIndex, name),683
records.length,684
)685
records.push(RecordDefinition {686
moduleIndex: module.moduleIndex,687
name: name,688
typeParameters: parameters,689
fields: [],690
})691
} else if !reservedNominal && item.kind == "statement/enum" {692
enumIndexes.insert(693
definitionNameKey(module.moduleIndex, name),694
enums.length,695
)696
enums.push(EnumDefinition {697
moduleIndex: module.moduleIndex,698
name: name,699
typeParameters: parameters,700
variants: [],701
})702
} else if !reservedNominal && item.kind == "statement/newtype" {703
newtypeIndexes.insert(704
definitionNameKey(module.moduleIndex, name),705
newtypes.length,706
)707
newtypes.push(NewtypeDefinition {708
moduleIndex: module.moduleIndex,709
name: name,710
typeParameters: parameters,711
base: emptyType("unknown"),712
})713
} else if !reservedNominal && item.kind == "statement/type-alias" {714
aliasIndexes.insert(715
definitionNameKey(module.moduleIndex, name),716
aliases.length,717
)718
aliases.push(AliasDefinition {719
moduleIndex: module.moduleIndex,720
name: name,721
typeParameters: parameters,722
body: emptyType("unknown"),723
})724
}725
}726
}727
()728
}730
record NominalFillContext {731
state: CheckerState,732
module: ModuleSyntax,733
item: SyntaxNode,734
name: string,735
origin: string,736
parameters: Array<string>,737
}739
function nominalFillContext(740
state: CheckerState,741
module: ModuleSyntax,742
item: SyntaxNode,743
) -> NominalFillContext {744
let name = match child(item, "name") {745
case Some(found) => text(module.source, found)746
case None => ""747
}748
NominalFillContext {749
state: state,750
module: module,751
item: item,752
name: name,753
origin: "source:{module.sourceOrdinal}:{item.lo}:{item.hi}",754
parameters: typeParameterNames(module.source, item),755
}756
}758
function formNominalChildType(759
service: CheckerService,760
context: NominalFillContext,761
node: SyntaxNode,762
field: string,763
) -> TypeValue {764
match child(node, field) {765
case Some(found) => formType(766
service,767
context.state,768
context.module.moduleIndex,769
context.module.source,770
found,771
context.parameters,772
context.origin,773
)774
case None => emptyType("unknown")775
}776
}778
function fillRecordNominal(service: CheckerService, context: NominalFillContext) -> () {779
let index = recordDefinitionIndex(780
context.state,781
context.module.moduleIndex,782
context.name,783
)784
let mut fields: Array<RecordField> = []785
let mut names: Array<string> = []786
for field in children(context.item, "fields") {787
let name = match child(field, "name") {788
case Some(found) => text(context.module.source, found)789
case None => ""790
}791
if containsString(names, name) {792
addDiagnostic(context.state, context.module.moduleIndex, "TPZ5008",793
"record field `{name}` is declared twice in `{context.name}`", "", field)794
continue795
}796
names.push(name)797
let typeValue = formNominalChildType(service, context, field, "type")798
match child(field, "type") {799
case Some(typeNode) => addNode(800
context.state,801
context.module.moduleIndex,802
"type",803
typeNode,804
typeValue,805
)806
case None => ()807
}808
fields.push(RecordField {809
name: name,810
typeValue: typeValue,811
hasDefault: match child(field, "default") {812
case Some(_) => true813
case None => false814
},815
})816
}817
if index >= 0 {818
replaceRecordDefinition(context.state, index, RecordDefinition {819
moduleIndex: context.module.moduleIndex,820
name: context.name,821
typeParameters: context.parameters,822
fields: fields,823
})824
}825
}827
function fillEnumNominal(service: CheckerService, context: NominalFillContext) -> () {828
let index = enumDefinitionIndex(829
context.state,830
context.module.moduleIndex,831
context.name,832
)833
let mut variants: Array<EnumVariant> = []834
for variant in children(context.item, "variants") {835
let nameNode = match child(variant, "name") { case Some(found) => found; case None => variant }836
let name = text(context.module.source, nameNode)837
if name == "None" || name == "Some" || name == "Ok" || name == "Err" {838
addDiagnostic(context.state, context.module.moduleIndex, "TPZ5022",839
"enum variant `{name}` collides with a reserved prelude constructor; choose another variant name",840
"", nameNode)841
continue842
}843
let mut payloads: Array<TypeValue> = []844
for payload in children(variant, "payload") {845
let typeValue = formType(846
service,847
context.state,848
context.module.moduleIndex,849
context.module.source,850
payload,851
context.parameters,852
context.origin,853
)854
addNode(855
context.state,856
context.module.moduleIndex,857
"type",858
payload,859
typeValue,860
)861
payloads.push(typeValue)862
if typeValue.kind == "union" {863
let mut containsEnum = false864
for member in typeValue.members {865
if member.kind == "enum" { containsEnum = true }866
}867
if containsEnum {868
addDiagnostic(context.state, context.module.moduleIndex, "TPZ5022",869
"enum variant `{name}` has a payload `{typeText(typeValue)}` that is a union containing an enum — enum-in-union payloads are not supported",870
"", nameNode)871
}872
}873
}874
variants.push(EnumVariant {875
name: name,876
payloads: payloads,877
})878
}879
if index >= 0 {880
replaceEnumDefinition(context.state, index, EnumDefinition {881
moduleIndex: context.module.moduleIndex,882
name: context.name,883
typeParameters: context.parameters,884
variants: variants,885
})886
}887
}889
function fillNewtypeNominal(service: CheckerService, context: NominalFillContext) -> () {890
let index = newtypeDefinitionIndex(891
context.state,892
context.module.moduleIndex,893
context.name,894
)895
if index >= 0 {896
let base = formNominalChildType(service, context, context.item, "base")897
match child(context.item, "base") {898
case Some(typeNode) => addNode(899
context.state,900
context.module.moduleIndex,901
"type",902
typeNode,903
base,904
)905
case None => ()906
}907
replaceNewtypeDefinition(context.state, index, NewtypeDefinition {908
moduleIndex: context.module.moduleIndex,909
name: context.name,910
typeParameters: context.parameters,911
base: base,912
})913
}914
}916
function fillAliasNominal(service: CheckerService, context: NominalFillContext) -> () {917
let index = aliasDefinitionIndex(918
context.state,919
context.module.moduleIndex,920
context.name,921
)922
if index >= 0 {923
let body = formNominalChildType(service, context, context.item, "type")924
match child(context.item, "type") {925
case Some(typeNode) => addNode(926
context.state,927
context.module.moduleIndex,928
"type",929
typeNode,930
body,931
)932
case None => ()933
}934
let mut aliases = context.state.aliases935
aliases[index] = AliasDefinition {936
moduleIndex: context.module.moduleIndex,937
name: context.name,938
typeParameters: context.parameters,939
body: body,940
}941
}942
}944
function fillNominalDefinition(945
service: CheckerService,946
state: CheckerState,947
module: ModuleSyntax,948
item: SyntaxNode,949
) -> () {950
if item.kind != "statement/record" && item.kind != "statement/enum" &&951
item.kind != "statement/newtype" && item.kind != "statement/type-alias" {952
return ()953
}954
let context = nominalFillContext(state, module, item)955
match item.kind {956
case "statement/record" => fillRecordNominal(service, context)957
case "statement/enum" => fillEnumNominal(service, context)958
case "statement/newtype" => fillNewtypeNominal(service, context)959
case "statement/type-alias" => fillAliasNominal(service, context)960
case _ => ()961
}962
}964
export function fillNominals(service: CheckerService, state: CheckerState) -> () {965
for module in state.modules {966
for statement in module.root.children {967
fillNominalDefinition(service, state, module, unwrapExport(statement))968
}969
}970
()971
}973
function normalizedProtocolType(974
service: CheckerService,975
state: CheckerState,976
module: ModuleSyntax,977
node: SyntaxNode,978
explicitParameter: string,979
origin: string,980
conformingType: TypeValue,981
) -> TypeValue {982
let mut parameters: Array<string> = ["Self"]983
let mut replacements: Array<TypeValue> = [conformingType]984
if explicitParameter != "" {985
parameters.push(explicitParameter)986
replacements.push(conformingType)987
}988
substitute(989
formType(990
service,991
state,992
module.moduleIndex,993
module.source,994
node,995
parameters,996
origin,997
),998
parameters,999
replacements,1000
)1001
}1003
function checkProtocolMethod(1004
service: CheckerService,1005
state: CheckerState,1006
module: ModuleSyntax,1007
protocolName: string,1008
explicitParameter: string,1009
origin: string,1010
method: SyntaxNode,1011
methodIndexes: Map<string, int>,1012
) -> Option<ProtocolMethodDefinition> {1013
let methodNameNode = match child(method, "name") {1014
case Some(found) => found1015
case None => return None1016
}1017
let methodName = text(module.source, methodNameNode)1018
match methodIndexes.get(methodName) {1019
case Some(_) => {1020
addDiagnostic(1021
state,1022
module.moduleIndex,1023
"TPZ5008",1024
"method `{methodName}` is already declared in protocol `{protocolName}`",1025
"",1026
methodNameNode,1027
)1028
return None1029
}1030
case None => ()1031
}1032
if children(method, "typeParameters").length > 0 {1033
addDiagnostic(1034
state,1035
module.moduleIndex,1036
"TPZ5022",1037
"protocol method `{protocolName}.{methodName}` cannot be generic",1038
"",1039
methodNameNode,1040
)1041
return None1042
}1043
let methodParameters = children(method, "parameters")1044
if methodParameters.length == 0 {1045
addDiagnostic(1046
state,1047
module.moduleIndex,1048
"TPZ5022",1049
"protocol method `{protocolName}.{methodName}` must take the conforming value as its first parameter",1050
"",1051
methodNameNode,1052
)1053
return None1054
}1055
let firstParameter = methodParameters[0]1056
let returnTypeNodes = children(method, "returnType")1057
if returnTypeNodes.length == 0 {1058
addDiagnostic(1059
state,1060
module.moduleIndex,1061
"TPZ5022",1062
"protocol method `{protocolName}.{methodName}` requires an explicit return type (use `-> ()` for unit)",1063
"",1064
methodNameNode,1065
)1066
return None1067
}1068
let returnTypeNode = returnTypeNodes[0]1069
for parameter in methodParameters {1070
if boolAttribute(parameter, "variadic") {1071
addDiagnostic(1072
state,1073
module.moduleIndex,1074
"TPZ5022",1075
"protocol method `{protocolName}.{methodName}` cannot be variadic",1076
"",1077
parameter,1078
)1079
return None1080
}1081
}1082
for parameter in methodParameters {1083
match child(parameter, "default") {1084
case Some(_) => {1085
addDiagnostic(1086
state,1087
module.moduleIndex,1088
"TPZ5022",1089
"protocol method `{protocolName}.{methodName}` cannot declare parameter defaults",1090
"",1091
parameter,1092
)1093
return None1094
}1095
case None => ()1096
}1097
}1098
let conformingType = rigid("Self", origin)1099
let mut parameterTypes: Array<TypeValue> = []1100
for parameter in methodParameters {1101
parameterTypes.push(match child(parameter, "type") {1102
case Some(found) => normalizedProtocolType(1103
service,1104
state,1105
module,1106
found,1107
explicitParameter,1108
origin,1109
conformingType,1110
)1111
case None => emptyType("unknown")1112
})1113
}1114
let firstTypeNode = match child(firstParameter, "type") {1115
case Some(found) => found1116
case None => firstParameter1117
}1118
let firstType = match parameterTypes.get(0) {1119
case Some(found) => found1120
case None => emptyType("unknown")1121
}1122
if !typeEqual(firstType, conformingType) {1123
addDiagnostic(1124
state,1125
module.moduleIndex,1126
"TPZ5022",1127
"protocol method `{protocolName}.{methodName}` must use `Self` or the protocol's type parameter as its first parameter type",1128
"",1129
firstTypeNode,1130
)1131
return None1132
}1133
let resultType = normalizedProtocolType(1134
service,1135
state,1136
module,1137
returnTypeNode,1138
explicitParameter,1139
origin,1140
conformingType,1141
)1142
let mut indexes = methodIndexes1143
indexes.insert(methodName, 1)1144
let mut parameterNames: Array<string> = []1145
for parameter in methodParameters {1146
parameterNames.push(match child(parameter, "name") {1147
case Some(found) => text(module.source, found)1148
case None => ""1149
})1150
}1151
Some(ProtocolMethodDefinition {1152
name: methodName,1153
parameterNames: parameterNames,1154
typeValue: functionType(parameterTypes, [], resultType),1155
})1156
}1158
function collectProtocol(1159
service: CheckerService,1160
state: CheckerState,1161
module: ModuleSyntax,1162
statement: SyntaxNode,1163
) -> () {1164
let item = unwrapExport(statement)1165
if item.kind != "statement/protocol" {1166
return ()1167
}1168
let nameNode = match child(item, "name") {1169
case Some(found) => found1170
case None => return ()1171
}1172
let name = text(module.source, nameNode)1173
if name == "Show" || name == "Eq" || name == "Order" || name == "JSON" {1174
addDiagnostic(1175
state,1176
module.moduleIndex,1177
"TPZ5008",1178
"`{name}` is a builtin protocol and cannot be redeclared",1179
"",1180
nameNode,1181
)1182
return ()1183
}1184
if protocolDefinitionIndex(state, module.moduleIndex, name) >= 0 {1185
addDiagnostic(1186
state,1187
module.moduleIndex,1188
"TPZ5008",1189
"protocol `{name}` is already declared",1190
"",1191
nameNode,1192
)1193
return ()1194
}1195
if recordDefinitionIndex(state, module.moduleIndex, name) >= 0 ||1196
enumDefinitionIndex(state, module.moduleIndex, name) >= 0 ||1197
newtypeDefinitionIndex(state, module.moduleIndex, name) >= 0 ||1198
aliasDefinitionIndex(state, module.moduleIndex, name) >= 0 {1199
addDiagnostic(1200
state,1201
module.moduleIndex,1202
"TPZ5022",1203
"`{name}` is already a type and cannot also be a protocol",1204
"",1205
nameNode,1206
)1207
return ()1208
}1209
let typeParameters = typeParameterNames(module.source, item)1210
if typeParameters.length > 1 {1211
addDiagnostic(1212
state,1213
module.moduleIndex,1214
"TPZ5022",1215
"protocol `{name}` takes at most one conforming-type parameter; found {typeParameters.length}",1216
"",1217
nameNode,1218
)1219
return ()1220
}1221
let explicitParameter = match typeParameters.get(0) {1222
case Some(found) => found1223
case None => ""1224
}1225
let origin = "{module.identity}::{name}::protocol"1226
let mut methodIndexes: Map<string, int> = Map.new()1227
let mut protocolMethods: Array<ProtocolMethodDefinition> = []1228
for method in children(item, "methods") {1229
match checkProtocolMethod(1230
service,1231
state,1232
module,1233
name,1234
explicitParameter,1235
origin,1236
method,1237
methodIndexes,1238
) {1239
case Some(found) => protocolMethods.push(found)1240
case None => ()1241
}1242
}1243
let mut indexes = state.protocolIndexes1244
let mut protocols = state.protocols1245
indexes.insert(definitionNameKey(module.moduleIndex, name), protocols.length)1246
protocols.push(ProtocolDefinition {1247
name: name,1248
methods: protocolMethods,1249
})1250
()1251
}1253
export function collectProtocols(service: CheckerService, state: CheckerState) -> () {1254
for module in state.modules {1255
for statement in module.root.children {1256
collectProtocol(service, state, module, statement)1257
}1258
}1259
()1260
}SHA256 bdbf1655fcb14e6463fcb03af1a8a947c8ac381972016b6229647c0d6a05a75f