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

Partial<T>[K] is not assignable to T[K] | undefined #47523

Closed
hearnden opened this issue Jan 20, 2022 · 3 comments · Fixed by #53112
Closed

Partial<T>[K] is not assignable to T[K] | undefined #47523

hearnden opened this issue Jan 20, 2022 · 3 comments · Fixed by #53112
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript

Comments

@hearnden
Copy link

hearnden commented Jan 20, 2022

Bug Report

...ok, I lied a little:

  • it is assignable if T is a type variable / generic; but
  • it is not assignable if T is concrete.

So this report can be seen either as the lack of support of an expected rule (when T is concrete), or an inconsistency between the rules for generics and the rules for concretes.

🔎 Search Terms

Searching for:

  • "Partial"
  • "Partial undefined"

I found these potentially related issues:

#45257 looks very similar. However, I think this report is a simpler case, and seems more directly related to the definition of Partial<> / optional properties (?), as something that adds | undefined to the type of a property access expression. It also looks to me like a fix for this report would fix #45257 naturally.

#31675 also looks related, but from the non-nullable perspective rather than the nullable one. The non-nullable inference rule also appears to be missing from both generic inference and concrete inference (e.g., NonNullable<Partial<T>[K]> is not assignable for T[K] for both generic T and concrete T), so at least there isn't an inconsistency there.

🕗 Version & Regression Information

This changed between versions 3.3.3 and 3.5.1.
In 3.3.3, Partial<T>[K] was assignable to T[K] | undefined for both generic T and concrete T.
From 3.5.1, assignability is still allowed for generic T, but not for any particular concrete T.

⏯ Playground Link

Playground link with relevant code

💻 Code

type Foo = {
    x: number,
    y: string,
};

/** Reads a key of any Partial<T> as an optional. */
function getValueGeneric<T, K extends keyof T>(o: Partial<T>, k: K): T[K] | undefined {
    return o[k];  // Ok: Type 'Partial<T>[K]' is assignable to type 'T[K] | undefined'
}

/** Reads a key of a Partial<Foo> as an optional. */
function getValueConcrete<K extends keyof Foo>(o: Partial<Foo>, k: K): Foo[K] | undefined {
    return o[k];  // Error: Type 'Partial<Foo>[K]' is not assignable to type 'Foo[K] | undefined'
}

🙁 Actual behavior

  • Partial<T>[K] is assignable to T[K] | undefined when T is a generic
  • Partial<T>[K] is not assignable to T[K] | undefined when T is concrete

🙂 Expected behavior

  • Assignability of Partial<T>[K] to T[K] | undefined should be consistent for both generic T and concrete T
  • Both should be allowed
@fatcerberus
Copy link

Possibly this is due to #30769?

@RyanCavanaugh
Copy link
Member

Indexed access on a mapped type stays deferred, by design (since this improves the specificness of many constructs). The only way to know that { [K in keyof T]: T[K] | undefined }[K] is assignable to T[K] | undefined is to know that at a higher level this mapped type doesn't do something else like { [K in keyof T]: T[K] | string | undefined }[K]. We can do some reaching backwards but it's not a generalizable principle, and pushes the inconsistency further down the line rather than remove it.

An equivalent[ish] definition that works well is

type Foo = { x: string, y: number };
function getValueConcrete<P extends Partial<Foo>, K extends (keyof Foo) & (keyof P)>(o: P, k: K): P[K] | undefined {
    return o[k];
}

this has the advantage that nonsense reads like

getValueConcrete({x: "" }, "y");

fail

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jan 20, 2022
@yume-chan
Copy link
Contributor

Does that mean the generic example doesn't report an error is because TypeScript didn't check it (deferred)?


The error message is cryptic:

Type 'Partial<Foo>[K]' is not assignable to type 'Foo[K] | undefined'.
  Type 'string | number | undefined' is not assignable to type 'Foo[K] | undefined'.
    Type 'string' is not assignable to type 'Foo[K] | undefined'.
      Type 'Partial<Foo>[K]' is not assignable to type 'Foo[K]'.
        Type 'number | undefined' is not assignable to type 'number'.
          Type 'undefined' is not assignable to type 'number'.

This usage looks very common to me, for example I may write:

type FooOptions = {
    x: number,
    y: string,
};

class Foo {
    constructor(private options: Partial<FooOptions>) { }

    getOption<K extends keyof FooOptions>(key: K): FooOptions[K] | undefined {
        return this.options[key]; // Error
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Fix Available A PR has been opened for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants