Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional Type Produces undefined Unexpectedly #41145

Closed
m4dc4p opened this issue Oct 17, 2020 · 2 comments
Closed

Conditional Type Produces undefined Unexpectedly #41145

m4dc4p opened this issue Oct 17, 2020 · 2 comments

Comments

@m4dc4p
Copy link

m4dc4p commented Oct 17, 2020

TypeScript Version: 4.0.2

Search Terms:
conditional types never keyof required properties

Expected behavior:

In the code below, I would expect the type requiredKeysType1 to be "name" | "email".

Actual behavior:
The type of requiredKeysType1 is actually "name" | "email" | undefined. I had to define the Definable to filter out undefined and I don't understand why. The type requiredKeysType2 shows usage of that type.

My goal here is to define a const array of the required properties on a given interface for use in a type guard (isPerson). I would like the compiler to complain if I remove a key from the Person interface and forget to update the array.

Not implemented here but wished for - I would like the compiler to complain if I add a required property to Person and forget to update the array, but I can't figure out how to make that happen.

Related Issues:

Didn't find any related issues.

Code

interface Person {
  name: string
  age?: number
  email: string
}

type requiredKeysType1 = keyof RequiredProperties<Person>
// requiredKeys1 = "name" | "email" | undefined
const requiredKeys1: Array<requiredKeysType1> = ["name"]

type requiredKeysType2 = Definable<keyof RequiredProperties<Person>>
// requiredKeys2 = "name" | "email"
const requiredKeys2: ReadonlyArray<requiredKeysType2> = ["name"] as const

function isPerson2(s?: object): s is Person {
    return !! (s && requiredKeys2.every((k) => k in s))
}

/**
 * Remove undefined from the type given (most useful for unions)
 */
type Definable<T> = T extends undefined ? never : T

// Due to https://dev.to/busypeoples/notes-on-typescript-conditional-types-4bh
type RemoveUndefinable<Type> = {
  [Key in keyof Type]: undefined extends Type[Key] ? never : Key
}[keyof Type];

/**
 * Retrieves all the keys for the given object that are required (can't be
 * undefined.)
 */
type RequiredProperties<Type> = { // for some reason, keyof appends an `undefined` value. `Definable` filters that out.
  [Key in RemoveUndefinable<Type>]: Type[Key]
};
Output
"use strict";
// requiredKeys1 = "name" | "email" | undefined
const requiredKeys1 = ["name"];
// requiredKeys2 = "name" | "email"
const requiredKeys2 = ["name"];
function isPerson2(s) {
    return !!(s && requiredKeys2.every((k) => k in s));
}
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "alwaysStrict": true,
    "esModuleInterop": true,
    "declaration": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": 2,
    "target": "ES2017",
    "jsx": "React",
    "module": "ESNext"
  }
}

Playground Link: Provided

@MartinJohns
Copy link
Contributor

MartinJohns commented Oct 17, 2020

I'm confused. In the title you say it produces never, but then you don't mention it anywhere, neither in the actual behavior nor the expected behavior. The code doesn't produce a never either. Did you mean to write undefined in your title?


Duplicate of #34992.

You can fix your code by writing:

type RemoveUndefinable<Type> = {
  [Key in keyof Type]-?: undefined extends Type[Key] ? never : Key
}[keyof Type];

@m4dc4p
Copy link
Author

m4dc4p commented Oct 17, 2020 via email

@m4dc4p m4dc4p changed the title Conditional Type Produces never Unexpectedly Conditional Type Produces undefined Unexpectedly Oct 17, 2020
@m4dc4p m4dc4p closed this as completed Oct 17, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants