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

How should other specs indicate that they create built-in functions that support [[Construct]]? #2209

Open
domenic opened this issue Oct 20, 2020 · 16 comments

Comments

@domenic
Copy link
Member

domenic commented Oct 20, 2020

In whatwg/webidl#698, we've come to the conclusion that all web platform classes should have a [[Construct]], even if it just throws a TypeError immediately. However, we're unsure how to express that in spec language.

Currently (in create an interface object step 4) we do

Let F be ! CreateBuiltinFunction(steps, « [[Unforgeables]] », realm, constructorProto).

However this doesn't appear to give us [[Construct]] automatically.

For the ES spec itself, it seems things are done fairly implicitly: https://tc39.es/ecma262/#sec-built-in-function-objects says

The behaviour specified for each built-in function via algorithm steps or other means is the specification of the function body behaviour for both [[Call]] and [[Construct]] invocations of the function. However, [[Construct]] invocation is not supported by all built-in functions.

then later

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.

which manifests in individual built-in functions via clauses like "the Number constructor" versus "the isFinite function".

For web specifications, how would you suggest specifying our counterpart? Especially given our more-imperative realm creation routine. My current guess is to add a step like

  1. Identify F as a constructor. NOTE: this means it implements the default [[Construct]] internal method for built-in functions.

although this feels a bit informal and wishy-washy. (The fact that we have to explain it with a note, instead of e.g. a hyperlink, is a sign of my discomfort.) Would that be the best approach?

@devsnek
Copy link
Member

devsnek commented Oct 20, 2020

Within the definition of builtin functions, webidl saying "constructor steps" seems like enough to satisfy ecma262. Confusion beyond that (for the web editors) could probably be handled by a note saying "this creates a function with a [[Construct]] slot because we identify it as such".

@jmdyck
Copy link
Collaborator

jmdyck commented Oct 20, 2020

(Side note: The ES spec defines the operation MakeConstructor, which might seem to be what you want, but it only operates on ordinary functions, and built-in functions aren't required to be ordinary.)

@syg syg added the editor call to be discussed in the next editor call label Oct 20, 2020
@syg
Copy link
Contributor

syg commented Oct 20, 2020

Good question, I'll bring this up in the next editor call.

I like the direction @jmdyck is going with. Where the other spec built-ins are ordinary (most of the time, I hope?), MakeConstructor should be used, since the example @domenic gives, a function object F is readily available.

@domenic, is there a concrete need for, or an example of, an exotic built-in constructor that I can look at?

@devsnek
Copy link
Member

devsnek commented Oct 20, 2020

MakeConstructor is currently only valid on ECMAScript functions, since it implies OrdinaryCallEvaluateBody

@syg
Copy link
Contributor

syg commented Oct 20, 2020

MakeConstructor is currently only valid on ECMAScript functions, since it implies OrdinaryCallEvaluateBody

Ah yes, good point. Then I think it's most clear to add a path to MakeConstructor (or a new AO largely the same as MakeConstructor) that invokes the function object's [[Call]] than to have prose like "Identify F as a constructor".

@domenic
Copy link
Member Author

domenic commented Oct 20, 2020

Then I think it's most clear to add a path to MakeConstructor (or a new AO largely the same as MakeConstructor) that invokes the function object's [[Call]] than to have prose like "Identify F as a constructor".

+1, that would be very nice and explicit!

@domenic, is there a concrete need for, or an example of, an exotic built-in constructor that I can look at?

I'm not sure what this is exactly getting at. From what I can tell of the current spec, every built-in function object is exotic:

An exotic object is an object that is not an ordinary object.

An ordinary object is an object that satisfies all of the following criteria: [...] If the object has a [[Call]] internal method, it uses the one defined in 9.2.1.

and built-in functions have the [[Call]] from 9.3.1, not 9.2.1.

@ExE-Boss
Copy link
Contributor

and built-in functions have the [[Call]] from 9.3.1, not 9.2.1.

That’s only if they’re not implemented using self‑hosted JS, like what SpiderMonkey does for some of its implementation.

@syg
Copy link
Contributor

syg commented Oct 20, 2020

I'm not sure what this is exactly getting at. From what I can tell of the current spec, every built-in function object is exotic:

Sorry, the question was worded confusingly. I was wondering if there is an example of a built-in constructor in other specs where the [[Construct]] behavior is not a simple wrapper around calling [[Call]]. That is, are there examples where the [[Call]] behavior is completely separate from [[Construct]] behavior?

But I think your first sentence actually answers my question: "...we've come to the conclusion that all web platform classes should have a [[Construct]], even if it just throws a TypeError immediately".

So MakeConstructor does the simple wrapper-around-[[Call]]-behavior. Is that the intended common use case, or is passing custom steps (like throwing a TypeError) the intended common use case?

@domenic
Copy link
Member Author

domenic commented Oct 20, 2020

Sorry, the question was worded confusingly. I was wondering if there is an example of a built-in constructor in other specs where the [[Construct]] behavior is not a simple wrapper around calling [[Call]]. That is, are there examples where the [[Call]] behavior is completely separate from [[Construct]] behavior?

Ah! In that case, the answer is no: all web platform classes want to use the same delegating-to-[[Call]] behavior. In "non-constructible" cases (e.g. Node), both [[Call]] and [[Construct]] will immediately throw a TypeError.

I can see now that my OP wording was confusing. It's reflecting the history of that whatwg/webidl#698 , and not super-helpful as an introduction of the problem for this issue tracker. (Briefly: all Web IDL "non-constructible classes" have always been specified as built-in functions whose function steps are to immediately throw a TypeError. whatwg/webidl#698 was debating whether attempting to construct them should throw a TypeError because they have no [[Construct]] at all, or should throw a TypeError because they have a [[Construct]] that delegates to their [[Call]]. This is not observable when doing new Node(), but it is observable when doing an IsConstructor check on Node. We came to the conclusion to prefer the delegation version.)

@syg
Copy link
Contributor

syg commented Oct 20, 2020

Thanks, that's very helpful!

@ljharb
Copy link
Member

ljharb commented Oct 20, 2020

@domenic its not clear to me in that issue why to prefer the delegation version; can you elaborate or link me to the relevant comments?

@jmdyck
Copy link
Collaborator

jmdyck commented Oct 21, 2020

I like the direction @jmdyck is going with.

I was actually saying "Don't bother going in that direction, it's a dead end."

Where the other spec built-ins are ordinary (most of the time, I hope?), ...

My guess would be that other specs don't mandate the implementation strategy for built-ins.

Then I think it's most clear to add a path to MakeConstructor (or a new AO largely the same as MakeConstructor) that invokes the function object's [[Call]] than to have prose like "Identify F as a constructor".

Presumably you don't mean that the new AO itself would invoke the function's [[Call]], but rather that it would set the function's [[Construct]] to invoke [[Call]]. However, it's unnecessary (non-conformant) to define a new [[Construct]] -- there are only two valid [[Construct]] methods for a built-in function: 9.2.2 or 9.3.2. So the new AO would boil down to:

1. If _F_ is implemented as an ordinary function, then
  1. Set F.[[Construct]] to the definition specified in 9.2.2.
1. Else,
  1. Set F.[[Construct]] to the definition specified in 9.3.2.

I'm not sure that's worth making into an operation, but I guess it's an option.

@syg
Copy link
Contributor

syg commented Oct 21, 2020

Presumably you don't mean that the new AO itself would invoke the function's [[Call]], but rather that it would set the function's [[Construct]] to invoke [[Call]].

Yes, that's what I meant.

However, it's unnecessary (non-conformant) to define a new [[Construct]] -- there are only two valid [[Construct]] methods for a built-in function: 9.2.2 or 9.3.2. So the new AO would boil down to:

This is indeed true, but I think "Set F.[[Consntruct]] to the definition specified in 9.3.2" could use editorial improvement. My understanding is that the steps passed to https://tc39.es/ecma262/#sec-createbuiltinfunction are slotted in to step 7 of 9.3.1 via step 5 of 9.3.3. This new AO I'm envisioning would make that explicit, hopefully.

@jmdyck
Copy link
Collaborator

jmdyck commented Oct 21, 2020

Step 7 of 9.3.1 sets the ScriptOrModule of _calleeContext_, which I don't think is relevant. If you mean step 10, then yes, in part...

In CreateBuiltinFunction's step 5, it creates a new built-in function object that when called performs the action described by _steps_. So it's implicit in this creation that the function's [[Call]] is set to either 9.2.1 or 9.3.1:

  • If it's 9.2.1, then (you can imagine that) _steps_ is converted into ES code and then parsed and processed as something like a function decl, so the resulting function object's [[FormalParameters]], [[ECMAScriptCode]], etc reflect _steps_.
  • If it's 9.3.1, then in 9.3.1's step 10, where it's evaluating F in a manner that conforms to the specification of _F_, the specification of F is roughly _steps_.

(And if applicable, all that similarly for [[Construct]].)

This new AO I'm envisioning would make that explicit, hopefully.

(So this new AO would handle [[Call]] as well as [[Construct]]?)

You could easily make the selection between 9.2.1 and 9.3.1 explicit, similar to the pseudocode for choosing between 9.2.2 and 9.3.2 I gave above. But note that @ExE-Boss proposed something like that in PR #2115, and got some pushback from @bakkot.

As for the magic of how _steps_ filters down to 9.2.1 or 9.3.1: well, I guess you could make it (more) explicit, but I'm not sure it would be a net benefit.

@syg syg removed the editor call to be discussed in the next editor call label Nov 4, 2020
@domenic
Copy link
Member Author

domenic commented Mar 15, 2021

What's the latest here? It looks like maybe MakeConstructor can be used for this now?

@domenic
Copy link
Member Author

domenic commented Mar 15, 2021

Hmm, I guess not, since MakeConstructor sets [[Construct]] to 10.2.2, but we'd instead want to set it to 10.3.2 I believe.

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

No branches or pull requests

6 participants