Home > Enterprise >  typescript Type 'T' is not assignable to type 'number'
typescript Type 'T' is not assignable to type 'number'

Time:09-22

I really tried to understand it myself, but I can't figure out what is the problem here. I've seen similar questions on stackoverflow, and I really want to avoid as any assertion this time.

TLDR:

here's a playground. the error is highlighted, what is wrong?

details

first consider class Vector and class Dir which extends Vector.

usage

the usage required to be as follow:

// the following initialization overloads are allowed:
let v1 = new Vector(1,2) // 2 args
let v2 = new Vector({x:10,y:20}) // object with x and y
let v3 = new Vector(v1) // clone

let v1a = v1.add(10) // will add 10 both to x and y
let v1b = v1.add(v2) // will add vector v2 to v1 and return a new vector

//dirs initialization are the same
let d1 = new Dir(v1) // d1 is instance of Dir
let d2 = d1.add(10) // **important** d1 is still a dir

(possible)implementation

it is written that 'T' is not assignable to type number, and it doesn't. I don't want to assign T as a number, but what I trying to do is to infer T from the constructor. any operation (like 'add') should return a new instance of the same class that the operation was made on.

export const operatorFunc = <T extends Vector>(p: T, p2: T | number, operator): T => {
    let _p2;
    if (typeof p2 === 'number') _p2 = { x: p2, y: p2 };
    else _p2 = p2;
    let v = new (p.constructor as any)(operator(p.x, _p2.x), operator(p.y, _p2.y));
    return v;
};

class Vector<T extends Vector=any>{
    x: number;
    y: number;

    constructor(x: number | T | { x: number; y: number }, y?: number) {
     //  not relevant
    }

    add(p: T | number) {
        return operatorFunc(this, p, (x, y) => x   y);
        //                        ^
        // TS2345: Argument of type 'number | T' is not assignable to parameter of type 'number | this'.
        //  Type 'T' is not assignable to type 'number | this'.
        //      Type 'T' is not assignable to type 'number'.
    }
}

class Dir extends Vector{
    // extra methods... (does not really relevant)
}

playground code

the playground seems to not work for me, so here's the code:

export const operatorFunc = <T extends Vector>(p: T, p2: T | number, operator): T => {
    let _p2;
    if (typeof p2 === 'number') _p2 = { x: p2, y: p2 };
    else _p2 = p2;
    let v = new (p.constructor as any)(operator(p.x, _p2.x), operator(p.y, _p2.y));
    return v;
};

class Vector<T extends Vector=any>{
    x: number;
    y: number;

    constructor(x: number | T | { x: number; y: number }, y?: number) {
     //  not relevant
    }

    add(p: T | number) {
        return operatorFunc(this, p, (x, y) => x   y);
        //                        ^
        // TS2345: Argument of type 'number | T' is not assignable to parameter of type 'number | this'.
        //  Type 'T' is not assignable to type 'number | this'.
        //      Type 'T' is not assignable to type 'number'.
    }
}

class Dir extends Vector{
    // extra methods... (does not really relevant)
}

// the following initialization overloads are allowed:
let v1 = new Vector(1,2) // 2 args
let v2 = new Vector({x:10,y:20}) // object with x and y
let v3 = new Vector(v1) // clone

let v1a = v1.add(10) // will add 10 both to x and y
let v1b = v1.add(v2) // will add vector v2 to v1 and return a new vector

//dirs initialization are the same
let d1 = new Dir(v1) // d1 is instance of Dir
let d2 = d1.add(10) // d1 is still dir

CodePudding user response:

I may not have fully understood what it is you're trying to achieve, but the following code seems to work:

type VectorLike = number | Vector | { x: number, y: number };

class Vector {
    public readonly x: number;
    public readonly y: number;

    constructor(x: VectorLike, y?: number) {
        [this.x, this.y] = Vector.parse(x, y);
    }

    public add(value: VectorLike): this {
        return Vector.operatorFunc(this, value, (x, y) => x   y);
    }

    private static operatorFunc<T extends Vector>(first: T, second: VectorLike, operator: (first: number, second: number) => number): T {
        const [secondX, secondY] = Vector.parse(second);
        const calcX = operator(first.x, secondX);
        const calcY = operator(first.y, secondY);
        return new (first.constructor as any)(calcX, calcY);
    }

    private static parse(value: VectorLike, y?: number): [first: number, second: number] {
        if (typeof value === "number") {
            return [value, y || value];
        } else if (value instanceof Vector) {
            const vector = value as Vector;
            return [vector.x, vector.y];
        } else {
            const obj = value as { x: number, y: number }
            return [obj.x, obj.y];
        }
    }
}

class Dir extends Vector {
}

let v1 = new Vector(1,2);
let v2 = new Vector({x:10,y:20});
let v3 = new Vector(v1);

let v1a = v1.add(10);
let v1b = v1.add(v2);

let d1 = new Dir(v1);
let d2 = d1.add(10);

console.log(v1);
console.log(v2);
console.log(v3);
console.log(v1a);
console.log(v1b);
console.log(d1);
console.log(d2);
  • Related