Skip to content

Commit e2100cd

Browse files
authored
Measure variance of aliased conditional types using variance markers (microsoft#27804)
* Measure variance of aliased conditional types using variance markers * Just do variance probing for all type aliases * Small limiter for predictability * Inline property set, remove unused functions
1 parent ccc1613 commit e2100cd

8 files changed

+1333
-72
lines changed

src/compiler/checker.ts

+84-60
Original file line numberDiff line numberDiff line change
@@ -7636,35 +7636,35 @@ namespace ts {
76367636
* @param typeParameters The requested type parameters.
76377637
* @param minTypeArgumentCount The minimum number of required type arguments.
76387638
*/
7639-
function fillMissingTypeArguments(typeArguments: Type[], typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
7640-
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined;
7641-
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) {
7639+
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type>, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
7640+
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type> | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined;
7641+
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type> | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) {
76427642
const numTypeParameters = length(typeParameters);
7643-
if (numTypeParameters) {
7644-
const numTypeArguments = length(typeArguments);
7645-
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
7646-
if (!typeArguments) {
7647-
typeArguments = [];
7648-
}
7643+
if (!numTypeParameters) {
7644+
return [];
7645+
}
7646+
const numTypeArguments = length(typeArguments);
7647+
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
7648+
const result = typeArguments ? typeArguments.slice() : [];
76497649

7650-
// Map an unsatisfied type parameter with a default type.
7651-
// If a type parameter does not have a default type, or if the default type
7652-
// is a forward reference, the empty object type is used.
7653-
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7654-
typeArguments[i] = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
7655-
}
7656-
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7657-
const mapper = createTypeMapper(typeParameters!, typeArguments);
7658-
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
7659-
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
7660-
defaultType = anyType;
7661-
}
7662-
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScriptImplicitAny);
7650+
// Map an unsatisfied type parameter with a default type.
7651+
// If a type parameter does not have a default type, or if the default type
7652+
// is a forward reference, the empty object type is used.
7653+
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7654+
result[i] = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
7655+
}
7656+
for (let i = numTypeArguments; i < numTypeParameters; i++) {
7657+
const mapper = createTypeMapper(typeParameters!, result);
7658+
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
7659+
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
7660+
defaultType = anyType;
76637661
}
7664-
typeArguments.length = typeParameters!.length;
7662+
result[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScriptImplicitAny);
76657663
}
7664+
result.length = typeParameters!.length;
7665+
return result;
76667666
}
7667-
return typeArguments;
7667+
return typeArguments && typeArguments.slice();
76687668
}
76697669

76707670
function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature {
@@ -8262,7 +8262,7 @@ namespace ts {
82628262
return checkNoTypeArguments(node, symbol) ? type : errorType;
82638263
}
82648264

8265-
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[] | undefined): Type {
8265+
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: ReadonlyArray<Type> | undefined): Type {
82668266
const type = getDeclaredTypeOfSymbol(symbol);
82678267
const links = getSymbolLinks(symbol);
82688268
const typeParameters = links.typeParameters!;
@@ -11783,9 +11783,7 @@ namespace ts {
1178311783
return result;
1178411784
}
1178511785

11786-
function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, variances: Variance[], reportErrors: boolean): Ternary {
11787-
const sources = source.typeArguments || emptyArray;
11788-
const targets = target.typeArguments || emptyArray;
11786+
function typeArgumentsRelatedTo(sources: ReadonlyArray<Type> = emptyArray, targets: ReadonlyArray<Type> = emptyArray, variances: ReadonlyArray<Variance> = emptyArray, reportErrors: boolean): Ternary {
1178911787
if (sources.length !== targets.length && relation === identityRelation) {
1179011788
return Ternary.False;
1179111789
}
@@ -11938,9 +11936,25 @@ namespace ts {
1193811936
}
1193911937
return Ternary.False;
1194011938
}
11939+
1194111940
let result: Ternary;
1194211941
let originalErrorInfo: DiagnosticMessageChain | undefined;
1194311942
const saveErrorInfo = errorInfo;
11943+
11944+
// We limit alias variance probing to only object and conditional types since their alias behavior
11945+
// is more predictable than other, interned types, which may or may not have an alias depending on
11946+
// the order in which things were checked.
11947+
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
11948+
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
11949+
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
11950+
const variances = getAliasVariances(source.aliasSymbol);
11951+
if (result = typeArgumentsRelatedTo(source.aliasTypeArguments, target.aliasTypeArguments, variances, reportErrors)) {
11952+
return result;
11953+
}
11954+
originalErrorInfo = errorInfo;
11955+
errorInfo = saveErrorInfo;
11956+
}
11957+
1194411958
if (target.flags & TypeFlags.TypeParameter) {
1194511959
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
1194611960
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
@@ -12101,7 +12115,7 @@ namespace ts {
1210112115
// type references (which are intended by be compared structurally). Obtain the variance
1210212116
// information for the type parameters and relate the type arguments accordingly.
1210312117
const variances = getVariances((<TypeReference>source).target);
12104-
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
12118+
if (result = typeArgumentsRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, variances, reportErrors)) {
1210512119
return result;
1210612120
}
1210712121
// The type arguments did not relate appropriately, but it may be because we have no variance
@@ -12608,48 +12622,58 @@ namespace ts {
1260812622
return result;
1260912623
}
1261012624

12625+
function getAliasVariances(symbol: Symbol) {
12626+
const links = getSymbolLinks(symbol);
12627+
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
12628+
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
12629+
type.aliasTypeArgumentsContainsMarker = true;
12630+
return type;
12631+
});
12632+
}
12633+
1261112634
// Return an array containing the variance of each type parameter. The variance is effectively
1261212635
// a digest of the type comparisons that occur for each type argument when instantiations of the
1261312636
// generic type are structurally compared. We infer the variance information by comparing
1261412637
// instantiations of the generic type for type arguments with known relations. The function
1261512638
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
1261612639
// has been invoked recursively for the given generic type.
12640+
function getVariancesWorker<TCache extends { variances?: Variance[] }>(typeParameters: ReadonlyArray<TypeParameter> = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): Variance[] {
12641+
let variances = cache.variances;
12642+
if (!variances) {
12643+
// The emptyArray singleton is used to signal a recursive invocation.
12644+
cache.variances = emptyArray;
12645+
variances = [];
12646+
for (const tp of typeParameters) {
12647+
// We first compare instantiations where the type parameter is replaced with
12648+
// marker types that have a known subtype relationship. From this we can infer
12649+
// invariance, covariance, contravariance or bivariance.
12650+
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
12651+
const typeWithSub = createMarkerType(cache, tp, markerSubType);
12652+
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
12653+
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
12654+
// If the instantiations appear to be related bivariantly it may be because the
12655+
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
12656+
// type). To determine this we compare instantiations where the type parameter is
12657+
// replaced with marker types that are known to be unrelated.
12658+
if (variance === Variance.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
12659+
variance = Variance.Independent;
12660+
}
12661+
variances.push(variance);
12662+
}
12663+
cache.variances = variances;
12664+
}
12665+
return variances;
12666+
}
12667+
1261712668
function getVariances(type: GenericType): Variance[] {
1261812669
if (!strictFunctionTypes) {
1261912670
return emptyArray;
1262012671
}
12621-
const typeParameters = type.typeParameters || emptyArray;
12622-
let variances = type.variances;
12623-
if (!variances) {
12624-
if (type === globalArrayType || type === globalReadonlyArrayType) {
12625-
// Arrays are known to be covariant, no need to spend time computing this
12626-
variances = [Variance.Covariant];
12627-
}
12628-
else {
12629-
// The emptyArray singleton is used to signal a recursive invocation.
12630-
type.variances = emptyArray;
12631-
variances = [];
12632-
for (const tp of typeParameters) {
12633-
// We first compare instantiations where the type parameter is replaced with
12634-
// marker types that have a known subtype relationship. From this we can infer
12635-
// invariance, covariance, contravariance or bivariance.
12636-
const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType);
12637-
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
12638-
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
12639-
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
12640-
// If the instantiations appear to be related bivariantly it may be because the
12641-
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
12642-
// type). To determine this we compare instantiations where the type parameter is
12643-
// replaced with marker types that are known to be unrelated.
12644-
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
12645-
variance = Variance.Independent;
12646-
}
12647-
variances.push(variance);
12648-
}
12649-
}
12650-
type.variances = variances;
12672+
if (type === globalArrayType || type === globalReadonlyArrayType) {
12673+
// Arrays are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters)
12674+
return emptyArray;
1265112675
}
12652-
return variances;
12676+
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
1265312677
}
1265412678

1265512679
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.

src/compiler/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -3647,6 +3647,7 @@ namespace ts {
36473647
originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol`
36483648
lateSymbol?: Symbol; // Late-bound symbol for a computed property
36493649
specifierCache?: Map<string>; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings
3650+
variances?: Variance[]; // Alias symbol type argument variance cache
36503651
}
36513652

36523653
/* @internal */
@@ -3899,6 +3900,7 @@ namespace ts {
38993900
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
39003901
aliasSymbol?: Symbol; // Alias associated with type
39013902
aliasTypeArguments?: ReadonlyArray<Type>; // Alias type arguments (if any)
3903+
/* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any)
39023904
/* @internal */
39033905
wildcardInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type
39043906
/* @internal */

0 commit comments

Comments
 (0)