Skip to content

Commit 466bcb2

Browse files
committed
Always fully compute variances structurally
1 parent d059791 commit 466bcb2

File tree

2 files changed

+58
-98
lines changed

2 files changed

+58
-98
lines changed

src/compiler/checker.ts

+37-75
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,6 @@ namespace ts {
346346
let instantiationCount = 0;
347347
let instantiationDepth = 0;
348348
let inlineLevel = 0;
349-
let varianceLevel = 0;
350-
let nestedVarianceSymbols: Symbol[] | undefined;
351-
let incompleteVariancesObserved = false;
352349
let currentNode: Node | undefined;
353350

354351
const emptySymbols = createSymbolTable();
@@ -16629,6 +16626,7 @@ namespace ts {
1662916626
return result;
1663016627
}
1663116628

16629+
1663216630
function getObjectTypeInstantiation(type: AnonymousType | DeferredTypeReference, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
1663316631
const declaration = type.objectFlags & ObjectFlags.Reference ? (type as TypeReference).node! :
1663416632
type.objectFlags & ObjectFlags.InstantiationExpressionType ? (type as InstantiationExpressionType).node :
@@ -16686,15 +16684,16 @@ namespace ts {
1668616684
}
1668716685

1668816686
function isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node) {
16689-
// If the type parameter doesn't have exactly one declaration, if there are invening statement blocks
16690-
// between the node and the type parameter declaration, if the node contains actual references to the
16691-
// type parameter, or if the node contains type queries, we consider the type parameter possibly referenced.
16692-
if (tp.symbol && tp.symbol.declarations && tp.symbol.declarations.length === 1) {
16693-
const container = tp.symbol.declarations[0].parent;
16694-
for (let n = node; n !== container; n = n.parent) {
16687+
// If there are intervening statement blocks between the node and the type parameter declaration, if the node
16688+
// contains actual references to the type parameter, or if the node contains type queries, we consider the
16689+
// type parameter possibly referenced.
16690+
if (tp.symbol && tp.symbol.declarations) {
16691+
let n = node;
16692+
while (!some(tp.symbol.declarations, d => n === d.parent)) {
1669516693
if (!n || n.kind === SyntaxKind.Block || n.kind === SyntaxKind.ConditionalType && forEachChild((n as ConditionalTypeNode).extendsType, containsReference)) {
1669616694
return true;
1669716695
}
16696+
n = n.parent;
1669816697
}
1669916698
return containsReference(node);
1670016699
}
@@ -19052,16 +19051,13 @@ namespace ts {
1905219051
// We limit alias variance probing to only object and conditional types since their alias behavior
1905319052
// is more predictable than other, interned types, which may or may not have an alias depending on
1905419053
// the order in which things were checked.
19055-
if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
19056-
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
19057-
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
19054+
if (sourceFlags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
1905819055
const variances = getAliasVariances(source.aliasSymbol);
19059-
if (variances === emptyArray) {
19060-
return Ternary.Unknown;
19061-
}
19062-
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
19063-
if (varianceResult !== undefined) {
19064-
return varianceResult;
19056+
if (variances !== emptyArray) {
19057+
const varianceResult = relateVariances(source.aliasTypeArguments, target.aliasTypeArguments, variances, intersectionState);
19058+
if (varianceResult !== undefined) {
19059+
return varianceResult;
19060+
}
1906519061
}
1906619062
}
1906719063

@@ -19432,26 +19428,21 @@ namespace ts {
1943219428
else if (isGenericMappedType(source)) {
1943319429
return Ternary.False;
1943419430
}
19435-
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target &&
19436-
!isTupleType(source) && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) {
19431+
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source as TypeReference).target === (target as TypeReference).target && !isTupleType(source)) {
1943719432
// When strictNullChecks is disabled, the element type of the empty array literal is undefinedWideningType,
1943819433
// and an empty array literal wouldn't be assignable to a `never[]` without this check.
1943919434
if (isEmptyArrayLiteralType(source)) {
1944019435
return Ternary.True;
1944119436
}
19442-
// We have type references to the same generic type, and the type references are not marker
19443-
// type references (which are intended by be compared structurally). Obtain the variance
19444-
// information for the type parameters and relate the type arguments accordingly.
19437+
// We have type references to the same generic type. Obtain the variance information for the type
19438+
// parameters and relate the type arguments accordingly. If variance information for the type is in
19439+
// the process of being computed, fall through and relate the type references structurally.
1944519440
const variances = getVariances((source as TypeReference).target);
19446-
// We return Ternary.Maybe for a recursive invocation of getVariances (signalled by emptyArray). This
19447-
// effectively means we measure variance only from type parameter occurrences that aren't nested in
19448-
// recursive instantiations of the generic type.
19449-
if (variances === emptyArray) {
19450-
return Ternary.Unknown;
19451-
}
19452-
const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState);
19453-
if (varianceResult !== undefined) {
19454-
return varianceResult;
19441+
if (variances !== emptyArray) {
19442+
const varianceResult = relateVariances(getTypeArguments(source as TypeReference), getTypeArguments(target as TypeReference), variances, intersectionState);
19443+
if (varianceResult !== undefined) {
19444+
return varianceResult;
19445+
}
1945519446
}
1945619447
}
1945719448
else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
@@ -20371,45 +20362,24 @@ namespace ts {
2037120362

2037220363
function getVariances(type: GenericType): VarianceFlags[] {
2037320364
// Arrays and tuples are known to be covariant, no need to spend time computing this.
20374-
if (type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple) {
20375-
return arrayVariances;
20376-
}
20377-
return getVariancesWorker(type.symbol, type.typeParameters, getMarkerTypeReference);
20378-
}
20379-
20380-
// Return a type reference where the source type parameter is replaced with the target marker
20381-
// type, and flag the result as a marker type reference.
20382-
function getMarkerTypeReference(symbol: Symbol, source: TypeParameter, target: Type) {
20383-
const type = getDeclaredTypeOfSymbol(symbol) as GenericType;
20384-
const result = createTypeReference(type, map(type.typeParameters, t => t === source ? target : t));
20385-
result.objectFlags |= ObjectFlags.MarkerType;
20386-
return result;
20365+
return type === globalArrayType || type === globalReadonlyArrayType || type.objectFlags & ObjectFlags.Tuple ?
20366+
arrayVariances :
20367+
getVariancesWorker(type.symbol, type.typeParameters);
2038720368
}
2038820369

2038920370
function getAliasVariances(symbol: Symbol) {
20390-
return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters, getMarkerTypeAliasReference);
20391-
}
20392-
20393-
function getMarkerTypeAliasReference(symbol: Symbol, source: TypeParameter, target: Type) {
20394-
const result = getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, makeUnaryTypeMapper(source, target)));
20395-
result.aliasTypeArgumentsContainsMarker = true;
20396-
return result;
20371+
return getVariancesWorker(symbol, getSymbolLinks(symbol).typeParameters);
2039720372
}
2039820373

2039920374
// Return an array containing the variance of each type parameter. The variance is effectively
2040020375
// a digest of the type comparisons that occur for each type argument when instantiations of the
2040120376
// generic type are structurally compared. We infer the variance information by comparing
2040220377
// instantiations of the generic type for type arguments with known relations. The function
2040320378
// returns the emptyArray singleton when invoked recursively for the given generic type.
20404-
function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray, createMarkerType: (symbol: Symbol, param: TypeParameter, marker: Type) => Type): VarianceFlags[] {
20379+
function getVariancesWorker(symbol: Symbol, typeParameters: readonly TypeParameter[] = emptyArray): VarianceFlags[] {
2040520380
const links = getSymbolLinks(symbol);
2040620381
if (!links.variances) {
2040720382
tracing?.push(tracing.Phase.CheckTypes, "getVariancesWorker", { arity: typeParameters.length, id: getSymbolId(symbol) });
20408-
if (varianceLevel > 0) {
20409-
nestedVarianceSymbols = append(nestedVarianceSymbols, symbol);
20410-
}
20411-
varianceLevel++;
20412-
// The emptyArray singleton is used to signal a recursive invocation.
2041320383
links.variances = emptyArray;
2041420384
const variances = [];
2041520385
for (const tp of typeParameters) {
@@ -20443,28 +20413,20 @@ namespace ts {
2044320413
variances.push(variance);
2044420414
}
2044520415
links.variances = variances;
20446-
varianceLevel--;
20447-
// Recursive invocations of getVariancesWorker occur when two or more types circularly reference each
20448-
// other. In such cases, the nested invocations might observe in-process variance computations, i.e.
20449-
// cases where getVariancesWorker returns emptyArray. If that happens we clear (and thus re-compute) the
20450-
// results of nested variance computations and only permanently record the outermost result. See #44572.
20451-
if (varianceLevel === 0) {
20452-
if (nestedVarianceSymbols && incompleteVariancesObserved) {
20453-
for (const sym of nestedVarianceSymbols) {
20454-
getSymbolLinks(sym).variances = undefined;
20455-
}
20456-
}
20457-
nestedVarianceSymbols = undefined;
20458-
incompleteVariancesObserved = false;
20459-
}
2046020416
tracing?.pop();
2046120417
}
20462-
else {
20463-
incompleteVariancesObserved ||= links.variances === emptyArray;
20464-
}
2046520418
return links.variances;
2046620419
}
2046720420

20421+
function createMarkerType(symbol: Symbol, source: TypeParameter, target: Type) {
20422+
const mapper = makeUnaryTypeMapper(source, target);
20423+
if (symbol.flags & SymbolFlags.TypeAlias) {
20424+
return getTypeAliasInstantiation(symbol, instantiateTypes(getSymbolLinks(symbol).typeParameters!, mapper));
20425+
}
20426+
const type = getDeclaredTypeOfSymbol(symbol) as GenericType;
20427+
return createTypeReference(type, instantiateTypes(type.typeParameters, mapper));
20428+
}
20429+
2046820430
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
2046920431
// See comment at call in recursiveTypeRelatedTo for when this case matters.
2047020432
function hasCovariantVoidArgument(typeArguments: readonly Type[], variances: VarianceFlags[]): boolean {

src/compiler/types.ts

+21-23
Original file line numberDiff line numberDiff line change
@@ -5227,7 +5227,6 @@ namespace ts {
52275227
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
52285228
aliasSymbol?: Symbol; // Alias associated with type
52295229
aliasTypeArguments?: readonly Type[]; // Alias type arguments (if any)
5230-
/* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any)
52315230
/* @internal */
52325231
permissiveInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type
52335232
/* @internal */
@@ -5306,22 +5305,21 @@ namespace ts {
53065305
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
53075306
ReverseMapped = 1 << 10, // Object contains a property from a reverse-mapped type
53085307
JsxAttributes = 1 << 11, // Jsx attributes type
5309-
MarkerType = 1 << 12, // Marker type used for variance probing
5310-
JSLiteral = 1 << 13, // Object type declared in JS - disables errors on read/write of nonexisting members
5311-
FreshLiteral = 1 << 14, // Fresh object literal
5312-
ArrayLiteral = 1 << 15, // Originates in an array literal
5308+
JSLiteral = 1 << 12, // Object type declared in JS - disables errors on read/write of nonexisting members
5309+
FreshLiteral = 1 << 13, // Fresh object literal
5310+
ArrayLiteral = 1 << 14, // Originates in an array literal
53135311
/* @internal */
5314-
PrimitiveUnion = 1 << 16, // Union of only primitive types
5312+
PrimitiveUnion = 1 << 15, // Union of only primitive types
53155313
/* @internal */
5316-
ContainsWideningType = 1 << 17, // Type is or contains undefined or null widening type
5314+
ContainsWideningType = 1 << 16, // Type is or contains undefined or null widening type
53175315
/* @internal */
5318-
ContainsObjectOrArrayLiteral = 1 << 18, // Type is or contains object literal type
5316+
ContainsObjectOrArrayLiteral = 1 << 17, // Type is or contains object literal type
53195317
/* @internal */
5320-
NonInferrableType = 1 << 19, // Type is or contains anyFunctionType or silentNeverType
5318+
NonInferrableType = 1 << 18, // Type is or contains anyFunctionType or silentNeverType
53215319
/* @internal */
5322-
CouldContainTypeVariablesComputed = 1 << 20, // CouldContainTypeVariables flag has been computed
5320+
CouldContainTypeVariablesComputed = 1 << 19, // CouldContainTypeVariables flag has been computed
53235321
/* @internal */
5324-
CouldContainTypeVariables = 1 << 21, // Type could contain a type variable
5322+
CouldContainTypeVariables = 1 << 20, // Type could contain a type variable
53255323

53265324
ClassOrInterface = Class | Interface,
53275325
/* @internal */
@@ -5333,36 +5331,36 @@ namespace ts {
53335331
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,
53345332

53355333
// Flags that require TypeFlags.Object
5336-
ContainsSpread = 1 << 22, // Object literal contains spread operation
5337-
ObjectRestType = 1 << 23, // Originates in object rest declaration
5338-
InstantiationExpressionType = 1 << 24, // Originates in instantiation expression
5334+
ContainsSpread = 1 << 21, // Object literal contains spread operation
5335+
ObjectRestType = 1 << 22, // Originates in object rest declaration
5336+
InstantiationExpressionType = 1 << 23, // Originates in instantiation expression
53395337
/* @internal */
5340-
IsClassInstanceClone = 1 << 25, // Type is a clone of a class instance type
5338+
IsClassInstanceClone = 1 << 24, // Type is a clone of a class instance type
53415339
// Flags that require TypeFlags.Object and ObjectFlags.Reference
53425340
/* @internal */
5343-
IdenticalBaseTypeCalculated = 1 << 26, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already
5341+
IdenticalBaseTypeCalculated = 1 << 25, // has had `getSingleBaseForNonAugmentingSubtype` invoked on it already
53445342
/* @internal */
5345-
IdenticalBaseTypeExists = 1 << 27, // has a defined cachedEquivalentBaseType member
5343+
IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member
53465344

53475345
// Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution
53485346
/* @internal */
5349-
IsGenericTypeComputed = 1 << 22, // IsGenericObjectType flag has been computed
5347+
IsGenericTypeComputed = 1 << 21, // IsGenericObjectType flag has been computed
53505348
/* @internal */
5351-
IsGenericObjectType = 1 << 23, // Union or intersection contains generic object type
5349+
IsGenericObjectType = 1 << 22, // Union or intersection contains generic object type
53525350
/* @internal */
5353-
IsGenericIndexType = 1 << 24, // Union or intersection contains generic index type
5351+
IsGenericIndexType = 1 << 23, // Union or intersection contains generic index type
53545352
/* @internal */
53555353
IsGenericType = IsGenericObjectType | IsGenericIndexType,
53565354

53575355
// Flags that require TypeFlags.Union
53585356
/* @internal */
5359-
ContainsIntersections = 1 << 25, // Union contains intersections
5357+
ContainsIntersections = 1 << 24, // Union contains intersections
53605358

53615359
// Flags that require TypeFlags.Intersection
53625360
/* @internal */
5363-
IsNeverIntersectionComputed = 1 << 25, // IsNeverLike flag has been computed
5361+
IsNeverIntersectionComputed = 1 << 24, // IsNeverLike flag has been computed
53645362
/* @internal */
5365-
IsNeverIntersection = 1 << 26, // Intersection reduces to never
5363+
IsNeverIntersection = 1 << 25, // Intersection reduces to never
53665364
}
53675365

53685366
/* @internal */

0 commit comments

Comments
 (0)