-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[inline-classes] Allow an inline class to implement a non-inline class #3090
Comments
Another related feature could be that any abstract member of an inline class (that's one declared by an abstract member declaration or an Then inline class A(int _value) implements int {} does two things: It adds (And with #2967, maybe we can extend a non-inline class too, as well as implement them.) |
It would indeed be nice to have a forwarding mechanism that gives the class designer more control! I'd like to have a forwarding mechanism which is a bit more general, though: It would be nice if we could abstract over sets of members, say, to obtain forwarding to a subset of the The proposal in #2506 uses an |
Thanks for creating this issue, Erik! I've mentioned this before, but for organization purposes, the JS interop use-case would be to have inline classes implement JS types, which are classes for now. They use This proposal addresses the case where the representation type is a subtype of the implemented non-inline class type. I've seen some interest in the case where the representation type isn't a subtype. This was discussed before in #2727 (comment), but I'm posting again here for organization and for an extra use-case. For example, we have a NodeList interface in JS. This is a type that behaves like a JS Of course, doing this would be unsound. Even if the inline class implements all the needed members, casting the inline type to |
Having an inline class, which is erased at runtime, implement an non-inline-class interface, which is not implemented by its representation object, is impossible for the reasons you mention. Simply doing: inline class C implements Iterable<int> { final NotAnIterable _ref; ...}
int? firstOrNull(Iterable<int> values) {
var it = values.iterator;
if (it.moveNext()) return it.current;
return null;
}
void main() {
C c = C(NotAnIterable());
firstOrNull(c);
} will pass the representation object, a We'll need a runtime wrapper to make that work, which is something A feature, different from the current inline classes, which could be a saving over having to just writing and applying the wrappers yourself, is in automatic wrapping, so you can keep a (So, basically, a |
Yup, there needs to be some type of runtime representation to make this work, which defeats the point of "zero-cost". It's not an ask we're going to push on, so limiting this to inline classes where the representation type is a subtype of the implemented non-inline class is reasonable for us. @lrhn FWIW, we're in the very very early stages of thinking about boxing for JS types. There are two cases:
It's not yet clear if we need something specific from the language to enable both, but good to see that there has been some precedence! @eernstg, to make sure we're aligned, I know we've asked for (and you've graciously responded to all of them :)):
Considering the benefit of this feature vs the work to complete it, this is probably the feature the web capabilities team would prioritize the most out of the three in the short-term. Let me know if I'm mistaken and this is something that needs a lot more iteration/work. |
@srujzs wrote:
If we adopt something like #3090 (this issue) then it should not be hard to do this: class JSObject {...}
class JSArray extends JSObject {...}
// An inline class at the top of the type graph implements `JSObject`.
inline class Element(JSObject rep) implements JSObject {...}
// Other inline classes implement `JSObject` by transitivity:
// `HtmlElement <: Element <: JSObject`, hence `HtmlElement <: JSObject`.
inline class HtmlElement(JSObject rep) implements Element {...}
// Some inline classes implement a subtype of `JSObject`, but then we just
// mention that subtype (here: `JSArray`) in the implements clause:
inline class Something(JSArray rep) implements SomeOtherThing, JSArray {...} So, presumably, the I don't know how important it is in this context, but I'm assuming that when we have
I agree that this is much less approachable. In particular if we want to say List<C> cs = ...;
List<JSArray> arrays = cs; // OK if `C <: JSArray`.
The proposal in #2967 (inline extends inline) allows for certain abbreviations. However, similar abbreviations are already considered using different mechanisms as well: The current proposal requires each inline class to declare exactly one final instance variable, and it is probably going to be very common that there is a constructor taking one argument. However, those things would be abbreviated substantially if something like primary constructors is accepted: // Without primary constructors:
@JS()
inline class Element {
final JSObject obj;
Element.fromJS(this.obj);
external Element();
}
@JS()
inline class DomElement implements Element {
final JSObject obj;
DomElement.fromJS(this.obj);
external DomElement();
}
// With primary constructors:
@JS()
inline class Element.fromJS(JSObject obj) {
external Element();
}
@JS()
inline class DomElement.fromJS(JSObject obj) implements Element {
external DomElement();
} As mentioned, if we adopt #3090 (this issue again) then the need to declare With respect to concise syntax, the obvious missing part would be that we still need to choose and write the name of the representation variable in every inline class, whereas #2967 allows the "sub-inline-class" to "inherit" it.
This could be addressed using something like the implicit construction proposal. |
For the DOM, probably. For general JS interop, we expect classes to be more shallow, so this would be less true.
For
I'm not entirely sure primary constructors help out too much for interop. It's useful to have a wrapping constructor like @JS()
inline class DomElement implements Element {
final JSObject obj;
external DomElement();
} to @JS()
inline class DomElement.fromJS(JSObject obj) implements Element {
external DomElement();
} which isn't bad, and we get a wrapping constructor out of it as well, but primary constructors seem to help make the non-external case much more concise than the external case. Obviously if we have primary external constructors, that'd make this much cooler: @JS()
external inline class DomElement.cons(JSObject obj) implements Element {} I haven't dug into the proposal yet to see if it does or doesn't include it. It does seem like a non-trivial amount of syntax design to support. |
@srujzs wrote:
No, every inline class member invocation is resolved statically and I would expect compilers to be able to compile invocations of
The primary constructors proposal here does not take external constructors into account, but we could easily allow @JS()
inline class external DomElement() implements Element {
final JSObject obj;
} I assumed that the external constructor would have no parameters (as shown in some earlier examples), and I also preserved the name ( However, if the external constructor does not have any parameters then we'd need to declare the representation variable using a regular declaration. Turning this around, why don't you make the addition of an external constructor an implicit consequence of having @JS()
inline class DomElement.fromJS(JSObject obj) implements Element {} and get both constructors as before. The ability to implicitly induce an external constructor doesn't look like too much magic to me, given that you are already using Another thing you could do (but that's probably too much magic ;-) would be to implicitly use the parameter type @JS()
inline class DomElement.fromJS(obj) implements Element {} |
I'll leave it up to you all to determine if
I suppose one alternative is inheriting the representation type from
IIUC, you're asking why we don't make the default generative constructor an external one in the compilers? It's certainly possible, and that same possibility exists for JS interop classes today. I'm personally opposed to it as it's confusing that an external object is created even though the default constructor is not marked
That's definitely too much magic. :) I think the representation type here is inferred by the CFE as Going back to this issue, do we have an idea on timelines for when a proposal will be finalized to allow inline classes to implement non-inline classes? |
I doubt we have enough time for this to be prototype-able for 3.1, but a friendly ping on timeline @eernstg :) |
@srujzs wrote:
Sure! However, there is no decision yet. @dart-lang/language-team, what's your take on this feature? Do you think we should just forget about it, do something completely different, or are you willing to move ahead aim for a feature specification? |
I think it's a very good idea, and I don't want to launch inline classes without it. |
Here is a proposal for a feature specification of this feature, expressed as an addendum to the inline class feature specification: #3138. |
Now, what class modifiers are appropriate for |
I found some possibly related mention. |
Modify the inline class feature specification to specify extension types. This is a feature renaming, but also a reintroduction of a special case of the primary constructor syntax, and an enhancement with support for non-extension type superinterfaces (cf. #3090). Note that many types cannot be used as superinterfaces (including `Function` and function types, records, `T?` for any `T`); we may choose to enable a broader set of types in the future, but at this time we prefer to keep it simple. For instance, `UP` may need to be revised if we accept a broader set of types.
Closing: This subfeature has been accepted: It is part of the extension types feature specification. |
Modify the inline class feature specification to specify extension types. This is a feature renaming, but also a reintroduction of a special case of the primary constructor syntax, and an enhancement with support for non-extension type superinterfaces (cf. #3090). Note that many types cannot be used as superinterfaces (including `Function` and function types, records, `T?` for any `T`); we may choose to enable a broader set of types in the future, but at this time we prefer to keep it simple. For instance, `UP` may need to be revised if we accept a broader set of types.
This issue describes a proposal to allow an inline class to implement a non-inline class, as long as it is a supertype of its representation type.
As it is currently specified, the
implements
clause of an inline class declaration is used to establish a subtype relationship to other inline classes. This allows the given inline class to "inherit" member implementations from those other inline classes, and it establishes a subtype relationship.Another subtype relationship which can safely be adopted is that the inline class is a subtype of its representation type, or any supertype of the representation type. We could broaden the applicability of the
implements
clause to establish that kind of subtype relationship as well:(I'm using primary constructors for brevity, assuming something like #3023)
This is sound because the run-time value of an expression of type
A
is an instance ofint
. It may be useful in the case where there is no desire to protect an object accessed as anA
against being accessed as anint
, and it is even considered to be better that it will readily "become anint
whenever needed" than not.The corresponding change to the feature specification could be that the following sentence in the section Composing inline classes is adjusted:
It is adjusted to end as follows:
Moreover:
We have had discussions about how to handle invocations of members of such non-inline superinterfaces. Here is one of the options that we had on the table:
Another option is to allow DV to redeclare some of those members, and only invoke members of the non-inline superinterface if there is no such redeclaration. If we make such redeclarations an error now then we can always add this as an enhancement.
[Edit] Further considerations that were raised in discussions:
The applicability of representation type members would presumably be transitive across subtype edges:
We could (probably easily) generalize this mechanism to allow the inline class to implement a non-inline class which is two or more steps in the inline-to-representation-type chain:
This is motivated by the fact that a the value of an expression of type
B
will always be an instance whose run-time type is a subtype ofnum
, and hence it is sound to makeB
a subtype ofnum
, and also to invoke members of the interface ofnum
.The text was updated successfully, but these errors were encountered: