diff --git a/_xtool/internal/libclang/_wrap/cursor.cpp b/_xtool/internal/libclang/_wrap/cursor.cpp index 190fb874..5761db9a 100644 --- a/_xtool/internal/libclang/_wrap/cursor.cpp +++ b/_xtool/internal/libclang/_wrap/cursor.cpp @@ -37,6 +37,8 @@ void wrap_clang_getCursorSemanticParent(CXCursor *C, CXCursor *parent) { *parent void wrap_clang_getCursorDefinition(CXCursor *C, CXCursor *def) { *def = clang_getCursorDefinition(*C); } +int wrap_clang_isCursorDefinition(CXCursor *cursor) { return clang_isCursorDefinition(*cursor); } + void wrap_clang_getCursorLexicalParent(CXCursor *C, CXCursor *parent) { *parent = clang_getCursorLexicalParent(*C); } void wrap_clang_getOverriddenCursors(CXCursor *cursor, CXCursor **overridden, unsigned *num_overridden) { diff --git a/_xtool/internal/libclang/clang.go b/_xtool/internal/libclang/clang.go index d52abaa7..86d15ba3 100644 --- a/_xtool/internal/libclang/clang.go +++ b/_xtool/internal/libclang/clang.go @@ -1643,6 +1643,17 @@ func (c Cursor) Definition() (def Cursor) { return } +/** + * Determine whether the declaration pointed to by this cursor + * is also a definition of that entity. + */ +// llgo:link (*Cursor).wrapIsCursorDefinition C.wrap_clang_isCursorDefinition +func (c *Cursor) wrapIsCursorDefinition() c.Int { return 0 } + +func (c Cursor) IsCursorDefinition() c.Int { + return c.wrapIsCursorDefinition() +} + /** * Determine the lexical parent of the given cursor. * diff --git a/_xtool/internal/parser/marshal.go b/_xtool/internal/parser/marshal.go index 86a19a88..5c773e3d 100644 --- a/_xtool/internal/parser/marshal.go +++ b/_xtool/internal/parser/marshal.go @@ -176,6 +176,9 @@ func XMarshalASTExpr(t ast.Expr) map[string]any { root["Ret"] = XMarshalASTExpr(d.Ret) case *ast.FieldList: root["_Type"] = "FieldList" + if d == nil { + return nil + } root["List"] = XMarshalFieldList(d.List) case *ast.Field: root["_Type"] = "Field" diff --git a/_xtool/internal/parser/parser.go b/_xtool/internal/parser/parser.go index e01f974f..0e533ad4 100644 --- a/_xtool/internal/parser/parser.go +++ b/_xtool/internal/parser/parser.go @@ -768,7 +768,7 @@ func (ct *Converter) ProcessRecordDecl(cursor clang.Cursor) []ast.Decl { 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 { childName := clang.GoString(child.String()) ct.logln("ProcessRecordDecl: Found named nested struct:", childName) decls = append(decls, &ast.TypeDecl{ @@ -826,23 +826,26 @@ func (ct *Converter) ProcessRecordType(cursor clang.Cursor) *ast.RecordType { ct.incIndent() defer ct.decIndent() + typ := &ast.RecordType{} + cursorName, cursorKind := getCursorDesc(cursor) ct.logln("ProcessRecordType: CursorName:", cursorName, "CursorKind:", cursorKind) - tag := toTag(cursor.Kind) - ct.logln("ProcessRecordType: toTag", tag) + typ.Tag = toTag(cursor.Kind) + ct.logln("ProcessRecordType: toTag", typ.Tag) + + if cursor.IsCursorDefinition() == 0 { + ct.logln("ProcessRecordType: forward declaration, no definition") + return typ + } ct.logln("ProcessRecordType: ProcessFieldList") - fields := ct.ProcessFieldList(cursor) + typ.Fields = ct.ProcessFieldList(cursor) ct.logln("ProcessRecordType: ProcessMethods") - methods := ct.ProcessMethods(cursor) + typ.Methods = ct.ProcessMethods(cursor) - return &ast.RecordType{ - Tag: tag, - Fields: fields, - Methods: methods, - } + return typ } // process ElaboratedType Reference diff --git a/_xtool/internal/parser/parser_test.go b/_xtool/internal/parser/parser_test.go index 37eb7c8a..2837baea 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"} + 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 for _, folder := range cases { @@ -31,7 +31,7 @@ func TestParserCppMode(t *testing.T) { } func TestParserCMode(t *testing.T) { - cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct"} + cases := []string{"enum", "struct", "union", "macro", "include", "typeof", "named_nested_struct", "forward_vs_empty"} for _, folder := range cases { t.Run(folder, func(t *testing.T) { testFrom(t, filepath.Join("testdata", folder), "temp.h", false, false) @@ -666,3 +666,31 @@ func TestPostOrderVisitChildren(t *testing.T) { fmt.Println("Unexpected child order:", childStr) } } + +func TestEmptyDeclVsForwardDecl(t *testing.T) { + config := &clangutils.Config{ + File: "./testdata/forward_vs_empty/temp.h", + Temp: false, + IsCpp: false, + } + + type isDefinition = bool + var decl map[string]isDefinition = map[string]isDefinition{ + "ForwardOnly": false, + "EmptyStruct": true, + } + + visit(config, func(cursor, parent clang.Cursor) clang.ChildVisitResult { + if cursor.Kind == clang.CursorStructDecl { + sdecl := clang.GoString(cursor.String()) + if _, ok := decl[sdecl]; ok { + isDefine := cursor.IsCursorDefinition() != 0 + if isDefine != decl[sdecl] { + t.Fatalf("StructDecl %s isDefinition expect %v, got %v", sdecl, decl[sdecl], isDefine) + } + } + fmt.Println("StructDecl Name:", clang.GoString(cursor.String()), "isDefinition:", cursor.IsCursorDefinition() != 0) + } + return clang.ChildVisit_Recurse + }) +} diff --git a/_xtool/internal/parser/testdata/forward_vs_empty/expect.json b/_xtool/internal/parser/testdata/forward_vs_empty/expect.json new file mode 100755 index 00000000..3196f715 --- /dev/null +++ b/_xtool/internal/parser/testdata/forward_vs_empty/expect.json @@ -0,0 +1,48 @@ +{ + "_Type": "File", + "decls": [ + { + "Doc": null, + "Loc": { + "File": "testdata/forward_vs_empty/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "ForwardOnly", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": null, + "Methods": null, + "Tag": 0, + "_Type": "RecordType" + }, + "_Type": "TypeDecl" + }, + { + "Doc": null, + "Loc": { + "File": "testdata/forward_vs_empty/temp.h", + "_Type": "Location" + }, + "Name": { + "Name": "EmptyStruct", + "_Type": "Ident" + }, + "Parent": null, + "Type": { + "Fields": { + "List": null, + "_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/forward_vs_empty/temp.h b/_xtool/internal/parser/testdata/forward_vs_empty/temp.h new file mode 100644 index 00000000..ffd75842 --- /dev/null +++ b/_xtool/internal/parser/testdata/forward_vs_empty/temp.h @@ -0,0 +1,2 @@ +struct ForwardOnly; +struct EmptyStruct {}; diff --git a/_xtool/internal/parser/testdata/forwarddecl1/expect.json b/_xtool/internal/parser/testdata/forwarddecl1/expect.json index 25fff7bc..ed56af94 100755 --- a/_xtool/internal/parser/testdata/forwarddecl1/expect.json +++ b/_xtool/internal/parser/testdata/forwarddecl1/expect.json @@ -101,10 +101,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -167,10 +164,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -189,10 +183,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -367,10 +358,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -536,10 +524,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -558,10 +543,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" diff --git a/_xtool/internal/parser/testdata/func/expect.json b/_xtool/internal/parser/testdata/func/expect.json index b849eeb4..4523c263 100755 --- a/_xtool/internal/parser/testdata/func/expect.json +++ b/_xtool/internal/parser/testdata/func/expect.json @@ -472,10 +472,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" @@ -494,10 +491,7 @@ }, "Parent": null, "Type": { - "Fields": { - "List": null, - "_Type": "FieldList" - }, + "Fields": null, "Methods": null, "Tag": 0, "_Type": "RecordType" diff --git a/cl/internal/convert/_testdata/forwarddecl/gogensig.expect b/cl/internal/convert/_testdata/forwarddecl/gogensig.expect index ab6678ba..cc79f975 100644 --- a/cl/internal/convert/_testdata/forwarddecl/gogensig.expect +++ b/cl/internal/convert/_testdata/forwarddecl/gogensig.expect @@ -129,7 +129,17 @@ type X_xmlParserCtxt struct { type XmlParserCtxt X_xmlParserCtxt type HtmlParserCtxt XmlParserCtxt +// https://github.com/goplus/llcppg/issues/526 +type ForwardOnly struct { + Unused [8]uint8 +} + +type EmptyStruct struct { +} + ===== llcppg.pub ===== +EmptyStruct +ForwardOnly Fts5Context Fts5ExtensionApi Fts5PhraseIter diff --git a/cl/internal/convert/_testdata/forwarddecl/hfile/temp.h b/cl/internal/convert/_testdata/forwarddecl/hfile/temp.h index 9e4709f9..c52f48ff 100644 --- a/cl/internal/convert/_testdata/forwarddecl/hfile/temp.h +++ b/cl/internal/convert/_testdata/forwarddecl/hfile/temp.h @@ -94,3 +94,7 @@ struct _xmlParserCtxt; typedef struct _xmlParserCtxt xmlParserCtxt; typedef xmlParserCtxt htmlParserCtxt; + +// https://github.com/goplus/llcppg/issues/526 +struct ForwardOnly; +struct EmptyStruct {}; \ No newline at end of file diff --git a/cl/internal/convert/package_test.go b/cl/internal/convert/package_test.go index 8a996da2..a2890552 100644 --- a/cl/internal/convert/package_test.go +++ b/cl/internal/convert/package_test.go @@ -719,7 +719,7 @@ func TestStructDecl(t *testing.T) { }, Type: &ast.RecordType{ Tag: ast.Struct, - Fields: nil, + Fields: &ast.FieldList{}, }, }, expected: ` @@ -1667,7 +1667,7 @@ func TestForwardDecl(t *testing.T) { }, Type: &ast.RecordType{ Tag: ast.Struct, - Fields: &ast.FieldList{}, + Fields: nil, }, } // forward decl @@ -1871,7 +1871,9 @@ func TestTypeClean(t *testing.T) { Object: ast.Object{ Name: &ast.Ident{Name: "Foo1"}, }, - Type: &ast.RecordType{Tag: ast.Struct}, + Type: &ast.RecordType{ + Tag: ast.Struct, + Fields: &ast.FieldList{}}, }, nc) }, headerFile: "/path/to/file1.h", diff --git a/cl/internal/convert/type.go b/cl/internal/convert/type.go index 7bd86c11..b1530ff2 100644 --- a/cl/internal/convert/type.go +++ b/cl/internal/convert/type.go @@ -334,7 +334,7 @@ func (p *TypeConv) ToDefaultEnumType() types.Type { // by only checking if Fields.List is empty // Should use recordType == nil to identify forward declarations, which requires llcppsigfetch support func (p *TypeConv) inComplete(recordType *ast.RecordType) bool { - return recordType.Fields != nil && len(recordType.Fields.List) == 0 + return recordType.Fields == nil } // The field name should be public if it's a record field