Skip to content

Don't widen ternary type in return statement. #31518

Description

@ccorcos

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:

Activity

  1. fatcerberus commented on May 21, 2019

    @fatcerberus

    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.

  2. RyanCavanaugh commented on May 22, 2019

    @RyanCavanaugh
    Member

    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

  3. fatcerberus commented on May 22, 2019

    @fatcerberus

    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.

  4. typescript-bot commented on May 25, 2019

    @typescript-bot
    Contributor

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

  5. ccorcos commented on May 28, 2019

    @ccorcos
    Author

    Ryan Cavanaugh (@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...

  6. fatcerberus commented on May 28, 2019

    @fatcerberus

    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.

  7. ccorcos commented on May 28, 2019

    @ccorcos
    Author

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

  8. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions