How to Restrict an Enum to Certain Cases of Another Enum

How to restrict an array to have every member of an enum in TypeScript

Option 1

We can solve this by creating a type containing all possible combinations of AllowedFruits.

type AllPermutations<T extends string | number> = [T] extends [never] 
? []
: {
[K in T]: [K, ...AllPermutations<Exclude<T, K>>]
}[T]

type AllFruitPermutations = AllPermutations<AllowedFruits>

This may result in bad performance if you have a lot of elements inside the enum because every single combination needs to be calculated first.

Let's see if this works:

/* Error */
/* Error */
const t1: AllFruitPermutations = []
const t2: AllFruitPermutations = [AllowedFruits.Apple]
const t3: AllFruitPermutations = [AllowedFruits.Apple, AllowedFruits.Banana]
const t4: AllFruitPermutations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear, AllowedFruits.Pear]

/* OK */
const t5: AllFruitPermutations = [AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear]

Playground

Option 2

It is also possible to solve this by passing allowedFruits to a function with a generic type.

We can create a generic helper type ExhaustiveFruits which checks if all enum values are present in the array.

type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string = `${AllowedFruits}`
> = [P] extends [never]
? O
: T extends [`${infer L}`]
? [P] extends [L]
? O
: never
: T extends [`${infer L}`, ...infer R]
? R extends AllowedFruits[]
? ExhaustiveFruits<O, R, Exclude<P, L>>
: never
: never

The logic of ExhaustiveFruits is quite simple: It is a recursive type where we start with a union of all enum values as P and the tuple of AllowedFruits as T.

For each element of T, the string value of the element is inferred with '${infer L}'. Afterwards this value is removed from the P union with Exclude<P, L>.

Every iteration there is a check if P is empty with [P] extends [never] or if the last element of T is the last element of P with [P] extends [L]. If this is the case, the original tuple O can be returned. If T is empty but P has still AllowedFruits in its union, never is returned.

The type can be used in a generic function createAllowedFruitsArray like this:

function createAllowedFruitsArray<
T extends AllowedFruits[]
>(arr: [...ExhaustiveFruits<T>]) : T {
return arr
}

Some checks to see if this is working:

createAllowedFruitsArray(
[] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana] // Error
)
createAllowedFruitsArray(
[AllowedFruits.Apple, AllowedFruits.Banana, AllowedFruits.Pear] // OK
)

Right now it would also be possible to use the same enum value multiple times, as long as all are used.

createAllowedFruitsArray(
[AllowedFruits.Apple,
AllowedFruits.Banana,
AllowedFruits.Pear,
AllowedFruits.Pear] // Also ok, even though Pear is twice in the array
)

But with a slight modification, we can also change this:

type ExhaustiveFruits<
O extends AllowedFruits[],
T extends AllowedFruits[] = O,
P extends string | number = `${AllowedFruits}`
> = [P] extends [never]
? O["length"] extends 0
? O
: never
: T["length"] extends 1
? [P] extends [`${T[0]}`]
? O
: never
: T extends [any, ...infer R]
? R extends AllowedFruits[]
? [`${T[0]}`] extends [P]
? ExhaustiveFruits<O, R, Exclude<P, `${T[0]}`>>
: never
: never
: never

Playground

How to restrict interface property type to subset of a declared enum?

The only way around this is to declare the enum differently.

One way to do this is quite verbose but acts a lot like a declared enum with unknown values. True enums in TypeScript add a lot of named types and values with a small amount of code, so to simulate it manually we need a lot of lines. The general technique is to declare a value and a type for each of A, A.B, A.C, and A.D, and to do it in such a way as to treat the types of each enum value as different from each other without knowing what they actually are:

declare namespace A {
interface _B { readonly __nominal: unique symbol; }
export type B = _B & number;
export const B: B;

interface _C { readonly __nominal: unique symbol; }
export type C = _C & number;
export const C: C;

interface _D { readonly __nominal: unique symbol; }
export type D = _D & number;
export const D: D;
}
type A = A.B | A.C | A.D;

In the above, the { readonly __nominal: unique symbol } serves as a branding technique using unique symbols to make TypeScript treat each of A.B, A.C, and A.D as nominally typed and therefore distinct types from each other despite having the "same" structure. This is lying to the compiler a little bit, since obviously at runtime A.C will not have a property named __nominal, but as long as you ignore this it should act fine.

Let's make sure: if you have a variable of the full enum type, you can assign any member to it:

let a: A = A.B; // okay
a = A.C; // okay
a = A.D; // okay

But if you have a variable of just one of the member types, you can't assign other ones:

let b: A.B = A.B; // specifically only A.B
b = A.C; // error! A.C not assignable to A.B
b = A.D; // error! A.D not assignable to A.B

Now your types are allowed:

interface ISomeRestrictedProps {
restrictedProp: A.B | A.C;
}

And behave more or less like you want:

const i: ISomeRestrictedProps = {
restrictedProp: A.B // okay
};
i.restrictedProp = A.C; // okay
i.restrictedProp = A.D; // error! D not assignable to B | C

Link to branded-enumlike code


Similarly, you can just do what @TitianCernicova-Dragomir suggested and give values to the enums. They don't really have to be the actual values, as long as they are distinct from each other and you don't make the mistake of treating them as the real values:

declare enum A {
B = 0xbbbbb, // dummy value for B
C = 0xccccc, // dummy value for C
D = 0xddddd // dummy value for D
}

Again you are lying to the compiler about the exact values, but as long as you just ignore the exact values and don't write code that cares about the exact values, it should be fine. The subsequent code should all work as you expect (and I'm not going to repeat the above code... you can see how it acts in the following link)

Link to dummy-val-enum code


In both of those you are lying a little bit to the compiler... the former method is verbose but doesn't pretend you know the numeric values of the enum, whereas the latter method is simpler but possibly runs more of a risk of an unwary developer thinking the dummy values are actual values.

Anyway, hope that helps; good luck!

Limit an argument of a function to an enum type only

It's possible to do this by using the tricks from How to create type safe enums? Taking the trick of type safe assignment described in the accepted answer:

typedef union
{
day_t monday;
day_t tuesday;
day_t wednesday;
day_t thursday;
day_t friday;
day_t saturday;
day_t sunday;
} typesafe_day_t;

#define day_assign(var, val) _Generic((var), day_t: (var) = (typesafe_day_t){ .val = val }.val)

Then given some function void set_day (day_t day), we can design a safe version using a wrapper macro:

#define set_day_s(day) set_day( day_assign((day_t){0},day) )

This makes a temporary copy of the passed argument and runs that through the type safety macro, blocking everything that isn't one of the mentioned enum constants.

Full example:

typedef enum
{
monday = 1,
tuesday,
wednesday,
thursday,
friday,
saturday,
sunday
} day_t;

typedef union
{
day_t monday;
day_t tuesday;
day_t wednesday;
day_t thursday;
day_t friday;
day_t saturday;
day_t sunday;
} typesafe_day_t;

#define day_assign(var, val) _Generic((var), day_t: (var) = (typesafe_day_t){ .val = val }.val)

void set_day (day_t day){ /* ... */ }
#define set_day_s(day) set_day( day_assign((day_t){0},day) )

int main (void)
{
set_day_s(monday); // OK

day_t d=monday;
// set_day_s(d); // compiler error
// set_day_s(1); // compiler error
}

Passing various enum case as function param in Swift

The linked question is actually quite close - you just have to change the parameter type to just T, instead of T.Type:

func printCase<T : RawRepresentable>(_ e: T) where T.RawValue == String {
print(e.rawValue)
}

Since you are accepting an instance of the enum, not the enum type itself.

Of course, you don't have to restrict this to enums where T.RawValue == String, since print can print Anything.

How to restrict enums in Kotlin?

What comes to mind (but does not work in Kotlin) would be to subclass the APIError enum while restricting the admissible values in each subclass. Is there a Kotlin solution that does something similar?

Yes, if you need to express a hierarchy, you could use sealed class/interface hierarchies with objects as leaves.

sealed class ApiError(val code: Int) {
object IncorrectCharacter : ApiError(1)
object MissingValue : ApiError(2)
}
sealed class SizeError(code: Int): ApiError(code) {
object TooSmall : SizeError(3)
object TooLarge : SizeError(4)
}

What you lose here compared to enums is the ability to list all possible values using ApiError.values(). But in this case it might not be an issue.

Also it might not be ideal to serialize (and even more so, deserialize), depending on which serialization library you're using.



Related Topics



Leave a reply



Submit