Skip to content

Commit

Permalink
Make the tests pass
Browse files Browse the repository at this point in the history
  • Loading branch information
VojtechVitek committed Apr 2, 2024
1 parent 1efd9b0 commit 610a74e
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 54 deletions.
11 changes: 9 additions & 2 deletions internal/parser/go_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ func (p *Parser) GoTypeName(typ types.Type) string {

name = strings.TrimPrefix(name, p.SchemaPkgName+".") // Typ (ignore root Pkg)
name = strings.TrimPrefix(name, "command-line-arguments.") // Typ (ignore "command-line-arguments" Pkg autogenerated by Go tool chain)
name = filepath.Base(name) // Pkg.Typ
name = filepath.Base(name) // pkg.Typ

if name == "invalid type" {
name = "invalidType"
}

return prefix + name // []*Pkg.Typ
return prefix + name // []*pkg.Typ
}

func (p *Parser) GoTypeImport(typ types.Type) string {
Expand All @@ -58,6 +58,13 @@ func (p *Parser) GoTypeImport(typ types.Type) string {
return name
}

func (p *Parser) GoTypeNameToWebrpc(typ string) string {
typ = strings.Trim(typ, "[]*.")
typ = filepath.Base(typ)
before, after, _ := strings.Cut(typ, ".")
return before + after
}

func findFirstLetter(s string) int {
for i, char := range s {
if unicode.IsLetter(char) {
Expand Down
27 changes: 11 additions & 16 deletions internal/parser/named.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/webrpc/webrpc/schema"
)

func (p *Parser) ParseNamedType(typeName string, typ types.Type) (varType *schema.VarType, err error) {
func (p *Parser) ParseNamedType(goTypeName string, typ types.Type) (varType *schema.VarType, err error) {
// On cache HIT, return a pointer to parsedType from cache.
if parsedType, ok := p.ParsedTypes[typ]; ok {
return parsedType, nil
Expand All @@ -19,7 +19,7 @@ func (p *Parser) ParseNamedType(typeName string, typ types.Type) (varType *schem
//
// Note: Since we're parsing the AST sequentially, we don't need to use mutex/sync.Map or anything.
cacheDoNotReturn := &schema.VarType{
Expr: typeName,
Expr: goTypeName,
}
p.ParsedTypes[typ] = cacheDoNotReturn

Expand All @@ -34,10 +34,10 @@ func (p *Parser) ParseNamedType(typeName string, typ types.Type) (varType *schem
case *types.Named:
pkg := v.Obj().Pkg()
underlying := v.Underlying()
typeName := p.GoTypeName(typ)
goTypeName := p.GoTypeName(typ)

if pkg != nil {
if typeName == "time.Time" {
if goTypeName == "time.Time" {
return &schema.VarType{
Expr: "timestamp",
Type: schema.T_Timestamp,
Expand Down Expand Up @@ -94,11 +94,6 @@ func (p *Parser) ParseNamedType(typeName string, typ types.Type) (varType *schem
}

var elem types.Type
// NOTE: As of Go 1.21, the following assignment
// var elem types.Type = u.Elem().Underlying()
// fails with syntax error:
// "u.Elem undefined (type types.Type has no field or method Elem)"
// even though both *types.Slice and *types.Array have the .Elem() method.
switch underlyingElem := u.(type) {
case *types.Slice:
elem = underlyingElem.Elem().Underlying()
Expand Down Expand Up @@ -133,29 +128,29 @@ func (p *Parser) ParseNamedType(typeName string, typ types.Type) (varType *schem
}, nil
}

return p.ParseNamedType(typeName, underlying)
return p.ParseNamedType(goTypeName, underlying)
}

case *types.Basic:
return p.ParseBasic(v)

case *types.Struct:
return p.ParseStruct(typeName, v)
return p.ParseStruct(goTypeName, v)

case *types.Slice:
return p.ParseSlice(typeName, v)
return p.ParseSlice(goTypeName, v)

case *types.Interface:
return p.ParseAny(typeName, v)
return p.ParseAny(goTypeName, v)

case *types.Map:
return p.ParseMap(typeName, v)
return p.ParseMap(goTypeName, v)

case *types.Pointer:
if typeName == "" {
if goTypeName == "" {
return p.ParseNamedType(p.GoTypeName(v), v.Elem())
}
return p.ParseNamedType(typeName, v.Elem())
return p.ParseNamedType(goTypeName, v.Elem())

default:
return nil, fmt.Errorf("unsupported argument type %T", typ)
Expand Down
30 changes: 23 additions & 7 deletions internal/parser/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"github.com/webrpc/webrpc/schema"
)

func (p *Parser) ParseStruct(typeName string, structTyp *types.Struct) (*schema.VarType, error) {
func (p *Parser) ParseStruct(goTypeName string, structTyp *types.Struct) (*schema.VarType, error) {
webrpcTypeName := p.GoTypeNameToWebrpc(goTypeName)

structType := &schema.Type{
Kind: "struct",
Name: typeName,
Name: webrpcTypeName,
}

for i := 0; i < structTyp.NumFields(); i++ {
Expand Down Expand Up @@ -39,7 +41,7 @@ func (p *Parser) ParseStruct(typeName string, structTyp *types.Struct) (*schema.
continue
}

field, err := p.parseStructField(typeName+"Field", structField, jsonTag)
field, err := p.parseStructField(goTypeName+"Field", structField, jsonTag)
if err != nil {
return nil, fmt.Errorf("parsing struct field %v: %w", i, err)
}
Expand All @@ -51,10 +53,10 @@ func (p *Parser) ParseStruct(typeName string, structTyp *types.Struct) (*schema.
p.Schema.Types = append(p.Schema.Types, structType)

return &schema.VarType{
Expr: typeName,
Expr: webrpcTypeName,
Type: schema.T_Struct,
Struct: &schema.VarStructType{
Name: typeName,
Name: webrpcTypeName,
Type: structType,
},
}, nil
Expand Down Expand Up @@ -111,12 +113,12 @@ func (p *Parser) parseStructField(structTypeName string, field *types.Var, jsonT
return structField, nil
}

if _, ok := field.Type().Underlying().(*types.Pointer); ok {
if _, ok := fieldType.Underlying().(*types.Pointer); ok {
optional = true
goFieldType = "*" + goFieldType
}

if _, ok := field.Type().Underlying().(*types.Struct); ok {
if _, ok := fieldType.Underlying().(*types.Struct); ok {
// Anonymous struct fields.
// Example:
// type Something struct {
Expand All @@ -127,6 +129,20 @@ func (p *Parser) parseStructField(structTypeName string, field *types.Var, jsonT
structTypeName = /*structTypeName + */ "Anonymous" + field.Name()
}

// TODO: Can we ever see type aliases here? If so, how do you trigger this?
if named, ok := fieldType.(*types.Named); ok {
if named.Obj().IsAlias() {
panic(fmt.Sprintf("alias: %v", fieldType))
}
}

// TODO: Can we ever see type aliases here? If so, how do you trigger this?
if named, ok := fieldType.Underlying().(*types.Named); ok {
if named.Obj().IsAlias() {
panic(fmt.Sprintf("alias: %v", fieldType))
}
}

varType, err := p.ParseNamedType(goFieldType, fieldType)
if err != nil {
return nil, fmt.Errorf("failed to parse var %v: %w", field.Name(), err)
Expand Down
24 changes: 14 additions & 10 deletions internal/parser/test/struct_fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,15 @@ func TestStructFieldJsonTags(t *testing.T) {
},
},
{
in: "Struct empty.Struct",
in: "Empty empty.Struct",
out: &field{
name: "Struct",
expr: "Struct",
name: "Empty",
expr: "emptyStruct",
t: schema.T_Struct,
goName: "Struct",
goName: "Empty",
goType: "empty.Struct",
goImport: "github.com/golang-cz/gospeak/internal/parser/test/empty",
Struct: &schema.VarStructType{Name: "Struct", Type: &schema.Type{Kind: "struct", Name: "Struct"}},
Struct: &schema.VarStructType{Name: "emptyStruct", Type: &schema.Type{Kind: "struct", Name: "emptyStruct"}},
},
},
//{
Expand All @@ -125,7 +125,7 @@ func TestStructFieldJsonTags(t *testing.T) {
var fields []*schema.TypeField
if tc.out != nil {
fields = []*schema.TypeField{
&schema.TypeField{
{
Name: tc.out.name,
Type: &schema.VarType{
Expr: tc.out.expr,
Expand Down Expand Up @@ -155,10 +155,12 @@ func TestStructFieldJsonTags(t *testing.T) {
Fields: fields,
}

got := parseTestStructCode(t, tc.in)
srcCode := genCodeWithStructField("TestStruct", tc.in)
got := parseTestStructCode(t, srcCode)

if !cmp.Equal(want, got) {
t.Errorf("%s\n%s\n", tc.in, coloredDiff(want, got))
t.Log(srcCode)
t.Errorf("%s\n%s", tc.in, coloredDiff(want, got))
}
}
}
Expand Down Expand Up @@ -191,7 +193,7 @@ func TestStructSliceField(t *testing.T) {
Kind: "struct",
Name: "TestStruct",
Fields: []*schema.TypeField{
&schema.TypeField{
{
Name: tc.out.name,
Type: &schema.VarType{
Expr: "[]" + tc.out.elemExpr,
Expand All @@ -214,9 +216,11 @@ func TestStructSliceField(t *testing.T) {
},
}

got := parseTestStructCode(t, tc.in)
srcCode := genCodeWithStructField("TestStruct", tc.in)
got := parseTestStructCode(t, srcCode)

if !cmp.Equal(want, got) {
t.Log(srcCode)
t.Errorf("%s\n%s\n", tc.in, coloredDiff(want, got))
}
}
Expand Down
37 changes: 18 additions & 19 deletions internal/parser/test/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import (
"golang.org/x/tools/go/packages"
)

func parseTestStructCode(t *testing.T, inputFields string) *schema.Type {
t.Helper()

srcCode := fmt.Sprintf(`package test
func genCodeWithStructField(structName string, inputField string) string {
return fmt.Sprintf(`package test
import (
"context"
Expand All @@ -27,7 +25,7 @@ func parseTestStructCode(t *testing.T, inputFields string) *schema.Type {
"github.com/golang-cz/gospeak/internal/parser/test/empty"
)
type TestStruct struct {
type %s struct {
%s
}
Expand Down Expand Up @@ -61,26 +59,27 @@ func parseTestStructCode(t *testing.T, inputFields string) *schema.Type {
var _ uuid.UUID
var _ Number
var _ Locale
`, inputFields)
`, structName, inputField)
}

func parseTestStructCode(t *testing.T, srcCode string) *schema.Type {
t.Helper()

p, err := testParser(srcCode)
if err != nil {
t.Fatal(fmt.Errorf("error creating test parser: %w", err))
}

if err := parseTestStruct(p); err != nil {
t.Fatal(fmt.Errorf("error parsing: %q: %w", inputFields, err))
if err := parseStruct(p, "TestStruct"); err != nil {
t.Fatal(fmt.Errorf("error parsing code: %w", err))
}

for _, t := range p.Schema.Types {
if t.Name != "TestStruct" {
continue
if t.Name == "TestStruct" {
return t
}

return t
}

t.Fatal("couldn't find TestStruct type")
return nil
}

Expand Down Expand Up @@ -148,22 +147,22 @@ func testParser(srcCode string) (*parser.Parser, error) {
return p, nil
}

func parseTestStruct(p *parser.Parser) error {
func parseStruct(p *parser.Parser, name string) error {
scope := p.Pkg.Types.Scope()

obj := scope.Lookup("TestStruct")
obj := scope.Lookup(name)
if obj == nil {
return fmt.Errorf("type TestStruct not defined")
return fmt.Errorf("type %s not defined", name)
}

testStruct, ok := obj.Type().Underlying().(*types.Struct)
if !ok {
return fmt.Errorf("type TestStruct is %T", obj.Type().Underlying())
return fmt.Errorf("type %s is %T, expected struct", name, obj.Type().Underlying())
}

_, err := p.ParseStruct("TestStruct", testStruct)
_, err := p.ParseStruct(name, testStruct)
if err != nil {
return fmt.Errorf("failed to parse struct TestStruct: %w", err)
return fmt.Errorf("failed to parse struct %s: %w", name, err)
}

return nil
Expand Down

0 comments on commit 610a74e

Please sign in to comment.