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

Use iterators to avoid calculating all properties of UnionOrIntersectionType #53346

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c53294b
Avoid calculating all properties of UnionOrIntersectionType when call…
jakebailey Mar 19, 2023
9c75a5f
Make code clearer, skip even more work
jakebailey Mar 19, 2023
8a6ba6d
Fix wording
jakebailey Mar 19, 2023
9c09e00
Flip skipYield to doYield
jakebailey Mar 19, 2023
2a48492
While I'm here, remove some lies
jakebailey Mar 19, 2023
47fe912
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Mar 19, 2023
7c97848
Fix findIterator
jakebailey Mar 19, 2023
b8d3664
Use a Set
jakebailey Mar 19, 2023
21ccbb9
Move out into worker, which seems to be faster
jakebailey Mar 19, 2023
ae6df30
Clearer semantics
jakebailey Mar 20, 2023
e6be1d4
It's just getting more and more complicated
jakebailey Mar 20, 2023
36617b9
Fix lint, for now
jakebailey Mar 20, 2023
eff54cc
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Mar 27, 2023
085a116
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Mar 27, 2023
cffe7f3
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Apr 7, 2023
6e98698
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Aug 22, 2023
391f090
dprint
jakebailey Aug 22, 2023
feb6dd8
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Aug 24, 2023
6148cde
Small tweak to make default clearer
jakebailey Aug 24, 2023
7a3f239
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Sep 30, 2023
1e89756
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Jan 20, 2024
5bbfc07
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Mar 24, 2024
a16ac3f
Merge branch 'main' into lazier-properties-of-union-or-intersection
jakebailey Jun 27, 2024
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
87 changes: 68 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ import {
findAncestor,
findBestPatternMatch,
findIndex,
findIterator,
findLast,
findLastIndex,
findUseStrictPrologue,
Expand Down Expand Up @@ -948,6 +949,7 @@ import {
skipTrivia,
skipTypeChecking,
some,
someIterator,
SourceFile,
SpreadAssignment,
SpreadElement,
Expand Down Expand Up @@ -1239,6 +1241,7 @@ const enum TypeSystemPropertyName {
ResolvedBaseTypes,
WriteType,
ParameterInitializerContainsUndefined,
ResolvedProperties,
}

/** @internal */
Expand Down Expand Up @@ -10127,6 +10130,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return !!getSymbolLinks(target as Symbol).writeType;
case TypeSystemPropertyName.ParameterInitializerContainsUndefined:
return getNodeLinks(target as ParameterDeclaration).parameterInitializerContainsUndefined !== undefined;
case TypeSystemPropertyName.ResolvedProperties:
return !!(target as UnionOrIntersectionType).resolvedProperties;
}
return Debug.assertNever(propertyName);
}
Expand Down Expand Up @@ -13465,26 +13470,70 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function getPropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType): Symbol[] {
if (!type.resolvedProperties) {
const members = createSymbolTable();
for (const current of type.types) {
for (const prop of getPropertiesOfType(current)) {
if (!members.has(prop.escapedName)) {
const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName);
if (combinedProp) {
members.set(prop.escapedName, combinedProp);
for (const _ of iteratePropertiesOfUnionOrIntersectionType(type, /*doYield*/ false)) {
Debug.fail("Iterator should have been empty.");
}
return type.resolvedProperties!;
}

function* iteratePropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType, doYield = true) {
if (type.resolvedProperties) {
if (doYield) {
yield* type.resolvedProperties;
}
return;
}

if (doYield && type.partiallyResolvedProperties) {
yield* type.partiallyResolvedProperties;
}

if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedProperties)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

The fact that we didn't have this before is... spooky. Not sure what to even do here.

Copy link
Member

Choose a reason for hiding this comment

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

We probably didn't bother, since, by construction, you can only have a union at the top level, followed by an intersection, and then none of either "deeper", so it's impossible for the actual structure of the type to recur. (And this types are too complicated to worry about most of the time.) Now, denormalized unions/intersections also make that less true, but we don't use denormalized types for anything other than type printback, so that's probably why it was usually fine.

return;
}

// If we have a generator already, pick up where we left off.
// Otherwise, we haven't started, so create a new one.
try {
const generator = type.partiallyResolvedPropertiesGenerator ??= iteratePropertiesOfUnionOrIntersectionTypeWorker(type);
for (const symbol of generator) {
type.partiallyResolvedProperties = append(type.partiallyResolvedProperties, symbol);
if (doYield) {
yield symbol;
}
}
}
finally {
// Ensure we pop when we're all done iterating. If done outside of a finally,
// this code won't actually execute.
popTypeResolution();
}

type.resolvedProperties = type.partiallyResolvedProperties ?? emptyArray;
type.partiallyResolvedProperties = undefined;
type.partiallyResolvedPropertiesGenerator = undefined;
}

function* iteratePropertiesOfUnionOrIntersectionTypeWorker(type: UnionOrIntersectionType) {
const seenSymbols = new Set<__String>();
for (const current of type.types) {
for (const prop of getPropertiesOfType(current)) {
if (!seenSymbols.has(prop.escapedName)) {
const combinedProp = getPropertyOfUnionOrIntersectionType(type, prop.escapedName);
if (combinedProp) {
seenSymbols.add(prop.escapedName);
if (isNamedMember(combinedProp, prop.escapedName)) {
yield combinedProp;
}
}
}
// The properties of a union type are those that are present in all constituent types, so
// we only need to check the properties of the first type without index signature
if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) {
break;
}
}
type.resolvedProperties = getNamedMembers(members);
// The properties of a union type are those that are present in all constituent types, so
// we only need to check the properties of the first type without index signature
if (type.flags & TypeFlags.Union && getIndexInfosOfType(current).length === 0) {
break;
}
}
return type.resolvedProperties;
}

function getPropertiesOfType(type: Type): Symbol[] {
Expand Down Expand Up @@ -14136,7 +14185,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else if (type.flags & TypeFlags.Intersection) {
if (!((type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersectionComputed)) {
(type as IntersectionType).objectFlags |= ObjectFlags.IsNeverIntersectionComputed |
(some(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0);
(someIterator(iteratePropertiesOfUnionOrIntersectionType(type as IntersectionType), isNeverReducedProperty) ? ObjectFlags.IsNeverIntersection : 0);
}
return (type as IntersectionType).objectFlags & ObjectFlags.IsNeverIntersection ? neverType : type;
}
Expand Down Expand Up @@ -14191,12 +14240,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function elaborateNeverIntersection(errorInfo: DiagnosticMessageChain | undefined, type: Type) {
if (type.flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsNeverIntersection) {
const neverProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType);
const neverProp = findIterator(iteratePropertiesOfUnionOrIntersectionType(type as IntersectionType), isDiscriminantWithNeverType);
if (neverProp) {
return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_has_conflicting_types_in_some_constituents,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(neverProp));
}
const privateProp = find(getPropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty);
const privateProp = findIterator(iteratePropertiesOfUnionOrIntersectionType(type as IntersectionType), isConflictingPrivateProperty);
if (privateProp) {
return chainDiagnosticMessages(errorInfo, Diagnostics.The_intersection_0_was_reduced_to_never_because_property_1_exists_in_multiple_constituents_and_is_private_in_some,
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.NoTypeReduction), symbolToString(privateProp));
Expand Down Expand Up @@ -22378,7 +22427,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function membersRelatedToIndexInfo(source: Type, targetInfo: IndexInfo, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
let result = Ternary.True;
const keyType = targetInfo.keyType;
const props = source.flags & TypeFlags.Intersection ? getPropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source);
const props = source.flags & TypeFlags.Intersection ? iteratePropertiesOfUnionOrIntersectionType(source as IntersectionType) : getPropertiesOfObjectType(source);
for (const prop of props) {
// Skip over ignored JSX and symbol-named members
if (isIgnoredJsxProperty(source, prop)) {
Expand Down
25 changes: 25 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ export function find<T>(array: readonly T[] | undefined, predicate: (element: T,
return undefined;
}

/** @internal */
export function findIterator<T>(iter: Iterable<T>, predicate: (element: T, index: number) => boolean, startIndex = 0): T | undefined {
let i = 0;
for (const value of iter) {
if (i >= startIndex) {
if (predicate(value, i)) {
return value;
}
}
i++;
}
return undefined;
}


/** @internal */
export function findLast<T, U extends T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined;
/** @internal */
Expand Down Expand Up @@ -680,6 +695,16 @@ export function some<T>(array: readonly T[] | undefined, predicate?: (value: T)
return false;
}

/** @internal */
export function someIterator<T>(iter: Iterable<T>, predicate: (value: T) => boolean): boolean {
for (const v of iter) {
if (predicate(v)) {
return true;
}
}
return false;
}

/**
* Calls the callback with (start, afterEnd) index pairs for each range where 'pred' is true.
*
Expand Down
12 changes: 8 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6415,13 +6415,17 @@ export interface UnionOrIntersectionType extends Type {
/** @internal */
propertyCacheWithoutObjectFunctionPropertyAugment?: SymbolTable; // Cache of resolved properties that does not augment function or object type properties
/** @internal */
resolvedProperties: Symbol[];
resolvedProperties?: Symbol[];
/** @internal */
resolvedIndexType: IndexType;
resolvedIndexType?: IndexType;
/** @internal */
resolvedStringIndexType?: IndexType;
/** @internal */
resolvedBaseConstraint?: Type;
/** @internal */
resolvedStringIndexType: IndexType;
partiallyResolvedProperties?: Symbol[];
/** @internal */
resolvedBaseConstraint: Type;
partiallyResolvedPropertiesGenerator?: Generator<Symbol, void>;
}

export interface UnionType extends UnionOrIntersectionType {
Expand Down