Reputation: 1402
In typescript, how can I express that a function only accepts an empty map or array?
Something like this (but it does not work):
function foo(emptyMapOrArray: {} | []) {
//...
}
// This should error
foo(["a"])
foo({a: 1})
// This should work
foo([])
foo({})
The use-case is that I want to be able to assign an "atomic" value to a part of a JSON document. Something like the cursor used in this paper. So a more complete example would be something like this:
function assign(map: {}, key: string, value: string | number | boolean | {} | []) {
map[key] = value
}
However I realised I can model the empty-map and empty-array as separate operations. But still I think it is interesting to know how expressive the type system in typescript can be in this case.
Upvotes: 3
Views: 19047
Reputation: 23692
--strictNullChecks
I suggest:
function foo(emptyMapOrArray: { [index: string]: never } | never[]) {
//...
}
Notice: This code requires the compiler option strictNullChecks
. Otherwise, the statement foo([])
doesn't compile.
--strictNullChecks
Without the compiler option strictNullChecks
, you could prefer:
function foo(emptyMapOrArray: { [index: string]: never } | undefined[]) {
//...
}
But it's not perfect:
foo([undefined]) // no error here, but it should!
Upvotes: 2
Reputation: 579
Like Paleo said, you can check for empty object with { [index: string]: never }
.
However, TypeScript can only enforce the type of elements you put into an array, it cannot check the length of an array for you, so the second part of your question is impossible with TypeScript.
This issue on GitHub has some comments on it (emphasis mine):
Our view is that tuple is an array for which we have static knowledge of the individual types of the first N elements and for which the element type is the union of the types of those N elements. We allow more than N elements to be present (since there's really not much we can do to prohibit it), but we do require those elements to have a compatible type.
When realizing that there is no checking on the number of array elements the tuple feature doesn't seem as attractive as it did in my first impression.
Tuples are basically objects with numeric properties. So
foo: [string, string]
is like sayingfoo: {0: string, 1: string}
.
According to the spec section 3.3.3, the type
[number, string]
is equivalent to an interface that extends Array:interface NSPair extends Array<number | string> { 0: number, 1: string }
If you try to implement an interface like the latter, you will get an error:
interface EmptyArray extends Array<any> { 0: never; }
const doesntWork: EmptyArray = [];
The error given being TS2322: Type 'undefined[]' is not assignable to type 'EmptyArray'. Property '0' is missing in type 'undefined[]'.
If you attempt extends Array<never>
, the error is the same. Basically no matter how you try to wrangle the definition, you'll get errors.
The way I understand it is that .length
is a runtime property. TypeScript would have to run your code to check it, which it can't do.
Upvotes: 3