Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Exception | |
| 3 | ||
| 4 | use IO.Std | |
| 5 | ||
| 6 | use Logging | |
| 7 | use Source | |
| 8 | ||
| 9 | use IR.Values | |
| 10 | use IR.VALUE_CONVERTER | |
| 11 | use IR.VALUE_BOXER | |
| 12 | ||
| 13 | use Semantic.LEAST_UPPER_BOUND_MAP | |
| 14 | use Semantic.Types.Type | |
| 15 | ||
| 16 | use Syntax.Trees.Definitions.PRAGMA | |
| 17 | ||
| 18 | use Ghul.Pipes | |
| 19 | ||
| 20 | ||
| 21 | // Receiver-presence checks, narrowing targets, access paths, pure-slot checks and call bookkeeping. | |
| 22 | partial COMPILE_EXPRESSIONS is | |
| 23 | // If `expr` is an unqualified identifier resolving (in the | |
| 24 | // current scope) to a narrowing subject, return that symbol; | |
| 25 | // null otherwise. Subjects are local variables, fields, and | |
| 26 | // properties whose getter is proven store-free — for those, | |
| 27 | // re-reading under an unchanged heap repeats the same | |
| 28 | // presence and dynamic-type answers, and the flow transfers | |
| 29 | // (on_call / on_heap_store) forget them the moment the heap | |
| 30 | // may have changed. A property with an unproven getter never | |
| 31 | // narrows. Qualified and member-access targets are deferred. | |
| 32 | // Reject a dereference through an optional receiver not | |
| 33 | // proven to hold a value here. Optionality is one concept | |
| 34 | // regardless of representation - a reference `T?`, a value-type | |
| 35 | // NULLABLE[T], or an unconstrained MAYBE[T] all answer is_optional | |
| 36 | // and are treated alike. Member access, indexing and `for ... in` | |
| 37 | // all reach the receiver's members off its non-optional shape, so | |
| 38 | // an un-narrowed optional receiver may be absent at the | |
| 39 | // dereference. A receiver proven present by flow narrowing is | |
| 40 | // safe. The same standing as "`'T?`' is not assignable to `'T?`'": | |
| 41 | // the same fact about the same static type, so no slug and | |
| 42 | // no suppression - narrow first (`!`, `if let`, `?.`) or copy | |
| 43 | // the value into a local. | |
| 44 | check_receiver_present(receiver: Trees.Expressions.Expression) is | |
| 45 | if !receiver_may_be_absent(receiver) then | |
| 46 | return | |
| 47 | fi | |
| 48 | ||
| 49 | let target = try_get_narrowing_target(receiver) | |
| 50 | let subject = if target? then target.name else "receiver" fi | |
| 51 | ||
| 52 | // Where the value was proven present earlier and a call | |
| 53 | // ended that, the message is the ordinary one with the call | |
| 54 | // it ended at. | |
| 55 | let dropped = DROPPED_NARROWING.for_presence(target, try_build_access_path(receiver), receiver.location) | |
| 56 | ||
| 57 | if let kill = dropped then | |
| 58 | _logger.error( | |
| 59 | receiver.location, | |
| 60 | "{subject} may not hold a value here", | |
| 61 | kill.crossing.location, | |
| 62 | DROPPED_NARROWING.message(kill) | |
| 63 | ) | |
| 64 | ||
| 65 | return | |
| 66 | fi | |
| 67 | ||
| 68 | _logger.error(receiver.location, "{subject} may not hold a value here") | |
| 69 | si | |
| 70 | ||
| 71 | // Whether a receiver could hold no value at this point: its type | |
| 72 | // is optional and flow narrowing has not proven it present. The | |
| 73 | // question behind the rejection above, asked separately because | |
| 74 | // operator resolution asks it too - an operator whose left | |
| 75 | // operand may be absent does not resolve at all, which is a | |
| 76 | // typing rule rather than advice. | |
| 77 | receiver_may_be_absent(receiver: Trees.Expressions.Expression) -> bool is | |
| 78 | let value = receiver.value | |
| 79 | ||
| 80 | if !value? then | |
| 81 | return false | |
| 82 | fi | |
| 83 | ||
| 84 | let type = value.type | |
| 85 | ||
| 86 | if !type? \/ !type.is_optional then | |
| 87 | return false | |
| 88 | fi | |
| 89 | ||
| 90 | let target = try_get_narrowing_target(receiver) | |
| 91 | ||
| 92 | return !(target? /\ _flow.is_non_null(target)) | |
| 93 | si | |
| 94 | ||
| 95 | // The member an assignment writes, for the definite-assignment | |
| 96 | // domain alone, written either `self.x = ...` or bare `x = ...`. | |
| 97 | // | |
| 98 | // The narrowing target answers a different question and declines | |
| 99 | // both of these. It declines `self.x = x` where a parameter | |
| 100 | // shadows the member, because a presence fact proven of the | |
| 101 | // parameter must not be borrowed by the member - but the write | |
| 102 | // happens whatever is shadowing, and the constructor idiom | |
| 103 | // `init(x: T) is self.x = x; si` depends on that. And it declines | |
| 104 | // a property whose getter is not proven store-free, because a | |
| 105 | // getter that stores could invalidate what was narrowed - but | |
| 106 | // "was it written" is settled by the write itself, and the getter | |
| 107 | // of an auto-property on an `open` class is exactly the case that | |
| 108 | // cannot be proven. | |
| 109 | try_get_member_assigned(expr: Trees.Expressions.Expression?) -> Semantic.Symbols.Symbol? is | |
| 110 | if !expr? then | |
| 111 | return null | |
| 112 | fi | |
| 113 | ||
| 114 | let context = current_instance_context | |
| 115 | ||
| 116 | if !context? then | |
| 117 | return null | |
| 118 | fi | |
| 119 | ||
| 120 | if let self_member: Trees.Expressions.MEMBER = expr /\ isa Trees.Expressions.SELF(self_member.left) then | |
| 121 | if self_member.is_coalesce \/ self_member.identifier.is_qualified then | |
| 122 | return null | |
| 123 | fi | |
| 124 | ||
| 125 | return context.find_direct(self_member.identifier.name) | |
| 126 | fi | |
| 127 | ||
| 128 | if let identifier: Trees.Expressions.IDENTIFIER = expr then | |
| 129 | if identifier.identifier.is_qualified then | |
| 130 | return null | |
| 131 | fi | |
| 132 | ||
| 133 | // Resolved through the enclosing scope rather than | |
| 134 | // straight off the instance, so a local or parameter of | |
| 135 | // the same name is seen to shadow the member - a bare | |
| 136 | // `x = ...` then writes the local and no member is | |
| 137 | // assigned. `try_find` rather than `find` because this is | |
| 138 | // speculative: `find` reports an unresolved name, and the | |
| 139 | // real resolution of this expression reports it already. | |
| 140 | let symbol = try_find(identifier.identifier) | |
| 141 | ||
| 142 | if symbol? /\ symbol == context.find_direct(identifier.identifier.name) then | |
| 143 | return symbol | |
| 144 | fi | |
| 145 | fi | |
| 146 | ||
| 147 | return null | |
| 148 | si | |
| 149 | ||
| 150 | try_get_narrowing_target(expr: Trees.Expressions.Expression?) -> Semantic.Symbols.Symbol? is | |
| 151 | if !expr? then | |
| 152 | return null | |
| 153 | fi | |
| 154 | ||
| 155 | // `self` narrows like a local, keyed on its instance-context | |
| 156 | // symbol. It can't be reassigned and an object's concrete type | |
| 157 | // is fixed for its lifetime, so a narrowing on `self` is never | |
| 158 | // killed by a call - it is sounder to narrow than a local. | |
| 159 | if isa Trees.Expressions.SELF(expr) then | |
| 160 | return current_instance_context | |
| 161 | fi | |
| 162 | ||
| 163 | // `self.x` usually names the same location the bare `x` | |
| 164 | // does, and where it does the two spellings share one fact | |
| 165 | // rather than keying their own — a guard written one way is | |
| 166 | // then seen by a use written the other. | |
| 167 | // | |
| 168 | // Only where it does. The bare form resolves by lexical | |
| 169 | // scope lookup, so a local or parameter of the same name | |
| 170 | // shadows the member and the two spellings name different | |
| 171 | // locations — the `init(name: string?)` that assigns | |
| 172 | // `self.name = name` is the everyday case. Sharing a fact | |
| 173 | // across that pair would narrow the member on presence | |
| 174 | // proved of the parameter. So the member is looked up on | |
| 175 | // the enclosing instance and the two must agree by | |
| 176 | // identity; where they don't, neither spelling may borrow | |
| 177 | // the other's fact and `self.x` narrows on nothing. | |
| 178 | if let self_member: Trees.Expressions.MEMBER = expr /\ isa Trees.Expressions.SELF(self_member.left) then | |
| 179 | if self_member.is_coalesce \/ self_member.identifier.is_qualified then | |
| 180 | return null | |
| 181 | fi | |
| 182 | ||
| 183 | let context = current_instance_context | |
| 184 | ||
| 185 | if !context? then | |
| 186 | return null | |
| 187 | fi | |
| 188 | ||
| 189 | let target = _narrowing_target_named(self_member.identifier) | |
| 190 | ||
| 191 | if !target? \/ target != context.find_direct(self_member.identifier.name) then | |
| 192 | return null | |
| 193 | fi | |
| 194 | ||
| 195 | return target | |
| 196 | fi | |
| 197 | ||
| 198 | if !isa Trees.Expressions.IDENTIFIER(expr) then | |
| 199 | return null | |
| 200 | fi | |
| 201 | ||
| 202 | return _narrowing_target_named(expr.identifier) | |
| 203 | si | |
| 204 | ||
| 205 | // The symbol an unqualified name narrows on, or null when it | |
| 206 | // names nothing the flow analysis tracks: a local or field, or | |
| 207 | // a property whose getter is proven store-free. | |
| 208 | _narrowing_target_named(identifier: Trees.Identifiers.Identifier) -> Semantic.Symbols.Symbol? is | |
| 209 | if identifier.is_qualified then | |
| 210 | return null | |
| 211 | fi | |
| 212 | ||
| 213 | let symbol = find(identifier) | |
| 214 | ||
| 215 | if !symbol? then | |
| 216 | return null | |
| 217 | fi | |
| 218 | ||
| 219 | if isa Semantic.Symbols.Variable(symbol) then | |
| 220 | return symbol | |
| 221 | fi | |
| 222 | ||
| 223 | if isa Semantic.Symbols.Property(symbol) then | |
| 224 | let property = cast Semantic.Symbols.Property(symbol) | |
| 225 | ||
| 226 | // Any readable property is a narrowing target; whether | |
| 227 | // its getter backs the fact is settled at formation once | |
| 228 | // the effect relations are solved - see | |
| 229 | // NARROW_ENV._declines_getter_fact. | |
| 230 | if property.read_function? then | |
| 231 | return property | |
| 232 | fi | |
| 233 | fi | |
| 234 | ||
| 235 | return null | |
| 236 | si | |
| 237 | ||
| 238 | // Build the re-readable access path an expression names, or | |
| 239 | // null when it isn't one: a local- or field-rooted chain of | |
| 240 | // instance field / store-free-getter reads (`receiver.prop`, | |
| 241 | // `receiver.a.b`). Calls, indexers, `?.`, qualified names, | |
| 242 | // self / super roots, struct receivers and properties with | |
| 243 | // unproven getters are all excluded — the result must be a | |
| 244 | // location that re-reads to the same presence answer under an | |
| 245 | // unchanged heap, and the flow transfers drop its facts the | |
| 246 | // moment the heap may have changed. Each hop is re-resolved | |
| 247 | // through `find` / `find_member` so the check site (`x.y?`) | |
| 248 | // and every use site (`x.y`) agree on the same root Variable | |
| 249 | // and member Symbols by identity, and so key the same | |
| 250 | // presence fact. | |
| 251 | try_build_access_path(expr: Trees.Expressions.Expression?) -> ACCESS_PATH? is | |
| 252 | if !expr? \/ !isa Trees.Expressions.MEMBER(expr) then | |
| 253 | return null | |
| 254 | fi | |
| 255 | ||
| 256 | let member = cast Trees.Expressions.MEMBER(expr) | |
| 257 | ||
| 258 | if member.is_coalesce \/ member.identifier.is_qualified then | |
| 259 | return null | |
| 260 | fi | |
| 261 | ||
| 262 | let root: Semantic.Symbols.Symbol? mut = null | |
| 263 | let members = Collections.LIST[Semantic.Symbols.Symbol]() | |
| 264 | ||
| 265 | if isa Trees.Expressions.IDENTIFIER(member.left) then | |
| 266 | let id = cast Trees.Expressions.IDENTIFIER(member.left) | |
| 267 | ||
| 268 | // The root is subject to the same rule as a hop: a | |
| 269 | // local or field read directly, or a property whose | |
| 270 | // getter is proven store-free. `try_get_narrowing_target` | |
| 271 | // is that rule, so a path root is exactly a symbol the | |
| 272 | // flow analysis already tracks on its own — which is | |
| 273 | // what makes the fact recorded at `h.name?` the one | |
| 274 | // read back at every later `h.name`. | |
| 275 | let symbol = try_get_narrowing_target(id) | |
| 276 | ||
| 277 | if !symbol? then | |
| 278 | return null | |
| 279 | fi | |
| 280 | ||
| 281 | root = symbol | |
| 282 | else | |
| 283 | let left_path = try_build_access_path(member.left) | |
| 284 | ||
| 285 | if !left_path? then | |
| 286 | return null | |
| 287 | fi | |
| 288 | ||
| 289 | root = left_path.root | |
| 290 | ||
| 291 | for m in left_path.members do | |
| 292 | members.add(m) | |
| 293 | od | |
| 294 | fi | |
| 295 | ||
| 296 | let left_value = member.left.value | |
| 297 | ||
| 298 | if !left_value? \/ !left_value.type? then | |
| 299 | return null | |
| 300 | fi | |
| 301 | ||
| 302 | let left_type = left_value.type | |
| 303 | ||
| 304 | if !isa Semantic.Types.NAMED(left_type) \/ left_type.is_value_type then | |
| 305 | return null | |
| 306 | fi | |
| 307 | ||
| 308 | let hop = left_type.find_member(member.identifier.name) | |
| 309 | ||
| 310 | if !hop? \/ !hop.is_instance then | |
| 311 | return null | |
| 312 | fi | |
| 313 | ||
| 314 | if !hop.is_field then | |
| 315 | let property = cast Semantic.Symbols.Property?(hop) | |
| 316 | ||
| 317 | // as in try_get_narrowing_target: any readable | |
| 318 | // property is an eligible hop, judged where a | |
| 319 | // consumption leans on the fact | |
| 320 | if !property? \/ !property.read_function? then | |
| 321 | return null | |
| 322 | fi | |
| 323 | fi | |
| 324 | ||
| 325 | members.add(hop) | |
| 326 | ||
| 327 | return ACCESS_PATH(root, members) | |
| 328 | si | |
| 329 | ||
| 330 | _check_pure_slots( | |
| 331 | location: LOCATION, | |
| 332 | value: IR.Values.Value?, | |
| 333 | syntax_arguments: Trees.Expressions.LIST? | |
| 334 | ) is | |
| 335 | _pure_slots.check_call(location, value, syntax_arguments) | |
| 336 | si | |
| 337 | ||
| 338 | // Record a compiled call or construction as a crossing on | |
| 339 | // every live heap fact — its receiver and arguments are | |
| 340 | // already read, and the callee may reassign a field through | |
| 341 | // an aliased receiver, so a later use of a fact carried over | |
| 342 | // this point is judged against it. `location` is the source | |
| 343 | // site of the expression: a Call value carries no ambient | |
| 344 | // location of its own, so the crossing must be anchored to | |
| 345 | // the syntax node rather than to `value.location`. | |
| 346 | _note_call(location: LOCATION, value: IR.Values.Value?) is | |
| 347 | _note_self_call(value) | |
| 348 | ||
| 349 | if !value? \/ !value.is_state_changing_call then | |
| 350 | // not a call at all, or a call whose callee is | |
| 351 | // structurally store-free — declared pure, a | |
| 352 | // whitelisted import, a synthesized backing read, a | |
| 353 | // pure function-typed invocation. Only the structural | |
| 354 | // tiers gate here: the proven answer does not exist | |
| 355 | // until after the solve, and a crossing skipped on a | |
| 356 | // stale proof could never be judged. | |
| 357 | return | |
| 358 | fi | |
| 359 | ||
| 360 | _flow.on_call_of(location, _called_function_of(value)) | |
| 361 | si | |
| 362 | ||
| 363 | // The single bounded callee behind a state-changing value, or | |
| 364 | // null for a closure invocation or anything else with no one | |
| 365 | // callee, which no judgement can discharge. | |
| 366 | _called_function_of(value: IR.Values.Value?) -> Semantic.Symbols.Function? is | |
| 367 | if let call: IR.Values.Call.INSTANCE = value then | |
| 368 | return call.function | |
| 369 | elif let call: IR.Values.Call.STATIC = value then | |
| 370 | return call.function | |
| 371 | elif let call: IR.Values.Call.GLOBAL = value then | |
| 372 | return call.function | |
| 373 | elif let call: IR.Values.Call.STRUCT = value then | |
| 374 | return call.function | |
| 375 | elif let construction: IR.Values.NEW = value then | |
| 376 | return construction.constructor | |
| 377 | elif let view: IR.Values.NARROW_VIEW = value then | |
| 378 | return _called_function_of(view.underlying) | |
| 379 | fi | |
| 380 | ||
| 381 | return null | |
| 382 | si | |
| 383 | ||
| 384 | // A call on `self` to a method of the enclosing type that cannot | |
| 385 | // be overridden, recorded on the must-call domain so a | |
| 386 | // constructor can be credited with what the callee assigns. | |
| 387 | // | |
| 388 | // Overridable is excluded rather than resolved: a subclass | |
| 389 | // override may assign nothing, and crediting the statically | |
| 390 | // resolved body would hide the null the check exists to find. | |
| 391 | _note_self_call(value: IR.Values.Value?) is | |
| 392 | if let call: IR.Values.Call.INSTANCE = value then | |
| 393 | if !call.from.is_self then | |
| 394 | return | |
| 395 | fi | |
| 396 | ||
| 397 | let callee = call.function | |
| 398 | ||
| 399 | if callee.is_virtual /\ !_cannot_be_overridden(callee) then | |
| 400 | return | |
| 401 | fi | |
| 402 | ||
| 403 | _flow.mark_called(callee) | |
| 404 | fi | |
| 405 | si | |
| 406 | ||
| 407 | // True when no override of `callee` can exist. Two conditions, | |
| 408 | // and both are needed: none in this build, which | |
| 409 | // `has_no_overriders` answers, and none possible from another | |
| 410 | // assembly, which only the owner's closure answers. A ghūl | |
| 411 | // class is closed to other assemblies unless declared `open`, | |
| 412 | // so an absence of overriders is the whole story there and | |
| 413 | // nowhere else — a trait, a struct, an imported type and an | |
| 414 | // `open` class can each be extended downstream by an override | |
| 415 | // that assigns nothing. | |
| 416 | _cannot_be_overridden(callee: Semantic.Symbols.Function) -> bool is | |
| 417 | // A constructor is reached through the type it constructs, so | |
| 418 | // there is no slot for a subclass to take over — the runtime | |
| 419 | // rejects a virtual `.ctor`, and the emitter never marks one. | |
| 420 | // The symbol reports `is_virtual` all the same, so a | |
| 421 | // constructor chaining to another would otherwise be declined | |
| 422 | // wherever the owner is `open`. | |
| 423 | if callee.is_constructor then | |
| 424 | return true | |
| 425 | fi | |
| 426 | ||
| 427 | if !callee.has_no_overriders then | |
| 428 | return false | |
| 429 | fi | |
| 430 | ||
| 431 | let owner = cast Semantic.Symbols.Classy?(callee.owner) | |
| 432 | ||
| 433 | // A closed trait's implementor set is enumerable in the same | |
| 434 | // way a closed class's subclass set is, so this could take | |
| 435 | // one too. It is left to classes deliberately: what the | |
| 436 | // trait case buys has not been measured, and getting it | |
| 437 | // wrong silently drops a store the caller depends on. | |
| 438 | return owner? /\ owner.is_class /\ !owner.is_open | |
| 439 | si | |
| 440 | ||
| 441 | // True iff `type` is a non-optional reference type — the kind | |
| 442 | // of slot the non-optional-by-default check guards. Value | |
| 443 | // types, `T?`, type variables, void and error/placeholder | |
| 444 | // types are all excluded. | |
| 445 | _is_non_optional_reference(type: Semantic.Types.Type?) -> bool => | |
| 446 | type? /\ type.is_named /\ | |
| 447 | !type.is_value_type /\ !type.is_optional /\ | |
| 448 | !type.is_type_variable /\ !type.is_void /\ | |
| 449 | !type.is_error /\ !type.is_inferred | |
| 450 | ||
| 451 | // True iff a value of `source` static type is guaranteed | |
| 452 | // castable to `target` at runtime. The plain assignability | |
| 453 | // check covers the direct subtype case. For an INTERSECTION | |
| 454 | // source (produced by class+trait flow narrowing — the | |
| 455 | // `if isa T(x) then cast T(x)` idiom when T is a trait | |
| 456 | // cross-cutting x's declared type), the intersection value | |
| 457 | // IS every member, so a target assignable from any member | |
| 458 | // is guaranteed to succeed. | |
| 459 | _cast_target_covers_source(target: Semantic.Types.Type, source: Semantic.Types.Type) -> bool is | |
| 460 | if target.is_assignable_from(source) then | |
| 461 | return true | |
| 462 | fi | |
| 463 | ||
| 464 | if isa Semantic.Types.INTERSECTION(source) then | |
| 465 | let intersection = source | |
| 466 | ||
| 467 | for m in intersection.members do | |
| 468 | if target.is_assignable_from(m) then | |
| 469 | return true | |
| 470 | fi | |
| 471 | od | |
| 472 | fi | |
| 473 | ||
| 474 | return false | |
| 475 | si | |
| 476 | ||
| 477 | // Non-optional-by-default: warn when a `T?` local the flow | |
| 478 | // analysis has not proven present reaches a non-optional | |
| 479 | // reference slot. `x?` / `isa` / `if let` clear it; an | |
| 480 | // explicit `x!` is exempt — the user took responsibility. A | |
| 481 | // non-variable `T?` source — a call result, a field — is not | |
| 482 | // flow-tracked, so it stays silent rather than risk a false | |
| 483 | // positive. The bare `null` literal is handled separately, in | |
| 484 | // visit(NULL), via its constraint. On by default; | |
| 485 | // `--no-warn-non-optional` opts out. A warning, not an error, | |
| 486 | // during the migration; the end state is `T?` not | |
| 487 | // assignment-compatible with `T`. | |
| 488 | check_non_optional(target: Semantic.Types.Type?, source: Trees.Expressions.Expression?, location: Source.LOCATION) is | |
| 489 | if _build_flags.no_warn_non_optional \/ !target? \/ !source? then | |
| 490 | return | |
| 491 | fi | |
| 492 | ||
| 493 | if !_is_non_optional_reference(target) then | |
| 494 | return | |
| 495 | fi | |
| 496 | ||
| 497 | let value = source.value | |
| 498 | ||
| 499 | if !value? \/ !value.type? then | |
| 500 | return | |
| 501 | fi | |
| 502 | ||
| 503 | if value.type.is_optional /\ !isa Trees.Expressions.UNWRAP(source) then | |
| 504 | let v = try_get_narrowing_target(source) | |
| 505 | ||
| 506 | if v? /\ !_flow.is_non_null(v) then | |
| 507 | if let kill = DROPPED_NARROWING.for_presence(v, null, location) then | |
| 508 | _logger.warn( | |
| 509 | location, | |
| 510 | "non-optional", | |
| 511 | "{target} expected but {v.name} may not hold a value", | |
| 512 | kill.crossing.location, | |
| 513 | DROPPED_NARROWING.message(kill) | |
| 514 | ) | |
| 515 | else | |
| 516 | _logger.warn(location, "non-optional", "{target} expected but {v.name} may not hold a value") | |
| 517 | fi | |
| 518 | fi | |
| 519 | fi | |
| 520 | si | |
| 521 | ||
| 522 | si | |
| 523 | si |