Skip to content
← Back

src/syntax/process/pattern_checker.ghul

1
namespace Syntax.Process is
2
use Logging
3
4
use Semantic.Types.Type
5
6
// Checks an `if let` / `case`-arm pattern. The compositional
7
// primitive is `check_pattern`, which takes the pattern shape and
8
// the source/target types directly — so a caller that has the
9
// types in hand (a `case`-arm walker computing source from the
10
// scrutinee and target from the arm's ascription) does not need
11
// to synthesise a VARIABLE first. `check_binding` is the
12
// if-let-shaped convenience wrapper: walks a binding, extracts
13
// the relevant types from its initializer and delegates.
14
//
15
// The caller is responsible for the flow env before invoking,
16
// and for any guard / body walking after.
17
class PATTERN_CHECKER is
18
_logger: Logger
19
_build_flags: Compiler.GLOBAL_BUILD_FLAGS
20
_visitor: COMPILE_EXPRESSIONS
21
_flow: NARROWING_FLOW
22
23
init(
24
logger: Logger,
25
build_flags: Compiler.GLOBAL_BUILD_FLAGS,
26
visitor: COMPILE_EXPRESSIONS,
27
flow: NARROWING_FLOW
28
) is
29
super.init()
30
31
_logger = logger
32
_build_flags = build_flags
33
_visitor = visitor
34
_flow = flow
35
si
36
37
check_binding(binding: Trees.Variables.VARIABLE) is
38
binding.walk(_visitor)
39
40
let source_type: Type? mut = null
41
let target_type: Type? mut = null
42
let init = binding.initializer
43
44
if init? then
45
if isa Trees.Expressions.CAST(init) then
46
let cast_expr = cast Trees.Expressions.CAST(init)
47
48
if let cast_expr.right?, right.value? then
49
source_type = value.type
50
fi
51
52
if let cast_expr.type_expression?, type_expression.type? then
53
target_type = type
54
fi
55
elif let init.value? then
56
source_type = value.type
57
fi
58
fi
59
60
check_pattern(
61
binding.left,
62
source_type,
63
target_type,
64
binding.location,
65
!binding.left.has_intrinsic_refutability
66
)
67
si
68
69
// `bare_form_requires_refutability = true` matches `if let`'s
70
// contract: a bare-form binding (no `: T` ascription) needs a
71
// source that's refutable on its own — a reference type, or an
72
// option-shaped value type. A non-nullable value-type bare form
73
// has nothing for the `if let` to test, so it draws the
74
// `irrefutable-destructure` warning instead — the then-arm
75
// always runs, so a plain `let` says the same thing more
76
// plainly. Callers (`case`-when patterns) whose bare form is a
77
// non-narrowing destructure pass false.
78
check_pattern(
79
left: Trees.Variables.VariableLeft,
80
source_type: Type?,
81
target_type: Type?,
82
location: Source.LOCATION,
83
bare_form_requires_refutability: bool
84
) is
85
// Warn when a narrowing always succeeds — the source type
86
// is statically known to be (a subtype of) the target and
87
// isn't optional, so the test is redundant. Only fires
88
// when there's actually a target (an ascription); the bare
89
// form has no narrowing to be redundant.
90
if target_type? /\ source_type? /\ !_build_flags.no_warn_narrowing_always_succeeds then
91
let src = source_type
92
let tgt = target_type
93
94
if
95
src.is_settled /\ !src.is_type_variable /\ !src.is_sentinel /\ !src.is_error /\
96
tgt.is_settled /\ !tgt.is_type_variable /\ !tgt.is_sentinel /\ !tgt.is_error /\
97
!src.is_optional /\
98
tgt.is_assignable_from(src)
99
then
100
_logger.warn(
101
location,
102
"narrowing-always-succeeds",
103
"{src} is already {tgt}"
104
)
105
fi
106
fi
107
108
// The presence test is the `?` (has-value) operator. A
109
// reference type tests for null. A value type must be
110
// option-shaped — `T?`/NULLABLE[T] or any struct with
111
// `has_value` and `value` members; the binding then
112
// yields the unwrapped `.value`. A plain value type is
113
// always present and cannot be tested this way.
114
let effective_type = if target_type? then target_type else source_type fi
115
116
if should_emit_value_type_narrow_error(effective_type, target_type, source_type) then
117
_logger.error(
118
location,
119
"cannot narrow {effective_type!}"
120
)
121
122
// Error recovery: an impossible match still binds its
123
// names — typed ERROR — so the one diagnostic above is
124
// not followed by a cascade of spurious errors on uses
125
// of the binding within the then-arm.
126
for name in left.names! do
127
let symbol = _visitor.find(name)
128
129
if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then
130
symbol.define()
131
132
(cast Semantic.Types.SettableTyped(symbol)).set_type(Semantic.Types.ERROR())
133
fi
134
od
135
elif
136
should_emit_irrefutable_destructure_warning(effective_type, target_type, bare_form_requires_refutability) /\
137
!_build_flags.no_warn_irrefutable_destructure
138
then
139
// Unlike the ascribed narrow above, there's no target
140
// type to name and no unsafe cast underneath — the
141
// pattern just has no runtime test anywhere in its
142
// shape, so the then-arm always runs. The bound names
143
// keep the real types the earlier walk already gave
144
// them; there's nothing to recover from.
145
_logger.warn(
146
location,
147
"irrefutable-destructure",
148
"this destructure always matches",
149
location,
150
"help: use a plain let instead"
151
)
152
fi
153
154
if effective_type? /\ effective_type.is_value_type then
155
let value_member = effective_type.find_member("value")
156
157
let value_member_type = if value_member? then value_member.type else null fi
158
159
if value_member_type? /\ effective_type.find_member("has_value")? then
160
// Option-shape value type: the bound names take the
161
// unwrapped `.value` type, not the optional itself.
162
_visitor.set_symbol_type(left, value_member_type)
163
fi
164
fi
165
166
// The bound names hold a value throughout the then-arm —
167
// that is what `if let` establishes — so a dereference of
168
// one is not flagged.
169
for name in left.names! do
170
let symbol = _visitor.find(name)
171
172
if let variable = cast Semantic.Symbols.Variable?(symbol) then
173
_flow.mark_non_null(variable)
174
fi
175
od
176
si
177
178
// Gating for the "cannot narrow {T}" error. An ascription onto a
179
// value type that isn't option-shaped is a real test only when
180
// the source can hold something other than that value: an
181
// optional, or a reference that may hold it boxed. Over a source
182
// that is itself a plain value type there is nothing to decide.
183
should_emit_value_type_narrow_error(
184
effective_type: Type?,
185
target_type: Type?,
186
source_type: Type?
187
) -> bool static is
188
if !target_type? \/ !_is_non_option_shaped_value_type(effective_type) then
189
return false
190
fi
191
192
return !source_type? \/ _is_non_option_shaped_value_type(source_type)
193
si
194
195
// The type an ascribed pattern tests the source against at run
196
// time. A plain value type has no absent value to report a
197
// failed test with, so the test is made against its optional,
198
// and the bound names take the unwrapped value.
199
runtime_test_type(
200
target_type: Type,
201
source_type: Type?,
202
innate_symbol_lookup: Semantic.Lookups.InnateSymbolLookup
203
) -> Type static is
204
if is_lifted_test(target_type, source_type) then
205
return innate_symbol_lookup.get_optional_type(target_type)
206
fi
207
208
return target_type
209
si
210
211
// True when an ascribed pattern tests against the optional of a
212
// plain value type. The source is then read as it stands, so it
213
// must not be narrowed to the target before the test runs: the
214
// narrowed read is a different representation.
215
is_lifted_test(target_type: Type?, source_type: Type?) -> bool static =>
216
_is_non_option_shaped_value_type(target_type) /\ !_is_non_option_shaped_value_type(source_type)
217
218
// Gating for the "irrefutable destructure" warning. Fires for
219
// a *bare* form (no `: T` ascription anywhere pinning a
220
// target) whose caller demands refutability — `if let`'s
221
// contract — over a value type that isn't option-shaped: with
222
// no ascription to reject the value under, and nothing in the
223
// pattern shape itself refutable (`bare_form_requires_
224
// refutability` is only ever true when
225
// `!left.has_intrinsic_refutability`), the then-arm always
226
// runs. `case`-when patterns pass
227
// `bare_form_requires_refutability = false`, so a bare
228
// destructure of a non-nullable value-type tuple there stays
229
// a silently-accepted arm rather than drawing this warning —
230
// matching how it's already treated. Extracted as a static
231
// helper so the gate is unit-testable in isolation.
232
should_emit_irrefutable_destructure_warning(
233
effective_type: Type?,
234
target_type: Type?,
235
bare_form_requires_refutability: bool
236
) -> bool static is
237
if target_type? then
238
return false
239
fi
240
241
if !_is_non_option_shaped_value_type(effective_type) then
242
return false
243
fi
244
245
return bare_form_requires_refutability
246
si
247
248
_is_non_option_shaped_value_type(effective_type: Type?) -> bool static is
249
if !effective_type? then
250
return false
251
fi
252
253
if !effective_type.is_value_type then
254
return false
255
fi
256
257
if effective_type.find_member("has_value")? /\ effective_type.find_member("value")? then
258
return false
259
fi
260
261
return true
262
si
263
si
264
si