Measure variance of aliased conditional types using variance markers (#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
This commit is contained in:
Wesley Wigham
2018-10-26 16:26:20 -07:00
committed by GitHub
parent ccc16136b2
commit e2100cd2cc
8 changed files with 1336 additions and 75 deletions

View File

@@ -7636,35 +7636,35 @@ namespace ts {
* @param typeParameters The requested type parameters.
* @param minTypeArgumentCount The minimum number of required type arguments.
*/
function fillMissingTypeArguments(typeArguments: Type[], typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined;
function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) {
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type>, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[];
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type> | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[] | undefined;
function fillMissingTypeArguments(typeArguments: ReadonlyArray<Type> | undefined, typeParameters: ReadonlyArray<TypeParameter> | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) {
const numTypeParameters = length(typeParameters);
if (numTypeParameters) {
const numTypeArguments = length(typeArguments);
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
if (!typeArguments) {
typeArguments = [];
}
// Map an unsatisfied type parameter with a default type.
// If a type parameter does not have a default type, or if the default type
// is a forward reference, the empty object type is used.
for (let i = numTypeArguments; i < numTypeParameters; i++) {
typeArguments[i] = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
}
for (let i = numTypeArguments; i < numTypeParameters; i++) {
const mapper = createTypeMapper(typeParameters!, typeArguments);
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
defaultType = anyType;
}
typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScriptImplicitAny);
}
typeArguments.length = typeParameters!.length;
}
if (!numTypeParameters) {
return [];
}
return typeArguments;
const numTypeArguments = length(typeArguments);
if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
const result = typeArguments ? typeArguments.slice() : [];
// Map an unsatisfied type parameter with a default type.
// If a type parameter does not have a default type, or if the default type
// is a forward reference, the empty object type is used.
for (let i = numTypeArguments; i < numTypeParameters; i++) {
result[i] = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
}
for (let i = numTypeArguments; i < numTypeParameters; i++) {
const mapper = createTypeMapper(typeParameters!, result);
let defaultType = getDefaultFromTypeParameter(typeParameters![i]);
if (isJavaScriptImplicitAny && defaultType && isTypeIdenticalTo(defaultType, emptyObjectType)) {
defaultType = anyType;
}
result[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScriptImplicitAny);
}
result.length = typeParameters!.length;
return result;
}
return typeArguments && typeArguments.slice();
}
function getSignatureFromDeclaration(declaration: SignatureDeclaration | JSDocSignature): Signature {
@@ -8262,7 +8262,7 @@ namespace ts {
return checkNoTypeArguments(node, symbol) ? type : errorType;
}
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: Type[] | undefined): Type {
function getTypeAliasInstantiation(symbol: Symbol, typeArguments: ReadonlyArray<Type> | undefined): Type {
const type = getDeclaredTypeOfSymbol(symbol);
const links = getSymbolLinks(symbol);
const typeParameters = links.typeParameters!;
@@ -11783,9 +11783,7 @@ namespace ts {
return result;
}
function typeArgumentsRelatedTo(source: TypeReference, target: TypeReference, variances: Variance[], reportErrors: boolean): Ternary {
const sources = source.typeArguments || emptyArray;
const targets = target.typeArguments || emptyArray;
function typeArgumentsRelatedTo(sources: ReadonlyArray<Type> = emptyArray, targets: ReadonlyArray<Type> = emptyArray, variances: ReadonlyArray<Variance> = emptyArray, reportErrors: boolean): Ternary {
if (sources.length !== targets.length && relation === identityRelation) {
return Ternary.False;
}
@@ -11938,9 +11936,25 @@ namespace ts {
}
return Ternary.False;
}
let result: Ternary;
let originalErrorInfo: DiagnosticMessageChain | undefined;
const saveErrorInfo = errorInfo;
// We limit alias variance probing to only object and conditional types since their alias behavior
// is more predictable than other, interned types, which may or may not have an alias depending on
// the order in which things were checked.
if (source.flags & (TypeFlags.Object | TypeFlags.Conditional) && source.aliasSymbol &&
source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
!(source.aliasTypeArgumentsContainsMarker || target.aliasTypeArgumentsContainsMarker)) {
const variances = getAliasVariances(source.aliasSymbol);
if (result = typeArgumentsRelatedTo(source.aliasTypeArguments, target.aliasTypeArguments, variances, reportErrors)) {
return result;
}
originalErrorInfo = errorInfo;
errorInfo = saveErrorInfo;
}
if (target.flags & TypeFlags.TypeParameter) {
// A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P].
if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(<MappedType>source) === getIndexType(target)) {
@@ -12101,7 +12115,7 @@ namespace ts {
// type references (which are intended by be compared structurally). Obtain the variance
// information for the type parameters and relate the type arguments accordingly.
const variances = getVariances((<TypeReference>source).target);
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
if (result = typeArgumentsRelatedTo((<TypeReference>source).typeArguments, (<TypeReference>target).typeArguments, variances, reportErrors)) {
return result;
}
// The type arguments did not relate appropriately, but it may be because we have no variance
@@ -12608,48 +12622,58 @@ namespace ts {
return result;
}
function getAliasVariances(symbol: Symbol) {
const links = getSymbolLinks(symbol);
return getVariancesWorker(links.typeParameters, links, (_links, param, marker) => {
const type = getTypeAliasInstantiation(symbol, instantiateTypes(links.typeParameters!, makeUnaryTypeMapper(param, marker)));
type.aliasTypeArgumentsContainsMarker = true;
return type;
});
}
// Return an array containing the variance of each type parameter. The variance is effectively
// a digest of the type comparisons that occur for each type argument when instantiations of the
// generic type are structurally compared. We infer the variance information by comparing
// instantiations of the generic type for type arguments with known relations. The function
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
// has been invoked recursively for the given generic type.
function getVariancesWorker<TCache extends { variances?: Variance[] }>(typeParameters: ReadonlyArray<TypeParameter> = emptyArray, cache: TCache, createMarkerType: (input: TCache, param: TypeParameter, marker: Type) => Type): Variance[] {
let variances = cache.variances;
if (!variances) {
// The emptyArray singleton is used to signal a recursive invocation.
cache.variances = emptyArray;
variances = [];
for (const tp of typeParameters) {
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = createMarkerType(cache, tp, markerSuperType);
const typeWithSub = createMarkerType(cache, tp, markerSubType);
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === Variance.Bivariant && isTypeAssignableTo(createMarkerType(cache, tp, markerOtherType), typeWithSuper)) {
variance = Variance.Independent;
}
variances.push(variance);
}
cache.variances = variances;
}
return variances;
}
function getVariances(type: GenericType): Variance[] {
if (!strictFunctionTypes) {
return emptyArray;
}
const typeParameters = type.typeParameters || emptyArray;
let variances = type.variances;
if (!variances) {
if (type === globalArrayType || type === globalReadonlyArrayType) {
// Arrays are known to be covariant, no need to spend time computing this
variances = [Variance.Covariant];
}
else {
// The emptyArray singleton is used to signal a recursive invocation.
type.variances = emptyArray;
variances = [];
for (const tp of typeParameters) {
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType);
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
// If the instantiations appear to be related bivariantly it may be because the
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
// type). To determine this we compare instantiations where the type parameter is
// replaced with marker types that are known to be unrelated.
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
variance = Variance.Independent;
}
variances.push(variance);
}
}
type.variances = variances;
if (type === globalArrayType || type === globalReadonlyArrayType) {
// Arrays are known to be covariant, no need to spend time computing this (emptyArray implies covariance for all parameters)
return emptyArray;
}
return variances;
return getVariancesWorker(type.typeParameters, type, getMarkerTypeReference);
}
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.

View File

@@ -3647,6 +3647,7 @@ namespace ts {
originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol`
lateSymbol?: Symbol; // Late-bound symbol for a computed property
specifierCache?: Map<string>; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings
variances?: Variance[]; // Alias symbol type argument variance cache
}
/* @internal */
@@ -3899,6 +3900,7 @@ namespace ts {
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
aliasSymbol?: Symbol; // Alias associated with type
aliasTypeArguments?: ReadonlyArray<Type>; // Alias type arguments (if any)
/* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any)
/* @internal */
wildcardInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type
/* @internal */