Skip to content

Conditional typing for this["key1"] is not possible because neither branch will be executed #51288

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
Valerionn opened this issue Oct 24, 2022 · 4 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@Valerionn
Copy link

Bug Report

πŸ”Ž Search Terms

"this" key typing, conditional types on "this"

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about this

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

class Test {
  public key1: string = "abc";
  public doSomethingThis(): void {
    const wrongVariableOfTypeKey1: this["key1"] = 5; // βœ”This should be a typing error and it is
    const correctVariableOfTypeKey1: this["key1"] = "string"; // βœ” This should not be a typing error and it's not

    type ConditionalThisType = this["key1"] extends string ? number : string;
    const a: ConditionalThisType = "string"; // βœ”This should be a typing error and it is
    const b: ConditionalThisType = 5; // ❌This should NOT be a typing error BUT it is
    console.log(a, b);
  }

  public doSomethingThisClass(): void {
    type ConditionalClassType = Test["key1"] extends string ? number : string;
    const a: ConditionalClassType = "string"; // βœ” This should be a typing error and it is
    const b: ConditionalClassType = 5; // βœ” This should not be a typing error and it's not
    console.log(a, b);
  }
}

πŸ™ Actual behavior

this["key1"] extends string is neither true nor false and no path of the conditional type is returned. It's not possible to assign either a string or a number to a variable with the type.

πŸ™‚ Expected behavior

this["key1"] extends string should be true in the example above and therefore the first part of the conditional typing should get returned and the ConditionalThisType should be number. In all cases, it should be possible to assign at least either a string or a number because the documentation for conditional types states that either the first branch or the second branch will be returned:

When the type on the left of the extends is assignable to the one on the right, then you’ll get the type in the first branch (the β€œtrue” branch); otherwise you’ll get the type in the latter branch (the β€œfalse” branch).

@MartinJohns
Copy link
Contributor

MartinJohns commented Oct 24, 2022

It's probably a design limitation, because conditional types are deferred when the type is not known, and this can also be a subclass.

Is there any reason to write code like this?

@Valerionn
Copy link
Author

The reason for this is the following code (which should allow all public properties of this which are a string):

class Test {
  public key1: string = 'abc';
  public key2: string|null = null;

  public doSomething(): void {
    setSomething(this, 'key1'); // doesn't work because it will call `setSomething<this>`, and then `this['key1']` doesn't fulfill the `extends string` condition
  }
}

function setSomething<T>(a: T, b: KeysForTypeInContext<string, T>): void {

}

type KeysForTypeInContext<T, TContext> = {
  [key in keyof TContext]: TContext[key] extends T ? key : never;
}[keyof TContext];

If there is a different way of doing this (without having to specify the this-type by e.g. using public doSomething(this: Test): void), then I would be happy too :)

However, I found the Typescript behavior in this case very strange - but thanks for the hint that this could be a subclass which could in theory change the type. That explains the behavior at least

@RyanCavanaugh
Copy link
Member

The sort of logic you want to represent here isn't possible today. See #48992

@RyanCavanaugh RyanCavanaugh added the Design Limitation Constraints of the existing architecture prevent this from being fixed label Oct 24, 2022
@KenjiTakahashi
Copy link

I'd like to chime in here, as I think I'm seeing a very similar problem. Except that it is not with this, but with a generic type.
I.e.

interface Ev<T> {
    e: T;
}

interface Test {
    finally: Ev<{status: "done"}>;
}

type Extends<T> = T extends Ev<infer E> ? number : string;

class C<T extends Test> {
    constructor() {
        const s1: Extends<T["finally"]> = "";
        const s2: Extends<T["finally"]> = 1;
    }
}

[Playground link]

The real life case is that interfaces Ev and Test come from a third party library, but I need to retrieve only the inside of the finally type.

Honestly, I find it a bit hard to grasp the concept of applying a ternary operator and getting neither of the branches evaluated. Wonder how that would work in "actual" language ;-)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

4 participants