Search code examples
javascriptconstructornew-operatorprimitive

Can a constructor ever return a primitive?


I'm asking this question because I've noticed that TypeScript allows declaring constructors that return primitive types, e.g.:

type Constructor1 = new () => string; // Primitive string

as opposed to

type Constructor2 = new () => String; // String object

This made me wonder if JavaScript actually permits creating a function that returns a primitive value when invoked with new semantics, i.e. a value that passes the primitiveness test:

function isPrimitive(value) {
    return value !== Object(value);
}

Needless to say, I could not find any example of a constructor invocation that produces a primitive value, so I imagine this must be just another weirdness of the TypeScript type model. Or does a primitive constructor really exist?


For reference, this is what I have tried out.

Predefined constructors

The predefined constructors Number, Boolean, String, etc. all produce an object when invoked with new, although they return a primitive value when called as regular functions. i.e.

isPrimitive(new Number()) // false

isPrimitive(Number())     // true

function isPrimitive(value) {
    return value !== Object(value);
}

console.log(isPrimitive(new Number()));
console.log(isPrimitive(Number()));

return in constructor

A return statement overrides the instance of this in the constructor, but only if the return value is an object:

const OBJECT = { foo: "bar" };
const PRIMITIVE = "baz";

function TheObject() {
    return OBJECT;
}

function ThePrimitive() {
    return PRIMITIVE;
}

console.log(isPrimitive(new TheObject()));    // prints false
console.log(isPrimitive(new ThePrimitive())); // prints false

function isPrimitive(value) {
    return value !== Object(value);
}

const OBJECT = { foo: "bar" };
const PRIMITIVE = "baz";

function TheObject() {
    return OBJECT;
}

function ThePrimitive() {
    return PRIMITIVE;
}

console.log(isPrimitive(new TheObject()));    // prints false
console.log(isPrimitive(new ThePrimitive())); // prints false

construct trap

A proxy can provide a construct trap to handle invocations to a function with new syntax. Whatever object the trap returns will be also returned by the constructor invocation. But, if a trap returns a primitive value other than undefined, a TypeError occurs.

const FooConstructor = new Proxy(
    class { },
    { construct: () => 'foo' }
);

new FooConstructor(); // throws TypeError: proxy [[Construct]] must return an object

function isPrimitive(value) {
    return value !== Object(value);
}

const FooConstructor = new Proxy(
    class { },
    { construct: () => 'foo' }
);

new FooConstructor();


More ideas?


Solution

  • Can a constructor ever return a primitive?

    The ECMAScript specification defines a constructor as:

    ...an object that supports the [[Construct]] internal method.

    Although exotic objects have some liberty in implementing internal methods, the specification states at 6.1.7.3 Invariants of the Essential Internal Methods:

    The Internal Methods of Objects of an ECMAScript engine must conform to the list of invariants specified below. Ordinary ECMAScript Objects as well as all standard exotic objects in this specification maintain these invariants. ECMAScript Proxy objects maintain these invariants by means of runtime checks on the result of traps invoked on the [[ProxyHandler]] object.

    Any implementation provided exotic objects must also maintain these invariants for those objects. Violation of these invariants may cause ECMAScript code to have unpredictable behaviour and create security issues. However, violation of these invariants must never compromise the memory safety of an implementation.

    An implementation must not allow these invariants to be circumvented in any manner such as by providing alternative interfaces that implement the functionality of the essential internal methods without enforcing their invariants.

    [...]

    The value returned by any internal method must be a Completion Record with either:

    • [[Type]] = normal, [[Target]] = empty, and [[Value]] = a value of the "normal return type" shown below for that internal method, or
    • [[Type]] = throw, [[Target]] = empty, and [[Value]] = any ECMAScript language value.

    [...]

    [[Construct]] ( )

    • The normal return type is Object.

    [...]

    So in conclusion, a compliant ECMAScript implementation does not allow the return value of the [[Construct]] internal method to be a primitive.

    Take note that "normal return type" has a specific meaning here, which is also introduced in the quote above. "Normal" here refers to the case where no error was thrown.


    You also phrased your question like this:

    This made me wonder if JavaScript actually permits creating a function that returns a primitive value when invoked with new semantics

    At 13.3.5 The new Operator, the specification stipulates that the Construct procedure is executed (if all checks pass):

    1. Return ? Construct(constructor, argList).

    And the procedure at 7.3.15 Construct ( F [ , argumentsList [ , newTarget ] ] ) in turn specifies:

    1. Return ? F.[[Construct]](argumentsList, newTarget).

    So the new operator will lead to the execution of the [[Construct]] internal method, and so the above applies.