Home > Blockchain >  readonly array union cannot be searched
readonly array union cannot be searched

Time:09-29

I want to search an element inside an readonly array union

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;
let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

// Argument of type 'number' is not assignable to parameter of type 'never'
areas[area].adjacencies.includes(3);

I also tried indexOf, but it didn't work either. And I found the includes type is ReadonlyArray<T>.includes(searchElement: never, fromIndex: number | undefined): boolean .

I suppose the includes type should be the union of the elements of two readonly array, just like below:

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(3);

How can I apply ElementUnion to includes or indexOf??

Here is the playground

CodePudding user response:

When you do as const you tell typescript "even if encounter a literal like 0 or "hello", rather than inferring it as a general number or string, infer it literally".

So unless both of the arrays has the element you search for, it wont work.

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8, 3]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

areas[area].adjacencies.includes(3);

Notice how I added a 3 to the second array.

Playground

Edit:

Look at the signature of the function, only the union of the arrays can be passed to it.

(method) ReadonlyArray<T>.includes(searchElement: 3, fromIndex: number | undefined): boolean

Edit 2:
Example:

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;

if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

// uh oh, .adjacencies can be either one of the arrays
// if the variable was declared without as const, the function would accept a number
// however since that it not the case, typescript takes the intersection
// (elements present in both arrays) as literal arguments
areas[area].adjacencies.includes(3);

///////////////////////////////////////////////////////////////
if (area === 'area1') {
  // here typescript knows what the elements are
  // but since the variable was declared as const, only literal values
  // that are in the array can be passed to the function
  //                          v Hover over this
  areas[area].adjacencies.includes(2);
} else {
  //                          v Hover over this
  areas[area].adjacencies.includes(6);
}

// Direct access
//                         v Hover over this
areas.area1.adjacencies.includes(2);
//                         v Hover over this
areas.area2.adjacencies.includes(6);
//                         v Hover over this
areas['area1'].adjacencies.includes(2)
//                         v Hover over this
areas['area2'].adjacencies.includes(6)

View in playground for hovers

Playground

CodePudding user response:

Thanks for @zerkms mentioning of https://stackoverflow.com/a/53035048/251311, there seems to be two workarounds.

const areas = {
  area1: {
    adjacencies: [2, 3, 4, 5]
  },
  area2: {
    adjacencies: [6, 7, 8]
  }
} as const;

type ValueOf<T extends object> = T[keyof T];
type Values = ValueOf<ValueOf<typeof areas>>
type ElementUnion = Values[number];

let area: keyof typeof areas;
if (Math.random() < 0.5) {
  area = "area1";
} else {
  area = "area2"
}

// first solution using type assertion
(areas[area].adjacencies as unknown as ElementUnion[]).includes(3); // works!

// second solution attempting using declaration merging
// from lib.es2016.array.include.d.ts, which I thick is quite ok
interface ReadonlyArray<T> {
  includes<T>(searchElement: T, fromIndex?: number): boolean;
}

areas[area].adjacencies is of type readonly [2, 3, 4, 5] | readonly [6, 7, 8], which should be equivalent of ReadonlyArray<2 | 3 | 4 | 5 | 6 | 7 | 8>, and I made an experiment

const readonlyArr = [1, 2, 3] as const;
readonlyArr.includes(3); // typescript successfully inferred T is 1 | 2 | 3

It seems like typescript is not able to convert union of readonly array to union of array members, which may be on purpose or not

  • Related