ecma262
ecma262 copied to clipboard
Drop the explicit function length and always use the implicit function length
derived from https://github.com/tc39/proposal-idl/issues/9#issuecomment-2921079717
While looking into introducing IDL into ECMAScript, the existence of the explicit function length ('The "length" property of this function is ...' paragraph') becomes slightly problematic, especially when the length doesn't match the parameter notation, given it requires extra annotation on the IDL.
If the length property is not programatically used in wild, dropping the explicit function length and always using the implicit length will make things simpler.
Here's the list of affected cases:
| Function | Explicit length | Implicit length |
|---|---|---|
Object ( [ value ] ) |
1 | 0 |
Object.assign ( target, ...sources ) |
2 | 1 |
Number.prototype.toString ( [ radix ] ) |
1 | 0 |
Math.hypot ( ...args ) |
2 | 0 |
Math.max ( ...args ) |
2 | 0 |
Math.min ( ...args ) |
2 | 0 |
Date ( ...values ) |
7 | 0 |
Date.UTC ( year [ , month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ] ] ) |
7 | 1 |
Date.prototype.setFullYear ( year [ , month [ , date ] ] ) |
3 | 1 |
Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
4 | 1 |
Date.prototype.setMinutes ( min [ , sec [ , ms ] ] ) |
3 | 1 |
Date.prototype.setMonth ( month [ , date ] ) |
2 | 1 |
Date.prototype.setSeconds ( sec [ , ms ] ) |
2 | 1 |
Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] ) |
3 | 1 |
Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
4 | 1 |
Date.prototype.setUTCMinutes ( min [ , sec [ , ms ] ] ) |
3 | 1 |
Date.prototype.setUTCMonth ( month [ , date ] ) |
2 | 1 |
Date.prototype.setUTCSeconds ( sec [ , ms ] ) |
2 | 1 |
String.fromCharCode ( ...codeUnits ) |
1 | 0 |
String.fromCodePoint ( ...codePoints ) |
1 | 0 |
String.prototype.concat ( ...args ) |
1 | 0 |
Array ( ...values ) |
1 | 0 |
Array.prototype.concat ( ...items ) |
1 | 0 |
Array.prototype.push ( ...items ) |
1 | 0 |
Array.prototype.unshift ( ...items ) |
1 | 0 |
Int8Array ( ...args ) |
3 | 0 |
Uint8Array ( ...args ) |
3 | 0 |
Uint8ClampedArray ( ...args ) |
3 | 0 |
Int16Array ( ...args ) |
3 | 0 |
Uint16Array ( ...args ) |
3 | 0 |
Int32Array ( ...args ) |
3 | 0 |
Uint32Array ( ...args ) |
3 | 0 |
BigInt64Array ( ...args ) |
3 | 0 |
BigUint64Array ( ...args ) |
3 | 0 |
Float16Array ( ...args ) |
3 | 0 |
Float32Array ( ...args ) |
3 | 0 |
Float64Array ( ...args ) |
3 | 0 |
JSON.parse ( text [ , reviver ] ) |
2 | 1 |
JSON.stringify ( value [ , replacer [ , space ] ] ) |
3 | 1 |
Array, Date, _TypedArray_s are using a kind of overloading (https://github.com/tc39/ecma262/pull/2176), and they may want to use different notation given introducing IDL will allow overloading.
Also, Function-related constructors are possibly-affected. I'm not sure if the implicit length should take the bodyArg into account or not.
At least WebIDL doesn't allow non-optional parameter after variadic parameter.
| Function | Explicit length | Implicit length |
|---|---|---|
Function ( ...parameterArgs, bodyArg ) |
1 | 0? |
GeneratorFunction ( ...parameterArgs, bodyArg ) |
1 | 0? |
AsyncGeneratorFunction ( ...parameterArgs, bodyArg ) |
1 | 0? |
AsyncFunction ( ...parameterArgs, bodyArg ) |
1 | 0? |
Also, there are some cases where the explicit length match the implicit length, which could just simply be removed:
%TypedArray% ()- (ECMA402)
String.prototype.localeCompare ( that [ , locales [ , options ] ] )
If necessary, we could look into telemetry or some script corpus to see if the built-in function length is actually accessed by scripts in wild, to see the impact of this change.
https://github.com/tc39/ecma262/issues/264 suggests that modifying the built-in functions' length properties won't cause too much web-compat issues.
also, https://github.com/tc39/ecma262/issues/1078 suggests we might still want some explicit length property for anonymous function, but the specific case touched by the issue is already rewritten with CreateBuiltinFunction call.
Could we instead update those functions to not mark the parameters as optional, so that the "signature" matches the expected length?
Good point.
Here's possible replacement for each case:
(A) optional to non-optional
Some functions just need to mark parameters non-optional.
| Current | Explicit length | Modified |
|---|---|---|
Object ( [ value ] ) |
1 | ( value ) |
Number.prototype.toString ( [ radix ] ) |
1 | ( radix ) |
Date.UTC ( year [ , month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ] ] ) |
7 | ( year, month, date, hours, minutes, seconds, ms ) |
Date.prototype.setFullYear ( year [ , month [ , date ] ] ) |
3 | ( year, month, date ) |
Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
4 | ( hour, min, sec, ms ) |
Date.prototype.setMinutes ( min [ , sec [ , ms ] ] ) |
3 | ( min, sec, ms ) |
Date.prototype.setMonth ( month [ , date ] ) |
2 | ( month, date ) |
Date.prototype.setSeconds ( sec [ , ms ] ) |
2 | ( sec, ms ) |
Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] ) |
3 | ( year, month, date ) |
Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
4 | ( hour, min, sec, ms ) |
Date.prototype.setUTCMinutes ( min [ , sec [ , ms ] ] ) |
3 | ( min, sec, ms ) |
Date.prototype.setUTCMonth ( month [ , date ] ) |
2 | ( month, date ) |
Date.prototype.setUTCSeconds ( sec [ , ms ] ) |
2 | ( sec, ms ) |
JSON.parse ( text [ , reviver ] ) |
2 | ( text, reviver ) |
JSON.stringify ( value [ , replacer [ , space ] ] ) |
3 | ( value, replacer, space ) |
(B) variadic to non-optional
Functions with variadic parameters need to extract one or two parameters from the variadic.
| Current | Explicit length | Modified |
|---|---|---|
Object.assign ( target, ...sources ) |
2 | ( target, source0, ...sources ) |
Math.hypot ( ...args ) |
2 | ( arg0, arg1, ...args ) |
Math.max ( ...args ) |
2 | ( arg0, arg1, ...args ) |
Math.min ( ...args ) |
2 | ( arg0, arg1, ...args ) |
String.fromCharCode ( ...codeUnits ) |
1 | ( codeUnit0, ...codeUnits ) |
String.fromCodePoint ( ...codePoints ) |
1 | ( codePoints0, ...codePoints ) |
String.prototype.concat ( ...args ) |
1 | ( arg0, ...args ) |
Array.prototype.concat ( ...items ) |
1 | ( item0, ...items ) |
Array.prototype.push ( ...items ) |
1 | ( item0, ...items ) |
Array.prototype.unshift ( ...items ) |
1 | ( item0, ...items ) |
(C) overloaded
As mentioned above, these functions are overloaded, and to my understanding the length comes from the following: (so, reverting to the overloaded notation will make the length match):
| Current | Explicit length | Modified |
|---|---|---|
Array ( ...values ) |
1 | ( lenOrValue0, ...values ) |
Date ( ...values ) |
7 | ( yearOrValue, month, date, hours, minutes, seconds, ms ) |
Int8Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Uint8Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Uint8ClampedArray ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Int16Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Uint16Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Int32Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Uint32Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
BigInt64Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
BigUint64Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Float16Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Float32Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Float64Array ( ...args ) |
3 | ( buffer, byteOffset, length ) |
Function-related constructors will need special handling anyway, so I'll leave them.
Anyway, the (A) cases could use the non-optional way easily. The (B) cases will require a bit of rewrite in the spec steps, but it won't be too problematic.
The lengths are always used in the wild, by polyfills if by nothing else, they can’t and shouldn’t change. Additionally, we should never be introducing observable changes merely to satisfy the needs of any spec DSL.
Additionally, we should never be introducing observable changes merely to satisfy the needs of any spec DSL.
I want to second this. Even having the risk of incentive to make design decisions based on constraints of an IDL is a sufficient enough argument for me to oppose adopting such a rigid framework. Rigid frameworks are great when their rigidity reinforces agreed-upon design goals. They are not good when they were developed independently and don't perfectly fit.
We should not pursue a normative change to the length, as the length was chosen by a person to be the best way to communicate the number of expected inputs. I'll discuss with the other editors tomorrow whether we should refactor the signatures to match the prescribed function lengths, as suggested by @nicolo-ribaudo.
At editor call today, we decided we'll go over each of the identified built-ins and determine on an individual basis whether it's appropriate to change the signature to better describe the expected/common usage, possibly aligning with the chosen length.
Thank you all for looking into it. Is there anything I can do for modifying the signature?
The length of existing methods can't be changed imo, ever, modulo adding an additional required argument when available/applicable. If a spec DSL can't handle the normative reality, the spec DSL needs to change.
Editor call:
| Function | Make optional parameters required? |
|---|---|
Object ( [ value ] ) |
Yes |
Object.assign ( target, ...sources ) |
No |
Number.prototype.toString ( [ radix ] ) |
No |
Math.hypot ( ...args ) |
No |
Math.max ( ...args ) |
No |
Math.min ( ...args ) |
No |
Date ( ...values ) |
No, but overloaded signature should be improved |
Date.UTC ( year [ , month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ] ] ) |
No |
Date.prototype.setFullYear ( year [ , month [ , date ] ] ) |
No |
Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
No |
Date.prototype.setMinutes ( min [ , sec [ , ms ] ] ) |
No |
Date.prototype.setMonth ( month [ , date ] ) |
No |
Date.prototype.setSeconds ( sec [ , ms ] ) |
No |
Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] ) |
No |
Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] ) |
No |
Date.prototype.setUTCMinutes ( min [ , sec [ , ms ] ] ) |
No |
Date.prototype.setUTCMonth ( month [ , date ] ) |
No |
Date.prototype.setUTCSeconds ( sec [ , ms ] ) |
No |
String.fromCharCode ( ...codeUnits ) |
No |
String.fromCodePoint ( ...codePoints ) |
No |
String.prototype.concat ( ...args ) |
No |
Array ( ...values ) |
No, but overloaded signature should be improved |
Array.prototype.concat ( ...items ) |
No |
Array.prototype.push ( ...items ) |
No |
Array.prototype.unshift ( ...items ) |
No |
Int8Array ( ...args ) |
No, but overloaded signature should be improved |
Uint8Array ( ...args ) |
No, |
Uint8ClampedArray ( ...args ) |
No, but overloaded signature should be improved |
Int16Array ( ...args ) |
No, but overloaded signature should be improved |
Uint16Array ( ...args ) |
No, but overloaded signature should be improved |
Int32Array ( ...args ) |
No, but overloaded signature should be improved |
Uint32Array ( ...args ) |
No, but overloaded signature should be improved |
BigInt64Array ( ...args ) |
No, but overloaded signature should be improved |
BigUint64Array ( ...args ) |
No, but overloaded signature should be improved |
Float16Array ( ...args ) |
No, but overloaded signature should be improved |
Float32Array ( ...args ) |
No, but overloaded signature should be improved |
Float64Array ( ...args ) |
No, but overloaded signature should be improved |
JSON.parse ( text [ , reviver ] ) |
No |
JSON.stringify ( value [ , replacer [ , space ] ] ) |
No |
In particular, for "overloaded signature should be improved", our current idea is to add a non-normative note section that spells out the different overloaded signatures.
We've also decided to leave all the Function constructor signatures as is.