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 Any
thing.
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 object
s 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
Create a Loading Image/ Activity Indicator, Until the Image Is Shown in the Screen in Swift
Xcode 7 Run on MAC Osx 10.10 Yosemite
Why Can't I Use .Reduce() in a One-Liner Swift Closure with a Variadic, Anonymous Argument
Swift Array to Array of Tuples
Swift Use Unicode Character in Localization.Strings
Swift: Print Name of a Function Stored in a Variable
Best Way to Avoid Capturing a Copy of the Value in Closure
Cast Cgfloat to Int in Extension Binaryfloatingpoint
Using Nil-Coalescing Operator with Try? for Function That Throws and Returns Optional
Xcode 6, Swift and Mock Verification Without Exceptions
Hmac Sha512 Using Commoncrypto in Swift 3.1
Attrackingmanager Stopped Working in iOS 15
Running Swift Build in Terminal Leading to "Platform Path" Errors
Catch Any Error, Specially Unexpectedly Found Nil in Swift
Non-Modular Headers of Openssl Library When Using Modulemap for Swift Framework
Make a Grid of Buttons of Same Width and Height in Swiftui