You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Apply uncalled function checks to boolean expressions (ts2774) #46051
Similar to #36048 (implemented in #36402), it would be great if uncalled function checks were applied to expressions like they are for if statements and ternaries.
exportdefaultclassRange{lowerBound: numberupperBound: numberconstructor(lowerBound: number,upperBound: number){this.lowerBound=lowerBoundthis.upperBound=upperBound}isEmpty(): boolean{returnthis.lowerBound==this.upperBound}overlaps(other: Range): boolean{constisDisjoint=other.upperBound<=this.lowerBound||this.upperBound<=other.lowerBound||this.isEmpty||other.isEmpty// <-- Always true without an errorreturn!isDisjoint}}
changed the title [-]Apply uncalled function checks to boolean expressions [/-][+]Apply uncalled function checks to boolean expressions (ts2774)[/+]on Sep 24, 2021
So, we can’t quite do exactly what this issue proposes, because the type of the logical expression ends up including the function itself, which only looks like a mistake if it never gets called. If the variable gets carried forward or passed around, it can be difficult or impossible to decide if it did ever get called. The two things that makes it possible to do these checks for if statements and ternaries are
Typically, the only purpose of the expression being tested for truthiness is to test it for truthiness, so we know if it has constant truthiness, something is suspicious. Contrast that to assigning a logical expression to a variable—that variable could be used for many purposes down the line, including but not limited to testing for truthiness.
In an if statement or ternary, we can increase our confidence that the suspicious code is really suspicious by checking the true branch of the conditional to see if the function is ever called. If it is called, like foo ? foo() : undefined, the user is clearly performing defensive checks, which might appear overly defensive to our type system, but sometimes types are wrong and something can be undefined when we think it can’t. Contrast this to assigning a logical expression to a variable, where we’d have to analyze everything in the same scope as the variable to find its uses. The if/ternary usually presents us a small (and definitely file-local) boundary where we have to check. If a variable for a logical expression appears in the global scope, it could be used anywhere in the whole program.
I think the place where you could maybe argue that we go wrong is not at const isDisjoint = ..., but at return !isDisjoint. The type of isDisjoint is true | (() => boolean), both sides of which are truthy, and so we could make the determination that this function always returns false, and perhaps say that’s suspicious. But on the other hand, it makes total sense for some functions/methods to return constant values, so that analysis alone is insufficient.
The example is super compelling, but I’m having trouble coming up with anything we could do that wouldn’t severely break something else.
Activity
[-]Apply uncalled function checks to boolean expressions [/-][+]Apply uncalled function checks to boolean expressions (ts2774)[/+]andrewbranch commentedon Oct 5, 2021
So, we can’t quite do exactly what this issue proposes, because the type of the logical expression ends up including the function itself, which only looks like a mistake if it never gets called. If the variable gets carried forward or passed around, it can be difficult or impossible to decide if it did ever get called. The two things that makes it possible to do these checks for
ifstatements and ternaries arefoo ? foo() : undefined, the user is clearly performing defensive checks, which might appear overly defensive to our type system, but sometimes types are wrong and something can beundefinedwhen we think it can’t. Contrast this to assigning a logical expression to a variable, where we’d have to analyze everything in the same scope as the variable to find its uses. The if/ternary usually presents us a small (and definitely file-local) boundary where we have to check. If a variable for a logical expression appears in the global scope, it could be used anywhere in the whole program.I think the place where you could maybe argue that we go wrong is not at
const isDisjoint = ..., but atreturn !isDisjoint. The type ofisDisjointistrue | (() => boolean), both sides of which are truthy, and so we could make the determination that this function always returns false, and perhaps say that’s suspicious. But on the other hand, it makes total sense for some functions/methods to return constant values, so that analysis alone is insufficient.The example is super compelling, but I’m having trouble coming up with anything we could do that wouldn’t severely break something else.