2

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 */
}
3
  • 2
    You should not mutate the object at all, if you want a parallelogram based on the triangle you create a new object based off the points in the triangle. Mutation is usually bad and can lead to side effects, especially when you mutate input parameters. Commented Jul 7, 2019 at 22:13
  • 1
    I know that it is generally bad practice. It was a contrived example, and this question is more for my edification than for practical use. The Object.freeze case is legit, though. Commented Jul 7, 2019 at 22:14
  • 1
    You can always just return it and specify the return type. Commented Jul 7, 2019 at 23:24

1 Answer 1

2

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
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.