1

I have the following function :

function demo<T>(init: T) {
    return init;
}

I would like to give init the default value {} when omitted. So I wrote :

function demo2<T>(init: T = {}) {
    return init;
}

But of course, this gives me the error :

Type '{}' is not assignable to type 'T'.
  'T' could be instantiated with an arbitrary type which could be unrelated to '{}'.(2322)

Link to playground.

How could I force T to be {} when the parameter is omitted ? Thank you.

5
  • you cannot guarantee that extended type also would be a partial. interface Derived extends Partial<ListVariables>{ bbb: string; } . Try to pass a variable of this type. Commented Dec 9, 2020 at 17:25
  • @Anatoly Yes but is there a way to automatically affect the empty object type to T when the parameter is omitted ? Commented Dec 9, 2020 at 17:27
  • How? If you use useListVariables<Derived> then you cannot use {} as a default value Commented Dec 9, 2020 at 17:29
  • Could you edit this code to be a minimal reproducible example suitable for dropping into a standalone IDE like The TypeScript Playground? Before I post answers I like to be able to test them against the example use case, but I don't know what ListVariables is, nor am I positive what results you want to see for different uses of useListVariables() (e.g., what if someone manually specifies the type parameter vs letting the compiler infer it). Any code that shows what you tried. Thanks! Commented Dec 9, 2020 at 18:27
  • @jcalz It's done ! Commented Dec 10, 2020 at 1:42

1 Answer 1

2

The error is technically correct; the callers of your demo2() function is allowed to specify the type parameter T to be whatever they want it to be. This means a call like the following will not result in a compiler error, yet will easily lead to a runtime error:

const z2 = demo2<{ a: number }>(); // uh oh, no error 
z2.a.toFixed(); // no error at compile time but "z2.a is undefined" at runtime

Still, if you think the likelihood of someone doing that (manually specifying a generic type parameter) is low, you can use a type assertion to suppress the error, while at the same time using a generic parameter default so that the compiler will infer {} for T when you leave out the init property:

// assertion with default type parameter
function demo3<T = {}>(init: T = {} as T) {
    return init;
}

This gives you the following desirable behavior:

const x3 = demo3(); // {}
const y3 = demo3({ a: 123 }); // {a: number}

while still allowing the following undesirable behavior:

const z3 = demo3<{ a: number }>(); // no compiler error
z3.a.toFixed(); // RUNTIME ERROR!

If you want to prohibit manually specifying T without passing in a value of type T, you might consider using something like an overloaded function with multiple call signatures:

// overload

function demo4(): {};
function demo4<T>(init: T): T;
function demo4(init = {}) {
    return init;
}

Here, if a caller leaves out the init parameter, the function is no longer treated as generic; the return type is just {}. On the other hand, if the caller provides an init parameter, then the function is treated just like your original demo() function. The implementation of the function works for either call signature.

This also results in the desirable behavior for "normal" calls:

const x4 = demo4(); // {}
const y4 = demo4({ a: 123 }); // {a: number}

And also gives a compiler warning if someone tries to call it the wrong way:

const z4 = demo4<{ a: number }>(); // compiler error! an argument for init is required

Playground link to code

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.