From a6da8d9eabc50fb106488d93ba1dff796dfe499b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Oct 2026 18:57:35 +0000 Subject: [PATCH] Create serializable synthetic call-signature declarations Co-authored-by: weswigham <2932786+weswigham@users.noreply.github.com> --- packages/typescript/test/async/api.test.ts | 12 ++++++++++- packages/typescript/test/sync/api.test.ts | 12 ++++++++++- tsc/internal/api/session.go | 16 +++++++++++--- tsc/internal/checker/checker.go | 2 +- tsc/internal/checker/syntheticsignature.go | 25 ++++++++++++++++++++++ 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 tsc/internal/checker/syntheticsignature.go diff --git a/packages/typescript/test/async/api.test.ts b/packages/typescript/test/async/api.test.ts index f4c99f02c81..f04a12b5d74 100644 --- a/packages/typescript/test/async/api.test.ts +++ b/packages/typescript/test/async/api.test.ts @@ -4967,7 +4967,17 @@ describe("Checker - getContextualType", { concurrency }, () => { const signatures = await project.checker.getSignaturesOfType(type, SignatureKind.Call); assert.equal(signatures.length, 1); assert.deepEqual((await signatures[0].getParameters()).map(parameter => parameter.name), ["target", "context"]); - assert.equal(signatures[0].declaration, undefined); + const declarationHandle = signatures[0].declaration; + assert.ok(declarationHandle); + assert.equal(declarationHandle.kind, SyntaxKind.FunctionType); + const declaration = await declarationHandle.resolve(); + assert.ok(declaration); + assert.ok(isSignatureDeclaration(declaration)); + assert.equal(declaration.kind, SyntaxKind.FunctionType); + const declarationSignature = await project.checker.getSignatureFromDeclaration(declaration); + assert.ok(declarationSignature); + assert.strictEqual(declarationSignature.declaration, declarationHandle); + assert.deepEqual(await project.program.getSourceFileNames(), ["/src/main.ts"]); }); test("contextual type from function parameter", async () => { diff --git a/packages/typescript/test/sync/api.test.ts b/packages/typescript/test/sync/api.test.ts index de2a6a1579f..158b4a81958 100644 --- a/packages/typescript/test/sync/api.test.ts +++ b/packages/typescript/test/sync/api.test.ts @@ -4792,7 +4792,17 @@ describe("Checker - getContextualType", { concurrency }, () => { const signatures = project.checker.getSignaturesOfType(type, SignatureKind.Call); assert.equal(signatures.length, 1); assert.deepEqual((signatures[0].getParameters()).map(parameter => parameter.name), ["target", "context"]); - assert.equal(signatures[0].declaration, undefined); + const declarationHandle = signatures[0].declaration; + assert.ok(declarationHandle); + assert.equal(declarationHandle.kind, SyntaxKind.FunctionType); + const declaration = declarationHandle.resolve(); + assert.ok(declaration); + assert.ok(isSignatureDeclaration(declaration)); + assert.equal(declaration.kind, SyntaxKind.FunctionType); + const declarationSignature = project.checker.getSignatureFromDeclaration(declaration); + assert.ok(declarationSignature); + assert.strictEqual(declarationSignature.declaration, declarationHandle); + assert.deepEqual(project.program.getSourceFileNames(), ["/src/main.ts"]); }); test("contextual type from function parameter", () => { diff --git a/tsc/internal/api/session.go b/tsc/internal/api/session.go index 5c46aad60fd..1ec3b3c120d 100644 --- a/tsc/internal/api/session.go +++ b/tsc/internal/api/session.go @@ -501,8 +501,8 @@ func (sd *snapshotData) newSignatureResponse(projectID project.Handle, sig *chec Flags: uint32(sig.Flags()), } - if declaration := sig.Declaration(); declaration != nil && ast.GetSourceFileOfNode(declaration) != nil { - resp.Declaration = sd.nodeHandleFrom(declaration) + if sig.Declaration() != nil { + resp.Declaration = sd.nodeHandleFrom(sig.Declaration()) } if len(sig.TypeParameters()) > 0 { @@ -2362,7 +2362,11 @@ func (s *Session) handleGetSourceFile(ctx context.Context, params *GetSourceFile return nil, err } - return s.encodeSourceFileResponse(program.GetSourceFile(params.File.ToFileName(program.BaseDirectory()))) + fileName := params.File.ToFileName(program.BaseDirectory()) + if fileName == checker.SyntheticSignatureFileName { + return s.encodeSourceFileResponse(checker.GetSyntheticSignatureSourceFile()) + } + return s.encodeSourceFileResponse(program.GetSourceFile(fileName)) } // handleGetConfigFileNames returns tsconfig file names associated with the project's command line. @@ -5029,6 +5033,12 @@ func (sd *snapshotData) resolveNodeHandle(program *compiler.Program, handle Node return nil, fmt.Errorf("%w: invalid node handle %q", ErrClientError, handle) } sourceFile := program.GetSourceFileByNodeID(ast.NodeId(nodeID)) + if sourceFile == nil { + syntheticFile := checker.GetSyntheticSignatureSourceFile() + if sourceFileNodeID(syntheticFile) == nodeID { + sourceFile = syntheticFile + } + } if sourceFile == nil { return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded or handle may be stale)", ErrClientError, handle) } diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 10ddb3c0c88..2f51cb9333c 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -31161,7 +31161,7 @@ func (c *Checker) newSetterFunctionType(t *Type) *Type { // Creates a synthetic `Signature` corresponding to a call signature. func (c *Checker) newCallSignature(typeParameters []*Type, thisParameter *ast.Symbol, parameters []*ast.Symbol, returnType *Type) *Signature { - decl := c.factory.NewFunctionTypeNode(nil, nil, c.factory.NewKeywordTypeNode(ast.KindAnyKeyword)) + decl := GetSyntheticSignatureSourceFile().Statements.Nodes[0].Type() return c.newSignature(SignatureFlagsNone, decl, typeParameters, thisParameter, parameters, returnType, nil, len(parameters)) } diff --git a/tsc/internal/checker/syntheticsignature.go b/tsc/internal/checker/syntheticsignature.go new file mode 100644 index 00000000000..0f871b085fd --- /dev/null +++ b/tsc/internal/checker/syntheticsignature.go @@ -0,0 +1,25 @@ +package checker + +import ( + "sync" + + "github.com/microsoft/TypeScript/tsc/internal/ast" + "github.com/microsoft/TypeScript/tsc/internal/core" + "github.com/microsoft/TypeScript/tsc/internal/parser" + "github.com/microsoft/TypeScript/tsc/internal/tspath" +) + +const SyntheticSignatureFileName tspath.RootedFilePath = "tsgo://synthetic/signatures.d.ts" + +var syntheticSignatureSourceFile = sync.OnceValue(func() *ast.SourceFile { + return parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: SyntheticSignatureFileName, + PathKey: tspath.CaseSensitive.PathKey(tspath.RootedPath(SyntheticSignatureFileName)), + }, "type SyntheticSignature = () => any;", core.ScriptKindTS) +}) + +// GetSyntheticSignatureSourceFile provides an immutable, shared AST for synthetic +// call-signature declarations. It is not an input file of any program. +func GetSyntheticSignatureSourceFile() *ast.SourceFile { + return syntheticSignatureSourceFile() +}