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

[v3] Fix binding generator bugs #4001

Open
wants to merge 24 commits into
base: v3-alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7cc0b66
Add some clarifying comments
fbbdev Jan 6, 2025
0e4a052
Remove special handling of window parameters
fbbdev Jan 9, 2025
6e04897
Improve internal method exclusion
fbbdev Jan 9, 2025
f5f06c1
Add test for internal method exclusion
fbbdev Jan 15, 2025
5d3dc0e
Remove useless blank field from app options
fbbdev Jan 15, 2025
d25886d
Remove redundant godebug setting
fbbdev Jan 15, 2025
804406a
Use new range for syntax to simplify code
fbbdev Jan 15, 2025
c49e103
Remove generator dependency on github.com/samber/lo
fbbdev Jan 15, 2025
3200713
Ensure generator testing tasks do not use the test cache
fbbdev Jan 15, 2025
a6f318f
Rename cyclic types test
fbbdev Jan 15, 2025
215d861
Test for cyclic imports
fbbdev Jan 15, 2025
720661e
Fix import cycle between model files
fbbdev Jan 10, 2025
5fcce72
Sort class aliases after their aliased class
fbbdev Jan 11, 2025
cb51682
Test class aliases
fbbdev Jan 11, 2025
f94a34a
Fix length of default value for array types
fbbdev Jan 14, 2025
6e366ed
Test array initialization
fbbdev Jan 14, 2025
7415949
Add changelog
fbbdev Jan 15, 2025
47aa75c
Update changelog
fbbdev Jan 15, 2025
f496fb7
Merge branch 'v3-alpha' into v3/bindgen-fixes
leaanthony Jan 16, 2025
be9b31c
Merge branch 'v3-alpha' into v3/bindgen-fixes
leaanthony Jan 16, 2025
5e84f8a
Fix contrived marking technique in model sorting algorithm
fbbdev Jan 16, 2025
f69420f
Merge branch 'v3-alpha' into v3/bindgen-fixes
leaanthony Jan 16, 2025
c5e99f5
Update binding example
fbbdev Jan 15, 2025
ee4ed99
Update test data
fbbdev Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 7 additions & 0 deletions docs/src/content/docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Breaking Changes

- Renamed Service methods: `Name` -> `ServiceName`, `OnStartup` -> `ServiceStartup`, `OnShutdown` -> `ServiceShutdown` by [@leaanthony](https://github.com/leaanthony)

### Added

- Support aarch64 AppImage builds by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Add diagnostics section to `wails doctor` by [@leaanthony](https://github.com/leaanthony)
- Add window to context when calling a service method by [@leaanthony](https://github.com/leaanthony)
- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony)
- Better panic handling by [@leaanthony](https://github.com/leaanthony)

### Fixed

- Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062)
- Updated the minimum system version in macOS .plist files from 10.13.0 to 10.15.0 by [@AkshayKalose](https://github.com/AkshayKalose) in [#3981](https://github.com/wailsapp/wails/pull/3981)
- Window ID skip issue by [@leaanthony](https://github.com/leaanthony)
- Fix nil menu issue when calling RegisterContextMenu by [@leaanthony](https://github.com/leaanthony)
- Fixed dependency cycles in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
- Fixed use-before-define errors in binding generator output by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)

### Changed

- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony)
- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)

## v3.0.0-alpha.9 - 2025-01-13

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "/wails/runtime.js";

/**
* Person holds someone's most important attributes
*/
export class Person {
/**
* Creates a new Person instance.
* @param {Partial<Person>} [$$source = {}] - The source object to create the Person.
*/
constructor($$source = {}) {
if (!("name" in $$source)) {
/**
* Name is the person's name
* @member
* @type {string}
*/
this["name"] = "";
}
if (!("counts" in $$source)) {
/**
* Counts tracks the number of time the person
* has been greeted in various ways
* @member
* @type {number[]}
*/
this["counts"] = [];
}

Object.assign(this, $$source);
}

/**
* Creates a new Person instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Person}
*/
static createFrom($$source = {}) {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("counts" in $$parsedSource) {
$$parsedSource["counts"] = $$createField1_0($$parsedSource["counts"]);
}
return new Person(/** @type {Partial<Person>} */($$parsedSource));
}
}

// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "/wails/runtime.js";

/**
* Person holds someone's most important attributes
*/
export class Person {
/**
* Creates a new Person instance.
* @param {Partial<Person>} [$$source = {}] - The source object to create the Person.
*/
constructor($$source = {}) {
if (!("name" in $$source)) {
/**
* Name is the person's name
* @member
* @type {string}
*/
this["name"] = "";
}
if (!("counts" in $$source)) {
/**
* Counts tracks the number of time the person
* has been greeted in various ways
* @member
* @type {number[]}
*/
this["counts"] = [];
}

Object.assign(this, $$source);
}

/**
* Creates a new Person instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Person}
*/
static createFrom($$source = {}) {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("counts" in $$parsedSource) {
$$parsedSource["counts"] = $$createField1_0($$parsedSource["counts"]);
}
return new Person(/** @type {Partial<Person>} */($$parsedSource));
}
}

// Private type creation functions
const $$createType0 = $Create.Array($Create.Any);
export {
Person
} from "./internal.js";
11 changes: 6 additions & 5 deletions v3/internal/generator/Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,26 @@ tasks:

test:
cmds:
- go test -v .
- go test -count=1 -v .
- task: test:check

test:analyse:
cmds:
- go test -v -run ^TestAnalyser .
- go test -count=1 -v -run ^TestAnalyser .

test:constants:
cmds:
- go test -v -run ^TestGenerateConstants .
- go test -v -count=1 -run ^TestGenerateConstants .

test:generate:
cmds:
- go test -v -run ^TestGenerator .
- go test -v -count=1 -run ^TestGenerator .
- task: test:check

test:regenerate:
cmds:
- cmd: rm -rf ./testdata/output/*
- cmd: go test -v -run ^TestGenerator .
- cmd: go test -v -count=1 -run ^TestGenerator .
ignore_error: true
- task: test:generate

Expand All @@ -40,6 +40,7 @@ tasks:
- install-deps
cmds:
- npx tsc
- npx madge --circular output/

install-deps:
internal: true
Expand Down
119 changes: 60 additions & 59 deletions v3/internal/generator/analyse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"go/token"
"go/types"
"iter"

"github.com/wailsapp/wails/v3/internal/generator/config"
"golang.org/x/tools/go/packages"
Expand All @@ -19,7 +20,7 @@ import (
// Results are deduplicated, i.e. yield is called at most once per object.
//
// If yield returns false, FindBoundTypes returns immediately.
func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger, yield func(*types.TypeName) bool) error {
func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger) (iter.Seq[*types.TypeName], error) {
type instanceInfo struct {
args *types.TypeList
pos token.Position
Expand Down Expand Up @@ -122,7 +123,7 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
signature := fn.Type().(*types.Signature)
if signature.Params().Len() > 2 || signature.Results().Len() != 1 || tp.Len() != 1 || tp.At(0).Obj() == nil {
logger.Warningf("Param Len: %d, Results Len: %d, tp.Len: %d, tp.At(0).Obj(): %v", signature.Params().Len(), signature.Results().Len(), tp.Len(), tp.At(0).Obj())
return ErrBadApplicationPackage
return nil, ErrBadApplicationPackage
}

// Schedule unique type param for analysis.
Expand All @@ -136,71 +137,71 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
// found tracks service types that have been found so far, for deduplication.
found := make(map[*types.TypeName]bool)

// Process targets.
for len(next) > 0 {
// Pop one target off the next list.
tgt := next[len(next)-1]
next = next[:len(next)-1]

// Prepare indirect binding message.
indirectMsg := ""
if tgt.cause.IsValid() {
indirectMsg = fmt.Sprintf(" (indirectly bound at %s)", tgt.cause)
}

for _, instance := range instances[tgt.obj] {
// Retrieve type argument.
serviceType := types.Unalias(instance.args.At(tgt.param))
return func(yield func(*types.TypeName) bool) {
// Process targets.
for len(next) > 0 {
// Pop one target off the next list.
tgt := next[len(next)-1]
next = next[:len(next)-1]

// Prepare indirect binding message.
indirectMsg := ""
if tgt.cause.IsValid() {
indirectMsg = fmt.Sprintf(" (indirectly bound at %s)", tgt.cause)
}

var named *types.Named
for _, instance := range instances[tgt.obj] {
// Retrieve type argument.
serviceType := types.Unalias(instance.args.At(tgt.param))

var named *types.Named

switch t := serviceType.(type) {
case *types.Named:
// Process named type.
named = t.Origin()

case *types.TypeParam:
// Schedule type parameter for analysis.
newtgt := target{owner[t.Obj()], t.Index()}
if !scheduled[newtgt] {
scheduled[newtgt] = true

// Retrieve position of call to application.NewService
// that caused this target to be scheduled.
cause := tgt.cause
if !tgt.cause.IsValid() {
// This _is_ a call to application.NewService.
cause = instance.pos
}

switch t := serviceType.(type) {
case *types.Named:
// Process named type.
named = t.Origin()

case *types.TypeParam:
// Schedule type parameter for analysis.
newtgt := target{owner[t.Obj()], t.Index()}
if !scheduled[newtgt] {
scheduled[newtgt] = true

// Retrieve position of call to application.NewService
// that caused this target to be scheduled.
cause := tgt.cause
if !tgt.cause.IsValid() {
// This _is_ a call to application.NewService.
cause = instance.pos
// Push on next list.
next = append(next, targetInfo{newtgt, cause})
}
continue

// Push on next list.
next = append(next, targetInfo{newtgt, cause})
default:
logger.Warningf("%s: ignoring anonymous service type %s%s", instance.pos, serviceType, indirectMsg)
continue
}
continue

default:
logger.Warningf("%s: ignoring anonymous service type %s%s", instance.pos, serviceType, indirectMsg)
continue
}

// Reject interfaces and generic types.
if types.IsInterface(named.Underlying()) {
logger.Warningf("%s: ignoring interface service type %s%s", instance.pos, named, indirectMsg)
continue
} else if named.TypeParams() != nil {
logger.Warningf("%s: ignoring generic service type %s", instance.pos, named, indirectMsg)
continue
}
// Reject interfaces and generic types.
if types.IsInterface(named.Underlying()) {
logger.Warningf("%s: ignoring interface service type %s%s", instance.pos, named, indirectMsg)
continue
} else if named.TypeParams() != nil {
logger.Warningf("%s: ignoring generic service type %s", instance.pos, named, indirectMsg)
continue
}

// Record and yield type object.
if !found[named.Obj()] {
found[named.Obj()] = true
if !yield(named.Obj()) {
return nil
// Record and yield type object.
if !found[named.Obj()] {
found[named.Obj()] = true
if !yield(named.Obj()) {
return
}
}
}
}
}

return nil
}, nil
fbbdev marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 5 additions & 4 deletions v3/internal/generator/analyse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,15 @@ func TestAnalyser(t *testing.T) {

got := make([]string, 0)

err = FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil), func(tn *types.TypeName) bool {
got = append(got, types.TypeString(tn.Type(), nil))
return true
})
services, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil))
if err != nil {
t.Error(err)
}

for obj := range services {
got = append(got, types.TypeString(obj.Type(), nil))
}
fbbdev marked this conversation as resolved.
Show resolved Hide resolved

slices.Sort(got)

if diff := cmp.Diff(test.want, got); diff != "" {
Expand Down
12 changes: 1 addition & 11 deletions v3/internal/generator/collect/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ type (

// ImportModels records whether models from the current package may be needed.
ImportModels bool
// ImportInternal records whether internal models from the current package may be needed.
ImportInternal bool

// External records information about each imported package,
// keyed by package path.
Expand Down Expand Up @@ -68,9 +66,6 @@ func (imports *ImportMap) Merge(other *ImportMap) {
if other.ImportModels {
imports.ImportModels = true
}
if other.ImportInternal {
imports.ImportInternal = true
}

for path, info := range other.External {
if _, ok := imports.External[path]; ok {
Expand Down Expand Up @@ -151,12 +146,7 @@ func (imports *ImportMap) addTypeImpl(typ types.Type, visited map[*types.TypeNam
}

if obj.Pkg().Path() == imports.Self {
// Record self import.
if obj.Exported() {
imports.ImportModels = true
} else {
imports.ImportInternal = true
}
imports.ImportModels = true
}

// Record model.
Expand Down
Loading
Loading