Abdo Salah
Abdo Salah

Reputation: 115

TypeScript : Generic type with primitive types constrain

I have the following generic classes in TypeScript

type UserId = number
type Primitive = string | number | boolean
class ColumnValue<T, S extends Primitive> {
    constructor(public columnName: String, public value: S) { }
}
abstract class Column<T> {
    constructor(public columnName: String) { }
    public set<S extends Primitive>(value: T): ColumnValue<T, S> {
        return new ColumnValue(this.columnName, this.getValue(value))
    }
    public abstract getValue<S extends Primitive>(value: T): S
}
let id = new class extends Column<UserId> {
    constructor() { super("id") }
    public getValue(value: UserId): number {
        return value
    }
}()

But I don't know why get this error:

Class '(Anonymous class)' incorrectly extends base class 'Column<number>'.
  Types of property 'getValue' are incompatible.
    Type '(value: number) => number' is not assignable to type '<S extends Primitive>(value: number) => S'.
      Type 'number' is not assignable to type 'S'

Upvotes: 5

Views: 6706

Answers (2)

cvsguimaraes
cvsguimaraes

Reputation: 13240

On Column getter and setter S isn't necessarily the same type so you should move the type param to its parent class: Column<T, S extends Primitive>.

type UserId = number
type Primitive = string | number | boolean
class ColumnValue<T, S extends Primitive> {
    constructor(public columnName: String, public value: S) { }
}
abstract class Column<T, S extends Primitive> {
    constructor(public columnName: String) { }
    public set(value: T): ColumnValue<T, S> {
        return new ColumnValue(this.columnName, this.getValue(value))
    }
    public abstract getValue(value: T): S
}
let id = new class extends Column<UserId, number> {
    constructor() { super("id") }
    public getValue(value: UserId): number {
        return value
    }
}()

The version above has no errors at least.

I understand that you probably want to infer the S from whatever type you use with you setter but Column must have a well defined type upon instantiation, so that means that you either explicit when calling the constructor (that is new Column<UserId, number>(...)) or add a S param in the constructor so S can be inferred from it (like in new Column<UserId>('id', 123))

Upvotes: 4

Rodris
Rodris

Reputation: 2848

Your getValue uses a generic S, so, the inherited implementation must use S as well.

let id = new class extends Column<UserId> {
    constructor() { super("id") }
    public getValue<S extends Primative>(value: UserId): S {
        return <S>value
    }
}()

If you take the S to the class, your function can be narrowed to number.

abstract class Column<T, S extends Primative> {
    constructor(public columnName: String) { }
    public set(value: T): ColumnValue<T, S> {
        return new ColumnValue(this.columnName, this.getValue(value))
    }
    public abstract getValue(value: T): S
}

let id = new class extends Column<UserId, UserId> {
    constructor() { super("id") }
    public getValue(value: UserId): number {
        return value
    }
}()

Upvotes: 1

Related Questions