mirror of
https://github.com/goplus/llgo.git
synced 2025-09-26 19:51:21 +08:00
939 lines
30 KiB
Go
939 lines
30 KiB
Go
//go:build !llgo
|
|
// +build !llgo
|
|
|
|
package header
|
|
|
|
import (
|
|
"bytes"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/goplus/gogen/packages"
|
|
"github.com/goplus/llgo/ssa"
|
|
"github.com/goplus/llvm"
|
|
)
|
|
|
|
func init() {
|
|
llvm.InitializeAllTargets()
|
|
llvm.InitializeAllTargetMCs()
|
|
llvm.InitializeAllTargetInfos()
|
|
llvm.InitializeAllAsmParsers()
|
|
llvm.InitializeAllAsmPrinters()
|
|
}
|
|
|
|
func TestGenCHeaderExport(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
prog.SetRuntime(func() *types.Package {
|
|
fset := token.NewFileSet()
|
|
imp := packages.NewImporter(fset)
|
|
pkg, _ := imp.Import(ssa.PkgRuntime)
|
|
return pkg
|
|
})
|
|
|
|
// Define main package and the 'Foo' type within it
|
|
mainPkgPath := "github.com/goplus/llgo/test_buildmode/main"
|
|
mainTypesPkg := types.NewPackage(mainPkgPath, "main")
|
|
fooFields := []*types.Var{
|
|
types.NewField(token.NoPos, mainTypesPkg, "a", types.Typ[types.Int], false),
|
|
types.NewField(token.NoPos, mainTypesPkg, "b", types.Typ[types.Float64], false),
|
|
}
|
|
fooStruct := types.NewStruct(fooFields, nil)
|
|
fooTypeName := types.NewTypeName(token.NoPos, mainTypesPkg, "Foo", nil)
|
|
fooNamed := types.NewNamed(fooTypeName, fooStruct, nil)
|
|
mainTypesPkg.Scope().Insert(fooTypeName)
|
|
|
|
// Create SSA package for main
|
|
mainPkg := prog.NewPackage("main", mainPkgPath)
|
|
|
|
// Define exported functions in mainPkg
|
|
mainPkg.NewFunc("HelloWorld", types.NewSignatureType(nil, nil, nil, nil, nil, false), ssa.InGo)
|
|
useFooPtrParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", types.NewPointer(fooNamed)))
|
|
useFooPtrResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", fooNamed))
|
|
useFooPtrSig := types.NewSignatureType(nil, nil, nil, useFooPtrParams, useFooPtrResults, false)
|
|
mainPkg.NewFunc("UseFooPtr", useFooPtrSig, ssa.InGo)
|
|
useFooParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", fooNamed))
|
|
useFooSig := types.NewSignatureType(nil, nil, nil, useFooParams, useFooPtrResults, false)
|
|
mainPkg.NewFunc("UseFoo", useFooSig, ssa.InGo)
|
|
|
|
// Set exports for main
|
|
mainPkg.SetExport("HelloWorld", "HelloWorld")
|
|
mainPkg.SetExport("UseFooPtr", "UseFooPtr")
|
|
mainPkg.SetExport("UseFoo", "UseFoo")
|
|
|
|
// Create package C
|
|
cPkgPath := "github.com/goplus/llgo/test_buildmode/bar"
|
|
cPkg := prog.NewPackage("C", cPkgPath)
|
|
addParams := types.NewTuple(
|
|
types.NewVar(token.NoPos, nil, "a", types.Typ[types.Int]),
|
|
types.NewVar(token.NoPos, nil, "b", types.Typ[types.Int]))
|
|
addResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.Int]))
|
|
addSig := types.NewSignatureType(nil, nil, nil, addParams, addResults, false)
|
|
cPkg.NewFunc("Add", addSig, ssa.InGo)
|
|
cPkg.NewFunc("Sub", addSig, ssa.InGo)
|
|
cPkg.SetExport("XAdd", "Add")
|
|
cPkg.SetExport("XSub", "Sub")
|
|
|
|
// Generate header
|
|
libname := "testbuild"
|
|
headerPath := os.TempDir() + "/testbuild.h"
|
|
err := GenHeaderFile(prog, []ssa.Package{mainPkg, cPkg}, libname, headerPath, true)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := os.ReadFile(headerPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
required := []string{
|
|
"/* Code generated by llgo; DO NOT EDIT. */",
|
|
"#ifndef __TESTBUILD_H_",
|
|
"#include <stdbool.h>",
|
|
"typedef struct { const char *p; intptr_t n; } GoString;",
|
|
"typedef struct {\n intptr_t a;\n double b;\n} main_Foo;",
|
|
"void\nHelloWorld(void);",
|
|
"main_Foo\nUseFooPtr(main_Foo* f);",
|
|
"main_Foo\nUseFoo(main_Foo f);",
|
|
"intptr_t\nAdd(intptr_t a, intptr_t b);",
|
|
"intptr_t\nSub(intptr_t a, intptr_t b);",
|
|
"#endif /* __TESTBUILD_H_ */",
|
|
}
|
|
|
|
got := string(data)
|
|
|
|
for _, sub := range required {
|
|
if !strings.Contains(got, sub) {
|
|
t.Fatalf("Generated content: %s\n", got)
|
|
t.Fatalf("Generated header is missing expected content:\n%s", sub)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCheaderWriterTypes(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Test complex integration scenarios only - basic types are covered by TestGoCTypeName
|
|
tests := []struct {
|
|
name string
|
|
goType types.Type
|
|
expected string
|
|
}{
|
|
{
|
|
name: "named struct",
|
|
goType: func() types.Type {
|
|
pkg := types.NewPackage("main", "main")
|
|
s := types.NewStruct([]*types.Var{types.NewField(0, nil, "f1", types.Typ[types.Int], false)}, nil)
|
|
return types.NewNamed(types.NewTypeName(0, pkg, "MyStruct", nil), s, nil)
|
|
}(),
|
|
expected: "typedef struct {\n intptr_t f1;\n} main_MyStruct;\n\n",
|
|
},
|
|
{
|
|
name: "struct with array field",
|
|
goType: func() types.Type {
|
|
arrayType := types.NewArray(types.Typ[types.Float64], 10)
|
|
return types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "Values", arrayType, false),
|
|
}, nil)
|
|
}(),
|
|
expected: "typedef struct {\n double Values[10];\n} struct_double_Values;\n\n",
|
|
},
|
|
{
|
|
name: "struct with multidimensional array",
|
|
goType: func() types.Type {
|
|
// Create a 2D array: [4][3]int32
|
|
innerArrayType := types.NewArray(types.Typ[types.Int32], 3)
|
|
outerArrayType := types.NewArray(innerArrayType, 4)
|
|
return types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "Matrix", outerArrayType, false),
|
|
}, nil)
|
|
}(),
|
|
expected: "typedef struct {\n int32_t Matrix[4][3];\n} struct_int32_t_Matrix;\n\n",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
hw.typeBuf.Reset()
|
|
hw.declaredTypes = make(map[string]bool) // Reset declared types for each run
|
|
|
|
// Mark predefined Go types as declared (same as in genHeader)
|
|
hw.declaredTypes["GoString"] = true
|
|
hw.declaredTypes["GoSlice"] = true
|
|
hw.declaredTypes["GoMap"] = true
|
|
hw.declaredTypes["GoChan"] = true
|
|
hw.declaredTypes["GoInterface"] = true
|
|
hw.declaredTypes["GoComplex64"] = true
|
|
hw.declaredTypes["GoComplex128"] = true
|
|
|
|
if err := hw.writeTypedef(tt.goType); err != nil {
|
|
t.Fatalf("writeTypedef() error = %v", err)
|
|
}
|
|
got := hw.typeBuf.String()
|
|
if got != tt.expected {
|
|
t.Errorf("writeTypedef() got = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test for goCTypeName function to cover all basic types
|
|
func TestGoCTypeName(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
tests := []struct {
|
|
name string
|
|
goType types.Type
|
|
expected string
|
|
}{
|
|
{name: "bool", goType: types.Typ[types.Bool], expected: "_Bool"},
|
|
{name: "int8", goType: types.Typ[types.Int8], expected: "int8_t"},
|
|
{name: "uint8", goType: types.Typ[types.Uint8], expected: "uint8_t"},
|
|
{name: "int16", goType: types.Typ[types.Int16], expected: "int16_t"},
|
|
{name: "uint16", goType: types.Typ[types.Uint16], expected: "uint16_t"},
|
|
{name: "int32", goType: types.Typ[types.Int32], expected: "int32_t"},
|
|
{name: "uint32", goType: types.Typ[types.Uint32], expected: "uint32_t"},
|
|
{name: "int64", goType: types.Typ[types.Int64], expected: "int64_t"},
|
|
{name: "uint64", goType: types.Typ[types.Uint64], expected: "uint64_t"},
|
|
{name: "int", goType: types.Typ[types.Int], expected: "intptr_t"},
|
|
{name: "uint", goType: types.Typ[types.Uint], expected: "uintptr_t"},
|
|
{name: "uintptr", goType: types.Typ[types.Uintptr], expected: "uintptr_t"},
|
|
{name: "float32", goType: types.Typ[types.Float32], expected: "float"},
|
|
{name: "float64", goType: types.Typ[types.Float64], expected: "double"},
|
|
{name: "complex64", goType: types.Typ[types.Complex64], expected: "GoComplex64"},
|
|
{name: "complex128", goType: types.Typ[types.Complex128], expected: "GoComplex128"},
|
|
{name: "string", goType: types.Typ[types.String], expected: "GoString"},
|
|
{name: "unsafe pointer", goType: types.Typ[types.UnsafePointer], expected: "void*"},
|
|
{name: "slice", goType: types.NewSlice(types.Typ[types.Int]), expected: "GoSlice"},
|
|
{name: "map", goType: types.NewMap(types.Typ[types.String], types.Typ[types.Int]), expected: "GoMap"},
|
|
{name: "chan", goType: types.NewChan(types.SendRecv, types.Typ[types.Int]), expected: "GoChan"},
|
|
{name: "interface", goType: types.NewInterfaceType(nil, nil), expected: "GoInterface"},
|
|
{
|
|
name: "array",
|
|
goType: types.NewArray(types.Typ[types.Int], 5),
|
|
expected: "intptr_t",
|
|
},
|
|
{
|
|
name: "pointer to int",
|
|
goType: types.NewPointer(types.Typ[types.Int]),
|
|
expected: "intptr_t*",
|
|
},
|
|
{
|
|
name: "pointer to unknown type",
|
|
goType: types.NewPointer(types.Typ[types.Invalid]),
|
|
expected: "void*",
|
|
},
|
|
{
|
|
name: "array of unknown type",
|
|
goType: types.NewArray(types.Typ[types.Invalid], 3),
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "signature type",
|
|
goType: types.NewSignature(nil, nil, nil, false),
|
|
expected: "void (*)(void)",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := hw.goCTypeName(tt.goType)
|
|
if got != tt.expected {
|
|
t.Errorf("goCTypeName() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test typeReferencesSelf function
|
|
func TestTypeReferencesSelf(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Create a named type for testing
|
|
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
|
|
nodeStruct := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "data", types.Typ[types.Int], false),
|
|
}, nil)
|
|
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
|
|
|
|
tests := []struct {
|
|
name string
|
|
typ types.Type
|
|
selfTypeName string
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "pointer to self",
|
|
typ: types.NewPointer(namedNode),
|
|
selfTypeName: "test_Node",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "slice of self",
|
|
typ: types.NewSlice(namedNode),
|
|
selfTypeName: "test_Node",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "array of self",
|
|
typ: types.NewArray(namedNode, 5),
|
|
selfTypeName: "test_Node",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "named type self",
|
|
typ: namedNode,
|
|
selfTypeName: "test_Node",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "basic type not self",
|
|
typ: types.Typ[types.Int],
|
|
selfTypeName: "test_Node",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "different named type",
|
|
typ: namedNode,
|
|
selfTypeName: "other_Type",
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := hw.typeReferencesSelf(tt.typ, tt.selfTypeName)
|
|
if got != tt.expected {
|
|
t.Errorf("typeReferencesSelf() = %v, want %v", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test array struct generation functions
|
|
func TestArrayStructGeneration(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Test ensureArrayStruct
|
|
arrayType := types.NewArray(types.Typ[types.Int32], 5)
|
|
name := hw.ensureArrayStruct(arrayType)
|
|
expectedName := "Array_int32_t_5"
|
|
if name != expectedName {
|
|
t.Errorf("ensureArrayStruct() = %q, want %q", name, expectedName)
|
|
}
|
|
|
|
// Test that typedef was generated
|
|
output := hw.typeBuf.String()
|
|
if !strings.Contains(output, "typedef struct") {
|
|
t.Error("ensureArrayStruct should generate typedef")
|
|
}
|
|
if !strings.Contains(output, "int32_t data[5]") {
|
|
t.Error("ensureArrayStruct should generate correct array field")
|
|
}
|
|
|
|
// Test duplicate prevention
|
|
hw.typeBuf.Reset()
|
|
name2 := hw.ensureArrayStruct(arrayType) // Call again
|
|
if name2 != name {
|
|
t.Errorf("ensureArrayStruct should return same name for same type")
|
|
}
|
|
duplicateOutput := hw.typeBuf.String()
|
|
if duplicateOutput != "" {
|
|
t.Error("ensureArrayStruct should not generate duplicate typedef")
|
|
}
|
|
}
|
|
|
|
// Test generateReturnType function
|
|
func TestGenerateReturnType(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Test basic type
|
|
basicRet := hw.generateReturnType(types.Typ[types.Int32])
|
|
if basicRet != "int32_t" {
|
|
t.Errorf("generateReturnType(int32) = %q, want %q", basicRet, "int32_t")
|
|
}
|
|
|
|
// Test array type (should generate struct wrapper)
|
|
arrayType := types.NewArray(types.Typ[types.Float64], 3)
|
|
arrayRet := hw.generateReturnType(arrayType)
|
|
expectedArrayRet := "Array_double_3"
|
|
if arrayRet != expectedArrayRet {
|
|
t.Errorf("generateReturnType(array) = %q, want %q", arrayRet, expectedArrayRet)
|
|
}
|
|
}
|
|
|
|
// Test generateTypedef function
|
|
func TestGenerateTypedef(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Test named struct
|
|
structType := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "value", types.Typ[types.Int], false),
|
|
}, nil)
|
|
namedType := types.NewNamed(types.NewTypeName(0, pkg, "TestStruct", nil), structType, nil)
|
|
|
|
typedef := hw.generateTypedef(namedType)
|
|
if !strings.Contains(typedef, "typedef struct") {
|
|
t.Error("generateTypedef should generate typedef for named struct")
|
|
}
|
|
|
|
// Test named basic type
|
|
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
|
|
typedef2 := hw.generateTypedef(namedInt)
|
|
if !strings.Contains(typedef2, "typedef intptr_t test_MyInt") {
|
|
t.Error("generateTypedef should generate typedef for named basic type")
|
|
}
|
|
}
|
|
|
|
// Test complex nested structures and dependencies
|
|
func TestComplexNestedStructures(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Create a complex nested structure
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Inner struct
|
|
innerStruct := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "value", types.Typ[types.Int], false),
|
|
}, nil)
|
|
|
|
// Named inner struct
|
|
innerTypeName := types.NewTypeName(0, pkg, "InnerStruct", nil)
|
|
namedInner := types.NewNamed(innerTypeName, innerStruct, nil)
|
|
|
|
// Outer struct with inner struct field
|
|
outerStruct := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "inner", namedInner, false),
|
|
types.NewField(0, nil, "ptr", types.NewPointer(namedInner), false),
|
|
types.NewField(0, nil, "slice", types.NewSlice(namedInner), false),
|
|
}, nil)
|
|
|
|
outerTypeName := types.NewTypeName(0, pkg, "OuterStruct", nil)
|
|
namedOuter := types.NewNamed(outerTypeName, outerStruct, nil)
|
|
|
|
// Test writeTypedef for complex structure
|
|
err := hw.writeTypedef(namedOuter)
|
|
if err != nil {
|
|
t.Fatalf("writeTypedef() error = %v", err)
|
|
}
|
|
|
|
output := hw.typeBuf.String()
|
|
|
|
// Should contain both inner and outer struct definitions
|
|
if !strings.Contains(output, "test_InnerStruct") {
|
|
t.Error("Expected inner struct typedef")
|
|
}
|
|
if !strings.Contains(output, "test_OuterStruct") {
|
|
t.Error("Expected outer struct typedef")
|
|
}
|
|
}
|
|
|
|
// Test goCTypeName with more type cases
|
|
|
|
// Test processDependentTypes for error paths and edge cases
|
|
func TestProcessDependentTypesEdgeCases(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Test signature type dependency (function parameters and results)
|
|
params := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
|
|
results := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
|
|
sigType := types.NewSignatureType(nil, nil, nil, params, results, false)
|
|
|
|
err := hw.processDependentTypes(sigType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("processDependentTypes(signature) error = %v", err)
|
|
}
|
|
|
|
// Test processSignatureTypes directly
|
|
err = hw.processSignatureTypes(sigType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("processSignatureTypes error = %v", err)
|
|
}
|
|
|
|
// Test Map type - this should trigger the Map case in writeTypedefRecursive
|
|
mapType := types.NewMap(types.Typ[types.String], types.Typ[types.Int])
|
|
err = hw.writeTypedefRecursive(mapType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(map) error = %v", err)
|
|
}
|
|
|
|
// Test Chan type - this should trigger the Chan case in writeTypedefRecursive
|
|
chanType := types.NewChan(types.SendRecv, types.Typ[types.Bool])
|
|
err = hw.writeTypedefRecursive(chanType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(chan) error = %v", err)
|
|
}
|
|
|
|
// Test Map with complex types to trigger both key and value processing
|
|
struct1 := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "key", types.Typ[types.String], false),
|
|
}, nil)
|
|
struct2 := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "value", types.Typ[types.Int], false),
|
|
}, nil)
|
|
complexMapType := types.NewMap(struct1, struct2)
|
|
err = hw.writeTypedefRecursive(complexMapType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(complex map) error = %v", err)
|
|
}
|
|
|
|
// Test function signature with no parameters (edge case)
|
|
noParamsSig := types.NewSignatureType(nil, nil, nil, nil, results, false)
|
|
err = hw.processSignatureTypes(noParamsSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("processSignatureTypes(no params) error = %v", err)
|
|
}
|
|
|
|
// Test function signature with no results (edge case)
|
|
noResultsSig := types.NewSignatureType(nil, nil, nil, params, nil, false)
|
|
err = hw.processSignatureTypes(noResultsSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("processSignatureTypes(no results) error = %v", err)
|
|
}
|
|
|
|
// Test function type (callback) parameters - IntCallback
|
|
intCallbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
|
|
intCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
|
|
intCallbackSig := types.NewSignatureType(nil, nil, nil, intCallbackParams, intCallbackResults, false)
|
|
err = hw.writeTypedefRecursive(intCallbackSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(IntCallback) error = %v", err)
|
|
}
|
|
|
|
// Test function type (callback) parameters - StringCallback
|
|
stringCallbackParams := types.NewTuple(types.NewVar(0, nil, "s", types.Typ[types.String]))
|
|
stringCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
|
|
stringCallbackSig := types.NewSignatureType(nil, nil, nil, stringCallbackParams, stringCallbackResults, false)
|
|
err = hw.writeTypedefRecursive(stringCallbackSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(StringCallback) error = %v", err)
|
|
}
|
|
|
|
// Test function type (callback) parameters - VoidCallback
|
|
voidCallbackSig := types.NewSignatureType(nil, nil, nil, nil, nil, false)
|
|
err = hw.writeTypedefRecursive(voidCallbackSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(VoidCallback) error = %v", err)
|
|
}
|
|
|
|
// Test Named function type - this should trigger the function typedef generation
|
|
pkg := types.NewPackage("test", "test")
|
|
callbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
|
|
callbackSig := types.NewSignatureType(nil, nil, nil, callbackParams, nil, false)
|
|
callbackTypeName := types.NewTypeName(0, pkg, "Callback", nil)
|
|
namedCallback := types.NewNamed(callbackTypeName, callbackSig, nil)
|
|
|
|
// Test Named function type with no parameters - NoParamCallback func() int
|
|
noParamCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
|
|
noParamCallbackSig := types.NewSignatureType(nil, nil, nil, nil, noParamCallbackResults, false)
|
|
noParamCallbackTypeName := types.NewTypeName(0, pkg, "NoParamCallback", nil)
|
|
namedNoParamCallback := types.NewNamed(noParamCallbackTypeName, noParamCallbackSig, nil)
|
|
|
|
err = hw.writeTypedef(namedCallback)
|
|
if err != nil {
|
|
t.Errorf("writeTypedef(named function) error = %v", err)
|
|
}
|
|
|
|
err = hw.writeTypedef(namedNoParamCallback)
|
|
if err != nil {
|
|
t.Errorf("writeTypedef(no param callback) error = %v", err)
|
|
}
|
|
|
|
// Verify the generated typedef contains function pointer syntax
|
|
output := hw.typeBuf.String()
|
|
if !strings.Contains(output, "test_Callback") {
|
|
t.Errorf("Expected named function typedef in output")
|
|
}
|
|
if !strings.Contains(output, "(*test_Callback)") {
|
|
t.Errorf("Expected function pointer syntax in typedef: %s", output)
|
|
}
|
|
if !strings.Contains(output, "test_NoParamCallback") {
|
|
t.Errorf("Expected no-param callback typedef in output")
|
|
}
|
|
if !strings.Contains(output, "(*test_NoParamCallback)(void)") {
|
|
t.Errorf("Expected no-param function pointer syntax in typedef: %s", output)
|
|
}
|
|
|
|
// Test function signature with unnamed parameters (like //export ProcessThreeUnnamedParams)
|
|
unnamedParams := types.NewTuple(
|
|
types.NewVar(0, nil, "", types.Typ[types.Int]),
|
|
types.NewVar(0, nil, "", types.Typ[types.String]),
|
|
types.NewVar(0, nil, "", types.Typ[types.Bool]),
|
|
)
|
|
unnamedResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Float64]))
|
|
unnamedSig := types.NewSignatureType(nil, nil, nil, unnamedParams, unnamedResults, false)
|
|
err = hw.writeTypedefRecursive(unnamedSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(unnamed params) error = %v", err)
|
|
}
|
|
}
|
|
|
|
// Test generateParameterDeclaration function
|
|
func TestGenerateParameterDeclaration(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
tests := []struct {
|
|
name string
|
|
paramType types.Type
|
|
paramName string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "basic type with name",
|
|
paramType: types.Typ[types.Int],
|
|
paramName: "x",
|
|
expected: "intptr_t x",
|
|
},
|
|
{
|
|
name: "basic type without name",
|
|
paramType: types.Typ[types.Int],
|
|
paramName: "",
|
|
expected: "intptr_t",
|
|
},
|
|
{
|
|
name: "array type with name",
|
|
paramType: types.NewArray(types.Typ[types.Int], 5),
|
|
paramName: "arr",
|
|
expected: "intptr_t* arr",
|
|
},
|
|
{
|
|
name: "array type without name",
|
|
paramType: types.NewArray(types.Typ[types.Int], 5),
|
|
paramName: "",
|
|
expected: "intptr_t*",
|
|
},
|
|
{
|
|
name: "multidimensional array with name",
|
|
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
|
|
paramName: "matrix",
|
|
expected: "intptr_t matrix[3][4]",
|
|
},
|
|
{
|
|
name: "multidimensional array without name",
|
|
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
|
|
paramName: "",
|
|
expected: "intptr_t[3][4]",
|
|
},
|
|
{
|
|
name: "pointer type with name",
|
|
paramType: types.NewPointer(types.Typ[types.Int]),
|
|
paramName: "ptr",
|
|
expected: "intptr_t* ptr",
|
|
},
|
|
{
|
|
name: "pointer type without name",
|
|
paramType: types.NewPointer(types.Typ[types.Int]),
|
|
paramName: "",
|
|
expected: "intptr_t*",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := hw.generateParameterDeclaration(tt.paramType, tt.paramName)
|
|
if got != tt.expected {
|
|
t.Errorf("generateParameterDeclaration() = %q, want %q", got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Test generateNamedStructTypedef with forward declaration
|
|
func TestGenerateNamedStructTypedefWithForwardDecl(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Create a self-referential struct that needs forward declaration
|
|
nodeName := types.NewTypeName(0, pkg, "Node", nil)
|
|
nodeNamed := types.NewNamed(nodeName, nil, nil)
|
|
|
|
// Create fields including a pointer to itself
|
|
fields := []*types.Var{
|
|
types.NewField(0, nil, "value", types.Typ[types.Int], false),
|
|
types.NewField(0, nil, "next", types.NewPointer(nodeNamed), false),
|
|
}
|
|
nodeStruct := types.NewStruct(fields, nil)
|
|
nodeNamed.SetUnderlying(nodeStruct)
|
|
|
|
// Test generateNamedStructTypedef
|
|
result := hw.generateNamedStructTypedef(nodeNamed, nodeStruct)
|
|
|
|
// Should contain forward declaration (with package prefix)
|
|
if !strings.Contains(result, "typedef struct test_Node test_Node;") {
|
|
t.Errorf("Expected forward declaration in result: %s", result)
|
|
}
|
|
|
|
// Should contain the actual struct definition
|
|
if !strings.Contains(result, "struct test_Node {") {
|
|
t.Errorf("Expected struct definition in result: %s", result)
|
|
}
|
|
}
|
|
|
|
// Test self-referential structures to ensure no infinite recursion
|
|
func TestSelfReferentialStructure(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Create a self-referential struct: Node with a pointer to itself
|
|
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
|
|
nodeStruct := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "data", types.Typ[types.Int], false),
|
|
}, nil)
|
|
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
|
|
|
|
// Add a self-referential field after creating the named type
|
|
nodeStructWithPtr := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "data", types.Typ[types.Int], false),
|
|
types.NewField(0, nil, "next", types.NewPointer(namedNode), false),
|
|
}, nil)
|
|
|
|
// Create a new named type with the updated struct
|
|
nodeTypeNameFinal := types.NewTypeName(0, pkg, "SelfRefNode", nil)
|
|
namedNodeFinal := types.NewNamed(nodeTypeNameFinal, nodeStructWithPtr, nil)
|
|
|
|
// This should not cause infinite recursion
|
|
err := hw.writeTypedef(namedNodeFinal)
|
|
if err != nil {
|
|
t.Fatalf("writeTypedef() error = %v", err)
|
|
}
|
|
|
|
output := hw.typeBuf.String()
|
|
if !strings.Contains(output, "test_SelfRefNode") {
|
|
t.Error("Expected self-referential struct typedef")
|
|
}
|
|
}
|
|
|
|
// Test function signature dependencies
|
|
func TestFunctionSignatureDependencies(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
pkg := types.NewPackage("test", "test")
|
|
|
|
// Create struct type for function parameters
|
|
paramStruct := types.NewStruct([]*types.Var{
|
|
types.NewField(0, nil, "id", types.Typ[types.Int], false),
|
|
}, nil)
|
|
paramTypeName := types.NewTypeName(0, pkg, "ParamStruct", nil)
|
|
namedParam := types.NewNamed(paramTypeName, paramStruct, nil)
|
|
|
|
// Create function signature with struct parameters and return values
|
|
params := types.NewTuple(
|
|
types.NewVar(0, nil, "input", namedParam),
|
|
types.NewVar(0, nil, "count", types.Typ[types.Int]),
|
|
)
|
|
results := types.NewTuple(
|
|
types.NewVar(0, nil, "output", namedParam),
|
|
)
|
|
|
|
funcSig := types.NewSignatureType(nil, nil, nil, params, results, false)
|
|
|
|
// Test that function signature dependencies are processed
|
|
err := hw.processDependentTypes(funcSig, make(map[string]bool))
|
|
if err != nil {
|
|
t.Fatalf("processDependentTypes() error = %v", err)
|
|
}
|
|
|
|
// Test named basic type alias (should trigger the "else" branch in processDependentTypes)
|
|
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
|
|
err = hw.processDependentTypes(namedInt, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("processDependentTypes(named int) error = %v", err)
|
|
}
|
|
|
|
// Test duplicate type prevention - mark type as already declared
|
|
hw2 := newCHeaderWriter(prog)
|
|
hw2.declaredTypes["test_DuplicateType"] = true
|
|
duplicateType := types.NewNamed(
|
|
types.NewTypeName(0, pkg, "DuplicateType", nil),
|
|
types.Typ[types.Int],
|
|
nil,
|
|
)
|
|
err = hw2.writeTypedefRecursive(duplicateType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(duplicate) error = %v", err)
|
|
}
|
|
if strings.Contains(hw2.typeBuf.String(), "DuplicateType") {
|
|
t.Error("Should not generate typedef for already declared type")
|
|
}
|
|
|
|
// Test visiting map to prevent infinite recursion
|
|
visiting := make(map[string]bool)
|
|
visiting["test_MyInt"] = true // Mark as visiting
|
|
err = hw.writeTypedefRecursive(namedInt, visiting)
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(already visiting) error = %v", err)
|
|
}
|
|
|
|
// Test invalid type (should trigger cType == "" path)
|
|
invalidType := types.Typ[types.Invalid]
|
|
err = hw.writeTypedefRecursive(invalidType, make(map[string]bool))
|
|
if err != nil {
|
|
t.Errorf("writeTypedefRecursive(invalid) error = %v", err)
|
|
}
|
|
}
|
|
|
|
// Test error conditions and edge cases
|
|
func TestEdgeCasesAndErrorConditions(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Test with properly initialized but empty function - skip nil test since it causes panic
|
|
// This would require creating a complete Function object which is complex
|
|
|
|
// Test writeCommonIncludes
|
|
hw.typeBuf.Reset()
|
|
err := hw.writeCommonIncludes()
|
|
if err != nil {
|
|
t.Fatalf("writeCommonIncludes() error = %v", err)
|
|
}
|
|
|
|
output := hw.typeBuf.String()
|
|
expectedIncludes := []string{
|
|
"GoString",
|
|
"GoSlice",
|
|
"GoMap",
|
|
"GoChan",
|
|
"GoInterface",
|
|
}
|
|
|
|
for _, expected := range expectedIncludes {
|
|
if !strings.Contains(output, expected) {
|
|
t.Errorf("Expected %s in common includes", expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test writeTo function
|
|
func TestWriteTo(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
hw := newCHeaderWriter(prog)
|
|
|
|
// Add some content to both buffers
|
|
hw.typeBuf.WriteString("typedef struct { int x; } TestStruct;\n")
|
|
hw.funcBuf.WriteString("void TestFunction(void);\n")
|
|
|
|
var output bytes.Buffer
|
|
err := hw.writeTo(&output)
|
|
if err != nil {
|
|
t.Fatalf("writeTo() error = %v", err)
|
|
}
|
|
|
|
got := output.String()
|
|
if !strings.Contains(got, "TestStruct") {
|
|
t.Error("writeTo() should write type definitions")
|
|
}
|
|
if !strings.Contains(got, "TestFunction") {
|
|
t.Error("writeTo() should write function declarations")
|
|
}
|
|
}
|
|
|
|
// Test genHeader function
|
|
func TestGenHeader(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
|
|
// Create a mock package
|
|
pkg := prog.NewPackage("", "testpkg")
|
|
|
|
var output bytes.Buffer
|
|
err := genHeader(prog, []ssa.Package{pkg}, &output)
|
|
if err != nil {
|
|
t.Fatalf("genHeader() error = %v", err)
|
|
}
|
|
|
|
got := output.String()
|
|
if !strings.Contains(got, "GoString") {
|
|
t.Error("genHeader() should include Go runtime types")
|
|
}
|
|
}
|
|
|
|
// Test GenCHeader function
|
|
func TestGenCHeader(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
|
|
// Create a mock package
|
|
pkg := prog.NewPackage("", "testpkg")
|
|
|
|
// Create a temp file for output
|
|
tmpfile, err := os.CreateTemp("", "test_header_*.h")
|
|
if err != nil {
|
|
t.Fatalf("Failed to create temp file: %v", err)
|
|
}
|
|
defer os.Remove(tmpfile.Name())
|
|
defer tmpfile.Close()
|
|
|
|
err = GenHeaderFile(prog, []ssa.Package{pkg}, "testlib", tmpfile.Name(), false)
|
|
if err != nil {
|
|
t.Fatalf("GenCHeader() error = %v", err)
|
|
}
|
|
|
|
// Read the file and verify content
|
|
content, err := os.ReadFile(tmpfile.Name())
|
|
if err != nil {
|
|
t.Fatalf("Failed to read generated file: %v", err)
|
|
}
|
|
|
|
got := string(content)
|
|
if !strings.Contains(got, "#ifndef") {
|
|
t.Error("GenCHeader() should generate header guards")
|
|
}
|
|
if !strings.Contains(got, "GoString") {
|
|
t.Error("GenCHeader() should include Go runtime types")
|
|
}
|
|
}
|
|
|
|
// Test genHeader with init function coverage
|
|
func TestGenHeaderWithInitFunction(t *testing.T) {
|
|
prog := ssa.NewProgram(nil)
|
|
|
|
// Create a package
|
|
pkgPath := "github.com/test/mypackage"
|
|
pkg := prog.NewPackage("", pkgPath)
|
|
|
|
// Create an init function signature: func()
|
|
initSig := types.NewSignature(nil, types.NewTuple(), types.NewTuple(), false)
|
|
|
|
// Create the init function with the expected name format
|
|
initFnName := pkgPath + ".init"
|
|
_ = pkg.NewFunc(initFnName, initSig, ssa.InGo)
|
|
|
|
// Test genHeader which should now detect the init function
|
|
var output bytes.Buffer
|
|
err := genHeader(prog, []ssa.Package{pkg}, &output)
|
|
if err != nil {
|
|
t.Fatalf("genHeader() error = %v", err)
|
|
}
|
|
|
|
got := output.String()
|
|
|
|
// Should contain Go runtime types
|
|
if !strings.Contains(got, "GoString") {
|
|
t.Error("genHeader() should include Go runtime types")
|
|
}
|
|
|
|
// Should contain the init function declaration with C-compatible name
|
|
expectedInitName := "github_com_test_mypackage_init"
|
|
if !strings.Contains(got, expectedInitName) {
|
|
t.Errorf("genHeader() should include init function declaration with name %s, got: %s", expectedInitName, got)
|
|
}
|
|
}
|