I have the following code:
const enum ShapeType {
Circle,
Rectangle
}
class Shape {
constructor(public shapeType: ShapeType) {}
}
class Circle extends Shape {
constructor(public x: number, public y: number, public r: number) {
super(ShapeType.Circle);
}
}
class Rectangle extends Shape {
constructor(public x: number, public y: number, public w: number, public h: number) {
super(ShapeType.Rectangle);
}
}
function handleRectangleRectangleCollision(r1: Rectangle, r2: Rectangle) {
return Helpers.doRectanglesCollide(r1.x, r1.y, r1.w, r1.h, r2.x, r2.y, r2.w, r2.h)
}
function handleRectangleCircleCollision(r: Rectangle, c: Circle) {
return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}
function handleCircleCircleCollision(c1: Circle, c2: Circle) {
return Helpers.circlesCollide(c1.x, c1.y, c1.r, c2.x, c2.y, c2.y);
}
function handleCircleRectangleCollision(c: Circle, r: Rectangle) {
return Helpers.circleRectangleCollision(c.x, c.y, c.r, r.x, r.y, r.w, r.h);
}
export let colliderMapping = {
[ShapeType.Rectangle]: {
[ShapeType.Rectangle]: handleRectangleRectangleCollision,
[ShapeType.Circle]: handleRectangleCircleCollision
},
[ShapeType.Circle]: {
[ShapeType.Circle]: handleCircleCircleCollision,
[ShapeType.Rectangle]: handleCircleRectangleCollision
}
}
function doShapesCollide(s1: Shape, s2: Shape) {
let colliderFn = colliderMapping[s1.shapeType][s2.shapeType];
return colliderFn(s1, s2);
}
And I am getting an error on the last last:
return colliderFn(s1, s2);
Argument of type 'Shape' is not assignable to parameter of type 'Rectangle & Circle'.
Type 'Shape' is missing the following properties from type 'Rectangle': x, y, w, h
I understand why I'm getting the error (I think), but I don't know how to solve it. I'm basically trying to implement a clean way of double-dispatch by having a mapping variable, such that every combination of shapes will return a valid function that I can call to see if they collide.
Is there any way to do this? If so, how?
Please take a look on my article
COnsider this super simple example:
Why
checkexpectsneverand not a union of all possible types?Because function arguments are in contravariant position, they are merged into never because
string & number & symbolis never;Try to change type of
checkargument to some object:It is clear that you have intersection of all possible arguments type.
There are several workarounds.
You can add conditional statements:
Above approach still causes a compilation error because
s1is aShapeandcolliderFnexpectsCircle.Circleis a subtype ofShapeand is more specific - hence it does not work.In order to make it work, you should add another one condition:
It works but it is ugly. Is not it?
You can also create several typeguards which makes code cleaner but adds more business logic.
Or you can convert union of functions to intersections, in other words you can produce function overloading.
Playground
Above change does not require you to change your business logic.