Skip to content
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

Define a callback constructor type #701

Open
annevk opened this issue Apr 1, 2019 · 10 comments
Open

Define a callback constructor type #701

annevk opened this issue Apr 1, 2019 · 10 comments

Comments

@annevk
Copy link
Member

annevk commented Apr 1, 2019

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 WebAudio/web-audio-api#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.

@bzbarsky
Copy link
Collaborator

bzbarsky commented Apr 1, 2019

How do people feel about callback constructor Name = returnType(args) kind of syntax?

@domenic @Ms2ger

@bzbarsky
Copy link
Collaborator

bzbarsky commented Apr 1, 2019

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.

@domenic
Copy link
Member

domenic commented Apr 1, 2019

callback constructor Name = returnType(args) SGTM

@domenic
Copy link
Member

domenic commented Apr 1, 2019

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.

@bzbarsky
Copy link
Collaborator

bzbarsky commented Apr 1, 2019

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.

@Ms2ger
Copy link
Member

Ms2ger commented Apr 12, 2019

Looks like Gecko already got an implementation of this in https://bugzilla.mozilla.org/show_bug.cgi?id=1542932

@bzbarsky
Copy link
Collaborator

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).

@dead-claudia
Copy link

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.

@bzbarsky
Copy link
Collaborator

bzbarsky commented Dec 6, 2019

What are the actual semantics of that proposal?

@dead-claudia
Copy link

@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.

    • They cannot inherit from interface mixins or ordinary dictionary types, but this restriction could likely be lifted for the latter.
  • 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.

    • There might be other conditions that need added here - I can't remember the exact nuance involved in the custom element upgrade steps, and I'm not very familiar with the precise way the other specs handle required subtyping. I know the custom element API has other invariants it checks, too, but I'm not sure how much that carries over elsewhere.
    • The interface inherited from must feature the [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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

5 participants