Skip to main content

cpp-style

Read before writing, modifying or reviewing any C++ code — redundancy (the rule we care most about), modules and files, errors and defense, async, logging, naming, words, templates, API preferences.

Quellinformationen

Repository
clice-io/clice
Letzte Quellaktivität
14. Oktober 2026 um 21:44
Erkannte Sprache von SKILL.md
Englisch
Sterne
1.355
Forks
86

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
cpp-style
description
Read before writing, modifying or reviewing any C++ code — redundancy (the rule we care most about), modules and files, errors and defense, async, logging, naming, words, templates, API preferences.
# clice C++ Coding Style ## Redundancy Is a Defect This is the convention we care most about. Redundant code is not a style nit — it actively misleads: every guard implies the guarded state can occur, every branch implies it can be taken, every parameter implies a caller needs it. When that implication is false, the reader wastes time defending against ghosts. - **Every branch must be reachable.** Before adding a guard or fallback, prove the state can actually occur — construct the input that hits it. If you can't, don't write it. - **No speculative generality.** No parameters, options, hooks, or abstraction layers for hypothetical future callers. Add them when the second real caller arrives. - **One way to do each thing.** Don't leave an old path alive next to its replacement "just in case" — migrate all callers and delete it in the same change. - **Re-read after every change.** Edits leave residue: conditions that became constant, variables read once, branches that now collapse, `else` after `return`, a helper with one remaining caller. Fold them before you're done — simplification that removes a concept beats one that merely shortens lines. - **Delete, don't comment out.** Git history is the archive. - **One behavior difference is one options field.** Two behaviors of the same logic are selected by a boolean in an options struct (`SemanticsOptions{.all_files = true}`), not by a subclass, a dummy collaborator, or a parallel code path. - **Cross-cutting invariants get a single writer.** Accounting, budgets, ownership and visibility guarantees live in one type whose methods are the only transitions and whose doc comment lists the invariants; before coding, grep every site the invariant touches and funnel them through one helper so a missing site is discoverable. Per-site patches never catch up with a reviewer who re-derives the invariant from the whole diff. ## Files & Organization - clice is one C++20 module, `clice`. An interface is a partition, `<stem>.cppm` beside its source, named by its path under `src/` (`module clice:vfs.path;`, `unittests.*` under `unittests/`); sources are implementation units `.cpp`, entry points `.cc` (`clice.cc`, `src/driver/*.cc`). - Every module unit but the primary interface (`src/module.cppm`, which exports nothing) opens with `module;` and `#include "modules/prelude.h"`, which imports the third-party module (`modules/`) and `std.compat` and replays their macros; then the macro headers it uses, the module declaration, and one `import :<partition>;` per partition it names. - `.h` is left for what a module cannot carry — macros (`*.macros.h`) and platform headers (`vfs/win32.h`) — with `#pragma once`, never include guards. - File names are `snake_case`. - Every file sees the names of all it imports: a file-local helper can clash with a name of another partition or lose overload resolution and ADL to a library function (`llvm::join`) no header used to bring in. - File-local helpers: a single one is `static`; a cluster of them goes in one anonymous namespace. ## Data & Types - `struct` by default, even for types with methods. `class` only when there is a real invariant that private access protects. - `enum class` with an explicit underlying type (e.g. `: std::uint8_t`); document each enumerator with `///` when its meaning is not obvious. - Prefer designated initializers (`{.field = value}`) for aggregate construction. - West const: `const T&`, never `T const&`. ## Errors, Exceptions & Defense - The project builds with `-fno-exceptions -fno-rtti`: no `throw`/`try`/`catch`, no `dynamic_cast`/`typeid`. Polymorphic inspection uses `llvm::isa<>` / `cast<>` / `dyn_cast<>`. - Fallible synchronous operations return `std::expected<T, E>` with `E` = `std::string` or `std::error_code`; async code uses kota's outcome types. `llvm::Error` only where an LLVM API forces it. - `assert` pins preconditions; impossible branches end in `std::unreachable()` — never `llvm_unreachable` (project convention). - Invalid external input (broken source code, malformed requests) degrades gracefully — empty result, input passed through — it never crashes the server. But graceful degradation is not scattered null checks: per the redundancy rule, defend only against states that can occur. The Debug (ASan + assertions) test surface is the safety net. ## Error Handling (control flow) - Scope an error variable with an `if` init-statement when nothing after the `if` needs it: `if (auto result = foo(); !result.has_value())`. - A type with `operator bool` needs no condition: `if (auto err = foo())`, never `if (auto err = foo(); err)`. - When the success path needs the variable, declare it as a plain local and return early on the error — never an init-statement that forces the success path into an `else`. ## Concurrency & Async - Async code is kota coroutines (`kota::task`, `co_await`) — no callback style. A public interface may stay synchronous and drive a coroutine internally when the caller has no event loop (see `Toolchain`). - Review coroutine code by interleavings, not function by function: list every `co_await` suspension point, the events that can fire while it is suspended (crash, respawn, dispatch, cancellation, another coroutine resuming), and trace the shared state through each chain; then check that what the frame captured before suspending is still valid when it resumes. Bugs here are always cross-function interleavings; a function-level read finds none of them. - Cancellation flows down the ownership tree through `task_group::cancel()`; tokens and sources exist only to cross an ownership boundary — the serve mode shutdown source in `MasterServer`, the batch commands' in `BatchLifetime`. Graceful shutdown is not cancellation — kota has no shield, a cancel cascades all the way down — so a shutdown is a cancellable serving phase bounded by `with_token`, then a non-cancellable drain that joins each task in order. - `cancellation_token::wait()` completes by cancelling itself. Awaited directly inside `when_any` it propagates that cancellation into the parent frame and skips everything after the `co_await`; wrap it in `with_token` (which catches internally) or add `.catch_cancel()`. ## Logging - Log through the `LOG_TRACE` / `LOG_DEBUG` / `LOG_INFO` / `LOG_WARN` / `LOG_ERR` macros (`std::format` syntax, source location captured automatically); `logging::critical` logs and aborts. Never call spdlog directly, never print diagnostics to stdout/stderr. ## Naming Conventions - **Variables, member fields, function names**: `snake_case`. Class member fields do NOT use any special suffix/prefix (no trailing `_`, no `m_` prefix). - **Class names, template parameter names, enum names**: `PascalCase`. Exception: some class names also use `snake_case` — follow the existing style in the project. - **Enum values**: `PascalCase`. - **Acronyms stay uppercase in type names**: `PCHFamily`, `ASTProjection`, `TUIndex` — never `PcmFamily`, `AstProjection`. - Doc comments on declarations use `///`; the bar for when to write a comment at all is in CLAUDE.md. ## Words to Use and Avoid Identifiers and comments use plain words: the word a newcomer would guess for the thing, not a metaphor or a rare word that needs a glossary (verdict, envelope, ledger). One word means one thing across the codebase — before naming, grep whether the word already means something else nearby, and don't split one concept over two words. A name that its own doc comment has to correct is the wrong name. These were renamed out of the codebase; don't bring them back: | Avoid | Say instead | | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | row / rows | an index record: `occurrence` / `relation`, else `entry`; a file's whole index data: `index` / `shard` / `variant`; a source line: `line` | | verdict | `result` / `decision` / `outcome`, whichever the site is | | claim | one word per site: `take`, `acquire`, `reserve`, `cover`, `report` | | envelope | what it is: `TUIndexBlob`, the TU index, a container, a header | | ledger | `table` / `list` | | wire (the serialized form) | `serialized` / `encoded`; "in the protocol" for IPC | | settle | `finish` / `become idle` | | flavor | `kind` / `variant` | | land / landing (a result arriving) | `commit` / `merge` / `finish` | | sweep | `pass` / `run` / `scan`; for cleanup, `remove` | | tier (an encoding choice) | `encoding` (`MaskEncoding`); the search ranking's `NameRank::tier` stays | | surface (verb), vouch for | `report` / `show`, `guarantee` / `confirm` | | stamp outside `vfs` | `version` / `generation` | Domain words with one meaning each — use them only in that meaning: - **spelling**: how a path is written — the anchored absolute path, `..` kept, handed only to the identity functions or to clang. - **host**: the source file whose compile context a header borrows. - **slot**: one worker's place in the worker pool. - **stamp**: in `vfs` only, a file's change fingerprint (size + mtime + ctime). ## Templates, Type Traits & Concepts - `std::remove_cvref_t<T>` only where `T` can be deduced as a reference: a forwarding reference `T&&`. `T`, `T&` and `const T&` parameters, class template parameters and return types never deduce one. - A type trait is a variable template specialized directly (`template<> inline constexpr bool is_my_type_v<MyType> = true;`), never a variable template wrapping a class template's `::value`. - A concept over a trait uses `T` as is (`concept MyTrait = is_my_type_v<T>;`) unless it deliberately sees through references and cv-qualifiers; the caller passes the right type. ## String Literals - Prefer C++11 raw string literals `R"(...)"` over escaped strings. Avoid `\"`, `\\`, `\n` in string literals when a raw literal is cleaner. ## Style - Prefer `[[maybe_unused]]` over `(void)` for intentionally unused variables or parameters. - Arithmetic counters step with `x += 1` / `x -= 1`, in `for` heads too — never `++`/`--`; split fused forms like `if(++x > n)` into two statements. Iterators and other types without `+=` are exempt. This applies to code you write or touch, not as a repository-wide sweep. ## Modern C++ Usage - Use C++20/23 APIs whenever possible. Do NOT use `<iostream>` facilities (`std::cout`, `std::cin`, `std::cerr`, etc.). Also do NOT use C-style I/O (`printf`, `fprintf`, etc.). - Prefer `std::ranges` / `std::views` APIs over raw loops and traditional `<algorithm>` calls. - Prefer LLVM's efficient data structures (e.g., `llvm::SmallVector`, `llvm::DenseMap`, `llvm::StringMap`, `llvm::StringRef`) over their `std` counterparts when appropriate. ## Parameter Passing Preferences - For string parameters, prefer `llvm::StringRef` > `std::string_view` > `const std::string&`. - For array/span parameters, prefer `llvm::ArrayRef` > `std::span` > `const std::vector&`.
Auf GitHub ansehen