Mahdi
All posts
vtoolchainopen-source

Twenty merged pull requests to the V compiler

What it takes to add a tool to a language that has been maintained by one person for eight years — v mcp, v clean, v mod why, a JSON5 parser and a gRPC client, and what each one was actually for.

V is a small language with a large surface. The compiler, the standard library, the formatter, the language server, the package manager and a dozen CLI tools all live in one repository maintained by one person, and every contribution upstream goes through that person's review.I have twenty merged pull requests into vlang/v and three open against vlang/vls. This is what that turned out to involve.The one that mattered: v mcpTwelve and a half thousand lines, seventy-five files. The largest thing I have shipped anywhere, by a wide margin.It adds v mcp, a Model Context Protocol server that exposes the compiler to a coding agent, plus v skills which installs the bundled agent skills:
v mcp serve --root DIR          resolve relative paths against DIR
v mcp serve --read-only         register no tool that writes a file
v mcp serve --instructions      print the model instructions and exit
v mcp tools                     list the tools, with what each one answers

v skills list                   the bundled catalog, and where each one stands
v skills add v-mcp              into .agents/skills/ of this project
v skills add v-mcp --global     into ~/.agents/skills, for this user
The design decision is the one that makes it worth twelve thousand lines. An agent editing V through a generic language server or through plain file reads works, but it cannot ask the questions only the compiler can answer: what does this file declare, what fails to compile, is this symbol the declaration or just a mention, what is the signature of that stdlib call.The tempting implementation is a separate index — parse the tree, build a symbol table, keep it current. That is a second, approximate version of something the compiler already does exactly. So v mcp serve calls the V parser, checker and formatter in process. It answers correctly about code that does not compile yet, because it is asking the same code that will later reject it.Fourteen tools, grouped by what they answer rather than by implementation:Projectv_project_info, v_modules, v_filesCodev_ast, v_symbols, v_symbol_at, v_references, v_stdlib_docCheckingv_check, v_test_run, v_doctor, v_veb_routes, v_skillsRunningv_run, v_evalEditingv_edit_replace, v_rename_symbol, v_formatv_stdlib_doc reads documentation out of the source of the module it names, so a signature never has to be guessed. v_rename_symbol works from the AST, so a comment or a string that happens to contain the name is left alone.The part I would point at if you only read one thingThe editing tools do not write by default.v_rename_symbol and v_format are dry runs unless told otherwise. v_edit_replace requires the caller to pass back the text it expects to find, so it refuses to write over a concurrent change and reports expected against actual instead of clobbering it. --read-only does not register the three at all.That is a deliberate decision about what an agent tool should be. A tool that edits by default will eventually edit something the agent was not looking at, and the failure is silent. Making the destructive path require an explicit argument means the safe path is the default and the dangerous one is something you had to mean.The VLS fix below is the same instinct applied to a test.Four small tools, one patternv clean, v mod why, v tool and v env went in as separate pull requests. Together they are one decision: the compiler had grown command-line tools faster than it had grown the things that keep those tools honest.ToolWhat it answersv cleanRemoves the executables a build leaves behind next to the sourcesv mod whyPrints the import chain that pulls a module into your buildv toolRuns a tool module by name instead of by pathv envReports the environment variables that steer the compilerEach is small — v clean is 440 lines, v mod why is 595. Each one added its own test file, its own vlib/v/help/ entry, and updated doc/docs.md. That is the part that took the effort, and it is the part that keeps them mergeable: a tool with no test and no help text is a tool that goes stale within a release.v mod why is the one I reach for most. VPM installs modules, so a dependency chain through three packages is not something you can eyeball, and it is exactly the question you ask when a build gets slow or a binary gets unexpectedly large.Latent errors that were blocking WindowsSeparate from the features: v fmt vlib/os and v test vlib/os did not compile on Windows at all.
vlib/os/os.c.v:628:6: error: infix expr: cannot use `int` (right expression) as `voidptr`
vlib/os/process_windows.c.v:422:10: error: infix expr: cannot use `int literal` (right expression) as `C.DWORD`
Eight errors, all latent, all invisible until a type-checking change in an earlier pull request made previously-unreachable dependency functions get checked. On Linux the paths compile differently and nothing noticed.This one has no feature in it at all. It is eight type errors in vlib/os/*.c.v, and without it a meaningful fraction of the standard library could not be formatted or tested on Windows — which is where a lot of V contributors work.It is also the clearest example of what upstream contribution actually is. The reward for that pull request is that some other person's v fmt stops failing.Smaller thingsA JSON5 parser, decoder and encoder in vlib/x/json5 — 3,482 lines. An experimental gRPC client and server in vlib/net/grpc — 3,404 lines across 24 files. Six pull requests tightening toml encoding and decoding: Option fields, enums, maps and arrays as collection elements, narrow integer types, embedded structs. yaml keeping integers integral in encode. A checker fix for map value types behind generic type names. A cgen fix letting the C preprocessor pick in Windows snapshots, because makev.bat could not bootstrap with the bundled tcc.Every one of those is a small, specific gap that someone hit and reported. That is most of what an upstream contribution is.The honest accountingTwenty merged, all into vlang/v, in about a week of focused work. Three more open against vlang/vls, none merged. And the changes sit on master, not in a released version — v mcp needs a build from source, and until there is a release containing it the tool is not something you can install.None of that is a criticism of the project. It is what contributing to a language that ships roughly every three months looks like. But a page about V would be misleading if it stopped at "20 merged PRs" and let the reader assume they could type v mcp today.Why it is worth doingI write this site to have somewhere to put things I learn. It turns out the best thing to learn is not a framework but a compiler's idea of what a tool should be.Two examples, and they are not related except that both are refusals:An agent tool that writes by default will eventually clobber something. So it does not. You have to say so.A test that interpolates a path into hand-written JSON will break on any platform whose paths contain a backslash. So it compares parsed URIs instead.Both decisions make the tool worse to demo and better to depend on, which is almost always the trade an upstream maintainer is making when they approve something.

Related