diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dba3c8584b153..1c810e4795da2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14773,6 +14773,32 @@ namespace ts { return type; } + // This function assumes the constituent type list is sorted and deduplicated. + function getIntersectionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { + if (types.length === 0) { + return neverType; + } + if (types.length === 1) { + return types[0]; + } + const id = getTypeListId(types) + getAliasId(aliasSymbol, aliasTypeArguments); + const existingType = intersectionTypes.get(id); + if (existingType) { + return existingType; + } + const type = createType(TypeFlags.Intersection) as IntersectionType; + type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable); + type.types = types; + type.aliasSymbol = aliasSymbol; + type.aliasTypeArguments = aliasTypeArguments; + if (types.length === 2 && types[0].flags & TypeFlags.BooleanLiteral && types[1].flags & TypeFlags.BooleanLiteral) { + type.flags |= TypeFlags.Boolean; + (type as IntersectionType & IntrinsicType).intrinsicName = "boolean"; + } + intersectionTypes.set(id, type); + return type; + } + function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { const links = getNodeLinks(node); if (!links.resolvedType) { @@ -23724,13 +23750,13 @@ namespace ts { return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); } - function filterType(type: Type, f: (t: Type) => boolean): Type { + function filterUnionOrIntersectionType(type: UnionOrIntersectionType, f: (t: Type) => boolean): Type { + const types = type.types; + const filtered = filter(types, f); + if (filtered === types) { + return type; + } if (type.flags & TypeFlags.Union) { - const types = (type as UnionType).types; - const filtered = filter(types, f); - if (filtered === types) { - return type; - } const origin = (type as UnionType).origin; let newOrigin: Type | undefined; if (origin && origin.flags & TypeFlags.Union) { @@ -23748,7 +23774,14 @@ namespace ts { newOrigin = createOriginUnionOrIntersectionType(TypeFlags.Union, originFiltered); } } - return getUnionTypeFromSortedList(filtered, (type as UnionType).objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + return getUnionTypeFromSortedList(filtered, type.objectFlags, /*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, newOrigin); + } + return getIntersectionTypeFromSortedList(filtered, type.objectFlags); + } + + function filterType(type: Type, f: (t: Type) => boolean): Type { + if (type.flags & TypeFlags.Union) { + return filterUnionOrIntersectionType(type as UnionType, f); } return type.flags & TypeFlags.Never || f(type) ? type : neverType; } @@ -24754,12 +24787,58 @@ namespace ts { return getApplicableIndexInfoForName(type, propName) ? true : !assumeTrue; } - function narrowByInKeyword(type: Type, name: __String, assumeTrue: boolean) { - if (type.flags & TypeFlags.Union - || type.flags & TypeFlags.Object && declaredType !== type - || isThisTypeParameter(type) - || type.flags & TypeFlags.Intersection && every((type as IntersectionType).types, t => t.symbol !== globalThisSymbol)) { - return filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); + function widenTypeWithSymbol(type: Type, newSymbol: Symbol): Type { + // If type is this/any/unknown, it cannot be widened. + if ((type.flags & TypeFlags.AnyOrUnknown) || isThisTypeParameter(type)) { + return type; + } + // If type is anonymous object, add the symbol directly + if (getObjectFlags(type) & ObjectFlags.Anonymous) { + return widenObjectType(type as ObjectType, newSymbol); + } + // If type is intersection, add the symbol to the first anonymous object component of the intersection + if (type.flags & TypeFlags.Intersection) { + const objectSubtype = (type as IntersectionType).types.find(t => getObjectFlags(t) & ObjectFlags.Anonymous) as ObjectType | undefined; + if (objectSubtype) { + const restOfIntersection = filterUnionOrIntersectionType(type as IntersectionType, t => t !== objectSubtype); + return createIntersectionType([restOfIntersection, widenObjectType(objectSubtype, newSymbol)]); + } + } + + // Otherwise, just add the new object type as an intersection + const newTypeWithSymbol = widenObjectType(createAnonymousType(undefined, createSymbolTable(), emptyArray, emptyArray, emptyArray), newSymbol); + return createIntersectionType([type, newTypeWithSymbol]); + + function widenObjectType(type: ObjectType, newSymbol: Symbol): Type { + const members = createSymbolTable(); + if (type.members !== undefined) { + mergeSymbolTable(members, type.members); + } + members.set(newSymbol.escapedName, newSymbol); + return createAnonymousType(undefined, members, type.callSignatures ?? emptyArray, type.constructSignatures ?? emptyArray, type.indexInfos ?? emptyArray); + } + } + + function narrowOrWidenTypeByInKeyword(type: Type, name: __String, assumeTrue: boolean) { + // If type contains global this, don't touch it + if (type.symbol === globalThisSymbol + || (type.flags & TypeFlags.UnionOrIntersection + && filterUnionOrIntersectionType(type as UnionOrIntersectionType, t => t.symbol === globalThisSymbol) !== neverType) + ) { + return type; + } + + // If union, filter out all components not containing the property + const subtypeWithProp = filterType(type, t => isTypePresencePossible(t, name, assumeTrue)); + if (subtypeWithProp !== neverType || getPropertyOfType(type, name, /* skipObjectFunctionPropertyAugment */ false)) { + return subtypeWithProp; + } + + // only widen property when the type does not contain string-index/name in any of the constituents. + if (assumeTrue && !getIndexInfoOfType(type, stringType)) { + const addSymbol = createSymbol(SymbolFlags.Property, name); + addSymbol.type = unknownType; + return widenTypeWithSymbol(type, addSymbol); } return type; } @@ -24828,7 +24907,7 @@ namespace ts { return getTypeWithFacts(type, assumeTrue ? TypeFacts.NEUndefined : TypeFacts.EQUndefined); } if (isMatchingReference(reference, target)) { - return narrowByInKeyword(type, name, assumeTrue); + return narrowOrWidenTypeByInKeyword(type, name, assumeTrue); } } break; diff --git a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types index c1eee6deff83f..77591c2822c6b 100644 --- a/tests/baselines/reference/conditionalTypeDoesntSpinForever.types +++ b/tests/baselines/reference/conditionalTypeDoesntSpinForever.types @@ -212,12 +212,12 @@ export enum PubSubRecordIsStoredInRedisAsA { >buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) : BuildPubSubRecordType >buildPubSubRecordType : (soFar: SO_FAR) => BuildPubSubRecordType >Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE} : SO_FAR & { identifier: TYPE; } ->Object.assign({}, soFar, {identifier: instance as TYPE}) : SO_FAR & { identifier: TYPE; } +>Object.assign({}, soFar, {identifier: instance as TYPE}) : SO_FAR & { record: unknown; } & { identifier: TYPE; } >Object.assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >Object : ObjectConstructor >assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >{} : {} ->soFar : SO_FAR +>soFar : SO_FAR & { record: unknown; } >{identifier: instance as TYPE} : { identifier: TYPE; } >identifier : TYPE >instance as TYPE : TYPE @@ -389,13 +389,13 @@ export enum PubSubRecordIsStoredInRedisAsA { >soFar : SO_FAR >"object" in soFar : boolean >"object" : "object" ->soFar : SO_FAR +>soFar : SO_FAR & { identifier: unknown; } >"maxMsToWaitBeforePublishing" in soFar : boolean >"maxMsToWaitBeforePublishing" : "maxMsToWaitBeforePublishing" ->soFar : SO_FAR +>soFar : SO_FAR & { identifier: unknown; object: unknown; } >"PubSubRecordIsStoredInRedisAsA" in soFar : boolean >"PubSubRecordIsStoredInRedisAsA" : "PubSubRecordIsStoredInRedisAsA" ->soFar : SO_FAR +>soFar : SO_FAR & { identifier: unknown; object: unknown; maxMsToWaitBeforePublishing: unknown; } >{} : {} >{ type: soFar, fields: () => new Set(Object.keys(soFar) as (keyof SO_FAR)[]), hasField: (fieldName: string | number | symbol) => fieldName in soFar } : { type: SO_FAR; fields: () => Set; hasField: (fieldName: string | number | symbol) => boolean; } diff --git a/tests/baselines/reference/controlFlowInOperator.js b/tests/baselines/reference/controlFlowInOperator.js index d4a5731eb9844..91f833229206b 100644 --- a/tests/baselines/reference/controlFlowInOperator.js +++ b/tests/baselines/reference/controlFlowInOperator.js @@ -3,6 +3,8 @@ const a = 'a'; const b = 'b'; const d = 'd'; +// Type narrowing + type A = { [a]: number; }; type B = { [b]: string; }; @@ -10,21 +12,205 @@ declare const c: A | B; if ('a' in c) { c; // A - c['a']; // number; + c['a']; // number +} else { + c; // B + c['b'] // string } if ('d' in c) { - c; // never + c; // (A | B) & { d: unknown; } +} else { + c; // (A | B) } if (a in c) { c; // A c[a]; // number; +} else { + c; // B + c[b] // string } if (d in c) { - c; // never + c; // (A | B) & { d: unknown; } +} else { + c; // (A | B) +} + +// Type widening + +declare const e: {}; + +if ('a' in e) { + e; // { a: unknown; } + e['a'] // unknown +} else { + e; // {} +} + +if (a in e) { + e; // { a: unknown; } + e[a] // unknown +} else { + e; // {} +} + +// Widening different types + +declare const e1: any; +if ('a' in e1) { + e1; // any } + +declare const e2: object; +if ('a' in e2) { + e2; // object & { a: unknown; } + e2['a']; // unknown +} + +declare const e3: { b: string; } & { c: number; }; +if ('a' in e3) { + e3; // { a: unknown; b: string; } & { c: number } + e3['a']; // unknown +} + +interface C { + cProp: string; +} +interface D { + dProp: number; +} +declare const e4: C & D; +if ('a' in e4) { + e4; // C & D & { a: unknown; } + e4['a']; // unknown +} + +declare const e5: never; +if ('a' in e5) { + e5; // never +} + +declare const e6: { b: string; (arg: string): boolean; } +if ('a' in e6) { + e6; // { a: unknown; b: string; (arg: string): boolean; } + e6['a']; // unknown + e6(''); // boolean; +} + +declare const e7: { b: string; new (arg: string): boolean; } +if ('a' in e7) { + e7; // { a: unknown; b: string; new (arg: string): boolean; } + e7['a']; // unknown + new e7(''); // boolean; +} + +declare const e8: { b: string; [index: number]: boolean; } +if ('a' in e8) { + e8; // { a: unknown; b: string; [index: number]: boolean; } + e8['a']; // unknown + e8[42]; // boolean; +} + +// More complex control flows + +e; // {} +if ( 'a' in e ) { + e; // { a: unknown; } + if ( 'b' in e ) { + e; // { a: unknown; b: unknown; } + e['a']; // unknown + e['b']; // unknown + } else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} + +e; // {} +if ( a in e ) { + e; // { a: unknown; } + if ( b in e ) { + e; // { a: unknown; b: unknown; } + e[a]; // unknown + e[b]; // unknown + } else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} + +e; // {} +if ( 'a' in e ) { + e; // { a: unknown; } + e['a']; // unknown +} else if ( 'b' in e ) { + e; // { b: unknown; } + e['b']; // unknown +} else { + e; // {} +} +e; // {} + +e; // {} +if ( a in e ) { + e; // { a: unknown; } + e[a]; // unknown +} else if ( b in e ) { + e; // { b: unknown; } + e[b]; // unknown +} else { + e; // {} +} +e; // {} + +declare const f: Array<{}> + +for (const g of f) { + g; // {} + if ('a' in g) { + g; // { a: unknown; } + g['a']; // unknown + } + g; // {} +} + +for (const g of f) { + g; // {} + if (a in g) { + g; // { a: unknown; } + g[a]; // unknown + } + g; // {} +} + +function h(i: {}) { + if ( 'a' in i ) { + i; // { a: unknown; } + } else if ( 'b' in i ) { + i; // { b: unknown; } + } else { + return; + } + i; // { a: unknown; } | { b: unknown; } +} +h(e); + +declare const j: { a: 'first'; b: string; } | { a: 'second'; c: string; }; + +if (j.a === 'first') { + j; // { a: 'first'; b: string; } + j['b']; // string + if ( 'c' in j ) { + j; // { a: 'first'; b: string; c: unknown; } + j['b']; // string + j['c']; // unknown + } +} +j; // { a: 'first'; b: string; } | { a: 'second'; c: string; } //// [controlFlowInOperator.js] @@ -33,15 +219,172 @@ var b = 'b'; var d = 'd'; if ('a' in c) { c; // A - c['a']; // number; + c['a']; // number +} +else { + c; // B + c['b']; // string } if ('d' in c) { - c; // never + c; // (A | B) & { d: unknown; } +} +else { + c; // (A | B) } if (a in c) { c; // A c[a]; // number; } +else { + c; // B + c[b]; // string +} if (d in c) { - c; // never + c; // (A | B) & { d: unknown; } +} +else { + c; // (A | B) +} +if ('a' in e) { + e; // { a: unknown; } + e['a']; // unknown +} +else { + e; // {} +} +if (a in e) { + e; // { a: unknown; } + e[a]; // unknown +} +else { + e; // {} +} +if ('a' in e1) { + e1; // any +} +if ('a' in e2) { + e2; // object & { a: unknown; } + e2['a']; // unknown +} +if ('a' in e3) { + e3; // { a: unknown; b: string; } & { c: number } + e3['a']; // unknown +} +if ('a' in e4) { + e4; // C & D & { a: unknown; } + e4['a']; // unknown +} +if ('a' in e5) { + e5; // never +} +if ('a' in e6) { + e6; // { a: unknown; b: string; (arg: string): boolean; } + e6['a']; // unknown + e6(''); // boolean; +} +if ('a' in e7) { + e7; // { a: unknown; b: string; new (arg: string): boolean; } + e7['a']; // unknown + new e7(''); // boolean; +} +if ('a' in e8) { + e8; // { a: unknown; b: string; [index: number]: boolean; } + e8['a']; // unknown + e8[42]; // boolean; +} +// More complex control flows +e; // {} +if ('a' in e) { + e; // { a: unknown; } + if ('b' in e) { + e; // { a: unknown; b: unknown; } + e['a']; // unknown + e['b']; // unknown + } + else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} +e; // {} +if (a in e) { + e; // { a: unknown; } + if (b in e) { + e; // { a: unknown; b: unknown; } + e[a]; // unknown + e[b]; // unknown + } + else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} +e; // {} +if ('a' in e) { + e; // { a: unknown; } + e['a']; // unknown +} +else if ('b' in e) { + e; // { b: unknown; } + e['b']; // unknown +} +else { + e; // {} +} +e; // {} +e; // {} +if (a in e) { + e; // { a: unknown; } + e[a]; // unknown +} +else if (b in e) { + e; // { b: unknown; } + e[b]; // unknown +} +else { + e; // {} +} +e; // {} +for (var _i = 0, f_1 = f; _i < f_1.length; _i++) { + var g = f_1[_i]; + g; // {} + if ('a' in g) { + g; // { a: unknown; } + g['a']; // unknown + } + g; // {} +} +for (var _a = 0, f_2 = f; _a < f_2.length; _a++) { + var g = f_2[_a]; + g; // {} + if (a in g) { + g; // { a: unknown; } + g[a]; // unknown + } + g; // {} +} +function h(i) { + if ('a' in i) { + i; // { a: unknown; } + } + else if ('b' in i) { + i; // { b: unknown; } + } + else { + return; + } + i; // { a: unknown; } | { b: unknown; } +} +h(e); +if (j.a === 'first') { + j; // { a: 'first'; b: string; } + j['b']; // string + if ('c' in j) { + j; // { a: 'first'; b: string; c: unknown; } + j['b']; // string + j['c']; // unknown + } } +j; // { a: 'first'; b: string; } | { a: 'second'; c: string; } diff --git a/tests/baselines/reference/controlFlowInOperator.symbols b/tests/baselines/reference/controlFlowInOperator.symbols index 03b0e4b40b4b5..749e508d92e31 100644 --- a/tests/baselines/reference/controlFlowInOperator.symbols +++ b/tests/baselines/reference/controlFlowInOperator.symbols @@ -8,56 +8,497 @@ const b = 'b'; const d = 'd'; >d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5)) +// Type narrowing + type A = { [a]: number; }; >A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14)) ->[a] : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10)) +>[a] : Symbol([a], Decl(controlFlowInOperator.ts, 6, 10)) >a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) type B = { [b]: string; }; ->B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26)) ->[b] : Symbol([b], Decl(controlFlowInOperator.ts, 5, 10)) +>B : Symbol(B, Decl(controlFlowInOperator.ts, 6, 26)) +>[b] : Symbol([b], Decl(controlFlowInOperator.ts, 7, 10)) >b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) declare const c: A | B; ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) >A : Symbol(A, Decl(controlFlowInOperator.ts, 2, 14)) ->B : Symbol(B, Decl(controlFlowInOperator.ts, 4, 26)) +>B : Symbol(B, Decl(controlFlowInOperator.ts, 6, 26)) if ('a' in c) { ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) c; // A ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) + + c['a']; // number +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) +>'a' : Symbol([a], Decl(controlFlowInOperator.ts, 6, 10)) + +} else { + c; // B +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) - c['a']; // number; ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) ->'a' : Symbol([a], Decl(controlFlowInOperator.ts, 4, 10)) + c['b'] // string +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) +>'b' : Symbol([b], Decl(controlFlowInOperator.ts, 7, 10)) } if ('d' in c) { ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) - c; // never ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) + c; // (A | B) & { d: unknown; } +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) + +} else { + c; // (A | B) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) } if (a in c) { >a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) c; // A ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) c[a]; // number; ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) >a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) + +} else { + c; // B +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) + + c[b] // string +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) } if (d in c) { >d : Symbol(d, Decl(controlFlowInOperator.ts, 2, 5)) ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) + + c; // (A | B) & { d: unknown; } +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) + +} else { + c; // (A | B) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 9, 13)) +} + +// Type widening + +declare const e: {}; +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +if ('a' in e) { +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e['a'] // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>'a' : Symbol(a) + +} else { + e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} + +if (a in e) { +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e[a] // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) + +} else { + e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} + +// Widening different types + +declare const e1: any; +>e1 : Symbol(e1, Decl(controlFlowInOperator.ts, 59, 13)) + +if ('a' in e1) { +>e1 : Symbol(e1, Decl(controlFlowInOperator.ts, 59, 13)) + + e1; // any +>e1 : Symbol(e1, Decl(controlFlowInOperator.ts, 59, 13)) +} + +declare const e2: object; +>e2 : Symbol(e2, Decl(controlFlowInOperator.ts, 64, 13)) + +if ('a' in e2) { +>e2 : Symbol(e2, Decl(controlFlowInOperator.ts, 64, 13)) + + e2; // object & { a: unknown; } +>e2 : Symbol(e2, Decl(controlFlowInOperator.ts, 64, 13)) + + e2['a']; // unknown +>e2 : Symbol(e2, Decl(controlFlowInOperator.ts, 64, 13)) +>'a' : Symbol(a) +} + +declare const e3: { b: string; } & { c: number; }; +>e3 : Symbol(e3, Decl(controlFlowInOperator.ts, 70, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 70, 19)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 70, 36)) + +if ('a' in e3) { +>e3 : Symbol(e3, Decl(controlFlowInOperator.ts, 70, 13)) + + e3; // { a: unknown; b: string; } & { c: number } +>e3 : Symbol(e3, Decl(controlFlowInOperator.ts, 70, 13)) + + e3['a']; // unknown +>e3 : Symbol(e3, Decl(controlFlowInOperator.ts, 70, 13)) +>'a' : Symbol(a) +} + +interface C { +>C : Symbol(C, Decl(controlFlowInOperator.ts, 74, 1)) + + cProp: string; +>cProp : Symbol(C.cProp, Decl(controlFlowInOperator.ts, 76, 13)) +} +interface D { +>D : Symbol(D, Decl(controlFlowInOperator.ts, 78, 1)) + + dProp: number; +>dProp : Symbol(D.dProp, Decl(controlFlowInOperator.ts, 79, 13)) +} +declare const e4: C & D; +>e4 : Symbol(e4, Decl(controlFlowInOperator.ts, 82, 13)) +>C : Symbol(C, Decl(controlFlowInOperator.ts, 74, 1)) +>D : Symbol(D, Decl(controlFlowInOperator.ts, 78, 1)) + +if ('a' in e4) { +>e4 : Symbol(e4, Decl(controlFlowInOperator.ts, 82, 13)) + + e4; // C & D & { a: unknown; } +>e4 : Symbol(e4, Decl(controlFlowInOperator.ts, 82, 13)) + + e4['a']; // unknown +>e4 : Symbol(e4, Decl(controlFlowInOperator.ts, 82, 13)) +>'a' : Symbol(a) +} + +declare const e5: never; +>e5 : Symbol(e5, Decl(controlFlowInOperator.ts, 88, 13)) + +if ('a' in e5) { +>e5 : Symbol(e5, Decl(controlFlowInOperator.ts, 88, 13)) + + e5; // never +>e5 : Symbol(e5, Decl(controlFlowInOperator.ts, 88, 13)) +} + +declare const e6: { b: string; (arg: string): boolean; } +>e6 : Symbol(e6, Decl(controlFlowInOperator.ts, 93, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 93, 19)) +>arg : Symbol(arg, Decl(controlFlowInOperator.ts, 93, 32)) + +if ('a' in e6) { +>e6 : Symbol(e6, Decl(controlFlowInOperator.ts, 93, 13)) + + e6; // { a: unknown; b: string; (arg: string): boolean; } +>e6 : Symbol(e6, Decl(controlFlowInOperator.ts, 93, 13)) + + e6['a']; // unknown +>e6 : Symbol(e6, Decl(controlFlowInOperator.ts, 93, 13)) +>'a' : Symbol(a) + + e6(''); // boolean; +>e6 : Symbol(e6, Decl(controlFlowInOperator.ts, 93, 13)) +} + +declare const e7: { b: string; new (arg: string): boolean; } +>e7 : Symbol(e7, Decl(controlFlowInOperator.ts, 100, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 100, 19)) +>arg : Symbol(arg, Decl(controlFlowInOperator.ts, 100, 36)) + +if ('a' in e7) { +>e7 : Symbol(e7, Decl(controlFlowInOperator.ts, 100, 13)) + + e7; // { a: unknown; b: string; new (arg: string): boolean; } +>e7 : Symbol(e7, Decl(controlFlowInOperator.ts, 100, 13)) + + e7['a']; // unknown +>e7 : Symbol(e7, Decl(controlFlowInOperator.ts, 100, 13)) +>'a' : Symbol(a) + + new e7(''); // boolean; +>e7 : Symbol(e7, Decl(controlFlowInOperator.ts, 100, 13)) +} + +declare const e8: { b: string; [index: number]: boolean; } +>e8 : Symbol(e8, Decl(controlFlowInOperator.ts, 107, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 107, 19)) +>index : Symbol(index, Decl(controlFlowInOperator.ts, 107, 32)) + +if ('a' in e8) { +>e8 : Symbol(e8, Decl(controlFlowInOperator.ts, 107, 13)) + + e8; // { a: unknown; b: string; [index: number]: boolean; } +>e8 : Symbol(e8, Decl(controlFlowInOperator.ts, 107, 13)) + + e8['a']; // unknown +>e8 : Symbol(e8, Decl(controlFlowInOperator.ts, 107, 13)) +>'a' : Symbol(a) + + e8[42]; // boolean; +>e8 : Symbol(e8, Decl(controlFlowInOperator.ts, 107, 13)) +} + +// More complex control flows + +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +if ( 'a' in e ) { +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + if ( 'b' in e ) { +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; b: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e['a']; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>'a' : Symbol(a) + + e['b']; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>'b' : Symbol(b) + + } else { + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + } + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +if ( a in e ) { +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + if ( b in e ) { +>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; b: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e[a]; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) + + e[b]; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) + + } else { + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + } + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +if ( 'a' in e ) { +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e['a']; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>'a' : Symbol(a) + +} else if ( 'b' in e ) { +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { b: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e['b']; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>'b' : Symbol(b) + +} else { + e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +if ( a in e ) { +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { a: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e[a]; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) + +} else if ( b in e ) { +>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e; // { b: unknown; } +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + + e[b]; // unknown +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 1, 5)) + +} else { + e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) +} +e; // {} +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +declare const f: Array<{}> +>f : Symbol(f, Decl(controlFlowInOperator.ts, 168, 13)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +for (const g of f) { +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) +>f : Symbol(f, Decl(controlFlowInOperator.ts, 168, 13)) + + g; // {} +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) + + if ('a' in g) { +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) + + g; // { a: unknown; } +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) + + g['a']; // unknown +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) +>'a' : Symbol(a) + } + g; // {} +>g : Symbol(g, Decl(controlFlowInOperator.ts, 170, 10)) +} + +for (const g of f) { +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) +>f : Symbol(f, Decl(controlFlowInOperator.ts, 168, 13)) + + g; // {} +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) + + if (a in g) { +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) + + g; // { a: unknown; } +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) + + g[a]; // unknown +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 0, 5)) + } + g; // {} +>g : Symbol(g, Decl(controlFlowInOperator.ts, 179, 10)) +} + +function h(i: {}) { +>h : Symbol(h, Decl(controlFlowInOperator.ts, 186, 1)) +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) + + if ( 'a' in i ) { +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) + + i; // { a: unknown; } +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) + + } else if ( 'b' in i ) { +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) + + i; // { b: unknown; } +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) + + } else { + return; + } + i; // { a: unknown; } | { b: unknown; } +>i : Symbol(i, Decl(controlFlowInOperator.ts, 188, 11)) +} +h(e); +>h : Symbol(h, Decl(controlFlowInOperator.ts, 186, 1)) +>e : Symbol(e, Decl(controlFlowInOperator.ts, 41, 13)) + +declare const j: { a: 'first'; b: string; } | { a: 'second'; c: string; }; +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 200, 18)) +>b : Symbol(b, Decl(controlFlowInOperator.ts, 200, 30)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 200, 47)) +>c : Symbol(c, Decl(controlFlowInOperator.ts, 200, 60)) + +if (j.a === 'first') { +>j.a : Symbol(a, Decl(controlFlowInOperator.ts, 200, 18), Decl(controlFlowInOperator.ts, 200, 47)) +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) +>a : Symbol(a, Decl(controlFlowInOperator.ts, 200, 18), Decl(controlFlowInOperator.ts, 200, 47)) + + j; // { a: 'first'; b: string; } +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) + + j['b']; // string +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) +>'b' : Symbol(b, Decl(controlFlowInOperator.ts, 200, 30)) + + if ( 'c' in j ) { +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) + + j; // { a: 'first'; b: string; c: unknown; } +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) + + j['b']; // string +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) +>'b' : Symbol(b, Decl(controlFlowInOperator.ts, 200, 30)) - c; // never ->c : Symbol(c, Decl(controlFlowInOperator.ts, 7, 13)) + j['c']; // unknown +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) +>'c' : Symbol(c) + } } +j; // { a: 'first'; b: string; } | { a: 'second'; c: string; } +>j : Symbol(j, Decl(controlFlowInOperator.ts, 200, 13)) diff --git a/tests/baselines/reference/controlFlowInOperator.types b/tests/baselines/reference/controlFlowInOperator.types index fb1c3f8ccc044..aa35a06c4b070 100644 --- a/tests/baselines/reference/controlFlowInOperator.types +++ b/tests/baselines/reference/controlFlowInOperator.types @@ -11,6 +11,8 @@ const d = 'd'; >d : "d" >'d' : "d" +// Type narrowing + type A = { [a]: number; }; >A : { a: number; } >[a] : number @@ -32,10 +34,19 @@ if ('a' in c) { c; // A >c : A - c['a']; // number; + c['a']; // number >c['a'] : number >c : A >'a' : "a" + +} else { + c; // B +>c : B + + c['b'] // string +>c['b'] : string +>c : B +>'b' : "b" } if ('d' in c) { @@ -43,8 +54,12 @@ if ('d' in c) { >'d' : "d" >c : A | B - c; // never ->c : never + c; // (A | B) & { d: unknown; } +>c : (A | B) & { d: unknown; } + +} else { + c; // (A | B) +>c : A | B } if (a in c) { @@ -59,6 +74,15 @@ if (a in c) { >c[a] : number >c : A >a : "a" + +} else { + c; // B +>c : B + + c[b] // string +>c[b] : string +>c : B +>b : "b" } if (d in c) { @@ -66,7 +90,489 @@ if (d in c) { >d : "d" >c : A | B - c; // never ->c : never + c; // (A | B) & { d: unknown; } +>c : (A | B) & { d: unknown; } + +} else { + c; // (A | B) +>c : A | B +} + +// Type widening + +declare const e: {}; +>e : {} + +if ('a' in e) { +>'a' in e : boolean +>'a' : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + e['a'] // unknown +>e['a'] : unknown +>e : { a: unknown; } +>'a' : "a" + +} else { + e; // {} +>e : {} +} + +if (a in e) { +>a in e : boolean +>a : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + e[a] // unknown +>e[a] : unknown +>e : { a: unknown; } +>a : "a" + +} else { + e; // {} +>e : {} +} + +// Widening different types + +declare const e1: any; +>e1 : any + +if ('a' in e1) { +>'a' in e1 : boolean +>'a' : "a" +>e1 : any + + e1; // any +>e1 : any +} + +declare const e2: object; +>e2 : object + +if ('a' in e2) { +>'a' in e2 : boolean +>'a' : "a" +>e2 : object + + e2; // object & { a: unknown; } +>e2 : object & { a: unknown; } + + e2['a']; // unknown +>e2['a'] : unknown +>e2 : object & { a: unknown; } +>'a' : "a" +} + +declare const e3: { b: string; } & { c: number; }; +>e3 : { b: string; } & { c: number; } +>b : string +>c : number + +if ('a' in e3) { +>'a' in e3 : boolean +>'a' : "a" +>e3 : { b: string; } & { c: number; } + + e3; // { a: unknown; b: string; } & { c: number } +>e3 : { c: number; } & { b: string; a: unknown; } + + e3['a']; // unknown +>e3['a'] : unknown +>e3 : { c: number; } & { b: string; a: unknown; } +>'a' : "a" +} + +interface C { + cProp: string; +>cProp : string +} +interface D { + dProp: number; +>dProp : number +} +declare const e4: C & D; +>e4 : C & D + +if ('a' in e4) { +>'a' in e4 : boolean +>'a' : "a" +>e4 : C & D + + e4; // C & D & { a: unknown; } +>e4 : (C & D) & { a: unknown; } + + e4['a']; // unknown +>e4['a'] : unknown +>e4 : (C & D) & { a: unknown; } +>'a' : "a" +} + +declare const e5: never; +>e5 : never + +if ('a' in e5) { +>'a' in e5 : boolean +>'a' : "a" +>e5 : never + + e5; // never +>e5 : never +} + +declare const e6: { b: string; (arg: string): boolean; } +>e6 : { (arg: string): boolean; b: string; } +>b : string +>arg : string + +if ('a' in e6) { +>'a' in e6 : boolean +>'a' : "a" +>e6 : { (arg: string): boolean; b: string; } + + e6; // { a: unknown; b: string; (arg: string): boolean; } +>e6 : { (arg: string): boolean; b: string; a: unknown; } + + e6['a']; // unknown +>e6['a'] : unknown +>e6 : { (arg: string): boolean; b: string; a: unknown; } +>'a' : "a" + + e6(''); // boolean; +>e6('') : boolean +>e6 : { (arg: string): boolean; b: string; a: unknown; } +>'' : "" +} + +declare const e7: { b: string; new (arg: string): boolean; } +>e7 : { new (arg: string): boolean; b: string; } +>b : string +>arg : string + +if ('a' in e7) { +>'a' in e7 : boolean +>'a' : "a" +>e7 : { new (arg: string): boolean; b: string; } + + e7; // { a: unknown; b: string; new (arg: string): boolean; } +>e7 : { new (arg: string): boolean; b: string; a: unknown; } + + e7['a']; // unknown +>e7['a'] : unknown +>e7 : { new (arg: string): boolean; b: string; a: unknown; } +>'a' : "a" + + new e7(''); // boolean; +>new e7('') : boolean +>e7 : { new (arg: string): boolean; b: string; a: unknown; } +>'' : "" +} + +declare const e8: { b: string; [index: number]: boolean; } +>e8 : { [index: number]: boolean; b: string; } +>b : string +>index : number + +if ('a' in e8) { +>'a' in e8 : boolean +>'a' : "a" +>e8 : { [index: number]: boolean; b: string; } + + e8; // { a: unknown; b: string; [index: number]: boolean; } +>e8 : { [index: number]: boolean; b: string; a: unknown; } + + e8['a']; // unknown +>e8['a'] : unknown +>e8 : { [index: number]: boolean; b: string; a: unknown; } +>'a' : "a" + + e8[42]; // boolean; +>e8[42] : boolean +>e8 : { [index: number]: boolean; b: string; a: unknown; } +>42 : 42 +} + +// More complex control flows + +e; // {} +>e : {} + +if ( 'a' in e ) { +>'a' in e : boolean +>'a' : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + if ( 'b' in e ) { +>'b' in e : boolean +>'b' : "b" +>e : { a: unknown; } + + e; // { a: unknown; b: unknown; } +>e : { a: unknown; b: unknown; } + + e['a']; // unknown +>e['a'] : unknown +>e : { a: unknown; b: unknown; } +>'a' : "a" + + e['b']; // unknown +>e['b'] : unknown +>e : { a: unknown; b: unknown; } +>'b' : "b" + + } else { + e; // { a: unknown; } +>e : { a: unknown; } + } + e; // { a: unknown; } +>e : { a: unknown; } +} +e; // {} +>e : {} + +e; // {} +>e : {} + +if ( a in e ) { +>a in e : boolean +>a : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + if ( b in e ) { +>b in e : boolean +>b : "b" +>e : { a: unknown; } + + e; // { a: unknown; b: unknown; } +>e : { a: unknown; b: unknown; } + + e[a]; // unknown +>e[a] : unknown +>e : { a: unknown; b: unknown; } +>a : "a" + + e[b]; // unknown +>e[b] : unknown +>e : { a: unknown; b: unknown; } +>b : "b" + + } else { + e; // { a: unknown; } +>e : { a: unknown; } + } + e; // { a: unknown; } +>e : { a: unknown; } +} +e; // {} +>e : {} + +e; // {} +>e : {} + +if ( 'a' in e ) { +>'a' in e : boolean +>'a' : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + e['a']; // unknown +>e['a'] : unknown +>e : { a: unknown; } +>'a' : "a" + +} else if ( 'b' in e ) { +>'b' in e : boolean +>'b' : "b" +>e : {} + + e; // { b: unknown; } +>e : { b: unknown; } + + e['b']; // unknown +>e['b'] : unknown +>e : { b: unknown; } +>'b' : "b" + +} else { + e; // {} +>e : {} +} +e; // {} +>e : {} + +e; // {} +>e : {} + +if ( a in e ) { +>a in e : boolean +>a : "a" +>e : {} + + e; // { a: unknown; } +>e : { a: unknown; } + + e[a]; // unknown +>e[a] : unknown +>e : { a: unknown; } +>a : "a" + +} else if ( b in e ) { +>b in e : boolean +>b : "b" +>e : {} + + e; // { b: unknown; } +>e : { b: unknown; } + + e[b]; // unknown +>e[b] : unknown +>e : { b: unknown; } +>b : "b" + +} else { + e; // {} +>e : {} +} +e; // {} +>e : {} + +declare const f: Array<{}> +>f : {}[] + +for (const g of f) { +>g : {} +>f : {}[] + + g; // {} +>g : {} + + if ('a' in g) { +>'a' in g : boolean +>'a' : "a" +>g : {} + + g; // { a: unknown; } +>g : { a: unknown; } + + g['a']; // unknown +>g['a'] : unknown +>g : { a: unknown; } +>'a' : "a" + } + g; // {} +>g : {} +} + +for (const g of f) { +>g : {} +>f : {}[] + + g; // {} +>g : {} + + if (a in g) { +>a in g : boolean +>a : "a" +>g : {} + + g; // { a: unknown; } +>g : { a: unknown; } + + g[a]; // unknown +>g[a] : unknown +>g : { a: unknown; } +>a : "a" + } + g; // {} +>g : {} +} + +function h(i: {}) { +>h : (i: {}) => void +>i : {} + + if ( 'a' in i ) { +>'a' in i : boolean +>'a' : "a" +>i : {} + + i; // { a: unknown; } +>i : { a: unknown; } + + } else if ( 'b' in i ) { +>'b' in i : boolean +>'b' : "b" +>i : {} + + i; // { b: unknown; } +>i : { b: unknown; } + + } else { + return; + } + i; // { a: unknown; } | { b: unknown; } +>i : { a: unknown; } | { b: unknown; } +} +h(e); +>h(e) : void +>h : (i: {}) => void +>e : {} + +declare const j: { a: 'first'; b: string; } | { a: 'second'; c: string; }; +>j : { a: 'first'; b: string; } | { a: 'second'; c: string; } +>a : "first" +>b : string +>a : "second" +>c : string + +if (j.a === 'first') { +>j.a === 'first' : boolean +>j.a : "first" | "second" +>j : { a: "first"; b: string; } | { a: "second"; c: string; } +>a : "first" | "second" +>'first' : "first" + + j; // { a: 'first'; b: string; } +>j : { a: "first"; b: string; } + + j['b']; // string +>j['b'] : string +>j : { a: "first"; b: string; } +>'b' : "b" + + if ( 'c' in j ) { +>'c' in j : boolean +>'c' : "c" +>j : { a: "first"; b: string; } + + j; // { a: 'first'; b: string; c: unknown; } +>j : { a: "first"; b: string; c: unknown; } + + j['b']; // string +>j['b'] : string +>j : { a: "first"; b: string; c: unknown; } +>'b' : "b" + + j['c']; // unknown +>j['c'] : unknown +>j : { a: "first"; b: string; c: unknown; } +>'c' : "c" + } } +j; // { a: 'first'; b: string; } | { a: 'second'; c: string; } +>j : { a: "first"; b: string; } | { a: "second"; c: string; } diff --git a/tests/baselines/reference/fixSignatureCaching.errors.txt b/tests/baselines/reference/fixSignatureCaching.errors.txt index d1c3ce6394fa8..4bb2276c3c554 100644 --- a/tests/baselines/reference/fixSignatureCaching.errors.txt +++ b/tests/baselines/reference/fixSignatureCaching.errors.txt @@ -3,6 +3,7 @@ tests/cases/conformance/fixSignatureCaching.ts(284,10): error TS2339: Property ' tests/cases/conformance/fixSignatureCaching.ts(293,10): error TS2339: Property 'FALLBACK_PHONE' does not exist on type '{}'. tests/cases/conformance/fixSignatureCaching.ts(294,10): error TS2339: Property 'FALLBACK_TABLET' does not exist on type '{}'. tests/cases/conformance/fixSignatureCaching.ts(295,10): error TS2339: Property 'FALLBACK_MOBILE' does not exist on type '{}'. +tests/cases/conformance/fixSignatureCaching.ts(301,17): error TS2339: Property 'isArray' does not exist on type 'never'. tests/cases/conformance/fixSignatureCaching.ts(330,74): error TS2339: Property 'mobileDetectRules' does not exist on type '{}'. tests/cases/conformance/fixSignatureCaching.ts(369,10): error TS2339: Property 'findMatch' does not exist on type '{}'. tests/cases/conformance/fixSignatureCaching.ts(387,10): error TS2339: Property 'findMatches' does not exist on type '{}'. @@ -58,7 +59,7 @@ tests/cases/conformance/fixSignatureCaching.ts(981,16): error TS2304: Cannot fin tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property 'MobileDetect' does not exist on type 'Window & typeof globalThis'. -==== tests/cases/conformance/fixSignatureCaching.ts (58 errors) ==== +==== tests/cases/conformance/fixSignatureCaching.ts (59 errors) ==== // Repro from #10697 (function (define, undefined) { @@ -370,6 +371,8 @@ tests/cases/conformance/fixSignatureCaching.ts(983,44): error TS2339: Property ' isArray = 'isArray' in Array ? function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : Array.isArray; + ~~~~~~~ +!!! error TS2339: Property 'isArray' does not exist on type 'never'. function equalIC(a, b) { return a != null && b != null && a.toLowerCase() === b.toLowerCase(); diff --git a/tests/baselines/reference/fixSignatureCaching.symbols b/tests/baselines/reference/fixSignatureCaching.symbols index 0ec1d2c63aae8..97efe41ce1205 100644 --- a/tests/baselines/reference/fixSignatureCaching.symbols +++ b/tests/baselines/reference/fixSignatureCaching.symbols @@ -825,9 +825,7 @@ define(function () { >value : Symbol(value, Decl(fixSignatureCaching.ts, 299, 20)) : Array.isArray; ->Array.isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) >Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) ->isArray : Symbol(ArrayConstructor.isArray, Decl(lib.es5.d.ts, --, --)) function equalIC(a, b) { >equalIC : Symbol(equalIC, Decl(fixSignatureCaching.ts, 300, 24)) diff --git a/tests/baselines/reference/fixSignatureCaching.types b/tests/baselines/reference/fixSignatureCaching.types index 06ab4f3df7eee..0a0d657a64d10 100644 --- a/tests/baselines/reference/fixSignatureCaching.types +++ b/tests/baselines/reference/fixSignatureCaching.types @@ -1127,9 +1127,9 @@ define(function () { >'[object Array]' : "[object Array]" isArray = 'isArray' in Array ->isArray = 'isArray' in Array ? function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : Array.isArray : (value: any) => boolean +>isArray = 'isArray' in Array ? function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : Array.isArray : any >isArray : any ->'isArray' in Array ? function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : Array.isArray : (value: any) => boolean +>'isArray' in Array ? function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : Array.isArray : any >'isArray' in Array : boolean >'isArray' : "isArray" >Array : ArrayConstructor @@ -1150,9 +1150,9 @@ define(function () { >'[object Array]' : "[object Array]" : Array.isArray; ->Array.isArray : (arg: any) => arg is any[] ->Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>Array.isArray : any +>Array : never +>isArray : any function equalIC(a, b) { >equalIC : (a: any, b: any) => boolean diff --git a/tests/baselines/reference/inKeywordTypeguard.errors.txt b/tests/baselines/reference/inKeywordTypeguard.errors.txt index cfb2b090a7af5..f8be23ee8db7b 100644 --- a/tests/baselines/reference/inKeywordTypeguard.errors.txt +++ b/tests/baselines/reference/inKeywordTypeguard.errors.txt @@ -5,8 +5,8 @@ tests/cases/compiler/inKeywordTypeguard.ts(16,11): error TS2339: Property 'a' do 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(49,11): error TS2339: Property 'a' does not exist on type '(AWithMethod | BWithMethod) & { c: unknown; }'. +tests/cases/compiler/inKeywordTypeguard.ts(50,11): error TS2339: Property 'b' does not exist on type '(AWithMethod | BWithMethod) & { c: unknown; }'. 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'. @@ -85,10 +85,10 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do if ("c" in x) { x.a(); ~ -!!! error TS2339: Property 'a' does not exist on type 'never'. +!!! error TS2339: Property 'a' does not exist on type '(AWithMethod | BWithMethod) & { c: unknown; }'. x.b(); ~ -!!! error TS2339: Property 'b' does not exist on type 'never'. +!!! error TS2339: Property 'b' does not exist on type '(AWithMethod | BWithMethod) & { c: unknown; }'. } else { x.a(); ~ @@ -169,7 +169,7 @@ tests/cases/compiler/inKeywordTypeguard.ts(94,26): error TS2339: Property 'a' do // Repro from #38608 declare const error: Error; if ('extra' in error) { - error // Still Error + error // Error & { extra: unknown; } } else { error // Error } diff --git a/tests/baselines/reference/inKeywordTypeguard.js b/tests/baselines/reference/inKeywordTypeguard.js index e91a6ddf6e4d4..83289c74df865 100644 --- a/tests/baselines/reference/inKeywordTypeguard.js +++ b/tests/baselines/reference/inKeywordTypeguard.js @@ -108,7 +108,7 @@ function positiveIntersectionTest(x: { a: string } & { b: string }) { // Repro from #38608 declare const error: Error; if ('extra' in error) { - error // Still Error + error // Error & { extra: unknown; } } else { error // Error } @@ -293,7 +293,7 @@ function positiveIntersectionTest(x) { } } if ('extra' in error) { - error; // Still Error + error; // Error & { extra: unknown; } } else { error; // Error diff --git a/tests/baselines/reference/inKeywordTypeguard.symbols b/tests/baselines/reference/inKeywordTypeguard.symbols index 91d9c42597740..c3c19d1b7eb91 100644 --- a/tests/baselines/reference/inKeywordTypeguard.symbols +++ b/tests/baselines/reference/inKeywordTypeguard.symbols @@ -270,7 +270,7 @@ declare const error: Error; if ('extra' in error) { >error : Symbol(error, Decl(inKeywordTypeguard.ts, 107, 13)) - error // Still Error + error // Error & { extra: unknown; } >error : Symbol(error, Decl(inKeywordTypeguard.ts, 107, 13)) } else { diff --git a/tests/baselines/reference/inKeywordTypeguard.types b/tests/baselines/reference/inKeywordTypeguard.types index cb0c545088822..2a08276b06e9d 100644 --- a/tests/baselines/reference/inKeywordTypeguard.types +++ b/tests/baselines/reference/inKeywordTypeguard.types @@ -146,13 +146,13 @@ function negativeTestClassesWithMemberMissingInBothClasses(x: AWithMethod | BWit x.a(); >x.a() : any >x.a : any ->x : never +>x : (AWithMethod | BWithMethod) & { c: unknown; } >a : any x.b(); >x.b() : any >x.b : any ->x : never +>x : (AWithMethod | BWithMethod) & { c: unknown; } >b : any } else { @@ -331,8 +331,8 @@ if ('extra' in error) { >'extra' : "extra" >error : Error - error // Still Error ->error : Error + error // Error & { extra: unknown; } +>error : Error & { extra: unknown; } } else { error // Error diff --git a/tests/cases/compiler/inKeywordTypeguard.ts b/tests/cases/compiler/inKeywordTypeguard.ts index e853d1935bee7..859459d1fd366 100644 --- a/tests/cases/compiler/inKeywordTypeguard.ts +++ b/tests/cases/compiler/inKeywordTypeguard.ts @@ -107,7 +107,7 @@ function positiveIntersectionTest(x: { a: string } & { b: string }) { // Repro from #38608 declare const error: Error; if ('extra' in error) { - error // Still Error + error // Error & { extra: unknown; } } else { error // Error } diff --git a/tests/cases/conformance/controlFlow/controlFlowInOperator.ts b/tests/cases/conformance/controlFlow/controlFlowInOperator.ts index 5dc27c45e8692..956626752d55b 100644 --- a/tests/cases/conformance/controlFlow/controlFlowInOperator.ts +++ b/tests/cases/conformance/controlFlow/controlFlowInOperator.ts @@ -2,6 +2,8 @@ const a = 'a'; const b = 'b'; const d = 'd'; +// Type narrowing + type A = { [a]: number; }; type B = { [b]: string; }; @@ -9,18 +11,202 @@ declare const c: A | B; if ('a' in c) { c; // A - c['a']; // number; + c['a']; // number +} else { + c; // B + c['b'] // string } if ('d' in c) { - c; // never + c; // (A | B) & { d: unknown; } +} else { + c; // (A | B) } if (a in c) { c; // A c[a]; // number; +} else { + c; // B + c[b] // string } if (d in c) { - c; // never + c; // (A | B) & { d: unknown; } +} else { + c; // (A | B) +} + +// Type widening + +declare const e: {}; + +if ('a' in e) { + e; // { a: unknown; } + e['a'] // unknown +} else { + e; // {} +} + +if (a in e) { + e; // { a: unknown; } + e[a] // unknown +} else { + e; // {} +} + +// Widening different types + +declare const e1: any; +if ('a' in e1) { + e1; // any +} + +declare const e2: object; +if ('a' in e2) { + e2; // object & { a: unknown; } + e2['a']; // unknown +} + +declare const e3: { b: string; } & { c: number; }; +if ('a' in e3) { + e3; // { a: unknown; b: string; } & { c: number } + e3['a']; // unknown +} + +interface C { + cProp: string; +} +interface D { + dProp: number; +} +declare const e4: C & D; +if ('a' in e4) { + e4; // C & D & { a: unknown; } + e4['a']; // unknown +} + +declare const e5: never; +if ('a' in e5) { + e5; // never +} + +declare const e6: { b: string; (arg: string): boolean; } +if ('a' in e6) { + e6; // { a: unknown; b: string; (arg: string): boolean; } + e6['a']; // unknown + e6(''); // boolean; +} + +declare const e7: { b: string; new (arg: string): boolean; } +if ('a' in e7) { + e7; // { a: unknown; b: string; new (arg: string): boolean; } + e7['a']; // unknown + new e7(''); // boolean; +} + +declare const e8: { b: string; [index: number]: boolean; } +if ('a' in e8) { + e8; // { a: unknown; b: string; [index: number]: boolean; } + e8['a']; // unknown + e8[42]; // boolean; +} + +// More complex control flows + +e; // {} +if ( 'a' in e ) { + e; // { a: unknown; } + if ( 'b' in e ) { + e; // { a: unknown; b: unknown; } + e['a']; // unknown + e['b']; // unknown + } else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} + +e; // {} +if ( a in e ) { + e; // { a: unknown; } + if ( b in e ) { + e; // { a: unknown; b: unknown; } + e[a]; // unknown + e[b]; // unknown + } else { + e; // { a: unknown; } + } + e; // { a: unknown; } +} +e; // {} + +e; // {} +if ( 'a' in e ) { + e; // { a: unknown; } + e['a']; // unknown +} else if ( 'b' in e ) { + e; // { b: unknown; } + e['b']; // unknown +} else { + e; // {} +} +e; // {} + +e; // {} +if ( a in e ) { + e; // { a: unknown; } + e[a]; // unknown +} else if ( b in e ) { + e; // { b: unknown; } + e[b]; // unknown +} else { + e; // {} +} +e; // {} + +declare const f: Array<{}> + +for (const g of f) { + g; // {} + if ('a' in g) { + g; // { a: unknown; } + g['a']; // unknown + } + g; // {} +} + +for (const g of f) { + g; // {} + if (a in g) { + g; // { a: unknown; } + g[a]; // unknown + } + g; // {} +} + +function h(i: {}) { + if ( 'a' in i ) { + i; // { a: unknown; } + } else if ( 'b' in i ) { + i; // { b: unknown; } + } else { + return; + } + i; // { a: unknown; } | { b: unknown; } +} +h(e); + +declare const j: { a: 'first'; b: string; } | { a: 'second'; c: string; }; + +if (j.a === 'first') { + j; // { a: 'first'; b: string; } + j['b']; // string + if ( 'c' in j ) { + j; // { a: 'first'; b: string; c: unknown; } + j['b']; // string + j['c']; // unknown + } } +j; // { a: 'first'; b: string; } | { a: 'second'; c: string; }