Add C#, C++ and C - #7
Merged
Merged
Conversation
C++ and C share a grammar, a query and a resolution family. @vscode/tree-sitter-wasm ships no C grammar; the C++ one parses C without error, and the tags a C file can produce are a subset of the C++ ones. The family is shared because C++ calls into C without ceremony. Two things the grammars forced. Inside a C++ class body the grammar names an inline member with a field_identifier rather than an identifier, so the obvious pattern matched nothing. That node type occurs nowhere else, which makes the match a method by construction. Out-of-line `int Service::run()` is tagged a method too, under `run` rather than `Service::run` — containment cannot reach it from outside the class, and a receiver call only ever reaches a method. Only definitions are tagged. A member declared in a class body is a field_declaration, not a function_definition, so a header declaration and its definition do not become two symbols. C# writes each modifier as its own node rather than grouping them as Java does, and states member visibility, so it opts out of inheriting export from the enclosing type. Measured on real projects rather than fixtures, and the results differ enough to be worth publishing: curl C 1068 files 42% resolved 2% ambiguous Newtonsoft.Json C# 945 files 22% resolved 57% ambiguous fmt C++ 77 files 21% resolved 29% ambiguous gson Java 264 files 18% resolved 32% ambiguous C is the densest call graph of any language here — free functions with project-unique names are exactly what a syntactic resolver can follow. The others send most calls through a receiver on a typed object, which it cannot. The README carries the table rather than leaving it to be discovered. Packed size 703 kB -> 1.4 MB; the C# and C++ grammars are 4.9 and 5.1 MB unpacked against 405 kB for Java. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both found by indexing a project holding all ten languages at once and counting the edges: eight, where ten were obvious. Java and C# were the two missing. A bare call reached only a non-method. That is right where a method needs its receiver spelled out — ECMAScript, Python, Go, Rust — and wrong in Java, C# and C++, where every function is a member and `helper()` inside a class means `this.helper()`. The rule was dropping every intra-class call in three of the languages just added. Languages now declare which convention they follow. Relaxing it exposed a second defect. A constructor carries the name of the type it builds, so with methods now reachable from a bare call, every `new S()` matched both the class and its constructor and drew no edge at all — gson lost 461 edges before this was fixed. Java and C# name constructors in the grammar, so their queries no longer tag them; C++ has no constructor node, so extract.ts drops a member that duplicates its type's name. Nothing is lost: a constructor is not separately addressable, and its source sits inside the class's range. Re-measured on the same projects: gson Java 4089 -> 5443 edges 18% -> 25% resolved Newtonsoft.Json C# 9293 -> 11701 22% -> 27% fmt C++ 3475 -> 4383 21% -> 27% curl C 21229 -> 21239 42% -> 42% (C has no methods) The ten-language project now resolves every call it should: 10 edges, none ambiguous, none unresolved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Builds on #6 (Java) — it is merged into this branch, so the diff includes it.
C++ and C share a grammar, a query and a resolution family.
@vscode/tree-sitter-wasmships no C grammar; the C++ one parses C without error and the tags a C file can produce are a subset. The family is shared because C++ calls into C without ceremony.Two things the grammars forced
Inside a C++ class body the grammar names an inline member
field_identifier, notidentifier. The obvious pattern matched nothing, and the test caught it —expected undefined to be 'method*'. That node type occurs nowhere else, which makes the match a method by construction.int Service::run() { … }is tagged a method directly, underrunrather thanService::run. Containment cannot reach it from outside the class, and a receiver call only ever reaches a method, so tagging it a function would make it unreachable fromobj.run().Only definitions are tagged. A member declared in a class body is a
field_declaration, not afunction_definition, so a header declaration and its definition never become two symbols — the duplication problem simply does not arise.C# writes each modifier as its own node rather than grouping them as Java does, and states member visibility, so it opts out of inheriting export from the enclosing type.
Measured on real projects
C gives the densest call graph of any language in the project — 21 229 edges from 10 427 symbols, with 2% ambiguity. Free functions with project-unique names are exactly what a syntactic resolver can follow.
C#, C++ and Java send most calls through a receiver on a typed object, and
x.Get(…)cannot be attributed without the type ofx. Refusing is the rule working; a wrong edge would be worse. C# is the weakest of the set, refusing more than half its call sites.The README carries this table beside the language list rather than leaving it to be discovered.
Size
Packed 703 kB → 1.4 MB, unpacked 5.8 MB → 16.3 MB. The C# and C++ grammars are 4.9 and 5.1 MB against 405 kB for Java. If that is too much for a globally installed CLI, the alternative is fetching grammars on first use — but that trades away the "nothing to compile, no network" property the install section currently promises, so it is a decision rather than an optimisation.
Verification
npm run build,npx tsc --noEmit,npm test— 385 tests passnpm run benchmark— ratio 0.699, success_delta 0no language for …; the C++ inline-member one then failed a second time for a real reason, which is what uncoveredfield_identifier