webidl icon indicating copy to clipboard operation
webidl copied to clipboard

Define that interface objects are constructors

Open Ms2ger opened this issue 6 years ago • 13 comments

ES claims the following (twice):

Built-in function objects that are not identified as constructors do not implement the [[Construct]] internal method unless otherwise specified in the description of a particular function.

We don't normatively identify the interface object as a constructor, even if it has a [Constructor]. I guess we should.

A less obvious question is what we should do if there's no [Constructor]. Right now, I think we're aiming to install a [[Construct]] internal method that always throws, but that makes the interface object pass IsConstructor().

Ms2ger avatar Mar 26 '19 14:03 Ms2ger

unless otherwise specified in the description of a particular function

This is the key, no? https://heycam.github.io/webidl/#Constructor specifies otherwise:

If the [Constructor] extended attribute appears on an interface, it indicates that the interface object for this interface will have an [[Construct]] internal method

So I think we're good here: thing with a [Constructor] have [[Construct]] and things without do not, as desired.

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

Though I guess that leaves open the question of the no-[Constructor] case. I just checked Gecko, and it does have a [[Constructor]] in that case, albeit one that always throws. I wonder whether the output of IsConstructor() is observable, other than by the exceptions that get thrown.

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

I guess you could do something where you use a DOM interface object as the .constructor of an array and see what happens in https://tc39.github.io/ecma262/#sec-arrayspeciescreate step 6.

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

And also whether a proxy for a DOM interface object ends up with a construct trap being called. So simple testcase:

<script>
  var p = new Proxy(Node, { construct: () => console.log("constructing") });
  new p()
</script>

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

On that testcase, Chrome and Firefox log "constructing"; Safari does not (and the exception it throws is about p not being a constructor). Yay interop. ;)

But OK, if we want to spec Chrome/Firefox behavior here then we need to change something in the idl spec.

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

And admittedly it would be good if the "is a constructor" bit was near the bit that defines the steps, not way off somewhere else...

bzbarsky avatar Mar 26 '19 15:03 bzbarsky

I've always assumed they had constructors (and [[Construct]] methods), just throwing ones.

I think it'd be very strange to have callable interface objects exist at all, and be reachable via the .constructor property, if they are not also [[Construct]]able.

In practice it doesn't matter much, but I do prefer keeping [[Construct]] and just saying that it throws for things without a constructor in the IDL.

domenic avatar Apr 03 '19 16:04 domenic

There was an interesting question about feature detection that was raised the other day: how to detect a change from "no [Constructor]" to "no-arg [Constructor]". Right now, I think "call the constructor and see if it throws" is the only way in Firefox and Chrome. The Proxy thing above could be used if we changed whether [[Construct]] happens...

But maybe this is enough of an edge case that it's not an issue.

@rniwa would Safari be open to changing its behavior here, or do you know whom I should be asking about it?

bzbarsky avatar Apr 03 '19 16:04 bzbarsky

What's the benefit of implementing Chrome/Firefox behavior here?

rniwa avatar Apr 03 '19 19:04 rniwa

FWIW, it seems there’s a number of ways to observe this distinction beyond just Proxy — pretty much anywhere IsConstructor is used, I guess. In addition to peeking out through various programmatic APIs, it can also impact evaluation of ‘bare’ syntax (third example below). Each of the following statements will throw a TypeError if Foo[[Construct]] is absent, but will not throw if it’s present:

Reflect.construct(Bar, [], Foo);

customElements.define('x-x', Foo);

class Bar extends Foo {}

Usually it’s throw vs don’t throw, but sometimes potentially neither throws but they still do other different things:

// If Foo[[Construct]] is present, (attempts) returning new Foo.
// If Foo[[Construct]] is absent, returns new Array.

Array.from.call(Foo, []);

// In practice here, the first case throws for the specific Web IDL issue under discussion,
// though it’s the reverse of the other cases (i.e. throw when [[Construct]] _is_ present).

I think it'd be very strange to have callable interface objects exist at all, and be reachable via the .constructor property, if they are not also [[Construct]]able.

I agree.

bathos avatar Oct 03 '19 06:10 bathos

@rniwa would Safari be open to changing its behavior here, or do you know whom I should be asking about it?

This behavior was changed in JSDOMConstructorNotConstructable should be a constructor, aligning Safari with Chrome/Firefox.

shvaikalesh avatar Oct 20 '20 19:10 shvaikalesh

Thanks @shvaikalesh! Do you know if there are web platform tests for this?

I looked into how we would update the Web IDL spec to be explicit about this, and it's still kinda unclear to me, largely because the ES spec is unclear. I will open an issue on tc39/ecma262 asking for guidance.

domenic avatar Oct 20 '20 19:10 domenic

Do you know if there are web platform tests for this?

There are tests asserting that (class extends HTML___Element {}) doesn't throw, and that's it.

idlharness.js has a FIXME for implementing [[Construct]] check.

shvaikalesh avatar Oct 20 '20 20:10 shvaikalesh