Add missing type argument constraints check (#51766)

* Add missing type argument constraints check

* Leverage existing routine for obtaining typeParameters
This commit is contained in:
Tomasz Lenarcik 2022-12-14 20:20:57 +01:00 committed by GitHub
parent eb9252ed9e
commit aa2781d44f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 4 deletions

View File

@ -17606,6 +17606,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getTypeOfSymbol(symbol); // intentionally doesn't use resolved symbol so type is cached as expected on the alias
}
else {
const type = tryGetDeclaredTypeOfSymbol(resolvedSymbol); // call this first to ensure typeParameters is populated (if applicable)
const typeParameters = type && getTypeParametersForTypeAndSymbol(type, resolvedSymbol);
if (node.typeArguments && typeParameters) {
addLazyDiagnostic(() => {
checkTypeArgumentConstraints(node, typeParameters);
});
}
return getTypeReferenceType(node, resolvedSymbol); // getTypeReferenceType doesn't handle aliases - it must get the resolved symbol
}
}
@ -37264,12 +37271,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return getEffectiveTypeArguments(node, typeParameters)[index];
}
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
function getEffectiveTypeArguments(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): Type[] {
return fillMissingTypeArguments(map(node.typeArguments!, getTypeFromTypeNode), typeParameters,
getMinTypeArgumentCount(typeParameters), isInJSFile(node));
}
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
function checkTypeArgumentConstraints(node: TypeReferenceNode | ExpressionWithTypeArguments | NodeWithTypeArguments, typeParameters: readonly TypeParameter[]): boolean {
let typeArguments: Type[] | undefined;
let mapper: TypeMapper | undefined;
let result = true;
@ -37290,13 +37297,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return result;
}
function getTypeParametersForTypeAndSymbol(type: Type, symbol: Symbol) {
if (!isErrorType(type)) {
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
(getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined);
}
return undefined;
}
function getTypeParametersForTypeReference(node: TypeReferenceNode | ExpressionWithTypeArguments) {
const type = getTypeFromTypeReference(node);
if (!isErrorType(type)) {
const symbol = getNodeLinks(node).resolvedSymbol;
if (symbol) {
return symbol.flags & SymbolFlags.TypeAlias && getSymbolLinks(symbol).typeParameters ||
(getObjectFlags(type) & ObjectFlags.Reference ? (type as TypeReference).target.localTypeParameters : undefined);
return getTypeParametersForTypeAndSymbol(type, symbol);
}
}
return undefined;

View File

@ -0,0 +1,12 @@
tests/cases/compiler/file2.ts(1,37): error TS2344: Type 'T' does not satisfy the constraint 'string'.
==== tests/cases/compiler/file1.ts (0 errors) ====
export type Foo<T extends string> = { foo: T }
==== tests/cases/compiler/file2.ts (1 errors) ====
type Bar<T> = import('./file1').Foo<T>;
~
!!! error TS2344: Type 'T' does not satisfy the constraint 'string'.
!!! related TS2208 tests/cases/compiler/file2.ts:1:10: This type parameter might need an `extends string` constraint.

View File

@ -0,0 +1,14 @@
=== tests/cases/compiler/file1.ts ===
export type Foo<T extends string> = { foo: T }
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))
>T : Symbol(T, Decl(file1.ts, 0, 16))
>foo : Symbol(foo, Decl(file1.ts, 0, 37))
>T : Symbol(T, Decl(file1.ts, 0, 16))
=== tests/cases/compiler/file2.ts ===
type Bar<T> = import('./file1').Foo<T>;
>Bar : Symbol(Bar, Decl(file2.ts, 0, 0))
>T : Symbol(T, Decl(file2.ts, 0, 9))
>Foo : Symbol(Foo, Decl(file1.ts, 0, 0))
>T : Symbol(T, Decl(file2.ts, 0, 9))

View File

@ -0,0 +1,9 @@
=== tests/cases/compiler/file1.ts ===
export type Foo<T extends string> = { foo: T }
>Foo : Foo<T>
>foo : T
=== tests/cases/compiler/file2.ts ===
type Bar<T> = import('./file1').Foo<T>;
>Bar : Bar<T>

View File

@ -0,0 +1,20 @@
tests/cases/compiler/file2.js(3,36): error TS2344: Type 'T' does not satisfy the constraint 'string'.
==== tests/cases/compiler/file1.js (0 errors) ====
/**
* @template {string} T
* @typedef {{ foo: T }} Foo
*/
export default {};
==== tests/cases/compiler/file2.js (1 errors) ====
/**
* @template T
* @typedef {import('./file1').Foo<T>} Bar
~
!!! error TS2344: Type 'T' does not satisfy the constraint 'string'.
!!! related TS2208 tests/cases/compiler/file2.js:2:14: This type parameter might need an `extends string` constraint.
*/

View File

@ -0,0 +1,16 @@
=== tests/cases/compiler/file1.js ===
/**
* @template {string} T
* @typedef {{ foo: T }} Foo
*/
export default {};
=== tests/cases/compiler/file2.js ===
/**
* @template T
* @typedef {import('./file1').Foo<T>} Bar
*/

View File

@ -0,0 +1,16 @@
=== tests/cases/compiler/file1.js ===
/**
* @template {string} T
* @typedef {{ foo: T }} Foo
*/
export default {};
>{} : {}
=== tests/cases/compiler/file2.js ===
/**
* @template T
* @typedef {import('./file1').Foo<T>} Bar
*/

View File

@ -0,0 +1,7 @@
// @noEmit: true
// @filename: file1.ts
export type Foo<T extends string> = { foo: T }
// @noEmit: true
// @filename: file2.ts
type Bar<T> = import('./file1').Foo<T>;

View File

@ -0,0 +1,19 @@
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: file1.js
/**
* @template {string} T
* @typedef {{ foo: T }} Foo
*/
export default {};
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @filename: file2.js
/**
* @template T
* @typedef {import('./file1').Foo<T>} Bar
*/