question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Pick<T, Exclude<keyof T, K>> & Pick<T, Extract<keyof T, K>> should be assignable to T

See original GitHub issue

TypeScript Version: 3.2.1

Search Terms: 3.2.1 extends intersection generic

Code

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

type Func<T> = (arg: T) => null

type Context = 'Context';

export function withRouteContextPropConsumer<
    T extends { routeContext: Context }
>(
    funcToWrap: Func<T>,
): Func<Omit<T, "routeContext">> {
    return (args: Omit<T, "routeContext">) => {
        const routeContext: Context = 'Context';
        return funcToWrap({ ...args, routeContext });
    };
}

Expected behavior: Code compiles without errors

Actual behavior:

Argument of type '{ routeContext: "Context"; }' is not assignable to parameter of type 'T'.

Playground Link: http://www.typescriptlang.org/play/#src=type Omit<T%2C K extends keyof T> %3D Pick<T%2C Exclude<keyof T%2C K>>%3B type Func<T> %3D (arg%3A T) %3D> null type Context %3D ‘Context’%3B export function withRouteContextPropConsumer< T extends { routeContext%3A Context } >( funcToWrap%3A Func<T>%2C )%3A Func<Omit<T%2C “routeContext”>> { return (args%3A Omit<T%2C “routeContext”>) %3D> { const routeContext%3A Context %3D ‘Context’%3B return funcToWrap({ ...args%2C routeContext })%3B }%3B } Related Issues: Nothing obvious

After upgrading from 3.0.3 to 3.2.1, it seems that tsc has (at least partially) lost the ability to reason about constrained generics.

In the example above (one of our React context helper functions, modified to remove the React dependency), the function is parameterised over a constrained generic:

T extends { routeContext: Context }

But a few lines later, the compiler complains that the generic T may not have a routeContext attribute. T must have a routeContext attribute however, because of the constraint. Perhaps the Omit helper is confusing things?

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:18
  • Comments:17 (4 by maintainers)

github_iconTop GitHub Comments

14reactions
ahejlsbergcommented, Dec 18, 2018

I changed the title of the issue since the core problem is that an intersection of complementary subsets of a higher order type, constructed using Pick<T, K> or by other means, is not assignable back to that higher order type.

Meanwhile, investigating the issue revealed other issues, notably #29067 and #29081. I have fixed both, and with the fixes in place it is now possible to use a type assertion back to the higher order type. For example:

function withDefaults<T, K extends keyof T>(f: (obj: T) => void, defaults: Pick<T, K>) {
    // In the resulting function, properties for which no defaults were provided are required,
    // and properties for which defaults were provided are optional.
    return (obj: Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>>) =>
        f({ ...defaults, ...obj } as T);  // Assertion required for now
}

type Item = { name: string, width: number, height: number };

function foo(item: Item) {}

let f1 = withDefaults(foo, { name: 'hi ' });
let f2 = withDefaults(foo, { name: 'hi', height: 42 })

f1({ width: 10 });  // Error, missing 'height'
f1({ width: 10, height: 20 });  // Ok
f2({ width: 10 });  // Ok
f2({ width: 10, height: 20 });  // Ok

If and when we implement the improvement suggested by this issue the as T assertion in the withDefaults function becomes unnecessary.

5reactions
hronrocommented, Mar 11, 2019

It seems this still not fixed in v3.4.0-dev.20190310

Read more comments on GitHub >

github_iconTop Results From Across the Web

Type '1' is not assignable to type 'T[Extract<keyof T, string>]'
I thought T[Extract<keyof T, string>] is number so assigning 1 which is number should work. What is wrong in my code? type Foo...
Read more >
How to use the keyof operator in TypeScript - LogRocket Blog
keyof T returns a union of string literal types. · extends means “is assignable” instead of “inherits”; K extends keyof T means that...
Read more >
Enforce that all keys in a map are a key of a certain type ...
type KeyOf<T> = Extract<keyof T, string>. And our function will now look like so: function foo<T>(someKey: KeyOf<T>) { const myMap = new Map()...
Read more >
Error - Type 'string[]' is not assignable to type 'keyof MyInterface[]'
keyOne: string keyTwo: string } const myArray: keyof MyInterface[] = ['keyOne', 'keyTwo']. Is there a simple way to get rid of this error?...
Read more >
React components done right with TypeScript mapped and ...
The keyof T operator gets a union type of all the key names of the ... Exclude<T, U> – Exclude from T those...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found