Faulty deduction can occur when implementing the return statement in an identity function, or when incorporating an optional parameter

Encountering strange behavior while working on identity functions in a wizard system schema. Using a constrained identity function for inference is causing issues with one property that cannot be inferred when using the following:

  • When the return value from the identity function uses the return keyword instead of a single-line return wrapped with parentheses.
  • OR
  • When declaring an optional argument within the identity function. The argument is declared in the type definition of the identity function, and when utilizing
    Parameters<typeof myFunction>
    , it's correctly inferred both with and without declaring the argument.

These issues are quite puzzling, indicating a possible oversight on my part or the presence of two rare bugs.

This issue persists across all available playground versions (tested down to 3.3.3) and even in version 4.8.

Check out the relevant code on the playground

To better understand, here are some snapshots:

TYPES DECLARATIONS:

type Schema = Record<string, unknown> // altered from original example for illustration

type StepFunction<
  TSchema extends Schema = Schema,
> = (anything: unknown) => {
  readonly schema: TSchema
  readonly toAnswers?: (keys: keyof TSchema) => unknown
}

function step<TSchema extends Schema = Schema>(
    stepVal: StepFunction<TSchema>,
  ): StepFunction<TSchema> {
    return stepVal
  }

EXAMPLES: Note that all functions return the same object! Variations lie in:

  • Whether the return keyword is used or not (!?!)
  • If there is an argument for the step function or not. Even if I use
    Parameters<typeof myStepValue>
    with a missing argument, it still infers correctly (!)
// WORKS: `keys` is correctly inferred based on the `schema`
// - no argument for `step` function
// - absence of `return` keyword
const workingExample = step(() => ({
  schema: {
    attribute: 'anything',
  },
  toAnswers: keys => {
    // RESULT: `keys` successfully inferred as `attribute`
    type Test = string extends typeof keys ? never : 'true'
    const test: Test = 'true'
    return { test }
  },
}))
// FAILS: `keys` is not inferred based on the `schema`
// - includes an argument for `step` function
const nonWorkingA = step(_something => ({
  schema: {
    attribute: 'anything',
  },
  toAnswers: keys => {
    // RESULT: `keys` fails to be inferred and defaults to `string`
    type Test = string extends typeof keys ? never : 'true'
    const test: Test = 'true'
    return { test }
  },
}))
// FAILS: `keys` is not inferred based on the `schema`
// - uses `return` keyword instead of a "single-return" wrapped in parentheses
const nonWorkingB = step(() => {
  return {
    schema: {
      attribute: 'anything',
    },
    toAnswers: keys => {
      // RESULT: `keys` fails to be inferred and defaults to `string`
      type Test = string extends typeof keys ? never : 'true'
      const test: Test = 'true'
      return { test }
    },
  }
})

Answer №1

If you want to simplify your type, consider the following:

type Step<TSchema> = (store: TSchema) => {
  data: {
    schema: TSchema
    toAnswers?: (keys: keyof TSchema) => unknown
  }
}

function step<TSchema extends Schema>(x: Step<TSchema>) {
  return x
}

There are two main reasons in my opinion:

  • In nonWorkingA, there are two sources of truth - one from the parameter and one from the internal object structure. If you specify the type of _store, it should work properly.
  • In nonWorkingB, you are using statements instead of expressions, which is causing TypeScript to have trouble inferring types.

I've been trying to tackle this issue for nearly 3 hours now, but I still can't seem to get normal type inference to work correctly.

Answer №2

It seems that the issue lies with TypeScript's inference process when it comes to context-sensitive expressions. More information on this problem can be found at this link and this link.

Despite the underlying TypeScript bug, a temporary workaround has been discovered by wrapping the return value in an identity function, which helps resolve some edge cases.

For example:

step(_something => (identityOfStep({
  schema: {
    attribute: 'anything',
  },
  toAnswers: keys => {
    // RESULT: `keys` failed to inferred hence defaults to `string`
    type Test = string extends typeof keys ? never : 'true'
    const test: Test = 'true'
    return { test }
  },
})))

View Example in a playground

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

Here is a way to return a 400 response in `express.js` when the JSON request body is invalid

How can I make my application send a response with status code 400 instead of throwing an error if the request body contains invalid JSON? import express from 'express' app.use(express.urlencoded({ extended: false })) app.use(express.json()) ...

Fetching data from MongoDB, loading over 3000 entries and implementing pagination

I'm facing a challenge where I need to display more than 3000 results in an HTML table by fetching MachineID, Username, and Data from my MongoDB. However, I am encountering difficulties when trying to render this data using datatables. The MachineID ...

Validating patterns in Angular without using a form

Seeking guidance on validating user input in Angular6 PrimeNG pInputText for a specific URL pattern, such as , possibly triggered on blur event. This particular field used to be part of a form but has since been relocated to a more complex 6-part form int ...

The subscribe method in Angular TS may be marked as deprecated, but worry not as it is still

I have developed a function that retrieves new data from a service file each time it is called. Here is how the function looks: onCarChange() { this.carService.getCarData(this.selectedCar).subscribe( async (response: CarData) => { if (response?.d ...

There was a parsing error due to encountering an unexpected reserved word 'interface' in the code, as flagged

I'm encountering an issue with my code when trying to utilize Props. The error message I'm receiving is "Parsing error: Unexpected reserved word 'interface'. (3:0)eslint". This project is being developed using next with TypeScript. Er ...

Is there a way to determine the specific type of a property or field during runtime in TypeScript?

Is there a way to retrieve the class or class name of a property in TypeScript, specifically from a property decorator when the property does not have a set value? Let's consider an example: class Example { abc: ABC } How can I access the class or ...

Ways to dynamically apply styles to the component tag depending on the visibility of its content

Consider a scenario where you have a component with logic to toggle the visibility of its contents: @Component({ selector: 'hello', template: `<div *ngIf="visible"> <h1>Hello {{name}}!</h1></div>`, styles: [`h1 { fo ...

What is the best way to fetch all Firebase database IDs using Angular?

Is there a way to fetch all data from Firebase database along with their respective IDs? Currently, I have two functions - getAll() and get(input) that retrieve specific products based on the given ID. However, my current implementation only returns obje ...

Error: TypeScript is unable to locate the 'moment' module

My TypeScript React application was set up using npx create-react-app --template typescript. However, when I try to start the app with npm start, I encounter an error in one of my files: TypeScript error in /<path>/App.tsx: Cannot find module ' ...

Using Firebase: retrieving getAdditionalUserInfo in onCreate of a Firebase Cloud function

Can anyone help me figure out how to retrieve extra data from a SAML login provider in the backend (firebase functions)? I can see the data on the client side but I'm struggling to access it in the backend. I've specified these dependencies for ...

Sending arguments to browser.executeScript

How can I provide parameters to browser.executeScript static sortableDragAndDropByClassName(dragElementClassName: string, dropElementClassName: string) { return browser.executeScript(function () { console.log(dragElementClassName); conso ...

Switch the checkbox attribute for multiple items within a carousel using Angular 2/Typescript

I am currently working on a carousel feature where each item has a checkbox above it. My goal is to be able to click on an item and have its corresponding checkbox checked. The current code successfully achieves this, but the issue I'm facing is that ...

Using TypeScript generics to efficiently differentiate nested objects within a parsed string

Consider the following type code: const shapes = { circle: { radius: 10 }, square: { area: 50 } } type ShapeType = typeof shapes type ShapeName = keyof ShapeType type ParsedShape<NAME extends ShapeName, PROPS extends Sh ...

Exporting a Typescript interface from a restricted npm package

I'm working on an npm module using TypeScript that includes several interfaces. In the index.ts file, I export all of the classes and interfaces. I defined the interfaces as "interface dto {a:string;} export default dto". However, when I import the ...

Exploring the power of Next.js, Styled-components, and leveraging Yandex Metrica Session Replay

I'm currently involved in a project that utilizes Next.js and styled-components. In my [slug].tsx file: export default function ProductDetails({ product }: IProductDetailsProps) { const router = useRouter(); if (router.isFallback) { return ( ...

@vx/enhanced responsiveness with TypeScript

I am currently utilizing the @vx/responsive library in an attempt to retrieve the width of a parent component. Below are snippets from my code: import * as React from 'react' import { inject, observer } from 'mobx-react' import { IGlob ...

Differences between Strings and Constants in React While Using Redux for Action Types

In the Redux guide, it is suggested to define strings constants for Redux action types: const FOO = 'FOO'; const BAR = 'BAR'; dispatch({ type: FOO }); It's worth noting that most concerns raised are relevant to untyped JavaScrip ...

Playing around with TypeScript + lambda expressions + lambda tiers (AWS)

Having trouble importing modules for jest tests in a setup involving lambdas, lambda layers, and tests. Here is the file structure: backend/ ├─ jest.config.js ├─ package.json ├─ babel.config.js ├─ layers/ │ ├─ tsconfig.json │ ├ ...

Guide to typing a new version of a function without any optional parameters using a mapped tuple

I am attempting to create a modified version of a function that has the same arguments as the original function, but with none being optional. I have tried using a mapped tuple approach with the following logic: type IFArgs = ArgsN<typeof getFunc> t ...

What is the best way to eliminate the # symbol in angular 5 URLs?

Currently, I am working on a project in Angular 5 and I need to remove the hash symbol (#) from my URL. The current URL looks like this: http://localhost:4200/#/product/add. While it works fine after being published on my domain, I encounter a 404 error ...