Skip to content

Narrowing this using custom type predicate affected by TS 5.0 #53436

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
Andarist opened this issue Mar 22, 2023 · 5 comments
Open

Narrowing this using custom type predicate affected by TS 5.0 #53436

Andarist opened this issue Mar 22, 2023 · 5 comments
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@Andarist
Copy link
Contributor

Bug Report

πŸ”Ž Search Terms

narrowing this predicate

πŸ•— Version & Regression Information

  • This changed between versions 4.9 and 5.0

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

interface Typestate<TContext extends object> {
  value: string;
  context: TContext;
}

interface State<TContext extends object, TState extends Typestate<TContext>> {
  value: TState["value"];
  context: TContext;
  matches: <TSV extends TState["value"]>(
    value: TSV
  ) => this is TState extends {
    value: TSV;
  }
    ? TState & {
        value: TSV;
      }
    : never;
}

interface Machine<TContext extends object, TState extends Typestate<TContext>> {
  initialState: State<TContext, TState>;
}

export declare function createMachine<
  TContext extends object,
  TState extends Typestate<TContext> = {
    value: any;
    context: TContext;
  }
>(): Machine<TContext, TState>;

const machine = createMachine<{ count: number }>();
const state = machine.initialState;

if (state.matches("idle")) {
  ((_accept: number) => {})(state.context.count);
  // @ts-expect-error
  ((_accept: string) => {})(state.context.count);
} else if (state.matches("latest")) {
  ((_accept: number) => {})(state.context.count);
  // @ts-expect-error
  ((_accept: string) => {})(state.context.count);
}

πŸ™ Actual behavior

state gets narrowed down to never in the else branch

πŸ™‚ Expected behavior

state to only get narrowed down to the appropriate value within the if branch

cc @ahejlsberg

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this labels Apr 7, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Apr 7, 2023
@unworthyEnzyme
Copy link

@Andarist can you check out this repo's devbranch? I'm using type predicate functions here. I'm getting the same error typescript version 4.9.4(with deno version 1.29.4) too. The weird thing is if I reorder the conditions so that the branches with the type inferred as never is on the top, the problem goes away. You can see the reordered version in the fixed-typescript-errors.

@Andarist
Copy link
Contributor Author

If you experience the same problem with 4.9 then it's a different issue - since the issue that I reported herein a behavior change that was introduced 5.0

@unworthyEnzyme
Copy link

ok, i'll open a separate issue. It looks quite similar, i couldn’t be sure.

@ssalbdivad
Copy link

ssalbdivad commented Oct 26, 2023

Ran into a possibly related issue today with some relatively straightforward types:

https://tsplay.dev/WGYBkN

type AbstractableConstructor<instance = {}> = abstract new (
    ...args: never[]
) => instance

type InstanceOf<constructor extends AbstractableConstructor> =  constructor extends AbstractableConstructor<infer instance> ? instance : never

export declare class ProtoNode<
	proto extends AbstractableConstructor = AbstractableConstructor
> {
    // Using the builtin InstanceType here unfortunately leads to any
    // This PR would also be a huge help!
    // https://github.com/microsoft/TypeScript/pull/55714
    declare instance: InstanceOf<proto>

	extendsOneOf<constructors extends readonly AbstractableConstructor[]>(
		...constructors: constructors
	): this is ProtoNode<constructors[number]>
}

const doSomething = (node: ProtoNode) => {
    if (node.extendsOneOf(Array)) {
        // Doesn't narrow node at all
        return node
        //      ^? ProtoNode<AbstractableConstructor<{}>>
    } else if (node.extendsOneOf(Date)) {
        // Thinks this is impossible
        return node
        //     ^? never
    }
    return false
}

#55714 would be a big help as well, but even working around that it seems the type guard doesn't actually do any narrowing, causing TS to infer that it is always satisfied?

Would be great to get a second look at this since the situations it leads to are very confusing to reason about.

@ssalbdivad
Copy link

Seems like upon further investigation the above may not be related to @Andarist's issue, but instead boils down to proto being treated as bivariant in ProtoNode (the repro is significantly simpler than the above, see #56225)

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

No branches or pull requests

4 participants