cs_lookup_method searches the other declarations of a type (same simple
name, same declared namespace: `partial` pieces, reference-assembly
copies) before it walks the base classes. With global-namespace types now
carrying the namespace "", every same-name type declared without a
namespace anywhere in the repository became a twin of every other one.
Across a repository that is a different type per assembly (each test
project has its own Program or Helper), so a member missing on the
receiver bound to an unrelated type ahead of the receiver's own base.
cs_namespace_twins returns no twins for a type in the global namespace.
Test: cs_lsp::cslsp_global_namespace_types_are_not_partial_twins runs the
production Tier-2 C# registry builder over cross-file defs: two unrelated
global Helper types and a base class. RED without the guard:
stop_base=0 stop_unrelated_global_twin=1. GREEN with it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
C# looks a simple type name up namespace by namespace, innermost outward.
At each namespace N it checks the members of N, then the usings of the
namespace declaration enclosing the reference. The global namespace is
the last N, and its declaration is the compilation unit. So a type
declared without a namespace hides a type that only a compilation-unit
`using` imports, while a `using` inside a namespace body still wins over
the global namespace. Step 8b used to rank every using before the global
namespace.
- CBMCSUsing records whether a directive sits inside a namespace body
(in_namespace). cs_collect_imports tracks it per node, including the
compilation-unit siblings after a file-scoped namespace; the cross-file
entry pre-loads the pipeline import list without placement and the AST
walk upgrades it.
- cs_visible_namespaces ranks a simple name as: enclosing namespaces
innermost outward, namespace-body usings, the global namespace "", and
last the compilation-unit usings.
Ranking the global namespace that high also requires that only a type
proven global claims it. The vendored grammar does not parse a
`ref partial struct` with members, and error recovery dissolves the
enclosing namespace, so every type after it in that namespace surfaced
with no namespace. Mapped to "" those types outranked the real, imported
declaration: on dotnet/runtime 5,313 CALLS moved from implementation
files into reference assemblies (System.Runtime.cs, which declares
DefaultInterpolatedStringHandler as a `ref partial struct`).
cs_type_decl_namespace now returns "" only when the compilation unit
parsed without errors and NULL (unknown, invisible to step 8b, as on
main) otherwise; cs_top_level_namespace passes that through.
Tests:
- parallel_csharp_nested_type_and_global_namespace_binding: the
compilation-unit `using` probe now expects the global type (spec), and
a new namespace-body `using` probe expects the imported one. RED with
the previous order: cu_using=0/0.
- parallel_csharp_parse_damaged_namespace_is_not_global (sequential and
parallel). RED without the clean-parse rule: real=0 no_damaged=0 on
both paths.
- cs_lsp::cslsp_decl_namespace_global_needs_clean_parse: extractor
contract ("" for a clean global type, the namespace when found, never
"" after a parse error).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolving a qualified type name A.B that no namespace binds now resolves
the qualifier A as a type first (nested types). A using alias whose target
is qualified by the alias itself re-enters that walk through the alias
step without end: `using Loop = Loop.Inner;` resolves Loop to the target
Loop.Inner, whose qualifier is Loop again, and a mutual pair (`using
Ping = Pong.Inner; using Pong = Ping.Inner;`) cycles the same way. Both
are legal C# when namespaces of those names exist, and the walk overflowed
the stack on them.
The walk now carries a depth counter in the per-file context and stops
after CS_QUALIFIER_MAX_DEPTH (8) levels. One level is one type-nesting
step: a namespace-qualified top-level type binds without recursing, so
real nesting stays far below the bound.
Test: cs_lsp::cslsp_cyclic_alias_qualifier_terminates (self and mutual
alias cycles; the file must still resolve Svc.Go). RED without the bound:
AddressSanitizer stack-overflow, exit 134. GREEN with it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Follow-up to #2347 (issue #2120). Step 8b binds same-name TOP-LEVEL types
by declared namespace, but two shapes still fell through to the short-name
fallback, where the prefix score against a path-derived QN is 0 for every
variant and registration order decided:
1. A qualified A.B whose qualifier A is a TYPE, not a namespace:
new Command.Result(...) and new Grain.Handler(dep).Handle(...).
cs_visible_namespaces only tried A as a namespace, and nested types
carry no namespace_qn, so the scan never had a candidate. Resolution
now falls back to resolving the qualifier as a type (recursively, one
segment shorter) and binding the nested member through the registry.
2. A top-level type with no namespace declaration (global namespace):
cs_top_level_namespace returned NULL for it, making it invisible to
step 8b. Top-level C# types now register namespace_qn "" and
cs_visible_namespaces appends the global namespace last.
Test: parallel_csharp_nested_type_and_global_namespace_binding covers a
nested-type constructor and the member call on its result, a chained
constructor receiver, and a same-name type split across the global
namespace and an imported namespace, in the sequential pipeline and in
the parallel pipeline with the production Tier-2 C# registry. Decoys are
registered first. Adapted to current main: the two constructor-edge
checks gate the parallel path only, because the sequential constructor
edge is still drawn by the textual constructor carrier, a layer this
change does not touch.
Distilled from #2401: the net change of commits d11be54f and 613060ea.
The temporary debug prints added in d11be54f and the stale copy of #2347
the branch carried (already on main as 50d19f85) are not taken.
Co-authored-by: Ersin Tarhan <ersintarhan@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
clang-format the new snprintf in detect_url_in_args (the line ran past
100 columns, the CI lint failure) and raise eprops to 1K: two escaped
255-byte strings plus the keys and the verb no longer fit in 512.
Add a parallel-route test with wrapper calls that actually reach the
arg-URL heuristic: a project wrapper whose verb is not a route suffix
(this.api.head / this.api.options, URL first) and a route-verb wrapper
whose URL sits in a keyword the registration reader does not take as a
path (self.session.get(resource=...)). Each gets one HTTP_CALLS edge typed
with its verb and no ANY twin. The sequential resolver has no arg-URL
heuristic (#2848), so the test is parallel only.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
All three seeded client edges carried "GET", so match_http_routes never
entered its callee-verb fallback. getItem and getJob are now stored
without a method, as an index that did not record the verb writes them.
Without the fallback the method-less fuzzy match lands on the DELETE and
POST routes seeded first; with it both reach their GET handlers. getItems
keeps its explicit GET.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
calls_emit_unresolved_service order: resolve_single_call already emits
every callee-matched HTTP/ASYNC call with a URL argument before it reaches
the empty-resolution fallback, and the global fetch has no route suffix,
so the order inside the fallback never decides an edge.
route_edge_visitor method fallback: every HTTP_CALLS emitter writes
"method" exactly when cbm_service_pattern_http_method(callee) finds a verb,
so recomputing it from the stored callee always yields NULL there.
Both files return to their main versions.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The new HttpClient receiver rows matched by prefix, and an uppercase
letter after an id counts as CamelCase glue, so "this.http" also matched
this.httpAdapter.get (a NestJS route registration) and this.httpServer.post,
and "$http" matched $httpBackend.expectGET (an AngularJS test double).
Each receiver row now ends in a dot, like "gin.", plus a line-break twin
(LF, and CR for CRLF files): a chain wrapped before the verb keeps the break
in its callee text ("this.http\n .post"). On
gothinkster/angular-realworld-example-app 5 of the 18 client calls are
written that way; with dot-only rows they fell back to phantom route
registrations. "HttpClient" duplicated the Java/C# row and
"this.httpClient." is covered by "httpClient.".
Unit cases pin both directions, and the Angular pipeline test gains a
wrapped call on both resolver routes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three small items promised in the #2315 review; behaviour is otherwise
unchanged.
1. A failed ADR restore on artifact import is no longer silent
(src/pipeline/artifact.c). The import dropped both a failed reopen of
the installed DB and the status of
cbm_store_adr_restore_from_graph_if_absent, so a team ADR that never
reached the sidecar left no trace. The restore moves into
artifact_import_restore_adr, which logs
level=warn msg=artifact.import err=adr_restore detail=<store error>
path=<db> on either failure. The import still returns 0: the graph DB
is already installed when the restore runs, and the restore stays
best-effort as documented. adr_restore_if_absent now sets the store
error for its begin, copy and commit failures, so the logged detail
names the failing step.
2. The doc comment of adr_copy_legacy_once sat above adr_copy_rows,
stacked on top of the comment that documents adr_copy_rows, which left
adr_copy_legacy_once undocumented. It now sits above
adr_copy_legacy_once (src/store/store.c).
3. adr_restore_if_absent no longer inserts the migrated marker. That
INSERT OR IGNORE could never change state. Its only caller,
cbm_store_adr_restore_from_graph_if_absent, takes the sidecar handle
from adr_conn_write, which hands out a handle (cached or fresh) only
after adr_copy_legacy_once returned OK, and that function returns OK
only when the marker row is already committed (fast path, or the
re-check under the write lock) or it just inserted and committed it.
No code deletes an adr_meta row, so the insert hit the existing
primary key every time, and its result was discarded anyway.
Tests: artifact_import_logs_failed_adr_restore puts a directory at
<db>.adr.db (the hook-free trick from the #2315 pipeline test), then
asserts the import still succeeds, the graph DB is installed, and the
warn line carries the store reason. It is red without the artifact.c
change. artifact_export_roundtrip_keeps_adr now also asserts that the
fresh destination sidecar is marked migrated after the import, which
pins the invariant item 3 relies on.
Refs #2315
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Resolved tests/test_pipeline.c as the main version plus this PR diff: the owner-segment field-hint tests follow the new bare init tests, and both RUN_TEST pairs stay registered (pass_parallel.c merged cleanly).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Go gives `init` no binding in the package block: init functions cannot
be referred to from anywhere in a program, and a method or field named
init needs a selector. A bare `init` is therefore always a local name -
a closure (`init := func(ctx) {...}; init(ctx)`), a parameter, or the
`package init` clause of a directory named init.
Before #1915 same_module bound such a name to the flat package init.
With per-file init QNs it now falls through to the short-name fallback
and binds unrelated methods named init. On kubernetes that minted 24
CALLS (22 e2e storage DefineTests closures among them), 28 WRITES,
7 USAGE and one derived TESTS edge, all false.
cbm_go_suppress_bare_init_bind refuses, for a bare Go `init`, any
registry binding onto a Function (every Go Function named init is a
package init) and a weak short-name binding onto a Method or Field.
lsp_* answers and selector calls (`w.init()`) are untouched. Wired at
the CALLS, USAGE and READS/WRITES seams of both resolvers.
Kubernetes, full index, main vs branch: only those 60 edges removed,
nodes identical, no resolver edge added; the 14 LSP-typed selector
CALLS into init methods are unchanged.
Tests: pipeline_go_bare_init_binds_nothing (+ parallel twin) are RED
with the predicate disabled (closure CALLS, parameter CALLS, closure
WRITES, package-clause USAGE onto Watcher.init) and GREEN with it; the
selector control keeps Watcher.Start/Boot -> Watcher.init.
go_bare_init_never_binds_init_namesake pins the predicate.
Refs #1915
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Go no longer runs the parallel field-type hint (e554a2ab), so the Go test
of this PR passes with or without the owner-segment fix. C# still reaches
try_field_type_hint, and its PascalCase method names make every candidate
QN contain a one-letter receiver's hint: `w.Write()` on a StreamWriter was
upgraded to an arbitrary project Write as field_type_hint. The test is RED
on main's pass_parallel.c and GREEN with the fix, and its positive control
pins `store.Put()` to Store.Put, never FileStore.Put.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>