# Wolf warnings — the philosophy, and the catalog GENERATED by `cargo xtask diag-catalog` from `crates/wolf_diag/src/registry.rs` — do not edit. The prose lives in `xtask/src/main.rs`; the per-code entries are the registry's. ## The severity contract An **error** rejects meaning: the program has none, so an error cannot be allowed, denied, or configured away. A **warning** marks a program that is legal and inadvisable — every W-code cites a concrete recorded hazard or a house idiom rule, never "might be slow someday" (spec/01 §9; D22). Warnings are mined from what actually bit people: each entry's rationale names the wound, and a warning that stops earning its keep is deleted rather than ignored. Idiom-arbiter codes mechanize the written API conventions — "idiomatic wolf" is checkable, not tribal. ## Levels, and who outranks whom Every warning is leveled: `allow` drops it, `warn` reports it, `deny` reports it at error severity and fails the build (the code stays `W####`, and a note names the rule so the reader knows the rejection is configuration, not semantics). Three sources set levels, most local first: 1. **`#[allow(w1301)]` in the source** — item-granular, part of the program, honored by every consumer; 2. **CLI flags** — `--allow/--warn/--deny `, `--deny-warnings`; 3. **the manifest** — `lints. = sel, …` in `wolf.pkg`. Selectors are one code (`W1301`), a family (`W13xx`), or `warnings`. Specificity wins (code over family over all); among equals the last rule set wins, and CLI rules are appended after manifest rules on purpose. ## Escape-hatch etiquette Allow **locally** and **with a reason**: an `#[allow]` sits on the one item that earns it, next to a comment saying why the shape is deliberate. Package-wide allows in the manifest are for staged adoption, not permanent silence. **CI posture is deny**: a tree that is warning-clean stays warning-clean by decree (`--deny-warnings`), and this repository's own corpus holds itself to exactly that bar. A warning everyone silences is a bug in the catalog — file it; the catalog answers by fixing the lint or retiring the number (retired numbers are never reused). ## Fix-its, and what `wolf fix` will touch Warnings carry mechanical fixes where one exists. Only **machine-applicable** suggestions are ever applied by `wolf fix` — a fix is machine-applicable only when the toolchain can prove every affected site is rewritten (W1002 rewrites the declaration and every call site together, and downgrades itself to a suggestion when a call site is not provably safe to touch). Everything else renders as a `help:` the author applies by hand. ## Cross-implementation posture The differential protocol's record carries a `warnings` array (`[proto.record.warn]`), and warning parity grows lint-by-lint: syntax- and name-level lints are shared-analysis (the reference interpreter can implement them), type/memory/concurrency lints are compiler-only until it grows the analysis — absence is honest, never a divergence. `#[allow]` is part of the program and suppresses identically on both sides. --- # The W-catalog ## W0301 — file only partially formatted: syntax errors present `wolf fmt` formats through the resilient parse tree, so a file with syntax errors still mostly formats: every well-formed declaration and statement is laid out canonically, while the regions the parser could not understand — plus one statement of margin on each side — pass through byte-for-byte untouched, so half-typed code is never mangled. This warning marks that partial result, and `wolf fmt` exits nonzero so scripts and editors know the file is not fully canonical yet. Fix the syntax errors it reports alongside this warning and run `wolf fmt` again; with a clean parse the whole file formats and the warning disappears. ## W0302 — `#[allow(…)]` names a code that is not registered The `#[allow(…)]` attribute suppresses warnings by their registered diagnostic codes — `#[allow(w1301)]` for one code, `#[allow(w13xx)]` for a family — and this argument matches no code in the registry (spec/01 §9; the catalog in `docs/diagnostics.md` lists every code that exists). An allow of nothing usually means a typo in the digits, or a code from a different toolchain version. Fix the code, or delete the argument; the attribute itself still applies its other arguments. This warning cannot suppress itself for the same misspelled code — name it correctly and the allow works. ## W0303 — this `#[allow]` allows nothing `#[allow]` with no arguments suppresses no warnings at all: the attribute's whole meaning is the list of codes it names, item by item (`#[allow(w1301)]`), so an empty one is inert — almost always a half-written suppression or a leftover from deleting the codes out of it. Name the warning codes to allow, or delete the attribute; wolf reports the dead attribute rather than letting it sit there implying a suppression that never happens. ## W0304 — this declaration shadows a prelude name The prelude's names — `assert`, `print`, `List`, the reflection intrinsics, the built-in type names — resolve in every file with no import, and a declaration with the same name silently wins over the prelude for its whole module (file boundaries create no scopes). The loss is invisible at every use site: a module that declares its own `assert` severs itself from the assertion trap entirely, and calls that read as the intrinsic quietly run the local one. Pick another name; the prelude's inventory is small, fixed, and worth avoiding wholesale. ## W0305 — this row tag shares its name with something else in scope Inside a function whose signature declares this tag, an identifier in raise position resolves to the tag first, while the same word everywhere else resolves to the item, import, or binding it shadows — one name, two meanings, decided by position. Programs with this collision have historically returned a module as an `int` with the caller's `else` never firing, which is why the rule of thumb is absolute: a tag may not share a name with anything in scope. Rename the tag (or the item); tags are cheap to rename because they exist only in rows, raises, and arms. ## W0306 — this statement is a bare prefix-operator expression A statement consisting only of a prefix operator applied to a value — `-total`, `!ready`, `&slot` — computes a value and throws it away, so it does nothing. Nearly always it is a broken continuation: the line above ended an expression, the newline terminated the statement, and what was meant as `a - b` became two statements with the second inert. Join the lines so the operator is read as binary (wolf never continues a statement across a newline that ends one), or delete the statement if the value really is unwanted. ## W0307 — the comparison after `else` applies to the fallback only `else` binds more loosely than any operator, so `count else 0 == max` parses as `count else (0 == max)` — the comparison happens first, against the fallback alone, and the `else` then defaults a boolean, not a count. When the intent is to default and then compare, wrap the default: `(count else 0) == max`. The warning fires on comparison operators in fallback position because a bare comparison as a fallback is nearly always the wrong grouping; parenthesize either reading to say which one is meant, and the warning stands down. ## W0308 — a `mut` argument is hidden inside a string interpolation This interpolation calls a function that mutates its argument, so building the string changes program state — a write buried where every reader expects pure formatting. Interpolations run left to right exactly once, so the code is well-defined; it is the placement that misleads, and such calls have a record of surfacing memory-model edge cases first. Hoist the call onto its own line, bind the result, and interpolate the binding: the mutation becomes visible where mutations are expected. ## W0309 — a raw string contains interpolation-shaped braces Raw literals interpolate nothing: `r"{who}"` is six bytes, braces included, while the same characters in every other string literal are an interpolation of `who`. Braces that spell an in-scope name inside a raw literal almost always mean the `r` prefix was added to (or left on) the wrong string. Drop the prefix to interpolate, or escape nothing and keep the raw literal if the six bytes are what is wanted — the warning exists because the two readings are one keystroke apart and produce different strings with no diagnostic. ## W0310 — the `get_` prefix names nothing House convention, applied mechanically: no function wears a `get_` prefix. The prefix carries no information — every function gets something — so the name that matters is the noun after it: `get_len` is `len`, `get_first` is `first`. The one blessed `get` spelling is the bare word itself, the checked-access operation that answers with an absence row instead of a sentinel. Rename the function to the noun it fetches; if it is genuinely checked access, name it `get` and give it the absence row. ## W0311 — a predicate-named function must answer `bool` Names spelling `is_` or `has_` are the predicate convention: callers read `is_empty(x)` as a yes-or-no question, and nothing else is allowed to wear the shape. This function has the prefix but does not return `bool`, so every call site reads as a test and is not one. Either return `bool`, or rename the function after what it actually produces — the prefix is the promise, and a signature that does not keep it is worse than no convention at all. ## W0312 — an `as_` conversion must borrow, and this one does not The naming split for conversions is allocation and ownership: `to_x` builds a new value, while `as_x` (and bare nouns) are views that borrow their operand and leave it untouched in the caller's hands. This `as_` function takes an operand `mut` or `take`, so it mutates or consumes what a caller reads as a borrowed view — the name promises one cost and the signature charges another. Rename it `to_x` (or after the operation it really performs), or change the mode to the read default a view deserves. ## W0313 — this `pub` item has no doc comment A `pub` item is a promise to other modules, and the doc comment is where the promise is written down: the contract sentence, the meaning of each row tag, the trap conditions. This exported item carries no `///` at all, so its callers get a name and a signature and must read the body for everything else. Write the contract line above the declaration; if the item is not worth documenting, it is usually not worth exporting — make it private instead. ## W0314 — this module contains exactly one item A module is a namespace, a visibility boundary, and a directory (D32) — real structure with a real reading cost. This one holds a single item, so the structure is all ceremony: importers write the module's name to reach one thing, and the tree grows a directory per function. Fold the item into the module that uses it, or grow the module into the family of items its name promises. A one-item module that is a deliberate seam can say so with an `#[allow]` and a reason. ## W0315 — nothing else in this package uses this `pub(pkg)` item `pub(pkg)` widens an item's visibility to the whole package — a claim that some other module in the package needs it. No other loaded module mentions this item's name, so the widened visibility is unearned: the item behaves as private and the declaration says otherwise, which misleads exactly the reader deciding whether a change to it is safe. Make it private; when another module really does take it up, widening back is a one-word change. ## W0316 — this module imports its own ancestor A child module reaching up into its ancestor couples the two in both directions at once: the ancestor owns the child structurally, and now the child depends on the ancestor's items too. The shape is legal — no cycle exists yet — but it sits one ordinary edit from the hard-error import cycle, because the ancestor importing any of its descendants closes the loop. Move the shared items down into the child (or into a sibling both can import) so the dependency runs one way, parent to child. ## W0401 — this literal does not fit the type it is cast to The value of this literal is known at compile time, and it lies outside the range of the cast's target type, so the conversion can never preserve it — the program carries a number that the type it is handed to cannot hold. Wolf's arithmetic is checked in every profile, and a conversion that must lose the value is the same hazard spelled as a cast. Use a wider target type, or change the literal; if truncation to the type's range is genuinely intended, say so with the `wrapping` family, which makes the wraparound part of the type. ## W0402 — `0.0 - x` is not negation Subtracting from a zero literal flips the sign of every float except one: `0.0 - (-0.0)` is `+0.0`, so the idiom silently erases the sign of a negative zero — and sign-honoring code (`copysign`, rounding, formatting of `-0.0`) then misbehaves on exactly one input. Wolf has a real unary minus: write `-x`, which negates every value including the zeros. The warning names the one-input difference because it has produced wrong library results that no test with nonzero data can catch. ## W0601 — this fallible result is silently discarded The expression produces a `!T` value — a result whose error row is part of its meaning — and the statement throws it away, error and all. A failure here vanishes without a trace: no propagation, no handling, no trap, just a dropped row, which is the one way wolf's error channel can be ignored by accident. Propagate it with `?`, handle it with `else`, or match on it; if the failure genuinely does not matter here, bind it away explicitly so the discard is visible to the reader. ## W0602 — a `pub` signature spells its error row anonymously This exported function writes its error row out inline, so every caller now depends on this exact set of tags with nothing naming the set — the row is public API that cannot be referred to, documented once, or evolved in one place. The row's meaning is stable (written rows never widen silently); what is hazardous is its evolution, since every later tag is a source-visible change at every call boundary. State the row deliberately and keep it small — one tag per failure a caller can act on — and reuse the same spelling at every boundary that shares it. ## W0603 — this row tag's case contradicts its payload A row tag's case is the reader's signal about whether there is anything to destructure: payload-free marks are lowercase bare words (`none`, `eof`, `parse`), payload-carrying tags are CapCase and name their payload type (`Parse(ParseErr)`). This tag breaks the pact — a CapCase mark implies data that is not there, a lowercase tag hides data that is, and `none` in particular never carries a payload, because "there is nothing here" and "this went wrong, here is how" are different answers on purpose. Rename the tag to match its payload, or move the payload to a tag whose case admits it. ## W0604 — bare `get` is the checked-access spelling, and this one cannot miss The convention reserves the bare name `get` for checked access: the operation that can find nothing and says so with an absence row (`-> T ! {none}`), consumed by `else` and `?`. This function is named `get` but declares no error row, so it promises a lookup and delivers a total function — callers reach for the `else` that checked access trains them to write, and there is nothing to handle. Give it the absence row if it can miss; name it after what it computes if it cannot. ## W0801 — a capitalized name in this pattern binds instead of matching This scrutinee has no cases for the name to test, so the bare identifier binds a fresh name — the arm matches every value, and any arms below it are dead. A capitalized name in pattern position reads as a variant or tag test, which is exactly why this shape has produced first-arm-always dispatch in correct-looking code. If a constant comparison was meant, use a guard (`n if n == Zed`); if a binding was meant, give it a lowercase name so it reads as one. ## W1001 — this region never allocates Region inference proves that no allocation is ever attributed to this region: nothing is built in it, nothing is moved into it, and its create/free pair is frame-local. The region is pure ceremony — it costs a reader the question "what lives here?" whose answer is "nothing", and it usually marks either leftover scaffolding or an allocation that silently landed in the ambient region instead of the one written for it. Delete the region, or move the allocation it was written for inside it. ## W1002 — this `mut` parameter is never written The parameter is declared `mut`, and the body never assigns to it, never passes it onward as `mut` or `take`, and never mutates through it — the mode buys writeback nothing uses. The cost lands on callers: every call site must spell `f(mut x)` and surrender exclusive access for a write that never happens, and every reader budgets for a mutation that is not there. Drop the `mut` from the parameter and from the call sites that pass it; the read default is the honest mode. (X1: the absence of a keyword is the mode.) ## W1003 — this `take` parameter is returned unchanged `take` is for true consumption — the caller gives the value up and it is gone. This function's body never touches the taken parameter and hands it straight back as its result, so the caller loses the value only to be handed it again through the return: a round trip that consumes nothing and costs every call site its binding. If the caller could reasonably keep using the value, the signature is wrong — take the read default (copying what it returns), or make the consumption real by transforming the value before it leaves. ## W1101 — this write stays inside the task The closure given to `spawn` assigns to a name captured from the enclosing function. Task captures copy (or move) at spawn time, so the write lands on the task's own copy and the enclosing binding never sees it — the program runs, exits cleanly, and computes with a value that was never updated. Send the result over a channel, or return it through the scope's join, so the data flow between tasks is explicit; cross-task shared mutation is what `sync` types are for. ## W1102 — the closure captured this value before it changed A closure captures by value at the moment it is created, and this binding is assigned after that moment — every later call of the closure still sees the old value. The program is legal and the copy semantics are deliberate; what bites is the reading, since the code looks like the closure tracks the variable. Create the closure after the last assignment, pass the value as a parameter at each call, or restructure so the captured binding never changes underneath it. ## W1301 — this `unsafe` block does not state its invariant Every `unsafe` block discharges a proof obligation the checker cannot: some invariant, maintained by this module, makes the raw-tier operations inside defined. The reader auditing the module needs that invariant written down, next to the block that relies on it — the convention is a `# Safety:` comment immediately above the block (or on its first line) stating what must hold and why it does. This is a style lint, not a gate: the block still checks and compiles. Add the comment; future auditors — including `wolf audit-surface` — read the rings by exactly these markers. ## W1302 — an `assume noalias` operand was reassigned `assume noalias p, q` asserts, at the point it is written, that its operands never alias — and the check is spent exactly there. Reassigning an operand afterwards leaves the assertion talking about a pointer the name no longer holds: `p = q` after the assume makes the two names alias while the license to assume otherwise still stands, which is undefined behavior waiting for an optimizer. State the assumption after the last assignment of its operands, or bind the asserted pointers to names that never change. ## W1501 — this doc comment links to a name that does not resolve A bracketed dotted path in a doc comment — `[List.push]`, `[connect]` — is an intra-doc link, and it resolves through the compiler's own name resolution rather than through string matching. That is the whole point: when the item it names is renamed or removed, the link breaks HERE, loudly, instead of silently becoming a dead reference on a published page. Write the path the code writes, or, if the brackets were meant as ordinary prose, drop them. Cross-package `std.` paths are not checked against this package's names and never warn. Documentation carries the same covenant as tests: it is verified, or it is not trusted.