@@ -457,10 +457,11 @@ static int64_t create_svc_route_node(cbm_pipeline_ctx_t *ctx, const char *url, c
457457
458458/* Insert an edge, splicing the call-site line (,"line":N) in before the closing
459459 * brace when one was captured. Mirrors finalize_and_emit() on the parallel path
460- * so CALLS edges carry their source line regardless of resolution path. Restricted
461- * to CALLS: route/config edge props feed full-only predump passes
462- * (create_route_nodes/create_data_flows), so altering them desyncs full vs
463- * incremental indexing. */
460+ * so CALLS edges carry their source line regardless of resolution path. Line and
461+ * args here are restricted to CALLS; HTTP_CALLS / ASYNC_CALLS props are finished
462+ * by the parallel formatter itself (emit_http_async_edge), because route/service
463+ * edge props feed full-only predump passes (create_route_nodes /
464+ * create_data_flows) and must not differ between the two passes. */
464465/* Append a ,"args":[{"i":0,"e":"<expr>","v":"<value>"},...] field onto a CALLS
465466 * edge's JSON props (the props buffer ends in '}'). The sequential pass omitted
466467 * this, so data_flow mode had no argument expressions to surface for small
@@ -585,8 +586,10 @@ static void emit_http_async_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call,
585586 * emit_http_async_service_edge: the old single format string closed the
586587 * method value's quote but not the broker's, emitting
587588 * "broker":"bullmq} on EVERY brokered ASYNC_CALLS edge — and its fixup
588- * only handled truncation, which never fires in the normal case (#898). */
589- char props [CBM_SZ_512 ];
589+ * only handled truncation, which never fires in the normal case (#898).
590+ * The call's args and the closing brace come from the parallel path's own
591+ * formatter, so a service edge is byte-identical whichever pass emits it. */
592+ char props [CBM_SZ_2K ];
590593 int n = snprintf (props , sizeof (props ), "{\"callee\":\"%s\",\"url_path\":\"%s\"" , esc_callee ,
591594 esc_url );
592595 if (method && n > 0 && (size_t )n < sizeof (props )) {
@@ -595,11 +598,8 @@ static void emit_http_async_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call,
595598 if (broker && n > 0 && (size_t )n < sizeof (props )) {
596599 n += snprintf (props + n , sizeof (props ) - (size_t )n , ",\"broker\":\"%s\"" , broker );
597600 }
598- if (n > 0 && (size_t )n < sizeof (props ) - 1 ) {
599- props [n ] = '}' ;
600- props [n + 1 ] = '\0' ;
601- }
602- calls_emit_edge (ctx -> gbuf , source -> id , route_id , edge_type , props , sizeof (props ), call );
601+ cbm_pipeline_close_call_props (props , n , call , false);
602+ cbm_gbuf_insert_edge (ctx -> gbuf , source -> id , route_id , edge_type , props );
603603}
604604
605605/* Classify a resolved call and emit the appropriate edge. */
@@ -671,7 +671,8 @@ static const cbm_gbuf_node_t *calls_find_source(cbm_pipeline_ctx_t *ctx, const c
671671static int resolve_single_call (cbm_pipeline_ctx_t * ctx , CBMCall * call , const CBMFileResult * result ,
672672 const char * rel , const char * module_qn , const char * * imp_keys ,
673673 const char * * imp_vals , int imp_count , CBMLanguage lang ,
674- const CBMImportArray * imports , const char * route_mount ) {
674+ const CBMImportArray * imports , const char * route_mount ,
675+ const cbm_php_vendor_names_t * php_vendor ) {
675676 const CBMResolvedCallArray * lsp_calls = & result -> resolved_calls ;
676677 const cbm_gbuf_node_t * source_node = calls_find_source (ctx , rel , call -> enclosing_func_qn );
677678 if (!source_node ) {
@@ -683,6 +684,12 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
683684 bool allow_tail = cbm_pipeline_lsp_allow_tail_match (lang );
684685 const CBMResolvedCall * lsp = cbm_pipeline_find_lsp_resolution_in_graph (
685686 lsp_calls , call , allow_tail , ctx -> gbuf , ctx -> project_name );
687+ /* #1186: a PHP call on a vendor type (`new Request`, or a receiver the LSP
688+ * typed as an imported vendor class) takes the LSP result or no target: a
689+ * same-named project symbol found by name alone is never the vendor's.
690+ * Service/route classification below still runs. Mirrors pass_parallel.c. */
691+ bool php_vendor_call = cbm_pipeline_php_vendor_call (php_vendor , result , call ) ||
692+ cbm_pipeline_lsp_external_receiver (lsp );
686693 if (lsp ) {
687694 bool exact_external_target = call -> requires_lsp_resolution &&
688695 cbm_pipeline_kotlin_external_target (lang , lsp -> callee_qn );
@@ -750,14 +757,19 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
750757 emit_http_async_edge (ctx , & routed , source_node , NULL , & svc_res , CBM_SVC_HTTP , false);
751758 return SKIP_ONE ;
752759 }
753- cbm_svc_kind_t csvc = cbm_service_pattern_match (call -> callee_name );
760+ /* A PHP call on a proven vendor class is classified by that class
761+ * (#1186): it skips the registry below, so the route-verb fallback would
762+ * otherwise mint a route registration for `GuzzleClient::get('/x')`. An
763+ * LSP row with a graph target returned above. MUST match pass_parallel.c. */
764+ cbm_svc_kind_t csvc ;
765+ const char * svc_name = cbm_pipeline_service_name (call , lsp , & csvc );
754766 if (csvc == CBM_SVC_HTTP || csvc == CBM_SVC_ASYNC ) {
755767 const char * cu = call -> first_string_arg ;
756768 bool chas_url = cu && cu [0 ] != '\0' &&
757769 (cu [0 ] == '/' || strstr (cu , "://" ) != NULL ||
758770 (csvc == CBM_SVC_ASYNC && strlen (cu ) > PAIR_LEN ));
759771 if (chas_url ) {
760- cbm_resolution_t svc_res = {.qualified_name = call -> callee_name ,
772+ cbm_resolution_t svc_res = {.qualified_name = svc_name ,
761773 .confidence = PC_SVC_PATTERN_CONF ,
762774 .strategy = "service_pattern" ,
763775 .candidate_count = 0 };
@@ -774,7 +786,7 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
774786 bool rust_external = lsp && cbm_pipeline_rust_external_target (
775787 lang , lsp -> strategy , lsp -> callee_qn , ctx -> project_name );
776788 cbm_resolution_t res = {0 };
777- if (!rust_external ) {
789+ if (!rust_external && ! php_vendor_call ) {
778790 res = cbm_registry_resolve_lang (ctx -> registry , call -> callee_name , module_qn , imp_keys ,
779791 imp_vals , imp_count , lang );
780792 /* Cross-language veto: a name-only guess never binds another
@@ -1147,6 +1159,8 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
11471159 const char * * imp_vals = NULL ;
11481160 int imp_count = 0 ;
11491161 build_import_map (ctx , rel , result , & imp_keys , & imp_vals , & imp_count );
1162+ cbm_php_vendor_names_t php_vendor ;
1163+ cbm_pipeline_php_vendor_names_build (ctx -> gbuf , ctx -> project_name , rel , result , & php_vendor );
11501164
11511165 /* Compute module QN for same-module resolution (directory-based for
11521166 * Java/Go so it matches their def-node QNs in the registry). */
@@ -1166,14 +1180,16 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
11661180 }
11671181 total_calls ++ ;
11681182 if (resolve_single_call (ctx , call , result , rel , module_qn , imp_keys , imp_vals ,
1169- imp_count , files [i ].language , & result -> imports , route_mount )) {
1183+ imp_count , files [i ].language , & result -> imports , route_mount ,
1184+ & php_vendor )) {
11701185 resolved ++ ;
11711186 } else {
11721187 unresolved ++ ;
11731188 }
11741189 }
11751190
11761191 free (module_qn );
1192+ cbm_pipeline_php_vendor_names_free (& php_vendor );
11771193 free_import_map (imp_keys , imp_vals , imp_count );
11781194 if (result_owned ) {
11791195 cbm_free_result (result );
0 commit comments