note:tue_jun_17_2025

This is an old revision of the document!


TS

typehero

declare const config: Chainable

const result = config
  .option('foo', 123)
  .option('name', 'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// expect the type of result to be:
interface Result {
  foo: number
  name: string
  bar: {
    value: string
  }
}

type Chainable<T = {}> = {
  option<K extends string, V>(key: Exclude<K, keyof T>, value: V): Chainable<Omit<T, K> & {[P in K]: V}>
  get(): T
}

Why K is Inferred, Not T?

TypeScript's inference flows from unknown type parameters based on known argument types:

// When you write:
config.option('database', 'mysql')

// TypeScript thinks:
// - Receiver type: Chainable<{}>, so T = {} (KNOWN)
// - First argument: 'database' (literal string)
//   → Must infer K = 'database' (UNKNOWN → INFERRED)
// - Second argument: 'mysql' (literal string)  
//   → Must infer V = 'mysql' (UNKNOWN → INFERRED)

What If T Were Also a Type Parameter?

If we hypothetically made T also inferrable, it would create ambiguity:

// Hypothetical (problematic) design:
type BadChainable = {
  option<T, K extends string, V>(
    key: Exclude<K, keyof T>, 
    value: V
  ): Chainable<Omit<T, K> & {[P in K]: V}>
}

// How would TypeScript know what T should be?
// There would be no way to infer T from the arguments!

  • note/tue_jun_17_2025.1750144450.txt.gz
  • Last modified: 2025/06/17 07:14
  • by lingao