类型系统
实验性。 预计存在一些限制。
Wippy 包含一个具有流敏感检查的渐进式类型系统。类型默认为非空。
local n: number = 3.14local i: integer = 42 -- integer 是 number 的子类型local s: string = "hello"local b: boolean = truelocal a: any = "anything" -- 显式动态(退出检查)local u: unknown = something -- 必须先收窄才能使用any 与 unknown
Section titled “any 与 unknown”-- any: 退出类型检查local a: any = get_data()a.foo.bar.baz() -- 无错误,运行时可能崩溃
-- unknown: 安全的未知类型,必须先收窄才能使用local u: unknown = get_data()u.foo -- 错误:无法访问 unknown 的属性if type(u) == "table" then -- 此处 u 收窄为 tableendNil 安全
Section titled “Nil 安全”类型默认为非空。使用 ? 表示可选值:
local x: number = nil -- 错误:nil 不能赋给 numberlocal y: number? = nil -- 正确:number? 表示"number 或 nil"local z: number? = 42 -- 正确类型检查器跟踪控制流:
local function process(x: number?): number if x ~= nil then return x -- 此处 x 是 number end return 0end
-- 早期返回模式local user, err = get_user(123)if err then return nil, err end-- 此处 user 收窄为非 nil
-- 或默认值local val = get_value() or 0 -- val: numberlocal val: number | string = get_value()
if type(val) == "number" then print(val + 1) -- val: numberelse print(val:upper()) -- val: stringendtype Status = "pending" | "active" | "done"
local s: Status = "pending" -- 正确local s: Status = "invalid" -- 错误local function add(a: number, b: number): number return a + bend
-- 多返回值local function div_mod(a: number, b: number): (number, number) return math.floor(a / b), a % bend
-- 错误返回(Lua 习惯用法)local function fetch(url: string): (string?, error?) -- 返回 (data, nil) 或 (nil, error)end
-- 一等函数类型local double: (number) -> number = function(x: number): number return x * 2end可变参数函数
Section titled “可变参数函数”local function sum(...: number): number local total: number = 0 for _, v in ipairs({...}) do total = total + v end return totalendtype 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} -- 正确local function identity<T>(x: T): T return xend
local n: number = identity(42)local s: string = identity("hello")type HasName = {name: string}
local function greet<T: HasName>(obj: T): string return "Hello, " .. obj.nameend
greet({name = "Alice"}) -- 正确greet({age = 30}) -- 错误:缺少 'name'合并多个类型:
type Named = {name: string}type Aged = {age: number}type Person = Named & Aged
local p: Person = {name = "Alice", age = 30}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 endendnever 类型
Section titled “never 类型”never 是底类型——不存在任何值:
function fail(msg: string): never error(msg)end错误处理模式
Section titled “错误处理模式”检查器理解 Lua 的错误习惯用法:
local value, err = call()if err then -- 此处 value 为 nil return nil, errend-- 此处 value 为非 nil,err 为 nilprint(value)非 Nil 断言
Section titled “非 Nil 断言”使用 ! 断言表达式为非 nil:
local user: User? = get_user()local name = user!.name -- 断言 user 为非 nil如果运行时值为 nil,则抛出错误。在你知道某值不能为 nil 但类型检查器无法证明时使用。
安全转换(验证)
Section titled “安全转换(验证)”将类型作为函数调用以验证并转换:
local data: any = get_json()local user = User(data) -- 验证并返回 Userlocal name = user.name -- 安全字段访问适用于基本类型和自定义类型:
local x: any = get_value()local s = string(x) -- 转换为 stringlocal n = integer(x) -- 转换为 integerlocal b = boolean(x) -- 转换为 boolean
type Point = {x: number, y: number}local p = Point(data) -- 验证记录结构Type:is() 方法
Section titled “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 是有效的 Pointelse return nil, err -- 验证失败end结果在条件中收窄:
if Point:is(data) then local p: Point = data -- data 收窄为 Pointend使用 :: 或 as 进行未检查的转换:
local data: any = get_data()local user = data :: User -- 无运行时检查local user = data as User -- 与 :: 相同少用。不安全转换绕过验证,如果值与类型不匹配可能引发运行时错误。
类型是带有内省方法的一等值。
print(Number:kind()) -- "number"print(Point:kind()) -- "record"print(Point:name()) -- "Point"遍历记录字段:
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? = nillocal optType = typeof(opt)print(optType:kind()) -- "optional"print(optType:inner():kind()) -- "number"type Status = "pending" | "active" | "done"
for variant in Status:variants() do print(variant)endlocal fn: (number, string) -> boolean
local fnType = typeof(fn)for param in fnType:params() do print(param:kind())endprint(fnType:ret():kind()) -- "boolean"print(Number == Number) -- trueprint(Integer <= Number) -- true (子类型)print(Integer < Number) -- true (严格子类型)类型作为表键
Section titled “类型作为表键”local handlers = {}handlers[Number] = function() return "number handler" endhandlers[String] = function() return "string handler" end
local h = handlers[typeof(value)]if h then h() end为函数签名添加类型:
-- 参数和返回类型local function process(input: string): number return #inputend
-- 局部变量类型local count: number = 0
-- 类型别名type StringArray = {string}type StringMap = {[string]: number}使用注解为类型添加运行时验证约束:
-- 单个验证器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" |
记录字段验证器
Section titled “记录字段验证器”type User = { age: number @min(0) @max(150), name: string @min_len(1) @max_len(100)}数组元素验证器
Section titled “数组元素验证器”local scores: {number @min(0) @max(100)} = {85, 90}联合成员验证器
Section titled “联合成员验证器”local id: number @min(1) | string @min_len(1) = 1| 位置 | 变型 | 说明 |
|---|---|---|
| 只读字段 | 协变 | 可使用子类型 |
| 可变字段 | 不变 | 必须精确匹配 |
| 函数参数 | 逆变 | 可使用超类型 |
| 函数返回 | 协变 | 可使用子类型 |
integer是number的子类型never是所有类型的子类型- 所有类型都是
any的子类型 - 联合子类型化:
A是A | B的子类型
逐步添加类型——未类型化的代码继续工作:
-- 现有代码不变地工作function old_function(x) return x + 1end
-- 新代码获得类型function new_function(x: number): number return x + 1end从向以下位置添加类型开始:
- API 边界处的函数签名
- HTTP 处理函数和队列消费者
- 关键业务逻辑
运行类型检查器:
wippy lint报告类型错误而不执行代码。