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

Topaz · 49379 B · Original file

1// Pattern typing, bindings, narrowing, coverage, and exhaustiveness.
2// Nominal catalogs enter as checker state; match control and diagnostics consume
3// the coverage result without duplicating pattern traversal.
5import types { SyntaxNode }
6import checker_types { TypeValue }
7import unicode { didYouMean }
8import resolver.modules_tables { collectPatternBindings }
9import checker.model {
10 CheckerService,
11 CheckerState,
12 CoverageGap,
13 EnumDefinition,
14 EnumVariant,
15 PatternCoverage,
16 RecordDefinition,
17 constructor,
18 emptyType,
19 inference,
20 literal,
21 nominal,
22 primitive,
23 rigid,
24 serviceInferExpression,
26import checker.type_algebra {
27 assignable,
28 hasHole,
29 stringJoin,
30 typeText,
31 unionType,
33import checker.syntax_index {
34 addDiagnostic,
35 addNode,
36 bindSymbol,
37 child,
38 children,
39 containsString,
40 moduleIdentityAt,
41 newtypeDefinitionIndex,
42 recordDefinitionIndex,
43 symbolTypeAt,
44 text,
46import checker.nominals {
47 formType,
48 substitute,
49 targetType,
51import checker.protocols_methods {
52 enumDefinitionForType,
53 newtypeDefinitionForType,
55import checker.members_capabilities { typeArgument }
57export function patternBindingNodes(pattern: SyntaxNode) -> Array<SyntaxNode> {
58 let mut output: Array<SyntaxNode> = []
59 if pattern.kind == "pattern/binding" || pattern.kind == "pattern/typed" {
60 match child(pattern, "name") {
61 case Some(found) => output.push(found)
62 case None => ()
63 }
64 return output
65 }
66 for nested in pattern.children {
67 for found in patternBindingNodes(nested) {
68 output.push(found)
69 }
70 }
71 output
74function patternAlternativeBindingNodes(
75 state: CheckerState,
76 moduleIndex: int,
77 source: Bytes,
78 pattern: SyntaxNode,
79 expected: TypeValue,
80) -> Array<SyntaxNode> {
81 if pattern.kind == "pattern/binding" {
82 let nameNode = match child(pattern, "name") {
83 case Some(found) => found
84 case None => pattern
85 }
86 let name = text(source, nameNode)
87 match enumDefinitionForType(state, moduleIndex, expected) {
88 case Some(definition) => for variant in definition.variants {
89 if variant.name == name && variant.payloads.length == 0 {
90 let empty: Array<SyntaxNode> = []
91 return empty
92 }
93 }
94 case None => ()
95 }
96 }
97 patternBindingNodes(pattern)
100function patternAlternativeHasBinding(
101 state: CheckerState,
102 moduleIndex: int,
103 source: Bytes,
104 pattern: SyntaxNode,
105 expected: TypeValue,
106 name: string,
107) -> bool {
108 for found in patternAlternativeBindingNodes(
109 state,
110 moduleIndex,
111 source,
112 pattern,
113 expected,
114 ) {
115 if text(source, found) == name {
116 return true
117 }
118 }
119 false
122// Let bindings use the same type-aware coverage as match patterns. In
123// particular, record fields are wrappers and pure-rest arrays and single
124// constructor types can be irrefutable despite their surface syntax.
125export function patternIsIrrefutable(
126 state: CheckerState,
127 moduleIndex: int,
128 expected: TypeValue,
129 coverage: PatternCoverage,
130) -> bool {
131 hasHole(expected) || coverageCoversAt(state, moduleIndex, coverage, expected, "")
134export function emptyPatternCoverage() -> PatternCoverage {
135 PatternCoverage { irrefutable: false, keys: [] }
138function irrefutablePatternCoverage() -> PatternCoverage {
139 PatternCoverage { irrefutable: true, keys: [] }
142function coverageHasKey(coverage: PatternCoverage, key: string) -> bool {
143 containsString(coverage.keys, key)
146function coverageWithKey(key: string) -> PatternCoverage {
147 PatternCoverage { irrefutable: false, keys: [key] }
150export function mergePatternCoverage(
151 left: PatternCoverage,
152 right: PatternCoverage,
153) -> PatternCoverage {
154 let mut keys = left.keys
155 for key in right.keys {
156 if !containsString(keys, key) {
157 keys.push(key)
158 }
159 }
160 PatternCoverage {
161 irrefutable: left.irrefutable || right.irrefutable,
162 keys: keys,
163 }
166function coveragePath(prefix: string, part: string) -> string {
167 if prefix == "" { part } else { "{prefix}/{part}" }
170function prefixPatternCoverage(
171 prefix: string,
172 nested: PatternCoverage,
173) -> PatternCoverage {
174 let mut keys: Array<string> = []
175 if nested.irrefutable {
176 keys.push("{prefix}/*")
177 }
178 for key in nested.keys {
179 keys.push("{prefix}/{key}")
180 }
181 PatternCoverage { irrefutable: false, keys: keys }
184function patternTypeOpaque(value: TypeValue) -> bool {
185 value.kind == "unknown" || value.kind == "inference-variable" ||
186 value.kind == "rigid" || value.kind == "foreign"
189function patternTypeDecidable(value: TypeValue) -> bool {
190 if value.kind == "primitive" || value.kind == "literal" {
191 return true
192 }
193 if value.kind == "union" {
194 for member in value.members {
195 if !patternTypeDecidable(member) { return false }
196 }
197 return true
198 }
199 if value.kind == "constructor" {
200 for argument in value.arguments {
201 if !patternTypeDecidable(argument) { return false }
202 }
203 return true
204 }
205 if value.kind == "record" {
206 for field in value.fields {
207 if !patternTypeDecidable(field) { return false }
208 }
209 return true
210 }
211 false
214function patternTypesOverlap(left: TypeValue, right: TypeValue) -> bool {
215 if hasHole(left) || hasHole(right) ||
216 assignable(left, right) || assignable(right, left) {
217 return true
218 }
219 if left.kind == "union" {
220 for member in left.members {
221 if patternTypesOverlap(member, right) { return true }
222 }
223 return false
224 }
225 if right.kind == "union" {
226 for member in right.members {
227 if patternTypesOverlap(left, member) { return true }
228 }
229 return false
230 }
231 if left.kind == "constructor" && right.kind == "constructor" {
232 return left.name == right.name
233 }
234 if left.kind == "enum" && right.kind == "enum" ||
235 left.kind == "nominal-record" && right.kind == "nominal-record" ||
236 left.kind == "newtype" && right.kind == "newtype" {
237 return left.kind == right.kind && left.identity == right.identity
238 }
239 if left.kind == "record" && right.kind == "record" &&
240 left.fieldNames.length == right.fieldNames.length {
241 let mut index = 0
242 while index < left.fieldNames.length {
243 let leftName = match left.fieldNames.get(index) {
244 case Some(found) => found
245 case None => return false
246 }
247 let rightName = match right.fieldNames.get(index) {
248 case Some(found) => found
249 case None => return false
250 }
251 let leftType = match left.fields.get(index) {
252 case Some(found) => found
253 case None => return false
254 }
255 let rightType = match right.fields.get(index) {
256 case Some(found) => found
257 case None => return false
258 }
259 if leftName != rightName ||
260 !patternTypesOverlap(leftType, rightType) {
261 return false
262 }
263 index += 1
264 }
265 return true
266 }
267 false
270function narrowPatternType(
271 scrutinee: TypeValue,
272 formed: TypeValue,
273) -> TypeValue {
274 if hasHole(scrutinee) { return formed }
275 if assignable(formed, scrutinee) { return scrutinee }
276 if assignable(scrutinee, formed) { return formed }
277 if scrutinee.kind == "union" {
278 let mut kept: Array<TypeValue> = []
279 for member in scrutinee.members {
280 if patternTypesOverlap(member, formed) {
281 kept.push(narrowPatternType(member, formed))
282 }
283 }
284 if kept.length > 0 { return unionType(kept) }
285 }
286 formed
289function patternNameSuggestion(
290 target: string,
291 candidates: Array<string>,
292) -> string {
293 didYouMean(target, candidates)
296function recordPatternHeadSuggestion(
297 state: CheckerState,
298 moduleIndex: int,
299 target: string,
300) -> string {
301 let mut candidates: Array<string> = []
302 for definition in state.records {
303 if definition.moduleIndex == moduleIndex {
304 candidates.push(definition.name)
305 }
306 }
307 patternNameSuggestion(target, candidates)
310function recordFieldSuggestion(
311 definition: RecordDefinition,
312 target: string,
313) -> string {
314 let mut candidates: Array<string> = []
315 for field in definition.fields { candidates.push(field.name) }
316 patternNameSuggestion(target, candidates)
319function structuralFieldSuggestion(
320 value: TypeValue,
321 target: string,
322) -> string {
323 patternNameSuggestion(target, value.fieldNames)
326function enumVariantSuggestion(
327 definition: EnumDefinition,
328 target: string,
329) -> string {
330 let mut candidates: Array<string> = []
331 for variant in definition.variants { candidates.push(variant.name) }
332 patternNameSuggestion(target, candidates)
335function nominalPatternIdentityFor(identity: string, origin: string) -> string {
336 if origin == "" { identity }
337 else { "{origin}::{identity}" }
340function nominalPatternIdentity(value: TypeValue) -> string {
341 nominalPatternIdentityFor(value.identity, value.origin)
344function coverageCoversAt(
345 state: CheckerState,
346 moduleIndex: int,
347 coverage: PatternCoverage,
348 value: TypeValue,
349 prefix: string,
350) -> bool {
351 if prefix == "" && coverage.irrefutable {
352 return true
353 }
354 if prefix != "" && coverageHasKey(coverage, "{prefix}/*") {
355 return true
356 }
357 if value.kind == "primitive" && value.name == "bool" {
358 return coverageHasKey(
359 coverage,
360 coveragePath(prefix, "literal:true"),
361 ) && coverageHasKey(
362 coverage,
363 coveragePath(prefix, "literal:false"),
364 )
365 }
366 if value.kind == "literal" {
367 return coverageHasKey(
368 coverage,
369 coveragePath(prefix, "literal:{typeText(value)}"),
370 )
371 }
372 if value.kind == "union" {
373 if value.members.length == 0 { return false }
374 for member in value.members {
375 if !coverageCoversAt(state, moduleIndex, coverage, member, prefix) {
376 return false
377 }
378 }
379 return true
380 }
381 if value.kind == "constructor" && value.name == "Option" {
382 return coverageCoversAt(
383 state,
384 moduleIndex,
385 coverage,
386 typeArgument(value, 0),
387 coveragePath(prefix, "constructor:Some"),
388 ) && coverageHasKey(
389 coverage,
390 coveragePath(prefix, "constructor:None/*"),
391 )
392 }
393 if value.kind == "constructor" && value.name == "Result" {
394 return coverageCoversAt(
395 state,
396 moduleIndex,
397 coverage,
398 typeArgument(value, 0),
399 coveragePath(prefix, "constructor:Ok"),
400 ) && coverageCoversAt(
401 state,
402 moduleIndex,
403 coverage,
404 typeArgument(value, 1),
405 coveragePath(prefix, "constructor:Err"),
406 )
407 }
408 if value.kind == "enum" {
409 return match enumDefinitionForType(state, moduleIndex, value) {
410 case Some(definition) => {
411 for variant in definition.variants {
412 let variantPrefix = coveragePath(
413 prefix,
414 "constructor:{variant.name}",
415 )
416 if variant.payloads.length == 0 {
417 if !coverageHasKey(coverage, "{variantPrefix}/*") {
418 return false
419 }
420 } else if variant.payloads.length == 1 {
421 let payload = match variant.payloads.get(0) {
422 case Some(found) => substitute(
423 found,
424 definition.typeParameters,
425 value.arguments,
426 )
427 case None => emptyType("unknown")
428 }
429 if !coverageCoversAt(
430 state,
431 definition.moduleIndex,
432 coverage,
433 payload,
434 variantPrefix,
435 ) {
436 return false
437 }
438 } else if !coverageHasKey(coverage, "{variantPrefix}/*") {
439 return false
440 }
441 }
442 true
443 }
444 case None => false
445 }
446 }
447 if value.kind == "nominal-record" {
448 return coverageHasKey(
449 coverage,
450 coveragePath(prefix, "record:{nominalPatternIdentity(value)}"),
451 )
452 }
453 false
456export function coverageGap(
457 state: CheckerState,
458 moduleIndex: int,
459 coverage: PatternCoverage,
460 value: TypeValue,
461) -> CoverageGap {
462 if coverage.irrefutable {
463 return CoverageGap { decidable: true, missing: [] }
464 }
465 let mut missing: Array<string> = []
466 if value.kind == "primitive" && value.name == "bool" {
467 for spelling in ["true", "false"] {
468 if !coverageHasKey(coverage, "literal:{spelling}") {
469 missing.push("`{spelling}`")
470 }
471 }
472 return CoverageGap { decidable: true, missing: missing }
473 }
474 if value.kind == "newtype" {
475 missing.push("`{typeText(value)}(_)`")
476 return CoverageGap { decidable: true, missing: missing }
477 }
478 if value.kind == "union" {
479 let mut decidable = true
480 for member in value.members {
481 if member.kind == "literal" {
482 if !coverageHasKey(coverage, "literal:{typeText(member)}") {
483 missing.push("`{typeText(member)}`")
484 }
485 } else if member.kind == "nominal-record" {
486 if !coverageHasKey(
487 coverage,
488 "record:{nominalPatternIdentity(member)}",
489 ) {
490 missing.push("`{typeText(member)} \{ … \}`")
491 }
492 } else {
493 decidable = false
494 }
495 }
496 return CoverageGap {
497 decidable: decidable,
498 missing: if decidable { missing } else { [] },
499 }
500 }
501 if value.kind == "constructor" && value.name == "Option" {
502 if !coverageCoversAt(
503 state,
504 moduleIndex,
505 coverage,
506 typeArgument(value, 0),
507 "constructor:Some",
508 ) {
509 missing.push("`Some`")
510 }
511 if !coverageHasKey(coverage, "constructor:None/*") {
512 missing.push("`None`")
513 }
514 return CoverageGap { decidable: true, missing: missing }
515 }
516 if value.kind == "constructor" && value.name == "Result" {
517 if !coverageCoversAt(
518 state,
519 moduleIndex,
520 coverage,
521 typeArgument(value, 0),
522 "constructor:Ok",
523 ) {
524 missing.push("`Ok`")
525 }
526 if !coverageCoversAt(
527 state,
528 moduleIndex,
529 coverage,
530 typeArgument(value, 1),
531 "constructor:Err",
532 ) {
533 missing.push("`Err`")
534 }
535 return CoverageGap { decidable: true, missing: missing }
536 }
537 if value.kind == "enum" {
538 return match enumDefinitionForType(state, moduleIndex, value) {
539 case Some(definition) => {
540 for variant in definition.variants {
541 let prefix = "constructor:{variant.name}"
542 let mut covered = false
543 if variant.payloads.length == 0 || variant.payloads.length > 1 {
544 covered = coverageHasKey(coverage, "{prefix}/*")
545 } else {
546 let payload = match variant.payloads.get(0) {
547 case Some(found) => substitute(
548 found,
549 definition.typeParameters,
550 value.arguments,
551 )
552 case None => emptyType("unknown")
553 }
554 covered = coverageCoversAt(
555 state,
556 definition.moduleIndex,
557 coverage,
558 payload,
559 prefix,
560 )
561 }
562 if !covered {
563 missing.push("`{typeText(value)}.{variant.name}`")
564 }
565 }
566 CoverageGap { decidable: true, missing: missing }
567 }
568 case None => CoverageGap { decidable: false, missing: [] }
569 }
570 }
571 if value.kind == "nominal-record" {
572 if !coverageHasKey(
573 coverage,
574 "record:{nominalPatternIdentity(value)}",
575 ) {
576 missing.push("`{typeText(value)} \{ … \}`")
577 }
578 return CoverageGap { decidable: true, missing: missing }
579 }
580 CoverageGap { decidable: false, missing: [] }
583function checkLiteralPattern(
584 service: CheckerService,
585 state: CheckerState,
586 moduleIndex: int,
587 source: Bytes,
588 pattern: SyntaxNode,
589 expected: TypeValue,
590) -> PatternCoverage {
591 let mut coverage = emptyPatternCoverage()
592 for fieldName in ["value", "low", "high"] {
593 match child(pattern, fieldName) {
594 case Some(value) => {
595 let inferred = serviceInferExpression(service, state, moduleIndex, source, value)
596 if pattern.kind == "pattern/literal" && fieldName == "value" {
597 coverage = coverageWithKey("literal:{typeText(inferred)}")
598 if patternTypeDecidable(expected) && !hasHole(inferred) &&
599 !patternTypesOverlap(inferred, expected) {
600 addDiagnostic(
601 state,
602 moduleIndex,
603 "TPZ5001",
604 "this pattern can never match `{typeText(expected)}`",
605 "",
606 value,
607 )
608 }
609 }
610 }
611 case None => ()
612 }
613 }
614 addNode(state, moduleIndex, "pattern", pattern, expected)
615 coverage
618export function bindNamedPatternType(
619 state: CheckerState,
620 moduleIndex: int,
621 source: Bytes,
622 pattern: SyntaxNode,
623 expected: TypeValue,
624 typeValue: TypeValue,
625 mutable: bool,
626) -> () {
627 let nameNode = match child(pattern, "name") {
628 case Some(found) => found
629 case None => pattern
630 }
631 let name = text(source, nameNode)
632 if pattern.kind == "pattern/binding" {
633 match enumDefinitionForType(state, moduleIndex, expected) {
634 case Some(definition) => for variant in definition.variants {
635 if variant.name == name && variant.payloads.length == 0 {
636 addNode(state, moduleIndex, "pattern", pattern, expected)
637 return ()
638 }
639 }
640 case None => ()
641 }
642 }
643 bindSymbol(
644 state,
645 moduleIndex,
646 nameNode.lo,
647 nameNode.hi,
648 name,
649 typeValue,
650 mutable,
651 )
652 addNode(
653 state,
654 moduleIndex,
655 "pattern",
656 pattern,
657 if pattern.kind == "pattern/typed" { expected } else { typeValue },
658 )
659 addNode(state, moduleIndex, "binding", nameNode, typeValue)
662function bindNamedPattern(
663 service: CheckerService,
664 state: CheckerState,
665 moduleIndex: int,
666 source: Bytes,
667 pattern: SyntaxNode,
668 expected: TypeValue,
669 mutable: bool,
670) -> PatternCoverage {
671 let typeValue = if pattern.kind == "pattern/typed" {
672 match child(pattern, "type") {
673 case Some(found) => formType(
674 service,
675 state,
676 moduleIndex,
677 source,
678 found,
679 [],
680 "pattern",
681 )
682 case None => expected
683 }
684 } else {
685 expected
686 }
687 let mut coverage = irrefutablePatternCoverage()
688 let mut bound = typeValue
689 if pattern.kind == "pattern/typed" {
690 if !hasHole(expected) && !hasHole(typeValue) &&
691 assignable(typeValue, expected) {
692 coverage = irrefutablePatternCoverage()
693 } else {
694 coverage = emptyPatternCoverage()
695 if patternTypeDecidable(expected) && patternTypeDecidable(typeValue) &&
696 !patternTypesOverlap(expected, typeValue) {
697 addDiagnostic(
698 state,
699 moduleIndex,
700 "TPZ5001",
701 "this pattern can never match `{typeText(expected)}`",
702 "",
703 pattern,
704 )
705 }
706 }
707 bound = narrowPatternType(expected, typeValue)
708 }
709 bindNamedPatternType(
710 state,
711 moduleIndex,
712 source,
713 pattern,
714 expected,
715 bound,
716 mutable,
717 )
718 coverage
721function recordFieldType(value: TypeValue, name: string) -> Option<TypeValue> {
722 if value.kind != "record" { return None }
723 let mut index = 0
724 for fieldName in value.fieldNames {
725 if fieldName == name {
726 return value.fields.get(index)
727 }
728 index += 1
729 }
730 None
733function literalPatternType(
734 service: CheckerService,
735 state: CheckerState,
736 moduleIndex: int,
737 source: Bytes,
738 pattern: SyntaxNode,
739) -> Option<TypeValue> {
740 if pattern.kind != "pattern/literal" { return None }
741 match child(pattern, "value") {
742 case Some(value) => Some(serviceInferExpression(service, state, moduleIndex, source, value))
743 case None => None
744 }
747function structuralRecordCandidate(
748 service: CheckerService,
749 state: CheckerState,
750 moduleIndex: int,
751 source: Bytes,
752 pattern: SyntaxNode,
753 value: TypeValue,
754) -> bool {
755 if value.kind != "record" { return false }
756 for field in children(pattern, "fields") {
757 let nameNode = match child(field, "name") {
758 case Some(found) => found
759 case None => field
760 }
761 let fieldType = match recordFieldType(value, text(source, nameNode)) {
762 case Some(found) => found
763 case None => return false
764 }
765 match child(field, "pattern") {
766 case Some(nested) => match literalPatternType(
767 service,
768 state,
769 moduleIndex,
770 source,
771 nested,
772 ) {
773 case Some(literalType) => if patternTypeDecidable(fieldType) &&
774 !patternTypesOverlap(literalType, fieldType) {
775 return false
776 }
777 case None => ()
778 }
779 case None => ()
780 }
781 }
782 true
785function bindRecordPattern(
786 service: CheckerService,
787 state: CheckerState,
788 moduleIndex: int,
789 source: Bytes,
790 pattern: SyntaxNode,
791 expected: TypeValue,
792 mutable: bool,
793) -> PatternCoverage {
794 if pattern.kind == "pattern/nominal-record" {
795 let nameNode = match child(pattern, "name") {
796 case Some(found) => found
797 case None => pattern
798 }
799 let recordName = text(source, nameNode)
800 let target = targetType(state, moduleIndex, nameNode)
801 let targetModule = target.moduleIndex
802 let targetName = target.name
803 let targetOrigin = moduleIdentityAt(state, targetModule)
804 let mut definition: Option<RecordDefinition> = None
805 let index = recordDefinitionIndex(state, targetModule, targetName)
806 if index >= 0 {
807 definition = state.records.get(index)
808 }
809 let mut candidates: Array<TypeValue> = []
810 let mut members: Array<TypeValue> = if expected.kind == "union" {
811 expected.members
812 } else {
813 [expected]
814 }
815 let mut hasRigid = false
816 let mut hasGradual = false
817 let mut allMatch = members.length > 0
818 for member in members {
819 if member.kind == "nominal-record" &&
820 member.identity == targetName && member.origin == targetOrigin {
821 candidates.push(member)
822 } else {
823 allMatch = false
824 if member.kind == "rigid" || member.kind == "foreign" {
825 hasRigid = true
826 }
827 if hasHole(member) { hasGradual = true }
828 }
829 }
830 match definition {
831 case Some(recordDefinition) => {
832 if hasRigid {
833 addDiagnostic(
834 state,
835 moduleIndex,
836 "TPZ5001",
837 "a `{recordName}` pattern cannot establish nominal identity for rigid scrutinee type `{typeText(expected)}`",
838 "",
839 pattern,
840 )
841 } else if candidates.length == 0 && !hasGradual {
842 addDiagnostic(
843 state,
844 moduleIndex,
845 "TPZ5001",
846 "this `{recordName}` pattern can never match `{typeText(expected)}`",
847 "",
848 pattern,
849 )
850 }
851 let mut allIrrefutable = true
852 let mut seenFields: Array<string> = []
853 for field in children(pattern, "fields") {
854 let fieldNameNode = match child(field, "name") {
855 case Some(found) => found
856 case None => field
857 }
858 let fieldName = text(source, fieldNameNode)
859 let duplicate = containsString(seenFields, fieldName)
860 seenFields.push(fieldName)
861 if duplicate {
862 addDiagnostic(
863 state,
864 moduleIndex,
865 "TPZ5008",
866 "nominal record pattern field `{fieldName}` is specified more than once",
867 "",
868 field,
869 )
870 }
871 let mut knownField = false
872 for declared in recordDefinition.fields {
873 if declared.name == fieldName { knownField = true }
874 }
875 if !knownField {
876 addDiagnostic(
877 state,
878 moduleIndex,
879 "TPZ5006",
880 "record `{recordName}` has no field `{fieldName}`" +
881 recordFieldSuggestion(recordDefinition, fieldName),
882 "",
883 field,
884 )
885 }
886 let mut fieldTypes: Array<TypeValue> = []
887 for candidate in candidates {
888 for declared in recordDefinition.fields {
889 if declared.name == fieldName {
890 fieldTypes.push(substitute(
891 declared.typeValue,
892 recordDefinition.typeParameters,
893 candidate.arguments,
894 ))
895 }
896 }
897 }
898 if hasGradual { fieldTypes.push(emptyType("unknown")) }
899 let fieldType = if fieldTypes.length == 0 {
900 let mut fallback = emptyType("unknown")
901 for declared in recordDefinition.fields {
902 if declared.name == fieldName { fallback = declared.typeValue }
903 }
904 fallback
905 } else {
906 unionType(fieldTypes)
907 }
908 match child(field, "pattern") {
909 case Some(nested) => {
910 let nestedCoverage = patternBindingTypeAt(
911 service,
912 state,
913 moduleIndex,
914 source,
915 nested,
916 fieldType,
917 mutable,
918 false,
919 )
920 allIrrefutable = allIrrefutable && nestedCoverage.irrefutable
921 }
922 case None => bindSymbol(
923 state,
924 moduleIndex,
925 fieldNameNode.lo,
926 fieldNameNode.hi,
927 fieldName,
928 fieldType,
929 mutable,
930 )
931 }
932 }
933 addNode(state, moduleIndex, "pattern", pattern, expected)
934 if allIrrefutable {
935 let targetIdentity = nominalPatternIdentityFor(
936 targetName,
937 targetOrigin,
938 )
939 let keyed = coverageWithKey("record:{targetIdentity}")
940 return if allMatch {
941 PatternCoverage { irrefutable: true, keys: keyed.keys }
942 } else {
943 keyed
944 }
945 }
946 return emptyPatternCoverage()
947 }
948 case None => {
949 addDiagnostic(
950 state,
951 moduleIndex,
952 "TPZ5002",
953 "unbound nominal record pattern head `{recordName}`" +
954 recordPatternHeadSuggestion(state, moduleIndex, recordName),
955 "",
956 nameNode,
957 )
958 addNode(state, moduleIndex, "pattern", pattern, expected)
959 return emptyPatternCoverage()
960 }
961 }
962 }
964 let mut members: Array<TypeValue> = if expected.kind == "union" {
965 expected.members
966 } else {
967 [expected]
968 }
969 let mut candidates: Array<TypeValue> = []
970 let mut rigids: Array<TypeValue> = []
971 let mut hasGradual = false
972 for member in members {
973 if structuralRecordCandidate(
974 service,
975 state,
976 moduleIndex,
977 source,
978 pattern,
979 member,
980 ) {
981 candidates.push(member)
982 } else if member.kind == "rigid" || member.kind == "foreign" {
983 rigids.push(member)
984 } else if hasHole(member) {
985 hasGradual = true
986 }
987 }
988 if expected.kind != "record" && candidates.length == 0 &&
989 rigids.length == 0 && !hasGradual &&
990 patternTypeDecidable(expected) {
991 addDiagnostic(
992 state,
993 moduleIndex,
994 "TPZ5001",
995 "this pattern can never match `{typeText(expected)}`",
996 "",
997 pattern,
998 )
999 }
1000 let mut allIrrefutable = expected.kind == "record"

SHA256 78fbac70b761990deb559cb45252cbb7bf382c22ccf6286e25bea7a9d18b361b