Sistema de Tipos
Wippy incluye un sistema de tipos gradual con verificación sensible al flujo. Los tipos son no nulos por defecto.
Primitivos
local n: number = 3.14
local i: integer = 42 -- integer es subtipo de number
local s: string = "hello"
local b: boolean = true
local a: any = "anything" -- dinámico explícito (excluir verificación)
local u: unknown = something -- debe reducirse antes de usar
any vs unknown
-- any: excluir verificación de tipos
local a: any = get_data()
a.foo.bar.baz() -- sin error, puede fallar en tiempo de ejecución
-- unknown: desconocido seguro, debe reducirse antes de usar
local u: unknown = get_data()
u.foo -- ERROR: no se puede acceder a propiedad de unknown
if type(u) == "table" then
-- u reducido a table aquí
end
Seguridad de Nil
Los tipos son no nulos por defecto. Use ? para valores opcionales:
local x: number = nil -- ERROR: nil no asignable a number
local y: number? = nil -- OK: number? significa "number o nil"
local z: number? = 42 -- OK
Reducción por Flujo de Control
El verificador de tipos rastrea el flujo de control:
local function process(x: number?): number
if x ~= nil then
return x -- x es number aquí
end
return 0
end
-- Patrón de retorno temprano
local user, err = get_user(123)
if err then return nil, err end
-- user reducido a no nulo aquí
-- O por defecto
local val = get_value() or 0 -- val: number
Tipos Union
local val: number | string = get_value()
if type(val) == "number" then
print(val + 1) -- val: number
else
print(val:upper()) -- val: string
end
Tipos Literales
type Status = "pending" | "active" | "done"
local s: Status = "pending" -- OK
local s: Status = "invalid" -- ERROR
Tipos de Función
local function add(a: number, b: number): number
return a + b
end
-- Múltiples retornos
local function div_mod(a: number, b: number): (number, number)
return math.floor(a / b), a % b
end
-- Retornos de error (idioma Lua)
local function fetch(url: string): (string?, error?)
-- devuelve (data, nil) o (nil, error)
end
-- Tipos de función de primera clase
local double: (number) -> number = function(x: number): number
return x * 2
end
Funciones Variádicas
local function sum(...: number): number
local total: number = 0
for _, v in ipairs({...}) do
total = total + v
end
return total
end
Tipos Record
type User = {name: string, age: number}
local u: User = {name = "alice", age = 25}
Campos Opcionales
type Config = {
host: string,
port: number,
timeout?: number,
debug?: boolean
}
local cfg: Config = {host = "localhost", port = 8080} -- OK
Genéricos
local function identity<T>(x: T): T
return x
end
local n: number = identity(42)
local s: string = identity("hello")
Genéricos con Restricciones
type HasName = {name: string}
local function greet<T: HasName>(obj: T): string
return "Hello, " .. obj.name
end
greet({name = "Alice"}) -- OK
greet({age = 30}) -- ERROR: falta 'name'
Tipos Intersección
Combine múltiples tipos:
type Named = {name: string}
type Aged = {age: number}
type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}
Uniones Etiquetadas
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
El Tipo never
never es el tipo inferior - no existen valores:
function fail(msg: string): never
error(msg)
end
Patrón de Manejo de Errores
El verificador entiende el idioma de error de Lua:
local value, err = call()
if err then
-- value es nil aquí
return nil, err
end
-- value es no nulo aquí, err es nil
print(value)
Aserción No Nula
Use ! para asegurar que una expresión es no nula:
local user: User? = get_user()
local name = user!.name -- asegura que user es no nulo
Si el valor es nil en tiempo de ejecución, se genera un error. Use cuando sepa que un valor no puede ser nil pero el verificador de tipos no puede probarlo.
Conversiones de Tipo
Conversión Segura (Validación)
Llame a un tipo como función para validar y convertir:
local data: any = get_json()
local user = User(data) -- valida y devuelve User
local name = user.name -- acceso seguro a campo
Funciona con primitivos y tipos personalizados:
local x: any = get_value()
local s = string(x) -- convertir a string
local n = integer(x) -- convertir a integer
local b = boolean(x) -- convertir a boolean
type Point = {x: number, y: number}
local p = Point(data) -- valida estructura de record
Método Type:is()
Validar sin lanzar, devuelve (value, nil) o (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 es Point válido
else
return nil, err -- validación fallida
end
El resultado se reduce en condicionales:
if Point:is(data) then
local p: Point = data -- data reducido a Point
end
Conversión Insegura
Use :: o as para conversiones sin verificar:
local data: any = get_data()
local user = data :: User -- sin verificación en tiempo de ejecución
local user = data as User -- igual que ::
Use con moderación. Las conversiones inseguras omiten la validación y pueden causar errores en tiempo de ejecución si el valor no coincide con el tipo.
Reflexión de Tipos
Los tipos son valores de primera clase con métodos de introspección.
Kind y Name
print(Number:kind()) -- "number"
print(Point:kind()) -- "record"
print(Point:name()) -- "Point"
Campos de Record
Itere sobre campos de record:
type User = {name: string, age: number}
for name, typ in User:fields() do
print(name, typ:kind())
end
-- name string
-- age number
Acceda a tipos de campo individuales:
local nameType = User.name -- tipo del campo 'name'
print(nameType:kind()) -- "string"
Tipos de Colección
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"
Tipos Opcionales
local opt: number? = nil
local optType = typeof(opt)
print(optType:kind()) -- "optional"
print(optType:inner():kind()) -- "number"
Tipos Union
type Status = "pending" | "active" | "done"
for variant in Status:variants() do
print(variant)
end
Tipos de Función
local fn: (number, string) -> boolean
local fnType = typeof(fn)
for param in fnType:params() do
print(param:kind())
end
print(fnType:ret():kind()) -- "boolean"
Comparación de Tipos
print(Number == Number) -- true
print(Integer <= Number) -- true (subtipo)
print(Integer < Number) -- true (subtipo estricto)
Tipos como Claves de Tabla
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
Anotaciones de Tipo
Agregue tipos a firmas de función:
-- Tipos de parámetro y retorno
local function process(input: string): number
return #input
end
-- Tipos de variable local
local count: number = 0
-- Alias de tipo
type StringArray = {string}
type StringMap = {[string]: number}
Validadores de Tipo
Agregue restricciones de validación en tiempo de ejecución a tipos usando anotaciones:
-- Validador único
local x: number @min(0) = 1
-- Múltiples validadores
local x: number @min(0) @max(100) = 50
-- Patrón de string
local email: string @pattern("^.+@.+$") = "test@example.com"
-- Validador sin argumentos
local x: number @integer = 42
Validadores Incorporados
| Validador | Se aplica a | Ejemplo |
|---|---|---|
@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" |
Validadores de Campo de Record
type User = {
age: number @min(0) @max(150),
name: string @min_len(1) @max_len(100)
}
Validadores de Elemento de Array
local scores: {number @min(0) @max(100)} = {85, 90}
Validadores de Miembro de Union
local id: number @min(1) | string @min_len(1) = 1
Reglas de Varianza
| Posición | Varianza | Descripción |
|---|---|---|
| Campo de solo lectura | Covariante | Puede usar subtipo |
| Campo mutable | Invariante | Debe coincidir exactamente |
| Parámetro de función | Contravariante | Puede usar supertipo |
| Retorno de función | Covariante | Puede usar subtipo |
Subtipado
integeres subtipo denumberneveres subtipo de todos los tipos- Todos los tipos son subtipos de
any - Subtipado de union:
Aes subtipo deA | B
Adopción Gradual
Agregue tipos incrementalmente - el código sin tipos continúa funcionando:
-- El código existente funciona sin cambios
function old_function(x)
return x + 1
end
-- El nuevo código obtiene tipos
function new_function(x: number): number
return x + 1
end
Comience agregando tipos a:
- Firmas de función en límites de API
- Manejadores HTTP y consumidores de cola
- Lógica de negocio crítica
Verificación de Tipos
Ejecute el verificador de tipos:
wippy lint
Reporta errores de tipo sin ejecutar código.