coder/guts

Incorrect type conversion for slice of nested struct alias

Closed this issue · 4 comments

When converting a slice of a nested struct alias, the type is incorrectly inferred as readonly unknown[] instead of the expected struct type.

Environments
Go version: go version go1.23.8 darwin/arm64
guts version: v1.3.0

Steps to Reproduce:

  1. Add the following Go code to testdata/alias/alias.go:

    package alias
    
    type FooStruct struct {
    	Key string
    }
    
    type AliasStruct = FooStruct
    type AliasStructNested = AliasStruct
    type AliasStructSlice = []FooStruct
    type AliasStructNestedSlice = []AliasStructNested
  2. Run the test for the alias generation:

    go test -run "TestGeneration/alias"

Actual Behavior:

The test fails with the following diff, showing AliasStructNestedSlice is converted to readonly unknown[]:

--- Expected
+++ Actual
@@ -15,2 +15,23 @@
 // From alias/alias.go
+export interface AliasStruct {
+    readonly Key: string;
+}
+
+// From alias/alias.go
+export interface AliasStructNested {
+    readonly Key: string;
+}
+
+// From alias/alias.go
+export type AliasStructNestedSlice = readonly unknown[];
+
+// From alias/alias.go
+export type AliasStructSlice = readonly FooStruct[];
+
+// From alias/alias.go
 export type Foo = string;
+
+// From alias/alias.go
+export interface FooStruct {
+    readonly Key: string;
+}

Specifically, the generated TypeScript for AliasStructNestedSlice is:

// From alias/alias.go
export type AliasStructNestedSlice = readonly unknown[];

Expected Behavior:

The AliasStructNestedSlice should be converted to a slice of the aliased struct type.

Potential Cause and Workaround:

It seems the issue might be related to how types.Alias is handled. In the following code snippet

guts/convert.go

Lines 1006 to 1008 in ad36901

case *types.Alias:
// TODO: Verify this is correct.
return ts.typescriptType(ty.Underlying())

If ty.Underlying() is changed to ty.Rhs(), AliasStructNestedSlice is converted to readonly FooStruct[]. The diff for the test results with this change is:

--- Expected
+++ Actual
@@ -15,2 +15,23 @@
 // From alias/alias.go
+export interface AliasStruct {
+    readonly Key: string;
+}
+
+// From alias/alias.go
+export interface AliasStructNested {
+    readonly Key: string;
+}
+
+// From alias/alias.go
+export type AliasStructNestedSlice = readonly FooStruct[];
+
+// From alias/alias.go
+export type AliasStructSlice = readonly FooStruct[];
+
+// From alias/alias.go
 export type Foo = string;
+
+// From alias/alias.go
+export interface FooStruct {
+    readonly Key: string;
+}

While changing ty.Underlying() to ty.Rhs() resolves the immediate issue of unknown[], I'm unsure whether AliasStructNestedSlice should be converted to readonly AliasStructNested[] or readonly FooStruct[]. Further discussion on the desired behavior for nested aliases would be beneficial.

I will take a look! .Rhs() was added somewhat recently.

golang/go#66559

That is a good question on the nested aliases. I could see the un-nesting of aliases being a post processing step that could be an opt in. 🤔

I'm going to let it traverse the alias all the way.

We could debate AliasStructNested[] vs FooStruct[]. At present I've been traversing the alias all the way, so I will continue that pattern for now.

Thank you for addressing this issue so promptly! I really appreciate it.

Thanks for raising the issue 👍