'Class implements type with generic method but does not conform to the type
I wish I had a more minimal example of the issue here. This is pretty short, but I can't seem to reproduce this issue without using type logic over tuples.
I'm defining types for a key-value store where keys can be tuples.
type Key = (string | number)[]
type KeyValuePairs = [Key, any]
Here's an example schema.
type Schema =
| [["a", number], number]
| [["b", string], string]
I want to be able to define the database interface separate from the class so that there can be different backends / implementation.
type KeyValueStoreApi<S extends KeyValuePairs> = {
get: <T extends S[0]>(key: T) => Extract<S, {0: T}>[1]
}
That return type appears to be what is causing me trouble as we'll see later. Anyways, I have an implementation of this type with a class:
class KeyValueStore<S extends KeyValuePairs> implements KeyValueStoreApi<S> {
get<T extends S[0]>(key: T): Extract<S, {0: T}>[1] {
return {} as any
}
}
It's the EXACT same type interface...
Thus functions in my program references the type rather than the class so that the implementations are interchangeable.
function f(db: KeyValueStoreApi<Schema>) {}
But typescript is unhappy.
const db = new KeyValueStore<Schema>()
f(db) // TypeError
Here's the error:
Argument of type 'KeyValueStore<Schema>' is not assignable to parameter of type 'KeyValueStoreApi<Schema>'. The types returned by 'get(...)' are incompatible between these types.
Type 'Extract<[["a", number], number], { 0: T; }>[1] | Extract<[["b", string], string], { 0: T; }>[1]' is not assignable to type 'Extract<[["a", number], number], { 0: T; }>[1] & Extract<[["b", string], string], { 0: T; }>[1]'.
Type 'Extract<[["a", number], number], { 0: T; }>[1]' is not assignable to type 'Extract<[["a", number], number], { 0: T; }>[1] & Extract<[["b", string], string], { 0: T; }>[1]'.
Type 'number' is not assignable to type 'Extract<[["a", number], number], { 0: T; }>[1] & Extract<[["b", string], string], { 0: T; }>[1]'.
Type 'number' is not assignable to type 'Extract<[["b", string], string], { 0: T; }>[1]'.
Type 'number' is not assignable to type 'string'.
Type 'Extract<[["a", number], number], { 0: T; }>[1]' is not assignable to type 'Extract<[["b", string], string], { 0: T; }>[1]'.
Type 'Extract<[["a", number], number], { 0: T; }>' is not assignable to type 'Extract<[["b", string], string], { 0: T; }>'.
Type '{ 0: T; } & [["a", number], number]' is not assignable to type 'Extract<[["b", string], string], { 0: T; }>'.(2345)
In fact, the class doesn't conform at all even though it's implementing the type!
const store: KeyValueStoreApi<Schema> = new KeyValueStore<Schema>() // same error
Any ideas what's going wrong here and how to fix it?
Edit 1
I've been able to reproduce this issue using objects instead of tuples: playgound. But it hasn't been too insightful really.
The main thing I've noticed is that if I get rid of the [1] part: Extract<S, {0: T}>[1] → Extract<S, {0: T}>, then I don't have any type errors... Pretty annoying though, because I need that to properly specify the type...
Solution 1:[1]
I figured it out! Though I could use help coming up with the language to describe the problem... I stumbled upon this solution with guess-and-check. I found this article and tried something similar using
type DistributiveProp<T, K extends keyof T> = T extends unknown
? T[K]
: never;
Then I replaced Extract<S, {0: T}>[1] with DistributiveProp<Extract<S, {0: T}>, 1>. No idea why it worked honestly... Thoughts?
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Chet |
