@@ -17773,6 +17773,13 @@ namespace ts {
17773
17773
return result;
17774
17774
}
17775
17775
17776
+ function getInstanceOfAliasOrReferenceWithMarker(input: Type, typeArguments: readonly Type[]) {
17777
+ const s = input.aliasSymbol ? getTypeAliasInstantiation(input.aliasSymbol, typeArguments) : createTypeReference((<TypeReference>input).target, typeArguments);
17778
+ if (s.aliasSymbol) s.aliasTypeArgumentsContainsMarker = true;
17779
+ else (<TypeReference>s).objectFlags |= ObjectFlags.MarkerType;
17780
+ return s;
17781
+ }
17782
+
17776
17783
function structuredTypeRelatedToWorker(source: Type, target: Type, reportErrors: boolean, intersectionState: IntersectionState): Ternary {
17777
17784
if (intersectionState & IntersectionState.PropertyCheck) {
17778
17785
return propertiesRelatedTo(source, target, reportErrors, /*excludedProperties*/ undefined, IntersectionState.None);
@@ -17861,6 +17868,67 @@ namespace ts {
17861
17868
}
17862
17869
}
17863
17870
17871
+ // If a more _general_ version of the source and target are being compared, consider them related with assumptions
17872
+ // eg, if { x: Q } and { x: Q, y: A } are being compared and we're about to look at { x: Q' } and { x: Q', y: A } where Q'
17873
+ // is some specialization or subtype of Q
17874
+ // This is difficult to detect generally, so we scan for prior comparisons of the same instantiated type, and match up matching
17875
+ // type arguments into sets to create a canonicalization based on those matches
17876
+ if (relation !== identityRelation && ((source.aliasSymbol && !source.aliasTypeArgumentsContainsMarker && source.aliasTypeArguments) || (getObjectFlags(source) & ObjectFlags.Reference && !!getTypeArguments(<TypeReference>source).length && !(getObjectFlags(source) & ObjectFlags.MarkerType))) &&
17877
+ ((target.aliasSymbol && !target.aliasTypeArgumentsContainsMarker && target.aliasTypeArguments) || (getObjectFlags(target) & ObjectFlags.Reference && !!getTypeArguments(<TypeReference>target).length && !(getObjectFlags(target) & ObjectFlags.MarkerType)))) {
17878
+ if (source.aliasSymbol || target.aliasSymbol || (<TypeReference>source).target !== (<TypeReference>target).target) { // ensure like symbols are just handled by standard variance analysis
17879
+ const sourceTypeArguments = source.aliasTypeArguments || getTypeArguments(<TypeReference>source);
17880
+ const sourceHasMarker = some(sourceTypeArguments, a => a === markerOtherType);
17881
+ const targetTypeArguments = target.aliasTypeArguments || getTypeArguments(<TypeReference>target);
17882
+ const targetHasMarker = some(targetTypeArguments, a => a === markerOtherType);
17883
+ // We're using `markerOtherType` as an existential, so we can't use it again if it's already in use,
17884
+ // as we'd get spurious equivalencies - we'd need to use a second existential type, and once we're doing
17885
+ // that we lose a lot of the benefit of canonicalizing back to a single-existential comparison, since then
17886
+ // we'd need to manufacture new type identities for every new existential we make
17887
+ // The above checks don't catch all cases this can occur, as they can only detect when the containing type
17888
+ // was flagged during construction as containing a marker; however if a marker enters a type through instantiation
17889
+ // we need to catch that here.
17890
+ // We only do this when there's a handful of possible ways to match the type parameters up, as otherwise we manufacture
17891
+ // an inordinate quantity of types just to calculate their IDs!
17892
+ if (!sourceHasMarker && !targetHasMarker && sourceTypeArguments.length * targetTypeArguments.length < 10) {
17893
+ const originalKey = getRelationKey(source, target, intersectionState, relation);
17894
+ for (let i = 0; i < sourceTypeArguments.length; i++) {
17895
+ for (let j = 0; j < targetTypeArguments.length; j++) {
17896
+ if ((!(sourceTypeArguments[i].flags & TypeFlags.TypeParameter) && !isTypeAny(sourceTypeArguments[i]) && sourceTypeArguments[i] === targetTypeArguments[j]) ||
17897
+ // Similarly, if we're comparing X<Q> to Z<any>, X<Q> is assignable to Z<any> trivially if X<?> is assignable to Z<?>
17898
+ (!(sourceTypeArguments[i].flags & TypeFlags.TypeParameter) && isTypeAny(targetTypeArguments[j])) ||
17899
+ // Again, but for `X<any>` vs `Z<Q>`
17900
+ (isTypeAny(sourceTypeArguments[i]) && !(targetTypeArguments[j].flags & TypeFlags.TypeParameter)) ||
17901
+ // Likewise, if we're comparing X<U> to Z<U> and are already comparing X<T> to Z<T>, we can assume it to be true
17902
+ !!(sourceTypeArguments[i].flags & TypeFlags.TypeParameter) && sourceTypeArguments[i] === targetTypeArguments[j]) {
17903
+ const sourceClone = sourceTypeArguments.slice();
17904
+ sourceClone[i] = markerOtherType;
17905
+ const s = getInstanceOfAliasOrReferenceWithMarker(source, sourceClone);
17906
+ const targetClone = targetTypeArguments.slice();
17907
+ targetClone[j] = markerOtherType;
17908
+ const t = getInstanceOfAliasOrReferenceWithMarker(target, targetClone);
17909
+ // If the marker-instantiated form looks "the same" as the type we already have (eg,
17910
+ // because we replace unconstrained generics with unconstrained generics), skip the check
17911
+ // since we'll otherwise deliver a spurious `Maybe` result from the key _just_ set upon
17912
+ // entry into `recursiveTypeRelatedTo`
17913
+ const existentialKey = getRelationKey(s, t, intersectionState, relation);
17914
+ if (existentialKey !== originalKey) {
17915
+ // We don't actually trigger the comparison, since we'd rather not do an extra comparison
17916
+ // if we haven't already started that more general comparison; instead we just look for the
17917
+ // key in the maybeKeys stack
17918
+ for (let i = 0; i < maybeCount; i++) {
17919
+ // If source and target are already being compared, consider them related with assumptions
17920
+ if (existentialKey === maybeKeys[i]) {
17921
+ return Ternary.Maybe;
17922
+ }
17923
+ }
17924
+ }
17925
+ }
17926
+ }
17927
+ }
17928
+ }
17929
+ }
17930
+ }
17931
+
17864
17932
// For a generic type T and a type U that is assignable to T, [...U] is assignable to T, U is assignable to readonly [...T],
17865
17933
// and U is assignable to [...T] when U is constrained to a mutable array or tuple type.
17866
17934
if (isSingleElementGenericTupleType(source) && !source.target.readonly && (result = isRelatedTo(getTypeArguments(source)[0], target)) ||
0 commit comments