Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
Deprecation#
Status: decided and built (2026-09-28). — CONCEPT's open question "deprecated (and since)" is answered here, and
everything section 3 decides is in the compiler: the clause in the parser, its rules and the warning at every use in the
checker, the rule deprecated of torb lint with its fix, the notice of torb doc, and the modifier, the tag and the
quick fix of the language server. torb check --deny deprecated is built too (2026-10-08). Section 6 lists what is
left.
A 1.0 that cannot deprecate can only freeze. RELEASE.md section 3 promises that std only grows within 1.x: a
member that turns out wrong is not removed, it is marked, and torb lint --fix moves its callers to what replaces it.
That needs something the compiler reads - a warning at the use, a rewrite a tool can apply - and not a sentence in a doc
comment that nothing checks.
public type Point {
/** The horizontal position, as it was stored. */
deprecated("it is computed from the polar form now", replacement: "x()", since: "0.4")
x: Int = 0
radius: Int
angle: Float
/** The horizontal position. */
fn x(): Int {
...
}
}warning: `Point.x` is deprecated since 0.4: it is computed from the polar form now
--> src/main.trb:12:13
|
12 | print point.x
| ^
= Write `x()` instead; `torb lint --fix --rule deprecated` rewrites it- 1. What was asked
- 2. Where this exists already
- 3. The decision — the clause, the rules, the warning, the fix
- 4. The field that becomes a method — the use case that settles the shape
- 5. What each tool does with it
- 6. Open
1. What was asked#
CONCEPT, "Open Questions", before this record:
deprecated(andsince): not documentation but something the compiler has to read. A modifier? Decide when the first API needs it. The use case that settles its shape: a field that becomes a method. The field stays for one version next to the new method, markeddeprecatedwith its replacement, andtorb lint --fixrewrites the callers (.xto.x()); the language server offers the same as a quick fix.
Three constraints come with the language:
- There are no annotations. CONCEPT's Decision Log refuses them ("No AST macros, no annotations"), and a tag inside a doc comment is refused as "annotations through the back door" (CONCEPT, "Doc Comments").
- A modifier is a word in front of a declaration:
public,private,protected,native,shared,static,lazy. The one modifier that ever took an argument wasprivate(var), whichprotected varreplaced. - The compiler reads it, so nothing of it is evaluated. A reason, a replacement and a version are constants of the
source, like the path of a
use.
2. Where this exists already#
| Language | Spelling | Reason | Replacement a tool applies | Version | Where it warns |
|---|---|---|---|---|---|
| Rust | #[deprecated(since = "1.2", note = "...")] | note | none in the attribute; clippy has its own table | since | every use, the crate itself included |
| Swift | @available(*, deprecated, renamed: "x()", message: "...") | message | renamed, which Xcode's fix-it applies | deprecated: 5.5 per platform | every use |
| Kotlin | @Deprecated("...", ReplaceWith("x()"), level = WARNING) | required | ReplaceWith, an expression the IDE substitutes | @SinceKotlin beside it | every use; level makes it an error or hides it |
| Java | @Deprecated(since = "9", forRemoval = true) plus the Javadoc tag | the Javadoc | none | since | outside the outermost class that declares it |
| C# | [Obsolete("...", error: false)] | the message | none | none | every use |
| Go | a // Deprecated: ... paragraph in the doc comment | the paragraph | none; gopls and staticcheck read the paragraph | none | only in the tools that read it |
| Dart | @Deprecated("...") | required | dart fix reads a separate data file | none | every use |
| Zig | none: the declaration becomes a @compileError | the error | none | none | — |
What they agree on: a reason is part of the mark; a use is a warning and not an error, because the point is that the old code keeps building; and the tools that migrate need the replacement as data. Swift and Kotlin are the two that carry the replacement in the mark itself, and they are the two whose editors rewrite a use in one step.
3. The decision#
deprecated(...) is a clause on the line above a declaration or a member, below its doc comment: a reason, then
replacement: and since:, every one a string literal the compiler reads.
/** <doc comment> */
deprecated("<why>"[, replacement: "<name>" | "<name>()"][, since: "<major>.<minor>[.<patch>]"])
<declaration or member>| Option | Example | For | Against |
|---|---|---|---|
| A clause of its own line, above the declaration — the decision | deprecated("why", replacement: "x()", since: "0.4") then x: Int | reads like the call it resembles; one line whatever the declaration; the declaration's own line stays what it was, so a diff that deprecates adds a line and changes none | a modifier with arguments, which the language had once (private(var)) and gave up; a word that is not reserved |
| A modifier in the line of the declaration | deprecated("why") fn size(): Int | where every other modifier stands | a long line on every deprecated member, broken by the formatter at the clause's parenthesis; the signature torb doc shows would carry the clause |
| A keyword without arguments, the rest in the doc comment | deprecated fn size(): Int and a # Deprecated heading | the smallest syntax | the replacement is prose a tool has to guess from; "no second syntax inside comments" (CONCEPT); a seventh heading |
| A doc comment paragraph (Go) | Deprecated: use [length] instead. | no syntax at all | exactly "annotations through the back door"; the compiler reads comments for nothing else |
| An annotation (Rust, Swift, Kotlin, Java) | @deprecated(...) | familiar | the language has no annotations and does not get them for one use |
The rules, each held by the parser or the checker with a message:
- The reason is required and comes first. A mark that says neither why nor what instead helps nobody; a warning
says the reason after "is deprecated", so it reads as the rest of a sentence (
the name says too little). replacement:is a name of the declaration's own scope: another member of the same type for a member, a name the module declares or imports otherwise.()behind it says the use becomes a call without arguments - a field that turned into a method - and is allowed only behind a function. A name that does not resolve is an error at the clause, so the fixtorb lintwrites cannot name nothing.since:is a version of two or three numbers, the number of the release the declaration became deprecated in - the one number RELEASE.md section 3 gives the toolchain, the language andstdtogether.- The arguments are string literals without interpolation. Nothing runs; a checked program's clause is data.
- It stands on a line of its own, below the doc comment and above the declaration. A doc comment below it is an error that still keeps the comment.
- It marks what somebody names: a
fn, atype, atrait, a type alias, apublic constof a module, and a field, a method, a constant or a case of a type, a trait, anextendor aforeignblock. Ause, anextendand aforeignblock are no declarations anybody names, and a declaration inside a body has no caller outside it: the clause on one of them is an error. deprecatedis a word only here. It is not reserved: a field, a parameter, a label and a function may still be nameddeprecated, and a calldeprecated(...)stays a call unless the next line starts a declaration.
A use is a warning, and a warning is no problem. torb check prints it after the problems and counts it in its
last line (3 files, no problems, 2 warnings); torb build prints it and builds. torb run and torb test print
nothing of it, because their output is the program's: a program that uses a deprecated declaration writes what it
writes.
What a use is. A name the checker resolved to the declaration: a member behind a ., a bare name, a case, the label
of a constructor, a copy or a case, a name in a type position. Three kinds of place are no use:
- the file that declares it, which keeps its implementation, the members that stand in for it and its examples;
- a declaration that is deprecated itself, whose code stays for old callers and may lean on what stays with it;
- a pattern, because a
matchhas to name every case for as long as the case exists - warning there would warn about the one thing a program cannot leave out.
A parameter. The same clause stands in a parameter list, on a line of its own in front of the parameter, and the parameter's name is the label it deprecates (2026-10-23):
public fn connect(
host: String,
deprecated("it is called timeout now", replacement: "timeout", since: "0.4")
wait: Int = 30,
timeout: Int = 30,
): ConnectionThe formatter keeps the clause above the parameter and a parameter after it on a line of its own. Four rules hold it, each an error with a message at the clause:
- The deprecated parameter has a default, so that a caller who follows the warning can leave the label out altogether.
replacement:names another parameter of the same function with the same type, so that renaming the label alone keeps the call whole. An unknown name, ax(), the parameter itself or a type that differs is an error.- A call that passes the deprecated parameter and its replacement is an error at the deprecated argument: the call says the same thing twice, and no fix could choose.
- A variadic,
var,lazyand receiver-closure parameter cannot be deprecated, and neither can a parameter of a function inside a body.
A call that passes the argument by label or by position is a use, and a warning like the others: `connect(wait:)` is deprecated since 0.4: it is called timeout now, with the note Pass `timeout:` instead. The same three places are no
use (the declaring file, a deprecated declaration, a pattern), and --deny deprecated makes it a problem. A tuple label
and the field of a case are not parameters and carry no clause.
4. The field that becomes a method#
A type has one namespace of members (CONCEPT, "Members"): a field and a method never share a name, because x would
mean two things. The use case CONCEPT names is exactly that pair, for one version: the field x stays for its callers
and the method x() replaces it. So the one pair of members that may share a name is a deprecated field whose
replacement is "x()" and a method x without parameters. Reading point.x reads the field, with the warning;
calling point.x() calls the method; torb lint --fix turns the first into the second. Every other pair is the error it
always was, including this one without the clause, and with a method that takes an argument.
The price is one sentence in the member rule, and it is paid only while the field is deprecated: removing the field in the next major removes the exception with it.
5. What each tool does with it#
torb check: the errors of section 3, and a warning per use (of a parameter: per argument) with a note that names the replacement;--deny deprecatedmakes each of those warnings a problem.torb lint --rule deprecated: the same uses as findings, each with its fix where the replacement can stand in its place - the name,x()for a read of a field that became a method - and theusethat imports a deprecated declaration renamed with it where the file does not see the replacement already. No fix where it cannot stand: a method as the target of an assignment or the label of a constructor, a name imported under another one. For a deprecated parameter the fix renames the label (wait:totimeout:); a positional argument and a call that passes the replacement as well keep the finding.torb doc: the item carriesdeprecationin its JSON (reason,replacement,since), and its page saysDeprecated since 0.4: why. Write x() instead.above the documentation.torb lsp: a use is a diagnostic of severity warning with the tagDeprecated(which an editor strikes through) and the fix as a quick fix; the declaration and every use carry the semantic token modifierdeprecated; a hover says it above the doc comment; a completion item carries the tag.
6. Open#
sincefor an addition. Kotlin's@SinceKotlinand Swift'sintroduced:say since when a declaration exists, so that a package whoselanguageline names an older version is told it uses something too new. The same clause could take it (available(since: "1.2")), and the checker would compare it with thelanguageline of docs/design/PROJECT.md section 10. It is an addition and waits for the first minor release after 1.0.- A level. Kotlin's
level = ERRORmakes a use an error for the last version before the removal. The smaller step is built (2026-10-08):torb check --deny deprecatedturns every warning of that run into a problem - anerror:block with the message, span and note of the warning, counted in the last line, exit1- and nothing else changes: it is no setting of a project, andbuild,run,test,lintand the language server do not take it. A level of the declaration itself, which makes the use an error for everyone, is not built. - A deprecated parameter in
torb docand the language server. The parameter clause (section 3) is checked, warned at and, withtorb lint, fixed; the entry of the parameter in the JSON and the page oftorb doc, and the tag of the language server on the argument, follow. - A deprecated tuple label or case field. Neither is a parameter; a renamed label there is a new overload today.