From af8a0e4e4e278685392d0f9c1cb359b5ddd81d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 12 Nov 2022 20:52:55 +0100 Subject: [PATCH 1/3] Fixed an issue with `in` not being able to be used on narrowed down expression of a generic nullable type --- src/compiler/checker.ts | 5 ++++- .../inKeywordTypeguard(strict=false).errors.txt | 6 ++++++ .../inKeywordTypeguard(strict=false).js | 10 ++++++++++ .../inKeywordTypeguard(strict=false).symbols | 13 +++++++++++++ .../inKeywordTypeguard(strict=false).types | 17 +++++++++++++++++ .../inKeywordTypeguard(strict=true).errors.txt | 6 ++++++ .../inKeywordTypeguard(strict=true).js | 10 ++++++++++ .../inKeywordTypeguard(strict=true).symbols | 13 +++++++++++++ .../inKeywordTypeguard(strict=true).types | 17 +++++++++++++++++ tests/cases/compiler/inKeywordTypeguard.ts | 6 ++++++ 10 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3be17bfdaa6da..41d15b37cf0cb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34196,7 +34196,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that include {} (we know that the other types in such intersections are assignable to object // since we already checked for that). if (hasEmptyObjectIntersection(rightType)) { - error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); + const constraint = getBaseConstraintOfType(rightType); + if (!constraint || isEmptyAnonymousObjectType(constraint)) { + error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); + } } } // The result is always of the Boolean primitive type. diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt index 009acf38c2b18..71266afdbf570 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt @@ -420,4 +420,10 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; + + // Repro from #51501 + + function isHTMLTable(table: T): boolean { + return !!table && 'html' in table; + } \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).js b/tests/baselines/reference/inKeywordTypeguard(strict=false).js index d254218557fa3..f76cad9d53850 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).js @@ -341,6 +341,12 @@ function foo(value: A) { const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; + +// Repro from #51501 + +function isHTMLTable(table: T): boolean { + return !!table && 'html' in table; +} //// [inKeywordTypeguard.js] @@ -655,3 +661,7 @@ function foo(value) { } // Repro from #50954 const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; +// Repro from #51501 +function isHTMLTable(table) { + return !!table && 'html' in table; +} diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols index c7ad966debf4d..6c23d114db093 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols @@ -853,3 +853,16 @@ const checkIsTouchDevice = () => >window : Symbol(window, Decl(lib.dom.d.ts, --, --)) >navigator : Symbol(navigator, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) +// Repro from #51501 + +function isHTMLTable(table: T): boolean { +>isHTMLTable : Symbol(isHTMLTable, Decl(inKeywordTypeguard.ts, 341, 71)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) + + return !!table && 'html' in table; +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).types b/tests/baselines/reference/inKeywordTypeguard(strict=false).types index 710206712629b..bc634e1bb1609 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).types @@ -1056,3 +1056,20 @@ const checkIsTouchDevice = () => >window : Window & typeof globalThis >navigator : Navigator +// Repro from #51501 + +function isHTMLTable(table: T): boolean { +>isHTMLTable : (table: T) => boolean +>null : null +>table : T + + return !!table && 'html' in table; +>!!table && 'html' in table : boolean +>!!table : boolean +>!table : boolean +>table : T +>'html' in table : boolean +>'html' : "html" +>table : T +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt index 1f11114fbd02c..dbac75b351e95 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt @@ -440,4 +440,10 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNulla const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; + + // Repro from #51501 + + function isHTMLTable(table: T): boolean { + return !!table && 'html' in table; + } \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).js b/tests/baselines/reference/inKeywordTypeguard(strict=true).js index 9acc8ca7520e4..9f0c5e6143ff5 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).js @@ -341,6 +341,12 @@ function foo(value: A) { const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; + +// Repro from #51501 + +function isHTMLTable(table: T): boolean { + return !!table && 'html' in table; +} //// [inKeywordTypeguard.js] @@ -656,3 +662,7 @@ function foo(value) { } // Repro from #50954 const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; +// Repro from #51501 +function isHTMLTable(table) { + return !!table && 'html' in table; +} diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols index c7ad966debf4d..6c23d114db093 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols @@ -853,3 +853,16 @@ const checkIsTouchDevice = () => >window : Symbol(window, Decl(lib.dom.d.ts, --, --)) >navigator : Symbol(navigator, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --)) +// Repro from #51501 + +function isHTMLTable(table: T): boolean { +>isHTMLTable : Symbol(isHTMLTable, Decl(inKeywordTypeguard.ts, 341, 71)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +>T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) + + return !!table && 'html' in table; +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +>table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) +} + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).types b/tests/baselines/reference/inKeywordTypeguard(strict=true).types index 86da8b3e869cb..a50a7d109cc75 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).types @@ -1056,3 +1056,20 @@ const checkIsTouchDevice = () => >window : Window & typeof globalThis >navigator : Navigator +// Repro from #51501 + +function isHTMLTable(table: T): boolean { +>isHTMLTable : (table: T) => boolean +>null : null +>table : T + + return !!table && 'html' in table; +>!!table && 'html' in table : boolean +>!!table : boolean +>!table : boolean +>table : T +>'html' in table : boolean +>'html' : "html" +>table : NonNullable +} + diff --git a/tests/cases/compiler/inKeywordTypeguard.ts b/tests/cases/compiler/inKeywordTypeguard.ts index 47266e55bd901..be75842b9508e 100644 --- a/tests/cases/compiler/inKeywordTypeguard.ts +++ b/tests/cases/compiler/inKeywordTypeguard.ts @@ -343,3 +343,9 @@ function foo(value: A) { const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" in window.navigator; + +// Repro from #51501 + +function isHTMLTable(table: T): boolean { + return !!table && 'html' in table; +} From 42ac98e7c4e72280213ad10c74bd4f4f80d9c335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 16 Nov 2022 21:14:35 +0100 Subject: [PATCH 2/3] Add another test case from a new issue --- .../inKeywordTypeguard(strict=false).errors.txt | 8 +++++++- .../inKeywordTypeguard(strict=false).js | 12 +++++++++++- .../inKeywordTypeguard(strict=false).symbols | 15 ++++++++++++++- .../inKeywordTypeguard(strict=false).types | 16 +++++++++++++++- .../inKeywordTypeguard(strict=true).errors.txt | 8 +++++++- .../reference/inKeywordTypeguard(strict=true).js | 12 +++++++++++- .../inKeywordTypeguard(strict=true).symbols | 15 ++++++++++++++- .../inKeywordTypeguard(strict=true).types | 16 +++++++++++++++- tests/cases/compiler/inKeywordTypeguard.ts | 8 +++++++- 9 files changed, 101 insertions(+), 9 deletions(-) diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt index 71266afdbf570..7d8bd0bea9a4b 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).errors.txt @@ -424,6 +424,12 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2322: Type 'T' is no // Repro from #51501 function isHTMLTable(table: T): boolean { - return !!table && 'html' in table; + return !!table && 'html' in table; } + + // Repro from #51549 + + const f =

(a: P & {}) => { + "foo" in a; + }; \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).js b/tests/baselines/reference/inKeywordTypeguard(strict=false).js index f76cad9d53850..ae1ccbb43c5be 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).js @@ -345,8 +345,14 @@ const checkIsTouchDevice = () => // Repro from #51501 function isHTMLTable(table: T): boolean { - return !!table && 'html' in table; + return !!table && 'html' in table; } + +// Repro from #51549 + +const f =

(a: P & {}) => { + "foo" in a; +}; //// [inKeywordTypeguard.js] @@ -665,3 +671,7 @@ const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" function isHTMLTable(table) { return !!table && 'html' in table; } +// Repro from #51549 +const f = (a) => { + "foo" in a; +}; diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols index 6c23d114db093..9444bf3fc988c 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).symbols @@ -861,8 +861,21 @@ function isHTMLTable(table: T): boolean { >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) >T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) - return !!table && 'html' in table; + return !!table && 'html' in table; >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) } +// Repro from #51549 + +const f =

(a: P & {}) => { +>f : Symbol(f, Decl(inKeywordTypeguard.ts, 351, 5)) +>P : Symbol(P, Decl(inKeywordTypeguard.ts, 351, 11)) +>a : Symbol(a, Decl(inKeywordTypeguard.ts, 351, 29)) +>P : Symbol(P, Decl(inKeywordTypeguard.ts, 351, 11)) + + "foo" in a; +>a : Symbol(a, Decl(inKeywordTypeguard.ts, 351, 29)) + +}; + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=false).types b/tests/baselines/reference/inKeywordTypeguard(strict=false).types index bc634e1bb1609..152eba6bda250 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=false).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=false).types @@ -1063,7 +1063,7 @@ function isHTMLTable(table: T): boolean { >null : null >table : T - return !!table && 'html' in table; + return !!table && 'html' in table; >!!table && 'html' in table : boolean >!!table : boolean >!table : boolean @@ -1073,3 +1073,17 @@ function isHTMLTable(table: T): boolean { >table : T } +// Repro from #51549 + +const f =

(a: P & {}) => { +>f :

(a: P & {}) => void +>

(a: P & {}) => { "foo" in a;} :

(a: P & {}) => void +>a : P & {} + + "foo" in a; +>"foo" in a : boolean +>"foo" : "foo" +>a : P & {} + +}; + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt index dbac75b351e95..d47a0718aac8a 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).errors.txt @@ -444,6 +444,12 @@ tests/cases/compiler/inKeywordTypeguard.ts(186,21): error TS2638: Type 'NonNulla // Repro from #51501 function isHTMLTable(table: T): boolean { - return !!table && 'html' in table; + return !!table && 'html' in table; } + + // Repro from #51549 + + const f =

(a: P & {}) => { + "foo" in a; + }; \ No newline at end of file diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).js b/tests/baselines/reference/inKeywordTypeguard(strict=true).js index 9f0c5e6143ff5..73b2cf77cae44 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).js +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).js @@ -345,8 +345,14 @@ const checkIsTouchDevice = () => // Repro from #51501 function isHTMLTable(table: T): boolean { - return !!table && 'html' in table; + return !!table && 'html' in table; } + +// Repro from #51549 + +const f =

(a: P & {}) => { + "foo" in a; +}; //// [inKeywordTypeguard.js] @@ -666,3 +672,7 @@ const checkIsTouchDevice = () => "ontouchstart" in window || "msMaxTouchPoints" function isHTMLTable(table) { return !!table && 'html' in table; } +// Repro from #51549 +const f = (a) => { + "foo" in a; +}; diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols index 6c23d114db093..9444bf3fc988c 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).symbols @@ -861,8 +861,21 @@ function isHTMLTable(table: T): boolean { >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) >T : Symbol(T, Decl(inKeywordTypeguard.ts, 345, 21)) - return !!table && 'html' in table; + return !!table && 'html' in table; >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) >table : Symbol(table, Decl(inKeywordTypeguard.ts, 345, 46)) } +// Repro from #51549 + +const f =

(a: P & {}) => { +>f : Symbol(f, Decl(inKeywordTypeguard.ts, 351, 5)) +>P : Symbol(P, Decl(inKeywordTypeguard.ts, 351, 11)) +>a : Symbol(a, Decl(inKeywordTypeguard.ts, 351, 29)) +>P : Symbol(P, Decl(inKeywordTypeguard.ts, 351, 11)) + + "foo" in a; +>a : Symbol(a, Decl(inKeywordTypeguard.ts, 351, 29)) + +}; + diff --git a/tests/baselines/reference/inKeywordTypeguard(strict=true).types b/tests/baselines/reference/inKeywordTypeguard(strict=true).types index a50a7d109cc75..491c4858e543e 100644 --- a/tests/baselines/reference/inKeywordTypeguard(strict=true).types +++ b/tests/baselines/reference/inKeywordTypeguard(strict=true).types @@ -1063,7 +1063,7 @@ function isHTMLTable(table: T): boolean { >null : null >table : T - return !!table && 'html' in table; + return !!table && 'html' in table; >!!table && 'html' in table : boolean >!!table : boolean >!table : boolean @@ -1073,3 +1073,17 @@ function isHTMLTable(table: T): boolean { >table : NonNullable } +// Repro from #51549 + +const f =

(a: P & {}) => { +>f :

(a: P & {}) => void +>

(a: P & {}) => { "foo" in a;} :

(a: P & {}) => void +>a : P & {} + + "foo" in a; +>"foo" in a : boolean +>"foo" : "foo" +>a : P & {} + +}; + diff --git a/tests/cases/compiler/inKeywordTypeguard.ts b/tests/cases/compiler/inKeywordTypeguard.ts index be75842b9508e..0c74dcded90ba 100644 --- a/tests/cases/compiler/inKeywordTypeguard.ts +++ b/tests/cases/compiler/inKeywordTypeguard.ts @@ -347,5 +347,11 @@ const checkIsTouchDevice = () => // Repro from #51501 function isHTMLTable(table: T): boolean { - return !!table && 'html' in table; + return !!table && 'html' in table; } + +// Repro from #51549 + +const f =

(a: P & {}) => { + "foo" in a; +}; From 1e7f3a9d9f9966767c48cc53d858e0d9e0848c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 21 Nov 2022 14:24:59 +0100 Subject: [PATCH 3/3] Move the fix to `hasEmptyObjectIntersection` --- src/compiler/checker.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 41d15b37cf0cb..41164ff435e21 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34167,7 +34167,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function hasEmptyObjectIntersection(type: Type): boolean { - return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && some((t as IntersectionType).types, isEmptyAnonymousObjectType)); + return someType(type, t => t === unknownEmptyObjectType || !!(t.flags & TypeFlags.Intersection) && isEmptyAnonymousObjectType(getBaseConstraintOrType(t))); } function checkInExpression(left: Expression, right: Expression, leftType: Type, rightType: Type): Type { @@ -34196,10 +34196,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // that include {} (we know that the other types in such intersections are assignable to object // since we already checked for that). if (hasEmptyObjectIntersection(rightType)) { - const constraint = getBaseConstraintOfType(rightType); - if (!constraint || isEmptyAnonymousObjectType(constraint)) { - error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); - } + error(right, Diagnostics.Type_0_may_represent_a_primitive_value_which_is_not_permitted_as_the_right_operand_of_the_in_operator, typeToString(rightType)); } } // The result is always of the Boolean primitive type.