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/expression_core.tpz
Topaz · 47647 B · Original file
1
// Core expression inference for literals, lambdas, aggregates, and members.2
// Control forms live beside it in expression_control, while call expressions3
// cross the explicit checker service rather than importing call leaves back.5
import types { SyntaxNode }6
import checker_types { TypeValue }7
import unicode { didYouMean }8
import checker.model {9
CheckerService,10
CheckerState,11
constructor,12
emptyType,13
functionType,14
inference,15
literal,16
nominal,17
primitive,18
rigid,19
serviceCheckBlock,20
serviceInferExpression,21
structural,22
}23
import checker.type_algebra {24
hasHole,25
hasPartialHole,26
joinedTypes,27
typeContains,28
typeText,29
unionType,30
widen,31
}32
import checker.syntax_index {33
addDiagnostic,34
addNode,35
bindSymbol,36
child,37
children,38
containsString,39
protocolDefinition,40
protocolMethodDefinition,41
referenceFor,42
symbolAt,43
text,44
unwrapExport,45
}46
import checker.nominals {47
addTypeArgumentArityDiagnostic,48
formType,49
substitute,50
}51
import checker.protocols_methods {52
enumDefinitionForType,53
newtypeDefinitionForType,54
receiverMethodType,55
recordDefinitionForType,56
referenceTargetType,57
}58
import checker.members_capabilities {59
arraySpreadElement,60
builtinNamespaceMemberType,61
containsByteBuffer,62
gateCollectionKey,63
iterableElement,64
memberType,65
protocolStaticMemberType,66
rigidUnionMissingConcreteMember,67
}68
import checker.call_facts {69
builtinIdentifierType,70
builtinMutator,71
builtinNamespaceObject,72
checkExpressionExpected,73
currentExpectedExpression,74
generatedLispexIntrinsic,75
inferExpressionContext,76
inferExpressionWithExpected,77
isBuiltinNamespace,78
isBareBindingExpression,79
reportExpectedType,80
unboundCalleeSuggestion,81
unboundSuggestion,82
userProtocolObject,83
}85
export function blockTerminates(block: SyntaxNode) -> bool {86
match child(block, "tail") {87
case Some(tail) => if expressionTerminates(tail) { return true }88
case None => ()89
}90
let statements = children(block, "statements")91
if statements.length == 0 {92
return false93
}94
match statements.get(statements.length - 1) {95
case Some(statement) => {96
let item = unwrapExport(statement)97
if item.kind == "statement/return" {98
return true99
}100
if item.kind == "statement/expression" {101
match item.children.get(0) {102
case Some(expression) => return expressionTerminates(expression)103
case None => ()104
}105
}106
}107
case None => ()108
}109
false110
}112
export function expressionTerminates(expression: SyntaxNode) -> bool {113
if expression.kind == "expression/block" {114
match child(expression, "block") {115
case Some(block) => return blockTerminates(block)116
case None => ()117
}118
}119
if expression.kind == "expression/parenthesized" {120
match child(expression, "operand") {121
case Some(operand) => return expressionTerminates(operand)122
case None => ()123
}124
}125
if expression.kind == "expression/if" {126
let thenTerminates = match child(expression, "then") {127
case Some(found) => blockTerminates(found)128
case None => false129
}130
let elseTerminates = match child(expression, "else") {131
case Some(found) => expressionTerminates(found)132
case None => false133
}134
return thenTerminates && elseTerminates135
}136
if expression.kind == "expression/match" {137
let cases = children(expression, "cases")138
if cases.length == 0 {139
return false140
}141
for foundCase in cases {142
match child(foundCase, "body") {143
case Some(body) => if body.kind != "match-case-return" &&144
!expressionTerminates(body) {145
return false146
}147
case None => return false148
}149
}150
return true151
}152
false153
}155
export function inferLambda(156
service: CheckerService,157
state: CheckerState,158
moduleIndex: int,159
source: Bytes,160
node: SyntaxNode,161
expected: Option<TypeValue>,162
) -> TypeValue {163
let parameterNodes = children(node, "parameters")164
match expected {165
case Some(functionValue) => if functionValue.kind == "function" &&166
functionValue.variadic.length == 0 &&167
functionValue.parameters.length != parameterNodes.length {168
addDiagnostic(169
state,170
moduleIndex,171
"TPZ5004",172
"this lambda takes {parameterNodes.length} parameter(s), but a function taking {functionValue.parameters.length} is expected",173
"",174
node,175
)176
}177
case None => ()178
}179
let expectedResult = match expected {180
case Some(functionValue) => if functionValue.kind == "function" {181
functionValue.result.get(0)182
} else {183
None184
}185
case None => None186
}187
let mut parameters: Array<TypeValue> = []188
let mut parameterIndex = 0189
for parameter in parameterNodes {190
let typeValue = match child(parameter, "type") {191
case Some(found) => formType(192
service,193
state,194
moduleIndex,195
source,196
found,197
[],198
"lambda",199
)200
case None => match expected {201
case Some(functionValue) => if functionValue.kind == "function" {202
match functionValue.parameters.get(parameterIndex) {203
case Some(found) => found204
case None => inference(parameter.lo)205
}206
} else {207
inference(parameter.lo)208
}209
case None => inference(parameter.lo)210
}211
}212
let nameNode = match child(parameter, "name") {213
case Some(found) => found214
case None => parameter215
}216
bindSymbol(217
state,218
moduleIndex,219
nameNode.lo,220
nameNode.hi,221
text(source, nameNode),222
typeValue,223
false,224
)225
addNode(state, moduleIndex, "binding", nameNode, typeValue)226
parameters.push(typeValue)227
parameterIndex += 1228
}229
let mut loopFloors = state.loopFloors230
loopFloors.push(state.loopFrames.length)231
let mut expectedReturns = state.expectedReturns232
expectedReturns.push(match expectedResult {233
case Some(found) => found234
case None => emptyType("unknown")235
})236
let mut returnJoins = state.returnJoins237
returnJoins.push([])238
let bodyNode = child(node, "body")239
let body = match bodyNode {240
case Some(found) => match expectedResult {241
case Some(result) => if hasHole(result) {242
serviceInferExpression(service, state, moduleIndex, source, found)243
} else {244
inferExpressionWithExpected(245
service,246
state,247
moduleIndex,248
source,249
found,250
result,251
true,252
)253
}254
case None => serviceInferExpression(service, state, moduleIndex, source, found)255
}256
case None => emptyType("unknown")257
}258
let mut returns = match returnJoins.get(returnJoins.length - 1) {259
case Some(found) => found260
case None => []261
}262
returnJoins.pop()263
expectedReturns.pop()264
loopFloors.pop()265
let result = if returns.length == 0 {266
body267
} else {268
match bodyNode {269
case Some(found) => if !expressionTerminates(found) {270
returns.push(body)271
}272
case None => ()273
}274
let mut unresolved = false275
for found in returns {276
if hasHole(found) {277
unresolved = true278
}279
}280
if unresolved {281
emptyType("unknown")282
} else {283
joinedTypes(state, returns, true)284
}285
}286
functionType(parameters, [], result)287
}289
export function hasLiteralJoinContext(290
state: CheckerState,291
expression: SyntaxNode,292
) -> bool {293
expressionContextAt(state.literalJoinContexts, expression)294
}296
export function hasBranchJoinContext(297
state: CheckerState,298
expression: SyntaxNode,299
) -> bool {300
expressionContextAt(state.branchJoinContexts, expression)301
}303
function expressionContextAt(contexts: Array<int>, expression: SyntaxNode) -> bool {304
match contexts.get(contexts.length - 1) {305
case Some(lo) => lo == expression.lo306
case None => false307
}308
}310
export function inferBranchExpression(311
service: CheckerService,312
state: CheckerState,313
moduleIndex: int,314
source: Bytes,315
expression: SyntaxNode,316
contextual: bool,317
) -> TypeValue {318
// Branches can solve each other's inference holes even when their literals319
// will be widened. Keep this separate from literal-preserving contexts.320
let mut joins = state.branchJoinContexts321
joins.push(expression.lo)322
let result = if expression.kind == "block" {323
serviceCheckBlock(service, state, moduleIndex, source, expression)324
} else if contextual && expression.kind != "expression/block" {325
inferExpressionContext(service, state, moduleIndex, source, expression)326
} else {327
serviceInferExpression(service, state, moduleIndex, source, expression)328
}329
joins.pop()330
result331
}333
export function checkedIterableElement(334
state: CheckerState,335
moduleIndex: int,336
node: SyntaxNode,337
iterator: TypeValue,338
) -> TypeValue {339
let element = iterableElement(iterator)340
if element.kind == "unknown" && !hasHole(iterator) {341
addDiagnostic(342
state,343
moduleIndex,344
"TPZ5001",345
"`{typeText(iterator)}` is not iterable (§10)",346
"",347
node,348
)349
}350
element351
}353
export function inferTextExpression(354
service: CheckerService,355
state: CheckerState,356
moduleIndex: int,357
source: Bytes,358
node: SyntaxNode,359
) -> TypeValue {360
let mut value = ""361
let mut interpolated = false362
for part in node.children {363
if part.kind == "string-part/text" {364
value = "{value}{text(source, part)}"365
}366
match child(part, "value") {367
case Some(expression) => {368
interpolated = true369
let inferred = serviceInferExpression(service, state, moduleIndex, source, expression)370
if containsByteBuffer(state, moduleIndex, inferred, []) {371
addDiagnostic(372
state,373
moduleIndex,374
"TPZ5001",375
"`ByteBuffer` values cannot be interpolated; snapshot to `Bytes` and encode explicitly",376
"",377
expression,378
)379
}380
}381
case None => ()382
}383
}384
if node.kind == "expression/template" {385
emptyType("template")386
} else if interpolated {387
primitive("string")388
} else {389
literal("string", value)390
}391
}393
export function inferIdentifierExpression(394
state: CheckerState,395
moduleIndex: int,396
source: Bytes,397
node: SyntaxNode,398
callee: bool,399
) -> TypeValue {400
match referenceTargetType(state, moduleIndex, node) {401
case Some(found) => found402
case None => {403
let name = text(source, node)404
if name == "None" {405
let expected = currentExpectedExpression(state, node)406
match expected {407
case Some(context) => if context.kind == "constructor" &&408
context.name == "Option" && context.arguments.length == 1 {409
return context410
}411
case None => ()412
}413
let solving = match expected {414
case Some(context) => !hasPartialHole(context) &&415
!typeContains(context, (part) => part.kind == "unknown" && part.origin == "generic-call-expectation")416
case None => false417
}418
if isBareBindingExpression(state, node) || solving {419
addDiagnostic(state, moduleIndex, "TPZ5020",420
"this expression needs a contextual type (§22.1): annotate the binding or surrounding expectation", "", node)421
emptyType("unknown")422
} else if hasLiteralJoinContext(state, node) || hasBranchJoinContext(state, node) {423
constructor("Option", [inference(node.lo)])424
} else {425
constructor("Option", [emptyType("unknown")])426
}427
} else if isBuiltinNamespace(name) {428
addDiagnostic(429
state,430
moduleIndex,431
"TPZ5002",432
"`{name}` is not bound",433
"",434
node,435
)436
emptyType("unknown")437
} else if generatedLispexIntrinsic(state, moduleIndex, name) {438
emptyType("unknown")439
} else {440
match builtinIdentifierType(name) {441
case Some(found) => found442
case None => {443
match referenceFor(state, moduleIndex, node.lo, node.hi) {444
case Some(reference) => if reference.targetModuleIndex < 0 &&445
reference.namespace == "value" && reference.role == "read" {446
addDiagnostic(447
state,448
moduleIndex,449
"TPZ5002",450
"`{reference.name}` is not bound" + if callee {451
unboundCalleeSuggestion(state, reference)452
} else {453
unboundSuggestion(state, reference)454
},455
"",456
node,457
)458
}459
case None => ()460
}461
emptyType("unknown")462
}463
}464
}465
}466
}467
}469
export function inferSequenceLiteralExpression(470
service: CheckerService,471
state: CheckerState,472
moduleIndex: int,473
source: Bytes,474
node: SyntaxNode,475
) -> TypeValue {476
let mut elementTypes: Array<TypeValue> = []477
let mut opaque = false478
if children(node, "elements").length == 0 {479
let name = if node.kind == "expression/array" { "Array" } else { "Set" }480
match currentExpectedExpression(state, node) {481
case Some(expected) => if expected.kind == "constructor" && expected.name == name {482
return expected483
}484
case None => ()485
}486
if isBareBindingExpression(state, node) {487
addDiagnostic(state, moduleIndex, "TPZ5020",488
if name == "Array" { "`[]` needs a contextual type (§22.1): annotate the binding or the expected element type" }489
else { "`set \{\}` needs a contextual type (§6): annotate the binding or the expected element type" }, "", node)490
}491
}492
let contextualElement = match currentExpectedExpression(state, node) {493
case Some(expected) => if node.kind == "expression/array" &&494
children(node, "elements").length > 0 &&495
expected.kind == "constructor" && expected.name == "Array" &&496
!hasHole(expected) && !typeContains(expected, (part) => part.kind == "rigid") {497
expected.arguments.get(0)498
} else { None }499
case None => None500
}501
for element in children(node, "elements") {502
if element.kind == "array-element/spread" {503
match child(element, "value") {504
case Some(value) => {505
let spreadType = serviceInferExpression(service, state, moduleIndex, source, value)506
let spread = arraySpreadElement(spreadType)507
if spread.status == "element" {508
elementTypes.push(spread.element)509
match contextualElement {510
case Some(expected) => reportExpectedType(511
state, moduleIndex, value, expected, spread.element,512
)513
case None => ()514
}515
} else {516
opaque = true517
if spread.status == "invalid" {518
addDiagnostic(519
state,520
moduleIndex,521
"TPZ5001",522
"array spread needs an `Array`, found `{typeText(spreadType)}`",523
"",524
value,525
)526
}527
}528
}529
case None => ()530
}531
} else {532
let inferred = match contextualElement {533
case Some(expected) => checkExpressionExpected(534
service, state, moduleIndex, source, element, expected,535
)536
case None => serviceInferExpression(service, state, moduleIndex, source, element)537
}538
elementTypes.push(widen(inferred))539
}540
}541
match contextualElement {542
case Some(expected) => return constructor("Array", [expected])543
case None => ()544
}545
let element = if opaque {546
emptyType("unknown")547
} else if elementTypes.length == 0 &&548
node.kind == "expression/array" {549
inference(node.lo)550
} else {551
unionType(elementTypes)552
}553
if node.kind == "expression/array" {554
constructor("Array", [element])555
} else {556
gateCollectionKey(service, state, moduleIndex, node, element)557
constructor("Set", [element])558
}559
}561
export function inferMapLiteralExpression(562
service: CheckerService,563
state: CheckerState,564
moduleIndex: int,565
source: Bytes,566
node: SyntaxNode,567
) -> TypeValue {568
if children(node, "entries").length == 0 {569
match currentExpectedExpression(state, node) {570
case Some(expected) => if expected.kind == "constructor" && expected.name == "Map" {571
return expected572
}573
case None => ()574
}575
if isBareBindingExpression(state, node) {576
addDiagnostic(state, moduleIndex, "TPZ5020",577
"`map \{\}` needs a contextual type (§6): annotate the binding or the expected key/value types", "", node)578
}579
}580
let mut keys: Array<TypeValue> = []581
let mut values: Array<TypeValue> = []582
let mut constantKeys: Array<string> = []583
for entry in children(node, "entries") {584
match child(entry, "key") {585
case Some(found) => {586
if found.kind == "expression/string" ||587
found.kind == "expression/integer" ||588
found.kind == "expression/bool" {589
let key = "{found.kind}:{text(source, found)}"590
if containsString(constantKeys, key) {591
addDiagnostic(592
state,593
moduleIndex,594
"TPZ5602",595
"duplicate key in `map \{ … \}` literal",596
"",597
found,598
)599
} else {600
constantKeys.push(key)601
}602
}603
keys.push(widen(serviceInferExpression(604
service,605
state,606
moduleIndex,607
source,608
found,609
)))610
}611
case None => ()612
}613
match child(entry, "value") {614
case Some(found) => values.push(widen(serviceInferExpression(615
service,616
state,617
moduleIndex,618
source,619
found,620
)))621
case None => ()622
}623
}624
let key = unionType(keys)625
gateCollectionKey(service, state, moduleIndex, node, key)626
constructor("Map", [key, unionType(values)])627
}629
export function inferRecordLiteralExpression(630
service: CheckerService,631
state: CheckerState,632
moduleIndex: int,633
source: Bytes,634
node: SyntaxNode,635
) -> TypeValue {636
let mut names: Array<string> = []637
let mut values: Array<TypeValue> = []638
let expected = match currentExpectedExpression(state, node) {639
case Some(found) => if found.kind == "record" { Some(found) } else { None }640
case None => None641
}642
for field in children(node, "fields") {643
let name = match child(field, "name") {644
case Some(found) => text(source, found)645
case None => ""646
}647
let value = match child(field, "value") {648
case Some(found) => match expected {649
case Some(record) => match structuralRecordFieldType(record, name) {650
case Some(fieldType) => {651
let _ = checkExpressionExpected(652
service,653
state,654
moduleIndex,655
source,656
found,657
fieldType,658
)659
fieldType660
}661
case None => widen(serviceInferExpression(662
service,663
state,664
moduleIndex,665
source,666
found,667
))668
}669
case None => widen(serviceInferExpression(670
service,671
state,672
moduleIndex,673
source,674
found,675
))676
}677
case None => emptyType("unknown")678
}679
if containsString(names, name) {680
addDiagnostic(681
state,682
moduleIndex,683
"TPZ5022",684
"record literal declares field `{name}` twice",685
"",686
field,687
)688
continue689
}690
names.push(name)691
values.push(value)692
}693
structural(names, values)694
}696
function inferMemberObjectType(697
service: CheckerService,698
state: CheckerState,699
moduleIndex: int,700
source: Bytes,701
object: SyntaxNode,702
protocolCall: bool,703
) -> TypeValue {704
if object.kind != "expression/identifier" {705
return serviceInferExpression(service, state, moduleIndex, source, object)706
}707
if builtinNamespaceObject(service, state, moduleIndex, source, object) {708
return emptyType("unknown")709
}710
if protocolCall {711
match userProtocolObject(state, moduleIndex, source, object) {712
case Some(_) => return emptyType("unknown")713
case None => ()714
}715
}716
let typeNamespace = match referenceFor(717
state,718
moduleIndex,719
object.lo,720
object.hi,721
) {722
case Some(reference) => reference.targetNamespace == "type"723
case None => false724
}725
if typeNamespace {726
match referenceTargetType(state, moduleIndex, object) {727
case Some(found) => found728
case None => serviceInferExpression(service, state, moduleIndex, source, object)729
}730
} else {731
serviceInferExpression(service, state, moduleIndex, source, object)732
}733
}735
export function contextualMemberObjectType(736
state: CheckerState,737
expression: SyntaxNode,738
objectType: TypeValue,739
) -> TypeValue {740
match currentExpectedExpression(state, expression) {741
case Some(expected) => if objectType.kind == "enum" &&742
expected.kind == "enum" && objectType.identity == expected.identity {743
expected744
} else {745
objectType746
}747
case None => objectType748
}749
}751
record MemberAccessContext {752
state: CheckerState,753
moduleIndex: int,754
source: Bytes,755
object: SyntaxNode,756
objectType: TypeValue,757
field: Option<SyntaxNode>,758
fieldName: string,759
builtinNamespace: bool,760
protocolNamespace: string,761
}763
// Optional-access carrier retaining both the inner type and wrapper identity.764
export record OptionalReceiver {765
innerType: TypeValue,766
container: string,767
}769
export record OptionalMemberAccess {770
rawType: TypeValue,771
wrappedType: TypeValue,772
receiverType: TypeValue,773
container: string,774
}776
export function optionalReceiver(objectType: TypeValue) -> Option<OptionalReceiver> {777
if objectType.kind == "constructor" && objectType.name == "Option" {778
return match objectType.arguments.get(0) {779
case Some(inner) => Some(OptionalReceiver {780
innerType: inner,781
container: "option",782
})783
case None => None784
}785
}786
if objectType.kind != "union" {787
return None788
}789
let mut nullable = false790
let mut innerMembers: Array<TypeValue> = []791
for member in objectType.members {792
if member.kind == "literal" && member.name == "null" {793
nullable = true794
} else {795
innerMembers.push(member)796
}797
}798
if nullable && innerMembers.length > 0 {799
Some(OptionalReceiver {800
innerType: unionType(innerMembers),801
container: "nullable",802
})803
} else {804
None805
}806
}808
export function wrapOptionalResult(rawType: TypeValue, container: string) -> TypeValue {809
if container == "" {810
return rawType811
}812
if hasHole(rawType) {813
return emptyType("unknown")814
}815
if container == "nullable" {816
return unionType([rawType, literal("null", "")])817
}818
if container == "option" {819
if rawType.kind == "constructor" && rawType.name == "Option" {820
return rawType821
}822
return constructor("Option", [rawType])823
}824
rawType825
}827
function memberObjectIsNamespace(828
service: CheckerService,829
state: CheckerState,830
moduleIndex: int,831
source: Bytes,832
object: SyntaxNode,833
) -> bool {834
if builtinNamespaceObject(service, state, moduleIndex, source, object) {835
return true836
}837
match userProtocolObject(state, moduleIndex, source, object) {838
case Some(_) => return true839
case None => ()840
}841
match referenceFor(state, moduleIndex, object.lo, object.hi) {842
case Some(reference) => reference.targetNamespace == "type"843
case None => false844
}845
}847
function memberNameSuggestion(target: string, candidates: Array<string>) -> string {848
didYouMean(target, candidates)849
}851
function reportMissingOptionalMember(852
context: MemberAccessContext,853
diagnosticNode: SyntaxNode,854
) -> () {855
if context.fieldName == "" {856
return ()857
}858
if context.objectType.kind == "record" {859
let hint = memberNameSuggestion(860
context.fieldName,861
context.objectType.fieldNames,862
)863
addDiagnostic(864
context.state,865
context.moduleIndex,866
"TPZ5006",867
"`{typeText(widen(context.objectType))}` has no field `{context.fieldName}`{hint}",868
"",869
diagnosticNode,870
)871
return ()872
}873
match recordDefinitionForType(874
context.state,875
context.moduleIndex,876
context.objectType,877
) {878
case Some(definition) => {879
let mut names: Array<string> = []880
for field in definition.fields {881
names.push(field.name)882
}883
let hint = memberNameSuggestion(context.fieldName, names)884
addDiagnostic(885
context.state,886
context.moduleIndex,887
"TPZ5006",888
"record `{context.objectType.identity}` has no field `{context.fieldName}`{hint}",889
"",890
diagnosticNode,891
)892
return ()893
}894
case None => ()895
}896
addDiagnostic(897
context.state,898
context.moduleIndex,899
"TPZ5006",900
"`{typeText(widen(context.objectType))}` has no member named `{context.fieldName}`",901
"",902
diagnosticNode,903
)904
}906
export function inferOptionalMemberAccess(907
service: CheckerService,908
state: CheckerState,909
moduleIndex: int,910
source: Bytes,911
node: SyntaxNode,912
diagnosticNode: SyntaxNode,913
) -> OptionalMemberAccess {914
let objectNode = match child(node, "object") {915
case Some(found) => found916
case None => node917
}918
let objectType = inferMemberObjectType(919
service,920
state,921
moduleIndex,922
source,923
objectNode,924
false,925
)926
let namespaceObject = memberObjectIsNamespace(927
service,928
state,929
moduleIndex,930
source,931
objectNode,932
)933
let receiver = if namespaceObject {934
addDiagnostic(935
state,936
moduleIndex,937
"TPZ5001",938
"`?.` needs an Option or nullable receiver, found `namespace`",939
"",940
diagnosticNode,941
)942
None943
} else {944
optionalReceiver(contextualMemberObjectType(945
state,946
node,947
objectType,948
))949
}950
let unwrapped = match receiver {951
case Some(found) => found952
case None => {953
if !hasHole(objectType) && !namespaceObject {954
addDiagnostic(955
state,956
moduleIndex,957
"TPZ5001",958
"`?.` needs an Option or nullable receiver, found `{typeText(widen(objectType))}`",959
"",960
diagnosticNode,961
)962
}963
return OptionalMemberAccess {964
rawType: emptyType("unknown"),965
wrappedType: emptyType("unknown"),966
receiverType: emptyType("unknown"),967
container: "",968
}969
}970
}971
let fieldNode = child(node, "field")972
let fieldName = match fieldNode {973
case Some(found) => text(source, found)974
case None => ""975
}976
let context = MemberAccessContext {977
state: state,978
moduleIndex: moduleIndex,979
source: source,980
object: objectNode,981
objectType: unwrapped.innerType,982
field: fieldNode,983
fieldName: fieldName,984
builtinNamespace: false,985
protocolNamespace: "",986
}987
let found = resolvedMemberAccessType(context)988
if (found.kind == "unknown" && !hasHole(unwrapped.innerType) ||989
rigidUnionMissingConcreteMember(990
state,991
moduleIndex,992
unwrapped.innerType,993
fieldName,994
)) && fieldName != "" {995
reportMissingOptionalMember(context, diagnosticNode)996
}997
OptionalMemberAccess {998
rawType: found,999
wrappedType: wrapOptionalResult(found, unwrapped.container),1000
receiverType: unwrapped.innerType,SHA256 bc57b886f5a6ab289823e433f59a8beee771a26355e09cca1da9de967b2ecb72