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 cast_expr.type_expression.type? then
53
target_type = cast_expr.type_expression.type;
54
fi
55
elif let init.value? then
56
source_type = value.type;
57
fi
58
fi
59
60
check_pattern(binding.left, source_type, target_type, binding.location, true);
61
si
62
63
// `bare_form_requires_refutability = true` matches `if let`'s
64
// contract: a bare-form binding (no `: T` ascription) needs a
65
// source that's refutable on its own — a reference type, or an
66
// option-shaped value type. A non-nullable value-type bare form
67
// is meaningless and rejected with an error plus ERROR-typed
68
// recovery on the bound names. Callers (`case`-when patterns)
69
// whose bare form is a non-narrowing destructure pass false.
70
check_pattern(
71
left: Trees.Variables.VariableLeft,
72
source_type: Type?,
73
target_type: Type?,
74
location: Source.LOCATION,
75
bare_form_requires_refutability: bool
76
) is
77
// Warn when a narrowing always succeeds — the source type
78
// is statically known to be (a subtype of) the target and
79
// isn't optional, so the test is redundant. Only fires
80
// when there's actually a target (an ascription); the bare
81
// form has no narrowing to be redundant.
82
if target_type? /\ source_type? /\ !_build_flags.no_warn_narrowing_always_succeeds then
83
let src = source_type;
84
let tgt = target_type;
85
86
if
87
src.is_settled /\ !src.is_type_variable /\ !src.is_sentinel /\ !src.is_error /\
88
tgt.is_settled /\ !tgt.is_type_variable /\ !tgt.is_sentinel /\ !tgt.is_error /\
89
!src.is_optional /\
90
tgt.is_assignable_from(src)
91
then
92
_logger.warn(
93
location,
94
"narrowing-always-succeeds",
95
"{src} is already {tgt}"
96
);
97
fi
98
fi
99
100
// The presence test is the `?` (has-value) operator. A
101
// reference type tests for null. A value type must be
102
// option-shaped — `T?`/NULLABLE[T] or any struct with
103
// `has_value` and `value` members; the binding then
104
// yields the unwrapped `.value`. A plain value type is
105
// always present and cannot be tested this way.
106
let effective_type = if target_type? then target_type else source_type fi;
107
108
if should_emit_value_type_narrow_error(effective_type, target_type, bare_form_requires_refutability) then
109
_logger.error(
110
location,
111
"cannot narrow {effective_type!}"
112
);
113
114
// Error recovery: an impossible match still binds its
115
// names — typed ERROR — so the one diagnostic above is
116
// not followed by a cascade of spurious errors on uses
117
// of the binding within the then-arm.
118
for name in left.names! do
119
let symbol = _visitor.find(name);
120
121
if symbol? /\ isa Semantic.Types.SettableTyped(symbol) then
122
symbol.define();
123
124
(cast Semantic.Types.SettableTyped(symbol)).set_type(Semantic.Types.ERROR());
125
fi
126
od
127
elif effective_type? /\ effective_type.is_value_type then
128
let value_member = effective_type.find_member("value");
129
130
let value_member_type = if value_member? then value_member.type else null fi;
131
132
if value_member_type? /\ effective_type.find_member("has_value")? then
133
// Option-shape value type: the bound names take the
134
// unwrapped `.value` type, not the optional itself.
135
_visitor.set_symbol_type(left, value_member_type);
136
fi
137
fi
138
139
// The bound names hold a value throughout the then-arm —
140
// that is what `if let` establishes — so a dereference of
141
// one is not flagged.
142
for name in left.names! do
143
let symbol = _visitor.find(name);
144
145
if let variable = cast Semantic.Symbols.Variable?(symbol) then
146
_flow.mark_non_null(variable);
147
fi
148
od
149
si
150
151
// Gating for the "cannot narrow {T}" error. The error fires
152
// when the source/target combination is a value-type narrow
153
// whose presence test makes no sense — the type is value-type
154
// and not option-shaped — AND the caller cares: either an
155
// explicit `: T` ascription is present (`target_type?`), or
156
// the caller demands the bare form be refutable on its own
157
// (`if let`'s contract). `case`-when patterns pass
158
// `bare_form_requires_refutability = false` so a bare
159
// destructure of a non-nullable value-type tuple is accepted
160
// as a destructure rather than rejected as a meaningless
161
// narrow. Extracted as a static helper so the gate is
162
// unit-testable in isolation.
163
should_emit_value_type_narrow_error(
164
effective_type: Type?,
165
target_type: Type?,
166
bare_form_requires_refutability: bool
167
) -> bool static is
168
if !effective_type? then
169
return false;
170
fi
171
172
if !effective_type.is_value_type then
173
return false;
174
fi
175
176
if effective_type.find_member("has_value")? /\ effective_type.find_member("value")? then
177
return false;
178
fi
179
180
return target_type? \/ bare_form_requires_refutability;
181
si
182
si
183
si