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

Incorrect report of Signature '(): false' must be a type predicate since TS 5.5 #59252

Closed
KnorpelSenf opened this issue Jul 12, 2024 · 9 comments Β· Fixed by #59310
Closed

Incorrect report of Signature '(): false' must be a type predicate since TS 5.5 #59252

KnorpelSenf opened this issue Jul 12, 2024 · 9 comments Β· Fixed by #59310
Labels
Bug A bug in TypeScript Help Wanted You can do this

Comments

@KnorpelSenf
Copy link

KnorpelSenf commented Jul 12, 2024

πŸ”Ž Search Terms

type predicate this inference object

πŸ•— Version & Regression Information

  • This changed between versions 5.4 and 5.5

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.5.3#code/KYDwDg9gTgLgBASwHY2FAZgQwMbDgeTBgQiUwBsAeAFQD44BvAKDlbgDcKBXYAfgC441ANws2AC0wBnAGrdgACgCUgmOIRTEmwsVIUa9AGSMO8wdTgBfUZabZSU+JgBGU4CkE6SZKki4BbZzR6AF5GMVZOch5BLiQAE2B0ZGB4gBoIuElZeWVwtgK4KGAYLigkOCxyN1ECywzrJiA

πŸ’» Code

export interface Optional<T> {
    value?: T;
    hasValue(): this is Optional<T> & { value: T };
}
const absent: Optional<number> = {
    value: undefined,
    hasValue() {
        return false;
    },
};

πŸ™ Actual behavior

Type '() => false' is not assignable to type '() => this is Optional<number> & { value: number; }'.
  Signature '(): false' must be a type predicate.deno-ts(2322)
file.ts(11, 5): The expected type comes from property 'hasValue' which is declared here on type 'Optional<number>'

πŸ™‚ Expected behavior

Code compiles just fine, just like it did prior to 5.5.

Additional information about the issue

It seems like it is impossible to install type predicates for this on POJOs. That is unexpected because the code works at runtime, and it compiled with TS 5.4. At runtime, this simply refers to the object which the function is installed on, and it is expected that the compiler can figure this out.

Note that this code does not even work when using a class as compilation fails for

class Absent implements Optional<number> {
    value: undefined;
    hasValue() {
        return false;
    }
}

with a related error:

Property 'hasValue' in type 'Absent' is not assignable to the same property in base type 'Optional<number>'.
  Type '() => boolean' is not assignable to type '() => this is Optional<number> & { value: number; }'.
    Signature '(): boolean' must be a type predicate.

This seems like a closely related issue to me, but please let me know if you'd like me to open a separate issue for it.

You can, however, add the return type fot the type predicate explicitly and do

class Absent implements Optional<number> {
    value: undefined;
    hasValue(): this is Absent & { value: number } {
        return false;
    }
}

which compiles just fine, but that obviously requires us to use

const absent = new Absent();

which was not the original goal.

Adding the same explicit type annotation to the POJO is naturally not possible because A 'this' type is available only in a non-static member of a class or interface.

@KnorpelSenf KnorpelSenf changed the title Incorrect report of β€œSignature '(): false' must be a type predicate” since TS 5.5 Incorrect report of Signature '(): false' must be a type predicate since TS 5.5 Jul 12, 2024
@MartinJohns
Copy link
Contributor

Note that this code does not even work when using a class as compilation fails for

This is a design limitation. Types are not inferred from the implementing interface. See #1373.

@KnorpelSenf
Copy link
Author

Note that this code does not even work when using a class as compilation fails for

This is a design limitation. Types are not inferred from the implementing interface. See #1373.

Right, I see. Thanks for the info! I guess that part is unrelated then.

@Andarist
Copy link
Contributor

This has changed in #57341 . This type predicates became consistent there with identifier type predicates. Notice how both of those are errors in TS 5.4:

export interface Foo {
  isNumber: (a: unknown) => a is number;
}
const foo1: Foo = {
  isNumber: (arg) => {
    return false;
  },
};
const foo2: Foo = {
  isNumber: (arg) => {
    return typeof arg === "number";
  },
};

In TS 5.5 the second one is no longer an error. Nice!

The takeaway is that functions are never contextually-typed as type predicates, which could very well be by design. But to be sure we should wait for a TS team member to chime in.

@RyanCavanaugh
Copy link
Member

Not contextually typing functions as having type predicates is intentional, since it'd be way too easy to write an incorrect type guard without realizing that the thing you're writing is a type guard (because they're unchecked).

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 12, 2024
@KnorpelSenf
Copy link
Author

I understand. How should the above JavaScript be expressed as idiomatic TypeScript then?

@RyanCavanaugh RyanCavanaugh removed the Working as Intended The behavior described is the intended behavior; this is not a bug label Jul 12, 2024
@RyanCavanaugh
Copy link
Member

Well, that's a good question

@KnorpelSenf
Copy link
Author

Perhaps drop the safety net and use the LSP to make it more transparent that hasValue effectively problems a type cast? Unless we just say that the above JS is bad style and would be rewritten, but I'm not sure how.

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

This is just a bug; this in this position refers to a symbol name, not a type name, so the "you cannot refer to it here" check is just bogus and should be allowed

const foo2 = {
  isNumber(): this is { b: string } {
    return true;
  },
};

@KnorpelSenf
Copy link
Author

Nice, thanks for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants