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.

Contextual inference of functions in tuple types failures

See original GitHub issue

Bug Report

🔎 Search Terms

contextual inference tuple inference

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about contextual inference

⏯ Playground Link

Playground link with relevant code

💻 Code

class State<T> {
  // Returns a function that takes t, applies it to all provided fns,
  // and pushes the results through the projector
  public getProjectionFactory<R extends readonly unknown[], P>(
    ...args: [
      ...fns: { [i in keyof R]: (t: T) => R[i] },
      projector: (...a: R) => P
    ]
  ): (t: T) => P {
    // ignoring implementation...
    return undefined as any;
  }
}

type Foo = { foo: number };
const aFooState = new State<Foo>();

const testOne = aFooState.getProjectionFactory(
  ({ foo }) =>  foo,
  () => 2,
  // 👇 incorrectly inferred to be [unknown, number]
  (...args) => ({ result: args })
);

// 🚫 failureOne is [unknown, number] instead of expected [number, number]
const { result: failureOne } = testOne({ foo: 3 });

// 👇 incorrectly infers fns to be ((t: Foo) => unknown)[]
const testTwo = aFooState.getProjectionFactory(
  () => 1,
  () => 2,
  <A extends readonly unknown[]>(...args: A) => ({ result: args })
);

// 🚫 failureTwo is unknown[] instead of expected [number, number]
const { result: resultTwo } = testTwo({ foo: 3 });

🙁 Actual behavior

The projector and fns in the tuple type examples are incorrectly inferred.

🙂 Expected behavior

projector should be inferred as (...args: [number, number]) => { result: [number, number] } in the first test example.

fns should be inferred as [(t: Foo) => number, (t: Foo) => number] in the second test example.

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:2
  • Comments:10 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
RyanCavanaughcommented, Jan 20, 2022

Reduced a bit further and tried alternate definitions. It seems like the return type computation is not being sufficiently deferred; the usual tricks here don’t work.

// Attempted definition
declare function project1<R extends readonly unknown[]>(
  ...args: [
    ...fns: { [i in keyof R]: (t: string) => R[i] },
    projector: (...a: R) => unknown
  ]
): void;

// Desired behavior in OK case:
project1(
  () => 0,
  () => "",
  (...args) => {
    // args: [number, string]
  }
)

// Fails with context-sensitive argument
project1(
  () => 0,
  () => "",
  n => n.substring(0), // <- n: string (correct)
  (...args) => {
    // args: [number, string, unknown]
    // but should be
    // args: [number, string, string]
  }
)

// Having *all* context-sensitive arguments: implicit any
project1(
  // s: implicit any
  s => s.length,
  (...args) => {

  });

// Alternate definition; same behavior
type ReturnsOf<T extends readonly unknown[]> = { [K in keyof T]: T[K] extends (...args: any[]) => infer R ? R : never; };
declare function project2<R extends readonly ((n: string) => unknown)[]>(
  ...args: [
    ...fns: R,
    projector: (...a: ReturnsOf<R>) => unknown
  ]
): void;

// Alternate definition yields s: implicit any in all cases
declare function project3<R extends readonly ((n: string) => unknown)[]>(
  ...args: [
    ...fns: R & (readonly ((s: string) => unknown)[]),
    projector: (...a: ReturnsOf<R>) => unknown
  ]
): void;

// Projector-first definition without using tuple spread is OK
declare function project4<R extends readonly ((n: string) => unknown)[]>(
  projector: (...a: R) => unknown,
  ...fns: R
  
): void;
project4(
  (...args) => {

  },
  s => s.length,
 );
1reaction
RyanCavanaughcommented, Jan 5, 2022

This looks like a consequence of not having unification; see #30134. In the other examples, there is a candidate for Results (or equivalent) being provided through a contextual type, but this isn’t present at test1.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Use typed identity functions to guide type inference
But when you use tuples or string literal types, this will sometimes go wrong. This post explores using identity functions with carefully ...
Read more >
Type inference and type annotations - Mypy documentation
Type inference is bidirectional and takes context into account. The value expression [1, 2] is type checked with the additional context that it...
Read more >
Harmonizing Classes, Functions, Tuples, and Type ...
types. Surprisingly, we find variance for function types and tuple ... best-effort type inference algorithm4 for type arguments to both classes and methods....
Read more >
Type Errors - Pyre
Invariance, combined with type inference, comes with a few gotchas. ... If strict mode is turned on, Pyre will error when a function...
Read more >
Why is my generic function not monomorphized for a tuple?
You write "I know from context that E<T> instances will always be TisTupleVariant1 or TisTupleVariant1 ". But that is a fact that you...
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