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