From 1f4bb5830792b69b98a63d1e73404fe09d4178a5 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 11:48:02 +0800 Subject: [PATCH 01/25] fix: detect unname record --- _xtool/internal/parser/parser.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 7fb3cf73..be8f315a 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -695,7 +695,7 @@ func (ct *Converter) createBaseField(cursor clang.Cursor) *ast.Field { field.Comment = commentGroup } } - if fieldName != "" { + if cursor.IsAnonymous() == 0 { field.Names = []*ast.Ident{{Name: fieldName}} } return field @@ -781,8 +781,14 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl { Type: ct.ProcessRecordType(cursor), } - anony := cursor.IsAnonymousRecordDecl() - if anony == 0 { + // NOTE(MeteorsLiu): IsAnonymousRecordDecl may return fake results for some special struct, like + // struct { + // int a; + // }; + // to avoid that case, we have to check the IsAnonymous result + isAnonymousRecord := cursor.IsAnonymousRecordDecl() > 0 || cursor.IsAnonymous() > 0 + + if !isAnonymousRecord { decl.Name = &ast.Ident{Name: cursorName} ct.logln("ProcessRecordDecl: has name", cursorName) } else { From 28fd750b4a80ab837bb54b94b4e49d1ff3a56149 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 12:36:13 +0800 Subject: [PATCH 02/25] test: do tests in non Cpp mode --- _xtool/internal/parser/parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 0e10a904..48cb0602 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -31,7 +31,7 @@ func TestParserCppMode(t *testing.T) { } func TestParserCMode(t *testing.T) { - cases := []string{"named_nested_struct"} + cases := []string{"enum", "func", "struct", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "named_nested_struct"} for _, folder := range cases { t.Run(folder, func(t *testing.T) { testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) From 42f16e2c0a8919cfff5fab22c9fa7c5251a1b956 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 14:05:00 +0800 Subject: [PATCH 03/25] test: remove function symbols test in non Cpp mode --- _xtool/internal/parser/parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 48cb0602..f5324657 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -31,7 +31,7 @@ func TestParserCppMode(t *testing.T) { } func TestParserCMode(t *testing.T) { - cases := []string{"enum", "func", "struct", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "named_nested_struct"} + cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct"} for _, folder := range cases { t.Run(folder, func(t *testing.T) { testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) From f7de231fa0e0b9aa385efc76f0e713535efd0c0d Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 15:12:02 +0800 Subject: [PATCH 04/25] chore: update comments --- _xtool/internal/parser/parser.go | 6 ++--- _xtool/internal/parser/testdata/struct/temp.h | 23 ++++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index be8f315a..e575a364 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -695,6 +695,7 @@ func (ct *Converter) createBaseField(cursor clang.Cursor) *ast.Field { field.Comment = commentGroup } } + fmt.Println(fieldName, cursor.IsAnonymous()) if cursor.IsAnonymous() == 0 { field.Names = []*ast.Ident{{Name: fieldName}} } @@ -781,10 +782,7 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl { Type: ct.ProcessRecordType(cursor), } - // NOTE(MeteorsLiu): IsAnonymousRecordDecl may return fake results for some special struct, like - // struct { - // int a; - // }; + // NOTE(MeteorsLiu): IsAnonymousRecordDecl may return fake results when we're in non Cpp mode // to avoid that case, we have to check the IsAnonymous result isAnonymousRecord := cursor.IsAnonymousRecordDecl() > 0 || cursor.IsAnonymous() > 0 diff --git a/_xtool/internal/parser/testdata/struct/temp.h b/_xtool/internal/parser/testdata/struct/temp.h index 52302512..66b4732e 100644 --- a/_xtool/internal/parser/testdata/struct/temp.h +++ b/_xtool/internal/parser/testdata/struct/temp.h @@ -1,25 +1,36 @@ -struct { +struct +{ int a; }; -struct Foo1 { +struct Foo1 +{ int a; int b; }; -struct Foo2 { +struct Foo2 +{ int a, b; }; -struct Foo3 { +struct Foo3 +{ int a; int (*Foo)(int, int); }; -struct Person { +struct Person +{ int age; - struct { + struct + { int year; int day; int month; } birthday; + + struct empty + { + int a; + }; }; From 3011d37d3740b9d39d9afa859e22eef4ecd66b80 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 15:12:59 +0800 Subject: [PATCH 05/25] revert test --- _xtool/internal/parser/testdata/struct/temp.h | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/_xtool/internal/parser/testdata/struct/temp.h b/_xtool/internal/parser/testdata/struct/temp.h index 66b4732e..52302512 100644 --- a/_xtool/internal/parser/testdata/struct/temp.h +++ b/_xtool/internal/parser/testdata/struct/temp.h @@ -1,36 +1,25 @@ -struct -{ +struct { int a; }; -struct Foo1 -{ +struct Foo1 { int a; int b; }; -struct Foo2 -{ +struct Foo2 { int a, b; }; -struct Foo3 -{ +struct Foo3 { int a; int (*Foo)(int, int); }; -struct Person -{ +struct Person { int age; - struct - { + struct { int year; int day; int month; } birthday; - - struct empty - { - int a; - }; }; From 2d4f719f73a20fef0af96e217322b470188afe08 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 15:14:48 +0800 Subject: [PATCH 06/25] chore: add comments --- _xtool/internal/parser/parser.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index e575a364..02697230 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -695,7 +695,8 @@ func (ct *Converter) createBaseField(cursor clang.Cursor) *ast.Field { field.Comment = commentGroup } } - fmt.Println(fieldName, cursor.IsAnonymous()) + // NOTE(MeteorsLiu): In non cpp mode, an anonymous field name may be `unname struct` instead of empty string + // so check it via IsAnonymous() if cursor.IsAnonymous() == 0 { field.Names = []*ast.Ident{{Name: fieldName}} } From b8bcc10156cc4956e41c573947f2140bc5aef091 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 4 Aug 2025 16:32:43 +0800 Subject: [PATCH 07/25] feat: handle nested struct in enum --- _xtool/internal/parser/parser.go | 54 +- _xtool/internal/parser/parser_test.go | 1212 +++++++++-------- _xtool/internal/parser/testdata/struct/temp.h | 18 +- .../convert/_testdata/enum/hfile/temp.h | 54 +- .../convert/_testdata/nested/gogensig.expect | 5 + .../convert/_testdata/nested/hfile/temp.h | 76 +- cl/internal/convert/convert.go | 1 + cl/internal/convert/convert_test.go | 2 +- 8 files changed, 780 insertions(+), 642 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 7fb3cf73..3e384caf 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -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 @@ -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) @@ -758,31 +759,53 @@ 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 && len(typ.Fields.List) > 0 { + switch child.Kind { + case clang.CursorStructDecl, clang.CursorUnionDecl: 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 && len(typ.Fields.List) > 0 { + 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), Type: ct.ProcessRecordType(cursor), } - anony := cursor.IsAnonymousRecordDecl() - if anony == 0 { + // NOTE(MeteorsLiu): IsAnonymousRecordDecl() cannot recognize these anonymous struct: + // struct + // { + // int a; + // }; + isAnonymousRecord := cursor.IsAnonymousRecordDecl() > 0 || cursor.IsAnonymous() > 0 + + if !isAnonymousRecord { decl.Name = &ast.Ident{Name: cursorName} ct.logln("ProcessRecordDecl: has name", cursorName) } else { @@ -857,17 +880,14 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { decl := t.TypeDeclaration() - if decl.IsAnonymous() != 0 { - // anonymous type refer (except anonymous RecordType&EnumType in TypedefDecl) - if decl.Kind == clang.CursorEnumDecl { - return ct.ProcessEnumType(decl) - } + if decl.IsAnonymous() != 0 && decl.Kind != clang.CursorEnumDecl { return ct.ProcessRecordType(decl) } // 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 { return &ast.TagExpr{ diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 0e10a904..0e44ad5d 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -4,41 +4,55 @@ import ( "encoding/json" "fmt" "os" - "path" "path/filepath" - "reflect" - "strings" "testing" - "github.com/goplus/lib/c" "github.com/goplus/lib/c/clang" clangutils "github.com/goplus/llcppg/_xtool/internal/clang" - "github.com/goplus/llcppg/_xtool/internal/clangtool" "github.com/goplus/llcppg/_xtool/internal/parser" - "github.com/goplus/llcppg/ast" - "github.com/goplus/llgo/xtool/clang/preprocessor" ) func TestParserCppMode(t *testing.T) { - cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof"} - // https://github.com/goplus/llgo/issues/1114 - // todo(zzy):use os.ReadDir - for _, folder := range cases { - t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", true, false) - }) - } + // parser.SetDebug(parser.DbgFlagAll) + + t.Run("struct", func(t *testing.T) { + testFrom(t, filepath.Join("testdata", "struct"), "temp.h", false, false) + }) + // cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof"} + // // https://github.com/goplus/llgo/issues/1114 + // // todo(zzy):use os.ReadDir + // for _, folder := range cases { + // t.Run(folder, func(t *testing.T) { + // testFrom(t, filepath.Join("testdata", folder), "temp.h", true, false) + // }) + // } } -func TestParserCMode(t *testing.T) { - cases := []string{"named_nested_struct"} - for _, folder := range cases { - t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) - }) +func TestPure(t *testing.T) { + config := &clangutils.Config{ + File: "./testdata/struct/temp.h", + Temp: false, + IsCpp: false, } + + visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { + name := clang.GoString(cursor.String()) + typ := clang.GoString(cursor.Kind.String()) + fmt.Println(name, typ) + return clang.ChildVisit_Recurse + }) + } +// func TestParserCMode(t *testing.T) { +// cases := []string{"named_nested_struct"} +// for _, folder := range cases { +// t.Run(folder, func(t *testing.T) { +// testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) +// }) +// } +// } + func testFrom(t *testing.T, dir string, filename string, isCpp, gen bool) { var expect string var err error @@ -76,538 +90,538 @@ func testFrom(t *testing.T, dir string, filename string, isCpp, gen bool) { } } -func TestNonBuiltinTypes(t *testing.T) { - tests := []struct { - TypeCode string - ExpectTypeStr string - expr ast.Expr - }{ - { - TypeCode: "int*", - ExpectTypeStr: "int *", - expr: &ast.PointerType{ - X: &ast.BuiltinType{ - Kind: ast.Int, - }, - }, - }, - { - TypeCode: "int***", - ExpectTypeStr: "int ***", - expr: &ast.PointerType{ - X: &ast.PointerType{ - X: &ast.PointerType{ - X: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - }, - }, - { - TypeCode: "int[]", - ExpectTypeStr: "int[]", - expr: &ast.ArrayType{ - Elt: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - { - TypeCode: "int[10]", - ExpectTypeStr: "int[10]", - expr: &ast.ArrayType{ - Elt: &ast.BuiltinType{Kind: ast.Int}, - Len: &ast.BasicLit{ - Kind: ast.IntLit, - Value: "10", - }, - }, - }, - { - TypeCode: "int[3][4]", - ExpectTypeStr: "int[3][4]", - expr: &ast.ArrayType{ - Elt: &ast.ArrayType{ - Elt: &ast.BuiltinType{Kind: ast.Int}, - Len: &ast.BasicLit{ - Kind: ast.IntLit, - Value: "4", - }, - }, - Len: &ast.BasicLit{ - Kind: ast.IntLit, - Value: "3", - }, - }, - }, - { - TypeCode: "int&", - ExpectTypeStr: "int &", - expr: &ast.LvalueRefType{ - X: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - { - TypeCode: "int&&", - ExpectTypeStr: "int &&", - expr: &ast.RvalueRefType{ - X: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - { - TypeCode: `struct Foo {}; - Foo`, - ExpectTypeStr: "Foo", - expr: &ast.Ident{ - Name: "Foo", - }, - }, - { - TypeCode: `struct Foo {}; - struct Foo`, - ExpectTypeStr: "struct Foo", - expr: &ast.TagExpr{ - Tag: ast.Struct, - Name: &ast.Ident{ - Name: "Foo", - }, - }, - }, - { - TypeCode: `struct { - int x; - }`, - ExpectTypeStr: "struct (unnamed struct at temp.h:1:1)", - expr: &ast.RecordType{ - Tag: ast.Struct, - Fields: &ast.FieldList{ - List: []*ast.Field{ - { - Names: []*ast.Ident{ - {Name: "x"}, - }, - Type: &ast.BuiltinType{Kind: ast.Int}, - Access: ast.Public, - }, - }, - }, - Methods: []*ast.FuncDecl{}, - }, - }, - { - TypeCode: `union Foo {}; - Foo`, - ExpectTypeStr: "Foo", - expr: &ast.Ident{ - Name: "Foo", - }, - }, - { - TypeCode: `union Foo {}; - union Foo`, - ExpectTypeStr: "union Foo", - expr: &ast.TagExpr{ - Tag: ast.Union, - Name: &ast.Ident{ - Name: "Foo", - }, - }, - }, - { - TypeCode: `union { - int x; - }`, - ExpectTypeStr: "union (unnamed union at temp.h:1:1)", - expr: &ast.RecordType{ - Tag: ast.Union, - Fields: &ast.FieldList{ - List: []*ast.Field{ - { - Names: []*ast.Ident{ - {Name: "x"}, - }, - Access: ast.Public, - Type: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - }, - Methods: []*ast.FuncDecl{}, - }, - }, - { - TypeCode: `enum Foo {}; - Foo`, - ExpectTypeStr: "Foo", - expr: &ast.Ident{ - Name: "Foo", - }, - }, - { - TypeCode: `enum Foo {}; - enum Foo`, - ExpectTypeStr: "enum Foo", - expr: &ast.TagExpr{ - Tag: ast.Enum, - Name: &ast.Ident{ - Name: "Foo", - }, - }, - }, - { - TypeCode: `enum { x = 42 }`, - ExpectTypeStr: "enum (unnamed enum at temp.h:1:1)", - expr: &ast.EnumType{ - Items: []*ast.EnumItem{ - { - Name: &ast.Ident{ - Name: "x", - }, - Value: &ast.BasicLit{ - Kind: ast.IntLit, - Value: "42", - }, - }, - }, - }, - }, - { - TypeCode: `class Foo {}; - Foo`, - ExpectTypeStr: "Foo", - expr: &ast.Ident{ - Name: "Foo", - }, - }, - { - TypeCode: `class Foo {}; - class Foo`, - ExpectTypeStr: "class Foo", - expr: &ast.TagExpr{ - Tag: ast.Class, - Name: &ast.Ident{ - Name: "Foo", - }, - }, - }, - { - TypeCode: `class { - int x; - }`, - ExpectTypeStr: "class (unnamed class at temp.h:1:1)", - expr: &ast.RecordType{ - Tag: ast.Class, - Fields: &ast.FieldList{ - List: []*ast.Field{ - { - Names: []*ast.Ident{ - {Name: "x"}, - }, - Access: ast.Private, - Type: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - }, - Methods: []*ast.FuncDecl{}, - }, - }, - { - TypeCode: `namespace a { - namespace b { - class c { - }; - } - } - a::b::c`, - ExpectTypeStr: "a::b::c", - expr: &ast.ScopingExpr{ - Parent: &ast.ScopingExpr{ - Parent: &ast.Ident{ - Name: "a", - }, - X: &ast.Ident{ - Name: "b", - }, - }, - X: &ast.Ident{ - Name: "c", - }, - }, - }, - { - TypeCode: `namespace a { - namespace b { - class c { - }; - } - } - class a::b::c`, - ExpectTypeStr: "class a::b::c", - expr: &ast.TagExpr{ - Tag: ast.Class, - Name: &ast.ScopingExpr{ - Parent: &ast.ScopingExpr{ - Parent: &ast.Ident{ - Name: "a", - }, - X: &ast.Ident{ - Name: "b", - }, - }, - X: &ast.Ident{ - Name: "c", - }, - }, - }, - }, - { - TypeCode: `int (*p)(int, int);`, - ExpectTypeStr: "int (*)(int, int)", - expr: &ast.PointerType{ - X: &ast.FuncType{ - Params: &ast.FieldList{ - List: []*ast.Field{ - { - Type: &ast.BuiltinType{Kind: ast.Int}, - }, - { - Type: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - }, - Ret: &ast.BuiltinType{Kind: ast.Int}, - }, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.ExpectTypeStr, func(t *testing.T) { - typ, index, unit := GetType(&GetTypeOptions{ - TypeCode: tc.TypeCode, - IsCpp: true, - }) - converter := &parser.Converter{} - expr := converter.ProcessType(typ) - typstr := typ.String() - if typGoStr := c.GoString(typstr.CStr()); typGoStr != tc.ExpectTypeStr { - t.Fatalf("expect %s , got %s", tc.ExpectTypeStr, typGoStr) - } - if !reflect.DeepEqual(expr, tc.expr) { - t.Fatalf("%s expect %#v, got %#v", tc.ExpectTypeStr, tc.expr, expr) - } - - typstr.Dispose() - - index.Dispose() - unit.Dispose() - }) - } -} - -func TestBuiltinType(t *testing.T) { - tests := []struct { - name string - typ clang.Type - expected ast.BuiltinType - }{ - {"Void", btType(clang.TypeVoid), ast.BuiltinType{Kind: ast.Void}}, - {"Bool", btType(clang.TypeBool), ast.BuiltinType{Kind: ast.Bool}}, - {"Char_S", btType(clang.TypeCharS), ast.BuiltinType{Kind: ast.Char, Flags: ast.Signed}}, - {"Char_U", btType(clang.TypeCharU), ast.BuiltinType{Kind: ast.Char, Flags: ast.Unsigned}}, - {"Char16", btType(clang.TypeChar16), ast.BuiltinType{Kind: ast.Char16}}, - {"Char32", btType(clang.TypeChar32), ast.BuiltinType{Kind: ast.Char32}}, - {"WChar", btType(clang.TypeWChar), ast.BuiltinType{Kind: ast.WChar}}, - {"Short", btType(clang.TypeShort), ast.BuiltinType{Kind: ast.Int, Flags: ast.Short}}, - {"UShort", btType(clang.TypeUShort), ast.BuiltinType{Kind: ast.Int, Flags: ast.Short | ast.Unsigned}}, - {"Int", btType(clang.TypeInt), ast.BuiltinType{Kind: ast.Int}}, - {"UInt", btType(clang.TypeUInt), ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}}, - {"Long", btType(clang.TypeLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}}, - {"ULong", btType(clang.TypeULong), ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}}, - {"LongLong", btType(clang.TypeLongLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong}}, - {"ULongLong", btType(clang.TypeULongLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong | ast.Unsigned}}, - {"Int128", btType(clang.TypeInt128), ast.BuiltinType{Kind: ast.Int128}}, - {"UInt128", btType(clang.TypeUInt128), ast.BuiltinType{Kind: ast.Int128, Flags: ast.Unsigned}}, - {"Float", btType(clang.TypeFloat), ast.BuiltinType{Kind: ast.Float}}, - {"Half", btType(clang.TypeHalf), ast.BuiltinType{Kind: ast.Float16}}, - {"Float16", btType(clang.TypeFloat16), ast.BuiltinType{Kind: ast.Float16}}, - {"Double", btType(clang.TypeDouble), ast.BuiltinType{Kind: ast.Float, Flags: ast.Double}}, - {"LongDouble", btType(clang.TypeLongDouble), ast.BuiltinType{Kind: ast.Float, Flags: ast.Long | ast.Double}}, - {"Float128", btType(clang.TypeFloat128), ast.BuiltinType{Kind: ast.Float128}}, - {"Complex", getComplexType(0), ast.BuiltinType{Kind: ast.Complex}}, - {"Complex", getComplexType(ast.Double), ast.BuiltinType{Flags: ast.Double, Kind: ast.Complex}}, - {"Complex", getComplexType(ast.Long | ast.Double), ast.BuiltinType{Flags: ast.Long | ast.Double, Kind: ast.Complex}}, - {"Unknown", btType(clang.TypeIbm128), ast.BuiltinType{Kind: ast.Void}}, - } - - converter := &parser.Converter{} - converter.Convert() - for _, bt := range tests { - res := converter.ProcessBuiltinType(bt.typ) - if res.Kind != bt.expected.Kind { - t.Fatalf("%s Kind mismatch:got %d want %d, \n", bt.name, res.Kind, bt.expected.Kind) - } - if res.Flags != bt.expected.Flags { - t.Fatalf("%s Flags mismatch:got %d,want %d\n", bt.name, res.Flags, bt.expected.Flags) - } - } -} - -// Char's Default Type in macos is signed char & in linux is unsigned char -// So we only confirm the char's kind is char & flags is unsigned or signed -func TestChar(t *testing.T) { - typ, index, transunit := GetType(&GetTypeOptions{ - TypeCode: "char", - IsCpp: false, - }) - converter := &parser.Converter{} - expr := converter.ProcessType(typ) - if btType, ok := expr.(*ast.BuiltinType); ok { - if btType.Kind == ast.Char { - if btType.Flags != ast.Signed && btType.Flags != ast.Unsigned { - t.Fatal("Char's flags is not signed or unsigned") - } - } - } else { - t.Fatal("Char's expr is not a builtin type") - } - index.Dispose() - transunit.Dispose() -} - -type GetTypeOptions struct { - TypeCode string // e.g. "char*", "char**" - - // ExpectTypeKind specifies the expected type kind (optional) - // Use clang.Type_Invalid to accept any type (default behavior) - // *For complex types (when is included), specifying this is crucial - // to filter out the correct type, as there will be multiple VarDecl fields present - ExpectTypeKind clang.TypeKind - - // Args contains additional compilation arguments passed to Clang (optional) - // These are appended after the default language-specific arguments - // Example: []string{"-std=c++11"} - Args []string - - // IsCpp indicates whether the code should be treated as C++ (true) or C (false) - // This affects the default language arguments passed to Clang: - // - For C++: []string{"-x", "c++"} - // - For C: []string{"-x", "c"} - // *For complex C types, C Must be specified - IsCpp bool -} - -// GetType returns the clang.Type of the given type code -// Need to dispose the index and unit after using -// e.g. index.Dispose(), unit.Dispose() -func GetType(option *GetTypeOptions) (clang.Type, *clang.Index, *clang.TranslationUnit) { - code := fmt.Sprintf("%s placeholder;", option.TypeCode) - index, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{ - File: code, - Temp: true, - Args: option.Args, - IsCpp: option.IsCpp, - }) - if err != nil { - panic(err) - } - cursor := unit.Cursor() - var typ clang.Type - clangutils.VisitChildren(cursor, func(child, parent clang.Cursor) clang.ChildVisitResult { - if child.Kind == clang.CursorVarDecl && (option.ExpectTypeKind == clang.TypeInvalid || option.ExpectTypeKind == child.Type().Kind) { - typ = child.Type() - return clang.ChildVisit_Break - } - return clang.ChildVisit_Continue - }) - return typ, index, unit -} - -func btType(kind clang.TypeKind) clang.Type { - return clang.Type{Kind: kind} -} - -// get complex type from source code parsed -func getComplexType(flag ast.TypeFlag) clang.Type { - var typeStr string - if flag&(ast.Long|ast.Double) == (ast.Long | ast.Double) { - typeStr = "long double" - } else if flag&ast.Double != 0 { - typeStr = "double" - } else { - typeStr = "float" - } - - code := fmt.Sprintf("#include \n%s complex", typeStr) - - // todo(zzy):free index and unit after test - typ, _, _ := GetType(&GetTypeOptions{ - TypeCode: code, - ExpectTypeKind: clang.TypeComplex, - IsCpp: false, - }) - - return typ -} - -func TestPreprocess(t *testing.T) { - combinedFile, err := os.CreateTemp("./", "compose_*.h") - if err != nil { - panic(err) - } - defer os.Remove(combinedFile.Name()) - - clangtool.ComposeIncludes([]string{"main.h", "compat.h"}, combinedFile.Name()) - - efile, err := os.CreateTemp("", "temp_*.i") - if err != nil { - panic(err) - } - defer os.Remove(efile.Name()) - - ppconf := &preprocessor.Config{ - Compiler: "clang", - Flags: []string{"-I./_testdata/hfile"}, - } - err = preprocessor.Do(combinedFile.Name(), efile.Name(), ppconf) - if err != nil { - t.Fatal(err) - } - - config := &clangutils.Config{ - File: efile.Name(), - Temp: false, - IsCpp: false, - } - - var str strings.Builder - - visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { - switch cursor.Kind { - case clang.CursorEnumDecl, clang.CursorStructDecl, clang.CursorUnionDecl, clang.CursorTypedefDecl: - var filename clang.String - var line, column c.Uint - cursor.Location().PresumedLocation(&filename, &line, &column) - str.WriteString("TypeKind: ") - str.WriteString(clang.GoString(cursor.Kind.String())) - str.WriteString(" Name: ") - str.WriteString(clang.GoString(cursor.String())) - str.WriteString("\n") - str.WriteString("Location: ") - str.WriteString(fmt.Sprintf("%s:%d:%d\n", path.Base(c.GoString(filename.CStr())), line, column)) - } - return clang.ChildVisit_Continue - }) - - expect := ` -TypeKind: StructDecl Name: A -Location: main.h:3:16 -TypeKind: TypedefDecl Name: A -Location: main.h:6:3 -TypeKind: TypedefDecl Name: B -Location: compat.h:3:11 -TypeKind: TypedefDecl Name: C -Location: main.h:8:11 -` - - compareOutput(t, expect, str.String()) -} +// func TestNonBuiltinTypes(t *testing.T) { +// tests := []struct { +// TypeCode string +// ExpectTypeStr string +// expr ast.Expr +// }{ +// { +// TypeCode: "int*", +// ExpectTypeStr: "int *", +// expr: &ast.PointerType{ +// X: &ast.BuiltinType{ +// Kind: ast.Int, +// }, +// }, +// }, +// { +// TypeCode: "int***", +// ExpectTypeStr: "int ***", +// expr: &ast.PointerType{ +// X: &ast.PointerType{ +// X: &ast.PointerType{ +// X: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// }, +// }, +// { +// TypeCode: "int[]", +// ExpectTypeStr: "int[]", +// expr: &ast.ArrayType{ +// Elt: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// { +// TypeCode: "int[10]", +// ExpectTypeStr: "int[10]", +// expr: &ast.ArrayType{ +// Elt: &ast.BuiltinType{Kind: ast.Int}, +// Len: &ast.BasicLit{ +// Kind: ast.IntLit, +// Value: "10", +// }, +// }, +// }, +// { +// TypeCode: "int[3][4]", +// ExpectTypeStr: "int[3][4]", +// expr: &ast.ArrayType{ +// Elt: &ast.ArrayType{ +// Elt: &ast.BuiltinType{Kind: ast.Int}, +// Len: &ast.BasicLit{ +// Kind: ast.IntLit, +// Value: "4", +// }, +// }, +// Len: &ast.BasicLit{ +// Kind: ast.IntLit, +// Value: "3", +// }, +// }, +// }, +// { +// TypeCode: "int&", +// ExpectTypeStr: "int &", +// expr: &ast.LvalueRefType{ +// X: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// { +// TypeCode: "int&&", +// ExpectTypeStr: "int &&", +// expr: &ast.RvalueRefType{ +// X: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// { +// TypeCode: `struct Foo {}; +// Foo`, +// ExpectTypeStr: "Foo", +// expr: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// { +// TypeCode: `struct Foo {}; +// struct Foo`, +// ExpectTypeStr: "struct Foo", +// expr: &ast.TagExpr{ +// Tag: ast.Struct, +// Name: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// }, +// { +// TypeCode: `struct { +// int x; +// }`, +// ExpectTypeStr: "struct (unnamed struct at temp.h:1:1)", +// expr: &ast.RecordType{ +// Tag: ast.Struct, +// Fields: &ast.FieldList{ +// List: []*ast.Field{ +// { +// Names: []*ast.Ident{ +// {Name: "x"}, +// }, +// Type: &ast.BuiltinType{Kind: ast.Int}, +// Access: ast.Public, +// }, +// }, +// }, +// Methods: []*ast.FuncDecl{}, +// }, +// }, +// { +// TypeCode: `union Foo {}; +// Foo`, +// ExpectTypeStr: "Foo", +// expr: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// { +// TypeCode: `union Foo {}; +// union Foo`, +// ExpectTypeStr: "union Foo", +// expr: &ast.TagExpr{ +// Tag: ast.Union, +// Name: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// }, +// { +// TypeCode: `union { +// int x; +// }`, +// ExpectTypeStr: "union (unnamed union at temp.h:1:1)", +// expr: &ast.RecordType{ +// Tag: ast.Union, +// Fields: &ast.FieldList{ +// List: []*ast.Field{ +// { +// Names: []*ast.Ident{ +// {Name: "x"}, +// }, +// Access: ast.Public, +// Type: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// }, +// Methods: []*ast.FuncDecl{}, +// }, +// }, +// { +// TypeCode: `enum Foo {}; +// Foo`, +// ExpectTypeStr: "Foo", +// expr: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// { +// TypeCode: `enum Foo {}; +// enum Foo`, +// ExpectTypeStr: "enum Foo", +// expr: &ast.TagExpr{ +// Tag: ast.Enum, +// Name: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// }, +// { +// TypeCode: `enum { x = 42 }`, +// ExpectTypeStr: "enum (unnamed enum at temp.h:1:1)", +// expr: &ast.EnumType{ +// Items: []*ast.EnumItem{ +// { +// Name: &ast.Ident{ +// Name: "x", +// }, +// Value: &ast.BasicLit{ +// Kind: ast.IntLit, +// Value: "42", +// }, +// }, +// }, +// }, +// }, +// { +// TypeCode: `class Foo {}; +// Foo`, +// ExpectTypeStr: "Foo", +// expr: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// { +// TypeCode: `class Foo {}; +// class Foo`, +// ExpectTypeStr: "class Foo", +// expr: &ast.TagExpr{ +// Tag: ast.Class, +// Name: &ast.Ident{ +// Name: "Foo", +// }, +// }, +// }, +// { +// TypeCode: `class { +// int x; +// }`, +// ExpectTypeStr: "class (unnamed class at temp.h:1:1)", +// expr: &ast.RecordType{ +// Tag: ast.Class, +// Fields: &ast.FieldList{ +// List: []*ast.Field{ +// { +// Names: []*ast.Ident{ +// {Name: "x"}, +// }, +// Access: ast.Private, +// Type: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// }, +// Methods: []*ast.FuncDecl{}, +// }, +// }, +// { +// TypeCode: `namespace a { +// namespace b { +// class c { +// }; +// } +// } +// a::b::c`, +// ExpectTypeStr: "a::b::c", +// expr: &ast.ScopingExpr{ +// Parent: &ast.ScopingExpr{ +// Parent: &ast.Ident{ +// Name: "a", +// }, +// X: &ast.Ident{ +// Name: "b", +// }, +// }, +// X: &ast.Ident{ +// Name: "c", +// }, +// }, +// }, +// { +// TypeCode: `namespace a { +// namespace b { +// class c { +// }; +// } +// } +// class a::b::c`, +// ExpectTypeStr: "class a::b::c", +// expr: &ast.TagExpr{ +// Tag: ast.Class, +// Name: &ast.ScopingExpr{ +// Parent: &ast.ScopingExpr{ +// Parent: &ast.Ident{ +// Name: "a", +// }, +// X: &ast.Ident{ +// Name: "b", +// }, +// }, +// X: &ast.Ident{ +// Name: "c", +// }, +// }, +// }, +// }, +// { +// TypeCode: `int (*p)(int, int);`, +// ExpectTypeStr: "int (*)(int, int)", +// expr: &ast.PointerType{ +// X: &ast.FuncType{ +// Params: &ast.FieldList{ +// List: []*ast.Field{ +// { +// Type: &ast.BuiltinType{Kind: ast.Int}, +// }, +// { +// Type: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// }, +// Ret: &ast.BuiltinType{Kind: ast.Int}, +// }, +// }, +// }, +// } + +// for _, tc := range tests { +// t.Run(tc.ExpectTypeStr, func(t *testing.T) { +// typ, index, unit := GetType(&GetTypeOptions{ +// TypeCode: tc.TypeCode, +// IsCpp: true, +// }) +// converter := &parser.Converter{} +// expr := converter.ProcessType(typ) +// typstr := typ.String() +// if typGoStr := c.GoString(typstr.CStr()); typGoStr != tc.ExpectTypeStr { +// t.Fatalf("expect %s , got %s", tc.ExpectTypeStr, typGoStr) +// } +// if !reflect.DeepEqual(expr, tc.expr) { +// t.Fatalf("%s expect %#v, got %#v", tc.ExpectTypeStr, tc.expr, expr) +// } + +// typstr.Dispose() + +// index.Dispose() +// unit.Dispose() +// }) +// } +// } + +// func TestBuiltinType(t *testing.T) { +// tests := []struct { +// name string +// typ clang.Type +// expected ast.BuiltinType +// }{ +// {"Void", btType(clang.TypeVoid), ast.BuiltinType{Kind: ast.Void}}, +// {"Bool", btType(clang.TypeBool), ast.BuiltinType{Kind: ast.Bool}}, +// {"Char_S", btType(clang.TypeCharS), ast.BuiltinType{Kind: ast.Char, Flags: ast.Signed}}, +// {"Char_U", btType(clang.TypeCharU), ast.BuiltinType{Kind: ast.Char, Flags: ast.Unsigned}}, +// {"Char16", btType(clang.TypeChar16), ast.BuiltinType{Kind: ast.Char16}}, +// {"Char32", btType(clang.TypeChar32), ast.BuiltinType{Kind: ast.Char32}}, +// {"WChar", btType(clang.TypeWChar), ast.BuiltinType{Kind: ast.WChar}}, +// {"Short", btType(clang.TypeShort), ast.BuiltinType{Kind: ast.Int, Flags: ast.Short}}, +// {"UShort", btType(clang.TypeUShort), ast.BuiltinType{Kind: ast.Int, Flags: ast.Short | ast.Unsigned}}, +// {"Int", btType(clang.TypeInt), ast.BuiltinType{Kind: ast.Int}}, +// {"UInt", btType(clang.TypeUInt), ast.BuiltinType{Kind: ast.Int, Flags: ast.Unsigned}}, +// {"Long", btType(clang.TypeLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.Long}}, +// {"ULong", btType(clang.TypeULong), ast.BuiltinType{Kind: ast.Int, Flags: ast.Long | ast.Unsigned}}, +// {"LongLong", btType(clang.TypeLongLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong}}, +// {"ULongLong", btType(clang.TypeULongLong), ast.BuiltinType{Kind: ast.Int, Flags: ast.LongLong | ast.Unsigned}}, +// {"Int128", btType(clang.TypeInt128), ast.BuiltinType{Kind: ast.Int128}}, +// {"UInt128", btType(clang.TypeUInt128), ast.BuiltinType{Kind: ast.Int128, Flags: ast.Unsigned}}, +// {"Float", btType(clang.TypeFloat), ast.BuiltinType{Kind: ast.Float}}, +// {"Half", btType(clang.TypeHalf), ast.BuiltinType{Kind: ast.Float16}}, +// {"Float16", btType(clang.TypeFloat16), ast.BuiltinType{Kind: ast.Float16}}, +// {"Double", btType(clang.TypeDouble), ast.BuiltinType{Kind: ast.Float, Flags: ast.Double}}, +// {"LongDouble", btType(clang.TypeLongDouble), ast.BuiltinType{Kind: ast.Float, Flags: ast.Long | ast.Double}}, +// {"Float128", btType(clang.TypeFloat128), ast.BuiltinType{Kind: ast.Float128}}, +// {"Complex", getComplexType(0), ast.BuiltinType{Kind: ast.Complex}}, +// {"Complex", getComplexType(ast.Double), ast.BuiltinType{Flags: ast.Double, Kind: ast.Complex}}, +// {"Complex", getComplexType(ast.Long | ast.Double), ast.BuiltinType{Flags: ast.Long | ast.Double, Kind: ast.Complex}}, +// {"Unknown", btType(clang.TypeIbm128), ast.BuiltinType{Kind: ast.Void}}, +// } + +// converter := &parser.Converter{} +// converter.Convert() +// for _, bt := range tests { +// res := converter.ProcessBuiltinType(bt.typ) +// if res.Kind != bt.expected.Kind { +// t.Fatalf("%s Kind mismatch:got %d want %d, \n", bt.name, res.Kind, bt.expected.Kind) +// } +// if res.Flags != bt.expected.Flags { +// t.Fatalf("%s Flags mismatch:got %d,want %d\n", bt.name, res.Flags, bt.expected.Flags) +// } +// } +// } + +// // Char's Default Type in macos is signed char & in linux is unsigned char +// // So we only confirm the char's kind is char & flags is unsigned or signed +// func TestChar(t *testing.T) { +// typ, index, transunit := GetType(&GetTypeOptions{ +// TypeCode: "char", +// IsCpp: false, +// }) +// converter := &parser.Converter{} +// expr := converter.ProcessType(typ) +// if btType, ok := expr.(*ast.BuiltinType); ok { +// if btType.Kind == ast.Char { +// if btType.Flags != ast.Signed && btType.Flags != ast.Unsigned { +// t.Fatal("Char's flags is not signed or unsigned") +// } +// } +// } else { +// t.Fatal("Char's expr is not a builtin type") +// } +// index.Dispose() +// transunit.Dispose() +// } + +// type GetTypeOptions struct { +// TypeCode string // e.g. "char*", "char**" + +// // ExpectTypeKind specifies the expected type kind (optional) +// // Use clang.Type_Invalid to accept any type (default behavior) +// // *For complex types (when is included), specifying this is crucial +// // to filter out the correct type, as there will be multiple VarDecl fields present +// ExpectTypeKind clang.TypeKind + +// // Args contains additional compilation arguments passed to Clang (optional) +// // These are appended after the default language-specific arguments +// // Example: []string{"-std=c++11"} +// Args []string + +// // IsCpp indicates whether the code should be treated as C++ (true) or C (false) +// // This affects the default language arguments passed to Clang: +// // - For C++: []string{"-x", "c++"} +// // - For C: []string{"-x", "c"} +// // *For complex C types, C Must be specified +// IsCpp bool +// } + +// // GetType returns the clang.Type of the given type code +// // Need to dispose the index and unit after using +// // e.g. index.Dispose(), unit.Dispose() +// func GetType(option *GetTypeOptions) (clang.Type, *clang.Index, *clang.TranslationUnit) { +// code := fmt.Sprintf("%s placeholder;", option.TypeCode) +// index, unit, err := clangutils.CreateTranslationUnit(&clangutils.Config{ +// File: code, +// Temp: true, +// Args: option.Args, +// IsCpp: option.IsCpp, +// }) +// if err != nil { +// panic(err) +// } +// cursor := unit.Cursor() +// var typ clang.Type +// clangutils.VisitChildren(cursor, func(child, parent clang.Cursor) clang.ChildVisitResult { +// if child.Kind == clang.CursorVarDecl && (option.ExpectTypeKind == clang.TypeInvalid || option.ExpectTypeKind == child.Type().Kind) { +// typ = child.Type() +// return clang.ChildVisit_Break +// } +// return clang.ChildVisit_Continue +// }) +// return typ, index, unit +// } + +// func btType(kind clang.TypeKind) clang.Type { +// return clang.Type{Kind: kind} +// } + +// // get complex type from source code parsed +// func getComplexType(flag ast.TypeFlag) clang.Type { +// var typeStr string +// if flag&(ast.Long|ast.Double) == (ast.Long | ast.Double) { +// typeStr = "long double" +// } else if flag&ast.Double != 0 { +// typeStr = "double" +// } else { +// typeStr = "float" +// } + +// code := fmt.Sprintf("#include \n%s complex", typeStr) + +// // todo(zzy):free index and unit after test +// typ, _, _ := GetType(&GetTypeOptions{ +// TypeCode: code, +// ExpectTypeKind: clang.TypeComplex, +// IsCpp: false, +// }) + +// return typ +// } + +// func TestPreprocess(t *testing.T) { +// combinedFile, err := os.CreateTemp("./", "compose_*.h") +// if err != nil { +// panic(err) +// } +// defer os.Remove(combinedFile.Name()) + +// clangtool.ComposeIncludes([]string{"main.h", "compat.h"}, combinedFile.Name()) + +// efile, err := os.CreateTemp("", "temp_*.i") +// if err != nil { +// panic(err) +// } +// defer os.Remove(efile.Name()) + +// ppconf := &preprocessor.Config{ +// Compiler: "clang", +// Flags: []string{"-I./_testdata/hfile"}, +// } +// err = preprocessor.Do(combinedFile.Name(), efile.Name(), ppconf) +// if err != nil { +// t.Fatal(err) +// } + +// config := &clangutils.Config{ +// File: efile.Name(), +// Temp: false, +// IsCpp: false, +// } + +// var str strings.Builder + +// visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { +// switch cursor.Kind { +// case clang.CursorEnumDecl, clang.CursorStructDecl, clang.CursorUnionDecl, clang.CursorTypedefDecl: +// var filename clang.String +// var line, column c.Uint +// cursor.Location().PresumedLocation(&filename, &line, &column) +// str.WriteString("TypeKind: ") +// str.WriteString(clang.GoString(cursor.Kind.String())) +// str.WriteString(" Name: ") +// str.WriteString(clang.GoString(cursor.String())) +// str.WriteString("\n") +// str.WriteString("Location: ") +// str.WriteString(fmt.Sprintf("%s:%d:%d\n", path.Base(c.GoString(filename.CStr())), line, column)) +// } +// return clang.ChildVisit_Continue +// }) + +// expect := ` +// TypeKind: StructDecl Name: A +// Location: main.h:3:16 +// TypeKind: TypedefDecl Name: A +// Location: main.h:6:3 +// TypeKind: TypedefDecl Name: B +// Location: compat.h:3:11 +// TypeKind: TypedefDecl Name: C +// Location: main.h:8:11 +// ` + +// compareOutput(t, expect, str.String()) +// } func visit(config *clangutils.Config, visitFunc func(cursor, parent clang.Cursor) clang.ChildVisitResult) { index, unit, err := clangutils.CreateTranslationUnit(config) @@ -620,49 +634,49 @@ func visit(config *clangutils.Config, visitFunc func(cursor, parent clang.Cursor unit.Dispose() } -func compareOutput(t *testing.T, expected, actual string) { - expected = strings.TrimSpace(expected) - actual = strings.TrimSpace(actual) - if expected != actual { - t.Fatalf("Test failed: expected \n%s \ngot \n%s", expected, actual) - } -} - -func TestPostOrderVisitChildren(t *testing.T) { - config := &clangutils.Config{ - File: "./testdata/named_nested_struct/temp.h", - Temp: false, - IsCpp: false, - } - - name := make(map[string]bool) - visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { - if cursor.Kind == clang.CursorStructDecl { - if !name[clang.GoString(cursor.String())] { - name[clang.GoString(cursor.String())] = true - file, line, column := clangutils.GetPresumedLocation(cursor.Location()) - fmt.Println("StructDecl Name:", clang.GoString(cursor.String()), file, line, column) - } - } - return clang.ChildVisit_Recurse - }) - - index, unit, err := clangutils.CreateTranslationUnit(config) - if err != nil { - panic(err) - } - defer index.Dispose() - defer unit.Dispose() - - childStr := make([]string, 6) - childs := parser.PostOrderVisitChildren(unit.Cursor(), func(child, parent clang.Cursor) bool { - return child.Kind == clang.CursorStructDecl - }) - for i, child := range childs { - childStr[i] = clang.GoString(child.String()) - } - expect := []string{"c", "d", "b", "f", "e", "a"} - if !reflect.DeepEqual(expect, childStr) { - fmt.Println("Unexpected child order:", childStr) - } -} +// func compareOutput(t *testing.T, expected, actual string) { +// expected = strings.TrimSpace(expected) +// actual = strings.TrimSpace(actual) +// if expected != actual { +// t.Fatalf("Test failed: expected \n%s \ngot \n%s", expected, actual) +// } +// } + +// func TestPostOrderVisitChildren(t *testing.T) { +// config := &clangutils.Config{ +// File: "./testdata/named_nested_struct/temp.h", +// Temp: false, +// IsCpp: false, +// } + +// name := make(map[string]bool) +// visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { +// if cursor.Kind == clang.CursorStructDecl { +// if !name[clang.GoString(cursor.String())] { +// name[clang.GoString(cursor.String())] = true +// file, line, column := clangutils.GetPresumedLocation(cursor.Location()) +// fmt.Println("StructDecl Name:", clang.GoString(cursor.String()), file, line, column) +// } +// } +// return clang.ChildVisit_Recurse +// }) + +// index, unit, err := clangutils.CreateTranslationUnit(config) +// if err != nil { +// panic(err) +// } +// defer index.Dispose() +// defer unit.Dispose() + +// childStr := make([]string, 6) +// childs := parser.PostOrderVisitChildren(unit.Cursor(), func(child, parent clang.Cursor) bool { +// return child.Kind == clang.CursorStructDecl +// }) +// for i, child := range childs { +// childStr[i] = clang.GoString(child.String()) +// } +// expect := []string{"c", "d", "b", "f", "e", "a"} +// if !reflect.DeepEqual(expect, childStr) { +// fmt.Println("Unexpected child order:", childStr) +// } +// } diff --git a/_xtool/internal/parser/testdata/struct/temp.h b/_xtool/internal/parser/testdata/struct/temp.h index 52302512..bda268fc 100644 --- a/_xtool/internal/parser/testdata/struct/temp.h +++ b/_xtool/internal/parser/testdata/struct/temp.h @@ -1,23 +1,29 @@ -struct { +struct +{ int a; }; -struct Foo1 { +struct Foo1 +{ int a; int b; }; -struct Foo2 { +struct Foo2 +{ int a, b; }; -struct Foo3 { +struct Foo3 +{ int a; int (*Foo)(int, int); }; -struct Person { +struct Person +{ int age; - struct { + struct + { int year; int day; int month; diff --git a/cl/internal/convert/_testdata/enum/hfile/temp.h b/cl/internal/convert/_testdata/enum/hfile/temp.h index bf448db1..0cbf1f33 100644 --- a/cl/internal/convert/_testdata/enum/hfile/temp.h +++ b/cl/internal/convert/_testdata/enum/hfile/temp.h @@ -1,28 +1,64 @@ -enum { enum1, enum2 }; +enum +{ + enum1, + enum2 +}; -enum { COLOR_DEFAULT = -1 }; +enum +{ + COLOR_DEFAULT = -1 +}; -enum spectrum { red, orange, yello, green, blue, violet }; +enum spectrum +{ + red, + orange, + yello, + green, + blue, + violet +}; -enum kids { nippy, slats, skippy, nina, liz }; +enum kids +{ + nippy, + slats, + skippy, + nina, + liz +}; -enum levels { low = 100, medium = 500, high = 2000 }; +enum levels +{ + low = 100, + medium = 500, + high = 2000 +}; -enum feline { cat, lynx = 10, puma, tiger }; +enum feline +{ + cat, + lynx = 10, + puma, + tiger +}; -typedef enum algorithm { +typedef enum algorithm +{ UNKNOWN = 0, NULL = 1, } algorithm_t; -typedef enum { +typedef enum +{ UNKNOWN2 = 0, NULL2 = 1, } algorithm_t2; typedef algorithm_t algorithm; -typedef enum { +typedef enum +{ GPG_ERR_NO_ERROR = 0, GPG_ERR_GENERAL = 1, GPG_ERR_UNKNOWN_PACKET = 2, diff --git a/cl/internal/convert/_testdata/nested/gogensig.expect b/cl/internal/convert/_testdata/nested/gogensig.expect index b7736df2..b6a55495 100644 --- a/cl/internal/convert/_testdata/nested/gogensig.expect +++ b/cl/internal/convert/_testdata/nested/gogensig.expect @@ -83,7 +83,12 @@ type A struct { EField E } +type NestedEnum struct { + Unused [8]uint8 +} + ===== llcppg.pub ===== +NestedEnum a A b B c C diff --git a/cl/internal/convert/_testdata/nested/hfile/temp.h b/cl/internal/convert/_testdata/nested/hfile/temp.h index 570de213..d03f1758 100644 --- a/cl/internal/convert/_testdata/nested/hfile/temp.h +++ b/cl/internal/convert/_testdata/nested/hfile/temp.h @@ -10,11 +10,12 @@ struct struct1 } init; }; - // https://github.com/goplus/llcppg/issues/514 // named nested struct -struct struct_with_nested { - struct inner_struct { +struct struct_with_nested +{ + struct inner_struct + { long l; } init; }; @@ -58,20 +59,75 @@ union union2 } init; }; - // https://github.com/goplus/llcppg/issues/514 -struct a { - struct b { - struct c { +struct a +{ + struct b + { + struct c + { int a; } c_field; - struct d { + struct d + { int b; } d_field; } b_field; - struct e { - struct f { + struct e + { + struct f + { int b; } f_field; } e_field; }; +struct NestedEnum +{ + enum + { + APR_BUCKET_DATA1 = 0, + APR_BUCKET_METADATA2 = 1 + } is_metadata1; + + struct a + { + int b; + } a_t; +}; + +struct NestedEnum2 +{ + enum + { + APR_BUCKET_DATA3 = 0, + APR_BUCKET_METADATA4 = 1 + }; +}; + +struct NestedEnum3 +{ + enum is_metadata3 + { + APR_BUCKET_DATA5 = 0, + APR_BUCKET_METADATA6 = 1 + }; +}; + +struct NestedEnum4 +{ + enum is_metadata4 + { + APR_BUCKET_DATA7 = 0, + APR_BUCKET_METADATA8 = 1 + } key; +}; + +enum OuterEnum +{ + APR_BUCKET_DATA9 = 0, + APR_BUCKET_METADATA10 = 1 +}; +struct Enum +{ + enum OuterEnum k; +}; diff --git a/cl/internal/convert/convert.go b/cl/internal/convert/convert.go index 4e5a1930..5c0e8c4b 100644 --- a/cl/internal/convert/convert.go +++ b/cl/internal/convert/convert.go @@ -125,6 +125,7 @@ func (p *Converter) Process() error { return fmt.Errorf("ConvDecl: %w", err) } ctx.setGoFile(goFile) + switch decl := decl.(type) { case *ast.TypeDecl: err = ctx.NewTypeDecl(goName, decl, pnc) diff --git a/cl/internal/convert/convert_test.go b/cl/internal/convert/convert_test.go index 73a7d3d3..5fc336c8 100644 --- a/cl/internal/convert/convert_test.go +++ b/cl/internal/convert/convert_test.go @@ -25,7 +25,7 @@ func init() { } func TestFromTestdata(t *testing.T) { - testFromDir(t, "./_testdata", false) + testFromDir(t, "./_testdata", true) } func TestDepWithVersion(t *testing.T) { From fe652c3d8b7534559e066016d4efae365e078d05 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 14:12:32 +0800 Subject: [PATCH 08/25] feat: support nested enum --- _xtool/internal/parser/parser.go | 29 +++++-- _xtool/internal/parser/parser_test.go | 16 ++-- _xtool/internal/parser/testdata/struct/temp.h | 55 +++++++++---- .../convert/_testdata/enum/hfile/temp.h | 54 ++---------- .../convert/_testdata/nested/gogensig.expect | 5 -- .../convert/_testdata/nested/hfile/temp.h | 76 +++-------------- .../_testdata/nestedenum/conf/llcppg.cfg | 7 ++ .../_testdata/nestedenum/gogensig.expect | 82 +++++++++++++++++++ .../convert/_testdata/nestedenum/hfile/temp.h | 54 ++++++++++++ cl/internal/convert/type.go | 16 ++-- 10 files changed, 238 insertions(+), 156 deletions(-) create mode 100644 cl/internal/convert/_testdata/nestedenum/conf/llcppg.cfg create mode 100644 cl/internal/convert/_testdata/nestedenum/gogensig.expect create mode 100644 cl/internal/convert/_testdata/nestedenum/hfile/temp.h diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index f2069c63..29b2ef4c 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -882,21 +882,39 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { if decl.IsAnonymous() != 0 && decl.Kind != clang.CursorEnumDecl { return ct.ProcessRecordType(decl) } + parts := clangutils.BuildScopingParts(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 decl.Kind == clang.CursorEnumDecl { + // by default, the type of an anonymous enum is int + if decl.IsAnonymous() > 0 { + return &ast.TagExpr{ + Tag: ast.Enum, + Name: &ast.BuiltinType{Kind: ast.Int}, + } + } + return &ast.TagExpr{ + Tag: ast.Enum, + // for typedef enum + Name: &ast.Ident{Name: parts[0]}, + } + } // for elaborated type, it could have a tag description // like struct A, union B, class C, enum D - parts := strings.SplitN(typeName, " ", 2) + typeParts := strings.SplitN(typeName, " ", 2) - if len(parts) == 2 { - if tagValue, ok := tagMap[parts[0]]; ok { + 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 { @@ -1042,7 +1060,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{ diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 1051d52c..7ba3b878 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -27,14 +27,14 @@ func TestParserCppMode(t *testing.T) { // } } -func TestParserCMode(t *testing.T) { - cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct"} - for _, folder := range cases { - t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) - }) - } -} +// func TestParserCMode(t *testing.T) { +// cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct"} +// for _, folder := range cases { +// t.Run(folder, func(t *testing.T) { +// testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) +// }) +// } +// } // func TestParserCMode(t *testing.T) { // cases := []string{"named_nested_struct"} diff --git a/_xtool/internal/parser/testdata/struct/temp.h b/_xtool/internal/parser/testdata/struct/temp.h index bda268fc..21e353fb 100644 --- a/_xtool/internal/parser/testdata/struct/temp.h +++ b/_xtool/internal/parser/testdata/struct/temp.h @@ -1,31 +1,50 @@ -struct +struct NestedEnum { - int a; + enum + { + APR_BUCKET_DATA1 = 0, + APR_BUCKET_METADATA2 = 1 + } is_metadata1_t; + + struct a + { + int b; + } a_t; }; -struct Foo1 +struct NestedEnum2 { - int a; - int b; + enum + { + APR_BUCKET_DATA3 = 0, + APR_BUCKET_METADATA4 = 1 + }; }; -struct Foo2 + +struct NestedEnum3 { - int a, b; + enum is_metadata3 + { + APR_BUCKET_DATA5 = 0, + APR_BUCKET_METADATA6 = 1 + }; }; -struct Foo3 +struct NestedEnum4 { - int a; - int (*Foo)(int, int); + enum is_metadata4 + { + APR_BUCKET_DATA7 = 0, + APR_BUCKET_METADATA8 = 1 + } key; }; -struct Person +enum OuterEnum { - int age; - struct - { - int year; - int day; - int month; - } birthday; + APR_BUCKET_DATA9 = 0, + APR_BUCKET_METADATA10 = 1 +}; +struct Enum +{ + enum OuterEnum k; }; diff --git a/cl/internal/convert/_testdata/enum/hfile/temp.h b/cl/internal/convert/_testdata/enum/hfile/temp.h index 0cbf1f33..bf448db1 100644 --- a/cl/internal/convert/_testdata/enum/hfile/temp.h +++ b/cl/internal/convert/_testdata/enum/hfile/temp.h @@ -1,64 +1,28 @@ -enum -{ - enum1, - enum2 -}; +enum { enum1, enum2 }; -enum -{ - COLOR_DEFAULT = -1 -}; +enum { COLOR_DEFAULT = -1 }; -enum spectrum -{ - red, - orange, - yello, - green, - blue, - violet -}; +enum spectrum { red, orange, yello, green, blue, violet }; -enum kids -{ - nippy, - slats, - skippy, - nina, - liz -}; +enum kids { nippy, slats, skippy, nina, liz }; -enum levels -{ - low = 100, - medium = 500, - high = 2000 -}; +enum levels { low = 100, medium = 500, high = 2000 }; -enum feline -{ - cat, - lynx = 10, - puma, - tiger -}; +enum feline { cat, lynx = 10, puma, tiger }; -typedef enum algorithm -{ +typedef enum algorithm { UNKNOWN = 0, NULL = 1, } algorithm_t; -typedef enum -{ +typedef enum { UNKNOWN2 = 0, NULL2 = 1, } algorithm_t2; typedef algorithm_t algorithm; -typedef enum -{ +typedef enum { GPG_ERR_NO_ERROR = 0, GPG_ERR_GENERAL = 1, GPG_ERR_UNKNOWN_PACKET = 2, diff --git a/cl/internal/convert/_testdata/nested/gogensig.expect b/cl/internal/convert/_testdata/nested/gogensig.expect index b6a55495..b7736df2 100644 --- a/cl/internal/convert/_testdata/nested/gogensig.expect +++ b/cl/internal/convert/_testdata/nested/gogensig.expect @@ -83,12 +83,7 @@ type A struct { EField E } -type NestedEnum struct { - Unused [8]uint8 -} - ===== llcppg.pub ===== -NestedEnum a A b B c C diff --git a/cl/internal/convert/_testdata/nested/hfile/temp.h b/cl/internal/convert/_testdata/nested/hfile/temp.h index d03f1758..570de213 100644 --- a/cl/internal/convert/_testdata/nested/hfile/temp.h +++ b/cl/internal/convert/_testdata/nested/hfile/temp.h @@ -10,12 +10,11 @@ struct struct1 } init; }; + // https://github.com/goplus/llcppg/issues/514 // named nested struct -struct struct_with_nested -{ - struct inner_struct - { +struct struct_with_nested { + struct inner_struct { long l; } init; }; @@ -59,75 +58,20 @@ union union2 } init; }; + // https://github.com/goplus/llcppg/issues/514 -struct a -{ - struct b - { - struct c - { +struct a { + struct b { + struct c { int a; } c_field; - struct d - { + struct d { int b; } d_field; } b_field; - struct e - { - struct f - { + struct e { + struct f { int b; } f_field; } e_field; }; -struct NestedEnum -{ - enum - { - APR_BUCKET_DATA1 = 0, - APR_BUCKET_METADATA2 = 1 - } is_metadata1; - - struct a - { - int b; - } a_t; -}; - -struct NestedEnum2 -{ - enum - { - APR_BUCKET_DATA3 = 0, - APR_BUCKET_METADATA4 = 1 - }; -}; - -struct NestedEnum3 -{ - enum is_metadata3 - { - APR_BUCKET_DATA5 = 0, - APR_BUCKET_METADATA6 = 1 - }; -}; - -struct NestedEnum4 -{ - enum is_metadata4 - { - APR_BUCKET_DATA7 = 0, - APR_BUCKET_METADATA8 = 1 - } key; -}; - -enum OuterEnum -{ - APR_BUCKET_DATA9 = 0, - APR_BUCKET_METADATA10 = 1 -}; -struct Enum -{ - enum OuterEnum k; -}; diff --git a/cl/internal/convert/_testdata/nestedenum/conf/llcppg.cfg b/cl/internal/convert/_testdata/nestedenum/conf/llcppg.cfg new file mode 100644 index 00000000..e54e679f --- /dev/null +++ b/cl/internal/convert/_testdata/nestedenum/conf/llcppg.cfg @@ -0,0 +1,7 @@ +{ + "name": "nestedenum", + "include": ["temp.h"], + "cplusplus":false, + "libs": "$(pkg-config --libs xxx)", + "trimPrefixes": [] +} diff --git a/cl/internal/convert/_testdata/nestedenum/gogensig.expect b/cl/internal/convert/_testdata/nestedenum/gogensig.expect new file mode 100644 index 00000000..5e3336d1 --- /dev/null +++ b/cl/internal/convert/_testdata/nestedenum/gogensig.expect @@ -0,0 +1,82 @@ +===== nestedenum_autogen_link.go ===== +package nestedenum + +import _ "github.com/goplus/lib/c" + +const LLGoPackage string = "link: $(pkg-config --libs xxx);" + +===== temp.go ===== +package nestedenum + +import ( + "github.com/goplus/lib/c" + _ "unsafe" +) + +const ( + APR_BUCKET_DATA1 c.Int = 0 + APR_BUCKET_METADATA2 c.Int = 1 +) +const ( + APR_BUCKET_DATA_A1 c.Int = 0 + APR_BUCKET_METADATA_A2 c.Int = 1 +) + +type A struct { + IsMetadata1T c.Int +} + +type NestedEnum struct { + IsMetadata1T c.Int + AT A +} + +const ( + APR_BUCKET_DATA3 c.Int = 0 + APR_BUCKET_METADATA4 c.Int = 1 +) + +type NestedEnum2 struct { + Unused [8]uint8 +} +type IsMetadata3 c.Int + +const ( + APR_BUCKET_DATA5 IsMetadata3 = 0 + APR_BUCKET_METADATA6 IsMetadata3 = 1 +) + +type NestedEnum3 struct { + Unused [8]uint8 +} +type IsMetadata4 c.Int + +const ( + APR_BUCKET_DATA7 IsMetadata4 = 0 + APR_BUCKET_METADATA8 IsMetadata4 = 1 +) + +type NestedEnum4 struct { + Key IsMetadata4 +} +type OuterEnum c.Int + +const ( + APR_BUCKET_DATA9 OuterEnum = 0 + APR_BUCKET_METADATA10 OuterEnum = 1 +) + +type Enum struct { + K OuterEnum +} + +===== llcppg.pub ===== +Enum +NestedEnum +NestedEnum2 +NestedEnum3 +NestedEnum4 +OuterEnum +a A +is_metadata3 IsMetadata3 +is_metadata4 IsMetadata4 \ No newline at end of file diff --git a/cl/internal/convert/_testdata/nestedenum/hfile/temp.h b/cl/internal/convert/_testdata/nestedenum/hfile/temp.h new file mode 100644 index 00000000..4fbcce95 --- /dev/null +++ b/cl/internal/convert/_testdata/nestedenum/hfile/temp.h @@ -0,0 +1,54 @@ +struct NestedEnum +{ + enum + { + APR_BUCKET_DATA1 = 0, + APR_BUCKET_METADATA2 = 1 + } is_metadata1_t; + + struct a + { + enum + { + APR_BUCKET_DATA_A1 = 0, + APR_BUCKET_METADATA_A2 = 1 + } is_metadata1_t; + } a_t; +}; + +struct NestedEnum2 +{ + enum + { + APR_BUCKET_DATA3 = 0, + APR_BUCKET_METADATA4 = 1 + }; +}; + +struct NestedEnum3 +{ + enum is_metadata3 + { + APR_BUCKET_DATA5 = 0, + APR_BUCKET_METADATA6 = 1 + }; +}; + +struct NestedEnum4 +{ + enum is_metadata4 + { + APR_BUCKET_DATA7 = 0, + APR_BUCKET_METADATA8 = 1 + } key; +}; + +enum OuterEnum +{ + APR_BUCKET_DATA9 = 0, + APR_BUCKET_METADATA10 = 1 +}; +struct Enum +{ + enum OuterEnum k; +}; diff --git a/cl/internal/convert/type.go b/cl/internal/convert/type.go index 7bd86c11..9084490a 100644 --- a/cl/internal/convert/type.go +++ b/cl/internal/convert/type.go @@ -159,15 +159,15 @@ func (p *TypeConv) handleIdentRefer(t ast.Expr) (types.Type, error) { case *ast.ScopingExpr: // todo(zzy) case *ast.TagExpr: - // todo(zzy):scoping - if ident, ok := t.Name.(*ast.Ident); ok { - typ, err := lookup(ident.Name) - if err != nil { - return nil, err - } - return typ, nil - } // todo(zzy):scoping expr + // FIXME(MeteorsLiu): when we support more tag type in the future, + // we need to split this logic into a function for readability + switch nameType := t.Name.(type) { + case *ast.Ident: + return lookup(nameType.Name) + case *ast.BuiltinType: + return p.typeMap.FindBuiltinType(*nameType) + } } return nil, fmt.Errorf("unsupported refer type %T", t) } From dcb330a3cee77817dd927dcdb45466c10147f0fb Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 14:15:24 +0800 Subject: [PATCH 09/25] test: revert struct test in parser --- _xtool/internal/parser/testdata/struct/temp.h | 59 ++++++------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/_xtool/internal/parser/testdata/struct/temp.h b/_xtool/internal/parser/testdata/struct/temp.h index 21e353fb..52302512 100644 --- a/_xtool/internal/parser/testdata/struct/temp.h +++ b/_xtool/internal/parser/testdata/struct/temp.h @@ -1,50 +1,25 @@ -struct NestedEnum -{ - enum - { - APR_BUCKET_DATA1 = 0, - APR_BUCKET_METADATA2 = 1 - } is_metadata1_t; - - struct a - { - int b; - } a_t; +struct { + int a; }; -struct NestedEnum2 -{ - enum - { - APR_BUCKET_DATA3 = 0, - APR_BUCKET_METADATA4 = 1 - }; +struct Foo1 { + int a; + int b; }; - -struct NestedEnum3 -{ - enum is_metadata3 - { - APR_BUCKET_DATA5 = 0, - APR_BUCKET_METADATA6 = 1 - }; +struct Foo2 { + int a, b; }; -struct NestedEnum4 -{ - enum is_metadata4 - { - APR_BUCKET_DATA7 = 0, - APR_BUCKET_METADATA8 = 1 - } key; +struct Foo3 { + int a; + int (*Foo)(int, int); }; -enum OuterEnum -{ - APR_BUCKET_DATA9 = 0, - APR_BUCKET_METADATA10 = 1 -}; -struct Enum -{ - enum OuterEnum k; +struct Person { + int age; + struct { + int year; + int day; + int month; + } birthday; }; From 5d01e11949484c7d374c2070cdcafb351032dc48 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 14:16:40 +0800 Subject: [PATCH 10/25] test: disable gen for convert test --- cl/internal/convert/convert_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cl/internal/convert/convert_test.go b/cl/internal/convert/convert_test.go index 5fc336c8..73a7d3d3 100644 --- a/cl/internal/convert/convert_test.go +++ b/cl/internal/convert/convert_test.go @@ -25,7 +25,7 @@ func init() { } func TestFromTestdata(t *testing.T) { - testFromDir(t, "./_testdata", true) + testFromDir(t, "./_testdata", false) } func TestDepWithVersion(t *testing.T) { From 98ced8aa44ca8d32ec9161d8c8ebe6540ffb6922 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 14:58:18 +0800 Subject: [PATCH 11/25] fix: four enum cases --- _xtool/internal/parser/parser.go | 18 ++++++++++++++---- _xtool/internal/parser/parser_test.go | 9 +++++++++ _xtool/internal/parser/testdata/enum/temp.h | 12 ++++++++---- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 29b2ef4c..0fc4d4ff 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -878,27 +878,37 @@ 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 && decl.Kind != clang.CursorEnumDecl { + if isAnonymousDecl && decl.Kind != clang.CursorEnumDecl { return ct.ProcessRecordType(decl) } parts := clangutils.BuildScopingParts(decl) + hasParent := len(parts) > 1 // 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 decl.Kind == clang.CursorEnumDecl { - // by default, the type of an anonymous enum is int - if decl.IsAnonymous() > 0 { + if (hasParent || isAnonymousDecl) && decl.Kind == clang.CursorEnumDecl { + // case 1: anonymous enum, but not nested + if !hasParent { + // this is not a nested enum, handle it normally + return ct.ProcessEnumType(decl) + } + // case 2: anonymous enum, nested + if isAnonymousDecl { + // by default, the type of an anonymous enum is int return &ast.TagExpr{ Tag: ast.Enum, Name: &ast.BuiltinType{Kind: ast.Int}, } } + // case3: named enum, nested return &ast.TagExpr{ Tag: ast.Enum, // for typedef enum Name: &ast.Ident{Name: parts[0]}, } + // case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. } // for elaborated type, it could have a tag description diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index f5324657..9beb696b 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -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)", diff --git a/_xtool/internal/parser/testdata/enum/temp.h b/_xtool/internal/parser/testdata/enum/temp.h index 4a4ba934..8e90dd58 100644 --- a/_xtool/internal/parser/testdata/enum/temp.h +++ b/_xtool/internal/parser/testdata/enum/temp.h @@ -1,20 +1,24 @@ -enum { +enum +{ a, b, c, }; -enum Foo1 { +enum Foo1 +{ Foo1a, Foo1b, Foo1c, }; -enum Foo2 { +enum Foo2 +{ Foo2a = 1, Foo2b = 2, Foo2c = 4, }; -enum Foo3 { +enum Foo3 +{ Foo3a = 1, Foo3b, Foo3c, From f8956e764cb6ce1473ffd3978f517c589d527999 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 16:37:50 +0800 Subject: [PATCH 12/25] fix: use builtin type directly for anonymous enum --- _xtool/internal/parser/parser.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index e1479ac5..e1ae1cb3 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -897,10 +897,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { // case 2: anonymous enum, nested if isAnonymousDecl { // by default, the type of an anonymous enum is int - return &ast.TagExpr{ - Tag: ast.Enum, - Name: &ast.BuiltinType{Kind: ast.Int}, - } + return &ast.BuiltinType{Kind: ast.Int} } // case3: named enum, nested return &ast.TagExpr{ From 451d919036fbe8e892514ef42e3c68c7c6fd2dd2 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 16:38:56 +0800 Subject: [PATCH 13/25] chore: add FIXME --- cl/internal/convert/type.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cl/internal/convert/type.go b/cl/internal/convert/type.go index 9084490a..25f6d4f1 100644 --- a/cl/internal/convert/type.go +++ b/cl/internal/convert/type.go @@ -165,8 +165,6 @@ func (p *TypeConv) handleIdentRefer(t ast.Expr) (types.Type, error) { switch nameType := t.Name.(type) { case *ast.Ident: return lookup(nameType.Name) - case *ast.BuiltinType: - return p.typeMap.FindBuiltinType(*nameType) } } return nil, fmt.Errorf("unsupported refer type %T", t) From 4a2aeb53caa0123dfed9fe257dc852f182dc1fe4 Mon Sep 17 00:00:00 2001 From: Haolan Date: Tue, 5 Aug 2025 16:39:33 +0800 Subject: [PATCH 14/25] chore: remove newline --- cl/internal/convert/convert.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cl/internal/convert/convert.go b/cl/internal/convert/convert.go index 5c0e8c4b..4e5a1930 100644 --- a/cl/internal/convert/convert.go +++ b/cl/internal/convert/convert.go @@ -125,7 +125,6 @@ func (p *Converter) Process() error { return fmt.Errorf("ConvDecl: %w", err) } ctx.setGoFile(goFile) - switch decl := decl.(type) { case *ast.TypeDecl: err = ctx.NewTypeDecl(goName, decl, pnc) From 0ec5a61bf149d88f47d3f70292c6764234dd2046 Mon Sep 17 00:00:00 2001 From: Haolan Date: Fri, 15 Aug 2025 15:45:26 +0800 Subject: [PATCH 15/25] fix: merge error --- _xtool/internal/parser/parser.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 182db182..5c8f4501 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -769,18 +769,17 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl { }) 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) // 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 && len(typ.Fields.List) > 0 { + if child.IsAnonymous() == 0 && typ.Fields != nil { decls = append(decls, &ast.TypeDecl{ Object: ct.CreateObject(child, &ast.Ident{Name: childName}), Type: ct.ProcessRecordType(child), From ec6f297effb6cb8c92922c1adfd52b5426bd06a6 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 10:00:53 +0800 Subject: [PATCH 16/25] revert temp.h --- _xtool/internal/parser/testdata/enum/temp.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/_xtool/internal/parser/testdata/enum/temp.h b/_xtool/internal/parser/testdata/enum/temp.h index 8e90dd58..4a4ba934 100644 --- a/_xtool/internal/parser/testdata/enum/temp.h +++ b/_xtool/internal/parser/testdata/enum/temp.h @@ -1,24 +1,20 @@ -enum -{ +enum { a, b, c, }; -enum Foo1 -{ +enum Foo1 { Foo1a, Foo1b, Foo1c, }; -enum Foo2 -{ +enum Foo2 { Foo2a = 1, Foo2b = 2, Foo2c = 4, }; -enum Foo3 -{ +enum Foo3 { Foo3a = 1, Foo3b, Foo3c, From ad3208ee142ec8e9ca2ad2e4410291f3262a7680 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 10:14:32 +0800 Subject: [PATCH 17/25] test: update testdata --- _xtool/internal/parser/parser_test.go | 5 +- .../parser/testdata/nestedenum/expect.json | 517 ++++++++++++++++++ .../parser/testdata/nestedenum/temp.h | 54 ++ .../_testdata/nestedenum/gogensig.expect | 2 - 4 files changed, 574 insertions(+), 4 deletions(-) create mode 100755 _xtool/internal/parser/testdata/nestedenum/expect.json create mode 100644 _xtool/internal/parser/testdata/nestedenum/temp.h diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 51873b6c..56e55065 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -20,12 +20,13 @@ 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"} // https://github.com/goplus/llgo/issues/1114 // todo(zzy):use os.ReadDir + cases := []string{"nestedenum"} for _, folder := range cases { t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", true, false) + testFrom(t, filepath.Join("testdata", folder), "temp.h", true, true) }) } } diff --git a/_xtool/internal/parser/testdata/nestedenum/expect.json b/_xtool/internal/parser/testdata/nestedenum/expect.json new file mode 100755 index 00000000..3c437df8 --- /dev/null +++ b/_xtool/internal/parser/testdata/nestedenum/expect.json @@ -0,0 +1,517 @@ +{ + "_Type": "File", + "decls": [ + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA1", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA2", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "X": { + "Name": "a", + "_Type": "Ident" + }, + "_Type": "ScopingExpr" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA_A1", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA_A2", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "a", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "is_metadata1_t", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "is_metadata1_t", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + }, + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "a_t", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "X": { + "Name": "a", + "_Type": "Ident" + }, + "_Type": "ScopingExpr" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Name": "NestedEnum2", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA3", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA4", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum2", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": null, + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "is_metadata3", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum3", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA5", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA6", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum3", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": null, + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "is_metadata4", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA7", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA8", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "key", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "Tag": 2, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "OuterEnum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA9", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA10", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "Enum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "k", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "OuterEnum", + "_Type": "Ident" + }, + "Tag": 2, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + } + ], + "includes": null, + "macros": null +} \ No newline at end of file diff --git a/_xtool/internal/parser/testdata/nestedenum/temp.h b/_xtool/internal/parser/testdata/nestedenum/temp.h new file mode 100644 index 00000000..4fbcce95 --- /dev/null +++ b/_xtool/internal/parser/testdata/nestedenum/temp.h @@ -0,0 +1,54 @@ +struct NestedEnum +{ + enum + { + APR_BUCKET_DATA1 = 0, + APR_BUCKET_METADATA2 = 1 + } is_metadata1_t; + + struct a + { + enum + { + APR_BUCKET_DATA_A1 = 0, + APR_BUCKET_METADATA_A2 = 1 + } is_metadata1_t; + } a_t; +}; + +struct NestedEnum2 +{ + enum + { + APR_BUCKET_DATA3 = 0, + APR_BUCKET_METADATA4 = 1 + }; +}; + +struct NestedEnum3 +{ + enum is_metadata3 + { + APR_BUCKET_DATA5 = 0, + APR_BUCKET_METADATA6 = 1 + }; +}; + +struct NestedEnum4 +{ + enum is_metadata4 + { + APR_BUCKET_DATA7 = 0, + APR_BUCKET_METADATA8 = 1 + } key; +}; + +enum OuterEnum +{ + APR_BUCKET_DATA9 = 0, + APR_BUCKET_METADATA10 = 1 +}; +struct Enum +{ + enum OuterEnum k; +}; diff --git a/cl/internal/convert/_testdata/nestedenum/gogensig.expect b/cl/internal/convert/_testdata/nestedenum/gogensig.expect index 5e3336d1..872aae02 100644 --- a/cl/internal/convert/_testdata/nestedenum/gogensig.expect +++ b/cl/internal/convert/_testdata/nestedenum/gogensig.expect @@ -37,7 +37,6 @@ const ( ) type NestedEnum2 struct { - Unused [8]uint8 } type IsMetadata3 c.Int @@ -47,7 +46,6 @@ const ( ) type NestedEnum3 struct { - Unused [8]uint8 } type IsMetadata4 c.Int From be526e4d930fc8ca264e1124178fce2701df073c Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 10:18:09 +0800 Subject: [PATCH 18/25] test: fix parser test --- _xtool/internal/parser/parser_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 56e55065..4db223d7 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -20,19 +20,18 @@ 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"} // https://github.com/goplus/llgo/issues/1114 // todo(zzy):use os.ReadDir - cases := []string{"nestedenum"} for _, folder := range cases { t.Run(folder, func(t *testing.T) { - testFrom(t, filepath.Join("testdata", folder), "temp.h", true, true) + testFrom(t, filepath.Join("testdata", folder), "temp.h", true, false) }) } } 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) From 56f3b3c412c4d7a61bdef3f37c1a538055f9c645 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 11:19:53 +0800 Subject: [PATCH 19/25] fix: check hasParent for non-cpp mode --- _xtool/internal/clang/clang.go | 19 + _xtool/internal/parser/parser.go | 5 +- _xtool/internal/parser/parser_test.go | 2 +- .../parser/testdata/nestedenum/expect.json | 39 +- .../testdata/nestedenum_cpp/expect.json | 517 ++++++++++++++++++ .../parser/testdata/nestedenum_cpp/temp.h | 54 ++ 6 files changed, 601 insertions(+), 35 deletions(-) create mode 100755 _xtool/internal/parser/testdata/nestedenum_cpp/expect.json create mode 100644 _xtool/internal/parser/testdata/nestedenum_cpp/temp.h diff --git a/_xtool/internal/clang/clang.go b/_xtool/internal/clang/clang.go index 752462fa..667d3a9b 100644 --- a/_xtool/internal/clang/clang.go +++ b/_xtool/internal/clang/clang.go @@ -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) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 5c8f4501..1fe59556 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -889,8 +889,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { return ct.ProcessRecordType(decl) } parts := clangutils.BuildScopingParts(decl) - hasParent := len(parts) > 1 - + 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 (hasParent || isAnonymousDecl) && decl.Kind == clang.CursorEnumDecl { @@ -908,7 +907,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { return &ast.TagExpr{ Tag: ast.Enum, // for typedef enum - Name: &ast.Ident{Name: parts[0]}, + Name: &ast.Ident{Name: parts[len(parts)-1]}, } // case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. } diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 4db223d7..a1e544cd 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -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", "nestedenum"} + cases := []string{"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 { diff --git a/_xtool/internal/parser/testdata/nestedenum/expect.json b/_xtool/internal/parser/testdata/nestedenum/expect.json index 3c437df8..2fb0b3b4 100755 --- a/_xtool/internal/parser/testdata/nestedenum/expect.json +++ b/_xtool/internal/parser/testdata/nestedenum/expect.json @@ -51,15 +51,8 @@ }, "Name": null, "Parent": { - "Parent": { - "Name": "NestedEnum", - "_Type": "Ident" - }, - "X": { - "Name": "a", - "_Type": "Ident" - }, - "_Type": "ScopingExpr" + "Name": "a", + "_Type": "Ident" }, "Type": { "Items": [ @@ -102,10 +95,7 @@ "Name": "a", "_Type": "Ident" }, - "Parent": { - "Name": "NestedEnum", - "_Type": "Ident" - }, + "Parent": null, "Type": { "Fields": { "List": [ @@ -181,15 +171,8 @@ ], "Type": { "Name": { - "Parent": { - "Name": "NestedEnum", - "_Type": "Ident" - }, - "X": { - "Name": "a", - "_Type": "Ident" - }, - "_Type": "ScopingExpr" + "Name": "a", + "_Type": "Ident" }, "Tag": 0, "_Type": "TagExpr" @@ -279,10 +262,7 @@ "Name": "is_metadata3", "_Type": "Ident" }, - "Parent": { - "Name": "NestedEnum3", - "_Type": "Ident" - }, + "Parent": null, "Type": { "Items": [ { @@ -346,10 +326,7 @@ "Name": "is_metadata4", "_Type": "Ident" }, - "Parent": { - "Name": "NestedEnum4", - "_Type": "Ident" - }, + "Parent": null, "Type": { "Items": [ { @@ -408,7 +385,7 @@ ], "Type": { "Name": { - "Name": "NestedEnum4", + "Name": "is_metadata4", "_Type": "Ident" }, "Tag": 2, diff --git a/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json b/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json new file mode 100755 index 00000000..4c1059f0 --- /dev/null +++ b/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json @@ -0,0 +1,517 @@ +{ + "_Type": "File", + "decls": [ + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA1", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA2", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "X": { + "Name": "a", + "_Type": "Ident" + }, + "_Type": "ScopingExpr" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA_A1", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA_A2", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "a", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "is_metadata1_t", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "is_metadata1_t", + "_Type": "Ident" + } + ], + "Type": { + "Flags": 0, + "Kind": 6, + "_Type": "BuiltinType" + }, + "_Type": "Field" + }, + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "a_t", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Parent": { + "Name": "NestedEnum", + "_Type": "Ident" + }, + "X": { + "Name": "a", + "_Type": "Ident" + }, + "_Type": "ScopingExpr" + }, + "Tag": 0, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": null, + "Parent": { + "Name": "NestedEnum2", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA3", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA4", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum2", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": null, + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "is_metadata3", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum3", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA5", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA6", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum3", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": null, + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "is_metadata4", + "_Type": "Ident" + }, + "Parent": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA7", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA8", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "key", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "is_metadata4", + "_Type": "Ident" + }, + "Tag": 2, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "OuterEnum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Items": [ + { + "Name": { + "Name": "APR_BUCKET_DATA9", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "0", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + }, + { + "Name": { + "Name": "APR_BUCKET_METADATA10", + "_Type": "Ident" + }, + "Value": { + "Kind": 0, + "Value": "1", + "_Type": "BasicLit" + }, + "_Type": "EnumItem" + } + ], + "_Type": "EnumType" + }, + "_Type": "EnumTypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/nestedenum_cpp/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "Enum", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": [ + { + "Access": 1, + "Comment": null, + "Doc": null, + "IsStatic": false, + "Names": [ + { + "Name": "k", + "_Type": "Ident" + } + ], + "Type": { + "Name": { + "Name": "OuterEnum", + "_Type": "Ident" + }, + "Tag": 2, + "_Type": "TagExpr" + }, + "_Type": "Field" + } + ], + "_Type": "FieldList" + }, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + } + ], + "includes": null, + "macros": null +} \ No newline at end of file diff --git a/_xtool/internal/parser/testdata/nestedenum_cpp/temp.h b/_xtool/internal/parser/testdata/nestedenum_cpp/temp.h new file mode 100644 index 00000000..4fbcce95 --- /dev/null +++ b/_xtool/internal/parser/testdata/nestedenum_cpp/temp.h @@ -0,0 +1,54 @@ +struct NestedEnum +{ + enum + { + APR_BUCKET_DATA1 = 0, + APR_BUCKET_METADATA2 = 1 + } is_metadata1_t; + + struct a + { + enum + { + APR_BUCKET_DATA_A1 = 0, + APR_BUCKET_METADATA_A2 = 1 + } is_metadata1_t; + } a_t; +}; + +struct NestedEnum2 +{ + enum + { + APR_BUCKET_DATA3 = 0, + APR_BUCKET_METADATA4 = 1 + }; +}; + +struct NestedEnum3 +{ + enum is_metadata3 + { + APR_BUCKET_DATA5 = 0, + APR_BUCKET_METADATA6 = 1 + }; +}; + +struct NestedEnum4 +{ + enum is_metadata4 + { + APR_BUCKET_DATA7 = 0, + APR_BUCKET_METADATA8 = 1 + } key; +}; + +enum OuterEnum +{ + APR_BUCKET_DATA9 = 0, + APR_BUCKET_METADATA10 = 1 +}; +struct Enum +{ + enum OuterEnum k; +}; From dd3260340319f752d25c9be097fa9e0b1c973bce Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 11:28:28 +0800 Subject: [PATCH 20/25] chore: add disscussion commets --- _xtool/internal/parser/parser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 1fe59556..9be9be94 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -901,6 +901,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { // case 2: anonymous enum, nested if isAnonymousDecl { // 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} } // case3: named enum, nested From 73d3e3117adcee5cbf4180e7a89fdb708704158a Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 11:33:27 +0800 Subject: [PATCH 21/25] test: revert unexpected change --- _xtool/internal/parser/parser_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index a1e544cd..4db223d7 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -20,7 +20,7 @@ import ( ) func TestParserCppMode(t *testing.T) { - cases := []string{"typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "forward_vs_empty", "nestedenum_cpp"} + cases := []string{"class", "comment", "enum", "func", "scope", "struct", "typedef", "union", "macro", "forwarddecl1", "forwarddecl2", "include", "typeof", "forward_vs_empty", "nestedenum"} // https://github.com/goplus/llgo/issues/1114 // todo(zzy):use os.ReadDir for _, folder := range cases { From 7db12c64d752d06717c85d17edb4792f92682af1 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 11:51:50 +0800 Subject: [PATCH 22/25] test: fix fallback --- _xtool/internal/parser/parser.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 9be9be94..2c452d14 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -892,7 +892,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { 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 (hasParent || isAnonymousDecl) && decl.Kind == clang.CursorEnumDecl { + if isAnonymousDecl && decl.Kind == clang.CursorEnumDecl { // case 1: anonymous enum, but not nested if !hasParent { // this is not a nested enum, handle it normally @@ -904,12 +904,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { // NOTE(MeteorsLiu): see disscussion https://github.com/goplus/llcppg/pull/530 return &ast.BuiltinType{Kind: ast.Int} } - // case3: named enum, nested - return &ast.TagExpr{ - Tag: ast.Enum, - // for typedef enum - Name: &ast.Ident{Name: parts[len(parts)-1]}, - } + // case3: named enum, nested, fallback to process as a ElaboratedType // case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. } From ad99773f982b0347e66cc0ca32e2a2f7ba0bf6b2 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 11:54:11 +0800 Subject: [PATCH 23/25] test: fix cpp mode test --- _xtool/internal/parser/parser_test.go | 2 +- .../parser/testdata/nestedenum_cpp/expect.json | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 4db223d7..5cfc3ef9 100644 --- a/_xtool/internal/parser/parser_test.go +++ b/_xtool/internal/parser/parser_test.go @@ -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", "nestedenum"} + 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 { diff --git a/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json b/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json index 4c1059f0..60f865bc 100755 --- a/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json +++ b/_xtool/internal/parser/testdata/nestedenum_cpp/expect.json @@ -408,8 +408,15 @@ ], "Type": { "Name": { - "Name": "is_metadata4", - "_Type": "Ident" + "Parent": { + "Name": "NestedEnum4", + "_Type": "Ident" + }, + "X": { + "Name": "is_metadata4", + "_Type": "Ident" + }, + "_Type": "ScopingExpr" }, "Tag": 2, "_Type": "TagExpr" From e57a21afb1416e153daa72193995e2bbb27f4088 Mon Sep 17 00:00:00 2001 From: Haolan Date: Wed, 24 Sep 2025 12:05:45 +0800 Subject: [PATCH 24/25] fix: remove unused logic --- _xtool/internal/parser/parser.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 2c452d14..2a5e272b 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -893,19 +893,18 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { // 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 + // case 1: anonymous enum, but not nested (typedef enum case) if !hasParent { // this is not a nested enum, handle it normally return ct.ProcessEnumType(decl) } - // case 2: anonymous enum, nested - if isAnonymousDecl { - // 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} - } - // case3: named enum, nested, fallback to process as a ElaboratedType - // case 4: named enum, non-nested, fallback to process as a ElaboratedType normally. + // 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 From f92891d14909ecedc4c41898499138ea0462d5dc Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Wed, 24 Sep 2025 13:55:27 +0800 Subject: [PATCH 25/25] Update _xtool/internal/parser/parser.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 张之阳 <51194195+luoliwoshang@users.noreply.github.com> --- _xtool/internal/parser/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index 2a5e272b..86bdc60b 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -893,7 +893,7 @@ func (ct *Converter) ProcessElaboratedType(t clang.Type) ast.Expr { // 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 (typedef enum case) + // 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)