Skip to content
← Back

src/semantic/arm_null_join.ghul

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