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

Type parameter cannot refine the other related type parameters #29276

Closed
mmis1000 opened this issue Jan 6, 2019 · 4 comments
Closed

Type parameter cannot refine the other related type parameters #29276

mmis1000 opened this issue Jan 6, 2019 · 4 comments
Labels
Domain: Control Flow The issue relates to control flow analysis Question An issue which isn't directly actionable in code

Comments

@mmis1000
Copy link

mmis1000 commented Jan 6, 2019

TypeScript Version: 3.2.2

Search Terms: type prarameter refine

Code

const state = {
  text: "",
  count: 0
};

type State = Readonly<typeof state>;

function isEqual<T extends string>(a: T, b: any): b is T {
    return a === b
}

const changeCount = <T extends keyof State, U extends State[T] = State[T]>(name: T, value: U) => {
    if (isEqual("text" as "text", name)) {
        const tyepT: State[T] = ""
        const tyepofName: State[typeof name] = ""

        state[name] = value // ❌
        state[name] = tyepT // ❌
        state[name] = tyepofName // ⭕
    }
};

changeCount("text", "") // ⭕
changeCount("count", 1) // ⭕

Expected behavior:
Calling the function automatically refine other parameter
T itself refined to 'text'
The refinement also applied on the type parameter rely on T
U is now extends string
value is assignable to state[name]

Actual behavior:
⭕ Calling the function automatically refine other parameter
T itself does not got refined to 'text', only typeof name does
❌ The refinement does not applied on the type parameter rely on T because T does not change
U is still 'extends Readonly<{ text: string; count1: number; count2: number; }>[T]'
value is not assignable to state[name]

Playground Link:
link
Related Issues:
#4742 (not sure if it describes the exact same issue)

Notes
Although refinement on calling is useful enough in most case.
You still can't consume the type correctly in function body itself, which is quite annoying.

@jack-williams
Copy link
Collaborator

jack-williams commented Jan 6, 2019

In general, narrowing a value of a generic type does not give enough proof to narrow the type itself. For example: I could call the function like so:

changeCount<"text" | "count">("text", 1);

Observing that name is equal to text tells us that T is related to "text", but it does not give us enough information to say that T is exactly "text". For this reason, the first error is correct:

state[name] = value // error

The type of value could be number if someone picked an instantiation for T that was sufficiently wide, such as "text" | "count".

The second error is raised also because, in general, type parameters cannot be narrowed. The type State[T] has to conservatively assume T is instantiated with the most general type "text" | "count"---the equality test is not sufficient to prove otherwise.

In this instance your type annotation is actually weakening the term, and removing the annotation removes the error. (Effectively you are going from string | number, to string.

if (isEqual("text" as "text", name)) {
    const typeT = "";
    state[name] = typeT; // ok
}

I will cynically take this opportunity to plug my suggestion of uniform types #28430. In most cases the caller would never want to instantiate the call with a union type and so T is always exactly "text" or "count". Under these constraints the compiler could potentially do more.

@weswigham weswigham added Question An issue which isn't directly actionable in code Domain: Control Flow The issue relates to control flow analysis labels Jan 7, 2019
@weswigham
Copy link
Member

Generally we don't let the narrowing of one type parameter via control flow affect another, either.

@mmis1000
Copy link
Author

mmis1000 commented Jan 8, 2019

However this is a very common pattern in javascript, a function accept a key and corresponds value, and operate on object base on the key.
This problem actually makes typing these function without any not possible at all.

@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
Domain: Control Flow The issue relates to control flow analysis Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants