Repository navigation
C# impact: seven resolution gaps found by a 16-repo mutation oracle - #1881
Merged
Merged
Conversation
`namespace Shop.Tests; using Results;` imports Shop.Results, and `Reflection.Info.Create()` written inside namespace Shop names Shop.Reflection.Info: C# reads the first segment of a using's name, and of a qualified name, from the enclosing namespaces outward. The engine took both as written, so the using imported nothing and every client type named through it read as a library type (`new Failure(..)` was boundary_lib), and the qualified receiver stayed untyped. - module_in_scope: a using whose name is no namespace at all takes the child of the file's namespace chain that does exist (the IR does not say whether the using sits inside the namespace; a name that is no namespace only compiles there). - type_name_cand: a type's qualified name is split at each dot into namespace prefix and dotted tail; the tail is a rank-1 candidate in every file whose namespace chain holds the prefix. Keyed from the declaration side. Case a-namespace-name-is-read-from-its-namespace fails 4/6 on the base (the two controls, a file in an unrelated namespace, pass on both). Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`axiomcode path Tests/OrderTests.cs Order.Place` was refused with "nothing named 'Tests/OrderTests.cs' is declared" on every C# graph, although the file is indexed: the file-endpoint branch matched .ts/.tsx/.js/.mjs/.cjs/.py/.java only. Every extension a front end reads (ax_langs.BY_EXT) is now a file endpoint. An extension outside the old list falls through to the name lookup when no indexed file has that name, so `Owner.cs` still resolves a method `cs` of Owner (the case's control). Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`using (var w = new Writer(..)) { w.Write(..); }` and
`using var w = new Writer();` left the receiver untyped: var_value_initializer
read LOCAL and CONST declarations only, and the IR gives these the
declarationKind USING. The resource is the disposable itself, so its
initializer is a value of its type exactly as a local's is.
Case a-using-resource-is-typed-by-its-initializer: both shapes fail on the base;
the control (a foreach variable is typed by its element, never by the
collection) passes on both.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
A constructor with no `: base(..)` / `: this(..)` runs `base()` before its body, and a class that declares no constructor gets an implicit one that does the same. Neither is written, so the base constructor had no caller: an abstract base whose constructor every derived class runs read as uncalled. New synthesised call_edges kind implicit_base_ctor (known_edge): - from a declared constructor without an initializer (and not a primary constructor whose heritage passes arguments), FromExpr = the base's heritage type reference, as primary_ctor_base does; - from a `new T()` site where T declares no constructor, beside its known_implicit_ctor row. The target is the base's constructor that takes no argument, walking up through bases that declare none. Registered in the bundle schema and ax_edges' kind vocabulary (`ctor`). Case a-constructor-runs-its-base-constructor: 4 positives fail on the base; the controls (`: base(name)` and `: this(..)` constructors gain no implicit edge) pass on both. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`var services = new ServiceCollection(); services.AddWidgets();` and `Configure(s => s.AddWidgets())` with `Configure(Action<IServiceCollection> c)` never reached the client's `AddWidgets(this IServiceCollection s)`: both receivers' types are unstaged, so they have no group, and call_recv_type_name -- the NAME the by-name rung (3) and the loose DI rung (7, #1472) compare -- had no clause for either. call_recv_type_name now also gives - an implicitly typed local initialised with `new T(..)`: the T written there; - an implicitly typed lambda parameter: the name of the delegate argument that types it (lambda_param_arg_ref, the same reference param_type reads). So the lambda's call matches by name (resolved) and the local's is a rung-7 candidate (one of a set, keeping its external label). Case an-unstaged-receiver-keeps-its-type-name: both positives fail on the base; controls (an extension on an in-source `this` type, a lambda typed as another unstaged type) pass on both; extension-on-unstaged-receiver unchanged. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`var f = Context.Current.Factory; f.Create()` -- the getter of the static property `Current` was an edge, but its result had no type: expr_type for a MEMBER_ACCESS needed an expr_type on the qualifier, and a qualifier that names a TYPE has none. So `.Factory` and every call after it were unresolved. Two clauses mirror the instance ones with ref_names_type on the qualifier (property and field). ref_names_type already refuses a name that also binds a value, the language's member-over-type rule. Case a-static-member-read-types-the-chain: four shapes (local, one chain, a var of the static read, a static field) fail on the base; the "Color Color" control -- a property named like the type is the value read -- passes on both. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
`Cache<int>.Get("k")`, `Registry<A, B>.Create()` and
`Pair<int, string>.Make().Use()` were unresolved: the parser emitted a
generic_name in expression position with typeArgumentCount set and NO
potentialQualifiedName, so the receiver had no name to resolve.
- parser: a generic_name row carries its identifier as the written name (its
arity stays in typeArgumentCount).
- engine: expr_type_argc projects the count; ref_names_type and the call
receiver's type_ref_demand resolve the name at that arity, so `Cache<int>`
names Cache`1 and never a non-generic Cache. Every receiver written without
type arguments has arity "0", which is what both clauses used before.
Case a-generic-type-name-is-a-static-receiver: three positives fail on the
base; the non-generic `Cache.Get` control resolves to its own type on both.
Parser C# suite 62/62.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
swapnilpaliwal-sd
force-pushed
the
fix/cs-impact-loop
branch
2 times, most recently
from
October 9, 2026 21:37
443a26a to
c0dab0b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Symptom
On real C# libraries,
impact <file:line> --testsmissed most of the tests that fail when the method breaks, and selected nothing at all for a quarter of breakable methods;path <test file>.cs <method>refused every C# file endpoint.Measured with a behavioural oracle on 16 open-source C# repositories pinned at tags at least six months old (xUnit, NUnit and MSTest; libraries, DI containers and extensions, parsers, mappers, mocking). For 50 methods per repository, sampled from the source with a Roslyn syntax walk (never from the graph), a guarded
throwis inserted at entry, the repository is rebuilt and its suite run; the truth is the set of test files with a test that passed in both clean runs and fails now. 11 repositories were used for tuning; 5 were held out and scored once at the end.Mechanism, one commit per pattern (each pattern seen in at least two repositories)
namespace A.Tests; using Results;importsA.Results, andReflection.Info.Create()insidenamespace AnamesA.Reflection.Info. Both were taken as written, so the using imported nothing and client types read as library types (boundary_lib), or the receiver stayed untyped.pathtakes a.csfile as an endpoint. The file branch listed.ts/.tsx/.js/.mjs/.cjs/.py/.javaonly. It now takes every extension inax_langs.BY_EXT; a new extension falls through to the name lookup when no file matches (Owner.cscan still name a method).usingresource is typed by its initializer.using (var w = new Writer())/using var w = …;have declarationKindUSING, whichvar_value_initializerdid not read, so every call on the resource had an untyped receiver.base()call is an edge. A constructor without: base(..)/: this(..), or a class with no constructor at itsnew, runs the base's parameterless constructor. New synthesised kindimplicit_base_ctor(bundle schema +ax_edgesvocabulary updated).var services = new ServiceCollection(); services.AddX()andConfigure(s => s.AddX())withAction<IServiceCollection>gavecall_recv_type_namenothing, so a clientAddX(this IServiceCollection)matched neither by name nor on the loose DI rung (csharp: a this IServiceCollection extension called on a ServiceCollection receiver is labelled a boundary call and impact omits it #1472).Ctx.Current.Factory.Create(): the getter was an edge but its result had no type, becauseexpr_typeon a member access needed a typed qualifier and a type name has none.Cache<int>.Get(),Registry<A,B>.Create(): the parser emitted thegeneric_namereceiver with no written name. It now carries the identifier (arity stays intypeArgumentCount) and the engine resolves it at that arity; every receiver without type arguments keeps arity"0", as both clauses used before.Each commit adds a
tests/cases/csharp/case that fails on the base and passes with the fix, with controls that pass on both (a file in an unrelated namespace, a foreach variable,: base(name)/: this(..)constructors, an in-sourcethistype, a property named like a type, a non-generic type of the same name).Numbers (base = apps/integration-0.1.9 a811ccf)
Tuning set, 11 repos, 550 mutants (443 with a failing test), cumulative:
testsverb with the break applied: recall 0.494 → 0.641. No tuning repo loses recall at any step. Precision falls with the added reach (tests that call a method without failing when it throws). Selections for mutants no test detects: 849 → 859 cumulative; step 6 alone cut them 900 → 837.Held-out set, 5 repos, 250 mutants (199 with a failing test), scored once:
One held-out repo loses one mutant's two files (0.724 → 0.712): the base selected them only through a
[by name]lead at a call on a static singleton (Empty.Instance.ToText()), which step 6 now resolves correctly to the singleton's own override; the real route is lost earlier, at anout varreceiver, on both.Cost: forced full index (parse + solve), base vs fix interleaved on 10 repos: within ±1 s on 9, +2 s on one (17 → 19 s); graph.sqlite +0–2 MB; edges to client methods +0.1% to +27%.
Checks
tests/run.pycsharp 241/241, java 333/333, typescript 269/269, javascript 307/307, python 306/306graph/test/csharp/run-tests.sh(oracle self-tests, 22 engine cases, staging, remote edges) green, output identical to the basetests/hook_languages.py7/7,tests/fastpath.py --lang csharp8/8, parsercsharp-tests.ts62/62,gen_decls_all.py --checkStill open (not in this PR)
A library calling client overrides (
GetHashCode/Equalsas dictionary keys,GetEnumeratorthroughforeach/LINQ), types created by reflection (Register<TMap>(),Activator), expression-tree mappers, anOuter.Innerqualifier resolved through an enclosing type, andpath's optimistic closure ignoringboundary_libsites (a client method mislabelled as a library call reads as "independent" instead of naming the site).