Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
590ac7ff5f
commit
20149b7f3a
456 changed files with 70406 additions and 0 deletions
248
internal/importers/ast.go
Normal file
248
internal/importers/ast.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The importers package uses go/ast to analyze Go packages or Go files
|
||||
// and collect references to types whose package has a package prefix.
|
||||
// It is used by the language specific importers to determine the set of
|
||||
// wrapper types to be generated.
|
||||
//
|
||||
// # For example, in the Go file
|
||||
//
|
||||
// package javaprogram
|
||||
//
|
||||
// import "Java/java/lang"
|
||||
//
|
||||
// func F() {
|
||||
//
|
||||
// o := lang.Object.New()
|
||||
// ...
|
||||
//
|
||||
// }
|
||||
//
|
||||
// the java importer uses this package to determine that the "java/lang"
|
||||
// package and the wrapper interface, lang.Object, needs to be generated.
|
||||
// After calling AnalyzeFile or AnalyzePackages, the References result
|
||||
// contains the reference to lang.Object and the names set will contain
|
||||
// "New".
|
||||
package importers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// References is the result of analyzing a Go file or set of Go packages.
|
||||
//
|
||||
// # For example, the Go file
|
||||
//
|
||||
// package pkg
|
||||
//
|
||||
// import "Prefix/some/Package"
|
||||
//
|
||||
// var A = Package.Identifier
|
||||
//
|
||||
// Will result in a single PkgRef with the "some/Package" package and
|
||||
// the Identifier name. The Names set will contain the single name,
|
||||
// "Identifier".
|
||||
type References struct {
|
||||
// The list of references to identifiers in packages that are
|
||||
// identified by a package prefix.
|
||||
Refs []PkgRef
|
||||
// The list of names used in at least one selector expression.
|
||||
// Useful as a conservative upper bound on the set of identifiers
|
||||
// referenced from a set of packages.
|
||||
Names map[string]struct{}
|
||||
// Embedders is a list of struct types with prefixed types
|
||||
// embedded.
|
||||
Embedders []Struct
|
||||
}
|
||||
|
||||
// Struct is a representation of a struct type with embedded
|
||||
// types.
|
||||
type Struct struct {
|
||||
Name string
|
||||
Pkg string
|
||||
PkgPath string
|
||||
Refs []PkgRef
|
||||
}
|
||||
|
||||
// PkgRef is a reference to an identifier in a package.
|
||||
type PkgRef struct {
|
||||
Name string
|
||||
Pkg string
|
||||
}
|
||||
|
||||
type refsSaver struct {
|
||||
pkgPrefix string
|
||||
*References
|
||||
refMap map[PkgRef]struct{}
|
||||
insideStruct bool
|
||||
}
|
||||
|
||||
// AnalyzeFile scans the provided file for references to packages with the given
|
||||
// package prefix. The list of unique (package, identifier) pairs is returned
|
||||
func AnalyzeFile(file *ast.File, pkgPrefix string) (*References, error) {
|
||||
visitor := newRefsSaver(pkgPrefix)
|
||||
fset := token.NewFileSet()
|
||||
files := map[string]*ast.File{file.Name.Name: file}
|
||||
// Ignore errors (from unknown packages)
|
||||
pkg, _ := ast.NewPackage(fset, files, visitor.importer(), nil)
|
||||
ast.Walk(visitor, pkg)
|
||||
visitor.findEmbeddingStructs("", pkg)
|
||||
return visitor.References, nil
|
||||
}
|
||||
|
||||
// AnalyzePackages scans the provided packages for references to packages with the given
|
||||
// package prefix. The list of unique (package, identifier) pairs is returned
|
||||
func AnalyzePackages(pkgs []*packages.Package, pkgPrefix string) (*References, error) {
|
||||
visitor := newRefsSaver(pkgPrefix)
|
||||
imp := visitor.importer()
|
||||
fset := token.NewFileSet()
|
||||
for _, pkg := range pkgs {
|
||||
files := make(map[string]*ast.File)
|
||||
for i, name := range pkg.GoFiles {
|
||||
files[name] = pkg.Syntax[i]
|
||||
}
|
||||
// Ignore errors (from unknown packages)
|
||||
astpkg, _ := ast.NewPackage(fset, files, imp, nil)
|
||||
ast.Walk(visitor, astpkg)
|
||||
visitor.findEmbeddingStructs(pkg.PkgPath, astpkg)
|
||||
}
|
||||
return visitor.References, nil
|
||||
}
|
||||
|
||||
// findEmbeddingStructs finds all top level declarations embedding a prefixed type.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// import "Prefix/some/Package"
|
||||
//
|
||||
// type T struct {
|
||||
//
|
||||
// Package.Class
|
||||
//
|
||||
// }
|
||||
func (v *refsSaver) findEmbeddingStructs(pkgpath string, pkg *ast.Package) {
|
||||
var names []string
|
||||
for _, obj := range pkg.Scope.Objects {
|
||||
if obj.Kind != ast.Typ || !ast.IsExported(obj.Name) {
|
||||
continue
|
||||
}
|
||||
names = append(names, obj.Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
obj := pkg.Scope.Objects[name]
|
||||
|
||||
t, ok := obj.Decl.(*ast.TypeSpec).Type.(*ast.StructType)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var refs []PkgRef
|
||||
for _, f := range t.Fields.List {
|
||||
sel, ok := f.Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ref, ok := v.addRef(sel)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(f.Names) > 0 && !f.Names[0].IsExported() {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
if len(refs) > 0 {
|
||||
v.Embedders = append(v.Embedders, Struct{
|
||||
Name: obj.Name,
|
||||
Pkg: pkg.Name,
|
||||
PkgPath: pkgpath,
|
||||
|
||||
Refs: refs,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRefsSaver(pkgPrefix string) *refsSaver {
|
||||
s := &refsSaver{
|
||||
pkgPrefix: pkgPrefix,
|
||||
refMap: make(map[PkgRef]struct{}),
|
||||
References: &References{},
|
||||
}
|
||||
s.Names = make(map[string]struct{})
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *refsSaver) importer() ast.Importer {
|
||||
return func(imports map[string]*ast.Object, pkgPath string) (*ast.Object, error) {
|
||||
if pkg, exists := imports[pkgPath]; exists {
|
||||
return pkg, nil
|
||||
}
|
||||
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
||||
return nil, errors.New("ignored")
|
||||
}
|
||||
pkg := ast.NewObj(ast.Pkg, path.Base(pkgPath))
|
||||
imports[pkgPath] = pkg
|
||||
return pkg, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *refsSaver) addRef(sel *ast.SelectorExpr) (PkgRef, bool) {
|
||||
x, ok := sel.X.(*ast.Ident)
|
||||
if !ok || x.Obj == nil {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
imp, ok := x.Obj.Decl.(*ast.ImportSpec)
|
||||
if !ok {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
pkgPath, err := strconv.Unquote(imp.Path.Value)
|
||||
if err != nil {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
if !strings.HasPrefix(pkgPath, v.pkgPrefix) {
|
||||
return PkgRef{}, false
|
||||
}
|
||||
pkgPath = pkgPath[len(v.pkgPrefix):]
|
||||
ref := PkgRef{Pkg: pkgPath, Name: sel.Sel.Name}
|
||||
if _, exists := v.refMap[ref]; !exists {
|
||||
v.refMap[ref] = struct{}{}
|
||||
v.Refs = append(v.Refs, ref)
|
||||
}
|
||||
return ref, true
|
||||
}
|
||||
|
||||
func (v *refsSaver) Visit(n ast.Node) ast.Visitor {
|
||||
switch n := n.(type) {
|
||||
case *ast.StructType:
|
||||
// Use a copy of refsSaver that only accepts exported fields. It refers
|
||||
// to the original refsSaver for collecting references.
|
||||
v2 := *v
|
||||
v2.insideStruct = true
|
||||
return &v2
|
||||
case *ast.Field:
|
||||
if v.insideStruct && len(n.Names) == 1 && !n.Names[0].IsExported() {
|
||||
return nil
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
v.Names[n.Sel.Name] = struct{}{}
|
||||
if _, ok := v.addRef(n); ok {
|
||||
return nil
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
if n.Recv != nil { // Methods
|
||||
v.Names[n.Name.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
69
internal/importers/ast_test.go
Normal file
69
internal/importers/ast_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package importers
|
||||
|
||||
import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnalyzer(t *testing.T) {
|
||||
file := `package ast_test
|
||||
|
||||
import "Prefix/some/pkg/Name"
|
||||
import "Prefix/some/pkg/Name2"
|
||||
|
||||
const c = Name.Constant
|
||||
|
||||
type T struct {
|
||||
Name.Type
|
||||
unexported Name.Type2
|
||||
}
|
||||
|
||||
func f() {
|
||||
Name2.Func().Func().Func()
|
||||
}
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "ast_test.go", file, parser.AllErrors)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
refs, err := AnalyzeFile(f, "Prefix/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exps := []PkgRef{
|
||||
{Pkg: "some/pkg/Name", Name: "Constant"},
|
||||
{Pkg: "some/pkg/Name", Name: "Type"},
|
||||
{Pkg: "some/pkg/Name2", Name: "Func"},
|
||||
{Pkg: "some/pkg/Name", Name: "Type2"},
|
||||
}
|
||||
if len(refs.Refs) != len(exps) {
|
||||
t.Fatalf("expected %d references; got %d", len(exps), len(refs.Refs))
|
||||
}
|
||||
for i, exp := range exps {
|
||||
if got := refs.Refs[i]; exp != got {
|
||||
t.Errorf("expected ref %v; got %v", exp, got)
|
||||
}
|
||||
}
|
||||
if _, exists := refs.Names["Constant"]; !exists {
|
||||
t.Errorf("expected \"Constant\" in the names set")
|
||||
}
|
||||
if len(refs.Embedders) != 1 {
|
||||
t.Fatalf("expected 1 struct; got %d", len(refs.Embedders))
|
||||
}
|
||||
s := refs.Embedders[0]
|
||||
exp := Struct{
|
||||
Name: "T",
|
||||
Pkg: "ast_test",
|
||||
Refs: []PkgRef{{Pkg: "some/pkg/Name", Name: "Type"}},
|
||||
}
|
||||
if !reflect.DeepEqual(exp, s) {
|
||||
t.Errorf("expected struct %v; got %v", exp, s)
|
||||
}
|
||||
}
|
1183
internal/importers/java/java.go
Normal file
1183
internal/importers/java/java.go
Normal file
File diff suppressed because it is too large
Load diff
206
internal/importers/java/java_test.go
Normal file
206
internal/importers/java/java_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package java
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
if !IsAvailable() {
|
||||
t.Skipf("javap not available")
|
||||
}
|
||||
tests := []struct {
|
||||
ref importers.PkgRef
|
||||
name string
|
||||
methods []*FuncSet
|
||||
}{
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "java/lang/Object", Name: "equals"},
|
||||
name: "java.lang.Object",
|
||||
methods: []*FuncSet{
|
||||
&FuncSet{
|
||||
Name: "equals",
|
||||
GoName: "Equals",
|
||||
CommonSig: CommonSig{
|
||||
Params: []*Type{&Type{Kind: Object, Class: "java.lang.Object"}}, Ret: &Type{Kind: Boolean}, HasRet: true,
|
||||
},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "equals", Desc: "(Ljava/lang/Object;)Z"}, ArgDesc: "Ljava/lang/Object;", JNIName: "equals", Public: true, Params: []*Type{&Type{Kind: Object, Class: "java.lang.Object"}}, Ret: &Type{Kind: Boolean}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "java/lang/Runnable", Name: "run"},
|
||||
name: "java.lang.Runnable",
|
||||
methods: []*FuncSet{
|
||||
&FuncSet{
|
||||
Name: "run",
|
||||
GoName: "Run",
|
||||
CommonSig: CommonSig{},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "run", Desc: "()V"}, ArgDesc: "", JNIName: "run", Public: true, Abstract: true}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
toString := &FuncSet{
|
||||
Name: "toString",
|
||||
GoName: "ToString",
|
||||
CommonSig: CommonSig{
|
||||
Ret: &Type{Kind: String}, HasRet: true,
|
||||
},
|
||||
Funcs: []*Func{&Func{FuncSig: FuncSig{Name: "toString", Desc: "()Ljava/lang/String;"}, ArgDesc: "", JNIName: "toString", Public: true, Ret: &Type{Kind: String}}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
refs := &importers.References{
|
||||
Refs: []importers.PkgRef{test.ref},
|
||||
Names: make(map[string]struct{}),
|
||||
}
|
||||
for _, m := range test.methods {
|
||||
refs.Names[m.GoName] = struct{}{}
|
||||
}
|
||||
classes, err := (&Importer{}).Import(refs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(classes) != 1 {
|
||||
t.Fatalf("got %d classes, expected 1", len(classes))
|
||||
}
|
||||
cls := classes[0]
|
||||
if cls.Name != test.name {
|
||||
t.Errorf("got class name %s, expected %s", cls.Name, test.name)
|
||||
}
|
||||
methods := test.methods
|
||||
if !cls.Interface {
|
||||
methods = append(methods, toString)
|
||||
}
|
||||
loop:
|
||||
for _, exp := range methods {
|
||||
for _, got := range cls.AllMethods {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
t.Errorf("failed to find method: %+v", exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testClsMap() map[string]*Class {
|
||||
//
|
||||
// A--
|
||||
// / \ \
|
||||
// B C \
|
||||
// \ / \ \
|
||||
// D E F
|
||||
//
|
||||
return map[string]*Class{
|
||||
"A": &Class{},
|
||||
"B": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
"C": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
"D": &Class{
|
||||
Supers: []string{"B", "C"},
|
||||
},
|
||||
"E": &Class{
|
||||
Supers: []string{"C"},
|
||||
},
|
||||
"F": &Class{
|
||||
Supers: []string{"A"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonTypes(t *testing.T) {
|
||||
clsMap := testClsMap()
|
||||
tests := [][3]*Type{
|
||||
{nil, nil, nil},
|
||||
{&Type{Kind: Int}, nil, nil},
|
||||
{&Type{Kind: Int}, &Type{Kind: Float}, nil},
|
||||
{&Type{Kind: Int}, &Type{Kind: Int}, &Type{Kind: Int}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "D"}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "E"}, &Type{Kind: Object, Class: "C"}},
|
||||
{&Type{Kind: Object, Class: "D"}, &Type{Kind: Object, Class: "F"}, &Type{Kind: Object, Class: "A"}},
|
||||
{&Type{Kind: Object, Class: "B"}, &Type{Kind: Object, Class: "E"}, &Type{Kind: Object, Class: "A"}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t1, t2, exp := test[0], test[1], test[2]
|
||||
got := commonType(clsMap, t1, t2)
|
||||
if !reflect.DeepEqual(got, exp) {
|
||||
t.Errorf("commonType(%+v, %+v) = %+v, expected %+v", t1, t2, got, exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonSig(t *testing.T) {
|
||||
tests := []struct {
|
||||
Sigs []CommonSig
|
||||
CommonSig
|
||||
}{
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{}, // f()
|
||||
},
|
||||
CommonSig: CommonSig{}, // f()
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Throws: true, HasRet: true, Ret: &Type{Kind: Int}}, // int f() throws
|
||||
},
|
||||
// int f() throws
|
||||
CommonSig: CommonSig{Throws: true, HasRet: true, Ret: &Type{Kind: Int}},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{}, // f()
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(int...)
|
||||
Variadic: true,
|
||||
Params: []*Type{&Type{Kind: Int}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Float}}}, // f(float)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(interface{})
|
||||
Params: []*Type{nil},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}}}, // f(int)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Int}, &Type{Kind: Int}}}, // f(int, int)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(int, int...)
|
||||
Variadic: true,
|
||||
Params: []*Type{&Type{Kind: Int}, &Type{Kind: Int}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Sigs: []CommonSig{
|
||||
CommonSig{Params: []*Type{&Type{Kind: Object, Class: "A"}}}, // f(A)
|
||||
CommonSig{Params: []*Type{&Type{Kind: Object, Class: "B"}}}, // f(B)
|
||||
},
|
||||
CommonSig: CommonSig{ // f(A)
|
||||
Params: []*Type{&Type{Kind: Object, Class: "A"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
clsMap := testClsMap()
|
||||
for _, test := range tests {
|
||||
got := combineSigs(clsMap, test.Sigs...)
|
||||
if !reflect.DeepEqual(got, test.CommonSig) {
|
||||
t.Errorf("commonSig(%+v) = %+v, expected %+v", test.Sigs, got, test.CommonSig)
|
||||
}
|
||||
}
|
||||
}
|
868
internal/importers/objc/objc.go
Normal file
868
internal/importers/objc/objc.go
Normal file
|
@ -0,0 +1,868 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The objc package takes the result of an AST traversal by the
|
||||
// importers package and uses the clang command to dump the type
|
||||
// information for the referenced ObjC classes and protocols.
|
||||
//
|
||||
// It is the of go/types for ObjC types and is used by the bind
|
||||
// package to generate Go wrappers for ObjC API on iOS.
|
||||
package objc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
sdkPath string
|
||||
sc *bufio.Scanner
|
||||
|
||||
decl string
|
||||
indent int
|
||||
last string
|
||||
// Current module as parsed from the AST tree.
|
||||
module string
|
||||
}
|
||||
|
||||
type TypeKind int
|
||||
|
||||
// Named represents ObjC classes and protocols.
|
||||
type Named struct {
|
||||
Name string
|
||||
GoName string
|
||||
Module string
|
||||
Funcs []*Func
|
||||
Methods []*Func
|
||||
AllMethods []*Func
|
||||
Supers []Super
|
||||
// For deduplication of function or method
|
||||
// declarations.
|
||||
funcMap map[string]struct{}
|
||||
Protocol bool
|
||||
// Generated is true if the type is wrapper of a
|
||||
// generated Go struct.
|
||||
Generated bool
|
||||
}
|
||||
|
||||
// Super denotes a super class or protocol.
|
||||
type Super struct {
|
||||
Name string
|
||||
Protocol bool
|
||||
}
|
||||
|
||||
// Func is a ObjC method, static functions as well as
|
||||
// instance methods.
|
||||
type Func struct {
|
||||
Sig string
|
||||
GoName string
|
||||
Params []*Param
|
||||
Ret *Type
|
||||
Static bool
|
||||
// Method whose name start with "init"
|
||||
Constructor bool
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
Name string
|
||||
Type *Type
|
||||
}
|
||||
|
||||
type Type struct {
|
||||
Kind TypeKind
|
||||
// For Interface and Protocol types.
|
||||
Name string
|
||||
// For 'id' types.
|
||||
instanceType bool
|
||||
// The declared type raw from the AST.
|
||||
Decl string
|
||||
// Set if the type is a pointer to its kind. For classes
|
||||
// Indirect is true if the type is a double pointer, e.g.
|
||||
// NSObject **.
|
||||
Indirect bool
|
||||
}
|
||||
|
||||
const (
|
||||
Unknown TypeKind = iota
|
||||
Protocol
|
||||
Class
|
||||
String
|
||||
Data
|
||||
Int
|
||||
Uint
|
||||
Short
|
||||
Ushort
|
||||
Bool
|
||||
Char
|
||||
Uchar
|
||||
Float
|
||||
Double
|
||||
)
|
||||
|
||||
// Import returns descriptors for a list of references to
|
||||
// ObjC protocols and classes.
|
||||
//
|
||||
// The type information is parsed from the output of clang -cc1
|
||||
// -ast-dump.
|
||||
func Import(refs *importers.References) ([]*Named, error) {
|
||||
var modules []string
|
||||
modMap := make(map[string]struct{})
|
||||
typeNames := make(map[string][]string)
|
||||
typeSet := make(map[string]struct{})
|
||||
genMods := make(map[string]struct{})
|
||||
for _, emb := range refs.Embedders {
|
||||
genMods[initialUpper(emb.Pkg)] = struct{}{}
|
||||
}
|
||||
for _, ref := range refs.Refs {
|
||||
var module, name string
|
||||
if idx := strings.Index(ref.Pkg, "/"); idx != -1 {
|
||||
// ref is a static method reference.
|
||||
module = ref.Pkg[:idx]
|
||||
name = ref.Pkg[idx+1:]
|
||||
} else {
|
||||
// ref is a type name.
|
||||
module = ref.Pkg
|
||||
name = ref.Name
|
||||
}
|
||||
if _, exists := typeSet[name]; !exists {
|
||||
typeNames[module] = append(typeNames[module], name)
|
||||
typeSet[name] = struct{}{}
|
||||
}
|
||||
if _, exists := modMap[module]; !exists {
|
||||
// Include the module only if it is generated.
|
||||
if _, exists := genMods[module]; !exists {
|
||||
modMap[module] = struct{}{}
|
||||
modules = append(modules, module)
|
||||
}
|
||||
}
|
||||
}
|
||||
sdkPathOut, err := exec.Command("xcrun", "--sdk", "iphonesimulator", "--show-sdk-path").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sdkPath := strings.TrimSpace(string(sdkPathOut))
|
||||
var allTypes []*Named
|
||||
typeMap := make(map[string]*Named)
|
||||
for _, module := range modules {
|
||||
types, err := importModule(string(sdkPath), module, typeNames[module], typeMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", module, err)
|
||||
}
|
||||
allTypes = append(allTypes, types...)
|
||||
}
|
||||
// Embedders refer to every exported Go struct that will have its class
|
||||
// generated. Allow Go code to reverse bind to those classes by synthesizing
|
||||
// their descriptors.
|
||||
for _, emb := range refs.Embedders {
|
||||
module := initialUpper(emb.Pkg)
|
||||
named := &Named{
|
||||
Name: module + emb.Name,
|
||||
GoName: emb.Name,
|
||||
Module: module,
|
||||
Generated: true,
|
||||
}
|
||||
for _, ref := range emb.Refs {
|
||||
t, exists := typeMap[ref.Name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("type not found: %q", ref.Name)
|
||||
}
|
||||
named.Supers = append(named.Supers, Super{
|
||||
Name: t.Name,
|
||||
Protocol: t.Protocol,
|
||||
})
|
||||
}
|
||||
typeMap[emb.Name] = named
|
||||
allTypes = append(allTypes, named)
|
||||
}
|
||||
initTypes(allTypes, refs, typeMap)
|
||||
// Include implicit types that are used in parameter or return values.
|
||||
newTypes := allTypes
|
||||
for len(newTypes) > 0 {
|
||||
var impTypes []*Named
|
||||
for _, t := range newTypes {
|
||||
for _, funcs := range [][]*Func{t.Funcs, t.AllMethods} {
|
||||
for _, f := range funcs {
|
||||
types := implicitFuncTypes(f)
|
||||
for _, name := range types {
|
||||
if _, exists := typeSet[name]; exists {
|
||||
continue
|
||||
}
|
||||
typeSet[name] = struct{}{}
|
||||
t, exists := typeMap[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("implicit type %q not found", name)
|
||||
}
|
||||
impTypes = append(impTypes, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
initTypes(impTypes, refs, typeMap)
|
||||
allTypes = append(allTypes, impTypes...)
|
||||
newTypes = impTypes
|
||||
}
|
||||
return allTypes, nil
|
||||
}
|
||||
|
||||
func implicitFuncTypes(f *Func) []string {
|
||||
var types []string
|
||||
if rt := f.Ret; rt != nil && !rt.instanceType && (rt.Kind == Class || rt.Kind == Protocol) {
|
||||
types = append(types, rt.Name)
|
||||
}
|
||||
for _, p := range f.Params {
|
||||
if t := p.Type; !t.instanceType && (t.Kind == Class || t.Kind == Protocol) {
|
||||
types = append(types, t.Name)
|
||||
}
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func initTypes(types []*Named, refs *importers.References, typeMap map[string]*Named) {
|
||||
for _, t := range types {
|
||||
fillAllMethods(t, typeMap)
|
||||
}
|
||||
// Move constructors to functions. They are represented in Go
|
||||
// as functions.
|
||||
for _, t := range types {
|
||||
var methods []*Func
|
||||
for _, f := range t.AllMethods {
|
||||
if f.Constructor {
|
||||
f.Static = true
|
||||
t.Funcs = append(t.Funcs, f)
|
||||
} else {
|
||||
methods = append(methods, f)
|
||||
}
|
||||
}
|
||||
t.AllMethods = methods
|
||||
}
|
||||
for _, t := range types {
|
||||
mangleMethodNames(t.AllMethods)
|
||||
mangleMethodNames(t.Funcs)
|
||||
}
|
||||
filterReferences(types, refs, typeMap)
|
||||
for _, t := range types {
|
||||
resolveInstanceTypes(t, t.Funcs)
|
||||
resolveInstanceTypes(t, t.AllMethods)
|
||||
}
|
||||
}
|
||||
|
||||
func filterReferences(types []*Named, refs *importers.References, typeMap map[string]*Named) {
|
||||
refFuncs := make(map[[2]string]struct{})
|
||||
for _, ref := range refs.Refs {
|
||||
if sep := strings.Index(ref.Pkg, "/"); sep != -1 {
|
||||
pkgName := ref.Pkg[sep+1:]
|
||||
n := typeMap[pkgName]
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
refFuncs[[...]string{pkgName, ref.Name}] = struct{}{}
|
||||
}
|
||||
}
|
||||
for _, t := range types {
|
||||
var filtered []*Func
|
||||
for _, f := range t.Funcs {
|
||||
if _, exists := refFuncs[[...]string{t.GoName, f.GoName}]; exists {
|
||||
filtered = append(filtered, f)
|
||||
}
|
||||
}
|
||||
t.Funcs = filtered
|
||||
filtered = nil
|
||||
for _, m := range t.Methods {
|
||||
if _, exists := refs.Names[m.GoName]; exists {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
t.Methods = filtered
|
||||
filtered = nil
|
||||
for _, m := range t.AllMethods {
|
||||
if _, exists := refs.Names[m.GoName]; exists {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
t.AllMethods = filtered
|
||||
}
|
||||
}
|
||||
|
||||
// mangleMethodNames assigns unique Go names to ObjC methods. If a method name is unique
|
||||
// within the same method list, its name is used with its first letter in upper case.
|
||||
// Multiple methods with the same name have their full signature appended, with : removed.
|
||||
func mangleMethodNames(allFuncs []*Func) {
|
||||
goName := func(n string, constructor bool) string {
|
||||
if constructor {
|
||||
n = "new" + n[len("init"):]
|
||||
}
|
||||
return initialUpper(n)
|
||||
}
|
||||
overloads := make(map[string][]*Func)
|
||||
for i, f := range allFuncs {
|
||||
// Copy function so each class can have its own
|
||||
// name mangling.
|
||||
f := *f
|
||||
allFuncs[i] = &f
|
||||
f.GoName = goName(f.Sig, f.Constructor)
|
||||
if colon := strings.Index(f.GoName, ":"); colon != -1 {
|
||||
f.GoName = f.GoName[:colon]
|
||||
}
|
||||
overloads[f.GoName] = append(overloads[f.GoName], &f)
|
||||
}
|
||||
fallbacks := make(map[string][]*Func)
|
||||
for _, funcs := range overloads {
|
||||
if len(funcs) == 1 {
|
||||
continue
|
||||
}
|
||||
for _, f := range funcs {
|
||||
sig := f.Sig
|
||||
if strings.HasSuffix(sig, ":") {
|
||||
sig = sig[:len(sig)-1]
|
||||
}
|
||||
sigElems := strings.Split(f.Sig, ":")
|
||||
for i := 0; i < len(sigElems); i++ {
|
||||
sigElems[i] = initialUpper(sigElems[i])
|
||||
}
|
||||
name := strings.Join(sigElems, "")
|
||||
f.GoName = goName(name, f.Constructor)
|
||||
fallbacks[f.GoName] = append(fallbacks[f.GoName], f)
|
||||
}
|
||||
}
|
||||
for _, funcs := range fallbacks {
|
||||
if len(funcs) == 1 {
|
||||
continue
|
||||
}
|
||||
for _, f := range funcs {
|
||||
name := strings.Replace(f.Sig, ":", "_", -1)
|
||||
f.GoName = goName(name, f.Constructor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveInstanceType(n *Named, t *Type) *Type {
|
||||
if !t.instanceType || t.Kind != Protocol {
|
||||
return t
|
||||
}
|
||||
// Copy and update the type name for instancetype types
|
||||
ct := *t
|
||||
ct.instanceType = false
|
||||
ct.Decl = n.Name + " *"
|
||||
if n.Name == "NSString" {
|
||||
ct.Kind = String
|
||||
ct.Name = ""
|
||||
} else {
|
||||
ct.Kind = Class
|
||||
ct.Name = n.Name
|
||||
}
|
||||
return &ct
|
||||
}
|
||||
|
||||
func resolveInstanceTypes(n *Named, funcs []*Func) {
|
||||
for _, f := range funcs {
|
||||
for _, p := range f.Params {
|
||||
p.Type = resolveInstanceType(n, p.Type)
|
||||
}
|
||||
if f.Ret != nil {
|
||||
f.Ret = resolveInstanceType(n, f.Ret)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fillAllMethods(n *Named, typeMap map[string]*Named) {
|
||||
if len(n.AllMethods) > 0 {
|
||||
return
|
||||
}
|
||||
if len(n.Supers) == 0 {
|
||||
n.AllMethods = n.Methods
|
||||
return
|
||||
}
|
||||
for _, sup := range n.Supers {
|
||||
super := lookup(sup.Name, sup.Protocol, typeMap)
|
||||
fillAllMethods(super, typeMap)
|
||||
}
|
||||
methods := make(map[string]struct{})
|
||||
for _, sup := range n.Supers {
|
||||
super := lookup(sup.Name, sup.Protocol, typeMap)
|
||||
for _, f := range super.AllMethods {
|
||||
if _, exists := methods[f.Sig]; !exists {
|
||||
methods[f.Sig] = struct{}{}
|
||||
n.AllMethods = append(n.AllMethods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range n.Methods {
|
||||
if _, exists := methods[f.Sig]; !exists {
|
||||
n.AllMethods = append(n.AllMethods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
frameworksPath = "/System/Library/Frameworks/"
|
||||
)
|
||||
|
||||
// importModule parses ObjC type information with clang -cc1 -ast-dump.
|
||||
//
|
||||
// TODO: Use module.map files to precisely model the @import Module.Identifier
|
||||
// directive. For now, importModules assumes the single umbrella header
|
||||
// file Module.framework/Headers/Module.h contains every declaration.
|
||||
func importModule(sdkPath, module string, identifiers []string, typeMap map[string]*Named) ([]*Named, error) {
|
||||
hFile := fmt.Sprintf(sdkPath+frameworksPath+"%s.framework/Headers/%[1]s.h", module)
|
||||
clang := exec.Command("xcrun", "--sdk", "iphonesimulator", "clang", "-cc1", "-triple", "x86_64-apple-ios8.0.0-simulator", "-isysroot", sdkPath, "-ast-dump", "-fblocks", "-fobjc-arc", "-x", "objective-c", hFile)
|
||||
out, err := clang.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clang failed to parse module: %v: %s", err, out)
|
||||
}
|
||||
p := &parser{
|
||||
sdkPath: sdkPath,
|
||||
sc: bufio.NewScanner(bytes.NewBuffer(out)),
|
||||
}
|
||||
if err := p.parseModule(module, typeMap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var types []*Named
|
||||
for _, ident := range identifiers {
|
||||
named, exists := typeMap[ident]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("no such type: %s", ident)
|
||||
}
|
||||
types = append(types, named)
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
func (p *parser) scanLine() bool {
|
||||
for {
|
||||
l := p.last
|
||||
if l == "" {
|
||||
if !p.sc.Scan() {
|
||||
return false
|
||||
}
|
||||
l = p.sc.Text()
|
||||
} else {
|
||||
p.last = ""
|
||||
}
|
||||
indent := (strings.Index(l, "-") + 1) / 2
|
||||
switch {
|
||||
case indent > p.indent:
|
||||
// Skip
|
||||
case indent < p.indent:
|
||||
p.indent--
|
||||
p.last = l
|
||||
return false
|
||||
case indent == p.indent:
|
||||
p.decl = l[p.indent*2:]
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseModule(module string, typeMap map[string]*Named) (err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
err = rerr.(error)
|
||||
}
|
||||
}()
|
||||
if !p.scanLine() {
|
||||
return nil
|
||||
}
|
||||
// A header file AST starts with
|
||||
//
|
||||
// TranslationUnitDecl 0x103833ad0 <<invalid sloc>> <invalid sloc>
|
||||
if w := p.scanWord(); w != "TranslationUnitDecl" {
|
||||
return fmt.Errorf("unexpected AST root: %q", w)
|
||||
}
|
||||
p.indent++
|
||||
for {
|
||||
if !p.scanLine() {
|
||||
break
|
||||
}
|
||||
switch w := p.scanWord(); w {
|
||||
case "ObjCCategoryDecl":
|
||||
// ObjCCategoryDecl 0x103d9bdb8 <line:48:1, line:63:2> line:48:12 NSDateCreation
|
||||
// |-ObjCInterface 0x103d9a788 'NSDate'
|
||||
// Skip the node address, the source code range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
catName := p.scanWord()
|
||||
p.indent++
|
||||
if !p.scanLine() {
|
||||
return fmt.Errorf("no interface for category %s", catName)
|
||||
}
|
||||
if w := p.scanWord(); w != "ObjCInterface" {
|
||||
return fmt.Errorf("unexpected declaaration %s for category %s", w, catName)
|
||||
}
|
||||
p.scanWord()
|
||||
clsName := p.scanWord()
|
||||
clsName = clsName[1 : len(clsName)-1]
|
||||
named := lookup(clsName, false, typeMap)
|
||||
if named == nil {
|
||||
return fmt.Errorf("category %s references unknown class %s", catName, clsName)
|
||||
}
|
||||
p.parseInterface(named)
|
||||
case "ObjCInterfaceDecl", "ObjCProtocolDecl":
|
||||
// ObjCProtocolDecl 0x104116450 <line:15:1, line:47:2> line:15:11 NSObject
|
||||
// or
|
||||
// ObjCInterfaceDecl 0x1041ca480 <line:17:29, line:64:2> line:17:40 UIResponder
|
||||
|
||||
prot := w == "ObjCProtocolDecl"
|
||||
|
||||
// Skip the node address, the source code range, position.
|
||||
p.scanWord()
|
||||
if strings.HasPrefix(p.decl, "prev ") {
|
||||
p.scanWord()
|
||||
p.scanWord()
|
||||
}
|
||||
p.parseLocation()
|
||||
if strings.HasPrefix(p.decl, "implicit ") {
|
||||
p.scanWord()
|
||||
}
|
||||
name := p.decl
|
||||
named := p.lookupOrCreate(name, prot, typeMap)
|
||||
p.indent++
|
||||
p.parseInterface(named)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookup(name string, prot bool, typeMap map[string]*Named) *Named {
|
||||
var mangled string
|
||||
if prot {
|
||||
mangled = name + "P"
|
||||
} else {
|
||||
mangled = name + "C"
|
||||
}
|
||||
if n := typeMap[mangled]; n != nil {
|
||||
return n
|
||||
}
|
||||
return typeMap[name]
|
||||
}
|
||||
|
||||
// lookupOrCreate looks up the type name in the type map. If it doesn't exist, it creates
|
||||
// and returns a new type. If it does exist, it returns the existing type. If there are both
|
||||
// a class and a protocol with the same name, their type names are mangled by prefixing
|
||||
// 'C' or 'P' and then re-inserted into the type map.
|
||||
func (p *parser) lookupOrCreate(name string, prot bool, typeMap map[string]*Named) *Named {
|
||||
mangled := name + "C"
|
||||
otherMangled := name + "P"
|
||||
if prot {
|
||||
mangled, otherMangled = otherMangled, mangled
|
||||
}
|
||||
named, exists := typeMap[mangled]
|
||||
if exists {
|
||||
return named
|
||||
}
|
||||
named, exists = typeMap[name]
|
||||
if exists {
|
||||
if named.Protocol == prot {
|
||||
return named
|
||||
}
|
||||
// Both a class and a protocol exists with the same name.
|
||||
delete(typeMap, name)
|
||||
named.GoName = otherMangled
|
||||
typeMap[otherMangled] = named
|
||||
named = &Named{
|
||||
GoName: mangled,
|
||||
}
|
||||
} else {
|
||||
named = &Named{
|
||||
GoName: name,
|
||||
}
|
||||
}
|
||||
named.Name = name
|
||||
named.Protocol = prot
|
||||
named.funcMap = make(map[string]struct{})
|
||||
named.Module = p.module
|
||||
typeMap[named.GoName] = named
|
||||
return named
|
||||
}
|
||||
|
||||
func (p *parser) parseInterface(n *Named) {
|
||||
for {
|
||||
more := p.scanLine()
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
switch w := p.scanWord(); w {
|
||||
case "super":
|
||||
if w := p.scanWord(); w != "ObjCInterface" {
|
||||
panic(fmt.Errorf("unknown super type: %s", w))
|
||||
}
|
||||
// Skip node address.
|
||||
p.scanWord()
|
||||
super := p.scanWord()
|
||||
// Remove single quotes
|
||||
super = super[1 : len(super)-1]
|
||||
n.Supers = append(n.Supers, Super{super, false})
|
||||
case "ObjCProtocol":
|
||||
p.scanWord()
|
||||
super := p.scanWord()
|
||||
super = super[1 : len(super)-1]
|
||||
n.Supers = append(n.Supers, Super{super, true})
|
||||
case "ObjCMethodDecl":
|
||||
f := p.parseMethod()
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
var key string
|
||||
if f.Static {
|
||||
key = "+" + f.Sig
|
||||
} else {
|
||||
key = "-" + f.Sig
|
||||
}
|
||||
if _, exists := n.funcMap[key]; !exists {
|
||||
n.funcMap[key] = struct{}{}
|
||||
if f.Static {
|
||||
n.Funcs = append(n.Funcs, f)
|
||||
} else {
|
||||
n.Methods = append(n.Methods, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseMethod() *Func {
|
||||
// ObjCMethodDecl 0x103bdfb80 <line:17:1, col:27> col:1 - isEqual: 'BOOL':'_Bool'
|
||||
|
||||
// Skip the address, range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
if strings.HasPrefix(p.decl, "implicit") {
|
||||
p.scanWord()
|
||||
}
|
||||
f := new(Func)
|
||||
switch w := p.scanWord(); w {
|
||||
case "+":
|
||||
f.Static = true
|
||||
case "-":
|
||||
f.Static = false
|
||||
default:
|
||||
panic(fmt.Errorf("unknown method type for %q", w))
|
||||
}
|
||||
f.Sig = p.scanWord()
|
||||
if f.Sig == "dealloc" {
|
||||
// ARC forbids dealloc
|
||||
return nil
|
||||
}
|
||||
if strings.HasPrefix(f.Sig, "init") {
|
||||
f.Constructor = true
|
||||
}
|
||||
f.Ret = p.parseType()
|
||||
p.indent++
|
||||
for {
|
||||
more := p.scanLine()
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
switch p.scanWord() {
|
||||
case "UnavailableAttr":
|
||||
p.indent--
|
||||
return nil
|
||||
case "ParmVarDecl":
|
||||
f.Params = append(f.Params, p.parseParameter())
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (p *parser) parseParameter() *Param {
|
||||
// ParmVarDecl 0x1041caca8 <col:70, col:80> col:80 event 'UIEvent * _Nullable':'UIEvent *'
|
||||
|
||||
// Skip address, source range, position.
|
||||
p.scanWord()
|
||||
p.parseLocation()
|
||||
return &Param{Name: p.scanWord(), Type: p.parseType()}
|
||||
}
|
||||
|
||||
func (p *parser) parseType() *Type {
|
||||
// NSUInteger':'unsigned long'
|
||||
s := strings.SplitN(p.decl, ":", 2)
|
||||
decl := s[0]
|
||||
var canon string
|
||||
if len(s) == 2 {
|
||||
canon = s[1]
|
||||
} else {
|
||||
canon = decl
|
||||
}
|
||||
// unquote the type
|
||||
canon = canon[1 : len(canon)-1]
|
||||
if canon == "void" {
|
||||
return nil
|
||||
}
|
||||
decl = decl[1 : len(decl)-1]
|
||||
instancetype := strings.HasPrefix(decl, "instancetype")
|
||||
// Strip modifiers
|
||||
mods := []string{"__strong", "__unsafe_unretained", "const", "__strong", "_Nonnull", "_Nullable", "__autoreleasing"}
|
||||
for _, mod := range mods {
|
||||
if idx := strings.Index(canon, mod); idx != -1 {
|
||||
canon = canon[:idx] + canon[idx+len(mod):]
|
||||
}
|
||||
if idx := strings.Index(decl, mod); idx != -1 {
|
||||
decl = decl[:idx] + decl[idx+len(mod):]
|
||||
}
|
||||
}
|
||||
canon = strings.TrimSpace(canon)
|
||||
decl = strings.TrimSpace(decl)
|
||||
t := &Type{
|
||||
Decl: decl,
|
||||
instanceType: instancetype,
|
||||
}
|
||||
switch canon {
|
||||
case "int", "long", "long long":
|
||||
t.Kind = Int
|
||||
case "unsigned int", "unsigned long", "unsigned long long":
|
||||
t.Kind = Uint
|
||||
case "short":
|
||||
t.Kind = Short
|
||||
case "unsigned short":
|
||||
t.Kind = Ushort
|
||||
case "char":
|
||||
t.Kind = Char
|
||||
case "unsigned char":
|
||||
t.Kind = Uchar
|
||||
case "float":
|
||||
t.Kind = Float
|
||||
case "double":
|
||||
t.Kind = Double
|
||||
case "_Bool":
|
||||
t.Kind = Bool
|
||||
case "NSString *":
|
||||
t.Kind = String
|
||||
case "NSData *":
|
||||
t.Kind = Data
|
||||
default:
|
||||
switch {
|
||||
case strings.HasPrefix(canon, "enum"):
|
||||
t.Kind = Int
|
||||
case strings.HasPrefix(canon, "id"):
|
||||
_, gen := p.splitGeneric(canon)
|
||||
t.Kind = Protocol
|
||||
t.Name = gen
|
||||
default:
|
||||
if ind := strings.Count(canon, "*"); 1 <= ind && ind <= 2 {
|
||||
space := strings.Index(canon, " ")
|
||||
name := canon[:space]
|
||||
name, _ = p.splitGeneric(name)
|
||||
t.Kind = Class
|
||||
t.Name = name
|
||||
t.Indirect = ind > 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (p *parser) splitGeneric(decl string) (string, string) {
|
||||
// NSArray<KeyType>
|
||||
if br := strings.Index(decl, "<"); br != -1 {
|
||||
return decl[:br], decl[br+1 : len(decl)-1]
|
||||
} else {
|
||||
return decl, ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseSrcPos() {
|
||||
const invPref = "<invalid sloc>"
|
||||
if strings.HasPrefix(p.decl, invPref) {
|
||||
p.decl = p.decl[len(invPref):]
|
||||
return
|
||||
}
|
||||
var loc string
|
||||
const scrPref = "<scratch space>"
|
||||
if strings.HasPrefix(p.decl, scrPref) {
|
||||
// <scratch space>:130:1
|
||||
p.decl = p.decl[len(scrPref):]
|
||||
loc = "line" + p.scanWord()
|
||||
} else {
|
||||
// line:17:2, col:18 or, a file location:
|
||||
// /.../UIKit.framework/Headers/UISelectionFeedbackGenerator.h:16:1
|
||||
loc = p.scanWord()
|
||||
}
|
||||
locs := strings.SplitN(loc, ":", 2)
|
||||
if len(locs) != 2 && len(locs) != 3 {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
switch loc := locs[0]; loc {
|
||||
case "line", "col":
|
||||
default:
|
||||
if !strings.HasPrefix(loc, p.sdkPath) {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
loc = loc[len(p.sdkPath):]
|
||||
switch {
|
||||
case strings.HasPrefix(loc, "/usr/include/objc/"):
|
||||
p.module = "Foundation"
|
||||
case strings.HasPrefix(loc, frameworksPath):
|
||||
loc = loc[len(frameworksPath):]
|
||||
i := strings.Index(loc, ".framework")
|
||||
if i == -1 {
|
||||
panic(fmt.Errorf("invalid source position: %q", loc))
|
||||
}
|
||||
p.module = loc[:i]
|
||||
// Some types are declared in CoreFoundation.framework
|
||||
// even though they belong in Foundation in Objective-C.
|
||||
if p.module == "CoreFoundation" {
|
||||
p.module = "Foundation"
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseLocation() {
|
||||
// Source ranges are on the form: <line:17:29, line:64:2>.
|
||||
if !strings.HasPrefix(p.decl, "<") {
|
||||
panic(fmt.Errorf("1no source range first in %s", p.decl))
|
||||
}
|
||||
p.decl = p.decl[1:]
|
||||
p.parseSrcPos()
|
||||
if strings.HasPrefix(p.decl, ", ") {
|
||||
p.decl = p.decl[2:]
|
||||
p.parseSrcPos()
|
||||
}
|
||||
if !strings.HasPrefix(p.decl, "> ") {
|
||||
panic(fmt.Errorf("no source range first in %s", p.decl))
|
||||
}
|
||||
p.decl = p.decl[2:]
|
||||
p.parseSrcPos()
|
||||
}
|
||||
|
||||
func (p *parser) scanWord() string {
|
||||
i := 0
|
||||
loop:
|
||||
for ; i < len(p.decl); i++ {
|
||||
switch p.decl[i] {
|
||||
case ' ', '>', ',':
|
||||
break loop
|
||||
}
|
||||
}
|
||||
w := p.decl[:i]
|
||||
p.decl = p.decl[i:]
|
||||
for len(p.decl) > 0 && p.decl[0] == ' ' {
|
||||
p.decl = p.decl[1:]
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func initialUpper(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(unicode.ToUpper(r)) + s[n:]
|
||||
}
|
||||
|
||||
func (t *Named) ObjcType() string {
|
||||
if t.Protocol {
|
||||
return fmt.Sprintf("id<%s> _Nullable", t.Name)
|
||||
} else {
|
||||
return t.Name + " * _Nullable"
|
||||
}
|
||||
}
|
83
internal/importers/objc/objc_test.go
Normal file
83
internal/importers/objc/objc_test.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package objc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
)
|
||||
|
||||
func TestImport(t *testing.T) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skipf("can only parse objc types on darwin")
|
||||
}
|
||||
tests := []struct {
|
||||
ref importers.PkgRef
|
||||
name string
|
||||
methods []*Func
|
||||
}{
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "Foundation/NSObjectP", Name: "Hash"},
|
||||
name: "NSObject",
|
||||
methods: []*Func{
|
||||
&Func{Sig: "hash", GoName: "Hash", Ret: &Type{Kind: Uint, Decl: "NSUInteger"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
ref: importers.PkgRef{Pkg: "Foundation/NSString", Name: "StringWithContentsOfFileEncodingError"},
|
||||
name: "NSString",
|
||||
methods: []*Func{
|
||||
&Func{
|
||||
Sig: "stringWithContentsOfFile:encoding:error:",
|
||||
GoName: "StringWithContentsOfFileEncodingError",
|
||||
Params: []*Param{
|
||||
&Param{Name: "path", Type: &Type{Kind: String, Decl: "NSString *"}},
|
||||
&Param{Name: "enc", Type: &Type{Kind: Uint, Decl: "NSStringEncoding"}},
|
||||
&Param{Name: "error", Type: &Type{Kind: Class, Name: "NSError", Decl: "NSError * * _Nullable", Indirect: true}},
|
||||
},
|
||||
Ret: &Type{Kind: 3, Decl: "NSString *"},
|
||||
Static: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
refs := &importers.References{
|
||||
Refs: []importers.PkgRef{test.ref},
|
||||
Names: make(map[string]struct{}),
|
||||
}
|
||||
for _, m := range test.methods {
|
||||
refs.Names[m.GoName] = struct{}{}
|
||||
}
|
||||
types, err := Import(refs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(types) == 0 {
|
||||
t.Fatalf("got no types, expected at least 1")
|
||||
}
|
||||
n := types[0]
|
||||
if n.Name != test.name {
|
||||
t.Errorf("got class name %s, expected %s", n.Name, test.name)
|
||||
}
|
||||
loop:
|
||||
for _, exp := range test.methods {
|
||||
for _, got := range n.AllMethods {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
for _, got := range n.Funcs {
|
||||
if reflect.DeepEqual(exp, got) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
t.Errorf("failed to find method: %+v", exp)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue