Tooling — the compiler and everything around it
The decision this page settles: which command answers the question you have. Every one of them is offline and version-matched to the compiler in front of you; none of them needs a network.
The one-line commands live in ref.md. This page is the model: what each tool reads, what it produces, and when it is the wrong tool.
1. The build commands
cpc build # multi-file: reads ./Cplus.toml, walks imports
cpc FILE.cplus -o BIN # single file, no imports, no manifest
cpc check # whole project, front-end only, no codegen
cpc check FILE # one file, front end + codegen (discarded)
cpc test # #[test] discovery and run
The distinction that costs people the most time: cpc check FILE does
not read the manifest. A file with any import fails there with E0852.
Single-file mode is for import-free snippets; anything real goes through
cpc build or project-mode cpc check.
The other asymmetry is deliberate: project-mode check stops after
borrowck, so it is the fast feedback loop; file-mode check also runs
codegen and throws the IR away, so a codegen-stage fault — a panic in the
emitter — is caught too.
What check cannot catch: invalid IR. It never invokes clang, so IR that
cpc emits but LLVM rejects passes check and fails only in a real build. That
gap is not hypothetical — == on an array type-checked, emitted
icmp eq [2 x i32], and died in clang with no error code and no span in the
user's file. When the question is "does this actually compile", build it;
check answers "is the front end happy".
Build flags, all of which apply to cpc FILE, cpc build, and cpc test:
| Flag | Effect |
|---|---|
--release |
-O3, no overflow checks on + - * |
--debug |
-O0 with overflow traps — the default |
-g / --debug-info |
DWARF metadata, and -g to clang |
--asan / --ubsan / --tsan / --msan |
LLVM sanitizers; asan/tsan/msan are mutually exclusive, ubsan composes |
--target NAME |
cross-compile (platforms.md) |
--min-os VERSION |
override a versioned triple's OS floor; place after --target |
--fp-contract=off|on|fast |
float contraction; off keeps a*b+c as fmul+fadd for bit-identical-to-C output |
--warn-deps |
show dependency warnings too |
--timings |
build cost to stderr, per phase and per package |
--diagnostics=human|short|json |
diagnostic rendering |
--warn-deps is worth knowing about. By default warnings are shown for
your own src/ only, which keeps a cold build of a large app readable —
but it also means a vendored package's warnings are invisible until you ask.
Errors are never suppressed.
--timings answers "why is this slow" with data instead of a guess. It
prints the phase table (resolve+sema+borrowck / codegen / prune /
clang+link) and then a per-package roll-up. On this project the answer is
usually clang.
2. The code graph — use it instead of grep
C+ ships a resolved, typed code-knowledge graph. For any "where is X", "who calls X", "what is the type here" question, query the graph. It returns the answer already resolved, which removes both the grep pass and the reasoning you would spend disambiguating names and stitching call sites together. Across a large vendor tree, grep also misses generated code and produces false positives that read as real.
Three front ends over one index:
| Front end | Shape | Use when |
|---|---|---|
cpc graph |
whole graph as JSON on stdout | you want to process it yourself |
cpc query <kind> … |
one answer as JSON, exit code signals found | a one-off from a shell |
cpc mcp |
resident MCP server on stdio | an agent or editor asking repeatedly |
cpc query kinds:
def SYMBOL members TYPE symbols [FILE]
refs SYMBOL callers FN callees FN
call-hierarchy FN [--depth N] context FN
type-at FILE:LINE:COL value-refs FILE:LINE:COL scope-at FILE:LINE:COL
complete FILE:LINE:COL
complete is the composed one. scope-at, type-at and members are the
three primitives a caret question decomposes into, and deciding which of the
three a caret is asking — after a ., after a ::, or neither — is C+'s own
rules, not the caller's policy. So that decision lives in the compiler and one
verb answers it:
after a `.` the receiver's fields and methods (variants are `::`, not `.`)
after a `::` the module an alias binds, or a type's methods and variants
otherwise everything in scope
The answer names its own context (member / path / scope), the prefix
it filtered on, and the ranked items. receiver_type absent on a member
answer means the receiver's type is not locally known — an empty list, never a
guess.
Every cpc query invocation pays the whole-project graph build (~2s). An
editor asking on a keystroke wants cpc mcp, which builds once and answers
from memory.
cpc mcp is the one to reach for from an agent. Beyond the read tools
(find_definition, find_references, find_callers, find_callees,
call_hierarchy, find_members, file_symbols, code_context,
type_at, scope_at, complete_at) it is live:
did_changehands over an unsaved buffer; every later answer is about that text. The caret is always in a buffer that differs from disk, so for anything at the caret this is not a refinement, it is the whole question.- Rebuilds run on a worker. Reads never block — they answer from the newest
finished graph. Pass
wait: trueon the call right before a question whose answer must reflect this exact text. - A buffer that does not parse is normal, not an error state: the last
good graph keeps answering and
graph_statuscarries the parse error. reloadrebuilds from disk after a branch switch, a generator run, or a dependency update — changes the server cannot see.graph_statusobserves without rebuilding. Ask it before concluding an answer is wrong.
cpc lsp starts the language server on stdin/stdout over the same index
(it delegates to the cpc-lsp binary on PATH or next to cpc). It is
resident on the same terms as cpc mcp — one graph per project root, built on
the first graph-backed request and kept warm, with open buffers overlaid onto
it and rebuilds on a worker — and it serves textDocument/completion from the
same complete composition, so an editor and an agent get the same answer at
the same caret. Trigger characters are . and :.
code_context deserves its own note: it is the one-shot edit pack for a
function — signature, callers, callees, and the types it touches. Prefer it
over three separate lookups when you are about to change something.
#[test] functions and completion. scope-at and complete answer "what
can I type here", and a test function is never the answer — it takes no arguments, only
the harness calls it, and in a suite-carrying module the tests outnumber the
API. So both omit them. They stay everywhere else: def, refs,
callers, and callees all find them, because a test calling a helper is a
real call edge and hiding it would make callers lie. In symbols — the
file outline, which should list tests — each one carries is_test: true, so
a consumer using that list for completion can filter on the same rule.
3. Diagnostics
cpc explain E0502 # cause, fix, worked example — offline
cpc explain --list # every code
The diagnostics are the designed teaching surface of this language. When an
error and your intuition disagree, cpc explain before you edit: it
carries the redesign the message alone cannot.
--diagnostics=json emits NDJSON, one object per diagnostic — the shape a
CI annotator or an editor wants. short is one line per diagnostic.
Warnings worth reading rather than silencing:
| Code | Says |
|---|---|
W0002 |
a drop frees a raw-pointer field only conditionally |
W0005 |
a .cplus file under src/ is unreachable from the entry — it never compiles, so nothing it says is checked |
W0006 |
a #[deprecated] item is used here |
W0824 / W0825 |
a callback parameter is declared without its _ctx slot, so callers can never pass a bound method |
W0005 matters more than a warning usually does: unreachable code is false evidence. A reader — human or agent — takes it for the live API.
4. Source and docs
cpc fmt FILE|DIR … # rewrite in place
cpc fmt --check DIR # no write; exit non-zero on any diff
cpc fmt --emit FILE # print to stdout, leave the file alone
cpc fmt --stdin # read stdin, write stdout — the editor hook
cpc doc FILE # public items + `///` docs -> target/doc/<name>.md
cpc headers # lib/include/ for the package in this directory
cpc fmt settles layout arguments. If a file does not round-trip through
the formatter, the file is wrong; there is nothing to discuss.
cpc headers turns src/ into C+ declaration files under lib/include/:
a concrete module becomes signatures (fn f(...) -> T;), and a module that
declares generics is copied verbatim, because a generic has no object code
until a consumer instantiates it. This is what lets a prebuilt package be
consumed without its source.
cpc --emit-header FILE is a different thing: a C header for every
C-ABI-representable export item, for a C consumer.
5. Reading the language itself
cpc skill # the whole language reference for an agent
cpc skill --lang-only # …without dependencies' own SKILL.md files
cpc skill prints skill.md embedded in the binary —
version-matched, no network. Inside a project it also prints the SKILL.md of
every dependency that ships one, which is usually what you want: the
language plus the packages actually in this build.
6. Packages
cpc init [--kind cli|gui] [--platform P]... [NAME]
cpc pm install [DIR] # resolve deps into the store
cpc pm update [DIR] # re-resolve and refresh
cpc pm add DIR NAME [SPEC] # add a package and its declared closure
cpc pm add DIR --maven G:A:V # add a third-party Maven/AAR pin
cpc pm remove DIR NAME # delete DIR/vendor/NAME
cpc pm manifest [DIR] # normalized JSON of a manifest
cpc pm maven [WHAT] [DIR] # list | classpath | manifests | res | jni
cpc pm maven price G:A:V # what a coordinate would cost
cpc init with no --platform scaffolds the zero-config host app. With
--platform (repeatable) it scaffolds a deliberately scoped app: one
[<platform>] entry per named platform, and building for one you did not
name is E0413 rather than a guess. --kind gui scaffolds a facet app with
the backend's full dependency closure already in the manifest; without
--kind, the platform decides — --platform ios is gui and cannot be
anything else, because iOS has no console for a printing entry to print to.
cpc pm add is the one to prefer over hand-editing [dependencies]: it
writes the package and its declared closure, mapping platform sections
onto your project's target platforms. Dependency resolution is flat — the
resolver validates every import against your one manifest and never reads a
dependency's own — so a missing transitive line is an error in your
manifest (E0852 / E0866), which is exactly the class of error add exists
to prevent.
Store flags: --local installs into DIR/vendor/ instead of the per-user
store; --store DIR overrides the store root (default $CPLUS_HOME, else
~/.cplus); --repo-url URL pointed at a local path is the offline mode.
Android's Java side, without Gradle. cpc pm add . --maven com.google.android.gms:play-services-maps:19.0.0 pins the coordinate in
[android.maven], resolves the POM closure (parent chains, property
interpolation, dependencyManagement, BOM imports) and downloads it into
~/.cplus/m2, exploding each AAR. Nothing in the Android toolchain resolves
dependencies — Gradle is the resolver — but resolution over pinned
coordinates is reading XML, so cpc pm does it. A build then reads the
result back, always offline:
d8 --release --min-api 26 --lib "$AJ" --output out $(cpc pm maven classpath)
manifests, res and jni name the other three things an AAR carries;
merging those is still the build script's job. Run cpc pm maven price <coord> before taking a dependency — an AAR is priced per library, and the
number is printable.
7. Introspection
cpc --tokens FILE # the token stream
cpc --ast FILE # the AST
cpc --emit-ll FILE # cpc's own IR
cpc --emit-ll-opt FILE # post-optimization IR (through clang)
cpc --emit-asm FILE # native assembly
cpc --emit-obj FILE -o OUT.o # relocatable object
cpc --emit-ll-project # merged IR for the whole project
cpc build --print-link-args # what the dependencies add to the link line
cpc --realtime-report[=json] # whole-project real-time contract digest
Place --target and --fp-contract before an inline emit flag and its
file.
When chasing a suspected miscompile, the useful ladder is --emit-ll (is
cpc's IR right?) → --emit-ll-opt (did an optimizer pass change the
meaning?) → --emit-asm. Comparing --release against --debug at each
rung localizes an optimization-level bug quickly.
8. Gotchas
cpcon PATH may not be the one you are building. In this repo, always run./target/release/cpcaftercargo build --release; a package-manager-installedcpcwill not have your changes and will fail in ways that look like language bugs.cpc testwith no file readsCplus.toml. Package suites are run from the package directory (cd vendor/<pkg> && cpc test), not from the repo root.- Warnings from dependencies are hidden by default —
--warn-depswhen you are auditing rather than building. cpc queryrebuilds the graph every time. Repeated questions wantcpc mcp.cpc headersreadssrc/only and nevertarget/, so a module that exists solely as a generated file intarget/is not in the published surface.