diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11164845ba139..88037738c0f48 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17592,6 +17592,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 } } @@ -37238,12 +37245,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; @@ -37264,13 +37271,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; diff --git a/tests/baselines/reference/unmetTypeConstraintInImportCall.errors.txt b/tests/baselines/reference/unmetTypeConstraintInImportCall.errors.txt new file mode 100644 index 0000000000000..e1b71fa536749 --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInImportCall.errors.txt @@ -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 = { foo: T } + +==== tests/cases/compiler/file2.ts (1 errors) ==== + type Bar = import('./file1').Foo; + ~ +!!! 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. + \ No newline at end of file diff --git a/tests/baselines/reference/unmetTypeConstraintInImportCall.symbols b/tests/baselines/reference/unmetTypeConstraintInImportCall.symbols new file mode 100644 index 0000000000000..a05993aaa806e --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInImportCall.symbols @@ -0,0 +1,14 @@ +=== tests/cases/compiler/file1.ts === +export type Foo = { 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 = import('./file1').Foo; +>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)) + diff --git a/tests/baselines/reference/unmetTypeConstraintInImportCall.types b/tests/baselines/reference/unmetTypeConstraintInImportCall.types new file mode 100644 index 0000000000000..db2db8a63c8bc --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInImportCall.types @@ -0,0 +1,9 @@ +=== tests/cases/compiler/file1.ts === +export type Foo = { foo: T } +>Foo : Foo +>foo : T + +=== tests/cases/compiler/file2.ts === +type Bar = import('./file1').Foo; +>Bar : Bar + diff --git a/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.errors.txt b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.errors.txt new file mode 100644 index 0000000000000..d5be6eb34b23c --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.errors.txt @@ -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} 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. + */ + \ No newline at end of file diff --git a/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.symbols b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.symbols new file mode 100644 index 0000000000000..e1e009687d87a --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.symbols @@ -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} Bar + */ + diff --git a/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.types b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.types new file mode 100644 index 0000000000000..386e7222e1f59 --- /dev/null +++ b/tests/baselines/reference/unmetTypeConstraintInJSDocImportCall.types @@ -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} Bar + */ + diff --git a/tests/cases/compiler/unmetTypeConstraintInImportCall.ts b/tests/cases/compiler/unmetTypeConstraintInImportCall.ts new file mode 100644 index 0000000000000..f9d4764917acb --- /dev/null +++ b/tests/cases/compiler/unmetTypeConstraintInImportCall.ts @@ -0,0 +1,7 @@ +// @noEmit: true +// @filename: file1.ts +export type Foo = { foo: T } + +// @noEmit: true +// @filename: file2.ts +type Bar = import('./file1').Foo; diff --git a/tests/cases/compiler/unmetTypeConstraintInJSDocImportCall.ts b/tests/cases/compiler/unmetTypeConstraintInJSDocImportCall.ts new file mode 100644 index 0000000000000..abdb33130cda3 --- /dev/null +++ b/tests/cases/compiler/unmetTypeConstraintInJSDocImportCall.ts @@ -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} Bar + */