mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 21:00:31 +08:00

* Rename predicates source file * Overhaul and document type predicates * Fix model collection logic for named types * Fix map key type rendering * Fix map creation code * Fix rendering of structs that implement marshaler interfaces * Fix type cycle detection to take type args into account * Fix enum and typeparam field initialisation * Improve unsupported type warnings * Remove internal models file * Deduplicate template code * Accept generic aliases in static analyser * Support new `encoding/json` flag `omitzero` * Handle special cases when rendering generic aliases * Update npm test dependencies * Test class aliases and implicit private dependencies * Test marshaler combinations * Test map key types * Remove bad map keys from unrelated tests * Test service discovery through generic aliases * Test generic aliases * Test warning messages * Disable go1.24 tests * Update changelog * Restore rendering of injected lines in index file * Test directives * Add wails:ignore directive * Fix typo * Move injections to the bottom of service files * Handle errors from closing files * Do not emit messages when services define only lifecycle methods * Add internal directive for services and models * Update changelog * Fix error in service templates * Test internal directive on services/models * Fix error in index template * Base testdata updates * Testdata for class aliases and implicit private dependencies * Testdata for marshaler combinations * Testdata for map key types * Testdata for bad map key fixes * Add weakly typed enums aka alias constants * Testdata for enum and typeparam field fixes * Testdata for generic aliases * Testdata for warning messages * Testdata for directives * Testdata for weakly typed enums * Update binding example * Update services example * Remove go1.24 testdata * Update cli doc * Fix analyser tests * Fix windows tests... hopefully * go mod tidy on examples * Update bindings guide --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
package generator
|
|
|
|
import (
|
|
"fmt"
|
|
"go/token"
|
|
"go/types"
|
|
"iter"
|
|
|
|
"github.com/wailsapp/wails/v3/internal/generator/config"
|
|
"golang.org/x/tools/go/packages"
|
|
)
|
|
|
|
// FindServices scans the given packages for invocations
|
|
// of the NewService function from the Wails application package.
|
|
//
|
|
// Whenever one is found and the type of its unique argument
|
|
// is a valid service type, the corresponding named type object
|
|
// is passed to yield.
|
|
//
|
|
// 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) (iter.Seq[*types.TypeName], error) {
|
|
type instanceInfo struct {
|
|
args *types.TypeList
|
|
pos token.Position
|
|
}
|
|
|
|
type target struct {
|
|
obj types.Object
|
|
param int
|
|
}
|
|
|
|
type targetInfo struct {
|
|
target
|
|
cause token.Position
|
|
}
|
|
|
|
// instances maps objects (TypeName or Func) to their instance list.
|
|
instances := make(map[types.Object][]instanceInfo)
|
|
|
|
// owner maps type parameter objects to their parent object (TypeName or Func)
|
|
owner := make(map[*types.TypeName]types.Object)
|
|
|
|
// scheduled holds the set of type parameters
|
|
// that have been already scheduled for analysis,
|
|
// for deduplication.
|
|
scheduled := make(map[target]bool)
|
|
|
|
// next lists type parameter objects that have yet to be analysed.
|
|
var next []targetInfo
|
|
|
|
// Initialise instance/owner maps and detect application.NewService.
|
|
for _, pkg := range pkgs {
|
|
for ident, instance := range pkg.TypesInfo.Instances {
|
|
obj := pkg.TypesInfo.Uses[ident]
|
|
|
|
// Add to instance map.
|
|
objInstances, seen := instances[obj]
|
|
instances[obj] = append(objInstances, instanceInfo{
|
|
instance.TypeArgs,
|
|
pkg.Fset.Position(ident.Pos()),
|
|
})
|
|
|
|
if seen {
|
|
continue
|
|
}
|
|
|
|
// Object seen for the first time:
|
|
// add type params to owner map.
|
|
var tp *types.TypeParamList
|
|
|
|
if t, ok := obj.Type().(interface{ TypeParams() *types.TypeParamList }); ok {
|
|
tp = t.TypeParams()
|
|
} else {
|
|
// Instantiated object has unexpected kind:
|
|
// the spec might have changed.
|
|
logger.Warningf(
|
|
"unexpected instantiation for %s: please report this to Wails maintainers",
|
|
types.ObjectString(obj, nil),
|
|
)
|
|
continue
|
|
}
|
|
|
|
// Add type params to owner map.
|
|
for i := range tp.Len() {
|
|
if param := tp.At(i).Obj(); param != nil {
|
|
owner[param] = obj
|
|
}
|
|
}
|
|
|
|
// If this is a named type, process methods.
|
|
if recv, ok := obj.Type().(*types.Named); ok && recv.NumMethods() > 0 {
|
|
// Register receiver type params.
|
|
for i := range recv.NumMethods() {
|
|
tp := recv.Method(i).Type().(*types.Signature).RecvTypeParams()
|
|
for j := range tp.Len() {
|
|
if param := tp.At(j).Obj(); param != nil {
|
|
owner[param] = obj
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(next) > 0 {
|
|
// application.NewService has been found already.
|
|
continue
|
|
}
|
|
|
|
fn, ok := obj.(*types.Func)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// Detect application.NewService
|
|
if fn.Name() == "NewService" && fn.Pkg().Path() == systemPaths.ApplicationPackage {
|
|
// Check signature.
|
|
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 nil, ErrBadApplicationPackage
|
|
}
|
|
|
|
// Schedule unique type param for analysis.
|
|
tgt := target{obj, 0}
|
|
scheduled[tgt] = true
|
|
next = append(next, targetInfo{target: tgt})
|
|
}
|
|
}
|
|
}
|
|
|
|
// found tracks service types that have been found so far, for deduplication.
|
|
found := make(map[*types.TypeName]bool)
|
|
|
|
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)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Push on next list.
|
|
next = append(next, targetInfo{newtgt, cause})
|
|
}
|
|
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
|
|
}
|
|
|
|
// Record and yield type object.
|
|
if !found[named.Obj()] {
|
|
found[named.Obj()] = true
|
|
if !yield(named.Obj()) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}, nil
|
|
}
|