5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-07 16:32:26 +08:00
wails/v3/internal/generator/collect/declaration.go
Fabio Massaioli 90b7ea944d
[v3] New binding generator (#3468)
* Support variadic arguments and slice, pointer types

* Fix computation of type namespaces

* Improve comments and general formatting

* Set default values correctly for composite types

* Add templates for bindings

Additionally:
* fixes generation of tuple return type
* improves imports and namespacing in JS mode
* general cleanup of generated code

* Simplify import list construction

* Refactor type generation code

Improves support for unknown types (encoded as any) and maps (using
Typescript index signatures)

* Support slices with pointer elements

* Match encoding/json behaviour in struct parser

* Update tests and example

* Add tests for complex method signatures and json tag parsing

* Add test `function_multiple_files`

* Attempt looking up idents with missing denotation

* Update test data

* fix quoted bool field

* Test quoted booleans

* Delete old parser code

* Remove old test data

* Update bindgen flags

* Makes call by ID the default

* Add package loading code

* Add static analyser

* Temporarily ignore binding generation code

* Add complex slice expressions test

* Fix variable reference analysis

* Unwrap casts to interface types

* Complete code comments

* Refactor static analyser

* Restrict options struct usage

* Update tests

* Fix method selector sink and source processing

* Improve Set API

* Add package info collector

* Rename analyser package to analyse

* Improve template functions

* Add index file templates

* Add glue code for binding generation

* Refactor collection and rendering code

* Implement binding generator

* Implement global index generation

* Improve marshaler and alias handling

* Use package path in binding calls by name

* Implement model collection and rendering

* Fix wrong exit condition in analyser

* Fix enum rendering

* Generate shortcuts for all packages.

* Implement generator tests

* Ignore non-pointer bound types

* Treat main package specially

* Compute stats

* Plug new API into generate command

* Support all named types

* Update JS runtime

* Report dual role types

* Remove go1.22 syntax

* Fix type assertion in TS bindings

* encoding/json compliance for arrays and slices

* Ignore got files in testdata

* Cleanup type rendering mechanism

* Update JS runtime

* Implement generic models

* Add missing field in renderer initialisation

* Improve generic creation code

* Add generic model test

* Add error reporting infrastructure

* Support configurable file names

* Detect file naming collisions

* Print final error report

* New shortcut file structure + collision detection

* Update test layout and data

* Autoconfiguration for analyser tests

* Live progress reporting

* Update code comments

* Fix model doc rendering

* Simplify name resolution

* Add test for out of tree types

* Fix generic creation code

* Fix potential collisions between methods and models

* Fix generic class alias rendering

* Report model discovery in debug mode

* Add interface mode for JS

* Collect interface method comments

* Add interface methods test

* Unwrap generic instantiations in method receivers

* Fix rendering of nullable types in interface mode

* Fix rendering of class aliases

* Expose promise cancel method to typescript

* Update test data

* Update binding example

* Fix rendering of aliased quoted type params

* Move to strongly typed bindings

* Implement lightweight analyser

* Update test cases

* Update binding example

* Add complex instantiation test

* Load full dependency tree

* Rewrite collector

* Update renderer to match new collector

* Update generator to match new collector

* Update test data

* Update binding example

* Configure includes and injections by language

* Improve system path resolution

* Support rich conditions in inject/include directives

* Fix error handling in Generator.Generate

* Retrieve compiled go file paths from fileset

* Do not rely on struct info in struct flattening algorithm

* Fix doc comment for findDeclaraion

* Fix bugs in embedded field handling

* Fix bugs and comments in package collection

* Remove useless fields from ServiceInfo

* Fix empty line at the beginning of TS indexes

* Remove global index and shortcuts

* Remove generation tests for individual packages

* Enforce lower-case file names

* Update test data

* Improve error reporting

* Update binding example

* Reintroduce go1.22 syntax

* Improve relative import path computation

* Improve alias support

* Add alias test

* Update test data

* Remove no services error

* Rename global analyser test

* Add workaround and test for bug in typeutil.Map

* Update test data

* Do not split fully qualified names

* Update typeutil package and remove workaround

* Unify alias/named type handling

* Fix rendering of generic named class aliases

* Fix rendering of array types

* Minor tweaks and cleanups

* Rmove namespaced export construct

* Update test data

* Update binding example

* Break type cycles

* Fix typo in comment

* Fix creation code for cyclic types

* Fix type of variadic params in interface mode

* Update test data

* Fix bad whitespace

* Refactor type assertions inside bound methods

* Update test data

* Rename field application.Options.Bind to Services

* Rename parser package to generator

* Update binding example

* Update test data

* Update generator readme

* Add typescript test harness

* Move test output to new subfolder

* Fix code generation bugs

* Use .js extensions in TS mode imports

* Update test data

* Revert default generator output dir to frontend/bindings

* Bump runtime package version

* Update templates

* Update changelog

* Improve newline handling

---------

Co-authored-by: Andreas Bichinger <andreas.bichinger@gmail.com>
2024-05-19 20:40:44 +10:00

239 lines
6.1 KiB
Go

package collect
import (
"cmp"
"go/ast"
"go/token"
"go/types"
"slices"
)
// findDeclaration returns the AST spec or declaration
// that defines the given _global_ type-checker object.
//
// Specifically, the first element in the returned slice
// is the relevant spec or declaration, followed by its chain
// of parent nodes up to the declaring [ast.File].
//
// If no corresponding declaration can be found within
// the set of registered packages, the returned slice is nil.
//
// Resulting node types are as follows:
// - global functions and concrete methods (*types.Func)
// map to *ast.FuncDecl nodes;
// - interface methods from global interfaces (*types.Func)
// map to *ast.Field nodes within their interface expression;
// - struct fields from global structs (*types.Var)
// map to *ast.Field nodes within their struct expression;
// - global constants and variables map to *ast.ValueSpec nodes;
// - global named types map to *ast.TypeSpec nodes;
// - for type parameters, the result is always nil;
// - for local objects defined within functions,
// field types, variable types or field values,
// the result is always nil;
//
// findDeclaration supports unsynchronised concurrent calls.
func (collector *Collector) findDeclaration(obj types.Object) (path []ast.Node) {
pkg := collector.Package(obj.Pkg()).Collect()
if pkg == nil {
return nil
}
// Perform a binary search to find the file enclosing the node.
// We can't use findEnclosingNode here because it is less accurate and less efficient with files.
fileIndex, exact := slices.BinarySearchFunc(pkg.Files, obj.Pos(), func(f *ast.File, p token.Pos) int {
return cmp.Compare(f.FileStart, p)
})
// If exact is true, pkg.Files[fileIndex] is the file we are looking for;
// otherwise, it is the first file whose start position is _after_ obj.Pos().
if !exact {
fileIndex--
}
// When exact is false, the position might lie within an empty segment in between two files.
if fileIndex < 0 || pkg.Files[fileIndex].FileEnd <= obj.Pos() {
return nil
}
file := pkg.Files[fileIndex]
// Find enclosing declaration.
decl := findEnclosingNode(file.Decls, obj.Pos())
if decl == nil {
// Invalid position.
return nil
}
var gen *ast.GenDecl
switch d := decl.(type) {
case *ast.FuncDecl:
if obj.Pos() == d.Name.Pos() {
// Object is function.
return []ast.Node{decl, file}
}
// Ignore local objects defined within function bodies.
return nil
case *ast.BadDecl:
// What's up??
return nil
case *ast.GenDecl:
gen = d
}
// Handle *ast.GenDecl
// Find enclosing ast.Spec
spec := findEnclosingNode(gen.Specs, obj.Pos())
if spec == nil {
// Invalid position.
return nil
}
var def ast.Expr
switch s := spec.(type) {
case *ast.ValueSpec:
if s.Names[0].Pos() <= obj.Pos() && obj.Pos() < s.Names[len(s.Names)-1].End() {
// Object is variable or constant.
return []ast.Node{spec, decl, file}
}
// Ignore local objects defined within variable types/values.
return nil
case *ast.TypeSpec:
if obj.Pos() == s.Name.Pos() {
// Object is named type.
return []ast.Node{spec, decl, file}
}
if obj.Pos() < s.Type.Pos() || s.Type.End() <= obj.Pos() {
// Type param or invalid position.
return nil
}
// Struct or interface field?
def = s.Type
}
// Handle struct or interface field.
var iface *ast.InterfaceType
switch d := def.(type) {
case *ast.StructType:
// Find enclosing field
field := findEnclosingNode(d.Fields.List, obj.Pos())
if field == nil {
// Invalid position.
return nil
}
if len(field.Names) == 0 {
// Handle embedded field.
ftype := ast.Unparen(field.Type)
// Unwrap pointer.
if ptr, ok := ftype.(*ast.StarExpr); ok {
ftype = ast.Unparen(ptr.X)
}
// Unwrap generic instantiation.
switch t := field.Type.(type) {
case *ast.IndexExpr:
ftype = ast.Unparen(t.X)
case *ast.IndexListExpr:
ftype = ast.Unparen(t.X)
}
// Unwrap selector.
if sel, ok := ftype.(*ast.SelectorExpr); ok {
ftype = sel.Sel
}
// ftype must now be an identifier.
if obj.Pos() == ftype.Pos() {
// Object is this embedded field.
return []ast.Node{field, d.Fields, def, spec, decl, file}
}
} else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() {
// Object is one of these fields.
return []ast.Node{field, d.Fields, def, spec, decl, file}
}
// Ignore local objects defined within field types.
return nil
case *ast.InterfaceType:
iface = d
default:
// Other local object or invalid position.
return nil
}
path = []ast.Node{file, decl, spec, def, iface.Methods}
// Handle interface method.
for {
field := findEnclosingNode(iface.Methods.List, obj.Pos())
if field == nil {
// Invalid position.
return nil
}
path = append(path, field)
if len(field.Names) == 0 {
// Handle embedded interface.
var ok bool
iface, ok = ast.Unparen(field.Type).(*ast.InterfaceType)
if !ok {
// Not embedded interface, ignore.
return nil
}
path = append(path, iface, iface.Methods)
// Explore embedded interface.
} else if field.Names[0].Pos() <= obj.Pos() && obj.Pos() < field.Names[len(field.Names)-1].End() {
// Object is one of these fields.
slices.Reverse(path)
return path
} else {
// Ignore local objects defined within interface method signatures.
return nil
}
}
}
// findEnclosingNode finds the unique node in nodes, if any,
// that encloses the given position.
//
// It uses binary search and therefore expects
// the nodes slice to be sorted in source order.
func findEnclosingNode[S ~[]E, E ast.Node](nodes S, pos token.Pos) (node E) {
// Perform a binary search to find the nearest node.
index, exact := slices.BinarySearchFunc(nodes, pos, func(n E, p token.Pos) int {
return cmp.Compare(n.Pos(), p)
})
// If exact is true, nodes[index] is the node we are looking for;
// otherwise, it is the first node whose start position is _after_ pos.
if !exact {
index--
}
// When exact is false, the position might lie within an empty segment in between two nodes.
if index < 0 || nodes[index].End() <= pos {
return // zero value, nil in practice.
}
return nodes[index]
}