click here to play with it -> flow.org/try/...
// X must be Assignable to boolean
// if (X is Assignable to true) -> Then
// else -> Else
type $If<X: boolean, Then, Else = empty> = $Call<
& ((true, Then, Else) => Then)
& ((false, Then, Else) => Else),
X,
Then,
Else,
>;
// X must be Assignable to boolean
// if (X is Assignable to true) -> false
// else -> true
type $Not<X: boolean> = $If<X, false, true>;
// X must be Assignable to boolean
// Y must be Assignable to boolean
// if (X is Assignable to true) -> Y
// else -> false
type $And<X: boolean, Y: boolean> = $If<X, Y, false>;
// X must be Assignable to boolean
// Y must be Assignable to boolean
// if (X is Assignable to true) -> true
// else -> Y
type $Or<X: boolean, Y: boolean> = $If<X, true, Y>;
// if (A is Assignable to B) true
// else false
type $Assignable<A, B> = $Call<
& ((B, true, false) => true)
& ((A, true, false) => false),
A,
true,
false,
>;
type User = {|
id: string,
name: string,
|}
type UserWith<Flags> = {|
...User,
...$If<$Assignable<'profile', Flags>, {| profile: string |}, {||}>,
...$If<$Assignable<'role', Flags>, {| role: string |}, {||}>
|}
;({
id: 'string',
name: 'string'
}: User) // ok
;({
id: 'string',
name: 'string',
profile: ''
}: User) // err
;({
id: 'string',
name: 'string',
profile: ''
}: UserWith<'profile'>) // ok
;({
id: 'string',
name: 'string',
role: ''
}: User) // err
;({
id: 'string',
name: 'string',
role: ''
}: UserWith<'role'>) // ok
;({
id: 'string',
name: 'string',
profile: '',
role: ''
}: UserWith<'role' | 'profile'>) // ok