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/type_algebra.tpz

Topaz · 28215 B · Original file

1// Structural type equality, assignability, joins, and partial-type solving.
2// The operations are independent of syntax traversal; checker leaves pass only
3// TypeValue data across this boundary.
5import types { PreviewTypeAtom }
6import checker_types { TypeValue }
7import checker.model {
8 CheckerState,
9 TypeBinding,
10 constructor,
11 emptyType,
12 inference,
13 literal,
14 nominal,
15 primitive,
16 rigid,
19export function typeText(value: TypeValue) -> string {
20 match value.kind {
21 case "primitive" => if value.name == "unit" { "()" } else { value.name }
22 case "literal" => match value.name {
23 case "string" => "\"{value.value}\""
24 case "null" => "null"
25 case _ => value.value
26 }
27 case "constructor" => if value.name == "Range" {
28 "range"
29 } else {
30 "{value.name}<{typeListText(value.arguments)}>"
31 }
32 case "function" => {
33 let mut parameters = value.parameters
34 let mut textValue = typeListText(parameters)
35 match value.variadic.get(0) {
36 case Some(item) => {
37 if textValue != "" {
38 textValue = "{textValue}, "
39 }
40 textValue = "{textValue}...{typeText(item)}"
41 }
42 case None => ()
43 }
44 let resultValue = match value.result.get(0) {
45 case Some(item) => item
46 case None => emptyType("unknown")
47 }
48 "({textValue}) -> {typeText(resultValue)}"
49 }
50 case "union" => stringJoinTypes(value.members, " | ")
51 case "record" => {
52 let mut fields: Array<string> = []
53 let mut index = 0
54 for field in value.fields {
55 let name = match value.fieldNames.get(index) {
56 case Some(found) => found
57 case None => ""
58 }
59 fields.push("{name}: {typeText(field)}")
60 index += 1
61 }
62 "\{ " + stringJoin(fields, ", ") + " \}"
63 }
64 case "enum" | "nominal-record" | "newtype" | "foreign" => if value.arguments.length == 0 {
65 value.identity
66 } else {
67 "{value.identity}<{typeListText(value.arguments)}>"
68 }
69 case "rigid" => value.name
70 case "template" => "template"
71 case "file" => "File"
72 case "json-value" => "JSONValue"
73 case "bytes" => "Bytes"
74 case "byte-buffer" => "ByteBuffer"
75 case "path" => "Path"
76 case "regex" => "Regex"
77 case "match" => "Match"
78 case "toml-value" => "TOMLValue"
79 case "url" => "URL"
80 case "date" => "Date"
81 case "big-int" => "BigInt"
82 case "decimal" => "Decimal"
83 case "rounding-mode" => "RoundingMode"
84 case "inference-variable" => "?{value.value}"
85 case _ => "?"
86 }
89export function typeListText(values: Array<TypeValue>) -> string {
90 stringJoinTypes(values, ", ")
93function stringJoinTypes(values: Array<TypeValue>, separator: string) -> string {
94 let mut output = ""
95 let mut index = 0
96 for value in values {
97 if index > 0 {
98 output = "{output}{separator}"
99 }
100 output = "{output}{typeText(value)}"
101 index += 1
102 }
103 output
106export function stringJoin(values: Array<string>, separator: string) -> string {
107 let mut output = ""
108 let mut index = 0
109 for value in values {
110 if index > 0 {
111 output = "{output}{separator}"
112 }
113 output = "{output}{value}"
114 index += 1
115 }
116 output
119function typeArraysEqual(left: Array<TypeValue>, right: Array<TypeValue>) -> bool {
120 if left.length != right.length {
121 return false
122 }
123 let mut index = 0
124 while index < left.length {
125 match [left.get(index), right.get(index)] {
126 case [Some(a), Some(b)] => if !typeEqual(a, b) { return false }
127 case _ => return false
128 }
129 index += 1
130 }
131 true
134function stringArraysEqual(left: Array<string>, right: Array<string>) -> bool {
135 if left.length != right.length {
136 return false
137 }
138 let mut index = 0
139 while index < left.length {
140 match [left.get(index), right.get(index)] {
141 case [Some(a), Some(b)] => if a != b { return false }
142 case _ => return false
143 }
144 index += 1
145 }
146 true
149export function typeEqual(left: TypeValue, right: TypeValue) -> bool {
150 left.kind == right.kind &&
151 left.name == right.name &&
152 left.value == right.value &&
153 left.identity == right.identity &&
154 left.origin == right.origin &&
155 typeArraysEqual(left.arguments, right.arguments) &&
156 typeArraysEqual(left.members, right.members) &&
157 stringArraysEqual(left.fieldNames, right.fieldNames) &&
158 typeArraysEqual(left.fields, right.fields) &&
159 typeArraysEqual(left.parameters, right.parameters) &&
160 typeArraysEqual(left.variadic, right.variadic) &&
161 typeArraysEqual(left.result, right.result)
164export function typeContains(
165 value: TypeValue,
166 predicate: (TypeValue) -> bool,
167) -> bool {
168 if predicate(value) {
169 return true
170 }
171 for item in value.arguments {
172 if typeContains(item, predicate) { return true }
173 }
174 for item in value.members {
175 if typeContains(item, predicate) { return true }
176 }
177 for field in value.fields {
178 if typeContains(field, predicate) { return true }
179 }
180 for item in value.parameters {
181 if typeContains(item, predicate) { return true }
182 }
183 for item in value.variadic {
184 if typeContains(item, predicate) { return true }
185 }
186 for item in value.result {
187 if typeContains(item, predicate) { return true }
188 }
189 false
192function holeTypeComponent(value: TypeValue) -> bool {
193 value.kind == "unknown" || value.kind == "inference-variable"
196export function hasHole(value: TypeValue) -> bool {
197 typeContains(value, holeTypeComponent)
200function covariantArgumentsAssignable(
201 expected: Array<TypeValue>,
202 actual: Array<TypeValue>,
203) -> bool {
204 if expected.length != actual.length {
205 return false
206 }
207 let mut index = 0
208 while index < expected.length {
209 match [expected.get(index), actual.get(index)] {
210 case [Some(expectedValue), Some(actualValue)] => if !assignable(
211 expectedValue,
212 actualValue,
213 ) {
214 return false
215 }
216 case _ => return false
217 }
218 index += 1
219 }
220 true
223function functionAssignable(expected: TypeValue, actual: TypeValue) -> bool {
224 if expected.parameters.length != actual.parameters.length ||
225 expected.variadic.length != actual.variadic.length {
226 return false
227 }
228 let mut index = 0
229 while index < expected.parameters.length {
230 match [expected.parameters.get(index), actual.parameters.get(index)] {
231 case [Some(expectedParameter), Some(actualParameter)] => if !assignable(
232 actualParameter,
233 expectedParameter,
234 ) {
235 return false
236 }
237 case _ => return false
238 }
239 index += 1
240 }
241 if expected.variadic.length == 1 {
242 match [expected.variadic.get(0), actual.variadic.get(0)] {
243 case [Some(expectedVariadic), Some(actualVariadic)] => if !assignable(
244 actualVariadic,
245 expectedVariadic,
246 ) {
247 return false
248 }
249 case _ => return false
250 }
251 }
252 match [expected.result.get(0), actual.result.get(0)] {
253 case [Some(expectedResult), Some(actualResult)] => assignable(
254 expectedResult,
255 actualResult,
256 )
257 case _ => false
258 }
261export function assignable(expected: TypeValue, actual: TypeValue) -> bool {
262 if hasHole(expected) || hasHole(actual) {
263 return true
264 }
265 if actual.kind == "union" {
266 for member in actual.members {
267 if !assignable(expected, member) {
268 return false
269 }
270 }
271 return true
272 }
273 let widened = widen(actual)
274 if expected.kind == "primitive" && widened.kind == "primitive" {
275 return expected.name == widened.name
276 }
277 if typeEqual(expected, actual) || typeEqual(expected, widened) {
278 return true
279 }
280 if expected.kind == "constructor" && actual.kind == "constructor" &&
281 expected.name == actual.name &&
282 expected.arguments.length == actual.arguments.length {
283 if expected.name == "Option" || expected.name == "Result" {
284 return covariantArgumentsAssignable(expected.arguments, actual.arguments)
285 }
286 return typeArraysEqual(expected.arguments, actual.arguments)
287 }
288 if expected.kind == "function" && actual.kind == "function" {
289 return functionAssignable(expected, actual)
290 }
291 if expected.kind == "record" && actual.kind == "record" &&
292 expected.fieldNames.length == actual.fieldNames.length {
293 let mut index = 0
294 while index < expected.fieldNames.length {
295 match [expected.fieldNames.get(index), actual.fieldNames.get(index)] {
296 case [Some(expectedName), Some(actualName)] => if expectedName !=
297 actualName { return false }
298 case _ => return false
299 }
300 match [expected.fields.get(index), actual.fields.get(index)] {
301 case [Some(expectedField), Some(actualField)] => if !assignable(
302 expectedField,
303 actualField,
304 ) { return false }
305 case _ => return false
306 }
307 index += 1
308 }
309 return true
310 }
311 if expected.kind == "union" {
312 for member in expected.members {
313 if assignable(member, actual) {
314 return true
315 }
316 }
317 }
318 false
321function flattenTypeInto(
322 output: Array<PreviewTypeAtom>,
323 value: TypeValue,
324 parent: int,
325 field: string,
326 index: int,
327 edgeName: string,
328) -> () {
329 let mut target = output
330 let ordinal = target.length
331 target.push(PreviewTypeAtom {
332 parent: parent,
333 field: field,
334 index: index,
335 edgeName: edgeName,
336 kind: value.kind,
337 name: value.name,
338 value: value.value,
339 identity: value.identity,
340 origin: value.origin,
341 })
342 let mut childIndex = 0
343 for item in value.arguments {
344 flattenTypeInto(target, item, ordinal, "arguments", childIndex, "")
345 childIndex += 1
346 }
347 childIndex = 0
348 for item in value.members {
349 flattenTypeInto(target, item, ordinal, "members", childIndex, "")
350 childIndex += 1
351 }
352 childIndex = 0
353 for item in value.fields {
354 let fieldName = match value.fieldNames.get(childIndex) {
355 case Some(found) => found
356 case None => ""
357 }
358 flattenTypeInto(target, item, ordinal, "fields", childIndex, fieldName)
359 childIndex += 1
360 }
361 childIndex = 0
362 for item in value.parameters {
363 flattenTypeInto(target, item, ordinal, "parameters", childIndex, "")
364 childIndex += 1
365 }
366 childIndex = 0
367 for item in value.variadic {
368 flattenTypeInto(target, item, ordinal, "variadic", childIndex, "")
369 childIndex += 1
370 }
371 childIndex = 0
372 for item in value.result {
373 flattenTypeInto(target, item, ordinal, "result", childIndex, "")
374 childIndex += 1
375 }
376 ()
379export function flattenType(value: TypeValue) -> Array<PreviewTypeAtom> {
380 let mut output: Array<PreviewTypeAtom> = []
381 flattenTypeInto(output, value, -1, "root", 0, "")
382 output
385export function flattenedHasHole(value: Array<PreviewTypeAtom>) -> bool {
386 for atom in value {
387 if atom.kind == "unknown" || atom.kind == "inference-variable" {
388 return true
389 }
390 }
391 false
394export function resolvedFlatType(
395 value: Array<PreviewTypeAtom>,
396 solutions: Map<string, TypeValue>,
397) -> Array<PreviewTypeAtom> {
398 let mut output: Array<PreviewTypeAtom> = []
399 let mut remappedIndexes: Array<int> = []
400 for atom in value {
401 let remappedParent = if atom.parent < 0 {
402 -1
403 } else {
404 match remappedIndexes.get(atom.parent) {
405 case Some(found) => found
406 case None => -1
407 }
408 }
409 let replacement = if atom.kind == "inference-variable" {
410 solutions.get("inference:{atom.value}")
411 } else {
412 None
413 }
414 match replacement {
415 case Some(solution) => {
416 let flattened = flattenType(solution)
417 let replacementRoot = output.length
418 for replacementAtom in flattened {
419 if replacementAtom.parent < 0 {
420 output.push(PreviewTypeAtom {
421 ...replacementAtom,
422 parent: remappedParent,
423 field: atom.field,
424 index: atom.index,
425 edgeName: atom.edgeName,
426 })
427 } else {
428 output.push(PreviewTypeAtom {
429 ...replacementAtom,
430 parent: replacementRoot + replacementAtom.parent,
431 })
432 }
433 }
434 remappedIndexes.push(replacementRoot)
435 }
436 case None => {
437 remappedIndexes.push(output.length)
438 output.push(PreviewTypeAtom { ...atom, parent: remappedParent })
439 }
440 }
441 }
442 output
445export function widen(value: TypeValue) -> TypeValue {
446 if value.kind != "literal" {
447 return value
448 }
449 match value.name {
450 case "int" => primitive("int")
451 case "float" => primitive("float")
452 case "string" => primitive("string")
453 case "bool" => primitive("bool")
454 case _ => value
455 }
458function compareText(left: string, right: string) -> int {
459 if left < right {
460 -1
461 } else if right < left {
462 1
463 } else {
464 0
465 }
468function compareInt(left: int, right: int) -> int {
469 if left < right {
470 -1
471 } else if right < left {
472 1
473 } else {
474 0
475 }
478function typeKindOrder(kind: string) -> int {
479 match kind {
480 case "primitive" => 0
481 case "literal" => 1
482 case "union" => 2
483 case "record" => 3
484 case "constructor" => 4
485 case "function" => 5
486 case "foreign" => 6
487 case "rigid" => 7
488 case "template" => 8
489 case "file" => 9
490 case "json-value" => 10
491 case "bytes" => 11
492 case "byte-buffer" => 12
493 case "path" => 13
494 case "regex" => 14
495 case "match" => 15
496 case "toml-value" => 16
497 case "url" => 17
498 case "date" => 18
499 case "big-int" => 19
500 case "decimal" => 20
501 case "rounding-mode" => 21
502 case "enum" => 22
503 case "nominal-record" => 23
504 case "newtype" => 24
505 case "unknown" => 25
506 case "inference-variable" => 26
507 case _ => 27
508 }
511function primitiveOrder(name: string) -> int {
512 match name {
513 case "int" => 0
514 case "float" => 1
515 case "string" => 2
516 case "bool" => 3
517 case "unit" => 4
518 case _ => 5
519 }
522function literalOrder(name: string) -> int {
523 match name {
524 case "string" => 0
525 case "int" => 1
526 case "float" => 2
527 case "bool" => 3
528 case "null" => 4
529 case _ => 5
530 }
533function constructorOrder(name: string) -> int {
534 match name {
535 case "Array" => 0
536 case "Map" => 1
537 case "Set" => 2
538 case "Option" => 3
539 case "Result" => 4
540 case "Range" => 5
541 case _ => 6
542 }
545function parsedTypeInt(value: string) -> int {
546 match toInt(value) {
547 case Some(found) => found
548 case None => 0
549 }
552function compareTypeLists(
553 left: Array<TypeValue>,
554 right: Array<TypeValue>,
555) -> int {
556 let mut index = 0
557 while index < left.length && index < right.length {
558 let leftValue = match left.get(index) {
559 case Some(found) => found
560 case None => emptyType("unknown")
561 }
562 let rightValue = match right.get(index) {
563 case Some(found) => found
564 case None => emptyType("unknown")
565 }
566 let compared = compareTypeValues(leftValue, rightValue)
567 if compared != 0 {
568 return compared
569 }
570 index += 1
571 }
572 compareInt(left.length, right.length)
575function compareRecordTypes(left: TypeValue, right: TypeValue) -> int {
576 let mut index = 0
577 while index < left.fieldNames.length && index < right.fieldNames.length {
578 let leftName = match left.fieldNames.get(index) {
579 case Some(found) => found
580 case None => ""
581 }
582 let rightName = match right.fieldNames.get(index) {
583 case Some(found) => found
584 case None => ""
585 }
586 let nameCompared = compareText(leftName, rightName)
587 if nameCompared != 0 {
588 return nameCompared
589 }
590 let leftType = match left.fields.get(index) {
591 case Some(found) => found
592 case None => emptyType("unknown")
593 }
594 let rightType = match right.fields.get(index) {
595 case Some(found) => found
596 case None => emptyType("unknown")
597 }
598 let typeCompared = compareTypeValues(leftType, rightType)
599 if typeCompared != 0 {
600 return typeCompared
601 }
602 index += 1
603 }
604 compareInt(left.fieldNames.length, right.fieldNames.length)
607function compareTypeValues(left: TypeValue, right: TypeValue) -> int {
608 let kindCompared = compareInt(
609 typeKindOrder(left.kind),
610 typeKindOrder(right.kind),
611 )
612 if kindCompared != 0 {
613 return kindCompared
614 }
615 match left.kind {
616 case "primitive" => compareInt(
617 primitiveOrder(left.name),
618 primitiveOrder(right.name),
619 )
620 case "literal" => {
621 let literalCompared = compareInt(
622 literalOrder(left.name),
623 literalOrder(right.name),
624 )
625 if literalCompared != 0 {
626 return literalCompared
627 }
628 if left.name == "int" {
629 compareInt(parsedTypeInt(left.value), parsedTypeInt(right.value))
630 } else {
631 compareText(left.value, right.value)
632 }
633 }
634 case "union" => compareTypeLists(left.members, right.members)
635 case "record" => compareRecordTypes(left, right)
636 case "constructor" => {
637 let constructorCompared = compareInt(
638 constructorOrder(left.name),
639 constructorOrder(right.name),
640 )
641 if constructorCompared != 0 {
642 return constructorCompared
643 }
644 compareTypeLists(left.arguments, right.arguments)
645 }
646 case "function" => {
647 let parametersCompared = compareTypeLists(
648 left.parameters,
649 right.parameters,
650 )
651 if parametersCompared != 0 {
652 return parametersCompared
653 }
654 let variadicCompared = compareTypeLists(left.variadic, right.variadic)
655 if variadicCompared != 0 {
656 return variadicCompared
657 }
658 compareTypeLists(left.result, right.result)
659 }
660 case "foreign" | "enum" | "nominal-record" | "newtype" => {
661 let identityCompared = compareText(left.identity, right.identity)
662 if identityCompared != 0 {
663 return identityCompared
664 }
665 let originCompared = compareText(left.origin, right.origin)
666 if originCompared != 0 {
667 return originCompared
668 }
669 compareTypeLists(left.arguments, right.arguments)
670 }
671 case "rigid" => {
672 let nameCompared = compareText(left.name, right.name)
673 if nameCompared != 0 {
674 return nameCompared
675 }
676 compareText(left.origin, right.origin)
677 }
678 case "inference-variable" => compareInt(
679 parsedTypeInt(left.value),
680 parsedTypeInt(right.value),
681 )
682 case _ => 0
683 }
686export function unionType(values: Array<TypeValue>) -> TypeValue {
687 let mut flat: Array<TypeValue> = []
688 for value in values {
689 if value.kind == "union" {
690 for member in value.members {
691 flat.push(member)
692 }
693 } else {
694 flat.push(value)
695 }
696 }
697 let mut ordered: Array<TypeValue> = []
698 for value in flat {
699 let mut duplicate = false
700 for existing in ordered {
701 if typeEqual(existing, value) {
702 duplicate = true
703 }
704 }
705 if !duplicate {
706 let mut inserted = false
707 let mut index = 0
708 while index < ordered.length {
709 let existing = match ordered.get(index) {
710 case Some(item) => item
711 case None => value
712 }
713 if compareTypeValues(value, existing) < 0 {
714 ordered.insert(index, value)
715 inserted = true
716 break
717 }
718 index += 1
719 }
720 if !inserted {
721 ordered.push(value)
722 }
723 }
724 }
725 match ordered.get(0) {
726 case Some(first) => if ordered.length == 1 {
727 first
728 } else {
729 TypeValue {
730 kind: "union",
731 name: "",
732 value: "",
733 identity: "",
734 origin: "",
735 arguments: [],
736 members: ordered,
737 fieldNames: [],
738 fields: [],
739 parameters: [],
740 variadic: [],
741 result: [],
742 }
743 }
744 case None => emptyType("unknown")
745 }
748export function widenedUnion(values: Array<TypeValue>) -> TypeValue {
749 let mut widened: Array<TypeValue> = []
750 for value in values {
751 widened.push(widen(value))
752 }
753 unionType(widened)
756function projectionOrigin(origin: string) -> bool {
757 origin.startsWith("projection:") ||
758 origin == "builtin::record-pattern" ||
759 origin == "builtin::constructor-pattern" ||
760 origin == "builtin::list-pattern" ||
761 origin == "builtin::iterable" ||
762 origin == "builtin::array-spread"
765function projectionTypeComponent(value: TypeValue) -> bool {
766 value.kind == "rigid" && projectionOrigin(value.origin)
769export function projectionType(value: TypeValue) -> bool {
770 typeContains(value, projectionTypeComponent)
773export function transformTypeComponents(
774 value: TypeValue,
775 replacement: (TypeValue) -> Option<TypeValue>,
776) -> TypeValue {
777 match replacement(value) {
778 case Some(transformed) => return transformed
779 case None => ()
780 }
781 let mut transformed: Array<TypeValue> = []
782 for item in value.arguments {
783 transformed.push(transformTypeComponents(item, replacement))
784 }
785 let transformedArguments = transformed
786 transformed = []
787 for item in value.members {
788 transformed.push(transformTypeComponents(item, replacement))
789 }
790 let transformedMembers = transformed
791 transformed = []
792 for item in value.fields {
793 transformed.push(transformTypeComponents(item, replacement))
794 }
795 let transformedFields = transformed
796 transformed = []
797 for item in value.parameters {
798 transformed.push(transformTypeComponents(item, replacement))
799 }
800 let transformedParameters = transformed
801 transformed = []
802 for item in value.variadic {
803 transformed.push(transformTypeComponents(item, replacement))
804 }
805 let transformedVariadic = transformed
806 transformed = []
807 for item in value.result {
808 transformed.push(transformTypeComponents(item, replacement))
809 }
810 TypeValue {
811 kind: value.kind,
812 name: value.name,
813 value: value.value,
814 identity: value.identity,
815 origin: value.origin,
816 arguments: transformedArguments,
817 members: transformedMembers,
818 fieldNames: value.fieldNames,
819 fields: transformedFields,
820 parameters: transformedParameters,
821 variadic: transformedVariadic,
822 result: transformed,
823 }
826function stripProjectionTypeComponent(value: TypeValue) -> Option<TypeValue> {
827 if value.kind == "rigid" && projectionOrigin(value.origin) {
828 Some(emptyType("unknown"))
829 } else {
830 None
831 }
834export function stripProjectionTypes(value: TypeValue) -> TypeValue {
835 transformTypeComponents(value, stripProjectionTypeComponent)
838function partialHoleKey(value: TypeValue) -> string {
839 if value.kind == "inference-variable" {
840 return "inference:{value.value}"
841 }
842 if value.kind == "rigid" && value.origin.startsWith("builtin::") &&
843 !projectionOrigin(value.origin) {
844 return "rigid:{value.origin}:{value.name}"
845 }
846 ""
849function trueUnknownTypeComponent(value: TypeValue) -> bool {
850 value.kind == "unknown"
853function hasTrueUnknown(value: TypeValue) -> bool {
854 typeContains(value, trueUnknownTypeComponent)
857function partialHoleTypeComponent(value: TypeValue) -> bool {
858 partialHoleKey(value) != ""
861export function hasPartialHole(value: TypeValue) -> bool {
862 typeContains(value, partialHoleTypeComponent)
865function partialBindingIndex(bindings: Array<TypeBinding>, key: string) -> int {
866 let mut index = 0
867 for binding in bindings {
868 if binding.name == key {
869 return index
870 }
871 index += 1
872 }
873 -1
876function bindPartialType(
877 bindings: Array<TypeBinding>,
878 key: string,
879 candidate: TypeValue,
880) -> () {
881 let candidateKey = partialHoleKey(candidate)
882 if candidateKey == key {
883 return ()
884 }
885 let mut target = bindings
886 let index = partialBindingIndex(target, key)
887 if index >= 0 {
888 match target.get(index) {
889 case Some(existing) => unifyPartialTypes(
890 existing.typeValue,
891 candidate,
892 target,
893 )
894 case None => ()
895 }
896 } else {
897 target.push(TypeBinding { name: key, typeValue: candidate })
898 }
899 ()
902function unifyPartialLists(
903 left: Array<TypeValue>,
904 right: Array<TypeValue>,
905 bindings: Array<TypeBinding>,
906) -> () {
907 if left.length != right.length {
908 return ()
909 }
910 let mut index = 0
911 while index < left.length {
912 match [left.get(index), right.get(index)] {
913 case [Some(a), Some(b)] => unifyPartialTypes(a, b, bindings)
914 case _ => ()
915 }
916 index += 1
917 }
918 ()
921function unifyPartialTypes(
922 left: TypeValue,
923 right: TypeValue,
924 bindings: Array<TypeBinding>,
925) -> () {
926 let leftKey = partialHoleKey(left)
927 let rightKey = partialHoleKey(right)
928 if leftKey != "" && rightKey != "" {
929 if leftKey == rightKey {
930 return ()
931 }
932 if leftKey < rightKey {
933 let mut target = bindings
934 if partialBindingIndex(target, rightKey) < 0 {
935 target.push(TypeBinding { name: rightKey, typeValue: left })
936 }
937 } else {
938 let mut target = bindings
939 if partialBindingIndex(target, leftKey) < 0 {
940 target.push(TypeBinding { name: leftKey, typeValue: right })
941 }
942 }
943 return ()
944 }
945 if leftKey != "" {
946 bindPartialType(bindings, leftKey, right)
947 return ()
948 }
949 if rightKey != "" {
950 bindPartialType(bindings, rightKey, left)
951 return ()
952 }
953 if left.kind != right.kind || left.name != right.name ||
954 left.identity != right.identity {
955 return ()
956 }
957 unifyPartialLists(left.arguments, right.arguments, bindings)
958 unifyPartialLists(left.members, right.members, bindings)
959 unifyPartialLists(left.fields, right.fields, bindings)
960 unifyPartialLists(left.parameters, right.parameters, bindings)
961 unifyPartialLists(left.variadic, right.variadic, bindings)
962 unifyPartialLists(left.result, right.result, bindings)
963 ()
966function partialTypeComponentReplacement(
967 value: TypeValue,
968 bindings: Array<TypeBinding>,
969) -> Option<TypeValue> {
970 let key = partialHoleKey(value)
971 if key == "" {
972 return None
973 }
974 let index = partialBindingIndex(bindings, key)
975 if index >= 0 {
976 return match bindings.get(index) {
977 case Some(found) => Some(substitutePartialTypes(found.typeValue, bindings))
978 case None => Some(value)
979 }
980 }
981 Some(value)
984function substitutePartialTypes(
985 value: TypeValue,
986 bindings: Array<TypeBinding>,
987) -> TypeValue {
988 transformTypeComponents(
989 value,
990 (component: TypeValue) => partialTypeComponentReplacement(
991 component,
992 bindings,
993 ),
994 )
997function flattenJoinTypes(values: Array<TypeValue>) -> Array<TypeValue> {
998 let mut flat: Array<TypeValue> = []
999 for value in values {
1000 if value.kind == "union" {
1001 for member in value.members {
1002 flat.push(member)
1004 } else {
1005 flat.push(value)
1008 flat
1011function retainInferenceSolutions(
1012 state: CheckerState,
1013 bindings: Array<TypeBinding>,
1014) -> () {
1015 let mut solutions = state.inferenceSolutions
1016 for binding in bindings {
1017 if binding.name.startsWith("inference:") {
1018 let completed = substitutePartialTypes(binding.typeValue, bindings)
1019 if !hasPartialHole(completed) {
1020 solutions.insert(binding.name, completed)
1024 ()
1027export function joinedTypes(
1028 state: CheckerState,
1029 values: Array<TypeValue>,
1030 widenResult: bool,
1031) -> TypeValue {
1032 let flat = flattenJoinTypes(values)
1033 for value in flat {
1034 if hasTrueUnknown(value) {
1035 return emptyType("unknown")
1038 let mut bindings: Array<TypeBinding> = []
1039 let mut leftIndex = 0
1040 while leftIndex < flat.length {
1041 let mut rightIndex = 0
1042 while rightIndex < flat.length {
1043 if leftIndex != rightIndex {
1044 match [flat.get(leftIndex), flat.get(rightIndex)] {
1045 case [Some(left), Some(right)] =>
1046 unifyPartialTypes(left, right, bindings)
1047 case _ => ()
1050 rightIndex += 1
1052 leftIndex += 1
1054 let mut solved: Array<TypeValue> = []
1055 for value in flat {
1056 let completed = substitutePartialTypes(value, bindings)
1057 if hasPartialHole(completed) {
1058 return emptyType("unknown")
1060 solved.push(completed)
1062 retainInferenceSolutions(state, bindings)
1063 if widenResult { widenedUnion(solved) } else { unionType(solved) }
1066export function solvePartialTypeAgainst(
1067 state: CheckerState,
1068 partial: TypeValue,
1069 concrete: TypeValue,
1070) -> TypeValue {
1071 let mut bindings: Array<TypeBinding> = []
1072 unifyPartialTypes(partial, concrete, bindings)
1073 let completed = substitutePartialTypes(partial, bindings)
1074 if hasPartialHole(completed) {
1075 partial
1076 } else {
1077 retainInferenceSolutions(state, bindings)
1078 completed

SHA256 dee4445a70ebd0931f444cb0c13614640f04321a9dd5c93a27a503d31facab03