Skip to content

Generic derived value type #28597

Open
Open

Description

@jurajkocan

Hey guys.
I can not see if I'm doing anything wrong here. I am expecting to be able to get exact type from generic type ...
Is this just limitation of typescript or i am doing anything whrong here?

TypeScript Version: 3.1.3

Search Terms:
Generic derived value type

Code

export type filterTypeName = 'price' | 'price_range';
export type priceFilterValue = {
  from: number;
  to: number;
};
export type priceRangeFilterValue = {
  fromRange: number;
  toRange: number;
};

export type filterValueType<T extends filterTypeName> =  
    T extends 'price' ? priceFilterValue
    : T extends 'price_range' ? priceRangeFilterValue : never;


type productFilterParameters<T extends filterTypeName> = {
  filter: T;
    value: filterValueType<T>;
};

function addProductFilter<T extends filterTypeName>(params: productFilterParameters<T>) { 
    switch (params.filter) { 
        case 'price':
            const priceFrom = params.value.from; //Property 'from' does not exist on type 'filterValueType<T>'    
            break;
        default:
            break;            
    }
}

// parameter value is derivered based on filter type... not working id function definition
addProductFilter({ filter: 'price', value: { from: 1, to: 2 } });

//expected behavior something like this

type productFilterParametersCorrect = {
    filter: 'price';
    value: priceFilterValue;
} | {
    filter: 'price_range';
    value: priceRangeFilterValue;
}

function addProductFilterCorrect(params: productFilterParametersCorrect) { 
    switch (params.filter) { 
        case 'price':            
            const priceFrom = params.value.from; // correct
            break;
        case 'price_range':
            const priceRangeFrom = params.value.fromRange; // correct
        default:
            break;            
    }
}

Expected behavior:
Code

const value: priceFilterValue

Actual behavior:
Code

const value: filterValueType<T>

Playground Link:
Playground

Related Issues:

Activity

  1. weswigham commented on Nov 19, 2018

    @weswigham
    Member

    Anders Hejlsberg (@ahejlsberg) it seems like we might be able to recognize discriminants in conditional types similarly to unions - though it also seems much less straightforward to resolve them.

  2. Nathan-Fenner commented on Nov 20, 2018

    @Nathan-Fenner

    This isn't a bug (see this discussion in #24085 ).

    Consider the following code:

    type ExamplePrice = filterValueType<"price">;
    // = { from: number; to: number; }
    
    type ExampleRange = filterValueType<"price_range">;
    // = { fromRange: number; toRange: number; }
    
    type ExampleBoth = filterValueType<"price" | "price_range">;
    // = priceFilterValue | priceRangeFilterValue

    The last is because (for whatever reason), conditional types are automatically mapped over unions.

    Therefore, I can make the following call to the function:

    const weirdInput: productFilterParameters<"price" | "price_range"> = {
      filter: "price",
      value: {
        fromRange: 0,
        toRange: 10,
      },
    }
    
    addProductFilter<"price" | "price_range">(weirdInput);

    with no errors. Everything type checks, as it's supposed to. Inside the function, the case will match "price", and yet there will be a fromRange and toRange field but no from and no to.

    I have a proposal at #27808 to add an T extends_oneof("price", "price_range") constraint which would make the above code work as expected.

  3. jurajkocan commented on Nov 22, 2018

    @jurajkocan
    Author

    Wesley Wigham (@weswigham) so is this possible to implement in to the typescript or should i find another solution?
    if so i will just wait for it.

  4. andyrichardson commented on May 2, 2019

    @andyrichardson

    I've just come across this issue myself. Here's a quick reproduction.

    type state = 'a' | 'b' | 'c';
    
    interface MyInterface<S extends state> {
      state: S,
      additionalProp: S extends 'a' ? string[] : never;
    }
    
    const myFun = <S extends state>(arg: MyInterface<S>) => {
      // This infer arg as MyInterface<'c'>  
      if (arg.state === 'c') {
        return arg.additionalProp.length; // This should throw "cannot use property 'length' on type never"
      }
    
      // Manually casting this type works as expected
      const forcedType: MyInterface<'b'> = { state: 'b' };
      forcedType.additionalProp.length // error
    }

    It looks as though, despite the conditional statement, the type of arg.state remains to be undetermined.

  5. Nathan-Fenner commented on May 3, 2019

    @Nathan-Fenner

    Andy Richardson (@andyrichardson) that behavior is as expected (although for a completely different reason).

    All operations are legal on never since it's a bottom type. It's impossible to have a value of type never (at least without cheating), so if you do get one, the impossible has already happened, and therefore there's nothing wrong with letting you do "impossible" operations.

    Since .length is valid on both never and string[] there's no problem.

    TypeScript does provide a best-effort check for operations on definitely never types, since those are likely mistakes. But for a value that's only maybe never it would be counterproductive to error.

    Replacing never with null will give you an error, as expected.

  6. andyrichardson commented on May 3, 2019

    @andyrichardson

    Nathan Fenner (@Nathan-Fenner) this isn't an issue relating to the use of null. If you replace, from the example, never with null you will see that an error is still not produced.

    The problem is that following the condition if (arg.state === 'c') { the type of arg within the conditional body should be MyInterface<'c'> (and therefore additional prop is of type never). This isn't the case as the type remains to be unknown and therefore the type of arg.additionalProp remains to be undetermined (string[] | never or string[] | null as with your suggestion).

  7. Nathan-Fenner commented on May 3, 2019

    @Nathan-Fenner

    The error absolutely is produced (provided that --strictNullChecks is on:

    See playground for example

    You obtain the error:

    Object is possibly null

    As expected. Your issue is due to misuse of never, not a bug in conditional types. Even then, it would be unrelated to the original issue here.

  8. 6 remaining items

  9. agalazis commented on Jul 21, 2019

    @agalazis

    Hello I was facing the same issue in my action factory (I might come back later with a minimal example). I think we need to come up with a way to declare the type as a Singletton or accept that it is singleton if it extends a singleton. In my case, it would be even more useful to allow a generic of singleton types eg ActionType<'my feature', 'my action type','my action name'>( being the type of 'myfeature | my action | my action name'
    (I was trying to achieve something like #12754 (comment) with the issue being that the resulting type couldn't be used as a discriminant)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions