Skip to content
← Back

src/syntax/process/arm_null_join.ghul

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