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: Constructor may be more concisely defined. #2055

Open
Huxpro opened this issue Jun 18, 2020 · 13 comments
Open

Normative: Constructor may be more concisely defined. #2055

Huxpro opened this issue Jun 18, 2020 · 13 comments

Comments

@Huxpro
Copy link
Member

Huxpro commented Jun 18, 2020

The current spec defined constructor as such:

A function object is an object that supports the [[Call]] internal method.

A constructor is an object that supports the [[Construct]] internal method.
Every object that supports [[Construct]] must support [[Call]];
that is, every constructor must be a function object.

Therefore, a constructor may also be referred to as a constructor function or constructor function object.
-- https://tc39.es/ecma262/#constructor

The bolded part seems to be unnecessarily verbose and complicated.
Can we just simplify the bolded part as below?

A constructor is a function object that supports the [[Construct]] internal method.

I'm more than happy to (figure out how to) send a PR if ppl liked the idea.

@michaelficarra
Copy link
Member

Your proposed wording permits objects that have a [[Construct]] but no [[Call]]. I'm actually not opposed to that, but it is a normative change, not just a simplified wording as you state.

@ljharb
Copy link
Member

ljharb commented Jun 18, 2020

I strongly support this change, but it would be normative unless we did some hijinx, and it would likely require some changes to Proxy, where these slots are observable (forcing it to be normative).

@jmdyck
Copy link
Collaborator

jmdyck commented Jun 18, 2020

Your proposed wording permits objects that have a [[Construct]] but no [[Call]].

No, such an object, having no [[Call]], would not satisfy the definition of a function, so would not be included by the phrase "a function object that supports ...". So the proposed change isn't the change you think it is.

The problem is actually that it doesn't address the question of an object that supports [[Construct]] but not [[Call]]: the current wording says such an object cannot exist; the proposed wording does not. See PR #1187 and #1196. (Whether this is a normative change is debateable. 1187 and 1196 were labelled "editorial".)

(Of course, this all changes if/when we get the normative change that @michaelficarra and @ljharb have in mind.)

@michaelficarra
Copy link
Member

michaelficarra commented Jun 18, 2020

@jmdyck I was not using the word "permits" as a synonym for "includes". I meant the new text does not prohibit such an object from existing, as the current text does.

@jmdyck
Copy link
Collaborator

jmdyck commented Jun 18, 2020

Yup, I just realized that. Sorry for the noise. (Still, the links to old PRs may be useful for the OP.)

@Huxpro
Copy link
Member Author

Huxpro commented Jun 19, 2020

OH so It was actually using "function object" before PR #1187. From what I understood from https://stackoverflow.com/questions/50109554/, that change was identified as an "editorial change" because it only made an implicit assumption explicit?

Your proposed wording permits objects that have a [[Construct]] but no [[Call]]. I'm actually not opposed to that, but it is a normative change, not just a simplified wording as you state.

I'm trying to understand the effects of permitting such object from existing.
Isn't there always infinite of invalid objects out there?
Would that be fine as long as we invalidate them from the isConstructor check?

I strongly support this change, but it would be normative unless we did some hijinx, and it would likely require some changes to Proxy, where these slots are observable (forcing it to be normative).

Ah, I wasn't aware they could be observable externally.
Can anyone point out the detail?
Does people rely on such behaviors?

@ljharb
Copy link
Member

ljharb commented Jun 19, 2020

A good chunk of the internet relies on Reflect.construct checking for the presence of [[Construct]] slot, for one: https://github.com/ljharb/es-abstract/blob/d959e6d039b4a186f4e3f13f7968d9a44bce9022/2015/IsConstructor.js#L27-L34

I did some testing with new Proxy and it turns out that it doesn't throw as I thought (for example, new Proxy({ a() {} }.a, { apply() {}, construct() {} }) works fine as does new Proxy(function * () {}, { apply() {}, construct() {} }) etc) - but the current spec text would need altering, since it only currently checks IsConstructor if IsCallable is also true.

@jmdyck
Copy link
Collaborator

jmdyck commented Jun 19, 2020

OH so It was actually using "function object" before PR #1187. From what I understood from https://stackoverflow.com/questions/50109554/, that change was identified as an "editorial change" because it only made an implicit assumption explicit?

Yup. (Or, at least, that was my take, and no-one disagreed.)

Your proposed wording permits objects that have a [[Construct]] but no [[Call]]. I'm actually not opposed to that, but it is a normative change, not just a simplified wording as you state.

I'm trying to understand the effects of permitting such object from existing.
Isn't there always infinite of invalid objects out there?

You are free to imagine that such objects exist "out there", but the spec requires implementations to behave in such a way that you can never observe the existence of such objects, so I'm not sure there's any benefit to imagining that they do exist.

@Huxpro Huxpro changed the title Editorial: Constructor can be more concisely defined. Normative: Constructor may be more concisely defined. Jul 7, 2020
@hax
Copy link
Member

hax commented Nov 13, 2021

This is an interesting issue, several days ago someone asked what's "constructor" really mean.

In the spec, BigInt and Symbol are constructors (20.4.1 The Symbol Constructor, 21.2.1 The BigInt Constructor), but not truely match the "constructors" definition as https://tc39.es/ecma262/#sec-constructor

function object that creates and initializes objects

--- they creates primitive values not objects. And engines like V8 give the error info "BigInt/Symbol is not a constructor" when new BigInt/Symbol. Actually I even don't know whether Symbol/BigInt don't have [[Construct]] (seems V8 error info implies that) or they have [[Construct]] but always throws.

@jmdyck
Copy link
Collaborator

jmdyck commented Nov 13, 2021

In the spec, BigInt and Symbol are constructors (20.4.1 The Symbol Constructor, 21.2.1 The BigInt Constructor), but not truely match the "constructors" definition as https://tc39.es/ecma262/#sec-constructor

function object that creates and initializes objects

Yeah, this is an example where the definition in "4.4 Terms and Definitions" conveys the general idea, but doesn't really match the precise definition in the body of the spec.

--- they creates primitive values not objects. And engines like V8 give the error info "BigInt/Symbol is not a constructor" when new BigInt/Symbol. Actually I even don't know whether Symbol/BigInt don't have [[Construct]] (seems V8 error info implies that) or they have [[Construct]] but always throws.

According to the definition in 6.1.7.2 (which started off this issue), a constructor is an object that supports the [[Construct]] internal method. And the spec definitely refers to Symbol and BigInt as constructors, so that implies they support [[Construct]].

Moreover, in each case, the algorithm says If NewTarget is not *undefined*. When a built-in function's [[Call]] is invoked, NewTarget is always *undefined*, so the only way that this test could succeed is if the [[Construct]] were invoked. (Which isn't definitive, but lends support.)


Some other spec wording re [[Construct]] that might be nice to clean up:

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

which suggests that every built-in function has both [[Call]] and [[Construct]], though in some cases, the [[Construct]] can't be invoked. That interpretation doesn't really fit with anything else in the spec.

Also, 10.3 and 18 ECMAScript Standard Built-in Objects both say:

Built-in function objects that are not identified as constructors do not implement the [[Construct]] internal method ...

which is fine, but they both go on to say:

... unless otherwise specified in the description of a particular function.

which allows for a function that is not identified as a constructor but nevertheless is specified to implement [[Construct]]. Which is a bit odd. I'm pretty sure there's no such thing in the spec. In fact, I don't even think there's a built-in function that we specifically say implements [[Construct]], regardless of whether we identify it as a constructor. So I'm not sure why that "unless" clause is there. (It dates back to ES6, so maybe made more sense then.)

@ljharb
Copy link
Member

ljharb commented Nov 13, 2021

In particular, it's super weird that "things you can new successfully" aren't the only things in the spec with [[Construct]], and "things you can Call successfully" aren't the only things in the spec with [[Call]]. It makes having two slots kind of useless when their presence can't be used to determine what kind of thing something is.

@hax
Copy link
Member

hax commented Nov 14, 2021

After some research, I noticed that Symbol must implement [[Construct]], because it is said "may be used as the value of an extends clause of a class definition but a super call to it will cause an exception", and extends X requires X have [[Construct]]. So Symbol must be a constructor (as the spec definition), maybe the engines should "fix" the error info (instead of confusing "Symbol is not a constructor", it could say "Symbol does not support new operator"...)

@hax
Copy link
Member

hax commented Nov 14, 2021

In particular, it's super weird that "things you can new successfully" aren't the only things in the spec with [[Construct]], and "things you can Call successfully" aren't the only things in the spec with [[Call]].

Yeah, it's weird, and I guess we can do nothing to "fix" it.

Besides Symbol, BigInt and other "constructors" for primitives, there are also some use cases of [[Construct]] but can't new, very common in platform APIs, they could be think as classes with private/protected constructors in other programming languages.

It makes having two slots kind of useless when their presence can't be used to determine what kind of thing something is.

Maybe we can try to advance Function.isConstructor/isCallable proposal (though we need better names)?

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

5 participants