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.

Generic derived value type

See original GitHub issue

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:

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:6
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
Nathan-Fennercommented, May 10, 2019

@andyrichardson The behavior is still as expected:

The reason strict null checks throws an error is because the type of arg.additionalProp is string[] | null. It should be of type null given arg has the type of MyInterface<'c'>

This isn’t the case. Given that state === 'c', you only know that 'c' is a subtype of S. In particular, (e.g.)S = 'a' | 'c' is still entirely possible:

const exampleValue: MyInterface<'a' | 'c'> = {
  state: 'c',
  additionalProp: ["foo", "bar", "baz"],
};

This value can be passed to myFun above; you’ll find that state === 'c' as expected, and also that additionalProp has type string[], not null. This works since conditional types are distributive, meaning that X | Y extends T ? A : B is really the same as (X extends T ? A : B) | (Y extends T ? A : B). This means that in the above, additionalProp really does have the type string[] | null, since 'a' follows the left path and 'c' follows the right one.

You can avoid distribution with the following trick:

interface MyInterface<S extends state> {
  state: S,
  additionalProp: [S] extends ['a'] ? string[] : never;
}

by wrapping the two types in a single-element tuple. However, this won’t actually solve the problem, because it is still impossible to distinguish (solely using flow types) that S is actually 'c' and not 'a' | 'c' or 'a' | 'b' | 'c' or 'b' | 'c' (since you can only learn what it is allowed to be, and not what it is not allowed to be, from runtime checks).

The following trick almost works, except the TS’s flow-typing is not sophisticated enough to understand that deducing S = 'c' is actually correct:

type state = 'a' | 'b' | 'c';

interface MyInterface<S extends state> {
  state: [S] extends ['a'] ? 'a' : [S] extends ['b'] ? 'b' : [S] extends ['c'] ? 'c' : null,
  additionalProp: [S] extends ['a'] ? string[] : null;
}

This makes the code actually sound (since the exampleValue above will no longer compile) since it enforces that the tag is consistently a singleton throughout the type, although you’ll need to perform a cast inside the function to convince tsc that it really behaves as intended.

1reaction
Nathan-Fennercommented, May 3, 2019

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Constraints on type parameters - C# Programming Guide
Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type ...
Read more >
Generics, Inheritance, and Subtypes (The Java™ Tutorials ...
You can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or...
Read more >
Assigning a generic type for derived classes from an abstract ...
From there, I am lost. TypeScript Playground link abstract class Base<T> { abstract value: T; } class Derived ...
Read more >
Generic Types and Inheritance - Mastering C# and .NET
Generic Types and Inheritance ; Same-type inheritance · BetterHolder.cs · 3. // The same type parameter as the base class ; Non-generic child...
Read more >
C#. Hierarchies of generic classes - BestProg
Generic classes support an inheritance mechanism and can form hierarchies. Any generic class that takes a type T as a parameter can be ......
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