jasnell / proposal-istypes

TC-39 Proposal for additional is{Type} APIs
201 stars 7 forks source link

Builtin.typeof(function() {}), 'function' or 'Function'? Same for Builtin.typeof({}) #28

Closed Jamesernator closed 7 years ago

Jamesernator commented 7 years ago

I think it makes more sense for it to be "Function", the reason for this is that the definition of typeof for "function" is essentially just any object with a [[Call]] internal property.

This is a bit confusing when AsyncFunction, GeneratorFunction (and eventually AsyncGeneratorFunction) all also have typeof "function", it seems a bit odd to have this more general typeof value to mean just Function instances.

I feel the same applies to object/Object as well for similar reasons.

I'd rather see the method called Builtin.type and have it by default return the constructor name for object types only using the lower case form for primitive types.

ljharb commented 7 years ago

I'm confused - all of these things are Function instances, typeof function, and have a [[Call]] internal slot. Why would you expect Builtin.typeof to give you a different result than typeof here?

ljharb commented 7 years ago

(specifically, generator functions and async functions are all instanceof Function)

Jamesernator commented 7 years ago

I just feel it's a bit weird that "function" is essentially the brand of instances of Function even though any exotic could in theory have a [[Call]] property.

Given that the point of adding Builtin.typeof is mostly to check constructors cross-realm it doesn't make sense to me that the function would return anything but the name of the constructor used to create the object (and in the case of primitives these have no constructor, so they use typeof).

It just seems silly to me that this would have a mix of typeof/constructor names for strictly object types:

function cloneToRealm(value) {
    // Yes I know some of these are essentially impossible
    switch (`Builtin.typeof(value)`) {
        case 'Uint8Array':
            ..
        ...
        case 'GeneratorFunction':

        case 'function':
            // Not a primitive yet looks like it's one
        case 'object':
            // Same thing with object       

        case 'number':
        case 'symbol':
        ...
            // primitives are obvious and distinct as they don't use
            // the CapWords naming scheme
            return value
}

// or this just seems weirdly inconsistent:

// in realm
const isFunction = func instanceof Function
const isMap = map instanceof Map

// vs cross-realm
const isFunction = Builtin.typeof(func) === 'function'
const isMap = Builtin.typeof(map) === 'Map'

That's why I also suggested it should just be called Builtin.type instead of Builtin.typeof.

ljharb commented 7 years ago

Any exotic with [[Call]] is supposed to be typeof function.

jasnell commented 7 years ago

Closing this. The behavior as currently written is as it should be

Jamesernator commented 7 years ago

My point was the opposite way around, it's that a typeof x === 'function' does not imply that x was constructed from Function which is why I think Function[Symbol.builtin] should return "Function" not 'function'.

e.g.:

const f = _ => {}
Object.setPrototypeOf(f, {})

typeof f === 'function' // true
Builtin.typeof(f) === 'function' // false, seems weirdly inconsistent
                                 // why bother using the typeof
                                 // values at all if it isn't even
                                 // consistent with typeof

Similarly for object:

typeof {} === 'object' // true
typeof Object.create(null) === 'object' // true

Builtin.typeof({}) === 'object' // true
Builtin.typeof(Object.create(null)) // false, seems weirdly inconsistent again

It'd make a lot more sense to me if both of those instead were checking for the constructor name e.g.:

Builtin.type({}) === "Object" // true, this is the name of the builtin
                              // that constructed the object
Builtin.type(_ => {}) === "Function" // true

const f = _ => {}
Object.setPrototypeOf(f, {})     // no longer inherits from Function so
Builtin.type(f) === 'Function'   // it isn't a surprise this is false

typeof f === 'function' // whereas this is still true because it remains
                        // [[Call]]-able

If the typeof route is going to be taken, I'd at least like to see some justification as to why Builtin.typeof/typeof aren't consistent yet use the names to mean different things (typeof using "function" to mean [[Call]]-able, vs Builtin.typeof using "function" to mean instanceof (in a cross-realm way) of Function).

ljharb commented 7 years ago

Function.prototype[Symbol.builtin].call(f) === 'function' would still be true, however.