diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index fe5c9e1e43..da3dac4d60 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6420,11 +6420,11 @@ namespace ts { * @param typeParameters The requested type parameters. * @param minTypeArgumentCount The minimum number of required type arguments. */ - function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScript: boolean) { + function fillMissingTypeArguments(typeArguments: Type[] | undefined, typeParameters: TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean) { const numTypeParameters = length(typeParameters); if (numTypeParameters) { const numTypeArguments = length(typeArguments); - if ((isJavaScript || numTypeArguments >= minTypeArgumentCount) && numTypeArguments <= numTypeParameters) { + if ((isJavaScriptImplicitAny || numTypeArguments >= minTypeArgumentCount) && numTypeArguments <= numTypeParameters) { if (!typeArguments) { typeArguments = []; } @@ -6433,12 +6433,12 @@ namespace ts { // If a type parameter does not have a default type, or if the default type // is a forward reference, the empty object type is used. for (let i = numTypeArguments; i < numTypeParameters; i++) { - typeArguments[i] = getDefaultTypeArgumentType(isJavaScript); + typeArguments[i] = getDefaultTypeArgumentType(isJavaScriptImplicitAny); } for (let i = numTypeArguments; i < numTypeParameters; i++) { const mapper = createTypeMapper(typeParameters, typeArguments); const defaultType = getDefaultFromTypeParameter(typeParameters[i]); - typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScript); + typeArguments[i] = defaultType ? instantiateType(defaultType, mapper) : getDefaultTypeArgumentType(isJavaScriptImplicitAny); } } } @@ -6874,21 +6874,25 @@ namespace ts { if (typeParameters) { const numTypeArguments = length(node.typeArguments); const minTypeArgumentCount = getMinTypeArgumentCount(typeParameters); - const isJavascript = isInJavaScriptFile(node); - if (!isJavascript && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { - error(node, - minTypeArgumentCount === typeParameters.length - ? Diagnostics.Generic_type_0_requires_1_type_argument_s - : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments, - typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), - minTypeArgumentCount, - typeParameters.length); + const isJs = isInJavaScriptFile(node); + const isJsImplicitAny = !compilerOptions.noImplicitAny && isJs; + if (!isJsImplicitAny && (numTypeArguments < minTypeArgumentCount || numTypeArguments > typeParameters.length)) { + const missingAugmentsTag = isJs && node.parent.kind !== SyntaxKind.JSDocAugmentsTag; + const diag = minTypeArgumentCount === typeParameters.length + ? missingAugmentsTag + ? Diagnostics.Expected_0_type_arguments_provide_these_with_an_extends_tag + : Diagnostics.Generic_type_0_requires_1_type_argument_s + : missingAugmentsTag + ? Diagnostics.Expected_0_1_type_arguments_provide_these_with_an_extends_tag + : Diagnostics.Generic_type_0_requires_between_1_and_2_type_arguments; + const typeStr = typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType); + error(node, diag, typeStr, minTypeArgumentCount, typeParameters.length); return unknownType; } // In a type reference, the outer type parameters of the referenced class or interface are automatically // supplied as type arguments and the type reference only specifies arguments for the local type parameters // of the class or interface. - const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgs, typeParameters, minTypeArgumentCount, isJavascript)); + const typeArguments = concatenate(type.outerTypeParameters, fillMissingTypeArguments(typeArgs, typeParameters, minTypeArgumentCount, isJsImplicitAny)); return createTypeReference(type, typeArguments); } if (node.typeArguments) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 3b6ecffcfb..0f88d5e4ac 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3539,6 +3539,14 @@ "category": "Error", "code": 8025 }, + "Expected {0} type arguments; provide these with an '@extends' tag.": { + "category": "Error", + "code": 8026 + }, + "Expected {0}-{1} type arguments; provide these with an '@extends' tag.": { + "category": "Error", + "code": 8027 + }, "Only identifiers/qualified-names with optional type arguments are currently supported in a class 'extends' clause.": { "category": "Error", "code": 9002 diff --git a/tests/baselines/reference/jsExtendsImplicitAny.errors.txt b/tests/baselines/reference/jsExtendsImplicitAny.errors.txt new file mode 100644 index 0000000000..4b836970e0 --- /dev/null +++ b/tests/baselines/reference/jsExtendsImplicitAny.errors.txt @@ -0,0 +1,17 @@ +/b.js(1,17): error TS8026: Expected A type arguments; provide these with an '@extends' tag. +/b.js(3,15): error TS2314: Generic type 'A' requires 1 type argument(s). + + +==== /a.d.ts (0 errors) ==== + declare class A { x: T; } + +==== /b.js (2 errors) ==== + class B extends A {} + ~ +!!! error TS8026: Expected A type arguments; provide these with an '@extends' tag. + + /** @augments A */ + ~ +!!! error TS2314: Generic type 'A' requires 1 type argument(s). + class C { } + \ No newline at end of file diff --git a/tests/baselines/reference/jsExtendsImplicitAny.symbols b/tests/baselines/reference/jsExtendsImplicitAny.symbols new file mode 100644 index 0000000000..0a6d71e109 --- /dev/null +++ b/tests/baselines/reference/jsExtendsImplicitAny.symbols @@ -0,0 +1,16 @@ +=== /a.d.ts === +declare class A { x: T; } +>A : Symbol(A, Decl(a.d.ts, 0, 0)) +>T : Symbol(T, Decl(a.d.ts, 0, 16)) +>x : Symbol(A.x, Decl(a.d.ts, 0, 20)) +>T : Symbol(T, Decl(a.d.ts, 0, 16)) + +=== /b.js === +class B extends A {} +>B : Symbol(B, Decl(b.js, 0, 0)) +>A : Symbol(A, Decl(a.d.ts, 0, 0)) + +/** @augments A */ +class C { } +>C : Symbol(C, Decl(b.js, 0, 20)) + diff --git a/tests/baselines/reference/jsExtendsImplicitAny.types b/tests/baselines/reference/jsExtendsImplicitAny.types new file mode 100644 index 0000000000..ba98ef3ed2 --- /dev/null +++ b/tests/baselines/reference/jsExtendsImplicitAny.types @@ -0,0 +1,16 @@ +=== /a.d.ts === +declare class A { x: T; } +>A : A +>T : T +>x : T +>T : T + +=== /b.js === +class B extends A {} +>B : B +>A : typeof A + +/** @augments A */ +class C { } +>C : C + diff --git a/tests/cases/compiler/jsExtendsImplicitAny.ts b/tests/cases/compiler/jsExtendsImplicitAny.ts new file mode 100644 index 0000000000..e3d737ceb3 --- /dev/null +++ b/tests/cases/compiler/jsExtendsImplicitAny.ts @@ -0,0 +1,13 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @noImplicitAny: true + +// @Filename: /a.d.ts +declare class A { x: T; } + +// @Filename: /b.js +class B extends A {} + +/** @augments A */ +class C { }