Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use Semantic.Types.Type; | |
| 3 | ||
| 4 | // How an `if` / `case` expression's arms join once the non-null arms' | |
| 5 | // LUB is known and the null arms have been counted. | |
| 6 | enum ArmNullJoinDecision is | |
| 7 | // No null arm (or no LUB): use the LUB unchanged. | |
| 8 | PLAIN, | |
| 9 | // Value-type LUB joined with a genuine null arm: widen to the | |
| 10 | // value type's optional carrier (NULLABLE[T]). | |
| 11 | WIDEN_VALUE_OPTIONAL, | |
| 12 | // Reference-type LUB joined with a genuine null arm: widen to the | |
| 13 | // reference optional. | |
| 14 | WIDEN_REFERENCE_OPTIONAL, | |
| 15 | // Any LUB (value-type, type-variable, or reference-type) joined | |
| 16 | // with only non-genuine null arms: incompatible, the caller | |
| 17 | // reports it. | |
| 18 | INCOMPATIBLE | |
| 19 | si | |
| 20 | ||
| 21 | // Decides how an `if` / `case` expression's arms join in the presence | |
| 22 | // of null arms. Shared by the if and case paths in COMPILE_CONDITIONALS, | |
| 23 | // which differ only in diagnostic wording. Pure: it names the outcome | |
| 24 | // and leaves the caller to build the type, so it carries no symbol-table | |
| 25 | // dependency and pins the decision in isolation. | |
| 26 | // | |
| 27 | // "Genuine" excludes the unsettled inferred sentinel and the error type, | |
| 28 | // both of which also answer is_null: an in-progress inferred arm must | |
| 29 | // not force a premature widen or a hard error, or recursive-lambda | |
| 30 | // inference could not converge across passes. Only a settled null (the | |
| 31 | // NULL literal type) is genuine. | |
| 32 | class ARM_NULL_JOIN is | |
| 33 | init() is | |
| 34 | super.init(); | |
| 35 | si | |
| 36 | ||
| 37 | // A null arm counts as genuine only when its type is settled — a | |
| 38 | // NULL literal, never an in-progress inferred sentinel or an error. | |
| 39 | is_genuine_null(arm_type: Type) -> bool => | |
| 40 | arm_type.is_null /\ arm_type.is_settled; | |
| 41 | ||
| 42 | decide( | |
| 43 | lub_type: Type?, | |
| 44 | seen_genuine_null: bool, | |
| 45 | seen_null: bool | |
| 46 | ) -> ArmNullJoinDecision is | |
| 47 | if !lub_type? \/ !seen_null then | |
| 48 | return ArmNullJoinDecision.PLAIN; | |
| 49 | fi | |
| 50 | ||
| 51 | // is_value_type answers true for a type variable too (every | |
| 52 | // GenericArgument does), so a type variable takes the value- | |
| 53 | // type path here. NULLABLE[T] is wrong for it, so it never | |
| 54 | // widens: it falls through to INCOMPATIBLE, matching the | |
| 55 | // pre-existing behaviour for a generic arm against null. | |
| 56 | if lub_type.is_value_type then | |
| 57 | if seen_genuine_null /\ !lub_type.is_type_variable then | |
| 58 | return ArmNullJoinDecision.WIDEN_VALUE_OPTIONAL; | |
| 59 | fi | |
| 60 | ||
| 61 | return ArmNullJoinDecision.INCOMPATIBLE; | |
| 62 | fi | |
| 63 | ||
| 64 | if seen_genuine_null then | |
| 65 | return ArmNullJoinDecision.WIDEN_REFERENCE_OPTIONAL; | |
| 66 | fi | |
| 67 | ||
| 68 | return ArmNullJoinDecision.INCOMPATIBLE; | |
| 69 | si | |
| 70 | si | |
| 71 | si |