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 consume3
// the coverage result without duplicating pattern traversal.5
import types { SyntaxNode }6
import checker_types { TypeValue }7
import unicode { didYouMean }8
import resolver.modules_tables { collectPatternBindings }9
import 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,25
}26
import checker.type_algebra {27
assignable,28
hasHole,29
stringJoin,30
typeText,31
unionType,32
}33
import 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,45
}46
import checker.nominals {47
formType,48
substitute,49
targetType,50
}51
import checker.protocols_methods {52
enumDefinitionForType,53
newtypeDefinitionForType,54
}55
import checker.members_capabilities { typeArgument }57
export 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 output65
}66
for nested in pattern.children {67
for found in patternBindingNodes(nested) {68
output.push(found)69
}70
}71
output72
}74
function 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) => found84
case None => pattern85
}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 empty92
}93
}94
case None => ()95
}96
}97
patternBindingNodes(pattern)98
}100
function 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 true117
}118
}119
false120
}122
// Let bindings use the same type-aware coverage as match patterns. In123
// particular, record fields are wrappers and pure-rest arrays and single124
// constructor types can be irrefutable despite their surface syntax.125
export function patternIsIrrefutable(126
state: CheckerState,127
moduleIndex: int,128
expected: TypeValue,129
coverage: PatternCoverage,130
) -> bool {131
hasHole(expected) || coverageCoversAt(state, moduleIndex, coverage, expected, "")132
}134
export function emptyPatternCoverage() -> PatternCoverage {135
PatternCoverage { irrefutable: false, keys: [] }136
}138
function irrefutablePatternCoverage() -> PatternCoverage {139
PatternCoverage { irrefutable: true, keys: [] }140
}142
function coverageHasKey(coverage: PatternCoverage, key: string) -> bool {143
containsString(coverage.keys, key)144
}146
function coverageWithKey(key: string) -> PatternCoverage {147
PatternCoverage { irrefutable: false, keys: [key] }148
}150
export function mergePatternCoverage(151
left: PatternCoverage,152
right: PatternCoverage,153
) -> PatternCoverage {154
let mut keys = left.keys155
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
}164
}166
function coveragePath(prefix: string, part: string) -> string {167
if prefix == "" { part } else { "{prefix}/{part}" }168
}170
function 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 }182
}184
function patternTypeOpaque(value: TypeValue) -> bool {185
value.kind == "unknown" || value.kind == "inference-variable" ||186
value.kind == "rigid" || value.kind == "foreign"187
}189
function patternTypeDecidable(value: TypeValue) -> bool {190
if value.kind == "primitive" || value.kind == "literal" {191
return true192
}193
if value.kind == "union" {194
for member in value.members {195
if !patternTypeDecidable(member) { return false }196
}197
return true198
}199
if value.kind == "constructor" {200
for argument in value.arguments {201
if !patternTypeDecidable(argument) { return false }202
}203
return true204
}205
if value.kind == "record" {206
for field in value.fields {207
if !patternTypeDecidable(field) { return false }208
}209
return true210
}211
false212
}214
function patternTypesOverlap(left: TypeValue, right: TypeValue) -> bool {215
if hasHole(left) || hasHole(right) ||216
assignable(left, right) || assignable(right, left) {217
return true218
}219
if left.kind == "union" {220
for member in left.members {221
if patternTypesOverlap(member, right) { return true }222
}223
return false224
}225
if right.kind == "union" {226
for member in right.members {227
if patternTypesOverlap(left, member) { return true }228
}229
return false230
}231
if left.kind == "constructor" && right.kind == "constructor" {232
return left.name == right.name233
}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.identity238
}239
if left.kind == "record" && right.kind == "record" &&240
left.fieldNames.length == right.fieldNames.length {241
let mut index = 0242
while index < left.fieldNames.length {243
let leftName = match left.fieldNames.get(index) {244
case Some(found) => found245
case None => return false246
}247
let rightName = match right.fieldNames.get(index) {248
case Some(found) => found249
case None => return false250
}251
let leftType = match left.fields.get(index) {252
case Some(found) => found253
case None => return false254
}255
let rightType = match right.fields.get(index) {256
case Some(found) => found257
case None => return false258
}259
if leftName != rightName ||260
!patternTypesOverlap(leftType, rightType) {261
return false262
}263
index += 1264
}265
return true266
}267
false268
}270
function 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
formed287
}289
function patternNameSuggestion(290
target: string,291
candidates: Array<string>,292
) -> string {293
didYouMean(target, candidates)294
}296
function 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)308
}310
function 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)317
}319
function structuralFieldSuggestion(320
value: TypeValue,321
target: string,322
) -> string {323
patternNameSuggestion(target, value.fieldNames)324
}326
function 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)333
}335
function nominalPatternIdentityFor(identity: string, origin: string) -> string {336
if origin == "" { identity }337
else { "{origin}::{identity}" }338
}340
function nominalPatternIdentity(value: TypeValue) -> string {341
nominalPatternIdentityFor(value.identity, value.origin)342
}344
function 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 true353
}354
if prefix != "" && coverageHasKey(coverage, "{prefix}/*") {355
return true356
}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 false377
}378
}379
return true380
}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 false419
}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 false437
}438
} else if !coverageHasKey(coverage, "{variantPrefix}/*") {439
return false440
}441
}442
true443
}444
case None => false445
}446
}447
if value.kind == "nominal-record" {448
return coverageHasKey(449
coverage,450
coveragePath(prefix, "record:{nominalPatternIdentity(value)}"),451
)452
}453
false454
}456
export 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 = true480
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 = false494
}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 = false543
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: [] }581
}583
function 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
coverage616
}618
export 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) => found629
case None => pattern630
}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)660
}662
function 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 => expected683
}684
} else {685
expected686
}687
let mut coverage = irrefutablePatternCoverage()688
let mut bound = typeValue689
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
coverage719
}721
function recordFieldType(value: TypeValue, name: string) -> Option<TypeValue> {722
if value.kind != "record" { return None }723
let mut index = 0724
for fieldName in value.fieldNames {725
if fieldName == name {726
return value.fields.get(index)727
}728
index += 1729
}730
None731
}733
function 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 => None744
}745
}747
function 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) => found759
case None => field760
}761
let fieldType = match recordFieldType(value, text(source, nameNode)) {762
case Some(found) => found763
case None => return false764
}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 false776
}777
case None => ()778
}779
case None => ()780
}781
}782
true783
}785
function 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) => found797
case None => pattern798
}799
let recordName = text(source, nameNode)800
let target = targetType(state, moduleIndex, nameNode)801
let targetModule = target.moduleIndex802
let targetName = target.name803
let targetOrigin = moduleIdentityAt(state, targetModule)804
let mut definition: Option<RecordDefinition> = None805
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.members812
} else {813
[expected]814
}815
let mut hasRigid = false816
let mut hasGradual = false817
let mut allMatch = members.length > 0818
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 = false824
if member.kind == "rigid" || member.kind == "foreign" {825
hasRigid = true826
}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 = true852
let mut seenFields: Array<string> = []853
for field in children(pattern, "fields") {854
let fieldNameNode = match child(field, "name") {855
case Some(found) => found856
case None => field857
}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 = false872
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
fallback905
} 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.irrefutable921
}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
keyed944
}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.members966
} else {967
[expected]968
}969
let mut candidates: Array<TypeValue> = []970
let mut rigids: Array<TypeValue> = []971
let mut hasGradual = false972
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 = true986
}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