Skip to content
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

in operator typeguard can widen types #46403

Closed
wants to merge 86 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
5a41201
not escape unicode char for import path string.
ShuiRuTian Jul 7, 2020
775fd9c
Merge remote-tracking branch 'upstream/master'
ShuiRuTian Jul 7, 2020
b974add
beta version.
ShuiRuTian Jul 24, 2020
49b574b
beta version.
ShuiRuTian Jul 25, 2020
734f4c2
fix.
ShuiRuTian Jul 25, 2020
b6cbb3e
revert code for debug.
ShuiRuTian Jul 25, 2020
d6cabee
fix some condition.
ShuiRuTian Jul 26, 2020
48b27e7
clearer code.
ShuiRuTian Jul 28, 2020
5936e27
fix
ShuiRuTian Jul 28, 2020
02ff40a
fix
ShuiRuTian Jul 28, 2020
6ec0edc
Merge branch 'master' into in-operator-add-property
ShuiRuTian Sep 4, 2020
79e81be
immutatble.
ShuiRuTian Sep 4, 2020
c2fc19e
revert some strange format change.
ShuiRuTian Sep 4, 2020
a5d17f3
correct test baseline.
ShuiRuTian Sep 4, 2020
540545a
Merge branch 'master' into in-operator-add-property
ShuiRuTian Sep 9, 2020
b6f36cd
fix test baseline
ShuiRuTian Sep 9, 2020
13b276c
fix lint.
ShuiRuTian Sep 9, 2020
84f5eeb
more clean code.
ShuiRuTian Oct 28, 2020
f2ee389
Merge branch 'main' into in-operator-add-property
marekdedic Oct 16, 2021
758d8df
Updated in keyword type widening to reflect changes in master
marekdedic Oct 16, 2021
d10b8d3
Grammar fix (widden -> widen)
marekdedic Oct 16, 2021
e245b88
Accepted baselines with in keyword type widening
marekdedic Oct 17, 2021
c79a1fd
Accepted comments into baseline
marekdedic Oct 17, 2021
f8586b8
Lint fixes
marekdedic Oct 17, 2021
3e70f10
More sophisticated tests for in operator type widening
marekdedic Oct 19, 2021
5def93a
Added more (failing) test cases for in typeguard type widening
marekdedic Oct 19, 2021
26a982b
Modified TS2361 to allow unknown on right-hand side
marekdedic Oct 19, 2021
e93b2dc
Revert "Modified TS2361 to allow unknown on right-hand side"
marekdedic Oct 21, 2021
7d49f33
Working less ambitious use of in operator widening
marekdedic Oct 21, 2021
728a8a3
Added tests for in operator type widening in complex control flows
marekdedic Oct 21, 2021
47d206e
Added tests for in operator type widening in yet more complex control…
marekdedic Oct 21, 2021
a29c5a5
WidenedByNarrow -> WidenedByIn
marekdedic Oct 21, 2021
5bcc2e6
createWidenType -> createWidenedType
marekdedic Oct 21, 2021
78a50e9
Updated intersection type code to better match master
marekdedic Oct 21, 2021
f7bcee0
isSomeDirectSubtypeContainsPropName -> someDirectSubtypeContainsPropName
marekdedic Oct 21, 2021
2015750
Simplified intersection type widening by widening first anonymous type
marekdedic Oct 21, 2021
08dcd94
Deduplicated code from someDirectSubtypeContainsPropName and merged i…
marekdedic Oct 22, 2021
9da464b
Fixed commit 08dcd94
marekdedic Oct 22, 2021
94b9311
Split out widenObjectType function
marekdedic Oct 22, 2021
a47d9b2
In operator injecting properties directly into object types
marekdedic Oct 22, 2021
5594882
Only adding properties directly to anonymous object types
marekdedic Oct 22, 2021
b9f6aaf
Accepted test baselines for test where property order changed
marekdedic Oct 22, 2021
4b4e63b
Reorganized and simplified widenTypeWithSymbol()
marekdedic Oct 22, 2021
fbd0454
Added more in operator tests
marekdedic Oct 22, 2021
6b7b0b1
Removed dead code
marekdedic Oct 22, 2021
225f5a1
Simplified controlFlowInOperator unit tests
marekdedic Oct 22, 2021
1f5c6c5
Reorganized and documented the logic of narrowOrWidenTypeByInKeyword
marekdedic Oct 23, 2021
c7641f1
Revert #38610 as it is no longer needed
marekdedic Oct 23, 2021
704ae12
Accepted tests baselines
marekdedic Oct 24, 2021
83fda77
In operator not touching globalThis
marekdedic Oct 24, 2021
d739a5a
Split filterUnionOrIntersectionType from filterType and deduplicated it
marekdedic Oct 24, 2021
b75b129
Split out typeTests
marekdedic Oct 24, 2021
624f586
Simplified logic around globalThis for in operator
marekdedic Oct 24, 2021
e53fb8a
Marked type tests as internal
marekdedic Oct 24, 2021
8bc9ef4
Added in operator unit test with call signature
marekdedic Oct 24, 2021
5eb7dcd
More thorough tests
marekdedic Oct 24, 2021
6010fff
Added in operator unit test with index signature
marekdedic Oct 24, 2021
caab233
Accepted test baseline
marekdedic Oct 24, 2021
762e56b
Simplified object flag checking, thanks @jakebailey
marekdedic Jan 14, 2022
b16a4a1
Reverted instersectionTypes back to Map<string, Type>
marekdedic Jan 14, 2022
ae755aa
Merge branch 'master' into in-operator-add-property
marekdedic Mar 6, 2022
b1fadcd
Update package-lock.json
typescript-bot Mar 12, 2022
46e7bc6
Update package-lock.json
typescript-bot Mar 14, 2022
efdcc0d
Update package-lock.json
typescript-bot Mar 22, 2022
0bbe651
Update package-lock.json
typescript-bot Mar 24, 2022
58e9420
Merge branch 'main' of github.com:marekdedic/TypeScript
marekdedic Mar 26, 2022
aca5344
Merge branch 'main' into in-operator-add-property
marekdedic Mar 26, 2022
9c681b6
reverted quickInfoImportNonunicodePath test changes
marekdedic Mar 26, 2022
acf47c8
Removed dead code
marekdedic Mar 26, 2022
7293a62
Removed the need for includePartialProperties
marekdedic Mar 26, 2022
8c3c2ac
Dissolved function isUnionOrIntersectionType
marekdedic Mar 26, 2022
dfe51a4
Dissolved function isUnionType
marekdedic Mar 26, 2022
5c32123
Dissolved function isIntersectionType
marekdedic Mar 26, 2022
d4be592
Dissolved function isObjectType
marekdedic Mar 26, 2022
2f997b5
Update package-lock.json
typescript-bot Apr 12, 2022
ed9eea3
Update package-lock.json
typescript-bot Apr 14, 2022
02c8b2d
Merge branch 'main' of https://github.com/microsoft/TypeScript
marekdedic Apr 14, 2022
75dc33e
Merge branch 'main' of github.com:marekdedic/TypeScript
marekdedic Apr 14, 2022
1b82d01
Update package-lock.json
typescript-bot Apr 15, 2022
f5555df
Update package-lock.json
typescript-bot Apr 19, 2022
ca14f27
Update package-lock.json
typescript-bot Apr 22, 2022
13d2150
Update package-lock.json
typescript-bot Apr 25, 2022
c891690
Update package-lock.json
typescript-bot Apr 26, 2022
2b60aa8
Merge branch 'main' of https://github.com/microsoft/TypeScript
marekdedic Apr 26, 2022
30f89ae
Merge branch 'main' of github.com:marekdedic/TypeScript
marekdedic Apr 26, 2022
ec41cbb
Merge branch 'main' into in-operator-add-property
marekdedic Apr 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 93 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/conditionalTypeDoesntSpinForever.types
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ export enum PubSubRecordIsStoredInRedisAsA {
>buildPubSubRecordType(Object.assign({}, soFar, {identifier: instance as TYPE}) as SO_FAR & {identifier: TYPE}) : BuildPubSubRecordType<SO_FAR & { identifier: TYPE; }>
>buildPubSubRecordType : <SO_FAR>(soFar: SO_FAR) => BuildPubSubRecordType<SO_FAR>
>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 : { <T extends {}, U>(target: T, source: U): T & U; <T extends {}, U, V>(target: T, source1: U, source2: V): T & U & V; <T extends {}, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
>Object : ObjectConstructor
>assign : { <T extends {}, U>(target: T, source: U): T & U; <T extends {}, U, V>(target: T, source1: U, source2: V): T & U & V; <T extends {}, U, V, W>(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
Expand Down Expand Up @@ -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<keyof SO_FAR>; hasField: (fieldName: string | number | symbol) => boolean; }

Expand Down
Loading