Skip to content

Commit 91691f6

Browse files
committed
Strict function type checking only for certain function types
1 parent 70e8f73 commit 91691f6

File tree

2 files changed

+91
-31
lines changed

2 files changed

+91
-31
lines changed

src/compiler/checker.ts

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ namespace ts {
285285
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
286286
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
287287
markerSubType.constraint = markerSuperType;
288+
const markerOtherType = <TypeParameter>createType(TypeFlags.TypeParameter);
288289

289290
const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
290291
const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, unknownType, /*typePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
@@ -3548,7 +3549,7 @@ namespace ts {
35483549
return;
35493550
}
35503551

3551-
if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length) {
3552+
if (resolved.callSignatures.length === 1 && !resolved.constructSignatures.length && isStrictSignature(resolved.callSignatures[0])) {
35523553
const parenthesizeSignature = shouldAddParenthesisAroundFunctionType(resolved.callSignatures[0], flags);
35533554
if (parenthesizeSignature) {
35543555
writePunctuation(writer, SyntaxKind.OpenParenToken);
@@ -3559,7 +3560,7 @@ namespace ts {
35593560
}
35603561
return;
35613562
}
3562-
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length) {
3563+
if (resolved.constructSignatures.length === 1 && !resolved.callSignatures.length && isStrictSignature(resolved.constructSignatures[0])) {
35633564
if (flags & TypeFormatFlags.InElementType) {
35643565
writePunctuation(writer, SyntaxKind.OpenParenToken);
35653566
}
@@ -8521,6 +8522,17 @@ namespace ts {
85218522
/*errorReporter*/ undefined, compareTypesAssignable) !== Ternary.False;
85228523
}
85238524

8525+
// A signature is considered strict if it is declared in a function type literal, a constructor type
8526+
// literal, a function expression, an arrow function, or a function declaration with no overloads. A
8527+
// strict signature is subject to strict checking in strictFunctionTypes mode.
8528+
function isStrictSignature(signature: Signature) {
8529+
const declaration = signature.declaration;
8530+
const kind = declaration ? declaration.kind : SyntaxKind.Unknown;
8531+
return kind === SyntaxKind.FunctionType || kind === SyntaxKind.ConstructorType ||
8532+
kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.ArrowFunction ||
8533+
(kind === SyntaxKind.FunctionDeclaration && getSingleCallSignature(getTypeOfSymbol(getSymbolOfNode(declaration))));
8534+
}
8535+
85248536
type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void;
85258537

85268538
/**
@@ -8546,9 +8558,7 @@ namespace ts {
85468558
source = instantiateSignatureInContextOf(source, target, /*contextualMapper*/ undefined, compareTypes);
85478559
}
85488560

8549-
const targetKind = target.declaration ? target.declaration.kind : SyntaxKind.Unknown;
8550-
const strictVariance = strictFunctionTypes && targetKind !== SyntaxKind.MethodDeclaration && targetKind !== SyntaxKind.MethodSignature;
8551-
8561+
const strictVariance = strictFunctionTypes && isStrictSignature(target);
85528562
let result = Ternary.True;
85538563

85548564
const sourceThisType = getThisTypeOfSignature(source);
@@ -9215,15 +9225,41 @@ namespace ts {
92159225
// in the process of computing variance information for recursive types and when
92169226
// comparing 'this' type arguments.
92179227
const variance = i < variances.length ? variances[i] : Variance.Covariant;
9218-
const s = sources[i];
9219-
const t = targets[i];
9220-
const related = variance === Variance.Covariant ? isRelatedTo(s, t, reportErrors) :
9221-
variance === Variance.Contravariant ? isRelatedTo(t, s, reportErrors) :
9222-
Ternary.False;
9223-
if (!related) {
9224-
return Ternary.False;
9228+
// We ignore arguments for independent type parameters (because they're never witnessed).
9229+
if (variance !== Variance.Independent) {
9230+
const s = sources[i];
9231+
const t = targets[i];
9232+
let related = Ternary.True;
9233+
if (variance === Variance.Covariant) {
9234+
related = isRelatedTo(s, t, reportErrors);
9235+
}
9236+
else if (variance === Variance.Contravariant) {
9237+
related = isRelatedTo(t, s, reportErrors);
9238+
}
9239+
else if (variance === Variance.Bivariant) {
9240+
// In the bivariant case we first compare contravariantly without reporting
9241+
// errors. Then, if that doesn't succeed, we compare covariantly with error
9242+
// reporting. Thus, error elaboration will be based on the the covariant check,
9243+
// which is generally easier to reason about.
9244+
related = isRelatedTo(t, s, /*reportErrors*/ false);
9245+
if (!related) {
9246+
related = isRelatedTo(s, t, reportErrors);
9247+
}
9248+
}
9249+
else {
9250+
// In the invariant case we first compare covariantly, and only when that
9251+
// succeeds do we proceed to compare contravariantly. Thus, error elaboration
9252+
// will typically be based on the covariant check.
9253+
related = isRelatedTo(s, t, reportErrors);
9254+
if (related) {
9255+
related &= isRelatedTo(t, s, reportErrors);
9256+
}
9257+
}
9258+
if (!related) {
9259+
return Ternary.False;
9260+
}
9261+
result &= related;
92259262
}
9226-
result &= related;
92279263
}
92289264
return result;
92299265
}
@@ -9388,14 +9424,21 @@ namespace ts {
93889424
!(source.flags & TypeFlags.MarkerType || target.flags & TypeFlags.MarkerType)) {
93899425
// We have type references to the same generic type, and the type references are not marker
93909426
// type references (which are intended by be compared structurally). Obtain the variance
9391-
// information for the type parameters and relate the type arguments accordingly. If we do
9392-
// not succeed, fall through and do a structural comparison instead (there are instances
9393-
// where the variance information isn't accurate, e.g. when type parameters are used only
9394-
// in bivariant positions or when a type argument is 'any' or 'void'.)
9427+
// information for the type parameters and relate the type arguments accordingly.
93959428
const variances = getVariances((<TypeReference>source).target);
93969429
if (result = typeArgumentsRelatedTo(<TypeReference>source, <TypeReference>target, variances, reportErrors)) {
93979430
return result;
93989431
}
9432+
// The type arguments did not relate appropriately, but it may be because we have no variance
9433+
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
9434+
// arguments). It might also be the case that the target type has a 'void' type argument for
9435+
// a covariant type parameter that is only used in return positions within the generic type
9436+
// (in which case any type argument is permitted on the source side). In those cases we proceed
9437+
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
9438+
// related and we can return here.
9439+
if (variances !== emptyArray && !hasCovariantVoidArgument(<TypeReference>target, variances)) {
9440+
return Ternary.False;
9441+
}
93999442
}
94009443
// Even if relationship doesn't hold for unions, intersections, or generic type references,
94019444
// it may hold in a structural comparison.
@@ -9814,14 +9857,12 @@ namespace ts {
98149857
return result;
98159858
}
98169859

9817-
// Return an array containing the variance of each type parameter. The variance information is
9818-
// computed by comparing instantiations of the generic type for type arguments with known relations.
9819-
// A type parameter is marked as covariant if a covariant comparison succeeds; otherwise, it is
9820-
// marked contravariant if a contravarint comparison succeeds; otherwise, it is marked invariant.
9821-
// One form of variance doesn't exclude another, so this information simply serves to indicate
9822-
// a "primary" relationship that can be checked as an optimization ahead of a full structural
9823-
// comparison. The function returns the emptyArray singleton if we're not in strictFunctionTypes
9824-
// mode or if the function has been invoked recursively for the given generic type.
9860+
// Return an array containing the variance of each type parameter. The variance is effectively
9861+
// a digest of the type comparisons that occur for each type argument when instantiations of the
9862+
// generic type are structurally compared. We infer the variance information by comparing
9863+
// instantiations of the generic type for type arguments with known relations. The function
9864+
// returns the emptyArray singleton if we're not in strictFunctionTypes mode or if the function
9865+
// has been invoked recursively for the given generic type.
98259866
function getVariances(type: GenericType): Variance[] {
98269867
if (!strictFunctionTypes) {
98279868
return emptyArray;
@@ -9838,14 +9879,20 @@ namespace ts {
98389879
type.variances = emptyArray;
98399880
variances = [];
98409881
for (const tp of typeParameters) {
9841-
// We compare instantiations where the type parameter is replaced with marker types
9842-
// that have a known subtype relationship. From this we infer covariance, contravariance
9843-
// or invariance.
9882+
// We first compare instantiations where the type parameter is replaced with
9883+
// marker types that have a known subtype relationship. From this we can infer
9884+
// invariance, covariance, contravariance or bivariance.
98449885
const typeWithSuper = getMarkerTypeReference(type, tp, markerSuperType);
98459886
const typeWithSub = getMarkerTypeReference(type, tp, markerSubType);
9846-
const variance = isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant :
9847-
isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant :
9848-
Variance.Invariant;
9887+
let variance = (isTypeAssignableTo(typeWithSub, typeWithSuper) ? Variance.Covariant : 0) |
9888+
(isTypeAssignableTo(typeWithSuper, typeWithSub) ? Variance.Contravariant : 0);
9889+
// If the instantiations appear to be related bivariantly it may be because the
9890+
// type parameter is independent (i.e. it isn't witnessed anywhere in the generic
9891+
// type). To determine this we compare instantiations where the type parameter is
9892+
// replaced with marker types that are known to be unrelated.
9893+
if (variance === Variance.Bivariant && isTypeAssignableTo(getMarkerTypeReference(type, tp, markerOtherType), typeWithSuper)) {
9894+
variance = Variance.Independent;
9895+
}
98499896
variances.push(variance);
98509897
}
98519898
}
@@ -9854,6 +9901,17 @@ namespace ts {
98549901
return variances;
98559902
}
98569903

9904+
// Return true if the given type reference has a 'void' type argument for a covariant type parameter.
9905+
// See comment at call in recursiveTypeRelatedTo for when this case matters.
9906+
function hasCovariantVoidArgument(type: TypeReference, variances: Variance[]): boolean {
9907+
for (let i = 0; i < variances.length; i++) {
9908+
if (variances[i] === Variance.Covariant && type.typeArguments[i].flags & TypeFlags.Void) {
9909+
return true;
9910+
}
9911+
}
9912+
return false;
9913+
}
9914+
98579915
function isUnconstrainedTypeParameter(type: Type) {
98589916
return type.flags & TypeFlags.TypeParameter && !getConstraintFromTypeParameter(<TypeParameter>type);
98599917
}

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3348,6 +3348,8 @@ namespace ts {
33483348
Invariant = 0, // Neither covariant nor contravariant
33493349
Covariant = 1, // Covariant
33503350
Contravariant = 2, // Contravariant
3351+
Bivariant = 3, // Both covariant and contravariant
3352+
Independent = 4, // Unwitnessed type parameter
33513353
}
33523354

33533355
// Generic class and interface types

0 commit comments

Comments
 (0)