Skip to content

Commit 7c7df7b

Browse files
committed
Add initial version of feature specification on interface conflicts.
This feature specification is intended to resolve bullet item 2 in the issue #31228. A rendered version corresponding to patch set 16 is available at https://gist.github.com/eernstg/a55c8000610a506bf0ca70b028d9f1eb. Change-Id: I7d3e67bd7dd2d2cfc73fbd491bcfbea1814421e0 Reviewed-on: https://dart-review.googlesource.com/c/40080 Reviewed-by: Lasse R.H. Nielsen <[email protected]>
1 parent f2bffc6 commit 7c7df7b

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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&mdash;when `foo` accepts an argument of type `dynamic`
202+
and when it accepts an `Object`&mdash;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&mdash;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

Comments
 (0)