Skip to content

Type guard cannot remove undefined from Partial<T>[keyof T] | undefined #45257

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

Open
Jason3S opened this issue Jul 31, 2021 · 4 comments
Open
Labels
Suggestion An idea for TypeScript

Comments

@Jason3S
Copy link

Jason3S commented Jul 31, 2021

Bug Report

When writing a function to merge object A with non-undefined values from object B into C where A is of type T and B is of type Partial<T>, I came across a situation where the type guard seems to be failing.

🔎 Search Terms

  • type guard
  • Partial

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about common-bugs-that-arent-bugs

⏯ Playground Link

Playground link with relevant code

💻 Code

function merge<T>(a: T, b: Partial<T>, keys: (keyof T)[]): T {
    const v: T = { ...a };
    for (const key of keys) {
        const value = b[key];
        if (value !== undefined) {
            v[key] = value;
        }
    }
    return v;
}

🙁 Actual behavior

There is an error at v[key] = value;:

Type 'T[keyof T] | undefined' is not assignable to type 'T[keyof T]'.
  Type 'undefined' is not assignable to type 'T[keyof T]'.

image

Clearly value is NOT undefined, yet the compiler complains that it is.

🙂 Expected behavior

I would expect that the code compiled.

@puchm
Copy link

puchm commented Jul 31, 2021

I am having a similar issue. Here is another example:

function foo<T extends { [key: string]: any }>(obj: T) {
    for(const key in obj) {
        obj[key] = 1
    }
}

function foo2(obj: { [key: string]: any }) {
    for(const key in obj) {
        obj[key] = 1
    }
}

The upper one gets the following error at the obj[key] line:
Type 'number' is not assignable to type 'T[Extract<keyof T, string>]'

The lower one does not have this issue. This indicates to me that the issue has something to do with the generics.

@ahejlsberg
Copy link
Member

The issue here is that we only perform control flow analysis for element accesses with literal property names (e.g. obj['foo']). So, no narrowing is performed for obj[key] because it has a computed property name. We could potentially consider narrowing such expressions when the index expression is a reference to a const variable. I'll mark this issue as a suggestion for that (though I wouldn't be surprised if we already have this suggestion).

@ahejlsberg ahejlsberg added the Suggestion An idea for TypeScript label Aug 1, 2021
@ahejlsberg
Copy link
Member

Actually, sorry, I misread the example. The issue is that the guard value !== undefined doesn't narrow type Partial<T>[keyof T] to T[keyof T]. I'm not sure how feasible it is to do that narrowing, but it is something we could consider.

@ilyakonrad
Copy link

I have a similar issue.

const patch: Partial<TableCacheStateModel> = {}
const state = context.getState()

Object.keys(state).forEach((key: keyof TableCacheStateModel) => {
  if (state[key].isDependantOnGroupSelector) {
    patch[key] = defaults[key]
  }
})

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants