slow.monster

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 the
3// checker-private nominal catalogs consumed by later phases.
5import types {
6 PreviewResolvedDeclaration,
7 SyntaxNode,
8}
9import checker_types { TypeValue }
10import 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,
34import checker.type_algebra {
35 transformTypeComponents,
36 typeEqual,
37 typeText,
38 unionType,
40import 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,
70function 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 _ => None
77 }
80function 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 _ => None
97 }
100function builtinTypeConstructorArity(name: string) -> int {
101 match name {
102 case "Array" | "Set" | "Option" => 1
103 case "Map" | "Result" => 2
104 case _ => -1
105 }
108export 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 )
127function typeParameterComponentReplacement(
128 value: TypeValue,
129 names: Array<string>,
130 arguments: Array<TypeValue>,
131) -> Option<TypeValue> {
132 if value.kind != "rigid" {
133 return None
134 }
135 let mut index = 0
136 for name in names {
137 if value.name == name {
138 return Some(match arguments.get(index) {
139 case Some(found) => found
140 case None => emptyType("unknown")
141 })
142 }
143 index += 1
144 }
145 Some(value)
148export 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 )
163function 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 None
177export 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.targetModuleIndex
191 } else {
192 moduleIndex
193 },
194 name: if reference.targetName != "" {
195 reference.targetName
196 } else {
197 sourceName
198 },
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 target
226 }
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 moduleIndex
233 },
234 name: if declaration.targetName != "" {
235 declaration.targetName
236 } else {
237 sourceName
238 },
239 }
240 case None => TypeTarget {
241 moduleIndex: moduleIndex,
242 name: sourceName,
243 }
244 }
245 }
248function 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) => found
261 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 }
275function 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)
299function 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 continue
325 }
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)
344function 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)
389function 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) => found
400 case None => node
401 }
402 let name = text(source, nameNode)
403 let argumentNodes = children(node, "arguments")
404 let argumentCount = argumentNodes.length
405 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 value
431 }
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 value
447 }
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 value
463 }
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 formed
506 }
507 let target = targetType(state, moduleIndex, nameNode)
508 let targetModule = target.moduleIndex
509 let targetName = target.name
510 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 name
518 }
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)
607export 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 }
631export function predeclareNominals(state: CheckerState) -> () {
632 let mut records = state.records
633 let mut enums = state.enums
634 let mut newtypes = state.newtypes
635 let mut aliases = state.aliases
636 let mut recordIndexes = state.recordIndexes
637 let mut enumIndexes = state.enumIndexes
638 let mut newtypeIndexes = state.newtypeIndexes
639 let mut aliasIndexes = state.aliasIndexes
640 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) => found
665 case None => item
666 },
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 ()
730record NominalFillContext {
731 state: CheckerState,
732 module: ModuleSyntax,
733 item: SyntaxNode,
734 name: string,
735 origin: string,
736 parameters: Array<string>,
739function 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 }
758function 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 }
778function 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 continue
795 }
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(_) => true
813 case None => false
814 },
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 }
827function 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 continue
842 }
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 = false
864 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 }
889function 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 }
916function 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.aliases
935 aliases[index] = AliasDefinition {
936 moduleIndex: context.module.moduleIndex,
937 name: context.name,
938 typeParameters: context.parameters,
939 body: body,
940 }
941 }
944function 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 }
964export 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 ()
973function 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,
1003function 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) => found
1015 case None => return None
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,
1028 return None
1030 case None => ()
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,
1041 return None
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,
1053 return None
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,
1066 return None
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,
1079 return None
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,
1093 return None
1095 case None => ()
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,
1111 case None => emptyType("unknown")
1112 })
1114 let firstTypeNode = match child(firstParameter, "type") {
1115 case Some(found) => found
1116 case None => firstParameter
1118 let firstType = match parameterTypes.get(0) {
1119 case Some(found) => found
1120 case None => emptyType("unknown")
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,
1131 return None
1133 let resultType = normalizedProtocolType(
1134 service,
1135 state,
1136 module,
1137 returnTypeNode,
1138 explicitParameter,
1139 origin,
1140 conformingType,
1142 let mut indexes = methodIndexes
1143 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 })
1151 Some(ProtocolMethodDefinition {
1152 name: methodName,
1153 parameterNames: parameterNames,
1154 typeValue: functionType(parameterTypes, [], resultType),
1155 })
1158function 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 ()
1168 let nameNode = match child(item, "name") {
1169 case Some(found) => found
1170 case None => return ()
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,
1182 return ()
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,
1193 return ()
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,
1207 return ()
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,
1219 return ()
1221 let explicitParameter = match typeParameters.get(0) {
1222 case Some(found) => found
1223 case None => ""
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 => ()
1243 let mut indexes = state.protocolIndexes
1244 let mut protocols = state.protocols
1245 indexes.insert(definitionNameKey(module.moduleIndex, name), protocols.length)
1246 protocols.push(ProtocolDefinition {
1247 name: name,
1248 methods: protocolMethods,
1249 })
1250 ()
1253export 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)
1259 ()

SHA256 bdbf1655fcb14e6463fcb03af1a8a947c8ac381972016b6229647c0d6a05a75f