Skip to content

Commit c11d001

Browse files
committed
Always combine nonlike call signatures, regarless of source type
1 parent a462c60 commit c11d001

9 files changed

+214
-54
lines changed

src/compiler/checker.ts

+40-43
Original file line numberDiff line numberDiff line change
@@ -6597,8 +6597,8 @@ namespace ts {
65976597
return signature.resolvedReturnType;
65986598
}
65996599

6600-
function isResolvingReturnTypeOfSignature(signature: Signature) {
6601-
return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0;
6600+
function isResolvingReturnTypeOfSignature(signature: Signature): boolean {
6601+
return !signature.resolvedReturnType && findResolutionCycleStartIndex(signature, TypeSystemPropertyName.ResolvedReturnType) >= 0 || (signature.unionSignatures && some(signature.unionSignatures, s => isResolvingReturnTypeOfSignature(s)));
66026602
}
66036603

66046604
function getRestTypeOfSignature(signature: Signature): Type {
@@ -13329,14 +13329,8 @@ namespace ts {
1332913329

1333013330
// If the given type is an object or union type with a single signature, and if that signature has at
1333113331
// least as many parameters as the given function, return the signature. Otherwise return undefined.
13332-
function getContextualCallSignature(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature {
13333-
const signatures = getSignaturesOfStructuredType(type, SignatureKind.Call);
13334-
if (signatures.length === 1) {
13335-
const signature = signatures[0];
13336-
if (!isAritySmaller(signature, node)) {
13337-
return signature;
13338-
}
13339-
}
13332+
function getContextualCallSignatures(type: Type, node: FunctionExpression | ArrowFunction | MethodDeclaration): Signature[] {
13333+
return filter(getSignaturesOfStructuredType(type, SignatureKind.Call), s => !isAritySmaller(s, node));
1334013334
}
1334113335

1334213336
/** If the contextual signature has fewer parameters than the function expression, do not use it */
@@ -13372,7 +13366,7 @@ namespace ts {
1337213366
getApparentTypeOfContextualType(node);
1337313367
}
1337413368

13375-
function combineSignatures(signatureList: Signature[]): Signature {
13369+
function combineSignatures(signatureList: Signature[], declaration?: SignatureDeclaration): Signature {
1337613370
// Produce a synthetic signature whose arguments are a union of the parameters of the inferred signatures and whose return type is an intersection
1337713371
let parameters: Symbol[];
1337813372
let minimumParameterCount = Number.POSITIVE_INFINITY;
@@ -13428,8 +13422,9 @@ namespace ts {
1342813422
parameterTypes.push(type);
1342913423
}
1343013424
}
13431-
// We do this so the name is reasonable for users
13432-
const paramName = escapeLeadingUnderscores(map(parameterNames, unescapeLeadingUnderscores).join("or"));
13425+
// We do this so the name is reasonable for users; however it is very rare for this symbol name to appear anywhere user-facing, as the result signature is used only for contextual typing
13426+
const canUseUserSuppliedName = declaration && declaration.parameters && declaration.parameters[i] && isIdentifier(declaration.parameters[i].name);
13427+
const paramName = canUseUserSuppliedName ? (declaration.parameters[i].name as Identifier).escapedText : escapeLeadingUnderscores(map(parameterNames, unescapeLeadingUnderscores).join("or"));
1343313428
const paramSymbol = createSymbol(SymbolFlags.FunctionScopedVariable, paramName);
1343413429
paramSymbol.type = getUnionType(parameterTypes);
1343513430
(parameters || (parameters = [])).push(paramSymbol);
@@ -13451,7 +13446,7 @@ namespace ts {
1345113446

1345213447
// TODO (weswigham): Merge type predicates?
1345313448
return createSignature(
13454-
/*declaration*/ undefined,
13449+
declaration,
1345513450
map(flatMap(signatureList, s => s.typeParameters), cloneTypeParameter),
1345613451
thisParameterSymbol,
1345713452
parameters,
@@ -13474,43 +13469,45 @@ namespace ts {
1347413469
if (!type) {
1347513470
return undefined;
1347613471
}
13472+
let signatureList: Signature[];
1347713473
if (!(type.flags & TypeFlags.Union)) {
13478-
return getContextualCallSignature(type, node);
13474+
signatureList = getContextualCallSignatures(type, node);
1347913475
}
13480-
let signatureList: Signature[];
13481-
const types = (<UnionType>type).types;
13482-
let mismatchedSignatures = false;
13483-
for (const current of types) {
13484-
const signature = getContextualCallSignature(current, node);
13485-
if (signature) {
13486-
if (!signatureList) {
13487-
// This signature will contribute to contextual union signature
13488-
signatureList = [signature];
13489-
}
13490-
else if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
13491-
// Signatures aren't identical, set flag to union parameter types, intersect return types
13492-
signatureList.push(signature);
13493-
mismatchedSignatures = true;
13494-
}
13495-
else {
13496-
// Use this signature for contextual union signature
13497-
signatureList.push(signature);
13476+
else {
13477+
const types = (<UnionType>type).types;
13478+
for (const current of types) {
13479+
const signatures = getContextualCallSignatures(current, node);
13480+
if (signatures && signatures.length) {
13481+
if (!signatureList) {
13482+
signatureList = signatures;
13483+
}
13484+
else {
13485+
signatureList = signatureList.concat(signatures);
13486+
}
1349813487
}
1349913488
}
1350013489
}
1350113490

13502-
if (mismatchedSignatures) {
13503-
return combineSignatures(signatureList);
13491+
if (!(signatureList && signatureList.length)) {
13492+
return undefined;
1350413493
}
1350513494

13506-
// Result is union of signatures collected (return type is union of return types of this signature set)
13507-
let result: Signature;
13508-
if (signatureList) {
13509-
result = cloneSignature(signatureList[0]);
13510-
// Clear resolved return type we possibly got from cloneSignature
13511-
result.resolvedReturnType = undefined;
13512-
result.unionSignatures = signatureList;
13495+
if (signatureList.length === 1) {
13496+
return signatureList[0];
1351313497
}
13498+
13499+
for (const signature of signatureList) {
13500+
if (!compareSignaturesIdentical(signatureList[0], signature, /*partialMatch*/ false, /*ignoreThisTypes*/ true, /*ignoreReturnTypes*/ true, compareTypesIdentical)) {
13501+
// Signatures not simply identical, combine them
13502+
return combineSignatures(signatureList, node);
13503+
}
13504+
}
13505+
13506+
// Result is union of signatures collected (return type is union of return types of this signature set)
13507+
const result = cloneSignature(signatureList[0]);
13508+
// Clear resolved return type we possibly got from cloneSignature
13509+
result.resolvedReturnType = undefined;
13510+
result.unionSignatures = signatureList;
1351413511
return result;
1351513512
}
1351613513

@@ -16989,7 +16986,7 @@ namespace ts {
1698916986
if (isUnitType(type) &&
1699016987
!(contextualSignature &&
1699116988
isLiteralContextualType(
16992-
contextualSignature === getSignatureFromDeclaration(func) ? type : getReturnTypeOfSignature(contextualSignature)))) {
16989+
(contextualSignature === getSignatureFromDeclaration(func) || contextualSignature.unionSignatures && some(contextualSignature.unionSignatures, s => s === getSignatureFromDeclaration(func))) ? type : getReturnTypeOfSignature(contextualSignature)))) {
1699316990
type = getWidenedLiteralType(type);
1699416991
}
1699516992

tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.symbols

+2-2
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ var x4: IWithCallSignatures | IWithCallSignatures4 = a => /*here a should be any
8484
>IWithCallSignatures : Symbol(IWithCallSignatures, Decl(contextualTypeWithUnionTypeCallSignatures.ts, 9, 1))
8585
>IWithCallSignatures4 : Symbol(IWithCallSignatures4, Decl(contextualTypeWithUnionTypeCallSignatures.ts, 18, 1))
8686
>a : Symbol(a, Decl(contextualTypeWithUnionTypeCallSignatures.ts, 35, 52))
87-
>a.toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
87+
>a.toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
8888
>a : Symbol(a, Decl(contextualTypeWithUnionTypeCallSignatures.ts, 35, 52))
89-
>toString : Symbol(Number.toString, Decl(lib.d.ts, --, --))
89+
>toString : Symbol(toString, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --))
9090

tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ var x4: IWithCallSignatures | IWithCallSignatures4 = a => /*here a should be any
9090
>x4 : IWithCallSignatures | IWithCallSignatures4
9191
>IWithCallSignatures : IWithCallSignatures
9292
>IWithCallSignatures4 : IWithCallSignatures4
93-
>a => /*here a should be any*/ a.toString() : (a: number) => string
94-
>a : number
93+
>a => /*here a should be any*/ a.toString() : (a: string | number) => string
94+
>a : string | number
9595
>a.toString() : string
96-
>a.toString : (radix?: number) => string
97-
>a : number
98-
>toString : (radix?: number) => string
96+
>a.toString : ((radix?: number) => string) | (() => string)
97+
>a : string | number
98+
>toString : ((radix?: number) => string) | (() => string)
9999

tests/baselines/reference/contextualTyping.errors.txt

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
tests/cases/compiler/contextualTyping.ts(36,5): error TS2322: Type '(n: string | number) => string | number' is not assignable to type '{ (n: number): number; (s1: string): number; }'.
2+
Type 'string | number' is not assignable to type 'number'.
3+
Type 'string' is not assignable to type 'number'.
14
tests/cases/compiler/contextualTyping.ts(189,18): error TS2384: Overload signatures must all be ambient or non-ambient.
25
tests/cases/compiler/contextualTyping.ts(197,15): error TS2300: Duplicate identifier 'Point'.
36
tests/cases/compiler/contextualTyping.ts(207,10): error TS2300: Duplicate identifier 'Point'.
47
tests/cases/compiler/contextualTyping.ts(230,5): error TS2322: Type '{}' is not assignable to type 'B'.
58
Property 'x' is missing in type '{}'.
69

710

8-
==== tests/cases/compiler/contextualTyping.ts (4 errors) ====
11+
==== tests/cases/compiler/contextualTyping.ts (5 errors) ====
912
// DEFAULT INTERFACES
1013
interface IFoo {
1114
n: number;
@@ -42,6 +45,10 @@ tests/cases/compiler/contextualTyping.ts(230,5): error TS2322: Type '{}' is not
4245
var c3t5: (n: number) => IFoo = function(n) { return <IFoo>({}) };
4346
var c3t6: (n: number, s: string) => IFoo = function(n, s) { return <IFoo>({}) };
4447
var c3t7: {
48+
~~~~
49+
!!! error TS2322: Type '(n: string | number) => string | number' is not assignable to type '{ (n: number): number; (s1: string): number; }'.
50+
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
51+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
4552
(n: number): number;
4653
(s1: string): number;
4754
} = function(n) { return n; };

tests/baselines/reference/contextualTypingOfLambdaWithMultipleSignatures.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ var foo: Foo;
1616
>Foo : Foo
1717

1818
foo.getFoo = bar => { };
19-
>foo.getFoo = bar => { } : (bar: any) => void
19+
>foo.getFoo = bar => { } : (bar: string | number) => void
2020
>foo.getFoo : { (n: number): void; (s: string): void; }
2121
>foo : Foo
2222
>getFoo : { (n: number): void; (s: string): void; }
23-
>bar => { } : (bar: any) => void
24-
>bar : any
23+
>bar => { } : (bar: string | number) => void
24+
>bar : string | number
2525

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
tests/cases/compiler/contextualTypingOfLambdaWithMultipleSignatures2.ts(6,23): error TS2339: Property 'asdf' does not exist on type 'string | number'.
2+
Property 'asdf' does not exist on type 'string'.
3+
4+
5+
==== tests/cases/compiler/contextualTypingOfLambdaWithMultipleSignatures2.ts (1 errors) ====
6+
var f: {
7+
(x: string): string;
8+
(x: number): string
9+
};
10+
11+
f = (a) => { return a.asdf }
12+
~~~~
13+
!!! error TS2339: Property 'asdf' does not exist on type 'string | number'.
14+
!!! error TS2339: Property 'asdf' does not exist on type 'string'.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
tests/cases/compiler/contextualTypingWithGenericAndNonGenericSignature.ts(8,1): error TS2322: Type '<T, U>(x: string | T, y: number | U) => string | T' is not assignable to type '{ (x: string, y: number): string; <T, U>(x: T, y: U): T; }'.
2+
Type 'string | T' is not assignable to type 'string'.
3+
Type 'T' is not assignable to type 'string'.
4+
tests/cases/compiler/contextualTypingWithGenericAndNonGenericSignature.ts(15,1): error TS2322: Type '<T, U>(x: string | T, y: number | U) => string | T' is not assignable to type '{ <T, U>(x: T, y: U): T; (x: string, y: number): string; }'.
5+
Type 'string | T' is not assignable to type 'string'.
6+
Type 'T' is not assignable to type 'string'.
7+
8+
9+
==== tests/cases/compiler/contextualTypingWithGenericAndNonGenericSignature.ts (2 errors) ====
10+
//• If e is a FunctionExpression or ArrowFunctionExpression with no type parameters and no parameter or return type annotations, and T is a function type with EXACTLY ONE non - generic call signature, then any inferences made for type parameters referenced by the parameters of T’s call signature are fixed(section 4.12.2) and e is processed with the contextual type T, as described in section 4.9.3.
11+
12+
var f2: {
13+
(x: string, y: number): string;
14+
<T, U>(x: T, y: U): T
15+
};
16+
17+
f2 = (x, y) => { return x }
18+
~~
19+
!!! error TS2322: Type '<T, U>(x: string | T, y: number | U) => string | T' is not assignable to type '{ (x: string, y: number): string; <T, U>(x: T, y: U): T; }'.
20+
!!! error TS2322: Type 'string | T' is not assignable to type 'string'.
21+
!!! error TS2322: Type 'T' is not assignable to type 'string'.
22+
23+
var f3: {
24+
<T, U>(x: T, y: U): T
25+
(x: string, y: number): string;
26+
};
27+
28+
f3 = (x, y) => { return x }
29+
~~
30+
!!! error TS2322: Type '<T, U>(x: string | T, y: number | U) => string | T' is not assignable to type '{ <T, U>(x: T, y: U): T; (x: string, y: number): string; }'.
31+
!!! error TS2322: Type 'string | T' is not assignable to type 'string'.
32+
!!! error TS2322: Type 'T' is not assignable to type 'string'.
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
tests/cases/conformance/types/specifyingTypes/typeLiterals/functionLiteralForOverloads.ts(3,5): error TS2322: Type '(x: string | number) => string | number' is not assignable to type '{ (x: string): string; (x: number): number; }'.
2+
Type 'string | number' is not assignable to type 'string'.
3+
Type 'number' is not assignable to type 'string'.
4+
tests/cases/conformance/types/specifyingTypes/typeLiterals/functionLiteralForOverloads.ts(8,5): error TS2322: Type '<T, T>(x: string | number) => string | number' is not assignable to type '{ <T>(x: string): string; <T>(x: number): number; }'.
5+
Type 'string | number' is not assignable to type 'string'.
6+
Type 'number' is not assignable to type 'string'.
7+
8+
9+
==== tests/cases/conformance/types/specifyingTypes/typeLiterals/functionLiteralForOverloads.ts (2 errors) ====
10+
// basic uses of function literals with overloads
11+
12+
var f: {
13+
~
14+
!!! error TS2322: Type '(x: string | number) => string | number' is not assignable to type '{ (x: string): string; (x: number): number; }'.
15+
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
16+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
17+
(x: string): string;
18+
(x: number): number;
19+
} = (x) => x;
20+
21+
var f2: {
22+
~~
23+
!!! error TS2322: Type '<T, T>(x: string | number) => string | number' is not assignable to type '{ <T>(x: string): string; <T>(x: number): number; }'.
24+
!!! error TS2322: Type 'string | number' is not assignable to type 'string'.
25+
!!! error TS2322: Type 'number' is not assignable to type 'string'.
26+
<T>(x: string): string;
27+
<T>(x: number): number;
28+
} = (x) => x;
29+
30+
var f3: {
31+
<T>(x: T): string;
32+
<T>(x: T): number;
33+
} = (x) => x;
34+
35+
var f4: {
36+
<T>(x: string): T;
37+
<T>(x: number): T;
38+
} = (x) => x;

0 commit comments

Comments
 (0)