Skip to content

Conditional type on a lookup always never #29211

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

Closed
atrauzzi opened this issue Dec 31, 2018 · 7 comments
Closed

Conditional type on a lookup always never #29211

atrauzzi opened this issue Dec 31, 2018 · 7 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@atrauzzi
Copy link

TypeScript Version: 3.3.0-dev.20181229
Search Terms:

  • conditional types
  • lookup
  • broken

Code

export interface PassthroughProps<
    DataType extends {[key: string]: any},
    DataTypeKey extends string = Extract<keyof DataType, string>,
    IdentityCandidate extends DataTypeKey = DataType[IdentityCandidate] extends string 
        ? IdentityCandidate 
        : never,
> {
    identityProperty: IdentityCandidate;
    label: DataTypeKey | ((option: DataType) => string);
}

interface MyType {
    id: string;
    name: string;
    times: number;
}

const myprops: PassthroughProps<MyType> = {
    // ITEM 1
    identityProperty: "times",
    label: "id",
};

Expected behavior:

Item 1

I'm expecting identityProperty to be ( "id" | "name" ), not never.

Actual behavior:

Item 1

I'm getting this error:

Type 'string' is not assignable to type 'never'.ts(2322)

@DanielRosenwasser
Copy link
Member

I think that #29179 has broken this anyway since IdentityCandidate extends DataTypeKey = DataType[IdentityCandidate] references itself in its default. Can you confirm @ahejlsberg?

@atrauzzi
Copy link
Author

atrauzzi commented Dec 31, 2018

I think in my tinkering I've come up with variants that don't rely on such, so -- if there's a suitable alternative to make the example a bit more robust, let me know. 😄

I should add -- without a circular reference, I'm not sure what other kind of syntax I'd have to come up with to express what I want here. The type is effectively justifying itself, or bailing out.

@ahejlsberg
Copy link
Member

I think that #29179 has broken this anyway since IdentityCandidate extends DataTypeKey = DataType[IdentityCandidate] references itself in its default. Can you confirm @ahejlsberg?

Yes, the code above now errors on the circular reference to IdentityCandidate in its own default (which is a good thing, since it doesn't make sense to reference a type parameter for which no type argument was specified).

@atrauzzi
Copy link
Author

atrauzzi commented Dec 31, 2018

@ahejlsberg - If that's the case, is there an alternate way for me to express:

IdentityCandidate extends DataTypeKey = DataType[IdentityCandidate] extends string 
        ? IdentityCandidate 
        : never,

I'm no fan of circular references, so I'm onboard there -- but I need a way to check if that type, when used as a key, retrieves a string. In this case, I'm not entirely convinced this is a circular reference, at least in intent.

@ahejlsberg
Copy link
Member

Not entirely clear on what you're trying to do, but I think you could write it like this:

export interface PassthroughProps<
    DataType extends {[key: string]: any},
    DataTypeKey extends string = Extract<keyof DataType, string>,
    IdentityCandidate = DataType[DataTypeKey] extends string ? DataType[DataTypeKey] : never,
> {
    identityProperty: IdentityCandidate;
    label: DataTypeKey | ((option: DataType) => string);
}

However, it looks to me like the IdentityCandidate type parameter is never supposed to be specified and is just intended as an alias for the default value. That's definitely an anti-pattern, and I would suggest inlining it instead:

export interface PassthroughProps<
    DataType extends {[key: string]: any},
    DataTypeKey extends string = Extract<keyof DataType, string>
> {
    identityProperty: DataType[DataTypeKey] extends string ? DataType[DataTypeKey] : never;
    label: DataTypeKey | ((option: DataType) => string);
}

Hope that helps.

@weswigham weswigham added the Question An issue which isn't directly actionable in code label Jan 3, 2019
@atrauzzi
Copy link
Author

atrauzzi commented Jan 5, 2019

So, I think overall the advice here worked, but there's one thing that could possibly be improved:

const key = item[this.props.identityProperty];

When I do the above, the value key, doesn't want to be a string. Even though this.props.identityProperty is required to be a key that resolves to a string value.

If I do this:

const key = item[this.props.identityProperty!] as string;

I get:

Conversion of type 'ItemType[NonNullable<ItemType[Extract<keyof ItemType, string>] extends string ? Extract<keyof ItemType, string> : never>]' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

I can do that, but I feel like the compiler has enough information here to know that the value I get when using this key will have to be a string?

@typescript-bot
Copy link
Collaborator

This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

5 participants