1

In this code example, the expandedMappedTuple does the same operation as the looseMappedTuple.

However, the looseMappedTuple has lost all information regarding the length and tuple sequence.

Is there a MappedTupleType which can fix this which could be added to the draft mapping code which follows, and cast the return value of .map with extra information from the original readonly tuple?

Alternatively is there another way to ensure strictMappedTuple has the same type as expandedMappedTuple?

Unlike with https://stackoverflow.com/a/66091730/2257198 the mapping is defined in MappedType and can be baked into the mapTuple function, (no higher-order types needed) and I am happy to use type assertions when the facts are so certain - I don't expect it to be inferred.

However, I've tried a lot of ways to define MappedTupleType and so far failed.

This is a deliberately toy example representing a real world problem from a list of constants I want to transform, preserving type and length.

type MappedType<T extends string> = `£${T}`;

function prefixCurrencyString<T extends string>(value: T): MappedType<T> {
  return `£${value}`;
}

const letters = ["a", "b", "c"] as const;

// Type of this const is correctly 
// readonly ["£a", "£b", "£c"]
// with length and ordering
const expandedMappedTuple = [
  prefixCurrencyString(letters[0]),
  prefixCurrencyString(letters[1]),
  prefixCurrencyString(letters[2]),
] as const;

// Type of this const is sadly
// ("£a" | "£b" | "£c")[]
// without length or ordering
const looseMappedTuple = letters.map(prefixCurrencyString);


/** TRY AND FIX IT */

// The aim of this type would be to 'cast' the array returned from .map
// as used in mapTuple() below

type MappedTupleType<Tup extends [...string[]]> = {
  [Index in keyof Tup]: MappedType<Tup[Index]>;
} & {length: Tup['length']}

function mapTuple<
  Tup extends Readonly<[...string[]]>,
  Fn extends <T extends string>(t: T) => MappedType<T>
>(list: Tup, fn: Fn) {
  const mappedList = list.map(fn);
  type Arr = typeof mappedList;
  return mappedList as MappedTupleType<Tup>;
}


// Type of this const should be 
// readonly ["£a", "£b", "£c"]
// just like expandedMappedTuple
const strictMappedTuple = mapTuple(letters, prefixCurrencyString);

Playground link

cefn
  • 2,895
  • 19
  • 28
  • Something like https://tsplay.dev/wEGrZm ? – Aleksey L. Dec 19 '21 at 15:39
  • 1
    That is exactly right. Now I need to find what's different compared to the way I approached it! I thought I'd fathomed somewhere else that extends [...something[]] preserved tuple nature but looks like that makes no difference. Looks like the `& string` resolves an important error I was facing. – cefn Dec 19 '21 at 15:46
  • In https://tsplay.dev/Ndd6kN I reintroduced the narrower type on the function definition, as this potentially eliminates incorrect casts (where the result is actually not sure to be of MappedType). An (x:string) => string is potentially too broad and would let in some runtime errors. But other than that I can run with this! – cefn Dec 19 '21 at 15:50

0 Answers0