Skip to content

Commit aa2781d

Browse files
authored
Add missing type argument constraints check (#51766)
* Add missing type argument constraints check * Leverage existing routine for obtaining typeParameters
1 parent eb9252e commit aa2781d

9 files changed

+131
-4
lines changed

src/compiler/checker.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -17606,6 +17606,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1760617606
return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias
1760717607
}
1760817608
else {
17609+
const type = tryGetDeclaredTypeOfSymbol(resolvedSymbol); // call this first to ensure typeParameters is populated (if applicable)
17610+
const typeParameters = type && getTypeParametersForTypeAndSymbol(type, resolvedSymbol);
17611+
if (node.typeArguments && typeParameters) {
17612+
addLazyDiagnostic(() => {
17613+
checkTypeArgumentConstraints(node, typeParameters);
17614+
});
17615+
}
1760917616
return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol
1761017617
}
1761117618
}
@@ -37264,12 +37271,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3726437271
return getEffectiveTypeArguments(node, typeParameters)[index];
3726537272
}
3726637273

37267-
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
37274+
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
3726837275
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters,
3726937276
getMinTypeArgumentCount(typeParameters), isInJSFile(node));
3727037277
}
3727137278

37272-
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
37279+
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
3727337280
let typeArguments: Type[] | undefined;
3727437281
let mapper: TypeMapper | undefined;
3727537282
let result = true;
@@ -37290,13 +37297,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3729037297
return result;
3729137298
}
3729237299

37300+
function getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) {
37301+
if (!isErrorType(type)) {
37302+
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
37303+
(getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined);
37304+
}
37305+
return undefined;
37306+
}
37307+
3729337308
function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) {
3729437309
const type = getTypeFromTypeReference(node);
3729537310
if (!isErrorType(type)) {
3729637311
const symbol = getNodeLinks(node).resolvedSymbol;
3729737312
if (symbol) {
37298-
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
37299-
(getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined);
37313+
return getTypeParametersForTypeAndSymbol(type, symbol);
3730037314
}
3730137315
}
3730237316
return undefined;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/compiler/file2.ts(1,37): error TS2344: Type 'T' does not satisfy the constraint 'string'.
2+
3+
4+
==== tests/cases/compiler/file1.ts (0 errors) ====
5+
export type Foo<T extends string> = { foo: T }
6+
7+
==== tests/cases/compiler/file2.ts (1 errors) ====
8+
type Bar<T> = import('./file1').Foo<T>;
9+
~
10+
!!! error TS2344: Type 'T' does not satisfy the constraint 'string'.
11+
!!! related TS2208 tests/cases/compiler/file2.ts:1:10: This type parameter might need an `extends string` constraint.
12+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
=== tests/cases/compiler/file1.ts ===
2+
export type Foo<T extends string> = { foo: T }
3+
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))
4+
>T : Symbol(T, Decl(file1.ts, 0, 16))
5+
>foo : Symbol(foo, Decl(file1.ts, 0, 37))
6+
>T : Symbol(T, Decl(file1.ts, 0, 16))
7+
8+
=== tests/cases/compiler/file2.ts ===
9+
type Bar<T> = import('./file1').Foo<T>;
10+
>Bar : Symbol(Bar, Decl(file2.ts, 0, 0))
11+
>T : Symbol(T, Decl(file2.ts, 0, 9))
12+
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))
13+
>T : Symbol(T, Decl(file2.ts, 0, 9))
14+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/compiler/file1.ts ===
2+
export type Foo<T extends string> = { foo: T }
3+
>Foo : Foo<T>
4+
>foo : T
5+
6+
=== tests/cases/compiler/file2.ts ===
7+
type Bar<T> = import('./file1').Foo<T>;
8+
>Bar : Bar<T>
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
tests/cases/compiler/file2.js(3,36): error TS2344: Type 'T' does not satisfy the constraint 'string'.
2+
3+
4+
==== tests/cases/compiler/file1.js (0 errors) ====
5+
/**
6+
* @template {string} T
7+
* @typedef {{ foo: T }} Foo
8+
*/
9+
10+
export default {};
11+
12+
==== tests/cases/compiler/file2.js (1 errors) ====
13+
/**
14+
* @template T
15+
* @typedef {import('./file1').Foo<T>} Bar
16+
~
17+
!!! error TS2344: Type 'T' does not satisfy the constraint 'string'.
18+
!!! related TS2208 tests/cases/compiler/file2.js:2:14: This type parameter might need an `extends string` constraint.
19+
*/
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/compiler/file1.js ===
2+
3+
/**
4+
* @template {string} T
5+
* @typedef {{ foo: T }} Foo
6+
*/
7+
8+
export default {};
9+
10+
=== tests/cases/compiler/file2.js ===
11+
12+
/**
13+
* @template T
14+
* @typedef {import('./file1').Foo<T>} Bar
15+
*/
16+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/compiler/file1.js ===
2+
/**
3+
* @template {string} T
4+
* @typedef {{ foo: T }} Foo
5+
*/
6+
7+
export default {};
8+
>{} : {}
9+
10+
=== tests/cases/compiler/file2.js ===
11+
12+
/**
13+
* @template T
14+
* @typedef {import('./file1').Foo<T>} Bar
15+
*/
16+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// @noEmit: true
2+
// @filename: file1.ts
3+
export type Foo<T extends string> = { foo: T }
4+
5+
// @noEmit: true
6+
// @filename: file2.ts
7+
type Bar<T> = import('./file1').Foo<T>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @allowJs: true
2+
// @checkJs: true
3+
// @noEmit: true
4+
// @filename: file1.js
5+
/**
6+
* @template {string} T
7+
* @typedef {{ foo: T }} Foo
8+
*/
9+
10+
export default {};
11+
12+
// @allowJs: true
13+
// @checkJs: true
14+
// @noEmit: true
15+
// @filename: file2.js
16+
/**
17+
* @template T
18+
* @typedef {import('./file1').Foo<T>} Bar
19+
*/

0 commit comments

Comments
 (0)