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 56 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
128 changes: 106 additions & 22 deletions src/compiler/checker.ts
Expand Up @@ -741,7 +741,7 @@ namespace ts {

const tupleTypes = new Map<string, GenericType>();
const unionTypes = new Map<string, UnionType>();
const intersectionTypes = new Map<string, Type>();
const intersectionTypes = new Map<string, IntersectionType>();
const stringLiteralTypes = new Map<string, StringLiteralType>();
const numberLiteralTypes = new Map<number, NumberLiteralType>();
const bigIntLiteralTypes = new Map<string, BigIntLiteralType>();
Expand Down Expand Up @@ -12118,8 +12118,11 @@ namespace ts {
return property;
}

function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
function getPropertyOfUnionOrIntersectionType(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean, includePartialProperties?: boolean): Symbol | undefined {
const property = getUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment);
if (includePartialProperties) {
return property;
}
// We need to filter out partial properties in union types
return property && !(getCheckFlags(property) & CheckFlags.ReadPartial) ? property : undefined;
}
Expand Down Expand Up @@ -12197,7 +12200,7 @@ namespace ts {
* @param type a type to look up property from
* @param name a name of property to look up in a given type
*/
function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
function getPropertyOfType(type: Type, name: __String, skipObjectFunctionPropertyAugment?: boolean, includePartialProperties?: boolean): Symbol | undefined {
type = getReducedApparentType(type);
if (type.flags & TypeFlags.Object) {
const resolved = resolveStructuredTypeMembers(type as ObjectType);
Expand All @@ -12219,7 +12222,7 @@ namespace ts {
return getPropertyOfObjectType(globalObjectType, name);
}
if (type.flags & TypeFlags.UnionOrIntersection) {
return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment);
return getPropertyOfUnionOrIntersectionType(type as UnionOrIntersectionType, name, skipObjectFunctionPropertyAugment, includePartialProperties);
}
return undefined;
}
Expand Down Expand Up @@ -14346,6 +14349,31 @@ 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);
let type = intersectionTypes.get(id);
if (!type) {
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 @@ -14585,7 +14613,7 @@ namespace ts {
return typeSet[0];
}
const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, aliasTypeArguments);
let result = intersectionTypes.get(id);
let result: Type | undefined = intersectionTypes.get(id);
if (!result) {
if (includes & TypeFlags.Union) {
if (intersectUnionsOfPrimitiveTypes(typeSet)) {
Expand Down Expand Up @@ -14620,7 +14648,7 @@ namespace ts {
else {
result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
}
intersectionTypes.set(id, result);
intersectionTypes.set(id, result as IntersectionType);
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
}
return result;
}
Expand Down Expand Up @@ -23074,14 +23102,14 @@ namespace ts {
return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type);
}

function filterType(type: Type, f: (t: Type) => boolean): 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;
function filterUnionOrIntersectionType(type: UnionOrIntersectionType, f: (t: Type) => boolean): Type {
const types = type.types;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably make a separate helper for filtering intersections - if you even still need it after swapping to using getSpreadType - rather then repurposing filterType. most of filterType's callers definitely don't want to filter intersection constituents.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, hopefully, the change won't be needed altogether with the introduction of getSpreadType (I use it in one other place, but it isn't necessary I think)

const filtered = filter(types, f);
if (filtered === types) {
return type;
}
if (isUnionType(type)) {
const origin = type.origin;
let newOrigin: Type | undefined;
if (origin && origin.flags & TypeFlags.Union) {
// If the origin type is a (denormalized) union type, filter its non-union constituents. If that ends
Expand All @@ -23098,7 +23126,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 (isUnionType(type)) {
return filterUnionOrIntersectionType(type, f);
}
return type.flags & TypeFlags.Never || f(type) ? type : neverType;
}
Expand Down Expand Up @@ -24062,12 +24097,61 @@ 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 (isObjectType(type) && type.objectFlags & ObjectFlags.Anonymous) {
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
return widenObjectType(type, newSymbol);
}
// If type is intersection, add the symbol to the first anonymous object component of the intersection
if (isIntersectionType(type)) {
const objectSubtype = type.types.find(t => isObjectType(t) && t.objectFlags & ObjectFlags.Anonymous) as ObjectType | undefined;
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
if (objectSubtype) {
const restOfIntersection = filterUnionOrIntersectionType(type, 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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @weswigham
I was directed to your comment, however, I don't really know what to do with it. Ideally, I think I would like to clone the symbol table and just add the new symbol to it (that way there are no merges), but I can't figure out how to clone a symbol table :/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I think I would like to clone the symbol table and just add the new symbol to it (that way there are no merges), but I can't figure out how to clone a symbol table :/

Don't merge the types :)

Instead, just make an anonymous type with a single member - the new one - and use getSpreadType to combine the original type with that new one. It'll combine directly where it can, and intersect otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially everything you're doing by hand here in the last 3/4ths of widenTypeWithSymbol, but already done for you. 😊

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,
I tried to do it this way:

-                    return widenTypeWithSymbol(type, addSymbol);
+                    const members = createSymbolTable([addSymbol]);
+                    const addType = createAnonymousType(undefined, members, emptyArray, emptyArray, emptyArray);
+                    return getSpreadType(type, addType, undefined, getObjectFlags(type), false);

It's a really neat solution that removes the entire widenTypeWithSymbol function, thanks. However, it discards all call signatures of type! Looking at the getSpreadType source, I could probably modify that to keep them, but I'm not sure that wouldn't break other stuff.

See it in action here

}
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
|| isUnionOrIntersectionType(type) && filterUnionOrIntersectionType(type, t => t.symbol === globalThisSymbol) !== neverType
) {
return type;
}
const someDirectSubtypeContainsProp = getPropertyOfType(type, name, /* skipObjectFunctionPropertyAugment */ false, /* includePartialProperties */ true);
if (someDirectSubtypeContainsProp) {
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
// If union, filter out all components not containing the property
// Otherwise, either return the type or never
if (type.flags & (TypeFlags.Object | TypeFlags.UnionOrIntersection)
|| isThisTypeParameter(type)
) {
return filterType(type, t => isTypePresencePossible(t, name, assumeTrue));
return isTypePresencePossible(type, name, assumeTrue) ? type : neverType;
}
}
// only widen property when the type does not contain string-index/name in any of the constituents.
if (assumeTrue && !someDirectSubtypeContainsProp && !getIndexInfoOfType(type, stringType)) {
const addSymbol = createSymbol(SymbolFlags.Property, name);
addSymbol.type = unknownType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should cache these symbols (and the resulting properties and single-property-types) globally within the checker. Object types aren't structurally cached, so it needs to be done explicitly when the type is constructed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do that once the discussion about this implementation is resolved

return widenTypeWithSymbol(type, addSymbol);
}
return type;
}
Expand Down Expand Up @@ -24136,7 +24220,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
1 change: 1 addition & 0 deletions src/compiler/tsconfig.json
Expand Up @@ -44,6 +44,7 @@
"checker.ts",
"visitorPublic.ts",
"sourcemap.ts",
"typeTests.ts",
"transformers/utilities.ts",
"transformers/destructuring.ts",
"transformers/taggedTemplate.ts",
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/typeTests.ts
@@ -0,0 +1,18 @@
/* @internal */
namespace ts {
marekdedic marked this conversation as resolved.
Show resolved Hide resolved
export function isUnionOrIntersectionType(type: Type): type is UnionOrIntersectionType {
return !!(type.flags & TypeFlags.UnionOrIntersection);
}

export function isUnionType(type: Type): type is UnionType {
return !!(type.flags & TypeFlags.Union);
}

export function isIntersectionType(type: Type): type is IntersectionType {
return !!(type.flags & TypeFlags.Intersection);
}

export function isObjectType(type: Type): type is ObjectType {
return !!(type.flags & TypeFlags.Object);
}
}
10 changes: 5 additions & 5 deletions tests/baselines/reference/conditionalTypeDoesntSpinForever.types
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, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; }
>Object : ObjectConstructor
>assign : { <T, U>(target: T, source: U): T & U; <T, U, V>(target: T, source1: U, source2: V): T & U & V; <T, 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