From dd29423ccc79a7ce46e4138d6609baf51f7d976e Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Thu, 25 Jul 2024 13:12:26 -0700 Subject: [PATCH 01/13] Create a few (failing) test cases of https://github.com/microsoft/TypeScript/issues/58449 --- ...tationOnExports50-generics-with-default.ts | 17 +++++++++++++++++ ...htly-more-complex-generics-with-default.ts | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports51-slightly-more-complex-generics-with-default.ts diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts new file mode 100644 index 0000000000000..b3a43630b40e9 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts @@ -0,0 +1,17 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @file lib.d.ts +////interface MyIterator {} +////let x: MyIterator; +////export const y = x; + +verify.codeFix({ + description: "Add annotation of type 'MyIterator'", + index: 0, + newFileContent: +`interface MyIterator {} +let x: MyIterator; +export const y: MyIterator = x;`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports51-slightly-more-complex-generics-with-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports51-slightly-more-complex-generics-with-default.ts new file mode 100644 index 0000000000000..048125a895e89 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports51-slightly-more-complex-generics-with-default.ts @@ -0,0 +1,19 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true + +////export interface Foo {} +////export function foo(x: Foo) { +//// return x; +////} + +verify.codeFix({ + description: "Add return type 'Foo'", + index: 0, + newFileContent: +`export interface Foo {} +export function foo(x: Foo): Foo { + return x; +}`, +}); From 6f8e37d057d9da4fbde45c3a48b0960ad0d93da8 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Wed, 7 Aug 2024 19:45:12 -0700 Subject: [PATCH 02/13] Add functionality to convert a ReferenceType into a "minimal" TypeNode This is the TypeNode representation of a ReferenceType that includes the minimal number of typeArguments that are still semantically equivalent to the full type. Also use this functionality in the isolatedDeclaration autofixer to fix https://github.com/microsoft/TypeScript/issues/58449 --- src/compiler/checker.ts | 1 + src/compiler/types.ts | 3 ++ .../fixMissingTypeAnnotationOnExports.ts | 11 ++-- src/services/codefixes/helpers.ts | 50 ++++++++++++++++++- 4 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91346429b1774..b0d5bde88cc71 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1640,6 +1640,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getBaseTypeOfLiteralType, getWidenedType, getWidenedLiteralType, + fillMissingTypeArguments, getTypeFromTypeNode: nodeIn => { const node = getParseTreeNode(nodeIn, isTypeNode); return node ? getTypeFromTypeNode(node) : errorType; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dd9dd059a4444..9e635cafd13a3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5031,6 +5031,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost { } export interface TypeChecker { + + /** @internal */ + fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; getTypeOfSymbol(symbol: Symbol): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 4d961dfa979cc..91df7e0806434 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -5,7 +5,8 @@ import { eachDiagnostic, registerCodeFix, typePredicateToAutoImportableTypeNode, - typeToAutoImportableTypeNode, + typeToMinimizedReferenceType, + typeNodeToAutoImportableTypeNode, } from "../_namespaces/ts.codefix.js"; import { ArrayBindingPattern, @@ -1096,9 +1097,9 @@ function withContext( return emptyInferenceResult; } - function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None) { + function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode|undefined { let isTruncated = false; - const result = typeToAutoImportableTypeNode(typeChecker, importAdder, type, enclosingDeclaration, scriptTarget, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, { + const minimizedTypeNode = typeToMinimizedReferenceType(typeChecker, type, enclosingDeclaration, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, { moduleResolverHost: program, trackSymbol() { return true; @@ -1107,6 +1108,10 @@ function withContext( isTruncated = true; }, }); + if (!minimizedTypeNode) { + return undefined; + } + const result = typeNodeToAutoImportableTypeNode(minimizedTypeNode, importAdder, scriptTarget); return isTruncated ? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) : result; } diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 656f3dce112b7..f71f66bac3d17 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -23,6 +23,7 @@ import { flatMap, FunctionDeclaration, FunctionExpression, + GenericType, GetAccessorDeclaration, getAllAccessorDeclarations, getCheckFlags, @@ -59,6 +60,8 @@ import { isSetAccessorDeclaration, isStringLiteral, isTypeNode, + isTypeReferenceNode, + TypeReferenceNode, isTypeUsableAsPropertyName, isYieldExpression, LanguageServiceHost, @@ -595,7 +598,15 @@ function createTypeParameterName(index: number) { /** @internal */ export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, type: Type, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { - let typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); + const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); + if (!typeNode) { + return undefined; + } + return typeNodeToAutoImportableTypeNode(typeNode, importAdder, scriptTarget); +} + +/** @internal */ +export function typeNodeToAutoImportableTypeNode(typeNode: TypeNode, importAdder: ImportAdder, scriptTarget: ScriptTarget): TypeNode | undefined { if (typeNode && isImportTypeNode(typeNode)) { const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget); if (importableReference) { @@ -608,6 +619,43 @@ export function typeToAutoImportableTypeNode(checker: TypeChecker, importAdder: return getSynthesizedDeepClone(typeNode); } +function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType, fullTypeArguments: readonly Type[]) : number { + if (fullTypeArguments !== type.typeArguments!) { + throw new Error('fullTypeArguments should be set') + } + const target = type.target; + next_cutoff: for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) { + const typeArguments = fullTypeArguments.slice(0, cutoff); + const filledIn = checker.fillMissingTypeArguments(typeArguments, target.typeParameters, cutoff, /*isJavaScriptImplicitAny*/ false); + for (let i = 0; i < filledIn.length; i++) { + // If they don't match, then we haven't yet reached the right cutoff + if (filledIn[i] !== fullTypeArguments[i]) continue next_cutoff; + } + return cutoff; + } + // If we make it all the way here, all the type arguments are required. + return fullTypeArguments.length; + } + +export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { + const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); + if (!typeNode) { + return undefined; + } + if (isTypeReferenceNode(typeNode)) { + const genericType = type as GenericType; + if (genericType.typeArguments) { + const cutoff = endOfRequiredTypeParameters(checker, genericType, genericType.typeArguments); + if (cutoff !== undefined && typeNode.typeArguments) { + // Looks like the wrong way to do this. What APIs should I use here? + (typeNode as any).typeArguments = typeNode.typeArguments.slice(0, cutoff); + } + } + + } + return typeNode; +} + /** @internal */ export function typePredicateToAutoImportableTypeNode(checker: TypeChecker, importAdder: ImportAdder, typePredicate: TypePredicate, contextNode: Node | undefined, scriptTarget: ScriptTarget, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { let typePredicateNode = checker.typePredicateToTypePredicateNode(typePredicate, contextNode, flags, internalFlags, tracker); From b0bc68fcabd64d09ac7020446d670d56234a97b5 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Wed, 7 Aug 2024 19:45:12 -0700 Subject: [PATCH 03/13] Add a few more test cases of the "negative" cases These are the cases where our heuristic makes some compromise that isn't what we would have preferred, but add the unit tests as a way of documenting the current behavior. --- ...nExports52-gerenrics-oversimplification.ts | 23 +++++++++++++++++++ ...otationOnExports53-nested-generic-types.ts | 21 +++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports53-nested-generic-types.ts diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts new file mode 100644 index 0000000000000..8a9a26acfb72b --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts @@ -0,0 +1,23 @@ +/// + +// In the abstract, we might prefer the inferred return type annotation to +// be identical to the parameter type (with 2 type parameters). +// Our current heursitic to avoid overly complex types in this case creates +// "overly simple" types, but this tradeoff seems reasonable. + +// @isolatedDeclarations: true +// @declaration: true +////export interface Foo {} +////export function foo(x: Foo) { +//// return x; +////} + +verify.codeFix({ + description: "Add return type 'Foo'", + index: 0, + newFileContent: +`export interface Foo {} +export function foo(x: Foo): Foo { + return x; +}`, +}); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports53-nested-generic-types.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports53-nested-generic-types.ts new file mode 100644 index 0000000000000..cebddd3c390c4 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports53-nested-generic-types.ts @@ -0,0 +1,21 @@ +/// + +// Our current heursitic to avoid overly verbose generic types +// doesn't handle generic types nested inside other types. + +// @isolatedDeclarations: true +// @declaration: true +////export interface Foo {} +////export function foo(x: Map>) { +//// return x; +////} + +verify.codeFix({ + description: "Add return type 'Map>'", + index: 0, + newFileContent: +`export interface Foo {} +export function foo(x: Map>): Map> { + return x; +}`, +}); From de61d6dadc71effd20bec0e99df8b598e198e339 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Mon, 19 Aug 2024 09:19:57 -0700 Subject: [PATCH 04/13] First fixes based on code review feedback --- src/compiler/types.ts | 3 +-- src/services/codefixes/helpers.ts | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9e635cafd13a3..aafdc1afc28b9 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5032,8 +5032,6 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost { export interface TypeChecker { - /** @internal */ - fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; getTypeOfSymbol(symbol: Symbol): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; @@ -5397,6 +5395,7 @@ export interface TypeChecker { /** @internal */ isTypeParameterPossiblyReferenced(tp: TypeParameter, node: Node): boolean; /** @internal */ typeHasCallOrConstructSignatures(type: Type): boolean; /** @internal */ getSymbolFlags(symbol: Symbol): SymbolFlags; + /** @internal */ fillMissingTypeArguments(typeArguments: readonly Type[], typeParameters: readonly TypeParameter[] | undefined, minTypeArgumentCount: number, isJavaScriptImplicitAny: boolean): Type[]; } /** @internal */ diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index f71f66bac3d17..e29ac09fb59dd 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -619,10 +619,9 @@ export function typeNodeToAutoImportableTypeNode(typeNode: TypeNode, importAdder return getSynthesizedDeepClone(typeNode); } -function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType, fullTypeArguments: readonly Type[]) : number { - if (fullTypeArguments !== type.typeArguments!) { - throw new Error('fullTypeArguments should be set') - } +function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType): number { + Debug.assert(type.typeArguments); + const fullTypeArguments = type.typeArguments; const target = type.target; next_cutoff: for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) { const typeArguments = fullTypeArguments.slice(0, cutoff); @@ -635,7 +634,7 @@ function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType, fu } // If we make it all the way here, all the type arguments are required. return fullTypeArguments.length; - } +} export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); @@ -645,9 +644,9 @@ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, c if (isTypeReferenceNode(typeNode)) { const genericType = type as GenericType; if (genericType.typeArguments) { - const cutoff = endOfRequiredTypeParameters(checker, genericType, genericType.typeArguments); + const cutoff = endOfRequiredTypeParameters(checker, genericType); if (cutoff !== undefined && typeNode.typeArguments) { - // Looks like the wrong way to do this. What APIs should I use here? + // Cast to any to mutate the newly created TypeNode (typeNode as any).typeArguments = typeNode.typeArguments.slice(0, cutoff); } } From b0b1e02c9179f2c0beda68f8634c35621a1ea993 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Mon, 19 Aug 2024 09:41:06 -0700 Subject: [PATCH 05/13] Update src/compiler/types.ts Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com> --- src/compiler/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index aafdc1afc28b9..80d7348f561a0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5031,7 +5031,6 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost { } export interface TypeChecker { - getTypeOfSymbolAtLocation(symbol: Symbol, node: Node): Type; getTypeOfSymbol(symbol: Symbol): Type; getDeclaredTypeOfSymbol(symbol: Symbol): Type; From 943761b1fd7f669bb32732384a1c42e131fe6dd1 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Mon, 19 Aug 2024 09:54:05 -0700 Subject: [PATCH 06/13] Fix unused import Also, mark typeToMinimizedReferenceType as `@internal`. --- src/services/codefixes/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index e29ac09fb59dd..d83a04b098bfd 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -61,7 +61,6 @@ import { isStringLiteral, isTypeNode, isTypeReferenceNode, - TypeReferenceNode, isTypeUsableAsPropertyName, isYieldExpression, LanguageServiceHost, @@ -636,6 +635,7 @@ function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType): n return fullTypeArguments.length; } +/** @internal */ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); if (!typeNode) { From d53552f35379f9f3781d2f9b8d227288983f3ba0 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Mon, 19 Aug 2024 10:44:11 -0700 Subject: [PATCH 07/13] Run dprint to fix formatting --- src/services/codefixes/fixMissingTypeAnnotationOnExports.ts | 4 ++-- src/services/codefixes/helpers.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts index 91df7e0806434..bc0a14dbfac51 100644 --- a/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts +++ b/src/services/codefixes/fixMissingTypeAnnotationOnExports.ts @@ -4,9 +4,9 @@ import { createImportAdder, eachDiagnostic, registerCodeFix, + typeNodeToAutoImportableTypeNode, typePredicateToAutoImportableTypeNode, typeToMinimizedReferenceType, - typeNodeToAutoImportableTypeNode, } from "../_namespaces/ts.codefix.js"; import { ArrayBindingPattern, @@ -1097,7 +1097,7 @@ function withContext( return emptyInferenceResult; } - function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode|undefined { + function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode | undefined { let isTruncated = false; const minimizedTypeNode = typeToMinimizedReferenceType(typeChecker, type, enclosingDeclaration, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, { moduleResolverHost: program, diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index d83a04b098bfd..bc4f158180c2a 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -650,7 +650,6 @@ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, c (typeNode as any).typeArguments = typeNode.typeArguments.slice(0, cutoff); } } - } return typeNode; } From 77ac42ffb60899d279ed3a478a696ee504bee803 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Mon, 19 Aug 2024 11:54:16 -0700 Subject: [PATCH 08/13] Use factory methods to create new AST node instead of mutating --- src/services/codefixes/helpers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index bc4f158180c2a..47d7103b14a43 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -637,7 +637,7 @@ function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType): n /** @internal */ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, contextNode: Node | undefined, flags?: NodeBuilderFlags, internalFlags?: InternalNodeBuilderFlags, tracker?: SymbolTracker): TypeNode | undefined { - const typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); + let typeNode = checker.typeToTypeNode(type, contextNode, flags, internalFlags, tracker); if (!typeNode) { return undefined; } @@ -646,8 +646,8 @@ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, c if (genericType.typeArguments) { const cutoff = endOfRequiredTypeParameters(checker, genericType); if (cutoff !== undefined && typeNode.typeArguments) { - // Cast to any to mutate the newly created TypeNode - (typeNode as any).typeArguments = typeNode.typeArguments.slice(0, cutoff); + const newTypeArguments = factory.createNodeArray(typeNode.typeArguments.slice(0, cutoff)); + typeNode = factory.updateTypeReferenceNode(typeNode, typeNode.typeName, newTypeArguments); } } } From 71c092a06322fab628db6eae3d4c3ecf7727aa20 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Tue, 20 Aug 2024 11:36:33 -0700 Subject: [PATCH 09/13] Avoid updating nodes when unnecessary Also include test cases with the global `Generator` and `Iterator` which, unlike `Iterable` and `IterableIterator`, are not special cased in the type system and were previously always generated with 3 type arguments. --- src/services/codefixes/helpers.ts | 4 ++-- ...notationOnExports50-generics-with-default.ts | 12 +++++------- ...eAnnotationOnExports54-generator-generics.ts | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports54-generator-generics.ts diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 47d7103b14a43..c218d3fa16600 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -643,9 +643,9 @@ export function typeToMinimizedReferenceType(checker: TypeChecker, type: Type, c } if (isTypeReferenceNode(typeNode)) { const genericType = type as GenericType; - if (genericType.typeArguments) { + if (genericType.typeArguments && typeNode.typeArguments) { const cutoff = endOfRequiredTypeParameters(checker, genericType); - if (cutoff !== undefined && typeNode.typeArguments) { + if (cutoff < typeNode.typeArguments.length) { const newTypeArguments = factory.createNodeArray(typeNode.typeArguments.slice(0, cutoff)); typeNode = factory.updateTypeReferenceNode(typeNode, typeNode.typeName, newTypeArguments); } diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts index b3a43630b40e9..8091543900ea2 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports50-generics-with-default.ts @@ -2,16 +2,14 @@ // @isolatedDeclarations: true // @declaration: true -// @file lib.d.ts -////interface MyIterator {} -////let x: MyIterator; +// @lib: es2015 +////let x: Iterator; ////export const y = x; verify.codeFix({ - description: "Add annotation of type 'MyIterator'", + description: "Add annotation of type 'Iterator'", index: 0, newFileContent: -`interface MyIterator {} -let x: MyIterator; -export const y: MyIterator = x;`, +`let x: Iterator; +export const y: Iterator = x;`, }); diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports54-generator-generics.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports54-generator-generics.ts new file mode 100644 index 0000000000000..d0075bd14ee44 --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports54-generator-generics.ts @@ -0,0 +1,17 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2015 +//// export function foo(x: Generator) { +//// return x; +//// } + +verify.codeFix({ + description: "Add return type 'Generator'", + index: 0, + newFileContent: +`export function foo(x: Generator): Generator { + return x; +}` +}); From 14deb8effa8cc3f2ef8a366c705742257d6a112b Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Tue, 20 Aug 2024 14:24:18 -0700 Subject: [PATCH 10/13] Add example case with a inferred generator return --- ...ypeAnnotationOnExports55-generator-return.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports55-generator-return.ts diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports55-generator-return.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports55-generator-return.ts new file mode 100644 index 0000000000000..5c9e3ade6adce --- /dev/null +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports55-generator-return.ts @@ -0,0 +1,17 @@ +/// + +// @isolatedDeclarations: true +// @declaration: true +// @lib: es2015 +//// export function *foo() { +//// yield 5; +//// } + +verify.codeFix({ + description: "Add return type 'Generator'", + index: 0, + newFileContent: +`export function *foo(): Generator { + yield 5; +}` +}); From a603aa226b066dce0e8abd3f0f452ec8c86bb60c Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Thu, 29 Aug 2024 14:49:32 -0700 Subject: [PATCH 11/13] Update tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts Fix typo Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> --- ...ingTypeAnnotationOnExports52-gerenrics-oversimplification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts index 8a9a26acfb72b..41166c64ce918 100644 --- a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts +++ b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts @@ -2,7 +2,7 @@ // In the abstract, we might prefer the inferred return type annotation to // be identical to the parameter type (with 2 type parameters). -// Our current heursitic to avoid overly complex types in this case creates +// Our current heuristic to avoid overly complex types in this case creates // "overly simple" types, but this tradeoff seems reasonable. // @isolatedDeclarations: true From 4e216575afcc8d4c3ee1d9ca5c166dbf1dc99e4f Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Thu, 29 Aug 2024 14:55:22 -0700 Subject: [PATCH 12/13] Update src/services/codefixes/helpers.ts Simplify loop Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> --- src/services/codefixes/helpers.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index c218d3fa16600..e7e1eba26178f 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -622,14 +622,12 @@ function endOfRequiredTypeParameters(checker: TypeChecker, type: GenericType): n Debug.assert(type.typeArguments); const fullTypeArguments = type.typeArguments; const target = type.target; - next_cutoff: for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) { + for (let cutoff = 0; cutoff < fullTypeArguments.length; cutoff++) { const typeArguments = fullTypeArguments.slice(0, cutoff); const filledIn = checker.fillMissingTypeArguments(typeArguments, target.typeParameters, cutoff, /*isJavaScriptImplicitAny*/ false); - for (let i = 0; i < filledIn.length; i++) { - // If they don't match, then we haven't yet reached the right cutoff - if (filledIn[i] !== fullTypeArguments[i]) continue next_cutoff; + if (filledIn.every((fill, i) => fill === fullTypeArguments[i])) { + return cutoff; } - return cutoff; } // If we make it all the way here, all the type arguments are required. return fullTypeArguments.length; From cd0cd4ed8c689ecc0d2d41a87d1e3d343af720d8 Mon Sep 17 00:00:00 2001 From: Ben Lickly Date: Thu, 29 Aug 2024 14:59:01 -0700 Subject: [PATCH 13/13] Fix typo in filename --- ...ssingTypeAnnotationOnExports52-generics-oversimplification.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/cases/fourslash/{codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts => codeFixMissingTypeAnnotationOnExports52-generics-oversimplification.ts} (100%) diff --git a/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts b/tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-generics-oversimplification.ts similarity index 100% rename from tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-gerenrics-oversimplification.ts rename to tests/cases/fourslash/codeFixMissingTypeAnnotationOnExports52-generics-oversimplification.ts