Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ccorcos opened this issue May 21, 2019 · 7 comments
Closed

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

ccorcos opened this issue May 21, 2019 · 7 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@ccorcos
Copy link

ccorcos commented May 21, 2019

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:

@fatcerberus
Copy link

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.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 22, 2019
@RyanCavanaugh
Copy link
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

@fatcerberus
Copy link

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.

@typescript-bot
Copy link
Collaborator

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

@ccorcos
Copy link
Author

ccorcos commented May 28, 2019

@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...

@fatcerberus
Copy link

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.

@ccorcos
Copy link
Author

ccorcos commented May 28, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants