Typescript: Don't widen ternary type in return statement.

Created on 21 May 2019  Â·  7Comments  Â·  Source: microsoft/TypeScript

TypeScript Version: 3.4.1

Search Terms:
ternary statement vs if statement
ternary statement type widening

Code

function main(): number {
    const x: any = {}
    return x.y ? x.y : undefined // no error
}

function main2(): number {
    const x: any = {}
    if (x.y) {
        return x.y
    } else {
        return undefined // error
    }
}

Expected behavior:
It appears that returning a value from a ternary statement has a different behavior than and if-statement. The ternary statement type is widened and resolved before returning. This causes some unsafe type behavior. I would expect the two programs above to work the same and throw and error because the type definition of the function returns a number and yet we are definitely returning undefined.

Actual behavior:

The entire ternary statement returns as an "any" type.

Playground Link: playground link

Related Issues:

Working as Intended

Most helpful comment

It's not even about return; the same problem occurs here:

let n: number;
const x: any = {}
n = x.y ? x.y : undefined // no error

const x: any = {}
if (x.y) {
    n = x.y
} else {
    n = undefined // error
}

The consistent proposal would be that when a source expression of an assignment (or equivalent) is a ternary operator, we should check that both of its true/false operands are assignable to the target. But this just opens up more "consistency" problems:

// m: any
let m = x.y ? x.y : undefined;
n = m; // This is still OK, good grief!

There's not any principled place along the rabbit hole to stop going down further, plus "fixing" this would a substantial breaking change

All 7 comments

I’m thinking the inconsistency here is that, in the first case, the entire ternary expression is inferred as any | undefined which collapses to just any; the compiler therefore sees a single return any and all is right with the world (even though it’s not). In the second case there are two return sites: one is return any which again is fine, and one is return undefined which is not.

It's not even about return; the same problem occurs here:

let n: number;
const x: any = {}
n = x.y ? x.y : undefined // no error

const x: any = {}
if (x.y) {
    n = x.y
} else {
    n = undefined // error
}

The consistent proposal would be that when a source expression of an assignment (or equivalent) is a ternary operator, we should check that both of its true/false operands are assignable to the target. But this just opens up more "consistency" problems:

// m: any
let m = x.y ? x.y : undefined;
n = m; // This is still OK, good grief!

There's not any principled place along the rabbit hole to stop going down further, plus "fixing" this would a substantial breaking change

Yeah, I knew the return wasn’t the source of the issue; it was more a way to highlight that the compiler sees the ternary as a single expression of type TrueCase | FalseCase rather than separate expressions as with the if-else.

This union behavior is generally sound; it just so happens that unioning any with any other type loses all information about the other type. any is kind of a “viral” type in that regard.

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@RyanCavanaugh, the any type is always going to create unsafe types but we can be pragmatic here. The ternary statement is functionally equivalent to the if-else statement with a hoisted variable. Why should one be more type-safe than the other? What if all ternary statements were treated by the typescript compiler the same as an if-else statement? We'd at least catch some obvious errors...

I think the challenge is that a ternary isn't really a statement but an expression. An expression must have a single type; in this case that type happens to be any | undefined which is unsound simply by the nature of any. Changing this, I suspect, would be very difficult: what happens when you have a ternary in the middle of a statement, e.g. as an argument to a function? What happens when there are several ternaries in the same statement, as I've been known to stack them to heavens:

In order to type check the above the way you suggest, the compiler would basically have to duplicate the statement and individually type check it for every combination of true/false for every single ternary in the statement. You'd have a combinatorial explosion.

The current behavior is, I suspect, the best we're going to get.

I see. That is a bummer, but thanks for looking into it!

Was this page helpful?
0 / 5 - 0 ratings