ecma262 icon indicating copy to clipboard operation
ecma262 copied to clipboard

Drop the explicit function length and always use the implicit function length

Open arai-a opened this issue 5 months ago • 5 comments

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.

arai-a avatar Jun 24 '25 08:06 arai-a

Could we instead update those functions to not mark the parameters as optional, so that the "signature" matches the expected length?

nicolo-ribaudo avatar Jun 24 '25 12:06 nicolo-ribaudo

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.

arai-a avatar Jun 24 '25 12:06 arai-a

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.

ljharb avatar Jun 24 '25 15:06 ljharb

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.

michaelficarra avatar Jun 24 '25 16:06 michaelficarra

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.

michaelficarra avatar Jun 25 '25 22:06 michaelficarra

Thank you all for looking into it. Is there anything I can do for modifying the signature?

arai-a avatar Jul 01 '25 08:07 arai-a

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.

ljharb avatar Jul 01 '25 16:07 ljharb

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.

syg avatar Aug 18 '25 19:08 syg