I need a type that is essentially "any string except for {reservedKeywords}". However, that is apparently either impossible or tricky and undwieldly.
I can settle for having to specify the string literals to use.
This is as close as I can get to what I want, using Exclude<T,K>
:
type AnySet<set extends string> = set
let test1: Exclude<AnySet<"a" | "1">, "1">
test1 = "a" //passes correctly
test1 = "1" //fails correctly
let test2: Exclude<AnySet<string>, "1">
test2 = "a" //passes correctly
test2 = "1" //passes (counterintuitively :( )
Is there any way I can prohibit passing string
directly?
Furthermore, is there a way to define a type is any collection of string literals but not specifically a string
?
Here's what I mean:
type AnySet<set extends string> = set
let test1: Exclude<AnySet<"a" | "1">, "1">
test1 = "a" //passes correctly
test1 = "1" //fails correctly
let test2: Exclude<AnySet<string>, "1">
test2 = "a" //passes correctly
test2 = "1" //passes (counterintuitively)
type FiniteSet<set extends ???> = set
let test3: Exclude<FiniteSet<"a" | "1">, "1">
test3 = "a" //should work
test3 = "1" //should not work
let test4: FiniteSet<string> //should fail
let test5: FiniteSet<"a" | "b" | "c"> //should work
CodePudding user response:
You could use a recursive constraint (so-called F-bounded quantification) like S extends Exclude<string, S>
:
type FiniteSet<S extends Exclude<string, S>> = S
let test3: Exclude<FiniteSet<"a" | "1">, "1">
test3 = "a" // okay
test3 = "1" // error
let test4: FiniteSet<string> // error
let test5: FiniteSet<"a" | "b" | "c"> // okay
This works because the Exclude<T, U>
utility type filters unions in T
to remove anything assignable to U
. If T
is string
then the output will either be string
(if string
is a not a subtype of U
) or never
(if string
is a subtype of U
). String literal types are subtypes of string
but not vice-versa, this gives the behavior you're looking for.