Skip to content

Commit 57eb903

Browse files
committed
Only allow extra members that dont contain any type variables
1 parent 71beb36 commit 57eb903

6 files changed

+101
-9
lines changed

src/compiler/checker.ts

+15-8
Original file line numberDiff line numberDiff line change
@@ -32166,18 +32166,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3216632166

3216732167
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
3216832168
function getSingleCallSignature(type: Type): Signature | undefined {
32169-
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false);
32169+
return getSingleSignature(type, SignatureKind.Call, /*allowMembersWithTypeVariables*/ false);
3217032170
}
3217132171

3217232172
function getSingleCallOrConstructSignature(type: Type): Signature | undefined {
32173-
return getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ false) ||
32174-
getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ false);
32173+
return getSingleSignature(type, SignatureKind.Call, /*allowMembersWithTypeVariables*/ false) ||
32174+
getSingleSignature(type, SignatureKind.Construct, /*allowMembersWithTypeVariables*/ false);
3217532175
}
3217632176

32177-
function getSingleSignature(type: Type, kind: SignatureKind, allowMembers: boolean): Signature | undefined {
32177+
function couldMembersContainTypeVariables(resolved: ResolvedType) {
32178+
return some(resolved.properties, sym => couldContainTypeVariables(getTypeOfSymbol(sym))) ||
32179+
some(resolved.indexInfos, info => couldContainTypeVariables(info.type));
32180+
}
32181+
32182+
function getSingleSignature(type: Type, kind: SignatureKind, allowMembersWithTypeVariables: boolean): Signature | undefined {
3217832183
if (type.flags & TypeFlags.Object) {
3217932184
const resolved = resolveStructuredTypeMembers(type as ObjectType);
32180-
if (allowMembers || resolved.properties.length === 0 && resolved.indexInfos.length === 0) {
32185+
// We could validate that the other members don't reference the type parameters we want to promote to the signature.
32186+
// That's pretty hard, so a reasonable proxy is checking that the other members of the type don't reference any type variables at all.
32187+
if (allowMembersWithTypeVariables || !couldMembersContainTypeVariables(resolved)) {
3218132188
if (kind === SignatureKind.Call && resolved.callSignatures.length === 1 && resolved.constructSignatures.length === 0) {
3218232189
return resolved.callSignatures[0];
3218332190
}
@@ -37346,13 +37353,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3734637353

3734737354
function instantiateTypeWithSingleGenericCallSignature(node: Expression | MethodDeclaration | QualifiedName, type: Type, checkMode?: CheckMode) {
3734837355
if (checkMode && checkMode & (CheckMode.Inferential | CheckMode.SkipGenericFunctions)) {
37349-
const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembers*/ true);
37350-
const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembers*/ true);
37356+
const callSignature = getSingleSignature(type, SignatureKind.Call, /*allowMembersWithTypeVariables*/ true);
37357+
const constructSignature = getSingleSignature(type, SignatureKind.Construct, /*allowMembersWithTypeVariables*/ true);
3735137358
const signature = callSignature || constructSignature;
3735237359
if (signature && signature.typeParameters) {
3735337360
const contextualType = getApparentTypeOfContextualType(node as Expression, ContextFlags.NoConstraints);
3735437361
if (contextualType) {
37355-
const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembers*/ true);
37362+
const contextualSignature = getSingleSignature(getNonNullableType(contextualType), callSignature ? SignatureKind.Call : SignatureKind.Construct, /*allowMembersWithTypeVariables*/ false);
3735637363
if (contextualSignature && !contextualSignature.typeParameters) {
3735737364
if (checkMode & CheckMode.SkipGenericFunctions) {
3735837365
skippedGenericFunction(node, checkMode);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
tests/cases/compiler/genericInferenceContextualTypeAsSignatureWithProperties.ts(17,15): error TS2345: Argument of type '(arg: 'a' | 'b') => void' is not assignable to parameter of type '(arg: "a" | "b" | "c") => void'.
2+
Types of parameters 'arg' and 'arg' are incompatible.
3+
Type '"a" | "b" | "c"' is not assignable to type '"a" | "b"'.
4+
Type '"c"' is not assignable to type '"a" | "b"'.
5+
6+
7+
==== tests/cases/compiler/genericInferenceContextualTypeAsSignatureWithProperties.ts (1 errors) ====
8+
// repro from #52262
9+
10+
const inner = <T extends 'a' | 'b' | 'c'>(cb: (arg: T) => void) => {};
11+
12+
interface FuncB<T> {
13+
(arg: T): void;
14+
x?: string;
15+
};
16+
const outerB = <T,>(func: FuncB<T>, arg: T) => {};
17+
outerB(inner, (arg: 'a' | 'b') => {});
18+
19+
interface FuncC<T> {
20+
(arg: T): void;
21+
x?: T;
22+
};
23+
const outerC = <T,>(func: FuncC<T>, arg: T) => {};
24+
outerC(inner, (arg: 'a' | 'b') => {}); // error
25+
~~~~~~~~~~~~~~~~~~~~~~
26+
!!! error TS2345: Argument of type '(arg: 'a' | 'b') => void' is not assignable to parameter of type '(arg: "a" | "b" | "c") => void'.
27+
!!! error TS2345: Types of parameters 'arg' and 'arg' are incompatible.
28+
!!! error TS2345: Type '"a" | "b" | "c"' is not assignable to type '"a" | "b"'.
29+
!!! error TS2345: Type '"c"' is not assignable to type '"a" | "b"'.
30+

tests/baselines/reference/genericInferenceContextualTypeAsSignatureWithProperties.symbols

+27
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,30 @@ outerB(inner, (arg: 'a' | 'b') => {});
3434
>inner : Symbol(inner, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 2, 5))
3535
>arg : Symbol(arg, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 9, 15))
3636

37+
interface FuncC<T> {
38+
>FuncC : Symbol(FuncC, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 9, 38))
39+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 11, 16))
40+
41+
(arg: T): void;
42+
>arg : Symbol(arg, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 12, 3))
43+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 11, 16))
44+
45+
x?: T;
46+
>x : Symbol(FuncC.x, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 12, 17))
47+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 11, 16))
48+
49+
};
50+
const outerC = <T,>(func: FuncC<T>, arg: T) => {};
51+
>outerC : Symbol(outerC, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 5))
52+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 16))
53+
>func : Symbol(func, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 20))
54+
>FuncC : Symbol(FuncC, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 9, 38))
55+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 16))
56+
>arg : Symbol(arg, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 35))
57+
>T : Symbol(T, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 16))
58+
59+
outerC(inner, (arg: 'a' | 'b') => {}); // error
60+
>outerC : Symbol(outerC, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 15, 5))
61+
>inner : Symbol(inner, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 2, 5))
62+
>arg : Symbol(arg, Decl(genericInferenceContextualTypeAsSignatureWithProperties.ts, 16, 15))
63+

tests/baselines/reference/genericInferenceContextualTypeAsSignatureWithProperties.types

+21
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,24 @@ outerB(inner, (arg: 'a' | 'b') => {});
2828
>(arg: 'a' | 'b') => {} : (arg: 'a' | 'b') => void
2929
>arg : "a" | "b"
3030

31+
interface FuncC<T> {
32+
(arg: T): void;
33+
>arg : T
34+
35+
x?: T;
36+
>x : T | undefined
37+
38+
};
39+
const outerC = <T,>(func: FuncC<T>, arg: T) => {};
40+
>outerC : <T>(func: FuncC<T>, arg: T) => void
41+
><T,>(func: FuncC<T>, arg: T) => {} : <T>(func: FuncC<T>, arg: T) => void
42+
>func : FuncC<T>
43+
>arg : T
44+
45+
outerC(inner, (arg: 'a' | 'b') => {}); // error
46+
>outerC(inner, (arg: 'a' | 'b') => {}) : void
47+
>outerC : <T>(func: FuncC<T>, arg: T) => void
48+
>inner : <T extends "a" | "b" | "c">(cb: (arg: T) => void) => void
49+
>(arg: 'a' | 'b') => {} : (arg: 'a' | 'b') => void
50+
>arg : "a" | "b"
51+

tests/baselines/reference/jsDeclarationsReactComponents.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import React from "react";
6161
*/
6262
const TabbedShowLayout = () => {
6363
>TabbedShowLayout : React.SFC<{}>
64-
>() => { return ( <div className="" key=""> ok </div> );} : { (): JSX.Element; defaultProps: Partial<{}> | undefined; }
64+
>() => { return ( <div className="" key=""> ok </div> );} : { (): React.ReactElement<any> | null; defaultProps: Partial<{}> | undefined; }
6565

6666
return (
6767
>( <div className="" key=""> ok </div> ) : JSX.Element

tests/cases/compiler/genericInferenceContextualTypeAsSignatureWithProperties.ts

+7
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,10 @@ interface FuncB<T> {
1111
};
1212
const outerB = <T,>(func: FuncB<T>, arg: T) => {};
1313
outerB(inner, (arg: 'a' | 'b') => {});
14+
15+
interface FuncC<T> {
16+
(arg: T): void;
17+
x?: T;
18+
};
19+
const outerC = <T,>(func: FuncC<T>, arg: T) => {};
20+
outerC(inner, (arg: 'a' | 'b') => {}); // error

0 commit comments

Comments
 (0)