-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parser Panic #148
Comments
@erdemtuna omg, I'm so sad. 😢 You beat me to it! At least mine has a different backtrace.
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:
|
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 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 |
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.The text was updated successfully, but these errors were encountered: