Optional parameter left unassigned is not automatically determined as undefined

Why is TypeScript not inferring the optional parameter in this function as undefined when it's omitted from the call?

function fooFunc<T extends number | undefined>(param?: T){
  let x: T extends undefined ? null : T
  x = param ?? null as any 
  return x
}

The function works as expected if explicitly passed undefined, but not if the argument is just omitted:

const fooResult1:number = fooFunc(3)

const fooResult2:null = fooFunc(undefined) // <-- this works

const fooResult3:null = fooFunc() // <-- this doesn't; it infers null | number, not null

This issue can be resolved by widening the constraint to include unknown, i.e.,

<T extends number | undefined | unknown>
. However, using unknown may lead to other problems later on.

What could be causing this behavior and is there a workaround for it?

Playground

Update

Based on research, it seems that missing type parameters are inferred as unknown in TypeScript. See this SO answer by @jcalz. So the question now is whether there is a way around this issue or if debugging with unknown is inevitable.

Update 2(revised)

I have encountered an issue with using unknown--while changing the function signature to

function fooFunc<T extends number | undefined | unknown>(param?: T){
  let x: T extends undefined ? null : T
  x = param ?? null as any 
  return x
}

makes the function work correctly when called without a parameter (fooFunc()), it also allows arbitrary parameters like fooFunc("hello"), which is undesired behavior.

The same issue arises even if the generic is embedded within an object, as shown below:

function bazFunc<T extends number | undefined | unknown>(param: {a: number; b?: T}){
  let x: T extends undefined ? null : T
  x = param.b ?? null as any 
  return x
}

bazFunc({a:1, b:"hello"}) // <-- Undesired behavior

function fooFunc<T extends number | undefined>(param?: T){ let x: [T] extends [number] ? T: null x = param ?? null as any return x } const fooResult1:number = fooFunc(3) const fooResult2:null = fooFunc(undefined) const fooResult3:null = fooFunc()

By utilizing brackets to prevent distribution of the conditional type, the function now resolves to the actual value of T in non-undefined cases, achieving the desired outcome in this scenario.

Answer №1

When examining the function call closely, it becomes evident that the missing type parameter is not inferred as `unknown` but rather as the upper bound of `number | undefined`. Consequently, the return type `T extends undefined ? null : T`, which belongs to a distributive conditional type, evaluates to `number | null`. This is because the `number` portion translates to `number` and the `undefined` portion translates to `null`. As a result, in the third example provided, the function's return type is `number | null`, which does not align with the expected type of `null`, leading to an error.

In scenarios like this, where you have a generic function with an optional parameter that only makes sense to be generic when the parameter is present, utilizing function overloads proves to be a practical solution. Through function overloads, you can explicitly define the desired return type when the function is called without an argument, while retaining its generic nature when invoked with an argument.

// overload for calling with no argument
function fooFunc(): null;
// overload for calling with one argument
function fooFunc<T extends number | undefined>(param: T): T extends undefined ? null : T;
// actual implementation
function fooFunc<T extends number | undefined>(param?: T) {
  let x: T extends undefined ? null : T
  x = param ?? null as any 
  return x
}

Playground Link

Answer №2

Give this a shot....

Here's a neat function called fooFunc that takes in a generic parameter T which extends either number or undefined. It checks if the param is provided, and if not, assigns null to x. Then it returns x.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating Instances of Variables Within a Class

Currently, I am working on a project using Ionic and Angular. I have come across various ways of instantiating variables and I'm unsure about the implications of each method. Here are three scenarios that confuse me: export class someClass { myVaria ...

Is your pure function component not receiving or responding to input props correctly?

Here is my code snippet: const actionCreators = { action: AppReducer.actionCreators.action } interface GlobalState { user: Model.User | null; } interface InputState { setStashBarWidth(width: number); stashWidth: number; } const Header = ...

Svelte warns of potential undefined Variable when using "bind:this={}"

Whenever I attempt to utilize the bind:this attribute in Svelte, I encounter this message in vscode: 'cardGroup' is possibly 'undefined'.js(18048) Upon execution, the following error arises: TypeError: Cannot read properties of undefin ...

What is the best way to instruct TypeScript to utilize a globally installed NPM @types package?

After running npm install @types/node, the TypeScript compiler worked perfectly with tsc -p tsconfig.json. However, when I attempted to install the package globally with npm install -g @types/node and deleted the local folder, I encountered the following ...

When I define a type in TypeScript, it displays "any" instead

Imagine a scenario where we have a basic abstract class that represents a piece in a board game such as chess or checkers. export abstract class Piece<Tags, Move, Position = Vector2> { public constructor(public position: Position, public tags = nul ...

Angular2: Unable to locate the 'environment' namespace

After configuring my tsconfig.json, I can now use short import paths in my code for brevity. This allows me to do things like import { FooService } from 'core' instead of the longer import { FooService } from '../../../core/services/foo/foo. ...

The functionality of arguments in the whenAllDone promise/deferred javascript helper seems to fail when attempting to encapsulate existing code within a Deferred

My goal is to implement the solution provided in TypeScript from Stack Overflow: UPDATE 2 - The issue with the original answer is that it does not support a single deferred. I have made modifications to reproduce the error in his fiddle. http://jsfiddle.n ...

Retrieving PHP information in an ionic 3 user interface

We are experiencing a problem with displaying data in Ionic 3, as the data is being sourced from PHP REST. Here is my Angular code for retrieving the data: this.auth.displayinformation(this.customer_info.cid).subscribe(takecusinfo => { if(takecusi ...

What is the best way to showcase an Angular component that has been initialized programmatically?

I currently have the following set up: Users Component HTML: <div class="users"> <ul class="users__list"> <li *ngFor="let user of users"></li> </ul> </div> Users Component TS: impo ...

The Angular Material date picker unpredictably updates when a date is manually changed and the tab key is pressed

My component involves the use of the Angular material date picker. However, I have encountered a strange issue with it. When I select a date using the calendar control, everything works fine. But if I manually change the date and then press the tab button, ...

Utilize TypeScript File array within the image tag in HTML with Angular 2

I am in the process of developing a web application that allows users to upload CSV data and images, which are then displayed on the application. However, I have encountered an issue where I am unable to display the imported images. The images are imported ...

Mastering Angular Apollo Error Resolution Methods

Hey everyone, I'm facing a problem with apollo-angular and apollo-link-error that I just can't seem to figure out. I've experimented with different approaches but have had no luck catching errors on the client-side of my angular web app. Bel ...

Collaborate on prisma types and enums for various projects

I'm currently working on an API that utilizes Prisma to provide data. This API is used in various projects. Within the API, I create types using Prisma.ModelGetPayload to specify the return types for certain API responses. import { Prisma } from " ...

Error: Jest + Typescript does not recognize the "describe" function

When setting up Jest with ts-jest, I encountered the error "ReferenceError: describe is not defined" during runtime. Check out this minimal example for more information: https://github.com/PFight/jest-ts-describe-not-defined-problem I'm not sure what ...

Why is the `node-config` configuration undefined within a TypeScript Jest environment?

I have a TypeScript module that is functional in both development and production environments. It utilizes https://github.com/lorenwest/node-config. When I attempt to import it into Jest for testing purposes, I encounter an error suggesting that the config ...

How to perform a fetch on a local path in Next.js?

Is there a way to use the fetch method with a relative path like this: export async function getServerSideProps() { // Fetch data from local API const res = await fetch(`/api/get_all_prices`) const data = await res.json() // Pass data to th ...

What is the best way to create an array of unspecified classes that adhere to a particular interface in TypeScript?

Currently, I am working on an interface that has specific properties and function specifications which are implemented by several classes. My objective is to create an array of arrays that contain instances of these classes. However, when I try the followi ...

If the input is unmounted in react-hook-form, the values from the first form may disappear

My form is divided into two parts: the first part collects firstName, lastName, and profilePhoto, while the second part collects email, password, confirmPassword, etc. However, when the user fills out the first part of the form and clicks "next", the val ...

Traversing through JSON objects in Angular 2

I am currently facing an issue while trying to iterate through a JSON object. Below is the sample JSON data: floors.ts this.floors= [ { floorName: "floor 1", result: [ { resFloor: "1", ...

Calling gtag("event") from an API route in NextJS

Is there a way to log an event on Google Analytics when an API route is accessed? Currently, my gtag implementation looks like this: export const logEvent = ({ action, category, label, value }: LogEventProps) => { (window as any).gtag("event&quo ...