Skip to content

Bad inference when passing a field of a discriminated union as field function instead of arrow function #52798

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
paoloricciuti opened this issue Feb 16, 2023 · 1 comment Β· Fixed by #52837
Labels
Needs Investigation This issue needs a team member to investigate its status.
Milestone

Comments

@paoloricciuti
Copy link

Bug Report

πŸ”Ž Search Terms

discriminated union function

⏯ Playground Link

Playground link

πŸ’» Code

import React from "react";

//three random types, non crucial for this example
type A = {
  a: boolean,
}

type B = {
  b: string,
}

type C = {
  c: number;
}

//this is the animations props i'm taking in. is a record of keys and an object composed of
// {value: number} and a discriminated union. If kind = "a" you you have to pass A fields
// and optionally a function that returns a partial of A.
type Animations = {
  [key: string]: ({ value: number } & (
    { kind: "a", func?(): Partial<A> } & A |
    { kind: "b", func?(): Partial<B> } & B |
    { kind: "c", func?(): Partial<C> } & C
  ))
}

type StyleParam<T extends Animations> = Record<keyof T, string>;

//the props of the component...it take animations T and a function that takes
//a Record with all the keys of T and strings.
type AnimatedViewProps<T extends Animations> = {
  style: (
    animationsValues: StyleParam<T>
  ) => string;
  animations: T;
};

const Component = <T extends Animations>({ animations, style }: AnimatedViewProps<T>) => <></>

const App = () => {
  return <>
    <Component animations={{
      test: {
        kind: "a",
        value: 1,
        a: true,
      }
    }}
      style={(anim) => {
              //^?
        //here has you can see i get a narrower type
        return ""
    }} />
    <Component animations={{
      test: {
        kind: "a",
        value: 1,
        a: true,
        //but as soon as i add this func
        func(){
          return {
            a: true,
          }
        }
      }
    }}
      style={(anim) => {
              //^?
        //i get a wider generic type
        return ""
    }} />
    <Component animations={{
      test: {
        kind: "a",
        value: 1,
        a: true,
        //but if the function is an arrow function
        func: ()=>{
          return {
            a: true,
          }
        }
      }
    }}
      style={(anim) => {
              //^?
        //is once again a narrower type
        return ""
    }} />
  </>
} 

πŸ™ Actual behavior

If i pass func as a field function the type inference gets lost

πŸ™‚ Expected behavior

It should not matter if the function is passed as an arrow function or as a normal function

@Andarist
Copy link
Contributor

The whole object containing a context-sensitive function is marked as non-inferrable, so u don't gather that object as an inference candidate, it escapes here, just before pushing it as a candidate:
https://github.dev/microsoft/TypeScript/blob/588dd82afe1dd7049f0915c9c45e6f4557bcbdc3/src/compiler/checker.ts#L24141-L24159

The context-sensitive function is replaced during inference with anyFunctionType (https://github.dev/microsoft/TypeScript/blob/588dd82afe1dd7049f0915c9c45e6f4557bcbdc3/src/compiler/checker.ts#L35391), that has ObjectFlags.NonInferrableType on it and that's a "propagating" flag so it taints the whole object here: https://github.dev/microsoft/TypeScript/blob/588dd82afe1dd7049f0915c9c45e6f4557bcbdc3/src/compiler/checker.ts#L29781

Perhaps this could be fixed if instead of using anyFunctionType to replace context-sensitive functions, we'd use some kind of a reverse~ symbol. If I understand correctly, the point here is to block inferring from this function + avoid exposing it to the user through the candidate. But if that property would be allowed on the candidate but if its type would get deferred and resolved later then maybe this could work. There is also a good chance that I don't know what I'm talking about - I don't have a good mental model for conflicting inference candidates and stuff like that yet.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Feb 17, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Feb 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants