Skip to content
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

Fix miscellaneous bindings and typescript export bugs #3978

Merged
merged 19 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 20 additions & 21 deletions v2/internal/binding/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,22 +262,19 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,

// Iterate this struct and add any struct field references
structType := reflect.TypeOf(s)
if hasElements(structType) {
for hasElements(structType) {
structType = structType.Elem()
}

for i := 0; i < structType.NumField(); i++ {
field := structType.Field(i)
if field.Anonymous {
if field.Anonymous || !field.IsExported() {
continue
}
kind := field.Type.Kind()
if kind == reflect.Struct {
if !field.IsExported() {
continue
}
fqname := field.Type.String()
sNameSplit := strings.Split(fqname, ".")
sNameSplit := strings.SplitN(fqname, ".", 2)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line 284 throws away the rest, we want to keep them

if len(sNameSplit) < 2 {
continue
}
Expand All @@ -288,22 +285,24 @@ func (b *Bindings) AddStructToGenerateTS(packageName string, structName string,
s := reflect.Indirect(a).Interface()
b.AddStructToGenerateTS(pName, sName, s)
}
} else if hasElements(field.Type) && field.Type.Elem().Kind() == reflect.Struct {
if !field.IsExported() {
continue
}
fqname := field.Type.Elem().String()
sNameSplit := strings.Split(fqname, ".")
if len(sNameSplit) < 2 {
continue
} else {
fType := field.Type
for hasElements(fType) {
fType = fType.Elem()
}
sName := sNameSplit[1]
pName := getPackageName(fqname)
typ := field.Type.Elem()
a := reflect.New(typ)
if b.hasExportedJSONFields(typ) {
s := reflect.Indirect(a).Interface()
b.AddStructToGenerateTS(pName, sName, s)
if fType.Kind() == reflect.Struct {
fqname := fType.String()
sNameSplit := strings.SplitN(fqname, ".", 2)
if len(sNameSplit) < 2 {
continue
}
sName := sNameSplit[1]
pName := getPackageName(fqname)
a := reflect.New(fType)
if b.hasExportedJSONFields(fType) {
s := reflect.Indirect(a).Interface()
b.AddStructToGenerateTS(pName, sName, s)
}
}
}
}
Expand Down
126 changes: 126 additions & 0 deletions v2/internal/binding/binding_test/binding_deepelements_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package binding_test

// Issues 2303, 3442, 3709

type DeepMessage struct {
Msg string
}

type DeepElements struct {
Single []int
Double [][]string
FourDouble [4][]float64
DoubleFour [][4]int64
Triple [][][]int

SingleMap map[string]int
SliceMap map[string][]int
DoubleSliceMap map[string][][]int

ArrayMap map[string][4]int
DoubleArrayMap1 map[string][4][]int
DoubleArrayMap2 map[string][][4]int
DoubleArrayMap3 map[string][4][4]int

OneStructs []*DeepMessage
TwoStructs [3][]*DeepMessage
ThreeStructs [][][]DeepMessage
MapStructs map[string][]*DeepMessage
MapTwoStructs map[string][4][]DeepMessage
MapThreeStructs map[string][][7][]*DeepMessage
}

func (x DeepElements) Get() DeepElements {
return x
}

var DeepElementsTest = BindingTest{
name: "DeepElements",
structs: []interface{}{
&DeepElements{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {

export class DeepMessage {
Msg: string;

static createFrom(source: any = {}) {
return new DeepMessage(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Msg = source["Msg"];
}
}
export class DeepElements {
Single: number[];
Double: string[][];
FourDouble: number[][];
DoubleFour: number[][];
Triple: number[][][];
SingleMap: Record<string, number>;
SliceMap: Record<string, number[]>;
DoubleSliceMap: Record<string, number[][]>;
ArrayMap: Record<string, number[]>;
DoubleArrayMap1: Record<string, number[][]>;
DoubleArrayMap2: Record<string, number[][]>;
DoubleArrayMap3: Record<string, number[][]>;
OneStructs: DeepMessage[];
TwoStructs: DeepMessage[][];
ThreeStructs: DeepMessage[][][];
MapStructs: Record<string, DeepMessage[]>;
MapTwoStructs: Record<string, DeepMessage[][]>;
MapThreeStructs: Record<string, DeepMessage[][][]>;

static createFrom(source: any = {}) {
return new DeepElements(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Single = source["Single"];
this.Double = source["Double"];
this.FourDouble = source["FourDouble"];
this.DoubleFour = source["DoubleFour"];
this.Triple = source["Triple"];
this.SingleMap = source["SingleMap"];
this.SliceMap = source["SliceMap"];
this.DoubleSliceMap = source["DoubleSliceMap"];
this.ArrayMap = source["ArrayMap"];
this.DoubleArrayMap1 = source["DoubleArrayMap1"];
this.DoubleArrayMap2 = source["DoubleArrayMap2"];
this.DoubleArrayMap3 = source["DoubleArrayMap3"];
this.OneStructs = this.convertValues(source["OneStructs"], DeepMessage);
this.TwoStructs = this.convertValues(source["TwoStructs"], DeepMessage);
this.ThreeStructs = this.convertValues(source["ThreeStructs"], DeepMessage);
this.MapStructs = this.convertValues(source["MapStructs"], DeepMessage[], true);
this.MapTwoStructs = this.convertValues(source["MapTwoStructs"], DeepMessage[][], true);
this.MapThreeStructs = this.convertValues(source["MapThreeStructs"], DeepMessage[][][], true);
}

convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}

}
`,
}
154 changes: 154 additions & 0 deletions v2/internal/binding/binding_test/binding_generics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package binding_test

import "github.com/wailsapp/wails/v2/internal/binding/binding_test/binding_test_import/float_package"

// Issues 3900, 3371, 2323 (no TS generics though)

type ListData[T interface{}] struct {
Total int64 `json:"Total"`
TotalPage int64 `json:"TotalPage"`
PageNum int `json:"PageNum"`
List []T `json:"List,omitempty"`
}

func (x ListData[T]) Get() ListData[T] {
return x
}

var Generics1Test = BindingTest{
name: "Generics1",
structs: []interface{}{
&ListData[string]{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {

export class ListData_string_ {
Total: number;
TotalPage: number;
PageNum: number;
List?: string[];

static createFrom(source: any = {}) {
return new ListData_string_(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Total = source["Total"];
this.TotalPage = source["TotalPage"];
this.PageNum = source["PageNum"];
this.List = source["List"];
}
}

}
`,
}

var Generics2Test = BindingTest{
name: "Generics2",
structs: []interface{}{
&ListData[float_package.SomeStruct]{},
&ListData[*float_package.SomeStruct]{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {

export class ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ {
Total: number;
TotalPage: number;
PageNum: number;
List?: float_package.SomeStruct[];

static createFrom(source: any = {}) {
return new ListData__github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Total = source["Total"];
this.TotalPage = source["TotalPage"];
this.PageNum = source["PageNum"];
this.List = this.convertValues(source["List"], float_package.SomeStruct);
}

convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}
export class ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_ {
Total: number;
TotalPage: number;
PageNum: number;
List?: float_package.SomeStruct[];

static createFrom(source: any = {}) {
return new ListData_github_com_wailsapp_wails_v2_internal_binding_binding_test_binding_test_import_float_package_SomeStruct_(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Total = source["Total"];
this.TotalPage = source["TotalPage"];
this.PageNum = source["PageNum"];
this.List = this.convertValues(source["List"], float_package.SomeStruct);
}

convertValues(a: any, classs: any, asMap: boolean = false): any {
if (!a) {
return a;
}
if (a.slice && a.map) {
return (a as any[]).map(elem => this.convertValues(elem, classs));
} else if ("object" === typeof a) {
if (asMap) {
for (const key of Object.keys(a)) {
a[key] = new classs(a[key]);
}
return a;
}
return new classs(a);
}
return a;
}
}

}

export namespace float_package {

export class SomeStruct {
string: string;

static createFrom(source: any = {}) {
return new SomeStruct(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.string = source["string"];
}
}

}
`,
}
47 changes: 47 additions & 0 deletions v2/internal/binding/binding_test/binding_ignored_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package binding_test

import (
"unsafe"
)

// Issues 3755, 3809

type Ignored struct {
Valid bool
Total func() int `json:"Total"`
UnsafeP unsafe.Pointer
Complex64 complex64 `json:"Complex"`
Complex128 complex128
StringChan chan string
}

func (x Ignored) Get() Ignored {
return x
}

var IgnoredTest = BindingTest{
name: "Ignored",
structs: []interface{}{
&Ignored{},
},
exemptions: nil,
shouldError: false,
want: `
export namespace binding_test {

export class Ignored {
Valid: boolean;

static createFrom(source: any = {}) {
return new Ignored(source);
}

constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.Valid = source["Valid"];
}
}

}
`,
}
Loading