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.
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
.
Most helpful comment
@emirotin If it can be helpful there's a possible workaround using a bit of type level programming