Skip to content
← Back

src/semantic/symbols/variance_position_checker.ghul

1
namespace Semantic.Symbols is
2
use Logging
3
4
use Source.LOCATION
5
6
use Types.Type
7
use Types.TypeVariance
8
9
use Ghul.Pipes
10
11
// Checks that a trait declaring type-parameter variance uses each
12
// covariant parameter only in output positions and each
13
// contravariant parameter only in input positions — the CLR's
14
// variance-safety rule (C#'s CS1961).
15
//
16
// A method parameter is an input (contravariant) position, a return
17
// type an output (covariant) position, and a base trait a covariant
18
// position. Descending through a generic type composes the current
19
// position with that generic's own per-argument variance.
20
//
21
// Coverage is the type-variable base case plus generic-type
22
// recursion — the common trait shapes. The CLR re-verifies variance
23
// at type load, so anything not modelled here still fails safely.
24
class VARIANCE_POSITION_CHECKER is
25
_trait: Classy
26
_logger: Logger
27
28
init(variant_trait: Classy, logger: Logger) is
29
_trait = variant_trait
30
_logger = logger
31
si
32
33
check() is
34
if !_trait.argument_variances? \/ _trait.argument_variances.count == 0 then
35
return
36
fi
37
38
for ancestor in _trait.ancestors do
39
check_type(ancestor, TypeVariance.COVARIANT, _trait.location)
40
od
41
42
for member in _trait.symbols do
43
check_member(member)
44
od
45
si
46
47
check_member(member: Symbol) is
48
if let group: FUNCTION_GROUP = member then
49
for f in group.functions do
50
check_function(f)
51
od
52
elif let function: Function = member then
53
check_function(function)
54
fi
55
si
56
57
check_function(f: Function) is
58
for argument_type in f.arguments do
59
check_type(argument_type, TypeVariance.CONTRAVARIANT, f.location)
60
od
61
62
check_type(f.return_type!, TypeVariance.COVARIANT, f.location)
63
si
64
65
check_type(type: Type?, position: TypeVariance, location: LOCATION) is
66
if !type? then
67
return
68
fi
69
70
let parameter_index = trait_parameter_index(type)
71
72
if parameter_index >= 0 then
73
let declared = _trait.get_argument_variance(parameter_index)
74
75
if declared != TypeVariance.INVARIANT /\ declared != position then
76
_logger.error(
77
location,
78
"{describe(declared)} type parameter {type} is used in {describe_position(position)}"
79
)
80
fi
81
82
return
83
fi
84
85
if let generic: Types.GENERIC = type then
86
for (i, argument) in generic.arguments |> index() do
87
check_type(argument, compose(position, generic.get_argument_type_variance(i)), location)
88
od
89
fi
90
si
91
92
// The index of `_trait`'s own type parameter that `type` refers
93
// to, or -1 when `type` is not one of them.
94
trait_parameter_index(type: Type) -> int is
95
if !type.is_type_variable then
96
return -1
97
fi
98
99
if let argument: GenericArgument = type.symbol /\ argument.owner == _trait then
100
return argument.index
101
fi
102
103
return -1
104
si
105
106
compose(outer: TypeVariance, inner: TypeVariance) -> TypeVariance =>
107
if inner == TypeVariance.INVARIANT \/ outer == TypeVariance.INVARIANT then
108
TypeVariance.INVARIANT
109
elif inner == TypeVariance.COVARIANT then
110
outer
111
else
112
flip(outer)
113
fi
114
115
flip(variance: TypeVariance) -> TypeVariance =>
116
if variance == TypeVariance.COVARIANT then
117
TypeVariance.CONTRAVARIANT
118
elif variance == TypeVariance.CONTRAVARIANT then
119
TypeVariance.COVARIANT
120
else
121
TypeVariance.INVARIANT
122
fi
123
124
describe(variance: TypeVariance) -> string =>
125
if variance == TypeVariance.COVARIANT then
126
"covariant"
127
elif variance == TypeVariance.CONTRAVARIANT then
128
"contravariant"
129
else
130
"invariant"
131
fi
132
133
describe_position(position: TypeVariance) -> string =>
134
if position == TypeVariance.COVARIANT then
135
"an output position"
136
elif position == TypeVariance.CONTRAVARIANT then
137
"an input position"
138
else
139
"an invariant position"
140
fi
141
si
142
si