Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd type guard for `in` keyword #15256
Conversation
msftclas
added
the
cla-signed
label
Apr 18, 2017
| function narrowByInKeyword(type: Type, literal: LiteralExpression, assumeTrue: boolean) { | ||
| if ((type.flags & (TypeFlags.Union | TypeFlags.Object)) || (type.flags & TypeFlags.TypeParameter && (type as TypeParameter).isThisType)) { | ||
| const propName = literal.text; | ||
| return filterType(type, t => !!getPropertyOfType(t, propName) === assumeTrue); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| if ("a" in x) { | ||
| x.a = "1"; | ||
| } else { | ||
| x.b = "1"; |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
IdeaHunter
Apr 18, 2017
Contributor
@RyanCavanaugh it isn't clear what should we do here since "a" in {a:undefined} === true and how we should look on it from strictNullChecks perspective...
Can you please give an advice how we should narrow this guy?
This comment has been minimized.
This comment has been minimized.
IdeaHunter
Apr 18, 2017
Contributor
Right now we have following logic: propertyIsInType === assumeTrue. If i got you right then we should change this logic to
propertyIsOptional
? assumeTrue && propertyIsInType
: propertyIsInType === assumeTrueright?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
IdeaHunter
Apr 20, 2017
•
Contributor
Im changing behavior to requested one and now line 57 it gives us an error that seems make sense
tests/cases/compiler/inKeywordTypeguard.ts error TS2339: Property 'b' does not exist on type 'AWithOptionalProp | BWithOptionalProp'.
Property 'b' does not exist on type 'AWithOptionalProp'
This comment has been minimized.
This comment has been minimized.
|
@RyanCavanaugh I have updated code to match requested behavior, can you, please, review it? |
This comment has been minimized.
This comment has been minimized.
|
Looks good overall to me. @ahejlsberg care to review? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
EliSnow
commented
May 16, 2017
|
I'm really looking forward to this landing! |
This comment has been minimized.
This comment has been minimized.
EliSnow
commented
Jun 7, 2017
|
Is it possible this will land for the Typescript 2.4 release? |
This comment has been minimized.
This comment has been minimized.
hax
commented
Sep 12, 2017
|
Can I ask what block this PR being merged? |
msftgits
removed
the
cla-signed
label
Sep 26, 2017
Microsoft
deleted a comment from
msftclas
Sep 27, 2017
This comment has been minimized.
This comment has been minimized.
pelotom
commented
Nov 5, 2017
|
Can anyone comment as to why this PR is still languishing 6 months later? |
This comment has been minimized.
This comment has been minimized.
|
There is two reasons of it:
|
| @@ -11336,6 +11336,34 @@ namespace ts { | |||
| return type; | |||
| } | |||
|
|
|||
| function isTypePresencePossible(type: Type, propName: string, shouldHaveProperty: boolean) { | |||
| const prop = getPropertyOfType(type, propName); | |||
| if (!prop) { | |||
This comment has been minimized.
This comment has been minimized.
RyanCavanaugh
Dec 4, 2017
Member
Two edits in this body - rename shouldHaveProperty to assumeTrue and reduce the if to
if (prop) {
return (prop.flags & SymbolFlags.Optional) ? true : assumeTrue;
}
return !assumeTrue;
This comment has been minimized.
This comment has been minimized.
|
Good to merge after one edit. Sorry for the delay on this |
| if ("a" in x) { | ||
| x.b = "1"; | ||
| ~ | ||
| !!! error TS2339: Property 'b' does not exist on type 'A'. |
This comment has been minimized.
This comment has been minimized.
Kovensky
Dec 5, 2017
•
Contributor
I'd really like to be able to use in for narrowing, but I'm not sure this is it...
Consider:
const foo = { a: true, b: '' }
negativeClassesTest(foo)foo is only valid as a B, but it would pass the in check, and then be treated as an A; but its a key is not of type string.
This means that, within the branch, the type of x should still be A|B, but you are allowed to access .a; I just don't know what type .a should have. It probably should be any, which should trip --noImplicitAny if it's not used with a type narrowing expression.
Scenarios:
function negativeClassesTest(x: A | B) {
if ("a" in x) {
x.a // --noImplicitAny error
const y: string = x.a // ideally a --noImplicitAny error, if it's possible to have "signalling any"s
const z = x.a as string // accepted because it's a cast
const u: string = typeof x.a === 'string' ? x.a : '' // accepted because it's narrowed
}
}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:
function negativeClassesTest(x: A | B) {
if ("a" in x && typeof x.a === "string") {
// x _should_ be an A!
}
}Alternative solution: Have a way to indicate that B has "not this key". Maybe, for example, interface B { a: never; b: string } should mean that B is not allowed to have an a key at all; even if it's set to a value of type never such as undefined!.
This comment has been minimized.
This comment has been minimized.
RyanCavanaugh
Dec 5, 2017
•
Member
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:
interface T { x: string, y?: number };
const s = { x: "", y: "uh oh" };
const not_t: { x: string } = s;
const unsound: T = not_t;
(unsound.y && unsound.y.toFixed());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 using in due to a lack of typo-checking).
For automatically disjointed unions, see #14094.
This comment has been minimized.
This comment has been minimized.
IdeaHunter
Dec 6, 2017
Contributor
@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:
class A { a: string }
class B { b: string }
class C { a: number; b: string }
function z(x: A|B|C){
if("a" in x){
var num_a: number = x.a; // error TS2322: Type 'string | number' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.
var str_a: string = x.a; // error TS2322: Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
}
}
This comment has been minimized.
This comment has been minimized.
|
Going to to do requested fixes later, after my work day ends :-) |
IdeaHunter commentedApr 18, 2017
Fixes #10485
Added support for typeguarding for following cases: