Skip to content

asserts this is T does not narrow type of this #60140

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

Open
OmarTawfik opened this issue Oct 4, 2024 Β· 5 comments
Open

asserts this is T does not narrow type of this #60140

OmarTawfik opened this issue Oct 4, 2024 Β· 5 comments
Labels
Cursed? It's likely this is extremely difficult to fix without making something else much, much worse Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Milestone

Comments

@OmarTawfik
Copy link

OmarTawfik commented Oct 4, 2024 β€’

πŸ”Ž Search Terms

πŸ•— Version & Regression Information

  • Playground: v5.7.0-dev.20241004
  • At least since v5.6.2

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/MYGwhgzhAECC0G8BQ1oHsB2ICeARApgGYCWG+AJgJIawAUAlIiqtMJhGiPgHQhoDmtAESwh9ANzMAvkmaQI+AE4AXOvQBc0eUuUxlAC2IwjcRNBkzZoedABCTVNpVrNT3dAMmT8BOaSXlbAAHfGgAYWgAXlMAHztJJEIAVwxgZWJMLSgdOmBNMI0shRUYYGhvJktk1PTM5XwIZQBGWjzwxmRUYG43NUku7kwcAhIyKhoGcWgAemnoAFFFRTRFTQAFZZCVbGgAciG8IlIKalhd6HI0BugMNGVofAAPI3u64NDdsN3uaA20LcCewOI2O4zOFyuMFu9yeL3QGA87z2tm+-lk1TSGQR9UaACZWvkOnJss5WhJmN1gUcxqdJjM5gB1FYAawgAEI0dAgA

πŸ’» Code

class A {
  onlyDefinedInA() {
    console.log("A");
  }

  assertA(): asserts this is A { }
}


class B {
  assertA(): asserts this is A { }
}

type C = A | B;

function assertA(c: C): asserts c is A {
}

function test1(c: C) {
  c.assertA();
  c.onlyDefinedInA(); // Error: Property 'onlyDefinedInA' does not exist on type 'C'. Property 'onlyDefinedInA' does not exist on type 'B'.
}

function test2(c: C) {
  assertA(c);
  c.onlyDefinedInA(); // Works!
}

Workbench Repro

πŸ™ Actual behavior

Type of c is not narrowed after the method call, producing an error.

πŸ™‚ Expected behavior

I expect the member function to to perform type narrowing.

Additional information about the issue

No response

OmarTawfik added a commit to NomicFoundation/jco that referenced this issue Oct 4, 2024
@Andarist
Copy link
Contributor

Andarist commented Oct 4, 2024

I also checked #59707, but it is unrelated (generic types).

It's still related - the generic types used there are red herring, the issue described there is not specific to them. See my comment in that issue, you have the same situation here - when the compiler sees a union it tries to create a composite signature from all of the constituents of that type. That process deliberately skips assertions.

@OmarTawfik
Copy link
Author

@Andarist I see. Thanks for confirming.

should it work or should it error in a visible way like @jcalz is suggesting?

Is there a technical reason that prevents the compiler from making it work? or is it just not implemented yet?
If it was the former, then I think it should be an explicit error, to save future users time debugging/root causing this like I did.
If it was the latter, then I suggest keeping the issue open, and maybe adding a few pointers on how to go about fixing it.

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this and removed Bug A bug in TypeScript Help Wanted You can do this labels Oct 16, 2024
@RyanCavanaugh
Copy link
Member

Not quite sure what to do with this.

It's obviously safe to maintain the predicateness when the asserted type in every signature is identical.

But only identical identical (i.e. mutual subtyping is not sufficient) is safe to do that, and we run into problems when we expose "identity" as a concept because you run into problems, e.g. if instead you had written

type X1 = { y: string; x: string };
type X2 = { x: string; y: string };

class A {
  onlyDefinedInA() {
    console.log("A");
  }

  assertA(): asserts this is X1 { }
}


class B {
  assertA(): asserts this is X2 { }
}

type C = A | B;

then this "wouldn't work" and we get bug reports that "X1 and X2 are identical" for some definition of "identical" and it just looks like a different kind of bug.

Or, worse, people start writing new cursed variants of IsEqual<T1, T2> and later when we fix this in some as-yet-undiscovered better way, we break that utility type.

So anyway I don't disagree it's suboptimal, but I am really not convinced that any known cure is better than the disease in this situation. boolean is still a sound inference here.

@RyanCavanaugh RyanCavanaugh added Cursed? It's likely this is extremely difficult to fix without making something else much, much worse Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases labels Oct 16, 2024
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Oct 16, 2024
@OmarTawfik
Copy link
Author

@RyanCavanaugh thanks for the detailed follow up!

In that case, at least until there is a better/future-proof way to support this, I wonder if it can be an explicit error in the meantime, to save future users time debugging/root causing this like I did. Unfortunately the current type information surfaced in the IDE indicates it should succeed (playground link above):

Image

@SebastienGllmt
Copy link

SebastienGllmt commented Dec 7, 2024 β€’

This is related to #28159

you can't change the value of a variable partway through a function. For the same reason that

let foo: number = 5;
foo = '' as string; // error: you already committed to foo being a number

you also get that

let self: this = this;
self = this as X1; // not going to work for the same reason

I agree it's unfortunate since this would be a useful functionality for builders so you don't have to construct a builder in one giant chain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Cursed? It's likely this is extremely difficult to fix without making something else much, much worse Possible Improvement The current behavior isn't wrong, but it's possible to see that it might be better in some cases
Projects
None yet
Development

No branches or pull requests

4 participants