Система типов
Экспериментально. Возможны некоторые ограничения.
Wippy включает постепенную систему типов с потоково-чувствительной проверкой. Типы по умолчанию non-nullable.
Примитивы
local n: number = 3.14
local i: integer = 42 -- integer — подтип number
local s: string = "hello"
local b: boolean = true
local a: any = "anything" -- явная динамика (отказ от проверки)
local u: unknown = something -- нужно сузить перед использованием
any vs unknown
-- any: отказ от проверки типов
local a: any = get_data()
a.foo.bar.baz() -- нет ошибки, может упасть в runtime
-- unknown: безопасный неизвестный тип, нужно сузить перед использованием
local u: unknown = get_data()
u.foo -- ОШИБКА: нельзя обращаться к полю unknown
if type(u) == "table" then
-- u сужено до table здесь
end
Безопасность nil
Типы по умолчанию non-nullable. Используйте ? для опциональных значений:
local x: number = nil -- ОШИБКА: nil не присваивается number
local y: number? = nil -- OK: number? означает "number или nil"
local z: number? = 42 -- OK
Сужение по потоку управления
Проверщик типов отслеживает поток управления:
local function process(x: number?): number
if x ~= nil then
return x -- x — number здесь
end
return 0
end
-- Паттерн раннего возврата
local user, err = get_user(123)
if err then return nil, err end
-- user сужено до non-nil здесь
-- Или значение по умолчанию
local val = get_value() or 0 -- val: number
Union-типы
local val: number | string = get_value()
if type(val) == "number" then
print(val + 1) -- val: number
else
print(val:upper()) -- val: string
end
Литеральные типы
type Status = "pending" | "active" | "done"
local s: Status = "pending" -- OK
local s: Status = "invalid" -- ОШИБКА
Типы функций
local function add(a: number, b: number): number
return a + b
end
-- Несколько возвратов
local function div_mod(a: number, b: number): (number, number)
return math.floor(a / b), a % b
end
-- Возврат ошибки (идиома Lua)
local function fetch(url: string): (string?, error?)
-- возвращает (data, nil) или (nil, error)
end
-- Функциональные типы первого класса
local double: (number) -> number = function(x: number): number
return x * 2
end
Variadic-функции
local function sum(...: number): number
local total: number = 0
for _, v in ipairs({...}) do
total = total + v
end
return total
end
Record-типы
type User = {name: string, age: number}
local u: User = {name = "alice", age = 25}
Опциональные поля
type Config = {
host: string,
port: number,
timeout?: number,
debug?: boolean
}
local cfg: Config = {host = "localhost", port = 8080} -- OK
Generics
local function identity<T>(x: T): T
return x
end
local n: number = identity(42)
local s: string = identity("hello")
Generics с ограничениями
type HasName = {name: string}
local function greet<T: HasName>(obj: T): string
return "Hello, " .. obj.name
end
greet({name = "Alice"}) -- OK
greet({age = 30}) -- ОШИБКА: отсутствует 'name'
Intersection-типы
Объединяют несколько типов:
type Named = {name: string}
type Aged = {age: number}
type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}
Tagged-юнионы
type Result<T, E> =
| {ok: true, value: T}
| {ok: false, error: E}
type LoadState =
| {status: "loading"}
| {status: "loaded", data: User}
| {status: "error", message: string}
local function render(state: LoadState): string
if state.status == "loading" then
return "Loading..."
elseif state.status == "loaded" then
return "Hello, " .. state.data.name
elseif state.status == "error" then
return "Error: " .. state.message
end
end
Тип never
never — нижний тип, не имеет значений:
function fail(msg: string): never
error(msg)
end
Паттерн обработки ошибок
Проверщик понимает идиому ошибок Lua:
local value, err = call()
if err then
-- value — nil здесь
return nil, err
end
-- value — non-nil здесь, err — nil
print(value)
Утверждение non-nil
Используйте ! для утверждения, что выражение не nil:
local user: User? = get_user()
local name = user!.name -- утверждаем, что user не nil
Если значение во время выполнения nil, возникает ошибка. Используйте, когда вы знаете, что значение не может быть nil, но проверщик типов не может это доказать.
Приведения типов
Безопасное приведение (валидация)
Вызовите тип как функцию для валидации и приведения:
local data: any = get_json()
local user = User(data) -- валидирует и возвращает User
local name = user.name -- безопасный доступ к полю
Работает с примитивами и пользовательскими типами:
local x: any = get_value()
local s = string(x) -- приведение к string
local n = integer(x) -- приведение к integer
local b = boolean(x) -- приведение к boolean
type Point = {x: number, y: number}
local p = Point(data) -- валидирует структуру record
Метод Type:is()
Валидирует без выброса ошибки, возвращает (value, nil) или (nil, error):
type Point = {x: number, y: number}
local data: any = get_input()
local p, err = Point:is(data)
if p then
local sum = p.x + p.y -- p — валидный Point
else
return nil, err -- валидация не прошла
end
Результат сужается в условиях:
if Point:is(data) then
local p: Point = data -- data сужено до Point
end
Небезопасное приведение
Используйте :: или as для непроверяемых приведений:
local data: any = get_data()
local user = data :: User -- без проверки в runtime
local user = data as User -- то же что и ::
Используйте редко. Небезопасные приведения обходят валидацию и могут вызвать ошибки в runtime, если значение не соответствует типу.
Рефлексия типов
Типы — значения первого класса с методами интроспекции.
Kind и Name
print(Number:kind()) -- "number"
print(Point:kind()) -- "record"
print(Point:name()) -- "Point"
Поля record
Итерация по полям record:
type User = {name: string, age: number}
for name, typ in User:fields() do
print(name, typ:kind())
end
-- name string
-- age number
Доступ к типам отдельных полей:
local nameType = User.name -- тип поля 'name'
print(nameType:kind()) -- "string"
Типы коллекций
local arr: {number} = {1, 2, 3}
local arrType = typeof(arr)
print(arrType:elem():kind()) -- "number"
local map: {[string]: number} = {}
local mapType = typeof(map)
print(mapType:key():kind()) -- "string"
print(mapType:val():kind()) -- "number"
Опциональные типы
local opt: number? = nil
local optType = typeof(opt)
print(optType:kind()) -- "optional"
print(optType:inner():kind()) -- "number"
Union-типы
type Status = "pending" | "active" | "done"
for variant in Status:variants() do
print(variant)
end
Типы функций
local fn: (number, string) -> boolean
local fnType = typeof(fn)
for param in fnType:params() do
print(param:kind())
end
print(fnType:ret():kind()) -- "boolean"
Сравнение типов
print(Number == Number) -- true
print(Integer <= Number) -- true (подтип)
print(Integer < Number) -- true (строгий подтип)
Типы как ключи таблиц
local handlers = {}
handlers[Number] = function() return "number handler" end
handlers[String] = function() return "string handler" end
local h = handlers[typeof(value)]
if h then h() end
Аннотации типов
Добавляйте типы в сигнатуры функций:
-- Типы параметров и возврата
local function process(input: string): number
return #input
end
-- Типы локальных переменных
local count: number = 0
-- Псевдонимы типов
type StringArray = {string}
type StringMap = {[string]: number}
Валидаторы типов
Добавляйте runtime-ограничения валидации к типам с помощью аннотаций:
-- Один валидатор
local x: number @min(0) = 1
-- Несколько валидаторов
local x: number @min(0) @max(100) = 50
-- Шаблон строки
local email: string @pattern("^.+@.+$") = "test@example.com"
-- Валидатор без аргументов
local x: number @integer = 42
Встроенные валидаторы
| Валидатор | Применяется к | Пример |
|---|---|---|
@min(n) |
number | local x: number @min(0) = 1 |
@max(n) |
number | local x: number @max(100) = 50 |
@min_len(n) |
string, array | local s: string @min_len(1) = "hi" |
@max_len(n) |
string, array | local s: string @max_len(10) = "hi" |
@pattern(regex) |
string | local email: string @pattern("^.+@.+$") = "a@b.com" |
Валидаторы полей record
type User = {
age: number @min(0) @max(150),
name: string @min_len(1) @max_len(100)
}
Валидаторы элементов массива
local scores: {number @min(0) @max(100)} = {85, 90}
Валидаторы членов union
local id: number @min(1) | string @min_len(1) = 1
Правила вариантности
| Позиция | Вариантность | Описание |
|---|---|---|
| Readonly-поле | Ковариантно | Можно использовать подтип |
| Mutable-поле | Инвариантно | Должно совпадать точно |
| Параметр функции | Контравариантно | Можно использовать супертип |
| Возврат функции | Ковариантно | Можно использовать подтип |
Подтипизация
integer— подтипnumbernever— подтип всех типов- Все типы — подтипы
any - Подтипизация union:
A— подтипA | B
Постепенное внедрение
Добавляйте типы постепенно — нетипизированный код продолжает работать:
-- Существующий код работает без изменений
function old_function(x)
return x + 1
end
-- Новый код получает типы
function new_function(x: number): number
return x + 1
end
Начните с добавления типов:
- В сигнатурах функций на границах API
- В HTTP-обработчиках и потребителях очередей
- В критической бизнес-логике
Проверка типов
Запустите проверщик типов:
wippy lint
Сообщает об ошибках типов без выполнения кода.