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);