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

Broken Type guard narrowing when strictnullcheck is false #61546

Open
JeanMeche opened this issue Apr 7, 2025 · 3 comments
Open

Broken Type guard narrowing when strictnullcheck is false #61546

JeanMeche opened this issue Apr 7, 2025 · 3 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@JeanMeche
Copy link
Contributor

JeanMeche commented Apr 7, 2025

πŸ”Ž Search Terms

  • narrowing
  • type guard
  • generic
  • strictNullCheck
  • strict

πŸ•— Version & Regression Information

Not a regression

⏯ Playground Link

https://www.typescriptlang.org/play/?strict=false&strictFunctionTypes=false&strictPropertyInitialization=false&strictBindCallApply=false#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgMQggB4AVAPjgG8AoOOKYTAEwiQBsBPOAN03YCuwAFxxSAbjpwAFpgDOANX5CAFAEpRMaQjmJdhEgFEQ2Qc2BkANHAFJz6ZMGblykgL40a505kZxsbHLw6ESiBsRBUMgA5nAAPjZ2wA5ITq40COhwKiEQAHSyisrA6mrUUrlSAPRVAHoA-DRucMDscni09JX0NQ1NnkA

πŸ’» Code

export interface Foo<T> {
  readonly value: T;
  hasValue(): this is Foo<Exclude<T, undefined>>;
}

declare const foo: Foo<string | undefined>;
if (foo.hasValue()) {
} else {
  foo
  //^ never when strictNullCheck false
}

πŸ™ Actual behavior

In the else block, foo is never

πŸ™‚ Expected behavior

foo should be Foo<string|undefined>

Additional information about the issue

No response

@MartinJohns
Copy link
Contributor

I would say this is working as expected and intended.

The type undefined does not exist when strictNullChecks is turned off. So the types you're dealing with are Foo<string> and Foo<Exclude<T, never>>, not Foo<string | undefined> and Foo<Exclude<T, string>>.

So you're checking if foo (typed Foo<string>) is of the type Foo<Exclude<string, never>>, which is essentially the same as Foo<string>, and as a result you end up with never in your else-block because there is no other type it could be.

@wartab
Copy link

wartab commented Apr 7, 2025

While I understand why it does this, I do still believe that this shouldn't be happening, because regardless of how the mechanism works internally, it doesn't respect the actually defined signature of the method.

Concretely, in this case, the error comes from the fact that Angular has a similar type definition in its library:
https://github.com/angular/angular/blob/9228a733631a7d3ba79456c7b2da6e6ff239d4cb/packages/core/src/resource/api.ts#L135-L142

export interface ResourceRef<T> extends WritableResource<T> {
  hasValue(): this is ResourceRef<Exclude<T, undefined>>;
}

When Angular user-land has strictnullcheck off, calling the code shown in this Angular issue will cause a compilation error, while in strict-mode, it won't. It seems counter-intuitive that code would compile in a strict mode, but doesn't in a non-strict mode.

@MartinJohns
Copy link
Contributor

it doesn't respect the actually defined signature of the method.

But it does. It's just that the types null and undefined are being erased / don't exist. With SNC turned off the types string | null and string are exactly the same, respectively ResourceRef<Exclude<T, undefined>> and ResourceRef<Exclude<T, never>> are the same.

Same as: #58723 (comment)

If SNC is off, you shouldn't be talking about null, it doesn't exist.

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Apr 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants