Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 709
GOTO 0 Avatar asked Dec 02 '21 07:12

GOTO 0


People also ask

Can a JavaScript constructor return a primitive value as a number or a string )?

Basically if your constructor returns a primitive value, such as a string, number, boolean, null or undefined, (or you don't return anything which is equivalent to returning undefined ), a newly created object that inherits from the constructor's prototype will be returned.

Can a constructor return anything?

No, constructor does not return any value. While declaring a constructor you will not have anything like return type. In general, Constructor is implicitly called at the time of instantiation. And it is not a method, its sole purpose is to initialize the instance variables.

Can functions return a primitive type?

In addition to primitive data types, methods (functions) can return the class objects.

Can constructor return a value in C++?

You do not specify a return type for a constructor. A return statement in the body of a constructor cannot have a return value.


2 Answers

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.

like image 120
trincot Avatar answered Sep 30 '22 02:09

trincot


A constructor function can return anything it wants, including primitives. It most often does not return anything, resulting in the primitive undefined.

However, any invocation of a constructor with new will follow the well-established rules and always return an object. This is declared in the specification for the internal [[construct]] method, which has the signature (a List of any, Object) → Object and is also specified with the clear invariant

[[Construct]]()

  • The normal return type is Object.
  • The target must also have a [[Call]] internal method.

This invariant applies to ordinary objects as well as to exotic ones, be they native or host-defined.

So yes, the TypeScript behaviour you described should be forbidden, a new signature cannot result in a primitive value. new () => string might as well be a compile-time error. Notice however that you won't be able to construct a value that is assignable to this type without resorting to any typecasts, so you might as well consider it equivalent to never.

like image 25
Bergi Avatar answered Sep 30 '22 01:09

Bergi