Conversation
There was a problem hiding this comment.
🟡 Changes recommended
There’s a confirmed incorrect type-shortening path for slice relation element types (generic args can be truncated), and the new shared test helper should create parent directories for nested relative paths.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR improves the generator’s type handling for generic Go types so that (1) instantiated type arguments are preserved when rendering helper types and (2) generic named types are correctly recognized as “column” types when they implement driver.Valuer / sql.Scanner (or other allowed interfaces), rather than being misclassified as associations.
Changes:
- Instantiate uninstantiated generic named types with
anybefore checkingtypes.Implements, to reliably detect interface implementations. - Preserve and correctly shorten complex type expressions (maps, multi-arg generic instantiations, empty interface) so generated helpers keep the original type arguments.
- Add focused regression tests using a temp-module generator harness for serializer-tagged fields and generic column/association classification.
File summaries
| File | Description |
|---|---|
| internal/gen/utils.go | Adds generic instantiation for interface checks and introduces robust shortTypeName (maps, multi-arg generics) + serializer tag detection. |
| internal/gen/generator.go | Fixes package/type splitting for generics, uses shortTypeName for generated helper types, and extends type printer for maps, multi-arg generics, and empty interface. |
| internal/gen/serializer_columns_test.go | Adds regression tests ensuring serializer-tagged fields generate field.Field[...] and expands shortTypeName coverage. |
| internal/gen/generic_column_types_test.go | Adds regression tests ensuring generic column types keep their type args and are classified by interface implementation. |
| internal/gen/generator_support_test.go | Adds shared test helper to generate from ad-hoc sources in a temp module and assert generated helper field types. |
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| @@ -464,7 +474,7 @@ func (f Field) Type() string { | |||
| elementType := filepath.Base(strings.TrimPrefix(goType, "[]")) | |||
| return fmt.Sprintf("field.Slice[%s]", elementType) | |||
| for name, content := range files { | ||
| if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0o644); err != nil { | ||
| t.Fatalf("write %s: %v", name, err) | ||
| } | ||
| } |
873c987 to
b085164
Compare
|
Addressed the review. The shared test helper now creates parent directories before writing, so a nested key like The slice finding was a real defect and is fixed.
|
There was a problem hiding this comment.
🟡 Changes recommended
Field.Type() can unnecessarily invoke packages.Load for slice/map types due to the new strings.Cut base-type extraction, potentially causing significant performance regressions during generation.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
| if typ := loadNamedType(f.file.goModDir, f.file.getFullImportPath(pkgName), typName); typ != nil { | ||
| if ImplementsAllowedInterfaces(typ) { // For interface-implementing types, use generic Field | ||
| return fmt.Sprintf("field.Field[%s]", filepath.Base(goType)) | ||
| return fmt.Sprintf("field.Field[%s]", shortTypeName(goType)) | ||
| } | ||
| } |
b085164 to
ae3038d
Compare
|
Addressed the second round, and this one was a real regression rather than a nit. You are right that the The lookup is now guarded by The shared test helper also reads the whole output tree rather than its top level. |
There was a problem hiding this comment.
🟡 Changes recommended
The interface-implementation lookup still resolves the package path incorrectly for fully-qualified types, so external interface-implementing types may remain misclassified despite the intended fix.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
| // Only a plausible named type is worth loading a package for. A container has none to find: | ||
| // "[]T" leaves an empty base name and "map[K]V" leaves "map", and the lookup would shell out | ||
| // to the go command for a package it then finds nothing in. A named slice or map still | ||
| // reaches this, its name being its own. | ||
| if isNamedTypeCandidate(goType, typName) { | ||
| if typ := loadNamedType(f.file.goModDir, f.file.getFullImportPath(pkgName), typName); typ != nil { | ||
| if ImplementsAllowedInterfaces(typ) { // For interface-implementing types, use generic Field | ||
| return fmt.Sprintf("field.Field[%s]", shortTypeName(goType)) | ||
| } | ||
| } | ||
| } |
ae3038d to
4f1549f
Compare
|
I do not think this one holds, and I have added a test rather than just argue it.
func (p *File) getFullImportPath(shortName string) string {
for _, i := range p.Imports {
if i.Name == shortName {
return i.Path
}
}
return shortName
}So for
Independently of the test, this is also what the change was written for. Running the generator over a real model package, a |
There was a problem hiding this comment.
🟡 Changes recommended
The updated map type rendering can cause map[...]... fields containing package-qualified element types to be misclassified as field.Struct[...] associations, which can generate incorrect helpers.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
| } else if strings.Contains(goType, ".") { | ||
| return fmt.Sprintf("field.Struct[%s]", filepath.Base(goType)) | ||
| return fmt.Sprintf("field.Struct[%s]", shortTypeName(goType)) | ||
| } |
…nterfaces A field of an instantiated generic type, datatypes.JSONType[map[string]string] for one, came out wrong twice over. Its type arguments were lost: the type printer had no case for a map type, an interface type or a second argument, so they rendered as any. And the package and type names were split at the last dot of the whole expression, which for Box[example.com/sample.Plain] falls inside the argument, so the named type was never found, its Valuer and Scanner interfaces never checked, and the column became an association helper: field.Struct[datatypes.JSONType[any]]. The split now happens on the part before the arguments, the lookup asks for the bare generic name, and the type is instantiated with any for each parameter before types.Implements is asked, which is unspecified for an uninstantiated generic type. The printer renders map types, multi-argument instantiations and the empty interface, and the helper keeps the arguments as the field spelled them, shortened per component so a package path inside an argument does not break the type. TestGenericColumnTypesKeepTheirArgumentsAndInterfaces covers a generic Valuer with a map, a local struct and a pointer instantiation, and a generic struct that stays an association with its two arguments intact. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
4f1549f to
d4461ee
Compare
|
Right, and this one was mine. Fixed. Rendering the map type is what exposed it: the association branch tests for a dot anywhere in the type, so A map is now its own branch, ahead of the association test, and the default return uses
|
There was a problem hiding this comment.
🟢 Approval recommended
The changes directly address the described generic-type and interface-detection issues and are covered by targeted new tests for both behavior and formatting.
Review details
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
Problem
A field of an instantiated generic type came out wrong twice over.
datatypes.JSONType[map[string]string], for one, generatedfield.Struct[datatypes.JSONType[any]]:Pair[K, V]), so they rendered asany.Box[example.com/sample.Plain]falls inside the argument, so the named type was never found and itsdriver.Valuerandsql.Scannerimplementations were never checked. The field fell through to the association helper.Fix
The package and type split happens on the part before the arguments and the lookup asks for the bare generic name.
ImplementsAllowedInterfacesinstantiates a generic type withanyfor each parameter before askingtypes.Implements, whose behaviour is unspecified for an uninstantiated generic type; constraints are not checked, since the question is which methods the type has andValueandScando not mention the parameters. The printer renders map types, multi-argument instantiations and the empty interface. The helper keeps the arguments as the field spelled them, shortened per component so a package path inside an argument does not break the type:field.Field[datatypes.JSONType[map[string]string]],field.Struct[sample.Pair[sample.Plain, int]].Test
TestGenericColumnTypesKeepTheirArgumentsAndInterfacesgenerates over a genericBox[T]implementingValuerandScanner, instantiated with a map, a local struct and through a pointer, and over a genericPair[K, V]that implements neither and stays an association with both arguments intact.TestShortTypeNamegains the multi-argument cases.This branch is stacked on #65, whose
shortTypeNameit extends; merging #65 first leaves this one with a single fix commit. The test helper commit is the one shared by the other pull requests from this fork.🤖 Generated with Claude Code