Types in shadow mode
Which types the language has, where you write them, what the type check reports in edition 1 and what it only counts. A type never changes what a script does.
Edition 1 has types, and it does not enforce them. You can write a type after a name, declare your own records and enums, and the checker reads them, but a wrong type is not an error in this edition. The type check runs in shadow mode: it reports a small set of mistakes (below), and for everything else it only counts what it would say, for the people who build the language. A script runs the same with the types it declares and without them.
This page says what is checked, what is not, and what a type does for you today.
The types#
| Kind | Names |
|---|---|
| Numbers and text | int, float, bool, text, time, price |
| Settings and sources | color, source, timeframe, symbol, session |
| Collections | array<T>, map<K, V>, matrix<T>, written with the type of their items: array<float>, map<int, text> |
| Objects of the chart | line, label, box, table, polyline and chart.point |
| Your own | a type (a record with named fields) and an enum (a fixed set of names) |
| Option sets | the set of words an option takes, such as h_align (left, center, right) |
Where you write a type#
After the name, with a colon:
let average: float = tech.sma(close, 20)
mut largest: float = na
state window: array<float> = array<float>()
input length: int = 20, name = "Length"
fn spread(high_price: float, low_price: float): float = high_price - low_priceOn a function, the types of the parameters and of the result. In a type, the type of each field. The type of an input is part of its definition (Inputs).
A record is declared with type and built with its fields by name. A method is a function written fn Type.name(...): its first parameter is the value in front of the dot, usually called self. An enum is declared with enum and read as Name.member:
// Types in shadow mode: a record and an enum are declared, constructed and read, and the checker reports their misuse as findings while the run is unchanged.
indicator "Records and enums"
edition = 1
display = pane
enum Trend
up
down
flat
type Reading
average: float = 0
trend: Trend = Trend.flat
input length: int = 20, name = "Length", min = 2
let average = tech.sma(close, length)
let reading = Reading(average = average, trend = if close > average then Trend.up else Trend.down)
plot(reading.average, name = "Average")
plot(if reading.trend == Trend.up then 1 else -1, name = "Direction", style = histogram)A record kept in a state can be replaced by a new one and changed by its methods:
// A record kept in a state, with a method of its own. Zone holds a price band and the number of bars that closed inside it.
// A new band is made every `length` bars from the highest high and lowest low; `zone.touch(close)` is a call of the method defined on the type.
indicator "Zone with a record"
edition = 1
display = price
type Zone
top: float = na
bottom: float = na
inside: int = 0
fn Zone.touch(self: Zone, price: float)
if price <= self.top and price >= self.bottom
self.inside += 1
input length: int = 30, name = "Length", min = 5
let top = tech.highest(high, length)
let bottom = tech.lowest(low, length)
state zone: Zone = Zone()
if bar.index % length == 0
zone = Zone(top = top, bottom = bottom)
zone.touch(close)
plot(zone.top, name = "Top", color = color.red)
plot(zone.bottom, name = "Bottom", color = color.green)
plot(zone.inside, name = "Closes inside", display = none)What is reported#
Three groups of mistakes are errors in edition 1.
Option words. An option takes a word from a fixed set: style = line in plot, h_align = left in table.cell. The word is read in the place where it is written, because the same word can belong to several sets (top is a vertical alignment and a place for a mark). Three things can go wrong, and each has its own code:
E3400: you declared a name that is also a word of that place, and it has another type. Your name wins, so the option would get your value, and the engine says so instead of choosing for you.E3406: a bare word that belongs to several sets stands on its own (let where = top), so nothing says which set it is from.E3407: a bare word is not a word of that place. The message names the nearest word and lists the ones the place has.
// Mistake: `top` is an option word of more than one set (a vertical alignment and a position), and a bare `let` does not say which one is meant (E3406).
indicator "Ambiguous option word"
edition = 1
display = price
let where = top
if bar.is_last
let t = table.new(top_right, 1, 1)
table.cell(t, 0, 0, "text", v_align = where)
plot(close)Write the type, and the word is read as a word of that set:
// The fix for E3406: write the type of the variable, so the word is read as a word of that set.
indicator "Option word with a type"
edition = 1
display = price
let where: v_align = top
if bar.is_last
let t = table.new(top_right, 1, 1)
table.cell(t, 0, 0, "text", v_align = where)
plot(close)// Mistake: `lin` is not a plot style (E3407). The message names the nearest word and lists the words that exist.
indicator "Unknown option word"
edition = 1
display = price
plot(close, name = "Close", style = lin)// Mistake: a variable named `left` hides the option word `left`, so `h_align = left` reads the number, not the alignment (E3400).
indicator "Variable hides an option word"
edition = 1
display = price
let left = 5
if bar.is_last
let t = table.new(top_right, 1, 1)
table.cell(t, 0, 0, "text", h_align = left)
plot(close)Records and enums. A field that the record does not have in a constructor is E3130, and a member that the enum does not have is E3171.
Methods. A method call such as items.at(i) is read by the type of the value in front of it: at is not the same method on an array and on a map. When the type is not known and the name could mean either, the call is E3170 and asks you to write the type; the same happens when you define a method of one name on two types and call it on a parameter that has no type.
What is not reported#
These are all accepted in edition 1 and run without a message. The programs on this page stay silent about them on purpose:
- A value that does not fit the type written after its name:
let a: int = "hello", or a text where a record field wants a number. - A call whose argument is of the wrong type, and an operation between a text and a number.
- A field that the record does not have, when it is read (
reading.nothing). - A
nawhere a number is wanted:let x: int = na.
The type check does count these, in a tool of the language's own tests that runs over the programs of the project, so that the people who build the language can see how often each would appear before any of them becomes a message. Nothing is decided here: the rules that would report them are deferred. A rule leaves shadow mode only when its count over those programs is zero or every case has been looked at. Until then this page is the list.
How the checker finds a type#
It knows a type when the script says it or when every place agrees: an annotation, a constructor or an array written out (array<int>(3, 0), [1, 2]), a field of a record, an item of an array whose item type is written, and the first value of a name together with every assignment to it. One assignment of another type, a compound assignment, a function parameter without a type, a loop variable: the type is then unknown. An unknown type is never an error and never changes what a script does. If you want the checker to know a type that it cannot work out, write it.
See also: Values, Functions, Diagnostics, Examples: types in shadow mode.