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.

Probable bug: Functional properties don't get inferred with circular type parameter constraints like `T extends M<T>`

See original GitHub issue

TypeScript Version: 4.0.2

Search Terms: Circular type parameter constraint, functional property inference, function parameter inference, function argument inference

Code:

declare const m: <T extends M<T>>(m: T) => T
type M<Self, K = Exclude<keyof Self, "k" | "t">> =
  { a?: number
  , b?: number
  , c?: number
  , d?: number
  , k?: K
  , t?: (k: K) => void
  }

// :)
// Case 1
m({
  a: 1,
  b: 2,
  k: "a"
})

// :)
// Case 2
m({
  a: 1,
  b: "x", // expected error
  k: "c" // expected error
})

// :(
// Case 3
m({ // type parameter becomes `unknown` (probably because t's first parameter is `any` initially kinda?)
  a: 1,
  b: 2,
  k: "a", 
  t: k => {} // k is inferred as `never` (instead of `"a" | "b"`)
})

// :|
// Case 4
m({
  a: 1,
  b: 2,
  k: "a", 
  t: (k: "a" | "b") => {} // have to explicitly type `k` which could have been inferred :(
})

Expected behavior: The parameter k of t should be inferred as "a" | "b". As property k already gets inferred in Case 1 & 2, so why not the k parameter of t

Actual behavior: The parameter of t is inferred as never and T in inferred as unknown. I assume first T gets resolved to unknown as t is initially inferred as (k: any) => void which does not satisfy the constraint of m.

Playground

Issue Analytics

  • State:open
  • Created 3 years ago
  • Comments:14 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
devanshjcommented, Jul 15, 2021

Repro for the phantom “_” bug. Playground. I know you can type this in a straight forward way but that’s besides the point for the above reasons. It’s only the case here because this is a minimal repro, in reality the requirements are much more.

// Initial issue
createMachine1({
  initial: "a",
  context: { foo: 1 },
  states: {
    a: {
      entry: p => { // p is any :(
      }
    }
  }
})

// with InferNarrowestObject workaround
createMachine2({
  initial: "a",
  context: { foo: 1 },
  states: {
    a: {
      entry: p => { // p is { foo: number }
      },
      _: null, // tho we need this phantom property...
    }
  }
})

// ...doesn't work without "_"
createMachine2({
  initial: "a",
  context: { foo: 1 },
  states: {
    a: {
      entry: p => {
      }
    }
  }
})

declare const createMachine1: <D extends Machine<D>>(definition: D) => void
declare const createMachine2: <D extends Machine<D>>(definition: InferNarrowestObject<D>) => void
type Machine<Self> = 
  { initial: keyof Prop<Self, "states">
  , context: Prop<Self, "context">
  , states:
    { [S in keyof Prop<Self, "states">]:
        { entry?: (context: Prop<Self, "context">) => void
        , _?: null
        }
    }
  }

type InferNarrowest<T> =
  T extends any
    ? ( T extends (...a: any[]) => any ? T :
        T extends object ? InferNarrowestObject<T> :
        T
      )
    : never

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

type Prop<T, P> =
  P extends keyof T ? T[P] : never
2reactions
RyanCavanaughcommented, Jun 22, 2021

Sorry for leaving in the bad comments, good reminder for me for next time 😅

Read more comments on GitHub >

github_iconTop Results From Across the Web

Constrain generic type parameter passed to a function
The only way that I've come up with is to infer generic parameters with conditional types and pass them to the function manually:...
Read more >
Chapter 18. Type Inference
This section defines inference variables, constraint formulas, and bounds, as the terms will be used throughout this chapter. It also presents notation.
Read more >
4. Functions - Programming TypeScript [Book] - O'Reilly
Chapter 4. Functions In the last chapter we covered the basics of TypeScript's type system: primitive types, objects, arrays, tuples, and enums, as...
Read more >
Generics - mypy 0.991 documentation
Generic types have one or more type parameters, which can be arbitrary types. ... Note that the generic aliases in typing don't support...
Read more >
Documentation - Everyday Types - TypeScript
Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach function, along with the inferred type...
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