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/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 expressions
3// cross the explicit checker service rather than importing call leaves back.
5import types { SyntaxNode }
6import checker_types { TypeValue }
7import unicode { didYouMean }
8import 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,
23import checker.type_algebra {
24 hasHole,
25 hasPartialHole,
26 joinedTypes,
27 typeContains,
28 typeText,
29 unionType,
30 widen,
32import 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,
46import checker.nominals {
47 addTypeArgumentArityDiagnostic,
48 formType,
49 substitute,
51import checker.protocols_methods {
52 enumDefinitionForType,
53 newtypeDefinitionForType,
54 receiverMethodType,
55 recordDefinitionForType,
56 referenceTargetType,
58import checker.members_capabilities {
59 arraySpreadElement,
60 builtinNamespaceMemberType,
61 containsByteBuffer,
62 gateCollectionKey,
63 iterableElement,
64 memberType,
65 protocolStaticMemberType,
66 rigidUnionMissingConcreteMember,
68import 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,
85export 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 false
93 }
94 match statements.get(statements.length - 1) {
95 case Some(statement) => {
96 let item = unwrapExport(statement)
97 if item.kind == "statement/return" {
98 return true
99 }
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 false
112export 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 => false
129 }
130 let elseTerminates = match child(expression, "else") {
131 case Some(found) => expressionTerminates(found)
132 case None => false
133 }
134 return thenTerminates && elseTerminates
135 }
136 if expression.kind == "expression/match" {
137 let cases = children(expression, "cases")
138 if cases.length == 0 {
139 return false
140 }
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 false
146 }
147 case None => return false
148 }
149 }
150 return true
151 }
152 false
155export 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 None
184 }
185 case None => None
186 }
187 let mut parameters: Array<TypeValue> = []
188 let mut parameterIndex = 0
189 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) => found
204 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) => found
214 case None => parameter
215 }
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 += 1
228 }
229 let mut loopFloors = state.loopFloors
230 loopFloors.push(state.loopFrames.length)
231 let mut expectedReturns = state.expectedReturns
232 expectedReturns.push(match expectedResult {
233 case Some(found) => found
234 case None => emptyType("unknown")
235 })
236 let mut returnJoins = state.returnJoins
237 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) => found
260 case None => []
261 }
262 returnJoins.pop()
263 expectedReturns.pop()
264 loopFloors.pop()
265 let result = if returns.length == 0 {
266 body
267 } else {
268 match bodyNode {
269 case Some(found) => if !expressionTerminates(found) {
270 returns.push(body)
271 }
272 case None => ()
273 }
274 let mut unresolved = false
275 for found in returns {
276 if hasHole(found) {
277 unresolved = true
278 }
279 }
280 if unresolved {
281 emptyType("unknown")
282 } else {
283 joinedTypes(state, returns, true)
284 }
285 }
286 functionType(parameters, [], result)
289export function hasLiteralJoinContext(
290 state: CheckerState,
291 expression: SyntaxNode,
292) -> bool {
293 expressionContextAt(state.literalJoinContexts, expression)
296export function hasBranchJoinContext(
297 state: CheckerState,
298 expression: SyntaxNode,
299) -> bool {
300 expressionContextAt(state.branchJoinContexts, expression)
303function expressionContextAt(contexts: Array<int>, expression: SyntaxNode) -> bool {
304 match contexts.get(contexts.length - 1) {
305 case Some(lo) => lo == expression.lo
306 case None => false
307 }
310export 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 literals
319 // will be widened. Keep this separate from literal-preserving contexts.
320 let mut joins = state.branchJoinContexts
321 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 result
333export 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 element
353export 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 = false
362 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 = true
369 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 }
393export 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) => found
402 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 context
410 }
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 => false
417 }
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) => found
442 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 }
469export 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 = false
478 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 expected
483 }
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 => None
500 }
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 = true
517 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 }
561export 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 expected
572 }
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)])
629export 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 => None
641 }
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 fieldType
660 }
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 continue
689 }
690 names.push(name)
691 values.push(value)
692 }
693 structural(names, values)
696function 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 => false
724 }
725 if typeNamespace {
726 match referenceTargetType(state, moduleIndex, object) {
727 case Some(found) => found
728 case None => serviceInferExpression(service, state, moduleIndex, source, object)
729 }
730 } else {
731 serviceInferExpression(service, state, moduleIndex, source, object)
732 }
735export 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 expected
744 } else {
745 objectType
746 }
747 case None => objectType
748 }
751record 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,
763// Optional-access carrier retaining both the inner type and wrapper identity.
764export record OptionalReceiver {
765 innerType: TypeValue,
766 container: string,
769export record OptionalMemberAccess {
770 rawType: TypeValue,
771 wrappedType: TypeValue,
772 receiverType: TypeValue,
773 container: string,
776export 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 => None
784 }
785 }
786 if objectType.kind != "union" {
787 return None
788 }
789 let mut nullable = false
790 let mut innerMembers: Array<TypeValue> = []
791 for member in objectType.members {
792 if member.kind == "literal" && member.name == "null" {
793 nullable = true
794 } 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 None
805 }
808export function wrapOptionalResult(rawType: TypeValue, container: string) -> TypeValue {
809 if container == "" {
810 return rawType
811 }
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 rawType
821 }
822 return constructor("Option", [rawType])
823 }
824 rawType
827function 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 true
836 }
837 match userProtocolObject(state, moduleIndex, source, object) {
838 case Some(_) => return true
839 case None => ()
840 }
841 match referenceFor(state, moduleIndex, object.lo, object.hi) {
842 case Some(reference) => reference.targetNamespace == "type"
843 case None => false
844 }
847function memberNameSuggestion(target: string, candidates: Array<string>) -> string {
848 didYouMean(target, candidates)
851function 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 )
906export 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) => found
916 case None => node
917 }
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 None
943 } else {
944 optionalReceiver(contextualMemberObjectType(
945 state,
946 node,
947 objectType,
948 ))
949 }
950 let unwrapped = match receiver {
951 case Some(found) => found
952 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