| 1592 | /* ── call resolution ────────────────────────────────────────────── */ |
| 1593 | |
| 1594 | static void resolve_function_call(PHPLSPContext *ctx, TSNode call, CBMResolvedKind kind) { |
| 1595 | TSNode fn = ts_node_child_by_field_name(call, "function", 8); |
| 1596 | if (ts_node_is_null(fn)) |
| 1597 | return; |
| 1598 | char *name = php_node_text(ctx, fn); |
| 1599 | if (!name) |
| 1600 | return; |
| 1601 | |
| 1602 | /* In PHP, `$handler(...)` is syntactically a function-call expression but |
| 1603 | * a typed object value dispatches to its materialized `__invoke` method. |
| 1604 | * This is precise only for a first-class callable and a receiver type whose |
| 1605 | * exact method exists in the registry; otherwise leave it unresolved so |
| 1606 | * the usage pipeline can retain ordinary value semantics. */ |
| 1607 | if (kind == CBM_RESOLVED_CALL_REFERENCE && strcmp(ts_node_type(fn), "variable_name") == 0) { |
| 1608 | const CBMType *receiver = php_eval_expr_type(ctx, fn); |
| 1609 | const char *class_qn = NULL; |
| 1610 | if (receiver && receiver->kind == CBM_TYPE_NAMED) { |
| 1611 | class_qn = receiver->data.named.qualified_name; |
| 1612 | } else if (receiver && receiver->kind == CBM_TYPE_TEMPLATE) { |
| 1613 | class_qn = receiver->data.template_type.template_name; |
| 1614 | } |
| 1615 | const CBMRegisteredFunc *invoke = |
| 1616 | class_qn ? php_lookup_method(ctx, class_qn, "__invoke") : NULL; |
| 1617 | if (invoke && invoke->qualified_name) { |
| 1618 | emit_resolved_reason_kind(ctx, invoke->qualified_name, "php_invokable_reference", 0.95f, |
| 1619 | name, kind); |
| 1620 | } |
| 1621 | return; |
| 1622 | } |
| 1623 | |
| 1624 | /* `use function Foo\\bar;` mapping. */ |
| 1625 | const char *target = find_use(ctx, name, CBM_PHP_USE_FUNCTION); |
| 1626 | if (target) { |
| 1627 | const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, target); |
| 1628 | if (f) { |
| 1629 | emit_resolved_kind(ctx, f->qualified_name, "php_function_namespaced", 0.95f, kind); |
| 1630 | return; |
| 1631 | } |
| 1632 | } |
| 1633 | |
| 1634 | /* Current namespace, then global fallback. */ |
| 1635 | if (ctx->current_namespace_qn && ctx->current_namespace_qn[0]) { |
| 1636 | const char *ns_qn = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->current_namespace_qn, name); |
| 1637 | const CBMRegisteredFunc *f = cbm_registry_lookup_func(ctx->registry, ns_qn); |
| 1638 | if (f) { |
| 1639 | emit_resolved_kind(ctx, f->qualified_name, "php_function_namespaced", 0.95f, kind); |
| 1640 | return; |
| 1641 | } |
| 1642 | } |
| 1643 | |
| 1644 | /* Look for any free function with this short_name. Prefer the one in the |
| 1645 | * file's project (i.e. with the longest module-prefix overlap). */ |
| 1646 | const CBMRegisteredFunc *best = NULL; |
| 1647 | int best_score = -1; |
| 1648 | for (int i = 0; ctx->registry && i < ctx->registry->func_count; i++) { |
| 1649 | const CBMRegisteredFunc *cand = &ctx->registry->funcs[i]; |
| 1650 | if (cand->receiver_type) |
| 1651 | continue; |
no test coverage detected