diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2ca07e30f287b..2a5128b015112 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -197,6 +197,7 @@ import { findBestPatternMatch, findConstructorDeclaration, findIndex, + findIterator, findLast, findLastIndex, findUseStrictPrologue, @@ -996,6 +997,7 @@ import { skipTypeChecking, skipTypeParentheses, some, + someIterator, SourceFile, SpreadAssignment, SpreadElement, @@ -1297,6 +1299,7 @@ const enum TypeSystemPropertyName { ResolvedBaseTypes, WriteType, ParameterInitializerContainsUndefined, + ResolvedProperties, } // dprint-ignore @@ -10985,6 +10988,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); } @@ -14464,26 +14469,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, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); - if (combinedProp) { - members.set(prop.escapedName, combinedProp); + for (const _ of iteratePropertiesOfUnionOrIntersectionType(type, /*skipYield*/ true)) { + Debug.fail("Iterator should have been empty."); + } + return type.resolvedProperties!; + } + + function* iteratePropertiesOfUnionOrIntersectionType(type: UnionOrIntersectionType, skipYield = false) { + if (type.resolvedProperties) { + if (!skipYield) { + yield* type.resolvedProperties; + } + return; + } + + if (!skipYield && type.partiallyResolvedProperties) { + yield* type.partiallyResolvedProperties; + } + + if (!pushTypeResolution(type, TypeSystemPropertyName.ResolvedProperties)) { + 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 (!skipYield) { + 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, /*skipObjectFunctionPropertyAugment*/ !!(type.flags & TypeFlags.Intersection)); + 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[] { @@ -15187,7 +15236,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; } @@ -15242,11 +15291,11 @@ 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)); } @@ -23998,7 +24047,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)) { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 977da801ed946..8e83f3eeea9ab 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -176,6 +176,20 @@ export function find(array: readonly T[] | undefined, predicate: (element: T, return undefined; } +/** @internal */ +export function findIterator(iter: Iterable, 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(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined; /** @internal */ @@ -648,6 +662,16 @@ export function some(array: readonly T[] | undefined, predicate?: (value: T) return false; } +/** @internal */ +export function someIterator(iter: Iterable, 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. * diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 33f1b459215f6..54bdf7ba2520a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6535,13 +6535,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; } export interface UnionType extends UnionOrIntersectionType {