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

Normative: Define default constructors using spec steps #2216

Merged
merged 1 commit into from
Feb 28, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 40 additions & 22 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -10736,10 +10736,14 @@ <h1>[[Call]] ( _thisArgument_, _argumentsList_ )</h1>
<p>The [[Call]] internal method of an ECMAScript function object _F_ takes arguments _thisArgument_ (an ECMAScript language value) and _argumentsList_ (a List of ECMAScript language values). It performs the following steps when called:</p>
<emu-alg>
1. Assert: _F_ is an ECMAScript function object.
1. If _F_.[[IsClassConstructor]] is *true*, throw a *TypeError* exception.
1. Let _callerContext_ be the running execution context.
1. Let _calleeContext_ be PrepareForOrdinaryCall(_F_, *undefined*).
1. Assert: _calleeContext_ is now the running execution context.
1. If _F_.[[IsClassConstructor]] is *true*, then
1. Let _error_ be a newly created *TypeError* object.
devsnek marked this conversation as resolved.
Show resolved Hide resolved
1. NOTE: _error_ is created in _calleeContext_ with _F_'s associated Realm Record.
1. Remove _calleeContext_ from the execution context stack and restore _callerContext_ as the running execution context.
1. Return ThrowCompletion(_error_).
1. Perform OrdinaryCallBindThis(_F_, _calleeContext_, _thisArgument_).
1. Let _result_ be OrdinaryCallEvaluateBody(_F_, _argumentsList_).
1. [id="step-call-pop-context-stack"] Remove _calleeContext_ from the execution context stack and restore _callerContext_ as the running execution context.
Expand Down Expand Up @@ -10917,10 +10921,11 @@ <h1>%ThrowTypeError% ( )</h1>
<h1>MakeConstructor ( _F_ [ , _writablePrototype_ [ , _prototype_ ] ] )</h1>
<p>The abstract operation MakeConstructor takes argument _F_ (a function object) and optional arguments _writablePrototype_ (a Boolean) and _prototype_ (an Object). It converts _F_ into a constructor. It performs the following steps when called:</p>
<emu-alg>
1. Assert: _F_ is an ECMAScript function object.
1. Assert: IsConstructor(_F_) is *false*.
1. Assert: _F_ is an extensible object that does not have a *"prototype"* own property.
1. Set _F_.[[Construct]] to the definition specified in <emu-xref href="#sec-ecmascript-function-objects-construct-argumentslist-newtarget"></emu-xref>.
1. Assert: _F_ is an ECMAScript function object or a built-in function object.
1. If _F_ is an ECMAScript function object, then
1. Assert: IsConstructor(_F_) is *false*.
1. Assert: _F_ is an extensible object that does not have a *"prototype"* own property.
1. Set _F_.[[Construct]] to the definition specified in <emu-xref href="#sec-ecmascript-function-objects-construct-argumentslist-newtarget"></emu-xref>.
1. Set _F_.[[ConstructorKind]] to ~base~.
1. If _writablePrototype_ is not present, set _writablePrototype_ to *true*.
1. If _prototype_ is not present, then
Expand Down Expand Up @@ -11158,7 +11163,7 @@ <h1>CreateBuiltinFunction ( _steps_, _length_, _name_, _internalSlotsList_ [ , _
<p>The abstract operation CreateBuiltinFunction takes arguments _steps_, _length_, _name_, and _internalSlotsList_ (a List of names of internal slots) and optional arguments _realm_, _prototype_, and _prefix_. _internalSlotsList_ contains the names of additional internal slots that must be defined as part of the object. This operation creates a built-in function object. It performs the following steps when called:</p>
<emu-alg>
1. Assert: _steps_ is either a set of algorithm steps or other definition of a function's behaviour provided in this specification.
1. If _realm_ is not present, set _realm_ to the current Realm Record.
1. If _realm_ is not present or _realm_ is ~empty~, set _realm_ to the current Realm Record.
1. Assert: _realm_ is a Realm Record.
1. If _prototype_ is not present, set _prototype_ to _realm_.[[Intrinsics]].[[%Function.prototype%]].
1. Let _func_ be a new built-in function object that when called performs the action described by _steps_. The new function object has internal slots whose names are the elements of _internalSlotsList_, and an [[InitialName]] internal slot.
Expand Down Expand Up @@ -20703,23 +20708,18 @@ <h1>Runtime Semantics: ClassDefinitionEvaluation</h1>
1. Let _proto_ be ! OrdinaryObjectCreate(_protoParent_).
1. If |ClassBody_opt| is not present, let _constructor_ be ~empty~.
1. Else, let _constructor_ be ConstructorMethod of |ClassBody|.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this wait for #2109 and use an abstract closure?

Copy link
Contributor

@ExE-Boss ExE-Boss Feb 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the default constructor doesn’t need to capture any variable from the current scope, so I feel like it’s unnecessary in this case.

1. If _constructor_ is ~empty~, then
1. If |ClassHeritage_opt| is present, then
1. Let _constructorText_ be the source text
<pre><code class="javascript">constructor(...args) { super(...args); }</code></pre>
1. Else,
1. Let _constructorText_ be the source text
<pre><code class="javascript">constructor() {}</code></pre>
1. Set _constructor_ to ParseText(_constructorText_, |MethodDefinition[~Yield, ~Await]|).
1. Assert: _constructor_ is a Parse Node.
1. Set the running execution context's LexicalEnvironment to _classScope_.
devsnek marked this conversation as resolved.
Show resolved Hide resolved
devsnek marked this conversation as resolved.
Show resolved Hide resolved
devsnek marked this conversation as resolved.
Show resolved Hide resolved
ljharb marked this conversation as resolved.
Show resolved Hide resolved
1. Let _constructorInfo_ be ! DefineMethod of _constructor_ with arguments _proto_ and _constructorParent_.
1. Let _F_ be _constructorInfo_.[[Closure]].
1. Perform SetFunctionName(_F_, _className_).
1. Perform MakeConstructor(_F_, *false*, _proto_).
1. If _constructor_ is ~empty~, then
1. Let _steps_ be the algorithm steps defined in <emu-xref href="#sec-default-constructor-functions" title></emu-xref>.
devsnek marked this conversation as resolved.
Show resolved Hide resolved
1. Let _F_ be ! CreateBuiltinFunction(_steps_, 0, _className_, &laquo; [[ConstructorKind]], [[SourceText]] &raquo;, ~empty~, _constructorParent_).
1. Else,
1. Let _constructorInfo_ be ! DefineMethod of _constructor_ with arguments _proto_ and _constructorParent_.
1. Let _F_ be _constructorInfo_.[[Closure]].
1. Perform ! MakeClassConstructor(_F_).
1. Perform ! SetFunctionName(_F_, _className_).
1. Perform ! MakeConstructor(_F_, *false*, _proto_).
1. If |ClassHeritage_opt| is present, set _F_.[[ConstructorKind]] to ~derived~.
1. Perform MakeClassConstructor(_F_).
1. Perform CreateMethodProperty(_proto_, *"constructor"*, _F_).
1. Perform ! CreateMethodProperty(_proto_, *"constructor"*, _F_).
devsnek marked this conversation as resolved.
Show resolved Hide resolved
1. If |ClassBody_opt| is not present, let _methods_ be a new empty List.
1. Else, let _methods_ be NonConstructorMethodDefinitions of |ClassBody|.
1. For each |ClassElement| _m_ of _methods_, do
Expand All @@ -20735,6 +20735,24 @@ <h1>Runtime Semantics: ClassDefinitionEvaluation</h1>
1. Perform _classScope_.InitializeBinding(_classBinding_, _F_).
1. Return _F_.
</emu-alg>

<emu-clause id="sec-default-constructor-functions">
<h1>Default Constructor Functions</h1>
<p>When a Default Constructor Function is called with zero or more arguments which form the rest parameter ..._args_, the following steps are taken:</p>
<emu-alg>
1. If NewTarget is *undefined*, throw a *TypeError* exception.
ljharb marked this conversation as resolved.
Show resolved Hide resolved
1. Let _F_ be the active function object.
1. If _F_.[[ConstructorKind]] is ~derived~, then
1. NOTE: This branch behaves similarly to `constructor(...args) { super(...args); }`. The most notable distinction is that while the aforementioned ECMAScript source text observably calls the @@iterator method on `%Array.prototype%`, a Default Constructor Function does not.
1. Let _func_ be ! _F_.[[GetPrototypeOf]]().
1. If IsConstructor(_func_) is *false*, throw a *TypeError* exception.
1. Return ? Construct(_func_, _args_, NewTarget).
devsnek marked this conversation as resolved.
Show resolved Hide resolved
1. Else,
1. NOTE: This branch behaves similarly to `constructor() {}`.
1. Return ? OrdinaryCreateFromConstructor(NewTarget, *"%Object.prototype%"*).
</emu-alg>
<p>The *"length"* property of a default constructor function is *+0*<sub>𝔽</sub>.</p>
</emu-clause>
</emu-clause>

<emu-clause id="sec-runtime-semantics-bindingclassdeclarationevaluation" type="sdo" aoid="BindingClassDeclarationEvaluation">
Expand Down Expand Up @@ -25192,9 +25210,9 @@ <h1>Function.prototype.toString ( )</h1>
<p>When the `toString` method is called, the following steps are taken:</p>
<emu-alg>
1. Let _func_ be the *this* value.
1. If _func_ is a <emu-xref href="#sec-built-in-function-objects">built-in function object</emu-xref>, return an implementation-defined String source code representation of _func_. The representation must have the syntax of a |NativeFunction|. Additionally, if _func_ has an [[InitialName]] internal slot and _func_.[[InitialName]] is a String, the portion of the returned String that would be matched by |NativeFunctionAccessor?| |PropertyName| must be the value of _func_.[[InitialName]].
Copy link

@bergus bergus Jan 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Coming from #2608 (comment))

I see why this change was necessary: default constructors that are created using CreateBuiltinFunction now are "built-in functions" and have an [[InitialName]] slot; however they should still return the [[SourceText]] from the user's program.

But the new spec text does permit implementations to return the [[SourceText]] for basically arbitrary built-in functions now, especially for those that are implemented as ECMAScript function objects. I do not think that change was intentional, I believe that all functions which are not defined by user code (but rather, by the implementation) should have to return a string in NativeFunction format. At least this was listed as a goal in the Function.prototype.toString revision proposal.

Would it be sensible to rollback this swapping of Function.prototype.toString steps, and instead change CreateBuiltinFunction to not create an [[InitialName]] slot for default constructors?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would implementations be forced to omit source text? An engine should always be free to provide meaningful source text in .toString if it so chooses.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's the (not unreasonable) general expectation that native functions can be detected by looking for [native code] in the toString representation, and this is used in many codebases. See https://davidwalsh.name/detect-native-function or https://stackoverflow.com/questions/6598945/detect-if-function-is-native-to-browser for reference.
The Function.prototype.toString revision proposal did mean to "standardise the string representation of built-in functions and host objects". There has gone a lot of thought into this, and there were requests to also ensure proper function name representations and even exact whitespace patterns in the NativeFunction syntax.

If you want to change this, fine, but it seems this happened without discussion in TC39, likely even by accident.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is an unreasonable expectation, since (function () {}).bind() produces the same toString representation.

To my recollection, it was very much not an accident to permit native functions to have any source text they want, as long as it's either valid ecmascript, or, the native code representation.

1. If Type(_func_) is Object and _func_ has a [[SourceText]] internal slot and _func_.[[SourceText]] is a sequence of Unicode code points and ! HostHasSourceTextAvailable(_func_) is *true*, then
1. Return ! CodePointsToString(_func_.[[SourceText]]).
1. If _func_ is a <emu-xref href="#sec-built-in-function-objects">built-in function object</emu-xref>, return an implementation-defined String source code representation of _func_. The representation must have the syntax of a |NativeFunction|. Additionally, if _func_ has an [[InitialName]] internal slot and _func_.[[InitialName]] is a String, the portion of the returned String that would be matched by |NativeFunctionAccessor?| |PropertyName| must be the value of _func_.[[InitialName]].
1. If Type(_func_) is Object and IsCallable(_func_) is *true*, return an implementation-defined String source code representation of _func_. The representation must have the syntax of a |NativeFunction|.
1. Throw a *TypeError* exception.
</emu-alg>
Expand Down