Typescript: Suggestion: Allow skipping the properties of type `void`

Created on 14 Apr 2017  ·  9Comments  ·  Source: microsoft/TypeScript

TypeScript Version: 2.3.0

Code

I'd like this to be a valid TS:

interface T {
  a: number;
  b: void;
}

const x: T = { a: 1 }

Why would I?

This contrived example doesn't make much sense. Why would someone define a property of type void and then be willing to skip it, right?

Here's a lengthy explanation of my problem: http://stackoverflow.com/questions/43387510/typescript-for-generic-type-interface-make-the-property-required-only-if-its

Long story short I have a generic interface, whenever the type parameter is a _real type_ I want the property to be required, so I cannot mark it as optional.
But if the type is void (and I do use 2.3.0's default type parameters feature to set it and thus make the type parameter itself optional) I don't want to keep typing b: undefined everywhere.

Impact on emitted code: none, it only touches the TS compile-time validation.

Question

Most helpful comment

@emirotin If it can be helpful there's a possible workaround using a bit of type level programming

// type level boolean
type Bool = 'true' | 'false'
// type level if
type If<Test extends Bool, Then, Else> = {
  true: Then,
  false: Else
}[Test]

// definition

type Void = { isVoid: 'true', type: void }
type Type<T> = { isVoid: 'false', type: T }
type AnyType = Void | Type<any>

// usage

type BaseAction = { type: string }

type Action<T extends AnyType> = If<
  T['isVoid'],
  BaseAction,
  BaseAction & { payload: T['type'] }
  >

// tests

const action1: Action<Void> = { type: 'action1' }
const action2: Action<Type<number>> = { type: 'action1', payload: 2 }
const action3: Action<Type<number>> = { type: 'action1', payload: '2' } // error
const action4: Action<Type<number>> = { type: 'action1' } // error
const action5: Action<Void> = { type: 'action1', payload: undefined } // error

All 9 comments

it seems to me that your type T should be defined as:

type T<P> = {a: number } | {a: number, b: P};

That wouldn't do the trick because it doesn't enforce b when P isn't void:

then yous should have two types:

type PlainAction = { a: number };
type ActionWithPayload<T> = { a: number, b: T };
type AnyAction<T> = PlainAction | ActionWithPayload<T>;

const x: ActionWithPayload<number> = { a: 1 }; // Error

You're not making use of the AnyAction type which is as useless as the previous example — doesn't enforce b.

The linked SO question explains why having two separate types is impractical.

@emirotin If it can be helpful there's a possible workaround using a bit of type level programming

// type level boolean
type Bool = 'true' | 'false'
// type level if
type If<Test extends Bool, Then, Else> = {
  true: Then,
  false: Else
}[Test]

// definition

type Void = { isVoid: 'true', type: void }
type Type<T> = { isVoid: 'false', type: T }
type AnyType = Void | Type<any>

// usage

type BaseAction = { type: string }

type Action<T extends AnyType> = If<
  T['isVoid'],
  BaseAction,
  BaseAction & { payload: T['type'] }
  >

// tests

const action1: Action<Void> = { type: 'action1' }
const action2: Action<Type<number>> = { type: 'action1', payload: 2 }
const action3: Action<Type<number>> = { type: 'action1', payload: '2' } // error
const action4: Action<Type<number>> = { type: 'action1' } // error
const action5: Action<Void> = { type: 'action1', payload: undefined } // error

Wow @gcanti this is rad :) The If part with immediate object access is something I would never expect to work.

Obviously I'd like my code to work with "native" types without the custom Type / Void wrappers, but anyway this is an awesome lesson, thank you.

Hm, actually this doesn't seem to work: the If seems to be generalized to Then | Else, and also any seems to be plaguing the entire codebase preventing it from being specific (but the latter is easy to fix): playground

@emirotin I'm not sure how the playground works (which version, which flags...), I found it unreliable: it often shows different results with respect to my local configuration. The code I posted seems to work fine in my setup (TypeScript v2.2.2)

Huh, will try locally then, thanks again
On Sat, Apr 15, 2017 at 4:43 PM Giulio Canti notifications@github.com
wrote:

@emirotin https://github.com/emirotin I'm not sure how the playground
works (which version, which flags...), I found it unreliable: it often
shows different results with respect to my local configuration. The code I
posted seems to work fine in my setup (TypeScript v2.2.2)


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/15189#issuecomment-294294158,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAgGCKdWR7Un5IKsFPVr18pHYhkabCEmks5rwMlxgaJpZM4M9mQ4
.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  ·  3Comments

Roam-Cooper picture Roam-Cooper  ·  3Comments

fwanicka picture fwanicka  ·  3Comments

Antony-Jones picture Antony-Jones  ·  3Comments

dlaberge picture dlaberge  ·  3Comments