/* * test_go_lsp.c — Tests for Go LSP type-aware call resolution. * * Ports from internal/cbm/lsp_test.go (49 tests). * Categories: * - Single-file: param type inference, return type propagation, method chaining, * multi-return, channels, range, type switch, closures, composites, builtins, * type assertions, embedding, interfaces, generics, stdlib, diagnostics, etc. * - Cross-file: method dispatch, return type chains, interface dispatch, * field chains, map index, stdlib interfaces */ #include "test_framework.h" #include "cbm.h" #include "lsp/go_lsp.h" /* ── Helpers ───────────────────────────────────────────────────── */ /* Extract a Go file using cbm_extract_file (runs single-file LSP internally) */ static CBMFileResult *extract_go(const char *source) { return cbm_extract_file(source, (int)strlen(source), CBM_LANG_GO, "test", "main.go", 0, NULL, NULL); } /* Search resolved_calls for a match where caller contains callerSub * and callee contains calleeSub. Returns index or -1. */ static int find_resolved(const CBMFileResult *r, const char *callerSub, const char *calleeSub) { for (int i = 0; i < r->resolved_calls.count; i++) { const CBMResolvedCall *rc = &r->resolved_calls.items[i]; if (rc->caller_qn && strstr(rc->caller_qn, callerSub) && rc->callee_qn && strstr(rc->callee_qn, calleeSub)) return i; } return -1; } /* Assert that a resolved call exists. Returns the index. */ static int require_resolved(const CBMFileResult *r, const char *callerSub, const char *calleeSub) { int idx = find_resolved(r, callerSub, calleeSub); if (idx < 0) { printf(" MISSING resolved call: caller~%s -> callee~%s (have %d)\n", callerSub, calleeSub, r->resolved_calls.count); for (int i = 0; i < r->resolved_calls.count; i++) { const CBMResolvedCall *rc = &r->resolved_calls.items[i]; printf(" %s -> %s [%s %.2f]\n", rc->caller_qn ? rc->caller_qn : "(null)", rc->callee_qn ? rc->callee_qn : "(null)", rc->strategy ? rc->strategy : "(null)", rc->confidence); } } return idx; } /* Count resolved calls matching pattern */ static int count_resolved(const CBMFileResult *r, const char *callerSub, const char *calleeSub) { int n = 0; for (int i = 0; i < r->resolved_calls.count; i++) { const CBMResolvedCall *rc = &r->resolved_calls.items[i]; if (rc->caller_qn && strstr(rc->caller_qn, callerSub) && rc->callee_qn && strstr(rc->callee_qn, calleeSub)) n++; } return n; } /* Search cross-file resolved calls array */ static int find_resolved_arr(const CBMResolvedCallArray *arr, const char *callerSub, const char *calleeSub) { for (int i = 0; i < arr->count; i++) { const CBMResolvedCall *rc = &arr->items[i]; if (rc->caller_qn && strstr(rc->caller_qn, callerSub) && rc->callee_qn && strstr(rc->callee_qn, calleeSub)) return i; } return -1; } static int find_resolved_arr_confident(const CBMResolvedCallArray *arr, const char *callerSub, const char *calleeSub) { for (int i = 0; i < arr->count; i++) { const CBMResolvedCall *rc = &arr->items[i]; if (rc->confidence > 0 && rc->caller_qn && strstr(rc->caller_qn, callerSub) && rc->callee_qn && strstr(rc->callee_qn, calleeSub)) return i; } return -1; } /* ── Category 1: Parameter type inference ──────────────────────── */ TEST(golsp_param_type_simple) { CBMFileResult *r = extract_go("package main\n\n" "type Database struct{}\n\n" "func (d *Database) Query(sql string) string { return \"\" }\n\n" "func doWork(db *Database) {\n\tdb.Query(\"SELECT 1\")\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doWork", "Query"), 0); cbm_free_result(r); PASS(); } TEST(golsp_param_type_multi) { CBMFileResult *r = extract_go("package main\n\n" "type Logger struct{}\ntype Config struct{}\n\n" "func (l *Logger) Info(msg string) {}\n" "func (c *Config) Get(key string) string { return \"\" }\n\n" "func setup(log *Logger, cfg *Config) {\n" "\tlog.Info(\"starting\")\n\tcfg.Get(\"port\")\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "setup", "Info"), 0); ASSERT_GTE(require_resolved(r, "setup", "Get"), 0); cbm_free_result(r); PASS(); } /* ── Category 2: Return type propagation ───────────────────────── */ TEST(golsp_return_type) { CBMFileResult *r = extract_go("package main\n\n" "type File struct{}\n\n" "func (f *File) Read(buf []byte) int { return 0 }\n" "func Open(path string) *File { return nil }\n\n" "func doRead() {\n\tf := Open(\"/tmp/test\")\n\tf.Read(nil)\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doRead", "Read"), 0); cbm_free_result(r); PASS(); } TEST(golsp_return_type_chain) { CBMFileResult *r = extract_go("package main\n\n" "type Builder struct{}\ntype Result struct{}\n\n" "func (b *Builder) Build() *Result { return nil }\n" "func (r *Result) String() string { return \"\" }\n" "func NewBuilder() *Builder { return nil }\n\n" "func doChain() {\n\tb := NewBuilder()\n\tr := b.Build()\n\tr.String()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doChain", "Build"), 0); ASSERT_GTE(require_resolved(r, "doChain", "String"), 0); cbm_free_result(r); PASS(); } /* ── Category 3: Method chaining ───────────────────────────────── */ TEST(golsp_method_chaining) { CBMFileResult *r = extract_go("package main\n\n" "type Query struct{}\n\n" "func (q *Query) Where(cond string) *Query { return q }\n" "func (q *Query) Limit(n int) *Query { return q }\n" "func (q *Query) Execute() int { return 0 }\n" "func NewQuery() *Query { return nil }\n\n" "func doQuery() {\n\tNewQuery().Where(\"x=1\").Limit(10).Execute()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doQuery", "Where"), 0); ASSERT_GTE(require_resolved(r, "doQuery", "Limit"), 0); ASSERT_GTE(require_resolved(r, "doQuery", "Execute"), 0); cbm_free_result(r); PASS(); } /* ── Category 4: Multi-return ──────────────────────────────────── */ TEST(golsp_multi_return) { CBMFileResult *r = extract_go("package main\n\n" "type Conn struct{}\n\n" "func (c *Conn) Close() error { return nil }\n" "func Dial(addr string) (*Conn, error) { return nil, nil }\n\n" "func doConnect() {\n\tc, _ := Dial(\"localhost\")\n\tc.Close()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doConnect", "Close"), 0); cbm_free_result(r); PASS(); } /* ── Category 5: Channel receive ───────────────────────────────── */ TEST(golsp_channel_receive) { CBMFileResult *r = extract_go("package main\n\n" "type Event struct{}\n\n" "func (e *Event) Process() {}\n\n" "func handleEvents(ch chan *Event) {\n\te := <-ch\n\te.Process()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "handleEvents", "Process"), 0); cbm_free_result(r); PASS(); } /* ── Category 6: Range variables ───────────────────────────────── */ TEST(golsp_range_slice) { CBMFileResult *r = extract_go("package main\n\n" "type User struct{}\n\n" "func (u *User) Name() string { return \"\" }\n\n" "func listNames(users []*User) {\n" "\tfor _, u := range users {\n\t\tu.Name()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "listNames", "Name"), 0); cbm_free_result(r); PASS(); } TEST(golsp_range_map) { CBMFileResult *r = extract_go("package main\n\n" "type Service struct{}\n\n" "func (s *Service) Start() {}\n\n" "func startAll(services map[string]*Service) {\n" "\tfor _, svc := range services {\n\t\tsvc.Start()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "startAll", "Start"), 0); cbm_free_result(r); PASS(); } /* ── Category 7: Type switch ───────────────────────────────────── */ TEST(golsp_type_switch) { CBMFileResult *r = extract_go("package main\n\n" "type Dog struct{}\ntype Cat struct{}\n\n" "func (d *Dog) Speak() string { return \"woof\" }\n" "func (c *Cat) Speak() string { return \"meow\" }\n\n" "func describe(animal interface{}) {\n" "\tswitch a := animal.(type) {\n" "\tcase *Dog:\n\t\ta.Speak()\n" "\tcase *Cat:\n\t\ta.Speak()\n\t}\n}\n"); ASSERT_NOT_NULL(r); int n = count_resolved(r, "describe", "Speak"); ASSERT_GT(n, 0); /* Verify strategy is type_dispatch */ for (int i = 0; i < r->resolved_calls.count; i++) { const CBMResolvedCall *rc = &r->resolved_calls.items[i]; if (rc->caller_qn && strstr(rc->caller_qn, "describe") && rc->callee_qn && strstr(rc->callee_qn, "Speak") && rc->confidence > 0) { ASSERT_STR_EQ(rc->strategy, "lsp_type_dispatch"); } } cbm_free_result(r); PASS(); } /* ── Category 8: Closures ──────────────────────────────────────── */ TEST(golsp_closure) { CBMFileResult *r = extract_go("package main\n\n" "type Database struct{}\n\n" "func (db *Database) Query() {}\n\n" "func startWorker(db *Database) {\n" "\tgo func() {\n\t\tdb.Query()\n\t}()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "startWorker", "Query"), 0); cbm_free_result(r); PASS(); } /* ── Category 9: Composite literals ────────────────────────────── */ TEST(golsp_composite_literal) { CBMFileResult *r = extract_go("package main\n\n" "type Config struct{}\n\n" "func (c *Config) Validate() bool { return true }\n\n" "func makeConfig() {\n\tc := &Config{}\n\tc.Validate()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "makeConfig", "Validate"), 0); cbm_free_result(r); PASS(); } TEST(golsp_composite_literal_direct) { CBMFileResult *r = extract_go("package main\n\n" "type Handler struct{}\n\n" "func (h *Handler) ServeHTTP() {}\n\n" "func serve() {\n\t(&Handler{}).ServeHTTP()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "serve", "ServeHTTP"), 0); cbm_free_result(r); PASS(); } /* ── Category 10: Builtins (make) ──────────────────────────────── */ TEST(golsp_make_slice) { CBMFileResult *r = extract_go("package main\n\n" "type Item struct{}\n\n" "func (it *Item) Process() {}\n\n" "func work() {\n\titems := make([]*Item, 0)\n\titems[0].Process()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "work", "Process"), 0); cbm_free_result(r); PASS(); } /* ── Category 11: Type assertions ──────────────────────────────── */ TEST(golsp_type_assertion) { CBMFileResult *r = extract_go("package main\n\n" "type Writer struct{}\n\n" "func (w *Writer) Write(data []byte) int { return 0 }\n\n" "func writeData(x interface{}) {\n\tw := x.(*Writer)\n\tw.Write(nil)\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "writeData", "Write"), 0); cbm_free_result(r); PASS(); } /* ── Category 12: Struct embedding ─────────────────────────────── */ TEST(golsp_struct_embedding) { CBMFileResult *r = extract_go("package main\n\n" "type Base struct{}\n\n" "func (b *Base) Save() {}\n\n" "type Extended struct {\n\tBase\n}\n\n" "func persist(e *Extended) {\n\te.Save()\n}\n"); ASSERT_NOT_NULL(r); int idx = require_resolved(r, "persist", "Save"); ASSERT_GTE(idx, 0); ASSERT_STR_EQ(r->resolved_calls.items[idx].strategy, "lsp_embed_dispatch"); cbm_free_result(r); PASS(); } /* ── Category 13: Interface dispatch ───────────────────────────── */ TEST(golsp_interface_dispatch) { CBMFileResult *r = extract_go("package main\n\n" "type Writer interface {\n\tWrite(data []byte) int\n}\n\n" "func writeAll(w Writer) {\n\tw.Write(nil)\n}\n"); ASSERT_NOT_NULL(r); int idx = require_resolved(r, "writeAll", "Write"); ASSERT_GTE(idx, 0); ASSERT_STR_EQ(r->resolved_calls.items[idx].strategy, "lsp_interface_dispatch"); ASSERT_TRUE(r->resolved_calls.items[idx].confidence <= 0.90f); cbm_free_result(r); PASS(); } /* ── Category 14-15: Generics ──────────────────────────────────── */ TEST(golsp_explicit_generics) { CBMFileResult *r = extract_go( "package main\n\n" "type User struct{}\nfunc (u User) Name() string { return \"\" }\n\n" "type Result struct{}\nfunc (r Result) Value() int { return 0 }\n\n" "func Filter[T any, U any](s []T, pred func(T) bool) []U {\n\treturn nil\n}\n\n" "func Transform[T any, R any](input T, f func(T) R) R {\n\treturn f(input)\n}\n\n" "func main() {\n" "\tvar users []User\n" "\tu := Filter[User, User](users, nil)\n" "\t_ = u\n\n" "\tr := Transform[User, Result](users[0], func(u User) Result { return Result{} })\n" "\tr.Value()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "main", "Filter"), 0); ASSERT_GTE(require_resolved(r, "main", "Transform"), 0); ASSERT_GTE(require_resolved(r, "main", "Value"), 0); cbm_free_result(r); PASS(); } TEST(golsp_implicit_generics) { CBMFileResult *r = extract_go("package main\n\n" "type User struct{}\nfunc (u User) Name() string { return \"\" }\n\n" "func Filter[T any](s []T, pred func(T) bool) []T { return nil }\n\n" "func main() {\n" "\tvar users []User\n" "\tresult := Filter(users, func(u User) bool { return true })\n" "\tfor _, u := range result {\n\t\tu.Name()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "main", "Filter"), 0); ASSERT_GTE(require_resolved(r, "main", "Name"), 0); cbm_free_result(r); PASS(); } /* ── Return types + param names ────────────────────────────────── */ TEST(golsp_return_types_extracted) { CBMFileResult *r = extract_go("package main\n\n" "type Foo struct{}\n\n" "func GetFoo() *Foo { return nil }\n" "func Multi() (int, error) { return 0, nil }\n"); ASSERT_NOT_NULL(r); /* Verify that return_types are populated on definitions */ int found_getfoo = 0, found_multi = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "GetFoo") == 0) { found_getfoo = 1; /* return_type should contain "Foo" or "*Foo" */ ASSERT_NOT_NULL(r->defs.items[i].return_type); } if (strcmp(r->defs.items[i].name, "Multi") == 0) { found_multi = 1; } } ASSERT_TRUE(found_getfoo); ASSERT_TRUE(found_multi); cbm_free_result(r); PASS(); } TEST(golsp_param_names_extracted) { CBMFileResult *r = extract_go("package main\n\n" "func Process(name string, count int, verbose bool) {}\n"); ASSERT_NOT_NULL(r); /* Verify param_names on the Process def */ int found = 0; for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, "Process") == 0) { found = 1; /* param_names should be populated in signature */ ASSERT_NOT_NULL(r->defs.items[i].signature); } } ASSERT_TRUE(found); cbm_free_result(r); PASS(); } /* ── Direct function calls ─────────────────────────────────────── */ TEST(golsp_direct_func_call) { CBMFileResult *r = extract_go("package main\n\n" "func helper() int { return 42 }\n\n" "func caller() {\n\thelper()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "caller", "helper"), 0); cbm_free_result(r); PASS(); } /* ── Stdlib integration ────────────────────────────────────────── */ TEST(golsp_stdlib_os_open) { CBMFileResult *r = extract_go("package main\n\n" "import \"os\"\n\n" "func readFile(path string) {\n" "\tf, _ := os.Open(path)\n\tf.Close()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "readFile", "os.Open"), 0); ASSERT_GTE(require_resolved(r, "readFile", "Close"), 0); cbm_free_result(r); PASS(); } TEST(golsp_stdlib_fmt_sprintf) { CBMFileResult *r = extract_go("package main\n\n" "import \"fmt\"\n\n" "func format(name string) string {\n" "\treturn fmt.Sprintf(\"hello %s\", name)\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "format", "fmt.Sprintf"), 0); cbm_free_result(r); PASS(); } /* ── Select receive ────────────────────────────────────────────── */ TEST(golsp_select_receive) { CBMFileResult *r = extract_go("package main\n\n" "type Msg struct{}\n\n" "func (m *Msg) Process() {}\n\n" "func worker(ch chan *Msg, done chan bool) {\n" "\tselect {\n\tcase msg := <-ch:\n\t\tmsg.Process()\n" "\tcase <-done:\n\t\treturn\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "worker", "Process"), 0); cbm_free_result(r); PASS(); } /* ── Struct field access ───────────────────────────────────────── */ TEST(golsp_struct_field_access) { CBMFileResult *r = extract_go("package main\n\n" "type User struct {\n\tName string\n\tAge int\n\tProfile *Profile\n}\n\n" "type Profile struct {\n\tBio string\n}\n\n" "func (p *Profile) Summary() string { return \"\" }\n\n" "func showUser(u *User) {\n\t_ = u.Name\n\tp := u.Profile\n\tp.Summary()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "showUser", "Summary"), 0); cbm_free_result(r); PASS(); } /* ── Pointer/value receivers ───────────────────────────────────── */ TEST(golsp_pointer_value_receivers) { CBMFileResult *r = extract_go("package main\n\n" "type Conn struct{}\n\n" "func (c *Conn) Close() {}\n" "func (c Conn) Status() string { return \"\" }\n\n" "func usePointer(c *Conn) {\n\tc.Close()\n\tc.Status()\n}\n\n" "func useValue(c Conn) {\n\tc.Status()\n\tc.Close()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "usePointer", "Close"), 0); ASSERT_GTE(require_resolved(r, "usePointer", "Status"), 0); ASSERT_GTE(require_resolved(r, "useValue", "Status"), 0); ASSERT_GTE(require_resolved(r, "useValue", "Close"), 0); cbm_free_result(r); PASS(); } /* ── Diagnostics ───────────────────────────────────────────────── */ TEST(golsp_diagnostics) { CBMFileResult *r = extract_go("package main\n\n" "import \"unknownpkg\"\n\n" "func doStuff() {\n" "\tunknownpkg.Foo()\n\tx := getUnknown()\n\tx.Bar()\n}\n"); ASSERT_NOT_NULL(r); /* Should have at least one diagnostic (confidence==0, reason non-null) */ int diag_count = 0; for (int i = 0; i < r->resolved_calls.count; i++) { const CBMResolvedCall *rc = &r->resolved_calls.items[i]; if (rc->confidence == 0 && rc->reason != NULL && strlen(rc->reason) > 0) { diag_count++; ASSERT_STR_EQ(rc->strategy, "lsp_unresolved"); } } ASSERT_GT(diag_count, 0); cbm_free_result(r); PASS(); } /* ── Variadic args ─────────────────────────────────────────────── */ TEST(golsp_variadic_args) { CBMFileResult *r = extract_go("package main\n\n" "type Logger struct{}\n\n" "func (l *Logger) Info(msg string) {}\n\n" "func logAll(loggers ...*Logger) {\n" "\tfor _, l := range loggers {\n\t\tl.Info(\"hello\")\n\t}\n}\n"); ASSERT_NOT_NULL(r); int idx = require_resolved(r, "logAll", "Info"); ASSERT_GTE(idx, 0); ASSERT_TRUE(r->resolved_calls.items[idx].confidence > 0); cbm_free_result(r); PASS(); } /* ── Named returns ─────────────────────────────────────────────── */ TEST(golsp_named_returns) { CBMFileResult *r = extract_go("package main\n\n" "type Conn struct{}\n\n" "func (c *Conn) Close() {}\n\n" "func open() (conn *Conn, err error) {\n" "\tconn = &Conn{}\n\tconn.Close()\n\treturn\n}\n"); ASSERT_NOT_NULL(r); int idx = require_resolved(r, "open", "Close"); ASSERT_GTE(idx, 0); ASSERT_TRUE(r->resolved_calls.items[idx].confidence > 0); cbm_free_result(r); PASS(); } /* ── Type alias ────────────────────────────────────────────────── */ TEST(golsp_type_alias) { CBMFileResult *r = extract_go("package main\n\n" "type Base struct{}\n\n" "func (b *Base) DoWork() {}\n\n" "type Alias = Base\n\n" "func useAlias(a *Alias) {\n\ta.DoWork()\n}\n"); ASSERT_NOT_NULL(r); int idx = require_resolved(r, "useAlias", "DoWork"); ASSERT_GTE(idx, 0); ASSERT_TRUE(r->resolved_calls.items[idx].confidence > 0); cbm_free_result(r); PASS(); } /* ── Interface satisfaction (single implementer) ───────────────── */ TEST(golsp_interface_satisfaction) { CBMFileResult *r = extract_go( "package main\n\n" "type DataProcessor interface {\n" "\tProcessChunk(data []byte) (int, error)\n" "\tFinalize() error\n}\n\n" "type MyProcessor struct{}\n\n" "func (p *MyProcessor) ProcessChunk(data []byte) (int, error) { return 0, nil }\n" "func (p *MyProcessor) Finalize() error { return nil }\n\n" "func runProcessor(dp DataProcessor) {\n" "\tdp.ProcessChunk([]byte(\"hello\"))\n" "\tdp.Finalize()\n}\n"); ASSERT_NOT_NULL(r); int idx = find_resolved(r, "runProcessor", "ProcessChunk"); ASSERT_GTE(idx, 0); ASSERT_STR_EQ(r->resolved_calls.items[idx].strategy, "lsp_interface_resolve"); ASSERT_TRUE(r->resolved_calls.items[idx].confidence >= 0.89f); int idx2 = find_resolved(r, "runProcessor", "Finalize"); ASSERT_GTE(idx2, 0); ASSERT_STR_EQ(r->resolved_calls.items[idx2].strategy, "lsp_interface_resolve"); cbm_free_result(r); PASS(); } /* ── Package-level var/const ───────────────────────────────────── */ TEST(golsp_package_level_var) { CBMFileResult *r = extract_go("package main\n\n" "type Database struct{}\n\n" "func (d *Database) Query(sql string) string { return \"\" }\n\n" "func NewDatabase() *Database { return &Database{} }\n\n" "var db = NewDatabase()\n\n" "func handler() {\n\tdb.Query(\"SELECT 1\")\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "handler", "Query"), 0); cbm_free_result(r); PASS(); } TEST(golsp_package_level_const) { CBMFileResult *r = extract_go("package main\n\n" "type Logger struct{}\n\n" "func (l *Logger) Info(msg string) {}\n\n" "func NewLogger() *Logger { return &Logger{} }\n\n" "var logger = NewLogger()\n\n" "func doWork() {\n\tlogger.Info(\"starting\")\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doWork", "Info"), 0); cbm_free_result(r); PASS(); } /* ── If-init ───────────────────────────────────────────────────── */ TEST(golsp_if_init) { CBMFileResult *r = extract_go("package main\n\n" "type MyError struct{}\n\n" "func (e *MyError) Error() string { return \"\" }\n" "func (e *MyError) Code() int { return 0 }\n\n" "func getError() *MyError { return nil }\n\n" "func handle() {\n" "\tif err := getError(); err != nil {\n\t\terr.Code()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "handle", "Code"), 0); cbm_free_result(r); PASS(); } /* ── Embedded field promotion ──────────────────────────────────── */ TEST(golsp_embedded_field_promotion) { CBMFileResult *r = extract_go( "package main\n\n" "type Inner struct {\n\tName string\n}\n\n" "type Outer struct {\n\tInner\n}\n\n" "type Processor struct{}\n\n" "func (p *Processor) Process(name string) {}\n\n" "func doWork() {\n\to := &Outer{}\n\tp := &Processor{}\n\tp.Process(o.Name)\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "doWork", "Process"), 0); cbm_free_result(r); PASS(); } /* ── For-init ──────────────────────────────────────────────────── */ TEST(golsp_for_init) { CBMFileResult *r = extract_go("package main\n\n" "type Counter struct{}\n\n" "func (c *Counter) Value() int { return 0 }\n\n" "func NewCounter() *Counter { return &Counter{} }\n\n" "func loop() {\n" "\tfor c := NewCounter(); c.Value() < 10; {\n\t\tc.Value()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "loop", "Value"), 0); cbm_free_result(r); PASS(); } /* ── Type conversion ───────────────────────────────────────────── */ TEST(golsp_type_conversion) { CBMFileResult *r = extract_go("package main\n\n" "type MyString string\n\n" "func (s MyString) Upper() string { return \"\" }\n\n" "func convert() {\n\ts := MyString(\"hello\")\n\ts.Upper()\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "convert", "Upper"), 0); cbm_free_result(r); PASS(); } /* ── Multi-name var ────────────────────────────────────────────── */ TEST(golsp_multi_name_var) { CBMFileResult *r = extract_go("package main\n\n" "type Config struct{}\n\n" "func (c *Config) Get(key string) string { return \"\" }\n\n" "var cfg1, cfg2 *Config\n\n" "func readConfig() {\n\tcfg1.Get(\"port\")\n\tcfg2.Get(\"host\")\n}\n"); ASSERT_NOT_NULL(r); int n = count_resolved(r, "readConfig", "Get"); ASSERT_GTE(n, 2); cbm_free_result(r); PASS(); } /* ── Switch init ───────────────────────────────────────────────── */ TEST(golsp_switch_init) { CBMFileResult *r = extract_go("package main\n\n" "type Validator struct{}\n\n" "func (v *Validator) Check() bool { return true }\n\n" "func NewValidator() *Validator { return &Validator{} }\n\n" "func validate() {\n" "\tswitch v := NewValidator(); v.Check() {\n" "\tcase true:\n\t\tv.Check()\n\t}\n}\n"); ASSERT_NOT_NULL(r); ASSERT_GTE(require_resolved(r, "validate", "Check"), 0); cbm_free_result(r); PASS(); } /* ── Interface method dispatch (single file) ───────────────────── */ TEST(golsp_interface_method_single_file) { CBMFileResult *r = extract_go("package main\n\n" "type Binder interface {\n\tBind(target any) error\n}\n\n" "type DefaultBinder struct{}\n\n" "func (d *DefaultBinder) Bind(target any) error { return nil }\n\n" "func process(b Binder) {\n\tb.Bind(\"hello\")\n}\n"); ASSERT_NOT_NULL(r); int idx = find_resolved(r, "process", "Bind"); ASSERT_GTE(idx, 0); const char *s = r->resolved_calls.items[idx].strategy; ASSERT_TRUE(strcmp(s, "lsp_interface_resolve") == 0 || strcmp(s, "lsp_interface_dispatch") == 0); cbm_free_result(r); PASS(); } /* ── Interface method dispatch (field chain) ───────────────────── */ TEST(golsp_interface_method_field_chain) { CBMFileResult *r = extract_go("package main\n\n" "type Binder interface {\n\tBind(target any) error\n}\n\n" "type DefaultBinder struct{}\n" "func (d *DefaultBinder) Bind(target any) error { return nil }\n\n" "type App struct {\n\tBinder Binder\n}\n\n" "type Context struct {\n\tapp *App\n}\n\n" "func (c *Context) process() {\n\tc.app.Binder.Bind(\"hello\")\n}\n"); ASSERT_NOT_NULL(r); int idx = find_resolved(r, "process", "Bind"); ASSERT_GTE(idx, 0); const char *s = r->resolved_calls.items[idx].strategy; ASSERT_TRUE(strcmp(s, "lsp_interface_resolve") == 0 || strcmp(s, "lsp_interface_dispatch") == 0); cbm_free_result(r); PASS(); } /* ── Cross-file tests (use cbm_run_go_lsp_cross directly) ──────── */ TEST(golsp_crossfile_method_dispatch) { const char *source = "package main\n\n" "import \"myapp/db\"\n\n" "func doQuery() {\n" "\tconn := db.Connect(\"localhost\")\n" "\tconn.Query(\"SELECT 1\")\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.doQuery", .short_name = "doQuery", .label = "Function", .def_module_qn = "test.main"}, {.qualified_name = "myapp/db.Connect", .short_name = "Connect", .label = "Function", .def_module_qn = "myapp/db", .return_types = "*Conn"}, {.qualified_name = "myapp/db.Conn", .short_name = "Conn", .label = "Type", .def_module_qn = "myapp/db"}, {.qualified_name = "myapp/db.Conn.Query", .short_name = "Query", .label = "Method", .def_module_qn = "myapp/db", .receiver_type = "myapp/db.Conn"}, }; const char *imp_names[] = {"db"}; const char *imp_qns[] = {"myapp/db"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names, imp_qns, 1, NULL, &out); ASSERT_GTE(find_resolved_arr(&out, "doQuery", "Connect"), 0); int idx = find_resolved_arr(&out, "doQuery", "Query"); ASSERT_GTE(idx, 0); ASSERT_STR_EQ(out.items[idx].strategy, "lsp_type_dispatch"); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_return_type_chain) { const char *source = "package main\n\n" "import \"myapp/repo\"\n\n" "func showUser() {\n" "\tuser := repo.GetUser(1)\n" "\tuser.Name()\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.showUser", .short_name = "showUser", .label = "Function", .def_module_qn = "test.main"}, {.qualified_name = "myapp/repo.GetUser", .short_name = "GetUser", .label = "Function", .def_module_qn = "myapp/repo", .return_types = "*User"}, {.qualified_name = "myapp/repo.User", .short_name = "User", .label = "Type", .def_module_qn = "myapp/repo"}, {.qualified_name = "myapp/repo.User.Name", .short_name = "Name", .label = "Method", .def_module_qn = "myapp/repo", .receiver_type = "myapp/repo.User"}, }; const char *imp_names[] = {"repo"}; const char *imp_qns[] = {"myapp/repo"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names, imp_qns, 1, NULL, &out); ASSERT_GTE(find_resolved_arr(&out, "showUser", "Name"), 0); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_interface_dispatch) { const char *source = "package main\n\n" "import \"myapp/svc\"\n\n" "func handler(b svc.Binder) {\n\tb.Bind(\"data\")\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.handler", .short_name = "handler", .label = "Function", .def_module_qn = "test.main"}, {.qualified_name = "myapp/svc.Binder", .short_name = "Binder", .label = "Interface", .def_module_qn = "myapp/svc", .is_interface = true}, {.qualified_name = "myapp/svc.DefaultBinder", .short_name = "DefaultBinder", .label = "Type", .def_module_qn = "myapp/svc"}, {.qualified_name = "myapp/svc.DefaultBinder.Bind", .short_name = "Bind", .label = "Method", .def_module_qn = "myapp/svc", .receiver_type = "myapp/svc.DefaultBinder"}, }; const char *imp_names[] = {"svc"}; const char *imp_qns[] = {"myapp/svc"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 4, imp_names, imp_qns, 1, NULL, &out); int idx = find_resolved_arr_confident(&out, "handler", "Bind"); ASSERT_GTE(idx, 0); const char *s = out.items[idx].strategy; ASSERT_TRUE(strcmp(s, "lsp_interface_resolve") == 0 || strcmp(s, "lsp_interface_dispatch") == 0); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_interface_field_chain) { const char *source = "package main\n\n" "import \"myapp/echo\"\n\n" "type Context struct {\n\techo *echo.Echo\n}\n\n" "func (c *Context) process() {\n\tc.echo.Binder.Bind(\"hello\")\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.Context", .short_name = "Context", .label = "Type", .def_module_qn = "test.main"}, {.qualified_name = "test.main.Context.process", .short_name = "process", .label = "Method", .def_module_qn = "test.main", .receiver_type = "test.main.Context"}, {.qualified_name = "myapp/echo.Echo", .short_name = "Echo", .label = "Type", .def_module_qn = "myapp/echo", .field_defs = "Binder:Binder"}, {.qualified_name = "myapp/echo.Binder", .short_name = "Binder", .label = "Interface", .def_module_qn = "myapp/echo", .is_interface = true}, {.qualified_name = "myapp/echo.DefaultBinder", .short_name = "DefaultBinder", .label = "Type", .def_module_qn = "myapp/echo"}, {.qualified_name = "myapp/echo.DefaultBinder.Bind", .short_name = "Bind", .label = "Method", .def_module_qn = "myapp/echo", .receiver_type = "myapp/echo.DefaultBinder"}, }; const char *imp_names[] = {"echo"}; const char *imp_qns[] = {"myapp/echo"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 6, imp_names, imp_qns, 1, NULL, &out); int idx = find_resolved_arr_confident(&out, "process", "Bind"); ASSERT_GTE(idx, 0); ASSERT_TRUE(out.items[idx].confidence >= 0.8f); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_map_index) { const char *source = "package main\n\n" "import \"myapp/echo\"\n\n" "func dispatch(vhosts map[string]*echo.Echo, host string) {\n" "\tif vh, ok := vhosts[host]; ok {\n" "\t\tvh.ServeHTTP(nil, nil)\n\t}\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.dispatch", .short_name = "dispatch", .label = "Function", .def_module_qn = "test.main"}, {.qualified_name = "myapp/echo.Echo", .short_name = "Echo", .label = "Type", .def_module_qn = "myapp/echo"}, {.qualified_name = "myapp/echo.Echo.ServeHTTP", .short_name = "ServeHTTP", .label = "Method", .def_module_qn = "myapp/echo", .receiver_type = "myapp/echo.Echo"}, }; const char *imp_names[] = {"echo"}; const char *imp_qns[] = {"myapp/echo"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 3, imp_names, imp_qns, 1, NULL, &out); int idx = find_resolved_arr_confident(&out, "dispatch", "ServeHTTP"); ASSERT_GTE(idx, 0); ASSERT_STR_EQ(out.items[idx].strategy, "lsp_type_dispatch"); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_stdlib_interface) { const char *source = "package main\n\n" "import \"context\"\n\n" "func process(ctx context.Context) {\n" "\t<-ctx.Done()\n\tctx.Err()\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.process", .short_name = "process", .label = "Function", .def_module_qn = "test.main"}, }; const char *imp_names[] = {"context"}; const char *imp_qns[] = {"context"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 1, imp_names, imp_qns, 1, NULL, &out); ASSERT_GTE(find_resolved_arr_confident(&out, "process", "Done"), 0); ASSERT_GTE(find_resolved_arr_confident(&out, "process", "Err"), 0); cbm_arena_destroy(&arena); PASS(); } TEST(golsp_crossfile_local_interface_single_impl) { const char *source = "package main\n\n" "import \"myapp/svc\"\n\n" "func process(s svc.Store) {\n\ts.Get(\"key\")\n\ts.Put(\"key\", \"val\")\n}\n"; CBMLSPDef defs[] = { {.qualified_name = "test.main.process", .short_name = "process", .label = "Function", .def_module_qn = "test.main"}, {.qualified_name = "myapp/svc.Store", .short_name = "Store", .label = "Interface", .def_module_qn = "myapp/svc", .is_interface = true, .method_names_str = "Get|Put"}, {.qualified_name = "myapp/svc.RedisStore", .short_name = "RedisStore", .label = "Class", .def_module_qn = "myapp/svc"}, {.qualified_name = "myapp/svc.RedisStore.Get", .short_name = "Get", .label = "Method", .def_module_qn = "myapp/svc", .receiver_type = "myapp/svc.RedisStore"}, {.qualified_name = "myapp/svc.RedisStore.Put", .short_name = "Put", .label = "Method", .def_module_qn = "myapp/svc", .receiver_type = "myapp/svc.RedisStore"}, }; const char *imp_names[] = {"svc"}; const char *imp_qns[] = {"myapp/svc"}; CBMArena arena; cbm_arena_init(&arena); CBMResolvedCallArray out = {0}; cbm_run_go_lsp_cross(&arena, source, (int)strlen(source), "test.main", defs, 5, imp_names, imp_qns, 1, NULL, &out); int idxGet = find_resolved_arr_confident(&out, "process", "Get"); ASSERT_GTE(idxGet, 0); ASSERT_STR_EQ(out.items[idxGet].strategy, "lsp_interface_resolve"); ASSERT_STR_EQ(out.items[idxGet].callee_qn, "myapp/svc.RedisStore.Get"); int idxPut = find_resolved_arr_confident(&out, "process", "Put"); ASSERT_GTE(idxPut, 0); ASSERT_STR_EQ(out.items[idxPut].strategy, "lsp_interface_resolve"); cbm_arena_destroy(&arena); PASS(); } /* ── Suite ─────────────────────────────────────────────────────── */ SUITE(go_lsp) { /* Single-file: param type inference */ RUN_TEST(golsp_param_type_simple); RUN_TEST(golsp_param_type_multi); /* Return type propagation */ RUN_TEST(golsp_return_type); RUN_TEST(golsp_return_type_chain); /* Method chaining */ RUN_TEST(golsp_method_chaining); /* Multi-return */ RUN_TEST(golsp_multi_return); /* Channel receive */ RUN_TEST(golsp_channel_receive); /* Range variables */ RUN_TEST(golsp_range_slice); RUN_TEST(golsp_range_map); /* Type switch */ RUN_TEST(golsp_type_switch); /* Closures */ RUN_TEST(golsp_closure); /* Composite literals */ RUN_TEST(golsp_composite_literal); RUN_TEST(golsp_composite_literal_direct); /* Builtins */ RUN_TEST(golsp_make_slice); /* Type assertions */ RUN_TEST(golsp_type_assertion); /* Struct embedding */ RUN_TEST(golsp_struct_embedding); /* Interface dispatch */ RUN_TEST(golsp_interface_dispatch); /* Generics */ RUN_TEST(golsp_explicit_generics); RUN_TEST(golsp_implicit_generics); /* Return types + param names */ RUN_TEST(golsp_return_types_extracted); RUN_TEST(golsp_param_names_extracted); /* Direct calls */ RUN_TEST(golsp_direct_func_call); /* Stdlib */ RUN_TEST(golsp_stdlib_os_open); RUN_TEST(golsp_stdlib_fmt_sprintf); /* Select receive */ RUN_TEST(golsp_select_receive); /* Struct field access */ RUN_TEST(golsp_struct_field_access); /* Pointer/value receivers */ RUN_TEST(golsp_pointer_value_receivers); /* Diagnostics */ RUN_TEST(golsp_diagnostics); /* Variadic */ RUN_TEST(golsp_variadic_args); /* Named returns */ RUN_TEST(golsp_named_returns); /* Type alias */ RUN_TEST(golsp_type_alias); /* Interface satisfaction */ RUN_TEST(golsp_interface_satisfaction); /* Package-level vars */ RUN_TEST(golsp_package_level_var); RUN_TEST(golsp_package_level_const); /* If-init, for-init, switch-init */ RUN_TEST(golsp_if_init); RUN_TEST(golsp_embedded_field_promotion); RUN_TEST(golsp_for_init); RUN_TEST(golsp_type_conversion); RUN_TEST(golsp_multi_name_var); RUN_TEST(golsp_switch_init); /* Interface method dispatch variants */ RUN_TEST(golsp_interface_method_single_file); RUN_TEST(golsp_interface_method_field_chain); /* Cross-file */ RUN_TEST(golsp_crossfile_method_dispatch); RUN_TEST(golsp_crossfile_return_type_chain); RUN_TEST(golsp_crossfile_interface_dispatch); RUN_TEST(golsp_crossfile_interface_field_chain); RUN_TEST(golsp_crossfile_map_index); RUN_TEST(golsp_crossfile_stdlib_interface); RUN_TEST(golsp_crossfile_local_interface_single_impl); }