Appearance
| 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 |