-
Notifications
You must be signed in to change notification settings - Fork 564
xgo/cl: support for kwargs #2448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Summary of ChangesHello @xushiwei, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
|
/review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces support for keyword arguments in function calls, a significant enhancement. The implementation correctly handles merging keyword arguments into struct and map types for both variadic and non-variadic functions. However, there are a few areas that need improvement. The code currently uses panic with "TODO" messages for unhandled cases, which should be replaced with proper compile-time error reporting to avoid crashing the compiler and to provide better feedback to users. Additionally, there's a potential issue in how the closing brace position of generated composite literals is calculated, which could affect source mapping and debugging tools. My review includes suggestions to address these points.
cl/expr.go
Outdated
| if idx < 0 || len(v.Args) < idx { | ||
| panic("TODO: no kwargs or arguments not enough") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of panic with a "TODO" message is not suitable for production code. This should be replaced with proper error handling that provides a clear compile-time error to the user. The current logic doesn't handle variadic functions without non-variadic parameters (e.g., func(...T)), leading to a panic. This case should be handled gracefully, for instance, by disallowing keyword arguments for such functions.
| if idx < 0 || len(v.Args) < idx { | |
| panic("TODO: no kwargs or arguments not enough") | |
| } | |
| if idx < 0 { | |
| panic(ctx.newCodeErrorf(v.Pos(), v.End(), "keyword arguments are not supported for a function with only variadic parameters")) | |
| } | |
| if len(v.Args) < idx { | |
| panic(ctx.newCodeErrorf(v.Pos(), v.End(), "not enough arguments for function call with keyword arguments")) | |
| } |
cl/expr.go
Outdated
| return mergeStringMapKwargs(kwargs) // map[string]T | ||
| } | ||
| } | ||
| panic("TODO: unexpected kwargs type") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using panic with a "TODO" message for unhandled cases is not robust. It should be replaced with proper error handling that reports a clear compile-time error, indicating which types are supported for keyword argument merging.
| panic("TODO: unexpected kwargs type") | |
| panic(ctx.newCodeErrorf(kwargs[0].Pos(), kwargs[len(kwargs)-1].End(), "keyword arguments can only be used for struct, pointer to struct, or map[string]T types, but got %v", t)) |
cl/expr.go
Outdated
| return &ast.CompositeLit{ | ||
| Lbrace: kwargs[0].Pos(), | ||
| Elts: elts, | ||
| Rbrace: kwargs[n-1].End() - 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The calculation of the Rbrace position for the generated CompositeLit as kwargs[n-1].End() - 1 is likely incorrect. The End() method on an AST node typically returns the position after the node, so subtracting 1 would point to the last character of the last keyword argument's value. A more correct position for the closing brace would be kwargs[n-1].End(), representing the position immediately after the keyword arguments. This ensures better source mapping for tools that consume this AST.
| Rbrace: kwargs[n-1].End() - 1, | |
| Rbrace: kwargs[n-1].End(), |
cl/expr.go
Outdated
| return &ast.CompositeLit{ | ||
| Lbrace: kwargs[0].Pos(), | ||
| Elts: elts, | ||
| Rbrace: kwargs[n-1].End() - 1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The calculation of the Rbrace position for the generated CompositeLit as kwargs[n-1].End() - 1 is likely incorrect. The End() method on an AST node typically returns the position after the node, so subtracting 1 would point to the last character of the last keyword argument's value. A more correct position for the closing brace would be kwargs[n-1].End(), representing the position immediately after the keyword arguments. This ensures better source mapping for tools that consume this AST.
| Rbrace: kwargs[n-1].End() - 1, | |
| Rbrace: kwargs[n-1].End(), |
Code Review SummaryGreat work on implementing kwargs support! The implementation is functionally sound with zero runtime overhead. Here are the key findings across code quality, performance, documentation, and security: 🔴 Critical IssuesError handling needs improvement - Generic panic messages should be replaced with context-rich errors for better debugging. Security concern - Potential out-of-bounds access without validation in helper functions. 📝 Key Recommendations
✅ Strengths
Detailed inline comments follow with specific line references. |
cl/expr.go
Outdated
| if fn.variadic { // has variadic parameter | ||
| idx := fn.size - 1 | ||
| if idx < 0 || len(v.Args) < idx { | ||
| panic("TODO: no kwargs or arguments not enough") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Inadequate error handling
Replace this TODO panic with a descriptive error message:
| panic("TODO: no kwargs or arguments not enough") | |
| panic(ctx.newCodeErrorf(v.Pos(), v.End(), "keyword arguments cannot be used when insufficient positional arguments are provided before variadic parameter")) |
This provides better context for debugging and matches the codebase's error reporting pattern.
cl/expr.go
Outdated
| copy(args[idx+1:], v.Args[idx:]) | ||
| } else { | ||
| copy(args, v.Args) | ||
| args[n] = mergeKwargs(v.Kwargs, fn.arg(n, false)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing validation before merge
fn.arg(n, false) can return nil when n exceeds the valid parameter range (see line 611). This will cause mergeKwargs to panic with a generic error. Add validation:
| args[n] = mergeKwargs(v.Kwargs, fn.arg(n, false)) | |
| argType := fn.arg(n, false) | |
| if argType == nil { | |
| panic(ctx.newCodeErrorf(v.Kwargs[0].Pos(), v.End(), "too many arguments including keyword arguments")) | |
| } | |
| args[n] = mergeKwargs(v.Kwargs, argType) |
cl/expr.go
Outdated
| return mergeStringMapKwargs(kwargs) // map[string]T | ||
| } | ||
| } | ||
| panic("TODO: unexpected kwargs type") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High: Improve error message with type information
Include the actual type encountered to help debugging:
| panic("TODO: unexpected kwargs type") | |
| panic(fmt.Sprintf("keyword arguments not supported for parameter type %v", t)) |
Also consider using ctx.newCodeErrorf if position information is available from the kwargs.
| } | ||
|
|
||
| func mergeStringMapKwargs(kwargs []*ast.KwargExpr) ast.Expr { | ||
| n := len(kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Security: Add bounds checking
Accessing kwargs[0] and kwargs[n-1] without validating the slice is non-empty could cause a panic. While the caller currently checks len(v.Kwargs) > 0, add defensive validation:
| n := len(kwargs) | |
| func mergeStringMapKwargs(kwargs []*ast.KwargExpr) ast.Expr { | |
| n := len(kwargs) | |
| if n == 0 { | |
| panic("internal error: empty kwargs slice") | |
| } | |
| elts := make([]ast.Expr, n) |
Same issue exists in mergeStructKwargs at line 785.
cl/expr.go
Outdated
| panic(err) | ||
| } | ||
|
|
||
| func mergeKwargs(kwargs []*ast.KwargExpr, t types.Type) ast.Expr { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation: Add function documentation
This is a key function for the kwargs feature. Please add a doc comment:
| func mergeKwargs(kwargs []*ast.KwargExpr, t types.Type) ast.Expr { | |
| // mergeKwargs converts keyword arguments into an appropriate composite literal | |
| // based on the target parameter type: | |
| // - *struct or struct: creates a struct literal with field names | |
| // - map[string]T: creates a map literal with string keys | |
| // - other types: panics with error message | |
| // | |
| // The function handles automatic field name capitalization for unexported struct fields. | |
| func mergeKwargs(kwargs []*ast.KwargExpr, t types.Type) ast.Expr { |
| fnt := pfn.Type | ||
| fn := &fnType{} | ||
| fn.load(fnt) | ||
| if len(v.Kwargs) > 0 { // https://github.com/goplus/xgo/issues/2443 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation: Add algorithm overview
The kwargs transformation logic is complex, especially for variadic functions. Add a comment explaining the overall approach:
| if len(v.Kwargs) > 0 { // https://github.com/goplus/xgo/issues/2443 | |
| if len(v.Kwargs) > 0 { // https://github.com/goplus/xgo/issues/2443 | |
| // Process keyword arguments by converting them into a composite literal argument. | |
| // For variadic functions: kwargs are inserted at the last required parameter position | |
| // For non-variadic functions: kwargs are appended as an additional argument | |
| // The mergeKwargs function transforms kwargs based on target parameter type. | |
| n := len(v.Args) |
cl/expr.go
Outdated
| } | ||
| } | ||
|
|
||
| func getFldName(name *ast.Ident, t *types.Struct) *ast.Ident { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation: Explain field resolution behavior
Add documentation for this important function that handles Python-style naming conventions:
| func getFldName(name *ast.Ident, t *types.Struct) *ast.Ident { | |
| // getFldName resolves a kwarg name to the actual struct field name. | |
| // It handles three cases: | |
| // 1. Exact match: kwarg name matches struct field name exactly | |
| // 2. Capitalization match: unexported kwarg name matches capitalized exported field | |
| // (e.g., "cache" matches "Cache") | |
| // 3. Fallback: returns original name if no match found (type checker will report error) | |
| // | |
| // This allows Python-style lowercase kwargs to map to Go's exported field conventions. | |
| func getFldName(name *ast.Ident, t *types.Struct) *ast.Ident { |
cl/expr.go
Outdated
| if fn.variadic { // has variadic parameter | ||
| idx := fn.size - 1 | ||
| if idx < 0 || len(v.Args) < idx { | ||
| panic("TODO: no kwargs or arguments not enough") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace panic with descriptive error message:
return ctx.newCodeErrorf(v.Pos(), v.End(),
"keyword arguments require at least %d positional arguments before variadic parameter, got %d",
idx, len(v.Args))Issue: The current generic panic "TODO: no kwargs or arguments not enough" makes debugging extremely difficult. Users need to know:
- Which function call failed
- How many arguments were expected
- How many were provided
Using ctx.newCodeErrorf provides position information and clear guidance.
cl/expr.go
Outdated
| return mergeStringMapKwargs(kwargs) // map[string]T | ||
| } | ||
| } | ||
| panic("TODO: unexpected kwargs type") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace panic with specific error message:
return ctx.newCodeErrorf(kwargs[0].Pos(), kwargs[len(kwargs)-1].End(),
"keyword arguments require parameter type to be struct, *struct, or map[string]T, got %v", t)Issue: The current "TODO: unexpected kwargs type" doesn't tell users:
- What type was encountered
- What types work with kwargs
This improved message clearly states the supported types and shows what was actually provided.
| if len(v.Kwargs) > 0 { // https://github.com/goplus/xgo/issues/2443 | ||
| n := len(v.Args) | ||
| args := make([]ast.Expr, n+1) | ||
| if fn.variadic { // has variadic parameter | ||
| idx := fn.size - 1 | ||
| if idx < 0 || len(v.Args) < idx { | ||
| panic("TODO: no kwargs or arguments not enough") | ||
| } | ||
| copy(args, v.Args[:idx]) | ||
| args[idx] = mergeKwargs(v.Kwargs, fn.params.At(idx).Type()) | ||
| copy(args[idx+1:], v.Args[idx:]) | ||
| } else { | ||
| copy(args, v.Args) | ||
| args[n] = mergeKwargs(v.Kwargs, fn.arg(n, false)) | ||
| } | ||
| ne := *v | ||
| ne.Args, ne.Kwargs = args, nil | ||
| v = &ne | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comprehensive comment explaining the kwargs transformation algorithm:
// Transform kwargs into composite literal arguments based on parameter type.
// For variadic functions: kwargs are inserted before the variadic parameter position.
// For non-variadic functions: kwargs are appended as an additional argument.
// The mergeKwargs function handles type-specific transformations (struct/map).
// https://github.com/goplus/xgo/issues/2443
if len(v.Kwargs) > 0 {
// ... existing code
}Issue: The current code only has a GitHub issue reference but doesn't explain:
- Why variadic and non-variadic functions are handled differently
- What the insertion strategy is
- What mergeKwargs does
This documentation is critical for future maintainers to understand the algorithm and edge cases.
cl/expr.go
Outdated
| func mergeKwargs(kwargs []*ast.KwargExpr, t types.Type) ast.Expr { | ||
| switch t := t.Underlying().(type) { | ||
| case *types.Pointer: | ||
| if t, ok := t.Elem().Underlying().(*types.Struct); ok { | ||
| return mergeStructKwargs(kwargs, t) | ||
| } | ||
| case *types.Struct: | ||
| return mergeStructKwargs(kwargs, t) | ||
| case *types.Map: | ||
| if t, ok := t.Key().Underlying().(*types.Basic); ok && t.Kind() == types.String { | ||
| return mergeStringMapKwargs(kwargs) // map[string]T | ||
| } | ||
| } | ||
| panic("TODO: unexpected kwargs type") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add function documentation:
// mergeKwargs converts keyword arguments into an appropriate composite literal
// based on the target parameter type:
// - struct or *struct: creates a struct literal with field names (via mergeStructKwargs)
// - map[string]T: creates a map literal with string keys (via mergeStringMapKwargs)
// Returns a CompositeLit expression that can be used as a regular function argument.
func mergeKwargs(kwargs []*ast.KwargExpr, t types.Type) ast.Expr {Issue: The function lacks documentation explaining what it does and how it decides which merge strategy to use. This is a key function in the kwargs implementation and needs clear documentation for maintainability.
| func mergeStringMapKwargs(kwargs []*ast.KwargExpr) ast.Expr { | ||
| n := len(kwargs) | ||
| elts := make([]ast.Expr, n) | ||
| for i, arg := range kwargs { | ||
| elts[i] = &ast.KeyValueExpr{ | ||
| Key: toBasicLit(arg.Name), | ||
| Value: arg.Value, | ||
| } | ||
| } | ||
| return &ast.CompositeLit{ | ||
| Lbrace: kwargs[0].Pos(), | ||
| Elts: elts, | ||
| Rbrace: kwargs[n-1].End() - 1, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add function documentation with example:
// mergeStringMapKwargs converts keyword arguments into a map[string]T composite literal.
// Each kwarg name becomes a string key in the resulting map literal.
// Example: foo(a: 1, b: 2) becomes foo(map[string]int{"a": 1, "b": 2})
func mergeStringMapKwargs(kwargs []*ast.KwargExpr) ast.Expr {Issue: This function has no documentation explaining the transformation. An example helps clarify the behavior.
cl/expr.go
Outdated
| func getFldName(name *ast.Ident, t *types.Struct) *ast.Ident { | ||
| var capName string | ||
| exported := name.IsExported() | ||
| if !exported { | ||
| capName = stringutil.Capitalize(name.Name) | ||
| } | ||
| for i, n := 0, t.NumFields(); i < n; i++ { | ||
| fld := t.Field(i) | ||
| if fld.Name() == name.Name { | ||
| return name | ||
| } | ||
| if !exported && fld.Exported() && fld.Name() == capName { | ||
| return &ast.Ident{NamePos: name.NamePos, Name: capName} | ||
| } | ||
| } | ||
| return name // fallback to origin name | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comprehensive documentation for field name resolution:
// getFldName resolves a kwarg identifier to the actual struct field name.
// It handles three cases:
// 1. Exact match: kwarg name matches struct field name exactly
// 2. Capitalization match: unexported kwarg name matches exported field when capitalized
// (e.g., "cache" matches "Cache", allowing Python-style lowercase kwargs)
// 3. Fallback: returns original name if no match (type checker will report error later)
func getFldName(name *ast.Ident, t *types.Struct) *ast.Ident {Issue: This function implements important field name resolution logic that allows Python-style lowercase kwargs to map to Go's exported field conventions. Without documentation, the capitalization behavior is mysterious and could cause confusion.
The documentation explains all three cases and the fallback strategy, making the code maintainable and the behavior predictable.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2448 +/- ##
==========================================
- Coverage 93.23% 93.23% -0.01%
==========================================
Files 34 34
Lines 11515 11599 +84
==========================================
+ Hits 10736 10814 +78
- Misses 616 620 +4
- Partials 163 165 +2 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
/review |
Co-authored-by: niupilot[bot] <230321281+niupilot[bot]@users.noreply.github.com>
|
/review |
Code Review CompleteCritical Issues Found1. Missing nil check (cl/expr.go:732)High Severity: Fix: Add validation before the call: argType := fn.arg(n, false)
if argType == nil {
panic(ctx.newCodeErrorf(v.Pos(), v.End(), "too many arguments including keyword arguments"))
}
args[n] = mergeKwargs(ctx, v, argType)2. Unsafe array access (cl/expr.go:785, 801)Medium Severity: Functions Fix: Add defensive check at start of both functions: if len(kwargs) == 0 {
panic("internal error: empty kwargs slice")
}Design Concerns
Strengths
RecommendationAddress the critical nil check issue before merging. 👉 Want me to re-run this review? Type |
#2443