Open annevk opened 5 years ago
How do people feel about callback constructor Name = returnType(args)
kind of syntax?
@domenic @Ms2ger
It has the drawback of adding a new reserved word to IDL and hence needing to munge various places that want "identifier or anything that looks like one" in the grammar, but we should be able to just find all of those by searching the grammar for "interface", by analogy with callback interface
.
callback constructor Name = returnType(args)
SGTM
Note however we may want to consider defining this feature more holistically. For example custom elements, and I believe worklet APIs, want to not only construct the constructor, but pull properties off of it (e.g. step 10 of https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define). That could lead to different syntax.
I did consider callback class Name = ...
which looks a lot like the callback interface
bits we have been busy removing...
That said the audio worklet case just seems to want to construct the thing. I agree we should look at the other possible consumers to see what they want.
Looks like Gecko already got an implementation of this in https://bugzilla.mozilla.org/show_bug.cgi?id=1542932
Note that at the moment that does not have the semantics proposed above. In particular we do not verify that the passed-in function is a constructor in the binding layer. From the point of view of observable binding behavior it's just an alias for callback
, but exposes a Construct
rather than Call
on the Gecko side. In other words, it's Gecko developer ergonomics convenience, but without web-visible changes of any sort (which this proposal would have, due to doing IsConstructor
checks earlier).
What about something like this:
// No inheritance required
interface dictionary Foo {
// Same `constructor` as usual
constructor(...);
// Works like standard `dictionary` properties
static Type someStaticProp;
static Type someStaticProp = "default";
required static Type someStaticProp;
// Instance property
attribute Type someInstanceProp;
attribute Type someInstanceProp = "default";
required attribute Type someInstanceProp;
// Instance method
attribute Type someMethod(...);
required attribute Type someMethod(...);
};
// Works like above, but requires instances to extend a particular interface,
// like via `class MyFoo extends Bar {}`. Note: it can only otherwise extend
// other interface dictionaries, much like how dictionaries can only extend
// from other dictionaries.
interface Bar { ... }
interface dictionary Foo : Bar { ... }
Concept is rough, and it is technically larger than everything else that's been proposed here, but it aligns pretty well conceptually with WebIDL's current processing model and shouldn't require more than mechanical changes from how it's currently consumed. It also is intended to satisfy the needs of both Houdini, custom elements, and worklets.
What are the actual semantics of that proposal?
@bzbarsky So here's what I was thinking:
constructor
dictates what prototype the function may be constructed as, similar to how callback
types dictate the prototype functions may be called as.
Static attributes are read as if the type were a dictionary
. So these two are equivalent mod the requirement values implementing Type
must also be constructible in the second case.
dictionary Foo {
Type someStaticProp;
Type someStaticProp = "default";
required Type someStaticProp;
Type someStaticMethod(Foo someFoo);
};
interface dictionary Foo {
constructor(...);
static Type someStaticProp;
static Type someStaticProp = "default";
required static Type someStaticProp;
static Type someStaticMethod(Foo someFoo);
};
Non-static attributes are based on the value returned from the constructor. So assuming the callback
type was only constructed and never called, these two types would be functionally equivalent mod the extra binding that would not be exposed by the callback interface:
callback Foo = FooInstance (double a, double b);
dictionary FooInstance {
Type someInstanceProp;
Type someInstanceProp = "default";
required Type someInstanceProp;
Type someMethod(...);
required Type someMethod(...);
};
interface dictionary Foo {
constructor(double a, double b);
attribute Type someInstanceProp;
attribute Type someInstanceProp = "default";
required attribute Type someInstanceProp;
attribute Type someMethod(...);
required attribute Type someMethod(...);
};
(Of course, it might be useful to allow referencing the type and value separately, but that's something that can be hashed out later.)
Interface dictionaries can inherit from other interface dictionaries, in which they inherit all constructors, static properties, attributes, methods, and so on. If a constructor
is specified, none of its parents may have them, and if no constructor
is specified, at most one of them may have a constructor
. If an interface dictionary does not inherit from other interface dictionaries, it may have at most one constructor
. Outside of interface dictionary inheritance, they may only be referenced if a constructor
is present.
Interface dictionaries can inherit from a single interface provided it has a constructor
. In this case, values must extend that interface (and not be identical to it) to be valid, and they must return an instance of that interface. If those two conditions are not fulfilled, a TypeError
is thrown.
[Exposed]
extended attribute. (Otherwise, it's a pretty useless requirement. It does however avoid the need to reify interfaces themselves into literal identities of some form.)I was trying to avoid over-specifying here, and I probably left a few holes here by accident. But hopefully this clarifies what I'm suggesting. The overarching goal is to have a solution that solves the needs at hand flexibly while still being intuitively WebIDL. (And implementations can just desugar, abstract, and track a few new invariants - it's not a radical change to implement.)
In particular something we verify to be a callback that also passes https://tc39.github.io/ecma262/#sec-isconstructor.
This could be used to remove the first step of https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define.
It would also help https://github.com/WebAudio/web-audio-api/pull/1843 and other Worklet APIs dealing with constructors.
Invoking the constructor would go through https://heycam.github.io/webidl/#construct-a-callback-function though that can be tightened up a bit by no longer having to check that it's a constructor.