Skip to content

Commit

Permalink
Merge pull request #11 from moznion/generics
Browse files Browse the repository at this point in the history
Support the type parameters for go's generics at func and struct declarations
  • Loading branch information
moznion authored Mar 30, 2022
2 parents 07420ca + 524d5e6 commit 417d6b7
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 37 deletions.
12 changes: 0 additions & 12 deletions .circleci/config.yml

This file was deleted.

27 changes: 27 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
on:
push:

jobs:
test:
name: Check
strategy:
matrix:
go-version: [1.18.x]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: check out
uses: actions/checkout@v2
- name: install tools
run: "go install golang.org/x/tools/cmd/goimports@latest && go install honnef.co/go/tools/cmd/staticcheck@latest && go install github.com/moznion/go-errgen/cmd/errgen@latest"
- name: check
run: make check
- name: upload coverage
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

*.out
cover.html
coverage.txt

/vendor/
/Godeps/
Expand Down
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@
PKGS := $(shell go list ./... | grep -v go-errgen)

check: test lint vet fmt-check
ci-check: ci-test lint vet fmt-check

test: errgen
go test -v -cover $(PKGS)

ci-test: errgen
go test -v -cover -race -coverprofile=coverage.txt -covermode=atomic $(PKGS)

test-coverage: errgen
go test -v -cover -coverprofile cover.out $(PKGS)
go tool cover -html=cover.out -o cover.html

lint:
golint -set_exit_status $(PKGS)
staticcheck ./...

vet:
go vet $(PKGS)
Expand Down
2 changes: 1 addition & 1 deletion generator/else.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (e *Else) Statements(statements ...Statement) *Else {

// Generate generates `else` block as golang code.
func (e *Else) Generate(indentLevel int) (string, error) {
stmt := fmt.Sprintf(" else {\n")
stmt := " else {\n"

indent := BuildIndent(indentLevel)
nextIndentLevel := indentLevel + 1
Expand Down
1 change: 1 addition & 0 deletions generator/func_invocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

// FuncInvocation represents a code generator for func invocation.
// FIXME add function name and type-parameters, this would be a breaking change.
type FuncInvocation struct {
parameters []string
callers []string
Expand Down
28 changes: 27 additions & 1 deletion generator/func_signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type FuncSignature struct {
paramCallers []string
funcNameCaller string
returnTypesCallers []string
typeParameters TypeParameters
}

// NewFuncParameter returns a new `FuncSignature`.
Expand Down Expand Up @@ -81,6 +82,7 @@ func (f *FuncSignature) AddParameters(funcParameters ...*FuncParameter) *FuncSig
paramCallers: append(f.paramCallers, fetchClientCallerLineAsSlice(len(funcParameters))...),
funcNameCaller: f.funcNameCaller,
returnTypesCallers: f.returnTypesCallers,
typeParameters: f.typeParameters,
}
}

Expand All @@ -94,6 +96,7 @@ func (f *FuncSignature) Parameters(funcParameters ...*FuncParameter) *FuncSignat
paramCallers: fetchClientCallerLineAsSlice(len(funcParameters)),
funcNameCaller: f.funcNameCaller,
returnTypesCallers: f.returnTypesCallers,
typeParameters: f.typeParameters,
}
}

Expand Down Expand Up @@ -121,6 +124,7 @@ func (f *FuncSignature) AddReturnTypeStatements(returnTypes ...*FuncReturnType)
paramCallers: f.paramCallers,
funcNameCaller: f.funcNameCaller,
returnTypesCallers: append(f.returnTypesCallers, fetchClientCallerLineAsSlice(len(returnTypes))...),
typeParameters: f.typeParameters,
}
}

Expand Down Expand Up @@ -148,6 +152,20 @@ func (f *FuncSignature) ReturnTypeStatements(returnTypes ...*FuncReturnType) *Fu
paramCallers: f.paramCallers,
funcNameCaller: f.funcNameCaller,
returnTypesCallers: fetchClientCallerLineAsSlice(len(returnTypes)),
typeParameters: f.typeParameters,
}
}

// TypeParameters sets the TypeParameters onto the caller FuncSignature.
func (f *FuncSignature) TypeParameters(typeParameters TypeParameters) *FuncSignature {
return &FuncSignature{
funcName: f.funcName,
funcParameters: f.funcParameters,
returnTypes: f.returnTypes,
paramCallers: f.paramCallers,
funcNameCaller: f.funcNameCaller,
returnTypesCallers: f.returnTypesCallers,
typeParameters: typeParameters,
}
}

Expand All @@ -159,7 +177,15 @@ func (f *FuncSignature) Generate(indentLevel int) (string, error) {

stmt := f.funcName

typeBoundaries := []int{}
if f.typeParameters != nil && len(f.typeParameters) > 0 {
typeParametersStmt, err := f.typeParameters.Generate(indentLevel)
if err != nil {
return "", err
}
stmt += typeParametersStmt
}

var typeBoundaries []int
typeExisted := true
typeMissingCaller := ""
for i, param := range f.funcParameters {
Expand Down
8 changes: 5 additions & 3 deletions generator/func_signature_doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import (
func ExampleFuncSignature_Generate() {
generator := NewFuncSignature(
"myFunc",
).AddParameters(
NewFuncParameter("foo", "string"),
).TypeParameters([]*TypeParameter{
NewTypeParameter("T", "string"),
}).AddParameters(
NewFuncParameter("foo", "T"),
NewFuncParameter("bar", "int"),
).AddReturnTypes("string", "error")
).AddReturnTypes("T", "error")

generated, err := generator.Generate(0)
if err != nil {
Expand Down
22 changes: 22 additions & 0 deletions generator/func_signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ func TestShouldGeneratingFuncSignatureBeSuccessful(t *testing.T) {
).AddReturnTypes("string", "error").
Parameters(NewFuncParameter("buz", "error")).
ReturnTypes("int64"),

"myFunc[T string](\n\tfoo T,\n\tbar int,\n) (T, error)": NewFuncSignature(
"myFunc",
).TypeParameters([]*TypeParameter{
NewTypeParameter("T", "string"),
}).AddParameters(
NewFuncParameter("foo", "T"),
NewFuncParameter("bar", "int"),
).AddReturnTypes("T", "error"),
}

for expected, signature := range dataset {
Expand Down Expand Up @@ -198,3 +207,16 @@ func TestShouldGeneratingFuncSignatureRaisesUnnamedRetTypeIsAfterNamedRetType(t
`^\`+strings.Split(errmsg.UnnamedReturnTypeAppearsAfterNamedReturnTypeError("").Error(), " ")[0],
), err.Error())
}

func TestShouldGenerateFuncSignatureRaiseErrorWhenInvalidTypeParameterHasGiven(t *testing.T) {
_, err := NewFuncSignature(
"myFunc",
).TypeParameters([]*TypeParameter{
NewTypeParameter("T", ""),
}).AddParameters(
NewFuncParameter("foo", "T"),
).AddReturnTypes("T", "error").Generate(0)

assert.Error(t, err)
assert.Equal(t, errmsg.TypeParameterTypeIsEmptyErrType, errmsg.IdentifyErrs(err))
}
37 changes: 30 additions & 7 deletions generator/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ type StructField struct {

// Struct represents a code generator for `struct` notation.
type Struct struct {
name string
fields []*StructField
nameCaller string
fieldsCallers []string
name string
fields []*StructField
nameCaller string
fieldsCallers []string
typeParameters TypeParameters
}

// NewStruct returns a new `Struct`.
Expand All @@ -45,8 +46,20 @@ func (sg *Struct) AddField(name string, typ string, tag ...string) *Struct {
typ: typ,
tag: t,
}),
nameCaller: sg.nameCaller,
fieldsCallers: append(sg.fieldsCallers, fetchClientCallerLine()),
nameCaller: sg.nameCaller,
fieldsCallers: append(sg.fieldsCallers, fetchClientCallerLine()),
typeParameters: sg.typeParameters,
}
}

// TypeParameters sets the TypeParameters onto the current caller Struct.
func (sg *Struct) TypeParameters(typeParameters TypeParameters) *Struct {
return &Struct{
name: sg.name,
fields: sg.fields,
nameCaller: sg.nameCaller,
fieldsCallers: sg.fieldsCallers,
typeParameters: typeParameters,
}
}

Expand All @@ -57,7 +70,17 @@ func (sg *Struct) Generate(indentLevel int) (string, error) {
}

indent := BuildIndent(indentLevel)
stmt := fmt.Sprintf("%stype %s struct {\n", indent, sg.name)

typeParametersStmt := ""
if sg.typeParameters != nil && len(sg.typeParameters) > 0 {
var err error
typeParametersStmt, err = sg.typeParameters.Generate(indentLevel)
if err != nil {
return "", err
}
}

stmt := fmt.Sprintf("%stype %s%s struct {\n", indent, sg.name, typeParametersStmt)

for i, field := range sg.fields {
if field.name == "" {
Expand Down
3 changes: 2 additions & 1 deletion generator/struct_doc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
func ExampleStruct_Generate() {
generator := NewStruct("MyStruct")
generator = generator.
AddField("foo", "string").
TypeParameters(TypeParameters{NewTypeParameter("T", "string")}).
AddField("foo", "T").
AddField("bar", "int64", `custom:"tag"`)

generated, err := generator.Generate(0)
Expand Down
26 changes: 26 additions & 0 deletions generator/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ func TestShouldGenerateStructStatementBeSucceeded(t *testing.T) {
}
}

func TestShouldGenerateStructStatementWithTypeParametersSuccessfully(t *testing.T) {
generator := NewStruct("TestStruct")
generator = generator.
TypeParameters(TypeParameters{NewTypeParameter("T", "string")}).
AddField("Foo", "T").
AddField("Bar", "int64", `json:"bar"`)

gen, err := generator.Generate(0)
expected := "type TestStruct[T string] struct {\n" +
" Foo T\n" +
" Bar int64 `json:\"bar\"`\n" +
"}\n"
assert.NoError(t, err)
assert.Equal(t, expected, gen)
}

func TestShouldRaiseErrorWhenStructNameIsEmpty(t *testing.T) {
structGenerator := NewStruct("")

Expand All @@ -65,3 +81,13 @@ func TestShouldRaiseErrorWhenFieldTypeIsEmpty(t *testing.T) {
`^\`+strings.Split(errmsg.StructFieldTypeIsEmptyErr("").Error(), " ")[0],
), err.Error())
}

func TestShouldRaiseErrorWhenInvalidTypeParameterHasGiven(t *testing.T) {
generator := NewStruct("TestStruct")
generator = generator.
TypeParameters(TypeParameters{NewTypeParameter("", "string")}).
AddField("Foo", "T")
_, err := generator.Generate(0)
assert.Error(t, err)
assert.Equal(t, errmsg.TypeParameterParameterIsEmptyErrType, errmsg.IdentifyErrs(err))
}
52 changes: 52 additions & 0 deletions generator/type_parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package generator

import (
"strings"

"github.com/moznion/gowrtr/internal/errmsg"
)

// TypeParameter is a "generic" type parameter. e,g. `T string`; T is a parameter and int is type.
type TypeParameter struct {
parameter string
typ string
caller string
}

// NewTypeParameter returns a new TypeParameter.
func NewTypeParameter(parameter string, typ string) *TypeParameter {
return &TypeParameter{
parameter: parameter,
typ: typ,
caller: fetchClientCallerLine(),
}
}

// TypeParameters is an array of TypeParameter pointers for go generics.
type TypeParameters []*TypeParameter

// Generate generates the type-parameters as golang code.
func (tps TypeParameters) Generate(indentLevel int) (string, error) {
typeParameterStmts := make([]string, len(tps))
for i, tp := range tps {
if tp.parameter == "" {
return "", errmsg.TypeParameterParameterIsEmptyErr(tp.caller)
}

if tp.typ == "" {
return "", errmsg.TypeParameterTypeIsEmptyErr(tp.caller)
}

typeParameterStmts[i] = tp.parameter + " " + tp.typ
}

return "[" + strings.Join(typeParameterStmts, ", ") + "]", nil
}

// TypeArguments is an array of type argument for go generics.
type TypeArguments []string

// Generate generates the type-arguments as golang code.
func (tas TypeArguments) Generate(indentLevel int) (string, error) {
return "[" + strings.Join(tas, ", ") + "]", nil
}
15 changes: 15 additions & 0 deletions generator/type_parameters_doc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package generator

import (
"fmt"
"log"
)

func ExampleTypeArguments_Generate() {
generated, err := TypeArguments{"int", "string"}.Generate(0)
if err != nil {
log.Fatal(err)
}
fmt.Println("// example: f(T, U)")
fmt.Printf("f%s(intVar, strVar)\n", generated)
}
Loading

0 comments on commit 417d6b7

Please sign in to comment.