Skip to content

Commit d3be085

Browse files
feat: support nested enum (#528)
* fix: detect unname record * test: do tests in non Cpp mode * test: remove function symbols test in non Cpp mode * chore: update comments * revert test * chore: add comments * feat: handle nested struct in enum * feat: support nested enum * test: revert struct test in parser * test: disable gen for convert test * fix: four enum cases * fix: use builtin type directly for anonymous enum * chore: add FIXME * chore: remove newline * fix: merge error * revert temp.h * test: update testdata * test: fix parser test * fix: check hasParent for non-cpp mode * chore: add disscussion commets * test: revert unexpected change * test: fix fallback * test: fix cpp mode test * fix: remove unused logic * Update _xtool/internal/parser/parser.go Co-authored-by: 张之阳 <51194195+luoliwoshang@users.noreply.github.com> --------- Co-authored-by: 张之阳 <51194195+luoliwoshang@users.noreply.github.com>
1 parent 8f012bd commit d3be085

11 files changed

Lines changed: 1357 additions & 30 deletions

File tree

_xtool/internal/clang/clang.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,25 @@ func BuildScopingParts(cursor clang.Cursor) []string {
101101
return parts
102102
}
103103

104+
func HasParent(cursor clang.Cursor) bool {
105+
semanticParentsNum := 0
106+
node := cursor
107+
for node.IsNull() != 1 && node.Kind != clang.CursorTranslationUnit {
108+
semanticParentsNum++
109+
node = node.SemanticParent()
110+
}
111+
if semanticParentsNum > 1 {
112+
return true
113+
}
114+
node = cursor
115+
lexicalParentsNum := 0
116+
for node.IsNull() != 1 && node.Kind != clang.CursorTranslationUnit {
117+
lexicalParentsNum++
118+
node = node.LexicalParent()
119+
}
120+
return lexicalParentsNum > 1
121+
}
122+
104123
func VisitChildren(cursor clang.Cursor, fn Visitor) c.Uint {
105124
return clang.VisitChildren(cursor, func(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
106125
cfn := *(*Visitor)(clientData)

_xtool/internal/parser/parser.go

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ func (ct *Converter) ProcessEnumDecl(cursor clang.Cursor) *ast.EnumTypeDecl {
646646
decl.Name = &ast.Ident{Name: cursorName}
647647
ct.logln("ProcessEnumDecl: has name", cursorName)
648648
} else {
649-
ct.logln("ProcessRecordDecl: is anonymous")
649+
ct.logln("ProcessEnumDecl: is anonymous")
650650
}
651651

652652
return decl
@@ -679,6 +679,7 @@ func (ct *Converter) createBaseField(cursor clang.Cursor) *ast.Field {
679679
fieldName := toStr(cursor.String())
680680

681681
typ := cursor.Type()
682+
682683
typeName, typeKind := getTypeDesc(typ)
683684

684685
ct.logf("createBaseField: ProcessType %s TypeKind: %s", typeName, typeKind)
@@ -760,23 +761,41 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl {
760761
ct.logln("ProcessRecordDecl: CursorName:", cursorName, "CursorKind:", cursorKind)
761762

762763
childs := PostOrderVisitChildren(cursor, func(child, parent clang.Cursor) bool {
764+
// if we found a nested enum, handle it like nested struct
765+
if child.Kind == clang.CursorEnumDecl {
766+
return true
767+
}
763768
return (child.Kind == clang.CursorStructDecl || child.Kind == clang.CursorUnionDecl) && child.IsAnonymous() == 0
764769
})
765770

766771
for _, child := range childs {
767-
// Check if this is a named nested struct/union
768-
typ := ct.ProcessRecordType(child)
769-
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
770-
// but maybe make the forward decl in to AST is also good.
771-
if child.IsAnonymous() == 0 && typ.Fields != nil {
772+
switch child.Kind {
773+
case clang.CursorStructDecl, clang.CursorUnionDecl:
774+
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
775+
// but maybe make the forward decl in to AST is also good.
772776
childName := clang.GoString(child.String())
773777
ct.logln("ProcessRecordDecl: Found named nested struct:", childName)
774-
decls = append(decls, &ast.TypeDecl{
775-
Object: ct.CreateObject(child, &ast.Ident{Name: childName}),
776-
Type: ct.ProcessRecordType(child),
777-
})
778+
// Check if this is a named nested struct/union
779+
typ := ct.ProcessRecordType(child)
780+
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
781+
// but maybe make the forward decl in to AST is also good.
782+
if child.IsAnonymous() == 0 && typ.Fields != nil {
783+
decls = append(decls, &ast.TypeDecl{
784+
Object: ct.CreateObject(child, &ast.Ident{Name: childName}),
785+
Type: ct.ProcessRecordType(child),
786+
})
787+
}
788+
case clang.CursorEnumDecl:
789+
childName := clang.GoString(child.String())
790+
791+
ct.logln("ProcessRecordDecl: Found named nested enum:", childName)
792+
793+
ct.incIndent()
794+
decls = append(decls, ct.ProcessEnumDecl(child))
795+
ct.decIndent()
778796
}
779797
}
798+
ct.logln("ProcessRecordDecl: process record: ", cursorName)
780799

781800
decl := &ast.TypeDecl{
782801
Object: ct.CreateObject(cursor, nil),
@@ -864,28 +883,44 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr {
864883
ct.logln("ProcessElaboratedType: TypeName:", typeName, "TypeKind:", typeKind)
865884

866885
decl := t.TypeDeclaration()
886+
isAnonymousDecl := decl.IsAnonymous() > 0
867887

868-
if decl.IsAnonymous() != 0 {
869-
// anonymous type refer (except anonymous RecordType&EnumType in TypedefDecl)
870-
if decl.Kind == clang.CursorEnumDecl {
888+
if isAnonymousDecl && decl.Kind != clang.CursorEnumDecl {
889+
return ct.ProcessRecordType(decl)
890+
}
891+
parts := clangutils.BuildScopingParts(decl)
892+
hasParent := clangutils.HasParent(decl)
893+
// NOTE(MeteorsLiu): nested enum behaves different from nested struct, for example, we can find its semantic parent
894+
// however, it will cause we misidentified it as a class method expr, so take it out
895+
if isAnonymousDecl && decl.Kind == clang.CursorEnumDecl {
896+
// case 1: anonymous enum, but not nested (anonymous enum decl variable case)
897+
if !hasParent {
898+
// this is not a nested enum, handle it normally
871899
return ct.ProcessEnumType(decl)
872900
}
873-
return ct.ProcessRecordType(decl)
901+
// case 2: anonymous enum, nested (normal nested struct reference)
902+
// by default, the type of an anonymous enum is int
903+
// NOTE(MeteorsLiu): see disscussion https://github.com/goplus/llcppg/pull/530
904+
return &ast.BuiltinType{Kind: ast.Int}
905+
906+
// case 3: named enum, nested, fallback to process as a ElaboratedType (nornaml nested struct)
907+
// case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. (typedef enum case)
874908
}
875909

876910
// for elaborated type, it could have a tag description
877911
// like struct A, union B, class C, enum D
878-
parts := strings.SplitN(typeName, " ", 2)
879-
if len(parts) == 2 {
880-
if tagValue, ok := tagMap[parts[0]]; ok {
912+
typeParts := strings.SplitN(typeName, " ", 2)
913+
914+
if len(typeParts) == 2 {
915+
if tagValue, ok := tagMap[typeParts[0]]; ok {
881916
return &ast.TagExpr{
882917
Tag: tagValue,
883-
Name: ct.BuildScopingExpr(decl),
918+
Name: buildScopingFromParts(parts),
884919
}
885920
}
886921
}
887922

888-
return ct.BuildScopingExpr(decl)
923+
return buildScopingFromParts(parts)
889924
}
890925

891926
func (ct *Converter) ProcessTypeDefType(t clang.Type) ast.Expr {
@@ -1031,7 +1066,6 @@ func buildScopingFromParts(parts []string) ast.Expr {
10311066
if len(parts) == 0 {
10321067
return nil
10331068
}
1034-
10351069
var expr ast.Expr = &ast.Ident{Name: parts[0]}
10361070
for _, part := range parts[1:] {
10371071
expr = &ast.ScopingExpr{

_xtool/internal/parser/parser_test.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
)
2121

2222
func TestParserCppMode(t *testing.T) {
23-
cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "forward_vs_empty"}
23+
cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "forward_vs_empty", "nestedenum_cpp"}
2424
// https://github.com/goplus/llgo/issues/1114
2525
// todo(zzy):use os.ReadDir
2626
for _, folder := range cases {
@@ -31,7 +31,7 @@ func TestParserCppMode(t *testing.T) {
3131
}
3232

3333
func TestParserCMode(t *testing.T) {
34-
cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct", "forward_vs_empty"}
34+
cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct", "forward_vs_empty", "nestedenum"}
3535
for _, folder := range cases {
3636
t.Run(folder, func(t *testing.T) {
3737
testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false)
@@ -250,6 +250,15 @@ func TestNonBuiltinTypes(t *testing.T) {
250250
},
251251
},
252252
},
253+
{
254+
TypeCode: `struct Foo { enum Bar {} k; };
255+
enum Bar`,
256+
ExpectTypeStr: "enum Bar",
257+
expr: &ast.TagExpr{
258+
Tag: ast.Enum,
259+
Name: &ast.Ident{Name: "Bar"},
260+
},
261+
},
253262
{
254263
TypeCode: `enum { x = 42 }`,
255264
ExpectTypeStr: "enum (unnamed enum at temp.h:1:1)",

0 commit comments

Comments
 (0)