Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Cannot escape distribution of conditional type properly #30020

Copy link
Copy link
@aoberoi

Description

@aoberoi
Issue body actions

TypeScript Version: 3.4.0-dev.201xxxxx

Search Terms: distributive conditional types, 1-tuple

Code

// Here are a couple types that will populate a discriminated union
interface UnionA {
  type: 'a';
  foo: boolean;
}
interface UnionB {
  type: 'b';
  bar: number;
}

// This is the discriminated union
type Union = UnionA | UnionB;

// Here is a type that is like those in Union, but a little more general
interface MyDefault {
  type: string;
  /* ... more default properties */
}

// The goal is to have a type whose generic parameter which can identify a type by the  discriminant from
// Union, and if one isn't found, resolves to MyDefault

// First let's define a helper that will either resolve to T when its not the empty union, or
// Default when it is the empty union. Notice that the 1-tuple wrapper is used to avoid distribution.
type FallbackWhenBottom<T, Default> = [T] extends [never] ? Default : T;

// Now, this completes the goal.
type UnionByTypeWithDefault<T extends string> = FallbackWhenBottom<Extract<Union, { type: T }>, MyDefault>

// Let's test it out
type Test = UnionByTypeWithDefault<'a'>; // success: Test === UnionA
type Test2 = UnionByTypeWithDefault<'c'>; // fail: Test2 === never, expected Test2 === MyDefault

// DARN! Let's try to pick apart Test2 the way we think it should be evaluated

// This should be evaluated first (rem, FallbackWhenBottom was defined not to be distributive)
type Test3<T> = Extract<Union, { type: T }>;
type Test4 = Test3<'c'> // success: Test4 === never

// Then that should be substituted into FallbackWhenBottom
type Test5 = FallbackWhenBottom<never, MyDefault> // success: Test5 === MyDefault

// It seems like FallbackWhenBottom *is* behaving as distributive, as that's the only explanation
// for how Test2 could be never - T as never is being treated as the empty union. The 1-tuple should be preventing this ([never]), but it's not.

Expected behavior:

Test2 should evaluate to MyDefault

Actual behavior:

Test2 is evaluated to never

Playground Link: Link

Related Issues: #29368, #29627,

Reactions are currently unavailable

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.