Deprecation
A deprecated clause above a declaration or a member keeps it working for its callers, warns at every use outside its file, and names the replacement that torb lint --fix writes.
A declaration that turned out wrong is not removed while somebody still calls it: it is marked. The mark says why, what
to write instead and since which version, the checker warns at every use, and torb lint --fix moves the uses to the
replacement. That is how the standard library grows within a major version without breaking a program.
Example#
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
/** The horizontal position. */
fn x(): Int {
radius * 2
}
/** The distance from the origin, under its old name. */
deprecated("it says what it is under its new name", replacement: "length")
fn size(): Int {
radius
}
/** The distance from the origin. */
fn length(): Int {
radius
}
}
/** Reads a point, under its old name. */
deprecated("the name says too little", replacement: "parsePoint", since: "0.4")
public fn parse(text: String): Point {
parsePoint text
}
/** Reads a point from the length of a text. */
public fn parsePoint(text: String): Point {
Point radius: text.byteLength()
}A file that imports these and uses the old names gets one warning per use, and still builds:
warning: `Point.x` is deprecated since 0.4: it is computed from the polar form now
--> src/main.trb:4:13
|
4 | print point.x
| ^
= Write `x()` instead; `torb lint --fix --rule deprecated` rewrites it
1 file, no problems, 1 warningSyntax#
/** <doc comment> */
deprecated("<why>"[, replacement: "<name>" | "<name>()"][, since: "<major>.<minor>[.<patch>]"])
<declaration or member>A parameter carries the same clause, on a line of its own in front of it; its name is the label that is deprecated:
fn <name>(
<parameter>,
deprecated("<why>"[, replacement: "<parameter>"][, since: "<major>.<minor>[.<patch>]"])
<deprecated parameter> = <default>,
<parameter>,
)Rules#
The clause stands on a line of its own, below the doc comment and above what it marks. A doc comment between the clause and the declaration is an error; the comment belongs above the clause.
The reason is required and comes first. A warning prints it after "is deprecated since 0.4:", so it reads as the rest of that sentence.
replacement:names what the declaration's own scope has: another member of the same type for a member, a name the module declares or imports for a declaration of the module.()behind the name says that a use becomes a call without arguments, and is only allowed behind a method or a function. A name that does not resolve is an error at the clause:Does not compiletype Point { deprecated("the name was a mistake", replacement: "horizontal") x: Int } print Point(1).x // error: `horizontal` is no member of `Point`since:is the version the declaration became deprecated in, two or three numbers:"0.4","1.2.3".Every argument is a string literal without an interpolation. The compiler reads the clause; nothing of it runs.
It marks what somebody names: a
fn, atype, atrait, a type alias, aconstof a module, and a field, a method, a constant or a case of a type, a trait, anextendor aforeignblock. On ause, anextend, aforeignblock or a declaration inside a body it is an error:Does not compilefn outer() { deprecated("nobody outside calls it") fn inner() { } inner() } outer() // error: A declaration inside a body cannot be deprecatedEvery use outside the file that declares it is a warning. 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, acopyor a case, a name in a type position. Three places are no use: the file that declares it, a declaration that is deprecated itself, and a pattern - amatchhas to name every case for as long as the case exists.A warning is no problem.
torb checkprints the warnings after the problems and counts them in its last line,torb buildprints them and builds, andtorb runandtorb testprint nothing of them: their output is the program's.torb check --deny deprecatedmakes every one of them a problem for that run.A deprecated field may share its name with the method that replaces it. A type has one namespace of members, and this is the one pair that may share a name: a field whose replacement is
"x()"and a methodxthat takes nothing.point.xreads the field, with the warning, andpoint.x()calls the method. Without the clause, or with a method that takes an argument, the two names collide as they always do.torb lint --fix --rule deprecatedwrites the replacement at every use it can stand in, and renames theusethat imported a deprecated declaration. A read of the fieldxbecomesx(); a method as the target of an assignment or the label of a constructor has no fix, and the finding stays. The label of an argument for a deprecated parameter (rule 11) becomes the label of its replacement; an argument by position has no fix.A parameter may be deprecated, and it keeps working while it is. The clause stands on a line of its own in front of the parameter, and four rules hold it:
- the parameter has a default, so that a caller who follows the warning can leave the label out;
replacement:names another parameter of the same function, of the same type;- a call that passes the deprecated parameter and its replacement is an error;
- a variadic,
varorlazyparameter, one that takes a receiver closure, and a parameter of a function inside a body cannot be deprecated.
Does not compilefn connect( host: String, deprecated("it is called timeout now", replacement: "timeout", since: "0.4") wait: Int = 30, timeout: Int = 30, ): String { "{host}:{timeout}" } print connect("example", wait: 1, timeout: 2) // error: `wait` and `timeout` are both passedA call that passes the argument by label or by position is a use, and the warning names the replacement:
`connect(wait:)` is deprecated since 0.4: it is called timeout now. The places of rule 7 are no use either. Tuple labels and the fields of a case are not parameters and carry no clause.
What this is not#
It is not a comment. Go marks a deprecated declaration with a Deprecated: paragraph that only some tools read;
here the compiler reads the clause, checks what it names, and every tool - torb check, torb lint, torb doc, the
language server - says the same thing about it. A doc comment keeps saying what the declaration does.
It is not an annotation. The language has none (CONCEPT, Decision Log), and deprecated is no general mechanism
for attaching data to a declaration: it is one clause with three arguments, whose meaning the compiler knows.
It is not a removal. A deprecated declaration builds, runs and behaves as it did; it goes away in the next major version, and until then the warning and the fix are how its callers learn and move.
Related#
- Visibility - what a declaration's
publicpromises, which is what a deprecation keeps. - Why a method is a constant - the one namespace of members, and the one pair rule 9 lets share a name.
- torb lint - the rule
deprecatedand its fix. - torb doc - where a page says that a construct is deprecated.