AJP
AJP

Reputation: 28553

Typescript intersection of union and intersection not erroring

I would like to have a type that ensures an object has a type of A or B or A and B. However one of the cases that I think should fail is not. I'm sure it's something stupid I just can't see it yet.

interface ValueSelector
{
    type: "id" | "value_string"
    value: string
}

interface TemporalSelector
{
    id: number
}

type Selector = (ValueSelector & TemporalSelector) | ValueSelector | TemporalSelector

// Should error
const e0: Selector = {}
const e1: Selector = { id: 0, value: "" }  // <-- does not error
const e2: Selector = { type: "id" }
const e3: Selector = { type: "value_string" }
const e4: Selector = { value: "" }
const e5: Selector = { value: "" }

// Should pass
const a1: Selector = { id: 0 }
const a2: Selector = { type: "id", value: "" }
const a3: Selector = { type: "value_string", value: "" }
const a4: Selector = { id: 0, type: "id", value: "" }
const a5: Selector = { id: 0, type: "value_string", value: "" }

Upvotes: 1

Views: 129

Answers (1)

e1 does not trigger an error because { id: 0, value: "" } is already assignable to TemporalSelector since it expects only id property.

In order to make it work you can use the StrictUnion helper:

interface ValueSelector {
    type: "id" | "value_string"
    value: string
}

interface TemporalSelector {
    id: number
}

type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> = 
    T extends any 
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>


type Selector = (ValueSelector & TemporalSelector) | StrictUnion<ValueSelector | TemporalSelector>

// Should error
const e0: Selector = {}
const e1: Selector = { id: 0, value: "" }  // error
const e2: Selector = { type: "id" }
const e3: Selector = { type: "value_string" }
const e4: Selector = { value: "" }
const e5: Selector = { value: "" }

// Should pass
const a1: Selector = { id: 0 }
const a2: Selector = { type: "id", value: "" }
const a3: Selector = { type: "value_string", value: "" }
const a4: Selector = { id: 0, type: "id", value: "" }
const a5: Selector = { id: 0, type: "value_string", value: "" }

Playground

Upvotes: 2

Related Questions