Reputation: 14318
Suppose I have a function changeType
that takes in a Foo
and converts it into a Bar
.
interface Foo {
num: number;
}
interface Bar {
str: string;
}
function changeType(foo: Foo): void {
(foo as any).str = foo.num.toString();
delete (foo as any).num;
}
let foobar: Foo | Bar = { num: 1 };
changeType(foobar);
Is there any way to tell the TypeScript compiler that running changeType
will necessarily change the passed in argument to a different type? I thought maybe there might be something like:
function changeType(foo: Foo): foo as Bar { /*...*/ }
and then the function would have to return foo
rather than not returning anything. Even better would be some strange syntax like function(...): void, foo => Bar { ... }
.
I know this is a very strange case, but it is part of handling Object.freeze
--the part that seems to go ignored when discussing how to implement it. Typically the conversation focuses on the returned value, and not the passed value itself, which is modified.
Another case revolves around inheritance. Suppose you have a Triangle
and a Parallelogram
that both inherit from Shape
.
type Point = [number, number];
interface Shape {
name: 'triangle' | 'square';
points: Point[];
}
interface Triangle extends Shape {
name: 'triangle';
points: [Point, Point, Point];
}
interface Parallelogram extends Shape {
name: 'parallelogram';
points: [Point, Point, Point, Point];
}
Now you want to mutate the Triangle
into a Parallelogram
, rather than creating a new Shape
object.
function makeParallelogram(triangle: Triangle): void;
function makeParallelogram(shape: Shape): void {
Object.assign(shape, {
name: 'parallelogram',
points: (shape as Triangle).points.push(calcFourthPoint(shape as Triangle);
});
}
function calcFourthPoint(triangle: Triangle): Point {
/* some geometry here */
}
Upvotes: 2
Views: 564
Reputation: 11848
Exactly what you want is not possible with TypeScript. This issue from GitHub is very close to what you want. TypeScript cannot deeply analyze control flow of your code, so it will have troubles inferring correct types from what function does.
But pretty same effect can be achieved if using return statement, overloading and type guards.
function isFoo(f: Foo | Bar): f is Foo {
return (f as Foo).num !== undefined;
}
function changeType(foo: Foo): Bar
function changeType(foo: Bar): Foo
function changeType(foo: Foo | Bar): Foo | Bar {
if (isFoo(foo)) {
// Convertion logic can be anything. But I suggest not to mutate original object but create new one and fill it with props.
let res: Bar = { str: foo.num.toString() }
return res;
}
else {
let res: Foo = { num: +foo.str }
return res;
}
}
And the usage
let foobar: Foo | Bar = { num: 1 }; // Type is Foo
let r: Bar = changeType(foobar); // Return is Bar
Upvotes: 2