0

I got a couple of ways to define a NonEmptyArray from this question. They are mostly working but I get a weird thing when I use it with a code created array:

type NonEmptyArray<T> = T[] & { 0: T };

function myFunction(param: NonEmptyArray<number>){

}

const myArray = [1,2];

myFunction([1,2]); // OK
myFunction(myArray); // ERROR

Playground

The error says:

Argument of type 'number[]' is not assignable to parameter of type 'NonEmptyArray'. Property '0' is missing in type 'number[]' but required in type '{ 0: number; }'.(2345)

If I define the type hard then the error is gone:

type NonEmptyArray<T> = T[] & { 0: T };

function myFunction(param: NonEmptyArray<number>){

}

const myArray:  number[] & {0: number} = [1, 2];

myFunction([1,2]); // OK
myFunction(myArray); // Also OK

I am not sure if this is a bug in the type definition or in typescript or I am not seeing something. Does anyone knows what is happening here?

3 Answers 3

1

When you say

const myArray = [1,2];

Typescript infers it as number[] -- a mutable array that may or may not have an element. Hence myArray[0] may be undefined. Or, what if there function that resets the array to empty.

type NonEmptyArray<T> = T[] & { 0: T };

function myFunction(param: NonEmptyArray<number>){

}

const myArray = [1,2];

resetMyArrayEmpty(myArray) // <-- we do not know how it alters the array

myFunction([1,2]); // OK
myFunction(myArray); // ERROR

Typescript has no way to guarantee that defined array is going to stay immutable, hence the error. You either take the ownership and explicitly say the type is number[] & {0: number} or ensure that the array is immutable as described below:

A way to go about it is to take readonly array and define the array as using const assertion. Something like this:

type NonEmptyArray<T> = readonly T[] & { 0: T };

function myFunction(param: NonEmptyArray<number>){

}

const myArray = [1,2] as const;

myFunction([1,2]); // OK
myFunction(myArray); // OK

https://tsplay.dev/N7bVrw

Sign up to request clarification or add additional context in comments.

Comments

1

When you use assignment, the variable type is inferred if it's not defined, meaning

const myArray = [1,2];
// myArray is typed as number[] not [1,2]

number[] as you know is an array of any length containing only numbers.This means that the array is not required to have an element at index 0.

As such, 0 as a property is unknown in the number[] array.

This results in your error being thrown:

Property '0' is missing in type 'number[]' but required in type '{ 0: number; }'

As you've already noted, this does however work when you define the type for myArray.

const myArray:  number[] & {0: number} = [1, 2];

As this obviously matches the type you defined. (NonEmptyArray)

Comments

1

The type is inferred as number[], which can be empty:

type NonEmptyArray<T extends {}> = T[] & { 0: T };

function myFunction(param: NonEmptyArray<number>) {

}

const myArray = [1, 2];

myArray.pop(); // Empty the array
myArray.pop();

myFunction([1, 2]); // OK
myFunction(myArray); // ERROR, as expected

You can fix that by using as const:

const myConstArray = [1, 2] as const;
myFunction(myConstArray);

But then you get an error The type 'readonly [1, 2]' is 'readonly' and cannot be assigned to the mutable type 'number[]'., which you can fix with:

type NonEmptyArray<T extends {}> = readonly T[] & { 0: T };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.