FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Fixed an issue in boolean comparison narrowing when the reference is an optional chain by Andarist · Pull Request #56504 · microsoft/TypeScript · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .symbols  (1) .ts  (1) .tsx  (1) .txt  (1) .types  (1) All 5 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -27929,10 +27929,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (isMatchingConstructorReference(right)) {
return narrowTypeByConstructor(type, operator, left, assumeTrue);
}
if (isBooleanLiteral(right)) {
if (isBooleanLiteral(right) && !isAccessExpression(left)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

It turns out that the original type can be reassigned based on optionalChainContainsReference. That messed up the else branch because narrowTypeByBooleanComparison was operating on the wrong type.

I've also tried keeping originalType around and pass that to narrowTypeByBooleanComparison. That fixed the reported issue~ related to the wrong type in the else branch but it broke the same type within the if block. This happened because originally the intention was for that reassigned type to be returned through the return at the bottom of this function but I changed it to return originalType through narrowTypeByBooleanComparison.

So all in all, this seems like the simplest fix and reflects the intention the best. The motivation behind the original change was to improve non-access expression narrowing. Access expressions are already handled by discrimination-based narrowing etc.

Even though it worked previously (as far as I know) for non-optional chain access expressions, this helps them avoid some redundant work too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

This seems fine as a quick fix for now, though I suspect that we could try and do better later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Then again, not sure what "better" even means; an expression like (options?.a === false || options.b is not the same as (!options?.a || options.b) so realistically I don't think there's any extra info gained here.

return narrowTypeByBooleanComparison(type, left, right, operator, assumeTrue);
}
if (isBooleanLiteral(left)) {
if (isBooleanLiteral(left) && !isAccessExpression(right)) {
return narrowTypeByBooleanComparison(type, right, left, operator, assumeTrue);
}
break;
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
controlFlowOptionalChain3.tsx(30,8): error TS18048: 'foo' is possibly 'undefined'.
controlFlowOptionalChain3.tsx(36,31): error TS18048: 'options' is possibly 'undefined'.


==== controlFlowOptionalChain3.tsx (2 errors) ====
/// <reference path="/.lib/react16.d.ts" />

// https://github.com/microsoft/TypeScript/issues/56482

import React from "react";

interface Foo {
bar: boolean;
}

function test1(foo: Foo | undefined) {
if (foo?.bar === false) {
foo;
}
foo;
}

function test2(foo: Foo | undefined) {
if (foo?.bar === false) {
foo;
} else {
foo;
}
}

function Test3({ foo }: { foo: Foo | undefined }) {
return (
<div>
{foo?.bar === false && "foo"}
{foo.bar ? "true" : "false"}
~~~
!!! error TS18048: 'foo' is possibly 'undefined'.
</div>
);
}

function test4(options?: { a?: boolean; b?: boolean }) {
if (options?.a === false || options.b) {
~~~~~~~
!!! error TS18048: 'options' is possibly 'undefined'.
options;
}
}

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain3.tsx] ////

=== controlFlowOptionalChain3.tsx ===
/// <reference path="react16.d.ts" />

// https://github.com/microsoft/TypeScript/issues/56482

import React from "react";
>React : Symbol(React, Decl(controlFlowOptionalChain3.tsx, 4, 6))

interface Foo {
>Foo : Symbol(Foo, Decl(controlFlowOptionalChain3.tsx, 4, 26))

bar: boolean;
>bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))
}

function test1(foo: Foo | undefined) {
>test1 : Symbol(test1, Decl(controlFlowOptionalChain3.tsx, 8, 1))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 10, 15))
>Foo : Symbol(Foo, Decl(controlFlowOptionalChain3.tsx, 4, 26))

if (foo?.bar === false) {
>foo?.bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 10, 15))
>bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))

foo;
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 10, 15))
}
foo;
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 10, 15))
}

function test2(foo: Foo | undefined) {
>test2 : Symbol(test2, Decl(controlFlowOptionalChain3.tsx, 15, 1))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 17, 15))
>Foo : Symbol(Foo, Decl(controlFlowOptionalChain3.tsx, 4, 26))

if (foo?.bar === false) {
>foo?.bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 17, 15))
>bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))

foo;
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 17, 15))

} else {
foo;
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 17, 15))
}
}

function Test3({ foo }: { foo: Foo | undefined }) {
>Test3 : Symbol(Test3, Decl(controlFlowOptionalChain3.tsx, 23, 1))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 25, 16))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 25, 25))
>Foo : Symbol(Foo, Decl(controlFlowOptionalChain3.tsx, 4, 26))

return (
<div>
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))

{foo?.bar === false && "foo"}
>foo?.bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 25, 16))
>bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))

{foo.bar ? "true" : "false"}
>foo.bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))
>foo : Symbol(foo, Decl(controlFlowOptionalChain3.tsx, 25, 16))
>bar : Symbol(Foo.bar, Decl(controlFlowOptionalChain3.tsx, 6, 15))

</div>
>div : Symbol(JSX.IntrinsicElements.div, Decl(react16.d.ts, 2546, 114))

);
}

function test4(options?: { a?: boolean; b?: boolean }) {
>test4 : Symbol(test4, Decl(controlFlowOptionalChain3.tsx, 32, 1))
>options : Symbol(options, Decl(controlFlowOptionalChain3.tsx, 34, 15))
>a : Symbol(a, Decl(controlFlowOptionalChain3.tsx, 34, 26))
>b : Symbol(b, Decl(controlFlowOptionalChain3.tsx, 34, 39))

if (options?.a === false || options.b) {
>options?.a : Symbol(a, Decl(controlFlowOptionalChain3.tsx, 34, 26))
>options : Symbol(options, Decl(controlFlowOptionalChain3.tsx, 34, 15))
>a : Symbol(a, Decl(controlFlowOptionalChain3.tsx, 34, 26))
>options.b : Symbol(b, Decl(controlFlowOptionalChain3.tsx, 34, 39))
>options : Symbol(options, Decl(controlFlowOptionalChain3.tsx, 34, 15))
>b : Symbol(b, Decl(controlFlowOptionalChain3.tsx, 34, 39))

options;
>options : Symbol(options, Decl(controlFlowOptionalChain3.tsx, 34, 15))
}
}

110 changes: 110 additions & 0 deletions tests/baselines/reference/controlFlowOptionalChain3.types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//// [tests/cases/conformance/controlFlow/controlFlowOptionalChain3.tsx] ////

=== controlFlowOptionalChain3.tsx ===
/// <reference path="react16.d.ts" />

// https://github.com/microsoft/TypeScript/issues/56482

import React from "react";
>React : typeof React

interface Foo {
bar: boolean;
>bar : boolean
}

function test1(foo: Foo | undefined) {
>test1 : (foo: Foo | undefined) => void
>foo : Foo | undefined

if (foo?.bar === false) {
>foo?.bar === false : boolean
>foo?.bar : boolean | undefined
>foo : Foo | undefined
>bar : boolean | undefined
>false : false

foo;
>foo : Foo
}
foo;
>foo : Foo | undefined
}

function test2(foo: Foo | undefined) {
>test2 : (foo: Foo | undefined) => void
>foo : Foo | undefined

if (foo?.bar === false) {
>foo?.bar === false : boolean
>foo?.bar : boolean | undefined
>foo : Foo | undefined
>bar : boolean | undefined
>false : false

foo;
>foo : Foo

} else {
foo;
>foo : Foo | undefined
}
}

function Test3({ foo }: { foo: Foo | undefined }) {
>Test3 : ({ foo }: { foo: Foo | undefined; }) => JSX.Element
>foo : Foo | undefined
>foo : Foo | undefined

return (
>( <div> {foo?.bar === false && "foo"} {foo.bar ? "true" : "false"} </div> ) : JSX.Element

<div>
><div> {foo?.bar === false && "foo"} {foo.bar ? "true" : "false"} </div> : JSX.Element
>div : any

{foo?.bar === false && "foo"}
>foo?.bar === false && "foo" : false | "foo"
>foo?.bar === false : boolean
>foo?.bar : boolean | undefined
>foo : Foo | undefined
>bar : boolean | undefined
>false : false
>"foo" : "foo"

{foo.bar ? "true" : "false"}
>foo.bar ? "true" : "false" : "false" | "true"
>foo.bar : boolean
>foo : Foo | undefined
>bar : boolean
>"true" : "true"
>"false" : "false"

</div>
>div : any

);
}

function test4(options?: { a?: boolean; b?: boolean }) {
>test4 : (options?: { a?: boolean; b?: boolean;}) => void
>options : { a?: boolean | undefined; b?: boolean | undefined; } | undefined
>a : boolean | undefined
>b : boolean | undefined

if (options?.a === false || options.b) {
>options?.a === false || options.b : boolean | undefined
>options?.a === false : boolean
>options?.a : boolean | undefined
>options : { a?: boolean | undefined; b?: boolean | undefined; } | undefined
>a : boolean | undefined
>false : false
>options.b : boolean | undefined
>options : { a?: boolean | undefined; b?: boolean | undefined; } | undefined
>b : boolean | undefined

options;
>options : { a?: boolean | undefined; b?: boolean | undefined; } | undefined
}
}

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// @strict: true
// @noEmit: true
// @esModuleInterop: true
// @jsx: react

/// <reference path="/.lib/react16.d.ts" />

// https://github.com/microsoft/TypeScript/issues/56482

import React from "react";

interface Foo {
bar: boolean;
}

function test1(foo: Foo | undefined) {
if (foo?.bar === false) {
foo;
}
foo;
}

function test2(foo: Foo | undefined) {
if (foo?.bar === false) {
foo;
} else {
foo;
}
}

function Test3({ foo }: { foo: Foo | undefined }) {
return (
<div>
{foo?.bar === false && "foo"}
{foo.bar ? "true" : "false"}
</div>
);
}

function test4(options?: { a?: boolean; b?: boolean }) {
if (options?.a === false || options.b) {
options;
}
}

Back | FazBrowse Home | New Git URL