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