Skip to content

Types and Values

lash is dynamically typed. Variables do not carry type annotations; their type is determined at runtime by the value they hold.

TypeExample
stringlet greeting = "hello"
intlet count = 42
floatlet ratio = 3.14
boollet verbose = true
listlet files = ["a.txt", "b.txt"]
objectlet config = { editor: "vim", tabs: 4 }
semverlet ver = semver("1.2.3")

Every type defines .min and .max properties:

Type.min.max
int-92233720368547758089223372036854775807
float-1.7976931348623157e+3081.7976931348623157e+308
string"" (empty string)undefined
boolfalsetrue
list[] (empty list)undefined
object{} (empty object)undefined

The semver() constructor parses a semantic version string and returns a tagged object:

let ver = semver("1.2.3-beta+build.42")
FieldTypeDescription
majorintMajor version number
minorintMinor version number
patchintPatch version number
prereleasestringPre-release identifier (may be "")
buildstringBuild metadata (may be "")

The object carries a hidden __type entry (value "semver") excluded from keys() output. Comparison operators follow semver 2.0 precedence rules. String representation: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD].

In addition to the built-in types, scripts and plugins can declare their own types with type Name { ... }. A user-defined type isn’t a new runtime value shape — values remain strings — but it carries validation and completion metadata that drive typed parameter checking and tab-complete:

/// A local or remote git branch name.
type GitBranch {
cache = 2s
values(query) { `git branch -a --format=%(refname:short)` }
validate(x) { x in values("") } // optional
}
ClausePurpose
values(query)Required for completion types. Returns a list of candidate values, optionally filtered by the user’s current prefix. Feeds tab completion.
validate(x)Optional. Returns a boolean for whether x is acceptable. When omitted, lash derives it as x in values("").
cache = <duration>Optional. Memoizes values(query) per-query for the given TTL. Units: ms, s, m, h, d. Errors are never cached.
template = "path"Optional. Binds the type to a text template. Constructing the type renders the template with the literal’s fields; fields are inferred from the template body. See Typed Templates.

A function parameter typed with a user-defined type (fn gco(ref: GitBranch) { ... }) runs validate on the raw argument; a failure produces 'X' is not a valid GitBranch. In a union (GitBranch | int), the arms are tried in source order — the first one whose validate succeeds wins.

A type with a template clause is a different beast: it doesn’t carry validation or completion — its fields are inferred from the template, and constructing it produces a renderable value with implicit .render() and .write(stream) methods. See Typed Templates for the full feature.

See Defining a custom type for completion for a worked completion-type example, and Generate config from typed templates for a worked template-type example.

These properties are available on all value types:

  • .isSuccess — Returns true if the value is “successful”. For command capture results (raw or .capture objects), checks exit code == 0. For other values, returns truthiness.
  • .isFailure — The inverse of .isSuccess.
let items = [1, 2, 3]
let mixed = ["hello", 42, true]

Lists can hold values of any type, including mixed types.

let config = { host: "localhost", port: 8080 }

Bracket syntax and dot syntax are both supported:

config["host"] # bracket access
config.host # dot access (when key is a valid identifier)

Dot access on objects resolves the key name as a property lookup. It must not collide with built-in method names — use bracket syntax when a key matches a method name.

let nums = 1..10 # produces [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ranges produce integer lists and are usable anywhere a list is expected.