Skip to content

Type guard not working as expected in function overloading with union types #5826

Closed
@remojansen

Description

@remojansen
class Square {
    length : number; 
    constructor(length : number) {
        this.length = length;
    }
}

class Rectangle {
    length : number;
    width : number;
    constructor(length : number, width : number) {
        this.length = length;
        this.width = width;
    }
}

class Triangle {
    base : number
    height : number;
    constructor(base : number, height : number) {
        this.base = base;
        this.height = height;
    }
}

The following works as expected:

namespace function_overloading_with_any {

    function calculateArea(shape : Square) : number;    // overloaded signature
    function calculateArea(shape : Rectangle) : number; // overloaded signature
    function calculateArea(shape : Triangle) : number;  // overloaded signature
    function calculateArea(shape : any) : number        // implementation signature
    {
        if(shape instanceof Square) {
            return shape.length * shape.length;
        }
        else if(shape instanceof Rectangle) {
            return shape.length * shape.width;
        }
        else if(shape instanceof Triangle) {
            return (shape.base * shape.height) / 2;
        }
    }


    var square = new Square(5); 
    var rectangle = new Rectangle(3, 5); 
    var triangle = new Triangle(3, 5);

    calculateArea(square);  
    calculateArea(rectangle); 
    calculateArea(triangle); 
}

However, when I use an union type instead of anyin the implementation signature:

namespace chapter02.function_overloading.with_union {

    function calculateArea(shape : Square) : number;
    function calculateArea(shape : Rectangle) : number;
    function calculateArea(shape : Triangle) : number;
    function calculateArea(shape : (Square | Rectangle | Triangle)) : number {
        if(shape instanceof Square) {
            return shape.length * shape.length;
        }
        else if(shape instanceof Rectangle) {
            return shape.length * shape.width; // Error!
        }
        else if(shape instanceof Triangle) {
            return (shape.base * shape.height) / 2;
        }
    }

    var square = new Square(5); 
    var rectangle = new Rectangle(3, 5); 
    var triangle = new Triangle(3, 5);  

    calculateArea(square); 
    calculateArea(rectangle); 
    calculateArea(triangle);
}

I get a compilation error:

error TS2339: Property 'length' does not exist on type 'Triangle'.
error TS2339: Property 'width' does not exist on type 'Triangle'.

Looks like the type guard is not working as expected. I can use an if instead of if else to fix the error:

    function calculateArea(shape : Square) : number;
    function calculateArea(shape : Rectangle) : number;
    function calculateArea(shape : Triangle) : number;
    function calculateArea(shape : (Square | Rectangle | Triangle)) : number {
        if(shape instanceof Square) {
            return shape.length * shape.length;
        }
        if(shape instanceof Rectangle) {
            return shape.length * shape.width; // OK!
        }
        if(shape instanceof Triangle) {
            return (shape.base * shape.height) / 2;
        }
    }

I'm using TypeScript 1.8.0-dev.20151129 with gulp-typescript:

gulp.task('build', ['lint'], function() {

    var tsProject = ts.createProject('tsconfig.json', {
        typescript: require('typescript')
    });

    var compile = ts(tsProject);

    return gulp.src("src/**/**.ts")
                .pipe(compile)
                .js.pipe(gulp.dest("src/"));
});

And the following tsconfig.json compilation:

{
  "compilerOptions": {
    "target": "es5",
    "module": "umd",
    "moduleResolution": "node",
    "isolatedModules": false,
    "jsx": "react",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "declaration": false,
    "noImplicitAny": false,
    "removeComments": true,
    "noLib": false,
    "preserveConstEnums": true,
    "suppressImplicitAnyIndexErrors": true
  }
}

The source code that I used to discover this issue can be found here.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptBy DesignDeprecated - use "Working as Intended" or "Design Limitation" insteadFix AvailableA PR has been opened for this issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions