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#

CONCEPT, "Open Questions", before this record:

deprecated (and since): 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, marked deprecated with its replacement, and torb lint --fix rewrites the callers (.x to .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 was private(var), which protected var replaced.
  • 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#

LanguageSpellingReasonReplacement a tool appliesVersionWhere it warns
Rust#[deprecated(since = "1.2", note = "...")]notenone in the attribute; clippy has its own tablesinceevery use, the crate itself included
Swift@available(*, deprecated, renamed: "x()", message: "...")messagerenamed, which Xcode's fix-it appliesdeprecated: 5.5 per platformevery use
Kotlin@Deprecated("...", ReplaceWith("x()"), level = WARNING)requiredReplaceWith, an expression the IDE substitutes@SinceKotlin beside itevery use; level makes it an error or hides it
Java@Deprecated(since = "9", forRemoval = true) plus the Javadoc tagthe Javadocnonesinceoutside the outermost class that declares it
C#[Obsolete("...", error: false)]the messagenonenoneevery use
Goa // Deprecated: ... paragraph in the doc commentthe paragraphnone; gopls and staticcheck read the paragraphnoneonly in the tools that read it
Dart@Deprecated("...")requireddart fix reads a separate data filenoneevery use
Zignone: the declaration becomes a @compileErrorthe errornonenone—

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>
OptionExampleForAgainst
A clause of its own line, above the declaration — the decisiondeprecated("why", replacement: "x()", since: "0.4") then x: Intreads 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 nonea 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 declarationdeprecated("why") fn size(): Intwhere every other modifier standsa 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 commentdeprecated fn size(): Int and a # Deprecated headingthe smallest syntaxthe 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 allexactly "annotations through the back door"; the compiler reads comments for nothing else
An annotation (Rust, Swift, Kotlin, Java)@deprecated(...)familiarthe 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:

  1. 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).
  2. 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 fix torb lint writes cannot name nothing.
  3. 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 and std together.
  4. The arguments are string literals without interpolation. Nothing runs; a checked program's clause is data.
  5. 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.
  6. It marks what somebody names: a fn, a type, a trait, a type alias, a public const of a module, and a field, a method, a constant or a case of a type, a trait, an extend or a foreign block. A use, an extend and a foreign block 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.
  7. deprecated is a word only here. It is not reserved: a field, a parameter, a label and a function may still be named deprecated, and a call deprecated(...) 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 match has 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,
): Connection

The 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:

  1. The deprecated parameter has a default, so that a caller who follows the warning can leave the label out altogether.
  2. 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, a x(), the parameter itself or a type that differs is an error.
  3. 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.
  4. A variadic, var, lazy and 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 deprecated makes 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 the use that 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: to timeout:); a positional argument and a call that passes the replacement as well keep the finding.
  • torb doc: the item carries deprecation in its JSON (reason, replacement, since), and its page says Deprecated since 0.4: why. Write x() instead. above the documentation.
  • torb lsp: a use is a diagnostic of severity warning with the tag Deprecated (which an editor strikes through) and the fix as a quick fix; the declaration and every use carry the semantic token modifier deprecated; a hover says it above the doc comment; a completion item carries the tag.

6. Open#

  • since for an addition. Kotlin's @SinceKotlin and Swift's introduced: say since when a declaration exists, so that a package whose language line 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 the language line 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 = ERROR makes a use an error for the last version before the removal. The smaller step is built (2026-10-08): torb check --deny deprecated turns every warning of that run into a problem - an error: block with the message, span and note of the warning, counted in the last line, exit 1 - and nothing else changes: it is no setting of a project, and build, run, test, lint and 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 doc and the language server. The parameter clause (section 3) is checked, warned at and, with torb lint, fixed; the entry of the parameter in the JSON and the page of torb 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.
Esc