Open erdemtuna opened 1 year ago
@erdemtuna omg, I'm so sad. :cry: You beat me to it! At least mine has a different backtrace.
panic: runtime error: index out of range [0] with length 0
goroutine 1 [running]:
github.com/jfeliu007/goplantuml/parser.(*Struct).AddField(0xc0001eca10, 0xc0001c6f00, 0xc0001c8104?)
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/struct.go:99 +0x28b
github.com/jfeliu007/goplantuml/parser.handleGenDecStructType(0xc00009a190, {0xc0001c8104, 0x4}, 0xe?)
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:301 +0x65
github.com/jfeliu007/goplantuml/parser.(*ClassParser).processSpec(0xc00009a190, {0x627788?, 0xc0001c6e80?})
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:341 +0xfd
github.com/jfeliu007/goplantuml/parser.(*ClassParser).handleGenDecl(...)
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:327
github.com/jfeliu007/goplantuml/parser.(*ClassParser).parseFileDeclarations(0xc00009a190?, {0x627338?, 0xc0001c7080?})
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:263 +0xb3
github.com/jfeliu007/goplantuml/parser.(*ClassParser).parsePackage(0xc00009a190, {0x626938?, 0xc000182db0})
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:233 +0x3b2
github.com/jfeliu007/goplantuml/parser.(*ClassParser).parseDirectory(0x7f1a1bad76e8?, {0xc000026420, 0x2f})
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:254 +0xd8
github.com/jfeliu007/goplantuml/parser.NewClassDiagramWithOptions(0xc00018dc58)
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:177 +0x29b
github.com/jfeliu007/goplantuml/parser.NewClassDiagram({0xc00007ad80?, 0xc0000c9e70?, 0x8?}, {0x772378?, 0x0?, 0x1f?}, 0x47?)
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/parser/class_parser.go:209 +0xa5
main.main()
/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod/github.com/jfeliu007/goplantuml@v1.6.1/cmd/goplantuml/main.go:102 +0xcb6
While I doubt my environment matters for this, here it is anyway. It's non-standard because I have to cross-compile, but this is the normal (target=host) build:
+ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/daniel/blah/blah/.gopath/amd64-gO0/.cache/go-build"
GOENV="/home/daniel/blah/blah/.gopath/amd64-gO0/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/daniel/blah/blah/.gopath/amd64-gO0/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/daniel/blah/blah/.gopath/amd64-gO0"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/daniel/blah/blah/install/go-amd64-1.19.y"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/daniel/blah/blah/install/go-amd64-1.19.y/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.19.4"
GCCGO="/usr/bin/gccgo"
GOAMD64="v1"
AR="x86_64-pc-linux-gnu-gcc-ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/home/daniel/blah/blah/src/go/go.mod"
GOWORK=""
CGO_CFLAGS="-g -O0"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O0"
CGO_FFLAGS="-g -O0"
CGO_LDFLAGS="-g -O0"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build3860847493=/tmp/go-build -gno-record-gcc-switches"
OK, here's a patch that works, but hasn't been cleaned up or anything. I'll get around to forking and submitting a proper pull request. This adds at least partial support for Generics. Where you see &Type{theType, nil}
, it might not properly support Generics.
diff --git a/go.mod b/go.mod
index 63a30d2..670f08a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/jfeliu007/goplantuml
-go 1.17
+go 1.18
require (
github.com/spf13/afero v1.8.2
diff --git a/parser/class_parser.go b/parser/class_parser.go
index 7526b00..9c5a907 100644
--- a/parser/class_parser.go
+++ b/parser/class_parser.go
@@ -264,7 +264,7 @@ func (p *ClassParser) parseFileDeclarations(node ast.Decl) {
case *ast.FuncDecl:
p.handleFuncDecl(decl)
}
-}
+}//p result["g"].Files["/home/daniel/proj/uml/goplantuml/g/a.go"].Decls[0].Specs[0].TypeParams.List[0].Names
func (p *ClassParser) handleFuncDecl(decl *ast.FuncDecl) {
@@ -279,7 +279,7 @@ func (p *ClassParser) handleFuncDecl(decl *ast.FuncDecl) {
if theType[0] == "*"[0] {
theType = theType[1:]
}
- structure := p.getOrCreateStruct(theType)
+ structure := p.getOrCreateStruct(&Type{theType, nil})
if structure.Type == "" {
structure.Type = "class"
}
@@ -296,21 +296,21 @@ func (p *ClassParser) handleFuncDecl(decl *ast.FuncDecl) {
}
}
-func handleGenDecStructType(p *ClassParser, typeName string, c *ast.StructType) {
+func handleGenDecStructType(p *ClassParser, t *Type, c *ast.StructType) {
for _, f := range c.Fields.List {
- p.getOrCreateStruct(typeName).AddField(f, p.allImports)
+ p.getOrCreateStruct(t).AddField(f, p.allImports)
}
}
-func handleGenDecInterfaceType(p *ClassParser, typeName string, c *ast.InterfaceType) {
+func handleGenDecInterfaceType(p *ClassParser, typ *Type, c *ast.InterfaceType) {
for _, f := range c.Methods.List {
switch t := f.Type.(type) {
case *ast.FuncType:
- p.getOrCreateStruct(typeName).AddMethod(f, p.allImports)
+ p.getOrCreateStruct(typ).AddMethod(f, p.allImports)
break
case *ast.Ident:
f, _ := getFieldType(t, p.allImports)
- st := p.getOrCreateStruct(typeName)
+ st := p.getOrCreateStruct(typ)
f = replacePackageConstant(f, st.PackageName)
st.AddToComposition(f)
break
@@ -329,47 +329,47 @@ func (p *ClassParser) handleGenDecl(decl *ast.GenDecl) {
}
func (p *ClassParser) processSpec(spec ast.Spec) {
- var typeName string
+ var t Type
var alias *Alias
declarationType := "alias"
switch v := spec.(type) {
case *ast.TypeSpec:
- typeName = v.Name.Name
+ t = MakeType(v)
switch c := v.Type.(type) {
case *ast.StructType:
declarationType = "class"
- handleGenDecStructType(p, typeName, c)
+ handleGenDecStructType(p, &t, c)
case *ast.InterfaceType:
declarationType = "interface"
- handleGenDecInterfaceType(p, typeName, c)
+ handleGenDecInterfaceType(p, &t, c)
default:
basicType, _ := getFieldType(getBasicType(c), p.allImports)
aliasType, _ := getFieldType(c, p.allImports)
aliasType = replacePackageConstant(aliasType, "")
- if !isPrimitiveString(typeName) {
- typeName = fmt.Sprintf("%s.%s", p.currentPackageName, typeName)
+ if !isPrimitiveString(t.Name) {
+ t.Name = fmt.Sprintf("%s.%s", p.currentPackageName, t.Name)
}
packageName := p.currentPackageName
if isPrimitiveString(basicType) {
packageName = builtinPackageName
}
- alias = getNewAlias(fmt.Sprintf("%s.%s", packageName, aliasType), p.currentPackageName, typeName)
+ alias = getNewAlias(fmt.Sprintf("%s.%s", packageName, aliasType), p.currentPackageName, t.Name)
}
default:
// Not needed for class diagrams (Imports, global variables, regular functions, etc)
return
}
- p.getOrCreateStruct(typeName).Type = declarationType
- fullName := fmt.Sprintf("%s.%s", p.currentPackageName, typeName)
+ p.getOrCreateStruct(&t).Type = declarationType
+ fullName := fmt.Sprintf("%s.%s", p.currentPackageName, t.ToString(false, false))
switch declarationType {
case "interface":
p.allInterfaces[fullName] = struct{}{}
case "class":
p.allStructs[fullName] = struct{}{}
case "alias":
- p.allAliases[typeName] = alias
+ p.allAliases[t.Name] = alias
if strings.Count(alias.Name, ".") > 1 {
pack := strings.SplitN(alias.Name, ".", 2)
if _, ok := p.allRenamedStructs[pack[0]]; !ok {
@@ -520,7 +520,7 @@ func (p *ClassParser) renderStructure(structure *Struct, pack string, name strin
renderStructureType = "class"
}
- str.WriteLineWithDepth(1, fmt.Sprintf(`%s %s %s {`, renderStructureType, name, sType))
+ str.WriteLineWithDepth(1, fmt.Sprintf(`%s %s %s {`, renderStructureType, structure.TypeData.ToString(true, false), sType))
p.renderStructFields(structure, privateFields, publicFields)
p.renderStructMethods(structure, privateMethods, publicMethods)
p.renderCompositions(structure, name, composition)
@@ -677,20 +677,21 @@ func (p *ClassParser) renderStructFields(structure *Struct, privateFields *LineS
}
// Returns an initialized struct of the given name or returns the existing one if it was already created
-func (p *ClassParser) getOrCreateStruct(name string) *Struct {
- result, ok := p.structure[p.currentPackageName][name]
+func (p *ClassParser) getOrCreateStruct(t *Type) *Struct {
+ result, ok := p.structure[p.currentPackageName][t.Name]
if !ok {
result = &Struct{
PackageName: p.currentPackageName,
Functions: make([]*Function, 0),
Fields: make([]*Field, 0),
Type: "",
+ TypeData: *t,
Composition: make(map[string]struct{}, 0),
Extends: make(map[string]struct{}, 0),
Aggregations: make(map[string]struct{}, 0),
PrivateAggregations: make(map[string]struct{}, 0),
}
- p.structure[p.currentPackageName][name] = result
+ p.structure[p.currentPackageName][t.Name] = result
}
return result
}
diff --git a/parser/field.go b/parser/field.go
index fb25a88..a7bf893 100644
--- a/parser/field.go
+++ b/parser/field.go
@@ -3,6 +3,7 @@ package parser
import (
"fmt"
"strings"
+ "log"
"go/ast"
)
@@ -40,7 +41,14 @@ func getFieldType(exp ast.Expr, aliases map[string]string) (string, []string) {
return getFuncType(v, aliases)
case *ast.Ellipsis:
return getEllipsis(v, aliases)
+ case *ast.IndexExpr:
+ return getGenericType(v, aliases)
+ case *ast.IndexListExpr:
+ // Functions will have v.Indicies populated with the paraemter names,
+ // but we need the type with parameter names and types.
+ return getFieldType(v.X, aliases)
}
+ log.Panicf("getFieldType doesn't know what this is %#+v")
return "", []string{}
}
@@ -136,6 +144,37 @@ func getEllipsis(v *ast.Ellipsis, aliases map[string]string) (string, []string)
return fmt.Sprintf("...%s", t), []string{}
}
+func getGenericType(v *ast.IndexExpr, aliases map[string]string) (string, []string) {
+ t, _ := getFieldType(v.X, aliases)
+ if p, ok := v.Index.(*ast.Ident); ok {
+ log.Printf("getGenericType: %v, %s, %s\n", v, t, p.Name)
+ return fmt.Sprintf("%s<%s>", t, p.Name), []string{}
+ }
+ panic("oops bug")
+ return fmt.Sprintf("%s<%s>", t, "not_parsed"), []string{}
+}
+
+/*
+func getGenericTypeList(v *ast.IndexListExpr, aliases map[string]string) (string, []string) {
+ t, _ := getFieldType(v.X, aliases)
+ t += "["
+ first := true
+ for _, i := range v.Indices {
+ if p, ok := i.(*ast.Ident); ok {
+ log.Printf("getGenericTypeList: %v, %s, %s\n", v, t, p.Name)
+ if first {
+ first = false
+ } else {
+ t += ", "
+ }
+ t += p.Name
+ } else {
+ log.Panicf("index is %+v", i)
+ }
+ }
+ return t + "]", []string{}
+}*/
+
var globalPrimitives = map[string]struct{}{
"bool": {},
"string": {},
diff --git a/parser/function.go b/parser/function.go
index 994325c..3a202b6 100644
--- a/parser/function.go
+++ b/parser/function.go
@@ -8,6 +8,7 @@ import (
//Function holds the signature of a function with name, Parameters and Return values
type Function struct {
Name string
+ TypeParams []TypeParam
Parameters []*Field
ReturnValues []string
PackageName string
diff --git a/parser/struct.go b/parser/struct.go
index 1383775..9920de9 100644
--- a/parser/struct.go
+++ b/parser/struct.go
@@ -12,6 +12,7 @@ type Struct struct {
Functions []*Function
Fields []*Field
Type string
+ TypeData Type
Composition map[string]struct{}
Extends map[string]struct{}
Aggregations map[string]struct{}
diff --git a/parser/type.go b/parser/type.go
new file mode 100644
index 0000000..e676fa9
--- /dev/null
+++ b/parser/type.go
@@ -0,0 +1,120 @@
+package parser
+
+import (
+// "fmt"
+
+ "go/ast"
+)
+
+type TypeParam struct {
+ Name string
+ Constraint string
+}
+
+type TypeParams []TypeParam
+
+func typeParamExprToString(exp ast.Expr) string {
+ switch v := exp.(type) {
+ case *ast.Ident:
+ return v.Name
+ case *ast.BinaryExpr:
+ return typeParamExprToString(v.X) + " " + v.Op.String() + " " + typeParamExprToString(v.Y)
+ }
+ return ""
+}
+
+func MakeTypeParams(typeParams []*ast.Field) (ret TypeParams) {
+ var count = 0
+ var i = 0
+
+ if typeParams == nil && len(typeParams) == 0 {
+ return make(TypeParams, 0)
+ }
+
+ for _, tp := range typeParams {
+ count += len(tp.Names)
+ }
+
+ ret = make(TypeParams, count)
+
+ for _, tp := range typeParams {
+ c := typeParamExprToString(tp.Type)
+ for _, n := range tp.Names {
+ ret[i] = TypeParam {
+ Name: n.Name,
+ Constraint: c,
+ }
+ i += 1
+ }
+ }
+
+ return
+}
+
+func (this TypeParams) toGoDecl() string {
+ ret := ""
+ c := ""
+ for _, i := range this {
+ if len(ret) > 0 {
+ if c == i.Constraint {
+ ret += ", "
+ } else {
+ ret += " " + c + ", "
+ }
+ }
+ ret += i.Name
+ c = i.Constraint
+ }
+ if len(c) > 0 {
+ ret += " " + c
+ }
+
+ return ret
+}
+
+func (this TypeParams) toPumlDecl() string {
+ ret := ""
+ for _, i := range this {
+ if len(ret) > 0 {
+ ret += ", "
+ }
+ ret += i.Name + " " + i.Constraint
+ }
+ return ret
+}
+
+func (this TypeParams) ToString(asGo bool) string {
+ if this == nil {
+ return ""
+ }
+ if (asGo) {
+ return this.toGoDecl()
+ } else {
+ return this.toPumlDecl()
+ }
+}
+
+type Type struct {
+ Name string
+ Params TypeParams
+}
+
+func MakeType(ts *ast.TypeSpec) Type {
+ return Type {
+ Name: ts.Name.Name,
+ Params: MakeTypeParams(ts.TypeParams.List),
+ }
+}
+
+func (this *Type) ToString(asGoType bool, a bool) string {
+ if len(this.Params) == 0 {
+ return this.Name;
+ }
+
+ decl := this.Params.ToString(asGoType)
+ if a {
+ return this.Name + "[" + decl + "]"
+ } else {
+ return this.Name + "<" + decl + ">"
+ }
+}
\ No newline at end of file
package g
type B[T, V int | uint] struct {
a T
b V
}
func (this *B[T, V]) Func() {
}
Becomes:
Interfaces are still jacked up
Thank you for the fix @daniel-santos. I will try it once the fix is merged and released.
Hello, when I run the tool, the parser raises the following panic and error. A blank
puml
file is generated at the end.