Dealing with Unwrapped Variadic Tuple Types
Asked Answered
C

1

7

Consider the following code:

interface Wrap<Value> {
  pick(): Value
}

class WrapConstant<Value> implements Wrap<Value> {
  constructor(public readonly a: Value) { }
  pick(): Value { return this.a }
}

type Unwrap<Wrapped> = { [P in keyof Wrapped]: Wrapped[P] extends Wrap<infer Value> ? Value : never }

class WrapTuple<Tuple extends Wrap<unknown>[], Value = Unwrap<Tuple>> implements Wrap<Value> {
  readonly ts: Tuple
  constructor(...t: Tuple) { this.ts = t }

  pick(): Value { return this.ts.map(a => a.pick()) }                // fails to type check
}

type T1 = Unwrap<[WrapConstant<number>, WrapConstant<string>]>       // [number, string]

new WrapTuple(new WrapConstant(1), new WrapConstant("hello")).pick() // [1, "hello"]

Basically I'm unwrapping a tuple which I know to follow a certain shape (tuple of Wrap<Values>). The pick() function in WrapTuple is supposed to guarantee the return of the same shape of the unwrapped types (provided by Unwrap<Tuple>), although I get a type check error in that line. Questions:

  1. Is this because Unwrap<Tuple> is not guaranteed to have the same shape due to the conditional type inference?
  2. Is it possible to make it work without forcing a cast as unknown as Value?

Update: As commented by Linda, mapping a tuple does not result in a tuple. I tried merging my own declaration for map as suggested here:

interface Array<T> {
  map<U>(callbackfn: (value: T, index: number, array: T[]) => U, 
         thisArg?: any): { [K in keyof this]: U }
}

But this still requires the map to be asserted to Value:

pick(): Value { return this.ts.map(a => a.pick()) as Value }
Concoction answered 7/3, 2021 at 17:20 Comment(2)
.map is always going to fail the typecheck because it returns an array of variable length instead of a tuple.Venus
That's true, map will never return tuple, because it operates on mutable arrayInterval
I
0

UPDATED

Here is workaround:

interface Wrap<Value> {
    pick(): Value
}

class WrapConstant<Value> implements Wrap<Value> {
    constructor(public readonly a: Value) { }
    pick(): Value { return this.a }
}

type Unwrap<Wrapped> = { [P in keyof Wrapped]: Wrapped[P] extends Wrap<infer Value> ? Value : never }

class WrapTuple<Tuple extends ReadonlyArray<Wrap<unknown>>> implements Wrap<unknown> {
    readonly ts: Tuple
    constructor(...ts: [...Tuple]) {
        this.ts = ts
    }

    pick(): Unwrap<[...Tuple]> // <-- added overload
    pick() {
        return this.ts.map(a => a.pick())
    }
}


const foo = new WrapTuple(new WrapConstant(1), new WrapConstant("hello")).pick() // [number, string]

Playground

Looks like it works as expected with method overloading

Here you can find more workarounds with tuples. This is my blog

Interval answered 8/3, 2021 at 7:7 Comment(3)
Thanks for your workaround, but my problem is exactly for the case where I want to deal with tuples.Concoction
@HugoSerenoFerreira are you allowed to use as operator?Interval
@HugoSerenoFerreira I made an update. Hope it is still relevantInterval

© 2022 - 2024 — McMap. All rights reserved.