|
| 1 | +# Feature Specification: Interface Conflict Management |
| 2 | + |
| 3 | +**Owner**: eernst@ |
| 4 | + |
| 5 | +**Status**: Under discussion. |
| 6 | + |
| 7 | +**Version**: 0.3 (2018-04-24) |
| 8 | + |
| 9 | + |
| 10 | +This document is a Dart 2 feature specification which specifies how to |
| 11 | +handle conflicts among certain program elements associated with the |
| 12 | +interface of a class. In particular, it specifies that multiple occurrences |
| 13 | +of the same generic class in the superinterface hierarchy must receive the |
| 14 | +same type arguments, and that no attempts are made at synthesizing a |
| 15 | +suitable method signature if multiple distinct signatures are provided by |
| 16 | +the superinterfaces, and none of them resolves the conflict. |
| 17 | + |
| 18 | + |
| 19 | +## Motivation |
| 20 | + |
| 21 | +In Dart 1, the management of conflicts during the computation of the |
| 22 | +interface of a class is rather forgiving. On page 42 of |
| 23 | +[ECMA-408](https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf), |
| 24 | +we have the following: |
| 25 | + |
| 26 | +> However, if the above rules would cause multiple members |
| 27 | +> _m<sub>1</sub>, ..., m<sub>k</sub>_ |
| 28 | +> with the same name _n_ to be inherited (because identically named |
| 29 | +> members existed in several superinterfaces) then at most one member |
| 30 | +> is inherited. |
| 31 | +> |
| 32 | +> ... |
| 33 | +> |
| 34 | +> Then _I_ has a method named _n_, with _r_ required parameters of type |
| 35 | +> `dynamic`, _h_ positional parameters of type `dynamic`, named parameters |
| 36 | +> _s_ of type `dynamic` and return type `dynamic`. |
| 37 | +
|
| 38 | +In particular, the resulting class interface may then contain a method |
| 39 | +signature which has been synthesized during static analysis, and which |
| 40 | +differs from all declarations of the given method in the source code. |
| 41 | +In the case where some superintenfaces specify some optional positional |
| 42 | +parameters and others specify some named parameters, any attempt to |
| 43 | +implement the synthesized method signature other than via a user-defined |
| 44 | +`noSuchMethod` would fail (it would be a syntax error to declare both |
| 45 | +kinds of parameters in the same method declaration). |
| 46 | + |
| 47 | +For Dart 2 we modify this approach such that more emphasis is given to |
| 48 | +predictability, and less emphasis is given to convenience: No class |
| 49 | +interface will ever contain a method signature which has been |
| 50 | +synthesized during static analysis, it will always be one of the method |
| 51 | +interfaces that occur in the source code. In case of a conflict, the |
| 52 | +developer must explicitly specify how to resolve the conflict. |
| 53 | + |
| 54 | +To reinforce the same emphasis on predictability, we also specify that |
| 55 | +it is a compile-time error for a class to have two superinterfaces which |
| 56 | +are instantiations of the same generic class with different type arguments. |
| 57 | + |
| 58 | + |
| 59 | +## Syntax |
| 60 | + |
| 61 | +The grammar remains unchanged. |
| 62 | + |
| 63 | + |
| 64 | +## Static Analysis |
| 65 | + |
| 66 | +We introduce a new relation among types, _more interface-specific than_, |
| 67 | +which is similar to the subtype relation, but which treats top types |
| 68 | +differently. |
| 69 | + |
| 70 | +- The built-in class `Object` is more interface-specific than `void`. |
| 71 | +- The built-in type `dynamic` is more interface-specific than `void`. |
| 72 | +- None of `Object` and `dynamic` is more interface-specific than the other. |
| 73 | +- All other subtype rules are also valid rules about being more |
| 74 | + interface-specific. |
| 75 | + |
| 76 | +This means that we will express the complete rules for being 'more |
| 77 | +interface-specific than' as a slight modification of |
| 78 | +[subtyping.md](https://github.com/dart-lang/sdk/blob/master/docs/language/informal/subtyping.md) |
| 79 | +and in particular, the rule 'Right Top' will need to be split in cases |
| 80 | +such that `Object` and `dynamic` are more interface-specific than `void` and |
| 81 | +mutually unrelated, and all other types are more interface-specific than |
| 82 | +both `Object` and `dynamic`. |
| 83 | + |
| 84 | +*For example, `List<Object>` is more interface-specific than `List<void>` |
| 85 | +and incomparable to `List<dynamic>`; similarly, `int Function(void)` is |
| 86 | +more interface-specific than `void Function(Object)`, but the latter is |
| 87 | +incomparable to `void Function(dynamic)`.* |
| 88 | + |
| 89 | +It is a compile-time error if a class _C_ has two superinterfaces of the |
| 90 | +form _D<T<sub>1</sub> .. T<sub>k</sub>>_ respectively |
| 91 | +_D<S<sub>1</sub> .. S<sub>k</sub>>_ such that there is a _j_ in _1 .. k_ |
| 92 | +where _T<sub>j</sub>_ and _S<sub>j</sub>_ denote types that are not |
| 93 | +mutually more interface-specific than each other. |
| 94 | + |
| 95 | +*This means that the (direct and indirect) superinterfaces must agree on |
| 96 | +the type arguments passed to any given generic class. Note that the case |
| 97 | +where the number of type arguments differ is unimportant because at least |
| 98 | +one of them is already a compile-time error for other reasons. Also note |
| 99 | +that it is not sufficient that the type arguments to a given superinterface |
| 100 | +are mutual subtypes (say, if `C` implements both `I<dynamic>` and |
| 101 | +`I<Object>`), because that gives rise to ambiguities which are considered |
| 102 | +to be compile-time errors if they had been created in a different way.* |
| 103 | + |
| 104 | +This compile-time error also arises if the type arguments are not given |
| 105 | +explicitly. |
| 106 | + |
| 107 | +*They might be obtained via |
| 108 | +[instantiate-to-bound](https://github.com/dart-lang/sdk/blob/master/docs/language/informal/instantiate-to-bound.md) |
| 109 | +or, in case such a mechanism is introduced, they might be inferred.* |
| 110 | + |
| 111 | +*The language specification already contains verbiage to this effect, but we |
| 112 | +mention it here for two reasons: First, it is a recent change which has been |
| 113 | +discussed in the language team together with the rest of the topics in this |
| 114 | +document because of their similar nature and motivation. Second, we note |
| 115 | +that this restriction may be lifted in the future. It was a change in the |
| 116 | +specification which did not break many existing programs because `dart2js` |
| 117 | +always enforced that restriction (even though it was not specified in the |
| 118 | +language specification), so in that sense it just made the actual situation |
| 119 | +explicit. However, it may be possible to lift the restriction: Given that an |
| 120 | +instance of a class that has `List<int>` among its superinterfaces can be |
| 121 | +accessed via a variable of type `List<num>`, it seems unlikely that it would |
| 122 | +violate any language invariants to allow the class of that instance to have |
| 123 | +both `List<int>` and `List<num>` among its superinterfaces. We may then |
| 124 | +relax the rule to specify that for each generic class _G_ which occurs among |
| 125 | +superinterfaces, there must be a unique superinterface which is the most |
| 126 | +specific instantiation of _G_.* |
| 127 | + |
| 128 | +During computation of the interface of a class _C_, it may be the case that |
| 129 | +multiple direct superinterfaces have a declaration of a member of the same |
| 130 | +name _n_, and class _C_ does not declare member named _n_. |
| 131 | +Let _D<sub>1</sub> .. D<sub>n</sub>_ denote this set of declarations. |
| 132 | + |
| 133 | +It is a compile-time error if some declarations among |
| 134 | +_D<sub>1</sub> .. D<sub>n</sub>_ are getters and others are non-getters. |
| 135 | + |
| 136 | +Otherwise, if all of _D<sub>1</sub> .. D<sub>n</sub>_ are getter |
| 137 | +declarations, the interface of _C_ inherits one, _D<sub>j</sub>_, whose |
| 138 | +return type is more interface-specific than that of every declaration in |
| 139 | +_D<sub>1</sub> .. D<sub>n</sub>_. It is a compile-time error if no such |
| 140 | +_D<sub>j</sub>_ exists. |
| 141 | + |
| 142 | +*For example, it is an error to have two declarations with the signatures |
| 143 | +`Object get foo` and `dynamic get foo`, and no others, because none of |
| 144 | +these is more interface-specific than the other. This example illustrates |
| 145 | +why it is unsatisfactory to rely on subtyping alone: If we had accepted |
| 146 | +this kind of ambiguity then it would be difficult to justify the treatment |
| 147 | +of `o.foo.bar` during static analysis where `o` has type _C_: If it is |
| 148 | +considered to be a compile-time error then `dynamic get foo` is being |
| 149 | +ignored, and if it is not an error then `Object get foo` is being ignored, |
| 150 | +and each of these behaviors may be surprising and/or error-prone. Hence, we |
| 151 | +require such a conflict to be resolved explicitly, which may be done by |
| 152 | +writing a signature in the class which overrides both method signatures |
| 153 | +from the superinterfaces and explicitly chooses `Object` or `dynamic`.* |
| 154 | + |
| 155 | +Otherwise, (*when all declarations are non-getter declarations*), the |
| 156 | +interface of _C_ inherits one, _D<sub>j</sub>_, where its function type is |
| 157 | +more interface-specific than that of all declarations in |
| 158 | +_D<sub>1</sub> .. D<sub>n</sub>_. It is a compile-time error if no such |
| 159 | +declaration _D<sub>j</sub>_ exists. |
| 160 | + |
| 161 | +*In the case where more than one such declaration exists, it is known that |
| 162 | +their parameter list shapes are identical, and their return types and |
| 163 | +parameter types are pairwise mutually more interface-specific than each |
| 164 | +other (i.e., for any two such declarations _D<sub>i</sub>_ and _D<sub>j</sub>_, |
| 165 | +if _U<sub>i</sub>_ is the return type from _D<sub>i</sub>_ and |
| 166 | +_U<sub>j</sub>_ is the return type from _D<sub>j</sub>_ then |
| 167 | +_U<sub>i</sub>_ is more interface-specific than _U<sub>j</sub>_ and |
| 168 | +vice versa, and similarly for each parameter type). This still allows for |
| 169 | +some differences. We ignore differences in metadata on formal parameters |
| 170 | +(we do not consider method signatures in interfaces to have metadata). But |
| 171 | +we need to consider one more thing:* |
| 172 | + |
| 173 | +In this decision about which declaration among |
| 174 | +_D<sub>1</sub> .. D<sub>n</sub>_ |
| 175 | +the interface of the class _C_ will inherit, if we have multiple possible |
| 176 | +choices, let _D<sub>i</sub>_ and _D<sub>j</sub>_ be such a pair of possible |
| 177 | +choices. It is a compile-time error if _D<sub>i</sub>_ and _D<sub>j</sub>_ |
| 178 | +declare two optional formal parameters _p<sub>1</sub>_ and _p<sub>2</sub>_ |
| 179 | +such that they correspond to each other (*same name if named, or else same |
| 180 | +position*) and they specify different default values. |
| 181 | + |
| 182 | + |
| 183 | +## Discussion |
| 184 | + |
| 185 | +Conflicts among distinct top types may be considered to be spurious in the |
| 186 | +case where said type occurs in a contravariant position in the method |
| 187 | +signature. Consider the following example: |
| 188 | + |
| 189 | +```dart |
| 190 | +abstract class I1 { |
| 191 | + void foo(dynamic d); |
| 192 | +} |
| 193 | +
|
| 194 | +abstract class I2 { |
| 195 | + void foo(Object o); |
| 196 | +} |
| 197 | +
|
| 198 | +abstract class C implements I1, I2 {} |
| 199 | +``` |
| 200 | + |
| 201 | +In both situations—when `foo` accepts an argument of type `dynamic` |
| 202 | +and when it accepts an `Object`—the acceptable actual arguments are |
| 203 | +exactly the same: _Every_ object can be passed. Moreover, the formal |
| 204 | +parameters `d` and `o` are not in scope anywhere, so there will never be |
| 205 | +an expression like `d.bar` or `o.bar` which is allowed respectively |
| 206 | +rejected because the receiver is or is not `dynamic`. In other words, |
| 207 | +_it does not matter_ for clients of `C` whether that argument type is |
| 208 | +`dynamic` or `Object`. |
| 209 | + |
| 210 | +During inference, the type-from-context for an actual argument to `foo` |
| 211 | +will depend on the choice: It will be `dynamic` respectively `Object`. |
| 212 | +However, this choice will not affect the treatment of the actual |
| 213 | +argument. |
| 214 | + |
| 215 | +One case worth considering is the following: |
| 216 | + |
| 217 | +```dart |
| 218 | +abstract class I1 { |
| 219 | + void foo(dynamic f()); |
| 220 | +} |
| 221 | +
|
| 222 | +abstract class I2 { |
| 223 | + void foo(Object f()); |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +If a function literal is passed in at a call site, it may have its return |
| 228 | +type inferred to `dynamic` respectively `Object`. This will change the |
| 229 | +type-from-context for any returned expressions, but just like the case |
| 230 | +for the actual parameter, that will not change the treatment of such |
| 231 | +expressions. Again, it does not matter for clients calling `foo` whether |
| 232 | +that type is `dynamic` or `Object`. |
| 233 | + |
| 234 | +Conversely, the choice of top type matters when it is placed in a |
| 235 | +contravariant location in the parameter type: |
| 236 | + |
| 237 | +```dart |
| 238 | +abstract class I1 { |
| 239 | + void foo(int f(dynamic d)); |
| 240 | +} |
| 241 | +
|
| 242 | +abstract class I2 { |
| 243 | + void foo(int f(Object o)); |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +In this situation, a function literal used as an actual argument at a call |
| 248 | +site for `foo` would receive an inferred type annotation for its formal |
| 249 | +parameter of `dynamic` respectively `Object`, and the usage of that parameter |
| 250 | +in the body of the function literal would then differ. In other words, the |
| 251 | +developer who declares `foo` may decide whether the code in the body of the |
| 252 | +function literal at the call sites should use strict or relaxed type |
| 253 | +checking—and it would be highly error-prone if this decision were |
| 254 | +to be made in a way which is unspecified. |
| 255 | + |
| 256 | +All in all, it may be useful to "erase" all top types to `Object` when they |
| 257 | +occur in contravariant positions in method signatures, such that the |
| 258 | +differences that may exist do not create conflicts; in contrast, the top |
| 259 | +types that occur in covariant positions are significant, and hence the fact |
| 260 | +that we require such conflicts to be resolved explicitly is unlikely to be |
| 261 | +relaxed. |
| 262 | + |
| 263 | +## Updates |
| 264 | + |
| 265 | +* Apr 24th 2018, version 0.3: Renamed 'override-specific' to |
| 266 | + 'interface-specific', to avoid giving the impression that it can be |
| 267 | + used to determine whether a given signature can override another one |
| 268 | + (the override check must use different rules, e.g., it must allow |
| 269 | + `dynamic foo();` to override `Object foo();` _and_ vice versa). |
| 270 | + |
| 271 | +* Apr 16th 2018, version 0.2: Introduced the relation 'more |
| 272 | + override-specific than' in order to handle top types more consistently |
| 273 | + and concisely. |
| 274 | + |
| 275 | +* Feb 8th 2018, version 0.1: Initial version. |
0 commit comments