| 1450 | /* ── call resolution ────────────────────────────────────────────── */ |
| 1451 | |
| 1452 | static void resolve_function_call(PHPLSPContext *ctx, TSNode call) { |
| 1453 | TSNode fn = ts_node_child_by_field_name(call, "function", 8); |
| 1454 | if (ts_node_is_null(fn)) |
| 1455 | return; |
| 1456 | char *name = php_node_text(ctx, fn); |
| 1457 | if (!name) |
| 1458 | return; |
| 1459 | |
| 1460 | /* `use function Foo\\bar;` mapping. */ |
| 1461 | const char *target = find_use(ctx, name, CBM_PHP_USE_FUNCTION); |
| 1462 | if (target) { |
| 1463 | const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, target); |
| 1464 | if (f) { |
| 1465 | emit_resolved(ctx, f->qualified_name, "php_function_namespaced", 0.95f); |
| 1466 | return; |
| 1467 | } |
| 1468 | } |
| 1469 | |
| 1470 | /* Current namespace, then global fallback. */ |
| 1471 | if (ctx->current_namespace_qn && ctx->current_namespace_qn[0]) { |
| 1472 | const char *ns_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace_qn, name); |
| 1473 | const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, ns_qn); |
| 1474 | if (f) { |
| 1475 | emit_resolved(ctx, f->qualified_name, "php_function_namespaced", 0.95f); |
| 1476 | return; |
| 1477 | } |
| 1478 | } |
| 1479 | |
| 1480 | /* Look for any free function with this short_name. Prefer the one in the |
| 1481 | * file's project (i.e. with the longest module-prefix overlap). */ |
| 1482 | const CBMRegisteredFunc *best = NULL; |
| 1483 | int best_score = -1; |
| 1484 | for (int i = 0; ctx->registry && i < ctx->registry->func_count; i++) { |
| 1485 | const CBMRegisteredFunc *cand = &ctx->registry->funcs[i]; |
| 1486 | if (cand->receiver_type) |
| 1487 | continue; |
| 1488 | if (!cand->short_name || strcmp(cand->short_name, name) != 0) |
| 1489 | continue; |
| 1490 | int score = 0; |
| 1491 | if (cand->qualified_name && ctx->module_qn) { |
| 1492 | const char *m = ctx->module_qn; |
| 1493 | const char *q = cand->qualified_name; |
| 1494 | while (*m && *q && *m == *q) { |
| 1495 | if (*m == '.') |
| 1496 | score++; |
| 1497 | m++; |
| 1498 | q++; |
| 1499 | } |
| 1500 | } |
| 1501 | if (score > best_score) { |
| 1502 | best_score = score; |
| 1503 | best = cand; |
| 1504 | } |
| 1505 | } |
| 1506 | if (best) { |
| 1507 | emit_resolved(ctx, best->qualified_name, "php_function_global_fallback", 0.70f); |
| 1508 | return; |
| 1509 | } |
no test coverage detected