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_control.tpz

Topaz · 47054 B · Original file

1// Inference for operators, branches, loops, matches, and concurrent forms.
2// Core literals and member access stay in expression_core; recursive descent is
3// routed through the service boundary owned by the checker facade.
5import types { SyntaxNode }
6import checker_types { TypeValue }
7import checker.model {
8 CallPreparation,
9 CheckerService,
10 CheckerState,
11 LeadingCallArgument,
12 LoopFrame,
13 MAX_DURATION_MILLISECONDS,
14 MAX_DURATION_MINUTES,
15 MAX_DURATION_SECONDS,
16 constructor,
17 emptyType,
18 functionType,
19 literal,
20 nominal,
21 primitive,
22 serviceCheckBlock,
23 serviceCheckBlockExpected,
24 structural,
26import checker.type_algebra {
27 assignable,
28 hasHole,
29 hasPartialHole,
30 joinedTypes,
31 solvePartialTypeAgainst,
32 stringJoin,
33 typeText,
34 unionType,
35 widen,
36 widenedUnion,
38import checker.syntax_index {
39 addDiagnostic,
40 addNode,
41 attribute,
42 child,
43 children,
44 text,
46import checker.members_capabilities {
47 comparableType,
48 gateCollectionKey,
49 memberType,
50 orderCapability,
52import checker.patterns {
53 coverageGap,
54 emptyPatternCoverage,
55 mergePatternCoverage,
56 patternBindingType,
57 patternBindingTypeAt,
59import checker.call_facts {
60 addPipeCall,
61 argumentValue,
62 checkExpressionExpected,
63 checkReturn,
64 containsPlaceholder,
65 currentExpectedExpression,
66 currentFunctionReturn,
67 currentPipeValue,
68 inferExpressionWithExpected,
69 recordInferredExpression,
70 refineExpressionToExpected,
71 reportExpectedType,
73import checker.expression_core {
74 blockTerminates,
75 checkedIterableElement,
76 expressionTerminates,
77 hasBranchJoinContext,
78 hasLiteralJoinContext,
79 inferBranchExpression,
80 inferIdentifierExpression,
81 inferLambda,
82 inferMapLiteralExpression,
83 inferMemberAccessExpression,
84 inferRecordLiteralExpression,
85 inferRecordUpdateExpression,
86 inferSequenceLiteralExpression,
87 inferTextExpression,
89import checker.call_complete {
90 callArgumentInferenceContext,
91 completePreparedCall,
92 inferCall,
93 inferCallArguments,
94 inferContextualCallArgument,
95 leadingCallActuals,
96 prepareCall,
99function inferIfExpression(
100 service: CheckerService,
101 state: CheckerState,
102 moduleIndex: int,
103 source: Bytes,
104 node: SyntaxNode,
105) -> TypeValue {
106 let contextual = hasLiteralJoinContext(state, node)
107 let expected = currentExpectedExpression(state, node)
108 match child(node, "condition") {
109 case Some(found) => {
110 let _ = checkExpressionExpected(
111 service,
112 state,
113 moduleIndex,
114 source,
115 found,
116 primitive("bool"),
117 )
118 }
119 case None => ()
120 }
121 let mut branches: Array<TypeValue> = []
122 match child(node, "then") {
123 case Some(found) => {
124 let terminating = blockTerminates(found)
125 let branch = if terminating {
126 serviceCheckBlock(
127 service,
128 state,
129 moduleIndex,
130 source,
131 found,
132 )
133 } else {
134 match expected {
135 case Some(expectedType) => if hasHole(expectedType) {
136 inferBranchExpression(
137 service,
138 state,
139 moduleIndex,
140 source,
141 found,
142 contextual,
143 )
144 } else {
145 serviceCheckBlockExpected(
146 service,
147 state,
148 moduleIndex,
149 source,
150 found,
151 expectedType,
152 )
153 }
154 case None => inferBranchExpression(
155 service,
156 state,
157 moduleIndex,
158 source,
159 found,
160 contextual,
161 )
162 }
163 }
164 if !terminating {
165 branches.push(branch)
166 }
167 }
168 case None => ()
169 }
170 match child(node, "else") {
171 case Some(found) => {
172 let terminating = expressionTerminates(found)
173 let branch = if terminating {
174 inferExpression(service, state, moduleIndex, source, found)
175 } else {
176 match expected {
177 case Some(expectedType) => if hasHole(expectedType) {
178 inferBranchExpression(
179 service,
180 state,
181 moduleIndex,
182 source,
183 found,
184 contextual,
185 )
186 } else {
187 checkExpressionExpected(
188 service,
189 state,
190 moduleIndex,
191 source,
192 found,
193 expectedType,
194 )
195 }
196 case None => inferBranchExpression(
197 service,
198 state,
199 moduleIndex,
200 source,
201 found,
202 contextual,
203 )
204 }
205 }
206 if !terminating {
207 branches.push(branch)
208 }
209 }
210 case None => branches.push(primitive("unit"))
211 }
212 let expectedContext = match expected {
213 case Some(found) => !hasHole(found)
214 case None => false
215 }
216 joinedTypes(state, branches, !(contextual || expectedContext))
219function inferMatchExpression(
220 service: CheckerService,
221 state: CheckerState,
222 moduleIndex: int,
223 source: Bytes,
224 node: SyntaxNode,
225) -> TypeValue {
226 let contextual = hasLiteralJoinContext(state, node)
227 let scrutinee = match child(node, "scrutinee") {
228 case Some(found) => inferExpression(service, state, moduleIndex, source, found)
229 case None => emptyType("unknown")
230 }
231 let mut branches: Array<TypeValue> = []
232 let mut coverage = emptyPatternCoverage()
233 for foundCase in children(node, "cases") {
234 let mut caseCoverage = emptyPatternCoverage()
235 match child(foundCase, "pattern") {
236 case Some(pattern) => {
237 caseCoverage = patternBindingTypeAt(
238 service,
239 state,
240 moduleIndex,
241 source,
242 pattern,
243 scrutinee,
244 false,
245 true,
246 )
247 }
248 case None => ()
249 }
250 match child(foundCase, "guard") {
251 case Some(guard) => {
252 let _ = checkExpressionExpected(
253 service,
254 state,
255 moduleIndex,
256 source,
257 guard,
258 primitive("bool"),
259 )
260 }
261 case None => {
262 coverage = mergePatternCoverage(coverage, caseCoverage)
263 }
264 }
265 match child(foundCase, "body") {
266 case Some(body) => if body.kind == "match-case-return" {
267 checkReturn(service, state, moduleIndex, source, body)
268 } else {
269 let terminating = expressionTerminates(body)
270 let branch = if terminating {
271 inferExpression(service, state, moduleIndex, source, body)
272 } else {
273 match currentExpectedExpression(state, node) {
274 case Some(expected) => checkExpressionExpected(
275 service,
276 state,
277 moduleIndex,
278 source,
279 body,
280 expected,
281 )
282 case None => inferBranchExpression(
283 service,
284 state,
285 moduleIndex,
286 source,
287 body,
288 contextual,
289 )
290 }
291 }
292 if !terminating {
293 branches.push(branch)
294 }
295 }
296 case None => ()
297 }
298 }
299 let gap = coverageGap(state, moduleIndex, coverage, scrutinee)
300 if gap.decidable && gap.missing.length > 0 {
301 addDiagnostic(
302 state,
303 moduleIndex,
304 "TPZ5021",
305 "non-exhaustive match: missing {stringJoin(gap.missing, ", ")}",
306 "",
307 node,
308 )
309 }
310 let expectedContext = match currentExpectedExpression(state, node) {
311 case Some(expected) => !hasHole(expected)
312 case None => false
313 }
314 joinedTypes(state, branches, !(contextual || expectedContext))
317function currentLoopFloor(state: CheckerState) -> int {
318 if state.loopFloors.length == 0 {
319 return 0
320 }
321 match state.loopFloors.get(state.loopFloors.length - 1) {
322 case Some(found) => found
323 case None => 0
324 }
327function statementForExpression(
328 state: CheckerState,
329 node: SyntaxNode,
330) -> bool {
331 if state.statementExpressionContexts.length == 0 {
332 return false
333 }
334 match state.statementExpressionContexts.get(
335 state.statementExpressionContexts.length - 1,
336 ) {
337 case Some(lo) => lo == node.lo
338 case None => false
339 }
342function resolveLoopTarget(
343 state: CheckerState,
344 moduleIndex: int,
345 source: Bytes,
346 node: SyntaxNode,
347 keyword: string,
348) -> int {
349 let floor = currentLoopFloor(state)
350 match child(node, "label") {
351 case Some(label) => {
352 let name = text(source, label)
353 let mut index = state.loopFrames.length - 1
354 while index >= floor {
355 match state.loopFrames.get(index) {
356 case Some(frame) => if frame.label == name {
357 return index
358 }
359 case None => ()
360 }
361 index -= 1
362 }
363 addDiagnostic(
364 state,
365 moduleIndex,
366 "TPZ5720",
367 "no loop labeled `'{name}` in scope",
368 "",
369 node,
370 )
371 -1
372 }
373 case None => if state.loopFrames.length <= floor {
374 addDiagnostic(
375 state,
376 moduleIndex,
377 "TPZ5001",
378 "`{keyword}` outside a loop",
379 "",
380 node,
381 )
382 -1
383 } else {
384 let index = state.loopFrames.length - 1
385 match state.loopFrames.get(index) {
386 case Some(frame) => if frame.bareTarget {
387 index
388 } else {
389 addDiagnostic(
390 state,
391 moduleIndex,
392 "TPZ5001",
393 "bare `{keyword}` cannot target {frame.bareError}",
394 "",
395 node,
396 )
397 -1
398 }
399 case None => -1
400 }
401 }
402 }
405export function checkLoopExitStatement(
406 service: CheckerService,
407 state: CheckerState,
408 moduleIndex: int,
409 source: Bytes,
410 node: SyntaxNode,
411) -> () {
412 let keyword = if node.kind == "statement/break" {
413 "break"
414 } else {
415 "continue"
416 }
417 let targetIndex = resolveLoopTarget(
418 state,
419 moduleIndex,
420 source,
421 node,
422 keyword,
423 )
424 if keyword == "continue" {
425 return ()
426 }
427 let expected = if targetIndex >= 0 {
428 match state.loopFrames.get(targetIndex) {
429 case Some(frame) => frame.expected
430 case None => None
431 }
432 } else {
433 None
434 }
435 let valueType = match child(node, "value") {
436 case Some(value) => match expected {
437 case Some(expectedType) => {
438 let inferred = inferExpressionWithExpected(
439 service,
440 state,
441 moduleIndex,
442 source,
443 value,
444 expectedType,
445 false,
446 )
447 refineExpressionToExpected(
448 state,
449 moduleIndex,
450 value,
451 inferred,
452 expectedType,
453 )
454 }
455 case None => inferExpression(service, state, moduleIndex, source, value)
456 }
457 case None => primitive("unit")
458 }
459 if targetIndex < 0 {
460 return ()
461 }
462 match state.loopFrames.get(targetIndex) {
463 case Some(frame) => if frame.valueLoop {
464 let mut breaks = frame.breaks
465 if frame.expected == None && breaks.length > 0 {
466 match breaks.get(0) {
467 case Some(first) => {
468 let earlier = widen(first)
469 let current = widen(valueType)
470 if !hasHole(earlier) && !hasHole(current) &&
471 !assignable(earlier, current) &&
472 !assignable(current, earlier) {
473 addDiagnostic(
474 state,
475 moduleIndex,
476 "TPZ5721",
477 "`break` values targeting this loop disagree — earlier `break` is `{typeText(earlier)}`, this is `{typeText(current)}` (all break values must have the same type)",
478 "",
479 node,
480 )
481 }
482 }
483 case None => ()
484 }
485 }
486 breaks.push(valueType)
487 let mut frames = state.loopFrames
488 frames[targetIndex] = LoopFrame { ...frame, breaks: breaks }
489 }
490 case Some(_) => ()
491 case None => ()
492 }
493 ()
496function inferLoopExpression(
497 service: CheckerService,
498 state: CheckerState,
499 moduleIndex: int,
500 source: Bytes,
501 node: SyntaxNode,
502) -> TypeValue {
503 let expected = currentExpectedExpression(state, node)
504 let frame = LoopFrame {
505 label: match child(node, "label") {
506 case Some(label) => text(source, label)
507 case None => ""
508 },
509 valueLoop: true,
510 bareTarget: true,
511 bareError: "",
512 expected: expected,
513 breaks: [],
514 }
515 let mut frames = state.loopFrames
516 frames.push(frame)
517 match child(node, "body") {
518 case Some(body) => {
519 let _ = serviceCheckBlock(
520 service,
521 state,
522 moduleIndex,
523 source,
524 body,
525 )
526 }
527 case None => ()
528 }
529 let completed = match frames.get(frames.length - 1) {
530 case Some(found) => found
531 case None => frame
532 }
533 frames.pop()
534 if completed.breaks.length == 0 {
535 primitive("unit")
536 } else {
537 match expected {
538 case Some(_) => unionType(completed.breaks)
539 case None => widenedUnion(completed.breaks)
540 }
541 }
544function inferForExpression(
545 service: CheckerService,
546 state: CheckerState,
547 moduleIndex: int,
548 source: Bytes,
549 node: SyntaxNode,
550) -> TypeValue {
551 let statementPosition = statementForExpression(state, node)
552 let iteratorNode = child(node, "iterator")
553 let iterator = match iteratorNode {
554 case Some(found) => inferExpression(service, state, moduleIndex, source, found)
555 case None => emptyType("unknown")
556 }
557 match child(node, "pattern") {
558 case Some(pattern) => patternBindingType(
559 service,
560 state,
561 moduleIndex,
562 source,
563 pattern,
564 checkedIterableElement(
565 state,
566 moduleIndex,
567 match iteratorNode {
568 case Some(found) => found
569 case None => node
570 },
571 iterator,
572 ),
573 false,
574 )
575 case None => ()
576 }
577 let mut frames = state.loopFrames
578 frames.push(LoopFrame {
579 label: "",
580 valueLoop: false,
581 bareTarget: statementPosition,
582 bareError: "a value-collecting `for` (§5)",
583 expected: None,
584 breaks: [],
585 })
586 let bodyType = match child(node, "body") {
587 case Some(body) => serviceCheckBlock(
588 service,
589 state,
590 moduleIndex,
591 source,
592 body,
593 )
594 case None => primitive("unit")
595 }
596 frames.pop()
597 if statementPosition {
598 primitive("unit")
599 } else {
600 constructor("Array", [widen(bodyType)])
601 }
604function inferConcurrentExpression(
605 service: CheckerService,
606 state: CheckerState,
607 moduleIndex: int,
608 source: Bytes,
609 node: SyntaxNode,
610) -> TypeValue {
611 match child(node, "timeout") {
612 case Some(found) => {
613 if found.kind == "expression/duration" &&
614 !durationFitsMilliseconds(found) {
615 addDiagnostic(
616 state,
617 moduleIndex,
618 "TPZ5001",
619 "`concurrent` timeout duration must fit in u64 milliseconds (§15)",
620 "",
621 found,
622 )
623 }
624 let timeoutType = inferExpression(service, state, moduleIndex, source, found)
625 if timeoutType.kind == "unknown" {
626 addNode(state, moduleIndex, "expression", found, timeoutType)
627 }
628 }
629 case None => ()
630 }
631 let mut names: Array<string> = []
632 let mut values: Array<TypeValue> = []
633 for arm in children(node, "arms") {
634 let nameNode = match child(arm, "name") {
635 case Some(found) => found
636 case None => arm
637 }
638 let name = text(source, nameNode)
639 let mut duplicate = false
640 for existing in names {
641 if existing == name {
642 duplicate = true
643 }
644 }
645 if duplicate {
646 addDiagnostic(
647 state,
648 moduleIndex,
649 "TPZ5008",
650 "duplicate concurrent arm `{name}`",
651 "",
652 nameNode,
653 )
654 }
655 let value = match child(arm, "value") {
656 case Some(found) => widen(inferExpression(
657 service,
658 state,
659 moduleIndex,
660 source,
661 found,
662 ))
663 case None => emptyType("unknown")
664 }
665 names.push(name)
666 values.push(value)
667 }
668 let record = structural(names, values)
669 match child(node, "else") {
670 case Some(found) => {
671 if hasHole(record) || blockTerminates(found) {
672 let _ = serviceCheckBlock(service, state, moduleIndex, source, found)
673 } else {
674 let _ = serviceCheckBlockExpected(service, state, moduleIndex, source, found, record)
675 }
676 }
677 case None => ()
678 }
679 if hasHole(record) {
680 emptyType("unknown")
681 } else {
682 record
683 }
686function checkerByteAt(bytes: Bytes, index: int) -> int {
687 match bytes.get(index) {
688 case Some(value) => value
689 case None => -1
690 }
693function durationFitsMilliseconds(node: SyntaxNode) -> bool {
694 let maximum = match attribute(node, "unit") {
695 case "ms" => MAX_DURATION_MILLISECONDS
696 case "s" => MAX_DURATION_SECONDS
697 case "m" => MAX_DURATION_MINUTES
698 case _ => return false
699 }
700 let value = Bytes.encodeUtf8(attribute(node, "valueDecimal"))
701 let limit = Bytes.encodeUtf8(maximum)
702 let mut start = 0
703 while start + 1 < value.length() && checkerByteAt(value, start) == 48 {
704 start += 1
705 }
706 let digits = value.length() - start
707 if digits < limit.length() {
708 return true
709 }
710 if digits > limit.length() {
711 return false
712 }
713 let mut index = 0
714 while index < digits {
715 let actual = checkerByteAt(value, start + index)
716 let allowed = checkerByteAt(limit, index)
717 if actual < allowed {
718 return true
719 }
720 if actual > allowed {
721 return false
722 }
723 index += 1
724 }
725 true
728function constantZero(node: SyntaxNode) -> bool {
729 if node.kind == "expression/integer" {
730 return toInt(attribute(node, "valueDecimal")) == Some(0)
731 }
732 if node.kind == "expression/parenthesized" ||
733 node.kind == "expression/unary" &&
734 (attribute(node, "operator") == "plus" || attribute(node, "operator") == "minus") {
735 return match child(node, "operand") {
736 case Some(found) => constantZero(found)
737 case None => false
738 }
739 }
740 false
743function inferRangeExpression(
744 service: CheckerService,
745 state: CheckerState,
746 moduleIndex: int,
747 source: Bytes,
748 node: SyntaxNode,
749) -> TypeValue {
750 for fieldName in ["low", "high", "step"] {
751 match child(node, fieldName) {
752 case Some(found) => {
753 let endpoint = widen(inferExpression(
754 service,
755 state,
756 moduleIndex,
757 source,
758 found,
759 ))
760 if !hasHole(endpoint) &&
761 (endpoint.kind != "primitive" || endpoint.name != "int") {
762 addDiagnostic(
763 state,
764 moduleIndex,
765 "TPZ5001",
766 if fieldName == "step" {
767 "a range step is `int`, found `{typeText(endpoint)}`"
768 } else {
769 "range endpoints are `int`, found `{typeText(endpoint)}`"
770 },
771 "",
772 found,
773 )
774 }
775 if fieldName == "step" && constantZero(found) {
776 addDiagnostic(state, moduleIndex, "TPZ5001",
777 "the range step must not be zero (§10)", "", found)
778 }
779 }
780 case None => ()
781 }
782 }
783 constructor("Range", [primitive("int")])
786function sameBinaryType(
787 left: TypeValue,
788 right: TypeValue,
789 kind: string,
790 name: string,
791) -> bool {
792 left.kind == kind && right.kind == kind &&
793 left.name == name && right.name == name
796export function inferBinaryExpression(
797 service: CheckerService,
798 state: CheckerState,
799 moduleIndex: int,
800 source: Bytes,
801 node: SyntaxNode,
802) -> TypeValue {
803 let leftNode = child(node, "left")
804 let left = match leftNode {
805 case Some(found) => inferExpression(service, state, moduleIndex, source, found)
806 case None => emptyType("unknown")
807 }
808 let operator = attribute(node, "operator")
809 if operator == "coalesce" {
810 let optional = if left.kind == "constructor" && left.name == "Option" {
811 left.arguments.get(0)
812 } else if left.kind == "union" {
813 let mut nullable = false
814 let mut remaining: Array<TypeValue> = []
815 for member in left.members {
816 if member.kind == "literal" && member.name == "null" {
817 nullable = true
818 } else {
819 remaining.push(member)
820 }
821 }
822 if nullable && remaining.length > 0 {
823 Some(unionType(remaining))
824 } else {
825 None
826 }
827 } else {
828 None
829 }
830 return match optional {
831 case Some(inner) => {
832 match child(node, "right") {
833 case Some(found) => {
834 let _ = checkExpressionExpected(
835 service,
836 state,
837 moduleIndex,
838 source,
839 found,
840 inner,
841 )
842 }
843 case None => ()
844 }
845 inner
846 }
847 case None => {
848 match child(node, "right") {
849 case Some(found) => { let _ = inferExpression(service, state, moduleIndex, source, found) }
850 case None => ()
851 }
852 if !hasHole(left) {
853 addDiagnostic(
854 state,
855 moduleIndex,
856 "TPZ5001",
857 "`??` needs an Option or nullable left operand, found `{typeText(left)}`",
858 "",
859 node,
860 )
861 }
862 emptyType("unknown")
863 }
864 }
865 }
866 let right = match child(node, "right") {
867 case Some(found) => inferExpression(service, state, moduleIndex, source, found)
868 case None => emptyType("unknown")
869 }
870 let mut lhs = widen(left)
871 let mut rhs = widen(right)
872 if operator == "equal" || operator == "not-equal" {
873 if hasPartialHole(lhs) && !hasHole(rhs) {
874 lhs = solvePartialTypeAgainst(state, lhs, rhs)
875 } else if hasPartialHole(rhs) && !hasHole(lhs) {
876 rhs = solvePartialTypeAgainst(state, rhs, lhs)
877 }
878 }
879 if hasHole(lhs) || hasHole(rhs) {
880 return emptyType("unknown")
881 }
882 match operator {
883 case "power" | "divide" => if sameBinaryType(
884 lhs,
885 rhs,
886 "primitive",
887 "int",
888 ) {
889 primitive("int")
890 } else if sameBinaryType(lhs, rhs, "primitive", "float") {
891 primitive("float")
892 } else {
893 addDiagnostic(
894 state,
895 moduleIndex,
896 "TPZ5001",
897 "arithmetic needs matching numeric operands, found `{typeText(lhs)}` and `{typeText(rhs)}`",
898 "",
899 node,
900 )
901 emptyType("unknown")
902 }
903 case "multiply" | "subtract" => if sameBinaryType(
904 lhs,
905 rhs,
906 "primitive",
907 "int",
908 ) {
909 primitive("int")
910 } else if sameBinaryType(lhs, rhs, "primitive", "float") {
911 primitive("float")
912 } else if sameBinaryType(lhs, rhs, "big-int", "") {
913 emptyType("big-int")
914 } else if sameBinaryType(lhs, rhs, "decimal", "") {
915 emptyType("decimal")
916 } else {
917 addDiagnostic(
918 state,
919 moduleIndex,
920 "TPZ5001",
921 "arithmetic needs matching numeric operands, found `{typeText(lhs)}` and `{typeText(rhs)}`",
922 "",
923 node,
924 )
925 emptyType("unknown")
926 }
927 case "remainder" => if sameBinaryType(lhs, rhs, "primitive", "int") {
928 primitive("int")
929 } else {
930 addDiagnostic(
931 state,
932 moduleIndex,
933 "TPZ5001",
934 "`%` needs int operands in v5.2, found `{typeText(lhs)}` and `{typeText(rhs)}`",
935 "",
936 node,
937 )
938 emptyType("unknown")
939 }
940 case "add" => if sameBinaryType(lhs, rhs, "primitive", "int") {
941 primitive("int")
942 } else if sameBinaryType(lhs, rhs, "primitive", "float") {
943 primitive("float")
944 } else if sameBinaryType(lhs, rhs, "primitive", "string") {
945 primitive("string")
946 } else if sameBinaryType(lhs, rhs, "big-int", "") {
947 emptyType("big-int")
948 } else if sameBinaryType(lhs, rhs, "decimal", "") {
949 emptyType("decimal")
950 } else {
951 addDiagnostic(
952 state,
953 moduleIndex,
954 "TPZ5001",
955 "`+` needs matching int, float, string, BigInt, or Decimal operands, found `{typeText(lhs)}` and `{typeText(rhs)}`",
956 "",
957 node,
958 )
959 emptyType("unknown")
960 }
961 case "less-than" | "less-or-equal" | "greater-than" |
962 "greater-or-equal" => {
963 let scalar = sameBinaryType(lhs, rhs, "primitive", "int") ||
964 sameBinaryType(lhs, rhs, "primitive", "float") ||
965 sameBinaryType(lhs, rhs, "primitive", "string") ||
966 sameBinaryType(lhs, rhs, "big-int", "") ||
967 sameBinaryType(lhs, rhs, "decimal", "")
968 let nominal = (assignable(lhs, rhs) || assignable(rhs, lhs)) &&
969 orderCapability(state, moduleIndex, lhs, []) == "accept" &&
970 orderCapability(state, moduleIndex, rhs, []) == "accept"
971 if scalar || nominal {
972 primitive("bool")
973 } else {
974 addDiagnostic(
975 state,
976 moduleIndex,
977 "TPZ5007",
978 "`{typeText(lhs)}` and `{typeText(rhs)}` are not ordered comparable",
979 "",
980 node,
981 )
982 emptyType("unknown")
983 }
984 }
985 case "equal" | "not-equal" => if !comparableType(
986 state,
987 moduleIndex,
988 lhs,
989 [],
990 ) || !comparableType(state, moduleIndex, rhs, []) {
991 addDiagnostic(
992 state,
993 moduleIndex,
994 "TPZ5007",
995 "`{typeText(lhs)}` and `{typeText(rhs)}` are not comparable values",
996 "",
997 node,
998 )
999 emptyType("unknown")
1000 } else if assignable(lhs, rhs) || assignable(rhs, lhs) {

SHA256 da2ff50431d982324e69abe277c266c6312319a1ce6acfce334e3f59d430e8ee