------------- Prompt and background (Not crucial for understanding the question)----------------
In my TypeScript application, I work with objects that have references to other objects. These references can be either filled or empty, as illustrated below:
type Entity = {
ref1: string | Obj,
ref2: string | Obj
}
I wanted to implement a generic parameter T that allows me to specify which properties are populated. If T is not provided, all references must be strings. For example, if T = {ref1: Obj}, then ref1 should be populated while ref2 should remain empty. Similarly, if T = {ref1:Obj, ref2:Obj}, both references need to be populated. To achieve this flexibility, I ended up with the following approach:
type Entity<T extends Partial<{ref1: string|Obj, ref2:string|Obj}> = {}> = {
ref1: T["ref1"] extends string|Obj ? T["ref1"] : string,
ref2: T["ref2"] extends string|Obj ? T["ref2"] : string,
}
---------- The issue / query ------------------------
Everything was functioning correctly until I encountered a scenario where TypeScript should have raised an error but didn't. To investigate further, I simplified the above type since union types with conditional types can lead to unexpected results. Upon doing so, I observed a strange outcome in the TypeScript playground:
--- Minimal reproducible example (Playground link)---
type Entity<T extends {ref1?:unknown}> = {
ref1: T["ref1"] extends string ? T["ref1"]: number;
}
type A = Entity<{ref1: string}> extends Entity<{}> ? true : false // should be true
type B = Entity<{ref1: number}> extends Entity<{}> ? true : false // incorrectly evaluates to true
type C = Entity<{ref1: number}> extends Entity<{ref1: undefined}> ? true : false // should be false
type D = Entity<{ref1: string}> extends Entity<{ref1: undefined}> ? true : false // incorrectly returns false
This inconsistency doesn't seem logical: In case B, Entity<{ref1: number}> simplifies to {ref1: number} while Entity<{}> simplifies to {ref1: string}, making them incompatible.
However, TypeScript comprehends the situation accurately in case C when I provide more explicit definitions.
Is there something about TypeScript that I'm missing here to explain this behavior, or could it be a bug within TypeScript itself?