Substitute zero value for typed-nil pointer functool output#557
Open
PratikDhanave wants to merge 2 commits into
Open
Substitute zero value for typed-nil pointer functool output#557PratikDhanave wants to merge 2 commits into
PratikDhanave wants to merge 2 commits into
Conversation
A functool built with a pointer output type (HandlerFor[In, *Out]) could never return (nil, nil): the output schema is derived from the dereferenced, non-nullable object type, but a typed-nil pointer marshals to JSON null, so output normalization always failed with 'has type "null", want "object"'. Every call to such a tool errored, regardless of the handler's result. When Out is a pointer type and the handler returns a typed nil, substitute the zero value of the element type before normalizing, mirroring the MCP go-sdk's HandlerFor (which this package's HandlerFor is modeled on).
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a functool.HandlerFor parity gap for pointer-typed outputs (Out = *T) where a handler returning a typed-nil pointer ((nil, nil)) previously serialized to JSON null and then failed validation against the non-nullable, object-rooted output schema.
Changes:
- Precomputes a zero element pointer for pointer
Outtypes and substitutes it when the handler returns a typed-nil pointer, avoidingnulloutput normalization failures. - Adds a regression test ensuring
(*Out)(nil)output is accepted and becomes a non-nil zero-value pointer.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tool/functool/func.go | Adds typed-nil pointer output substitution logic during handler execution. |
| tool/functool/func_test.go | Adds regression test covering pointer output typed-nil behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+67
to
+70
| hasPointerOut := outType != nil && outType.Kind() == reflect.Pointer | ||
| if hasPointerOut { | ||
| elemZero = reflect.New(outType.Elem()).Interface().(Out) | ||
| } |
Contributor
Author
There was a problem hiding this comment.
Fixed in a follow-up commit — converts to outType before the assertion (reflect.New(outType.Elem()).Convert(outType).Interface().(Out)), with a named-pointer regression test.
reflect.New(outType.Elem()) yields an unnamed *Elem, which is not assertable to a named pointer output type (e.g. type P *T). Convert to the output type before the assertion so a functool with a named pointer output does not panic at construction.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
functoolcreated with a pointer output type —HandlerFor[In, *Out]— can never return(nil, nil), even though that is the idiomatic Go way to signal "no result".The output schema is derived from the dereferenced type (
jsonformat.ForTypedereferences, and the root object schema is deliberately non-nullable). A typed-nil*Outmarshals to JSONnull, and output normalization then validatesnullagainst atype: "object"root, which always fails:So the handler’s return value is unreachable and every call to a pointer-output tool errors.
The upstream
modelcontextprotocol/go-sdk— which this package’sHandlerForis explicitly modeled on — handles exactly this case: it computes anelemZero(non-nil only whenOutis a pointer) and substitutes it for a typed-nil output "to avoid typed nil, which will serialize as JSON null," so validation against the object schema succeeds. functool did no such substitution — a parity gap.Fix
In
New, whenOutis a pointer type, precompute the zero element value and substitute it when the handler returns a typed nil:Value-typed outputs are unaffected.
Test
TestFuncTool_PointerOutputNilReturnsZeroValuebuilds a tool whose handler returns(*Out)(nil)and assertsCallsucceeds with a zero-value*Out. It fails on the old code (thenull/objectvalidation error) and passes with the fix.