Appearance
| 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 |