Flatten the methods of a trait into the using class's method table. * * Tree-sitter-php emits `use Trait1, Trait2;` inside a class body as a * `use_declaration` (distinct from the namespace-level * namespace_use_declaration). The optional conflict resolution block * uses `use_instead_of_clause` (for `Trait1::foo insteadof Trait2`) and * `use_as_clause` (for `Trait2::foo as fooAlt`). * * Ph
| 3449 | * - skip insteadof (rare in practice; full support deferred) |
| 3450 | */ |
| 3451 | static void flatten_trait_into_class(PHPLSPContext *ctx, CBMTypeRegistry *reg, const char *class_qn, |
| 3452 | const char *trait_qn, const char *alias_for_method, |
| 3453 | const char *only_method_name) { |
| 3454 | if (!class_qn || !trait_qn) |
| 3455 | return; |
| 3456 | /* Resolve trait through alias-style lookup if needed. */ |
| 3457 | const CBMRegisteredType *t = cbm_registry_lookup_type(reg, trait_qn); |
| 3458 | if (!t) |
| 3459 | t = lookup_type_with_project(ctx, trait_qn); |
| 3460 | const char *canonical_trait_qn = t ? t->qualified_name : trait_qn; |
| 3461 | |
| 3462 | /* Iterate registry funcs whose receiver_type is the trait. |
| 3463 | * |
| 3464 | * Self-substitution: when the trait's method has a return type that |
| 3465 | * names the trait itself (e.g. `tap(): self` registered as |
| 3466 | * NAMED(trait_qn)), rewrite it to NAMED(using_class_qn) so chains |
| 3467 | * like `$c->tap()->classMethod()` resolve correctly. */ |
| 3468 | for (int i = 0; i < reg->func_count; i++) { |
| 3469 | const CBMRegisteredFunc *src = ®->funcs[i]; |
| 3470 | if (!src->receiver_type || strcmp(src->receiver_type, canonical_trait_qn) != 0) { |
| 3471 | continue; |
| 3472 | } |
| 3473 | if (only_method_name && src->short_name && strcmp(src->short_name, only_method_name) != 0) { |
| 3474 | continue; |
| 3475 | } |
| 3476 | const char *new_short = alias_for_method ? alias_for_method : src->short_name; |
| 3477 | if (!new_short) |
| 3478 | continue; |
| 3479 | CBMRegisteredFunc rf; |
| 3480 | memset(&rf, 0, sizeof(rf)); |
| 3481 | rf.qualified_name = cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, new_short); |
| 3482 | rf.short_name = cbm_arena_strdup(ctx->arena, new_short); |
| 3483 | rf.receiver_type = class_qn; |
| 3484 | rf.min_params = src->min_params; |
| 3485 | |
| 3486 | /* Self-substitute the return type: any NAMED(trait_qn) in the |
| 3487 | * signature's return becomes NAMED(class_qn). */ |
| 3488 | const CBMType *new_sig = src->signature; |
| 3489 | if (src->signature && src->signature->kind == CBM_TYPE_FUNC && |
| 3490 | src->signature->data.func.return_types) { |
| 3491 | const CBMType *ret0 = src->signature->data.func.return_types[0]; |
| 3492 | if (ret0 && ret0->kind == CBM_TYPE_NAMED && ret0->data.named.qualified_name && |
| 3493 | strcmp(ret0->data.named.qualified_name, canonical_trait_qn) == 0) { |
| 3494 | const CBMType **rets = |
| 3495 | (const CBMType **)cbm_arena_alloc(ctx->arena, 2 * sizeof(*rets)); |
| 3496 | if (rets) { |
| 3497 | rets[0] = cbm_type_named(ctx->arena, class_qn); |
| 3498 | rets[1] = NULL; |
| 3499 | new_sig = cbm_type_func(ctx->arena, NULL, NULL, rets); |
| 3500 | } |
| 3501 | } |
| 3502 | } |
| 3503 | rf.signature = new_sig; |
| 3504 | cbm_registry_add_func(reg, rf); |
| 3505 | } |
| 3506 | } |
| 3507 | |
| 3508 | static void process_trait_use(PHPLSPContext *ctx, CBMTypeRegistry *reg, const char *class_qn, |
no test coverage detected