-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Add type guard for in
keyword
#15256
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
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
159 changes: 159 additions & 0 deletions
159
tests/baselines/reference/inKeywordTypeguard.errors.txt
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,159 @@ | ||
tests/cases/compiler/inKeywordTypeguard.ts(6,11): error TS2339: Property 'b' does not exist on type 'A'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(8,11): error TS2339: Property 'a' does not exist on type 'B'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(14,11): error TS2339: Property 'b' does not exist on type 'A'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' does not exist on type 'B'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(27,11): error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. | ||
Property 'b' does not exist on type 'AWithOptionalProp'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(42,11): error TS2339: Property 'b' does not exist on type 'AWithMethod'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(49,11): error TS2339: Property 'a' does not exist on type 'never'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type 'never'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(52,11): error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. | ||
Property 'a' does not exist on type 'BWithMethod'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(53,11): error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. | ||
Property 'b' does not exist on type 'AWithMethod'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(62,11): error TS2339: Property 'b' does not exist on type 'A | C | D'. | ||
Property 'b' does not exist on type 'A'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(64,11): error TS2339: Property 'a' does not exist on type 'B'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(72,32): error TS2339: Property 'b' does not exist on type 'A'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(74,32): error TS2339: Property 'a' does not exist on type 'B'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(82,39): error TS2339: Property 'b' does not exist on type 'A'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(84,39): error TS2339: Property 'a' does not exist on type 'B'. | ||
tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' does not exist on type 'never'. | ||
|
||
|
||
==== tests/cases/compiler/inKeywordTypeguard.ts (17 errors) ==== | ||
class A { a: string; } | ||
class B { b: string; } | ||
|
||
function negativeClassesTest(x: A | B) { | ||
if ("a" in x) { | ||
x.b = "1"; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'A'. | ||
} else { | ||
x.a = "1"; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'B'. | ||
} | ||
} | ||
|
||
function positiveClassesTest(x: A | B) { | ||
if ("a" in x) { | ||
x.b = "1"; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'A'. | ||
} else { | ||
x.a = "1"; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'B'. | ||
} | ||
} | ||
|
||
class AWithOptionalProp { a?: string; } | ||
class BWithOptionalProp { b?: string; } | ||
|
||
function positiveTestClassesWithOptionalProperties(x: AWithOptionalProp | BWithOptionalProp) { | ||
if ("a" in x) { | ||
x.a = "1"; | ||
} else { | ||
x.b = "1"; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'. | ||
!!! error TS2339: Property 'b' does not exist on type 'AWithOptionalProp'. | ||
} | ||
} | ||
|
||
class AWithMethod { | ||
a(): string { return ""; } | ||
} | ||
|
||
class BWithMethod { | ||
b(): string { return ""; } | ||
} | ||
|
||
function negativeTestClassesWithMembers(x: AWithMethod | BWithMethod) { | ||
if ("a" in x) { | ||
x.a(); | ||
x.b(); | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'AWithMethod'. | ||
} else { | ||
} | ||
} | ||
|
||
function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWithMethod) { | ||
if ("c" in x) { | ||
x.a(); | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'never'. | ||
x.b(); | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'never'. | ||
} else { | ||
x.a(); | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'AWithMethod | BWithMethod'. | ||
!!! error TS2339: Property 'a' does not exist on type 'BWithMethod'. | ||
x.b(); | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'AWithMethod | BWithMethod'. | ||
!!! error TS2339: Property 'b' does not exist on type 'AWithMethod'. | ||
} | ||
} | ||
|
||
class C { a: string; } | ||
class D { a: string; } | ||
|
||
function negativeMultipleClassesTest(x: A | B | C | D) { | ||
if ("a" in x) { | ||
x.b = "1"; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'A | C | D'. | ||
!!! error TS2339: Property 'b' does not exist on type 'A'. | ||
} else { | ||
x.a = "1"; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'B'. | ||
} | ||
} | ||
|
||
class ClassWithUnionProp { prop: A | B } | ||
|
||
function negativePropTest(x: ClassWithUnionProp) { | ||
if ("a" in x.prop) { | ||
let y: string = x.prop.b; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'A'. | ||
} else { | ||
let z: string = x.prop.a; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'B'. | ||
} | ||
} | ||
|
||
class NegativeClassTest { | ||
protected prop: A | B; | ||
inThis() { | ||
if ("a" in this.prop) { | ||
let z: number = this.prop.b; | ||
~ | ||
!!! error TS2339: Property 'b' does not exist on type 'A'. | ||
} else { | ||
let y: string = this.prop.a; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'B'. | ||
} | ||
} | ||
} | ||
|
||
class UnreachableCodeDetection { | ||
a: string; | ||
inThis() { | ||
if ("a" in this) { | ||
} else { | ||
let y = this.a; | ||
~ | ||
!!! error TS2339: Property 'a' does not exist on type 'never'. | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really like to be able to use
in
for narrowing, but I'm not sure this is it...Consider:
foo
is only valid as aB
, but it would pass thein
check, and then be treated as anA
; but itsa
key is not of typestring
.This means that, within the branch, the type of
x
should still beA|B
, but you are allowed to access.a
; I just don't know what type.a
should have. It probably should beany
, which should trip--noImplicitAny
if it's not used with a type narrowing expression.Scenarios:
It doesn't seem to be all that useful to work like this, but it at least allows you to access
.a
at all.Bonus points, but probably hard to actually do in practice:
Alternative solution: Have a way to indicate that
B
has "not this key". Maybe, for example,interface B { a: never; b: string }
should mean thatB
is not allowed to have ana
key at all; even if it's set to a value of typenever
such asundefined!
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We considered the soundness aspect and it's not very different from existing soundness issues around aliased objects with undeclared properties. IOW you can already write this code, error-free (even in flow!), which observably fails:
The reality is that most unions are already correctly disjointed and don't alias enough to manifest the problem. Someone writing an
in
test is not going to write a "better" check; all that really happens in practice is that people add type assertions or move the code to an equally-unsound user-defined type predicate. On net I don't think this is any worse than the status quo (and is better because it induces fewer user-defined type predicates, which are error-prone if written usingin
due to a lack of typo-checking).For automatically disjointed unions, see #14094.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Kovensky im agree, this is nasty case, but it grows from the fact we have structural type system in place. If we, for example, had nominal type system, the info we provided in function definitions would exactly matching all its usages and we never had this problem in place.
Workaround is to give compiler more complete info about types given, like this: