Skip to content

Deferred conditional in value position of mapped type is not re-evaluated when sufficient information is available to pick a branch #42385

@beezee

Description

@beezee

Bug Report

Per the docs on conditional types, deferred conditionals are expected to be resolved once sufficient context is provided (e.g. at a call-site) such that the compiler can evaluate a single branch of the conditional.

It appears that when the conditional type is in the value position of a mapped type, this does not occur.

🔎 Search Terms

deferred conditional types, mapped types

🕗 Version & Regression Information

  • This is the behavior in every version I tried (all versions on playground), and I reviewed the entire FAQ

⏯ Playground Link

Playground link with relevant code

💻 Code

interface Targets<A> {
    left: A
    right: A
}
type Target = keyof Targets<any>
type Result<F extends Target, A> = Targets<A>[F]

type LR<F extends Target, L, R> = [F] extends ["left"] ? L : R

interface Ops<F extends Target> {
    _f: F
    str: Result<F, string>
    num: Result<F, number>
    lr<I, O>(a: Result<F, I>, o: Result<F, O>): Result<F, LR<F, I, O>>
    dict: <P>(p: {[k in keyof P]: Result<F, P[k]>}) => Result<F, P>
}
const left: Ops<"left"> = {} as any
const right: Ops<"right"> = {} as any

const ok = <F extends Target>(at: Ops<F>) => ({lr: at.lr(at.str, at.num)})
const orphaned = <F extends Target>(at: Ops<F>) => at.dict(ok(at))

const leftOk = ok(left)
const leftOrphaned = orphaned(left)

const rightOk = ok(right)
const rightOrphaned = orphaned(right)

🙁 Actual behavior

Note that the assignments of leftOk and rightOk properly evaluate the conditional type to the branches dictated by the respective arguments passed to ok, with leftOk taking type {lr: string} and rightOk taking type {lr: number}.

However with the same information available (same exact arguments, just passed through one extra layer of call-stack, and a mapped type), leftOrphaned and rightOrphaned do not evaluate the conditional type, both remaining at type {lr: LR<F, string, number>} despite F being determined by the respective arguments passed to orphaned.

🙂 Expected behavior

The types of leftOrphaned and rightOrphaned should match the types of leftOk and rightOk respectively. Application of a type argument to a type parameter (in this case "left" or "right" to F) should apply to sub-expressions in a function body, and those applications should hold under a mapped type.

Metadata

Metadata

Assignees

Labels

BugA bug in TypeScriptFix AvailableA PR has been opened for this issue

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions