Skip to content

Commit 8d46ffd

Browse files
Merge pull request #6075 from Microsoft/overloadCompatibility
Implement new overload compatibility checking
2 parents 4d792f2 + 731925b commit 8d46ffd

File tree

52 files changed

+1980
-228
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1980
-228
lines changed

src/compiler/checker.ts

Lines changed: 111 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3507,7 +3507,7 @@ namespace ts {
35073507

35083508
function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature {
35093509
for (const s of signatureList) {
3510-
if (compareSignatures(s, signature, partialMatch, ignoreReturnTypes, compareTypes)) {
3510+
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreReturnTypes, compareTypesIdentical)) {
35113511
return s;
35123512
}
35133513
}
@@ -4959,7 +4959,7 @@ namespace ts {
49594959
return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined);
49604960
}
49614961

4962-
function compareTypes(source: Type, target: Type): Ternary {
4962+
function compareTypesIdentical(source: Type, target: Type): Ternary {
49634963
return checkTypeRelatedTo(source, target, identityRelation, /*errorNode*/ undefined) ? Ternary.True : Ternary.False;
49644964
}
49654965

@@ -4979,10 +4979,96 @@ namespace ts {
49794979
return checkTypeRelatedTo(source, target, assignableRelation, errorNode, headMessage, containingMessageChain);
49804980
}
49814981

4982-
function isSignatureAssignableTo(source: Signature, target: Signature): boolean {
4983-
const sourceType = getOrCreateTypeFromSignature(source);
4984-
const targetType = getOrCreateTypeFromSignature(target);
4985-
return checkTypeRelatedTo(sourceType, targetType, assignableRelation, /*errorNode*/ undefined);
4982+
/**
4983+
* See signatureRelatedTo, compareSignaturesIdentical
4984+
*/
4985+
function isSignatureAssignableTo(source: Signature, target: Signature, ignoreReturnTypes: boolean): boolean {
4986+
// TODO (drosen): De-duplicate code between related functions.
4987+
if (source === target) {
4988+
return true;
4989+
}
4990+
if (!target.hasRestParameter && source.minArgumentCount > target.parameters.length) {
4991+
return false;
4992+
}
4993+
4994+
// Spec 1.0 Section 3.8.3 & 3.8.4:
4995+
// M and N (the signatures) are instantiated using type Any as the type argument for all type parameters declared by M and N
4996+
source = getErasedSignature(source);
4997+
target = getErasedSignature(target);
4998+
4999+
const sourceMax = getNumNonRestParameters(source);
5000+
const targetMax = getNumNonRestParameters(target);
5001+
const checkCount = getNumParametersToCheckForSignatureRelatability(source, sourceMax, target, targetMax);
5002+
for (let i = 0; i < checkCount; i++) {
5003+
const s = i < sourceMax ? getTypeOfSymbol(source.parameters[i]) : getRestTypeOfSignature(source);
5004+
const t = i < targetMax ? getTypeOfSymbol(target.parameters[i]) : getRestTypeOfSignature(target);
5005+
const related = isTypeAssignableTo(t, s) || isTypeAssignableTo(s, t);
5006+
if (!related) {
5007+
return false;
5008+
}
5009+
}
5010+
5011+
if (!ignoreReturnTypes) {
5012+
const targetReturnType = getReturnTypeOfSignature(target);
5013+
if (targetReturnType === voidType) {
5014+
return true;
5015+
}
5016+
const sourceReturnType = getReturnTypeOfSignature(source);
5017+
5018+
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
5019+
if (targetReturnType.flags & TypeFlags.PredicateType && (targetReturnType as PredicateType).predicate.kind === TypePredicateKind.Identifier) {
5020+
if (!(sourceReturnType.flags & TypeFlags.PredicateType)) {
5021+
return false;
5022+
}
5023+
}
5024+
5025+
return isTypeAssignableTo(sourceReturnType, targetReturnType);
5026+
}
5027+
5028+
return true;
5029+
}
5030+
5031+
function isImplementationCompatibleWithOverload(implementation: Signature, overload: Signature): boolean {
5032+
const erasedSource = getErasedSignature(implementation);
5033+
const erasedTarget = getErasedSignature(overload);
5034+
5035+
// First see if the return types are compatible in either direction.
5036+
const sourceReturnType = getReturnTypeOfSignature(erasedSource);
5037+
const targetReturnType = getReturnTypeOfSignature(erasedTarget);
5038+
if (targetReturnType === voidType
5039+
|| checkTypeRelatedTo(targetReturnType, sourceReturnType, assignableRelation, /*errorNode*/ undefined)
5040+
|| checkTypeRelatedTo(sourceReturnType, targetReturnType, assignableRelation, /*errorNode*/ undefined)) {
5041+
5042+
return isSignatureAssignableTo(erasedSource, erasedTarget, /*ignoreReturnTypes*/ true);
5043+
}
5044+
5045+
return false;
5046+
}
5047+
5048+
function getNumNonRestParameters(signature: Signature) {
5049+
const numParams = signature.parameters.length;
5050+
return signature.hasRestParameter ?
5051+
numParams - 1 :
5052+
numParams;
5053+
}
5054+
5055+
function getNumParametersToCheckForSignatureRelatability(source: Signature, sourceNonRestParamCount: number, target: Signature, targetNonRestParamCount: number) {
5056+
if (source.hasRestParameter === target.hasRestParameter) {
5057+
if (source.hasRestParameter) {
5058+
// If both have rest parameters, get the max and add 1 to
5059+
// compensate for the rest parameter.
5060+
return Math.max(sourceNonRestParamCount, targetNonRestParamCount) + 1;
5061+
}
5062+
else {
5063+
return Math.min(sourceNonRestParamCount, targetNonRestParamCount);
5064+
}
5065+
}
5066+
else {
5067+
// Return the count for whichever signature doesn't have rest parameters.
5068+
return source.hasRestParameter ?
5069+
targetNonRestParamCount :
5070+
sourceNonRestParamCount;
5071+
}
49865072
}
49875073

49885074
/**
@@ -5574,7 +5660,7 @@ namespace ts {
55745660
shouldElaborateErrors = false;
55755661
}
55765662
}
5577-
// don't elaborate the primitive apparent types (like Number)
5663+
// don't elaborate the primitive apparent types (like Number)
55785664
// because the actual primitives will have already been reported.
55795665
if (shouldElaborateErrors && !isPrimitiveApparentType(source)) {
55805666
reportError(Diagnostics.Type_0_provides_no_match_for_the_signature_1,
@@ -5621,7 +5707,11 @@ namespace ts {
56215707
}
56225708
}
56235709

5710+
/**
5711+
* See signatureAssignableTo, signatureAssignableTo
5712+
*/
56245713
function signatureRelatedTo(source: Signature, target: Signature, reportErrors: boolean): Ternary {
5714+
// TODO (drosen): De-duplicate code between related functions.
56255715
if (source === target) {
56265716
return Ternary.True;
56275717
}
@@ -5673,10 +5763,12 @@ namespace ts {
56735763
}
56745764

56755765
const targetReturnType = getReturnTypeOfSignature(target);
5676-
if (targetReturnType === voidType) return result;
5766+
if (targetReturnType === voidType) {
5767+
return result;
5768+
}
56775769
const sourceReturnType = getReturnTypeOfSignature(source);
56785770

5679-
// The follow block preserves old behavior forbidding boolean returning functions from being assignable to type guard returning functions
5771+
// The following block preserves behavior forbidding boolean returning functions from being assignable to type guard returning functions
56805772
if (targetReturnType.flags & TypeFlags.PredicateType && (targetReturnType as PredicateType).predicate.kind === TypePredicateKind.Identifier) {
56815773
if (!(sourceReturnType.flags & TypeFlags.PredicateType)) {
56825774
if (reportErrors) {
@@ -5697,7 +5789,7 @@ namespace ts {
56975789
}
56985790
let result = Ternary.True;
56995791
for (let i = 0, len = sourceSignatures.length; i < len; ++i) {
5700-
const related = compareSignatures(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo);
5792+
const related = compareSignaturesIdentical(sourceSignatures[i], targetSignatures[i], /*partialMatch*/ false, /*ignoreReturnTypes*/ false, isRelatedTo);
57015793
if (!related) {
57025794
return Ternary.False;
57035795
}
@@ -5830,7 +5922,7 @@ namespace ts {
58305922
}
58315923

58325924
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
5833-
return compareProperties(sourceProp, targetProp, compareTypes) !== Ternary.False;
5925+
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
58345926
}
58355927

58365928
function compareProperties(sourceProp: Symbol, targetProp: Symbol, compareTypes: (source: Type, target: Type) => Ternary): Ternary {
@@ -5877,7 +5969,11 @@ namespace ts {
58775969
return false;
58785970
}
58795971

5880-
function compareSignatures(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
5972+
/**
5973+
* See signatureRelatedTo, compareSignaturesIdentical
5974+
*/
5975+
function compareSignaturesIdentical(source: Signature, target: Signature, partialMatch: boolean, ignoreReturnTypes: boolean, compareTypes: (s: Type, t: Type) => Ternary): Ternary {
5976+
// TODO (drosen): De-duplicate code between related functions.
58815977
if (source === target) {
58825978
return Ternary.True;
58835979
}
@@ -7585,7 +7681,7 @@ namespace ts {
75857681
// This signature will contribute to contextual union signature
75867682
signatureList = [signature];
75877683
}
7588-
else if (!compareSignatures(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypes)) {
7684+
else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
75897685
// Signatures aren't identical, do not use
75907686
return undefined;
75917687
}
@@ -11606,7 +11702,7 @@ namespace ts {
1160611702
}
1160711703

1160811704
for (const otherSignature of signaturesToCheck) {
11609-
if (!otherSignature.hasStringLiterals && isSignatureAssignableTo(signature, otherSignature)) {
11705+
if (!otherSignature.hasStringLiterals && isSignatureAssignableTo(signature, otherSignature, /*ignoreReturnTypes*/ false)) {
1161011706
return;
1161111707
}
1161211708
}
@@ -11853,7 +11949,7 @@ namespace ts {
1185311949
//
1185411950
// The implementation is completely unrelated to the specialized signature, yet we do not check this.
1185511951
for (const signature of signatures) {
11856-
if (!signature.hasStringLiterals && !isSignatureAssignableTo(bodySignature, signature)) {
11952+
if (!signature.hasStringLiterals && !isImplementationCompatibleWithOverload(bodySignature, signature)) {
1185711953
error(signature.declaration, Diagnostics.Overload_signature_is_not_compatible_with_function_implementation);
1185811954
break;
1185911955
}

tests/baselines/reference/conformanceFunctionOverloads.js

Lines changed: 0 additions & 37 deletions
This file was deleted.

tests/baselines/reference/conformanceFunctionOverloads.symbols

Lines changed: 0 additions & 25 deletions
This file was deleted.

tests/baselines/reference/conformanceFunctionOverloads.types

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid01.ts(1,10): error TS2394: Overload signature is not compatible with function implementation.
2+
3+
4+
==== tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid01.ts (1 errors) ====
5+
function f(x: string): number;
6+
~
7+
!!! error TS2394: Overload signature is not compatible with function implementation.
8+
function f(x: string): void {
9+
return;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [functionOverloadCompatibilityWithVoid01.ts]
2+
function f(x: string): number;
3+
function f(x: string): void {
4+
return;
5+
}
6+
7+
//// [functionOverloadCompatibilityWithVoid01.js]
8+
function f(x) {
9+
return;
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [functionOverloadCompatibilityWithVoid02.ts]
2+
function f(x: string): void;
3+
function f(x: string): number {
4+
return 0;
5+
}
6+
7+
//// [functionOverloadCompatibilityWithVoid02.js]
8+
function f(x) {
9+
return 0;
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid02.ts ===
2+
function f(x: string): void;
3+
>f : Symbol(f, Decl(functionOverloadCompatibilityWithVoid02.ts, 0, 0), Decl(functionOverloadCompatibilityWithVoid02.ts, 0, 28))
4+
>x : Symbol(x, Decl(functionOverloadCompatibilityWithVoid02.ts, 0, 11))
5+
6+
function f(x: string): number {
7+
>f : Symbol(f, Decl(functionOverloadCompatibilityWithVoid02.ts, 0, 0), Decl(functionOverloadCompatibilityWithVoid02.ts, 0, 28))
8+
>x : Symbol(x, Decl(functionOverloadCompatibilityWithVoid02.ts, 1, 11))
9+
10+
return 0;
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid02.ts ===
2+
function f(x: string): void;
3+
>f : (x: string) => void
4+
>x : string
5+
6+
function f(x: string): number {
7+
>f : (x: string) => void
8+
>x : string
9+
10+
return 0;
11+
>0 : number
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//// [functionOverloadCompatibilityWithVoid03.ts]
2+
function f(x: string): void;
3+
function f(x: string): void {
4+
return;
5+
}
6+
7+
//// [functionOverloadCompatibilityWithVoid03.js]
8+
function f(x) {
9+
return;
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid03.ts ===
2+
function f(x: string): void;
3+
>f : Symbol(f, Decl(functionOverloadCompatibilityWithVoid03.ts, 0, 0), Decl(functionOverloadCompatibilityWithVoid03.ts, 0, 28))
4+
>x : Symbol(x, Decl(functionOverloadCompatibilityWithVoid03.ts, 0, 11))
5+
6+
function f(x: string): void {
7+
>f : Symbol(f, Decl(functionOverloadCompatibilityWithVoid03.ts, 0, 0), Decl(functionOverloadCompatibilityWithVoid03.ts, 0, 28))
8+
>x : Symbol(x, Decl(functionOverloadCompatibilityWithVoid03.ts, 1, 11))
9+
10+
return;
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=== tests/cases/conformance/functions/functionOverloadCompatibilityWithVoid03.ts ===
2+
function f(x: string): void;
3+
>f : (x: string) => void
4+
>x : string
5+
6+
function f(x: string): void {
7+
>f : (x: string) => void
8+
>x : string
9+
10+
return;
11+
}

0 commit comments

Comments
 (0)