question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

Reverse mapped type with a circular type param sometimes not treated as partially inferrable

See original GitHub issue

Bug Report

šŸ”Ž Search Terms

reverse mapped types, circular type, partially inferrable

āÆ Playground Link

Playground link with relevant code

šŸ’» Code

type AnyFunction = (...args: any[]) => any;

type InferNarrowest<T> = T extends any
  ? T extends AnyFunction
    ? T
    : T extends object
    ? InferNarrowestObject<T>
    : T
  : never;

type InferNarrowestObject<T> = {
  readonly [K in keyof T]: InferNarrowest<T[K]>;
};

type Config<TGlobal, TState = Prop<TGlobal, "states">> = {
  states: {
    [StateKey in keyof TState]: {
      on?: {};
    };
  };
} & {
  initial: keyof TState;
};

type Prop<T, K> = K extends keyof T ? T[K] : never;

const createMachine = <TConfig extends Config<TConfig>>(
  _config: InferNarrowestObject<TConfig>
): void => {};

createMachine({
  initial: "pending",
  states: {
    pending: {
      on: {
        done() {
          return "noData";
        },
      },
    },
  },
});

šŸ™ Actual behavior

Inferred signature is:

const createMachine: <Config<{
    initial: "pending";
    states: unknown;
}, unknown>>(_config: InferNarrowestObject<Config<{
    initial: "pending";
    states: unknown;
}, unknown>>) => void

šŸ™‚ Expected behavior

states: unknown shouldnā€™t be inferred here, the reverse mapped type should be able to infer an object literal type~. The type param should be inferred as something like:

const createMachine: <{
    initial: "pending";
    states: {
        pending: {
            on: {
                done: () => "noData";
            };
        };
    };
}>(_config: InferNarrowestObject<{
    initial: "pending";
    states: {
        pending: {
            on: {
                done: () => "noData";
            };
        };
    };
}>) => void

We can simplify the repro a little bit, but it will then yield an error at a different position and I canā€™t verify right now if the underlying issue is exactly the same in this case (although from what it looks like the root cause is super similar): TS playground without conditional type applied at the argument position

Note that we can fix both playground by using an arrow function instead of a method (probably related to a possible ā€œhiddenā€ this type param that makes this context sensitive in the case of a method).

The first one can be fixed by adding a dummy property to the object containing a method (this makes the object partially inferrable): TS playground with a dummy property added

Especially given that a dummy property fixes the problem it looks like a weird design limitations.

What Iā€™ve learned when debugging this:

  1. methods + functions with arguments are context sensitive and in checkFunctionExpressionOrObjectLiteralMethod they return anyFunctionType
  2. anyFunctionType has ObjectFlags.NonInferrableType on it
  3. this flag is ā€œpropagatingā€ and thus is set on the ā€œparentā€ object
  4. uninstantiatedType for the on property of this argument gets computed to {} (so itā€™s empty~)
  5. and thus it doesnā€™t pass isPartiallyInferableType check when resolving the structure of the reverse mapped type, since the object has ObjectFlags.NonInferrableType on it AND there are no other properties that would be treated as partially inferrable
  6. since no structure is resolved for this mapped type the unknown is returned for the states property
  7. this in turn makes initial property to error because keyof unknown is never

while a workaround is ā€œknownā€ here (weā€™ve learned this hard way though)~ the whole thing still has some problems because we canā€™t provide a param type for the arrow functions contained in on property because once we add any unannotated params to an arrow function it becomes context-sensitive and results in a similar problem: TS playground with context-sensitive arrow function

Related to https://github.com/microsoft/TypeScript/issues/40439

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:1
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
Andaristcommented, Apr 27, 2022

@andrewbranch happy to hop on a call if you would be up for it - Iā€™ve spent a few hours investigating this in the debugger so I have a good understanding of what happens and where.

0reactions
andrewbranchcommented, Apr 27, 2022

Setting a milestone to look at this because Iā€™m intrigued, but there is a significant chance I wonā€™t be able to get to it in that timeframe or I wonā€™t find anything actionable.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Issues Ā· microsoft/TypeScript Ā· GitHub
Reverse mapped type with a circular type param sometimes not treated as partially inferrable Needs Investigation This issue needs a team member toĀ ......
Read more >
Documentation - Mapped Types
Mapped types build on the syntax for index signatures, which are used to declare the types of properties which have not been declared...
Read more >
Ballerina Language Specification
A function-signature with a return-type-descriptor that uses a type-reference to refer to a parameter name in this way is said to be dependently-typed....
Read more >
yFiles for HTML - Changelog
addText now advertises the proper type for its targetElement parameter in the typings and not just the documentation. The License.value property has now...
Read more >
The Ceylon Language
A class, interface, or type parameter may be defined as a subtype of another type. ... The prefix \I or \i is not...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found