Zoltán Tamási
Zoltán Tamási

Reputation: 12809

Why isn't this type guard for string working?

I have a TypeScript method which accepts a parameter. The parameter can be either of type string or type Object. When I use a typeof check for type string in the method body, I still get error stating that indexOf and substr are not found on type string | Object.

As far as I understand the if block body should be type-guarded by the condition. What am I doing wrong?

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {

  var sourceParamObject: Object;
  if (typeof sourceParameters === "string") {
    if (sourceParameters.indexOf("?") >= 0)
      sourceParameters = sourceParameters.substr(sourceParameters.indexOf("?") + 1);
    sourceParamObject = $.deparam(sourceParameters, true);
  } else {
    sourceParamObject = sourceParameters;
  }

  ...
}

I'm using Visual Studio 2015 with latest TypeScript installed (1.8.x).

UPDATE

It turned out that the reassignment of the sourceParameters variable causes the type-guard to fail. I've posted a related potential bug report about this behavior here. In the meantime, several easy workarounds are available, I chose to inline the check and substring call with a ? conditional expression.

Upvotes: 2

Views: 1638

Answers (1)

Nitzan Tomer
Nitzan Tomer

Reputation: 164347

You need to cast it to the appropriate type:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (typeof sourceParameters === "string") {
        if ((<string> sourceParameters).indexOf("?") >= 0) {
            sourceParameters = (<string> sourceParameters).substr((<string> sourceParameters).indexOf("?") + 1);
        }
        sourceParamObject = $.deparam((<string> sourceParameters), true);
    } else {
        sourceParamObject = sourceParameters;
    }

    ...
}

Edit

If you want to avoid that casting, you can create your own type guard (which you haven't in your code):

function isString(x: any): x is string {
    return typeof x === "string";
}

and then:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (isString(sourceParameters)) {
        if (sourceParameters.indexOf("?") >= 0) {
            sourceParameters = sourceParameters.substr(sourceParameters.indexOf("?") + 1);
        }
        sourceParamObject = $.deparam(sourceParameters, true);
    } else {
        sourceParamObject = sourceParameters;
    }

    ...
}

more info in TypeScript Handbook | typeof type guards


2nd edit

Ok, after playing around with it for a while I understand what is the compiler problem with your code.
This variation of your code compiles with no errors:

function mergeUrlParameters(sourceParameters: string | Object, targetUrl: string, overwrite: boolean = false): string {
    var sourceParamObject: Object;

    if (typeof sourceParameters === "string") {
        sourceParamObject = $.deparam(sourceParameters.indexOf("?") >= 0 ? 
                sourceParameters.substr(sourceParameters.indexOf("?") + 1)
                : sourceParameters
            , true);
    } else {
        sourceParamObject = sourceParameters;
    }
}

The main difference being that in your code you reassign a value to sourceParameters and that probably confuses the compiler.

To illustrate the issue, consider this simple example:

function x(param: number | string): number {
    var num: number;

    if (typeof param === "string") {
        num = parseInt(param);
    } else {
        num = param;
    }

    return num;
}

function y(param: number | string): number {
    if (typeof param === "string") {
        param = parseInt(param);
    } 

    return param;
}

function x compiles without errors, but y has the following error:

Argument of type 'number | string' is not assignable to parameter of type 'string'. Type 'number' is not assignable to type 'string'

(check it out in the playground)

Upvotes: 1

Related Questions