Repository: mmcloughlin/avo
Pull request #58 (2019-01-22 06:28:12)

gotypes,build: add Implement
By using `Implement` you can provide a definition of a function, taking the signature from a stub in the package. One major benefit of this approach is it makes it easy to handle external types in the function signature.

Updates #53 #55

codecov-io: # [Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=h1) Report
> Merging [#58](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=desc) into [master](https://codecov.io/gh/mmcloughlin/avo/commit/4bc8de39bbb397b8b393404c6bd8242d6304c7a8?src=pr&el=desc) will **decrease** coverage by `0.01%`.
> The diff coverage is `52.38%`.

[![Impacted file tree graph](https://codecov.io/gh/mmcloughlin/avo/pull/58/graphs/tree.svg?width=650&token=DaOLKxzdQS&height=150&src=pr)](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #58      +/-   ##
==========================================
- Coverage   75.92%   75.91%   -0.02%     
==========================================
  Files          51       51              
  Lines       31100    31121      +21     
==========================================
+ Hits        23614    23625      +11     
- Misses       7381     7387       +6     
- Partials      105      109       +4
```

| Flag | Coverage Δ | |
|---|---|---|
| #integration | `6.05% <52.38%> (+0.03%)` | :arrow_up: |
| #unittests | `74% <0%> (-0.06%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [build/global.go](https://codecov.io/gh/mmcloughlin/avo/pull/58/diff?src=pr&el=tree#diff-YnVpbGQvZ2xvYmFsLmdv) | `78.57% <100%> (+0.52%)` | :arrow_up: |
| [build/context.go](https://codecov.io/gh/mmcloughlin/avo/pull/58/diff?src=pr&el=tree#diff-YnVpbGQvY29udGV4dC5nbw==) | `57.14% <45.45%> (-1.48%)` | :arrow_down: |
| [gotypes/signature.go](https://codecov.io/gh/mmcloughlin/avo/pull/58/diff?src=pr&el=tree#diff-Z290eXBlcy9zaWduYXR1cmUuZ28=) | `90.36% <55.55%> (-4.24%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=footer). Last update [4bc8de3...a3b1d51](https://codecov.io/gh/mmcloughlin/avo/pull/58?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/README.md b/README.md
index bdc962a..073eb27 100644
--- a/README.md
+++ b/README.md
@@ -171,8 +171,9 @@ For demonstrations of `avo` features:
 
 * **[args](examples/args):** Loading function arguments.
 * **[returns](examples/returns):** Building return values.
-* **[data](examples/data):** Defining `DATA` sections.
 * **[complex](examples/complex):** Working with `complex{64,128}` types.
+* **[data](examples/data):** Defining `DATA` sections.
+* **[ext](examples/ext):** Interacting with types from external packages.
 
 ### Real Examples
 
diff --git a/build/context.go b/build/context.go
index 7e14627..21fd6d0 100644
--- a/build/context.go
+++ b/build/context.go
@@ -111,6 +111,23 @@ func (c *Context) SignatureExpr(expr string) {
 	c.Signature(s)
 }
 
+// Implement starts building a function of the given name, whose type is
+// specified by a stub in the containing package.
+func (c *Context) Implement(name string) {
+	pkg := c.types()
+	if pkg == nil {
+		c.adderrormessage("no package specified")
+		return
+	}
+	s, err := gotypes.LookupSignature(pkg, name)
+	if err != nil {
+		c.adderror(err)
+		return
+	}
+	c.Function(name)
+	c.Signature(s)
+}
+
 func (c *Context) types() *types.Package {
 	if c.pkg == nil {
 		return nil
diff --git a/build/global.go b/build/global.go
index b50235d..d2d49c3 100644
--- a/build/global.go
+++ b/build/global.go
@@ -118,6 +118,10 @@ func Doc(lines ...string) { ctx.Doc(lines...) }
 // Attributes sets function attributes for the currently active function.
 func Attributes(a attr.Attribute) { ctx.Attributes(a) }
 
+// Implement starts building a function of the given name, whose type is
+// specified by a stub in the containing package.
+func Implement(name string) { ctx.Implement(name) }
+
 // AllocLocal allocates size bytes in the stack of the currently active function.
 // Returns a reference to the base pointer for the newly allocated region.
 func AllocLocal(size int) operand.Mem { return ctx.AllocLocal(size) }
diff --git a/examples/README.md b/examples/README.md
index 5b9e842..d0e2c17 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -11,6 +11,8 @@ Features:
 * **[returns](returns):** Building return values.
 * **[complex](complex):** Working with `complex{64,128}` types.
 * **[data](data):** Defining `DATA` sections.
+* **[ext](ext):** Interacting with types from external packages.
+
 
 "Real" examples:
 
diff --git a/examples/ext/README.md b/examples/ext/README.md
new file mode 100644
index 0000000..a56c184
--- /dev/null
+++ b/examples/ext/README.md
@@ -0,0 +1,33 @@
+# ext
+
+Demonstrates how to use external types in an `avo` function signature.
+
+In this case, you will need to write the function stub yourself.
+
+[embedmd]:# (stub.go /package/ $)
+```go
+package ext
+
+import "github.com/mmcloughlin/avo/examples/ext/ext"
+
+// StructFieldB returns field B.
+func StructFieldB(e ext.Struct) byte
+```
+
+Then in place of the usual `TEXT` declaration we use `Implement` to state that we are beginning the definition of a function already declared in the package stub file.
+
+[embedmd]:# (asm.go go /.*Package.*/ /RET.*/)
+```go
+	Package("github.com/mmcloughlin/avo/examples/ext")
+	Implement("StructFieldB")
+	b := Load(Param("e").Field("B"), GP8())
+	Store(b, ReturnIndex(0))
+	RET()
+```
+
+Finally, in this case the `go:generate` line is different since we do not need to generate the stub file.
+
+[embedmd]:# (ext_test.go go /.*go:generate.*/)
+```go
+//go:generate go run asm.go -out ext.s
+```
diff --git a/examples/ext/asm.go b/examples/ext/asm.go
new file mode 100644
index 0000000..bfa8079
--- /dev/null
+++ b/examples/ext/asm.go
@@ -0,0 +1,16 @@
+// +build ignore
+
+package main
+
+import (
+	. "github.com/mmcloughlin/avo/build"
+)
+
+func main() {
+	Package("github.com/mmcloughlin/avo/examples/ext")
+	Implement("StructFieldB")
+	b := Load(Param("e").Field("B"), GP8())
+	Store(b, ReturnIndex(0))
+	RET()
+	Generate()
+}
diff --git a/examples/ext/ext.s b/examples/ext/ext.s
new file mode 100644
index 0000000..b43e77f
--- /dev/null
+++ b/examples/ext/ext.s
@@ -0,0 +1,7 @@
+// Code generated by command: go run asm.go -out ext.s. DO NOT EDIT.
+
+// func StructFieldB(e ext.Struct) byte
+TEXT ·StructFieldB(SB), $0-25
+	MOVB e_B+6(FP), AL
+	MOVB AL, ret+24(FP)
+	RET
diff --git a/examples/ext/ext/ext.go b/examples/ext/ext/ext.go
new file mode 100644
index 0000000..88732a5
--- /dev/null
+++ b/examples/ext/ext/ext.go
@@ -0,0 +1,9 @@
+// Package ext is used as a target package in the external types example.
+package ext
+
+// Struct is the target type in the external types example.
+type Struct struct {
+	A [3]uint16
+	B byte
+	C string
+}
diff --git a/examples/ext/stub.go b/examples/ext/stub.go
new file mode 100644
index 0000000..37630d3
--- /dev/null
+++ b/examples/ext/stub.go
@@ -0,0 +1,7 @@
+// Package ext demonstrates how to interact with external types.
+package ext
+
+import "github.com/mmcloughlin/avo/examples/ext/ext"
+
+// StructFieldB returns field B.
+func StructFieldB(e ext.Struct) byte
diff --git a/gotypes/signature.go b/gotypes/signature.go
index 44a4606..5fed65e 100644
--- a/gotypes/signature.go
+++ b/gotypes/signature.go
@@ -3,6 +3,7 @@ package gotypes
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"go/token"
 	"go/types"
 	"strconv"
@@ -31,6 +32,20 @@ func NewSignatureVoid() *Signature {
 	return NewSignature(nil, types.NewSignature(nil, nil, nil, false))
 }
 
+// LookupSignature returns the signature of the named function in the provided package.
+func LookupSignature(pkg *types.Package, name string) (*Signature, error) {
+	scope := pkg.Scope()
+	obj := scope.Lookup(name)
+	if obj == nil {
+		return nil, fmt.Errorf("could not find function \"%s\"", name)
+	}
+	s, ok := obj.Type().(*types.Signature)
+	if !ok {
+		return nil, fmt.Errorf("object \"%s\" does not have signature type", name)
+	}
+	return NewSignature(pkg, s), nil
+}
+
 // ParseSignature builds a Signature by parsing a Go function type expression.
 // The function type must reference builtin types only; see
 // ParseSignatureInPackage if custom types are required.
@@ -67,7 +82,12 @@ func (s *Signature) Bytes() int { return s.Params().Bytes() + s.Results().Bytes(
 // String writes Signature as a string. This does not include the "func" keyword.
 func (s *Signature) String() string {
 	var buf bytes.Buffer
-	types.WriteSignature(&buf, s.sig, types.RelativeTo(s.pkg))
+	types.WriteSignature(&buf, s.sig, func(pkg *types.Package) string {
+		if pkg == s.pkg {
+			return ""
+		}
+		return pkg.Name()
+	})
 	return buf.String()
 }
 


Test patch:
diff --git a/examples/ext/ext_test.go b/examples/ext/ext_test.go
new file mode 100644
index 0000000..1d38287
--- /dev/null
+++ b/examples/ext/ext_test.go
@@ -0,0 +1,17 @@
+package ext
+
+import (
+	"testing"
+	"testing/quick"
+
+	"github.com/mmcloughlin/avo/examples/ext/ext"
+)
+
+//go:generate go run asm.go -out ext.s
+
+func TestFunc(t *testing.T) {
+	expect := func(e ext.Struct) byte { return e.B }
+	if err := quick.CheckEqual(StructFieldB, expect, nil); err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/gotypes/signature_test.go b/gotypes/signature_test.go
index aa40051..5c8e655 100644
--- a/gotypes/signature_test.go
+++ b/gotypes/signature_test.go
@@ -5,8 +5,64 @@ import (
 	"go/types"
 	"strings"
 	"testing"
+
+	"golang.org/x/tools/go/packages"
 )
 
+func TestLookupSignature(t *testing.T) {
+	pkg := LoadPackageTypes(t, "math")
+	s, err := LookupSignature(pkg, "Frexp")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expect, err := ParseSignature("func(f float64) (frac float64, exp int)")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if s.String() != expect.String() {
+		t.Errorf("\n   got: %s\nexpect: %s\n", s, expect)
+	}
+}
+
+func TestLookupSignatureErrors(t *testing.T) {
+	cases := []struct {
+		PackagePath   string
+		FunctionName  string
+		ExpectedError string
+	}{
+		{"runtime", "HmmIdk", "could not find function \"HmmIdk\""},
+		{"crypto", "Decrypter", "object \"Decrypter\" does not have signature type"},
+		{"encoding/base64", "StdEncoding", "object \"StdEncoding\" does not have signature type"},
+	}
+	for _, c := range cases {
+		pkg := LoadPackageTypes(t, c.PackagePath)
+		_, err := LookupSignature(pkg, c.FunctionName)
+		if err == nil {
+			t.Fatalf("expected error looking up '%s' in package '%s'", c.FunctionName, c.PackagePath)
+		}
+		if err.Error() != c.ExpectedError {
+			t.Fatalf("wrong error message\n   got: %q\nexpect: %q", err.Error(), c.ExpectedError)
+		}
+	}
+}
+
+func LoadPackageTypes(t *testing.T, path string) *types.Package {
+	t.Helper()
+	cfg := &packages.Config{
+		Mode: packages.LoadTypes,
+	}
+	pkgs, err := packages.Load(cfg, path)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(pkgs) != 1 {
+		t.Fatal("expected to load exactly one package")
+	}
+	return pkgs[0].Types
+}
+
 func TestParseSignature(t *testing.T) {
 	cases := []struct {
 		Expr         string