Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1f4bb58
fix: detect unname record
MeteorsLiu Aug 4, 2025
28fd750
test: do tests in non Cpp mode
MeteorsLiu Aug 4, 2025
42f16e2
test: remove function symbols test in non Cpp mode
MeteorsLiu Aug 4, 2025
f7de231
chore: update comments
MeteorsLiu Aug 4, 2025
3011d37
revert test
MeteorsLiu Aug 4, 2025
2d4f719
chore: add comments
MeteorsLiu Aug 4, 2025
b8bcc10
feat: handle nested struct in enum
MeteorsLiu Aug 4, 2025
2107b22
merge
MeteorsLiu Aug 4, 2025
fe652c3
feat: support nested enum
MeteorsLiu Aug 5, 2025
01abf49
merge
MeteorsLiu Aug 5, 2025
dcb330a
test: revert struct test in parser
MeteorsLiu Aug 5, 2025
5d01e11
test: disable gen for convert test
MeteorsLiu Aug 5, 2025
98ced8a
fix: four enum cases
MeteorsLiu Aug 5, 2025
2b6ffd5
Merge branch 'main' of https://github.com/goplus/llcppg into nested-e…
MeteorsLiu Aug 5, 2025
f8956e7
fix: use builtin type directly for anonymous enum
MeteorsLiu Aug 5, 2025
451d919
chore: add FIXME
MeteorsLiu Aug 5, 2025
4a2aeb5
chore: remove newline
MeteorsLiu Aug 5, 2025
f9076fb
merge
MeteorsLiu Aug 15, 2025
0ec5a61
fix: merge error
MeteorsLiu Aug 15, 2025
ec6f297
revert temp.h
MeteorsLiu Sep 24, 2025
b106fb4
Merge branch 'main' of https://github.com/goplus/llcppg into nested-e…
MeteorsLiu Sep 24, 2025
ad3208e
test: update testdata
MeteorsLiu Sep 24, 2025
be526e4
test: fix parser test
MeteorsLiu Sep 24, 2025
56f3b3c
fix: check hasParent for non-cpp mode
MeteorsLiu Sep 24, 2025
dd32603
chore: add disscussion commets
MeteorsLiu Sep 24, 2025
73d3e31
test: revert unexpected change
MeteorsLiu Sep 24, 2025
7db12c6
test: fix fallback
MeteorsLiu Sep 24, 2025
ad99773
test: fix cpp mode test
MeteorsLiu Sep 24, 2025
e57a21a
fix: remove unused logic
MeteorsLiu Sep 24, 2025
f92891d
Update _xtool/internal/parser/parser.go
MeteorsLiu Sep 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions _xtool/internal/clang/clang.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,25 @@ func BuildScopingParts(cursor clang.Cursor) []string {
return parts
}

func HasParent(cursor clang.Cursor) bool {
semanticParentsNum := 0
node := cursor
for node.IsNull() != 1 && node.Kind != clang.CursorTranslationUnit {
semanticParentsNum++
node = node.SemanticParent()
}
if semanticParentsNum > 1 {
return true
}
node = cursor
lexicalParentsNum := 0
for node.IsNull() != 1 && node.Kind != clang.CursorTranslationUnit {
lexicalParentsNum++
node = node.LexicalParent()
}
return lexicalParentsNum > 1
}

func VisitChildren(cursor clang.Cursor, fn Visitor) c.Uint {
return clang.VisitChildren(cursor, func(cursor, parent clang.Cursor, clientData unsafe.Pointer) clang.ChildVisitResult {
cfn := *(*Visitor)(clientData)
Expand Down
74 changes: 54 additions & 20 deletions _xtool/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ func (ct *Converter) ProcessEnumDecl(cursor clang.Cursor) *ast.EnumTypeDecl {
decl.Name = &ast.Ident{Name: cursorName}
ct.logln("ProcessEnumDecl: has name", cursorName)
} else {
ct.logln("ProcessRecordDecl: is anonymous")
ct.logln("ProcessEnumDecl: is anonymous")
}

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

typ := cursor.Type()

typeName, typeKind := getTypeDesc(typ)

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

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

for _, child := range childs {
// Check if this is a named nested struct/union
typ := ct.ProcessRecordType(child)
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
// but maybe make the forward decl in to AST is also good.
if child.IsAnonymous() == 0 && typ.Fields != nil {
switch child.Kind {
case clang.CursorStructDecl, clang.CursorUnionDecl:
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
// but maybe make the forward decl in to AST is also good.
childName := clang.GoString(child.String())
ct.logln("ProcessRecordDecl: Found named nested struct:", childName)
decls = append(decls, &ast.TypeDecl{
Object: ct.CreateObject(child, &ast.Ident{Name: childName}),
Type: ct.ProcessRecordType(child),
})
// Check if this is a named nested struct/union
typ := ct.ProcessRecordType(child)
// note(zzy):use len(typ.Fields.List) to ensure it has fields not a forward declaration
// but maybe make the forward decl in to AST is also good.
if child.IsAnonymous() == 0 && typ.Fields != nil {
decls = append(decls, &ast.TypeDecl{
Object: ct.CreateObject(child, &ast.Ident{Name: childName}),
Type: ct.ProcessRecordType(child),
})
}
case clang.CursorEnumDecl:
childName := clang.GoString(child.String())

ct.logln("ProcessRecordDecl: Found named nested enum:", childName)

ct.incIndent()
decls = append(decls, ct.ProcessEnumDecl(child))
ct.decIndent()
}
}
ct.logln("ProcessRecordDecl: process record: ", cursorName)

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

decl := t.TypeDeclaration()
isAnonymousDecl := decl.IsAnonymous() > 0

if decl.IsAnonymous() != 0 {
// anonymous type refer (except anonymous RecordType&EnumType in TypedefDecl)
if decl.Kind == clang.CursorEnumDecl {
if isAnonymousDecl && decl.Kind != clang.CursorEnumDecl {
return ct.ProcessRecordType(decl)
}
parts := clangutils.BuildScopingParts(decl)
hasParent := clangutils.HasParent(decl)
// NOTE(MeteorsLiu): nested enum behaves different from nested struct, for example, we can find its semantic parent
// however, it will cause we misidentified it as a class method expr, so take it out
if isAnonymousDecl && decl.Kind == clang.CursorEnumDecl {
// case 1: anonymous enum, but not nested (anonymous enum decl variable case)
if !hasParent {
// this is not a nested enum, handle it normally
return ct.ProcessEnumType(decl)
}
return ct.ProcessRecordType(decl)
// case 2: anonymous enum, nested (normal nested struct reference)
// by default, the type of an anonymous enum is int
// NOTE(MeteorsLiu): see disscussion https://github.com/goplus/llcppg/pull/530
return &ast.BuiltinType{Kind: ast.Int}

// case 3: named enum, nested, fallback to process as a ElaboratedType (nornaml nested struct)
// case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. (typedef enum case)
}

// for elaborated type, it could have a tag description
// like struct A, union B, class C, enum D
parts := strings.SplitN(typeName, " ", 2)
if len(parts) == 2 {
if tagValue, ok := tagMap[parts[0]]; ok {
typeParts := strings.SplitN(typeName, " ", 2)

if len(typeParts) == 2 {
if tagValue, ok := tagMap[typeParts[0]]; ok {
return &ast.TagExpr{
Tag: tagValue,
Name: ct.BuildScopingExpr(decl),
Name: buildScopingFromParts(parts),
}
}
}

return ct.BuildScopingExpr(decl)
return buildScopingFromParts(parts)
}

func (ct *Converter) ProcessTypeDefType(t clang.Type) ast.Expr {
Expand Down Expand Up @@ -1031,7 +1066,6 @@ func buildScopingFromParts(parts []string) ast.Expr {
if len(parts) == 0 {
return nil
}

var expr ast.Expr = &ast.Ident{Name: parts[0]}
for _, part := range parts[1:] {
expr = &ast.ScopingExpr{
Expand Down
13 changes: 11 additions & 2 deletions _xtool/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

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

func TestParserCMode(t *testing.T) {
cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct", "forward_vs_empty"}
cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct", "forward_vs_empty", "nestedenum"}
for _, folder := range cases {
t.Run(folder, func(t *testing.T) {
testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false)
Expand Down Expand Up @@ -250,6 +250,15 @@ func TestNonBuiltinTypes(t *testing.T) {
},
},
},
{
TypeCode: `struct Foo { enum Bar {} k; };
enum Bar`,
ExpectTypeStr: "enum Bar",
expr: &ast.TagExpr{
Tag: ast.Enum,
Name: &ast.Ident{Name: "Bar"},
},
},
{
TypeCode: `enum { x = 42 }`,
ExpectTypeStr: "enum (unnamed enum at temp.h:1:1)",
Expand Down
Loading
Loading