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
| 3671 | * - skip insteadof (rare in practice; full support deferred) |
| 3672 | */ |
| 3673 | static void flatten_trait_into_class(PHPLSPContext *ctx, CBMTypeRegistry *reg, const char *class_qn, |
| 3674 | const char *trait_qn, const char *alias_for_method, |
| 3675 | const char *only_method_name) { |
| 3676 | if (!class_qn || !trait_qn) |
| 3677 | return; |
| 3678 | /* Resolve trait through alias-style lookup if needed. */ |
| 3679 | const CBMRegisteredType *t = cbm_registry_lookup_type(reg, trait_qn); |
| 3680 | if (!t) |
| 3681 | t = lookup_type_with_project(ctx, trait_qn); |
| 3682 | const char *canonical_trait_qn = t ? t->qualified_name : trait_qn; |
| 3683 | |
| 3684 | /* A trait cannot meaningfully flatten into itself. PHP itself rejects |
| 3685 | * `trait T { use T; }` ("Trait T cannot use itself"), and an aliased |
| 3686 | * `use X as Y; use Y;` can resolve back onto the enclosing trait by short |
| 3687 | * name. Without this guard the loop below copies the trait's own methods |
| 3688 | * back onto it with receiver_type == canonical_trait_qn; because each copy |
| 3689 | * then re-matches the loop's filter while cbm_registry_add_func() keeps |
| 3690 | * growing reg->func_count, the iteration never terminates — it arena- |
| 3691 | * allocates a fresh method every pass until the process exhausts all |
| 3692 | * memory (observed: 40 GB+, freezing the host). See regression test |
| 3693 | * phplsp_trait_self_use_terminates. */ |
| 3694 | if (strcmp(class_qn, canonical_trait_qn) == 0) |
| 3695 | return; |
| 3696 | |
| 3697 | /* Iterate registry funcs whose receiver_type is the trait. |
| 3698 | * |
| 3699 | * Self-substitution: when the trait's method has a return type that |
| 3700 | * names the trait itself (e.g. `tap(): self` registered as |
| 3701 | * NAMED(trait_qn)), rewrite it to NAMED(using_class_qn) so chains |
| 3702 | * like `$c->tap()->classMethod()` resolve correctly. |
| 3703 | * |
| 3704 | * Snapshot the count before iterating: cbm_registry_add_func() below |
| 3705 | * appends to reg->funcs, so the loop must never visit entries it adds |
| 3706 | * during iteration (a mutate-while-iterating hazard). */ |
| 3707 | const int func_count_before = reg->func_count; |
| 3708 | for (int i = 0; i < func_count_before; i++) { |
| 3709 | const CBMRegisteredFunc *src = ®->funcs[i]; |
| 3710 | if (!src->receiver_type || strcmp(src->receiver_type, canonical_trait_qn) != 0) { |
| 3711 | continue; |
| 3712 | } |
| 3713 | if (only_method_name && src->short_name && strcmp(src->short_name, only_method_name) != 0) { |
| 3714 | continue; |
| 3715 | } |
| 3716 | const char *new_short = alias_for_method ? alias_for_method : src->short_name; |
| 3717 | if (!new_short) |
| 3718 | continue; |
| 3719 | CBMRegisteredFunc rf; |
| 3720 | memset(&rf, 0, sizeof(rf)); |
| 3721 | rf.qualified_name = cbm_arena_sprintf(ctx->arena, "%s.%s", class_qn, new_short); |
| 3722 | rf.short_name = cbm_arena_strdup(ctx->arena, new_short); |
| 3723 | rf.receiver_type = class_qn; |
| 3724 | rf.min_params = src->min_params; |
| 3725 | |
| 3726 | /* Self-substitute the return type: any NAMED(trait_qn) in the |
| 3727 | * signature's return becomes NAMED(class_qn). */ |
| 3728 | const CBMType *new_sig = src->signature; |
| 3729 | if (src->signature && src->signature->kind == CBM_TYPE_FUNC && |
| 3730 | src->signature->data.func.return_types) { |
no test coverage detected