Skip to content
← Back

src/syntax/process/null_safe_equality_gate.ghul

1
namespace Syntax.Process is
2
use Semantic.Types.Type
3
4
// Whether `a =~ b` gets its null checks written around the call.
5
//
6
// Each decline leaves the operands resolving exactly as they did
7
// before, so getting one wrong is a silent change of meaning rather
8
// than a compile failure: the call still builds and still answers,
9
// just from the wrong place.
10
class NULL_SAFE_EQUALITY_GATE is
11
// The operands have to include something absent to test. Which
12
// optional lowering each one uses does not matter here: the
13
// presence test and the payload are built per operand, so a
14
// reference optional and a `NULLABLE[T]` or `MAYBE[T]` reach
15
// the same shape.
16
accepts_operands(left_type: Type?, right_type: Type?) -> bool static is
17
if !left_type? \/ !right_type? then
18
return false
19
fi
20
21
if left_type.is_error \/ right_type.is_error then
22
return false
23
fi
24
25
return left_type.is_optional \/ right_type.is_optional
26
si
27
28
// The operator has to be one the lowering can call at all:
29
// reached with the left operand as its receiver, and taking
30
// one parameter. An enum's `=~` qualifies although it reports
31
// itself as neither static nor an instance member - it is an
32
// innate method, and its call takes a receiver like any other.
33
// One hidden from operator resolution is not what the
34
// expression calls in the first place, so it is left alone
35
// entirely.
36
//
37
// It also has to answer bool. Nothing requires `=~` to, the way
38
// `<>` is required to answer int, and the null arms have no
39
// sensible value to produce for an operator that answers
40
// something else - so rather than reinterpret its result, leave
41
// the comparison to resolve as it did.
42
accepts_operator(function: Semantic.Symbols.Function?, bool_type: Type?) -> bool static is
43
if !function? \/ !bool_type? then
44
return false
45
fi
46
47
if
48
!(function.is_instance \/ isa Semantic.Symbols.INNATE_METHOD(function)) \/
49
function.is_hidden_from_operator_resolution
50
then
51
return false
52
fi
53
54
if function.arguments.count != 1 then
55
return false
56
fi
57
58
let return_type = function.return_type
59
60
return return_type? /\ return_type.matches(bool_type)
61
si
62
63
// A global operator taking both operands as arguments, for the
64
// types the operands hold once presence is settled. Both
65
// parameters have to be non-optional: one that declares an
66
// optional parameter means to decide for an absent operand
67
// itself, and this lowering never hands it one, so it is left
68
// to resolve as written instead.
69
accepts_global_operator(function: Semantic.Symbols.Function?, bool_type: Type?) -> bool static is
70
if !function? \/ !bool_type? then
71
return false
72
fi
73
74
if function.is_instance \/ function.is_hidden_from_operator_resolution then
75
return false
76
fi
77
78
if function.arguments.count != 2 then
79
return false
80
fi
81
82
if function.arguments[0].is_optional \/ function.arguments[1].is_optional then
83
return false
84
fi
85
86
let return_type = function.return_type
87
88
return return_type? /\ return_type.matches(bool_type)
89
si
90
91
// Whether the absent *argument* is answered here rather than by
92
// the operator. An operator declaring that parameter optional
93
// is written to answer for one itself, so it is handed the
94
// value and decides.
95
//
96
// The right operand is the operator's only parameter when it
97
// takes the left as a receiver, and its second when it takes
98
// both as arguments.
99
//
100
// The absent *receiver* is never its decision: there is nothing
101
// to dispatch on, so that arm is always answered here.
102
guards_argument(function: Semantic.Symbols.Function?) -> bool static is
103
if !function? \/ function.arguments.count < 1 \/ function.arguments.count > 2 then
104
return false
105
fi
106
107
let argument = function.arguments[function.arguments.count - 1]
108
109
return !argument.is_optional
110
si
111
si
112
si