Type Annotation in Lua#

Overview#

Lua 语言本身并没有提供任何 type annotation 的支持. 不过对于不同的 IDE, 有一些工具可以提供 type annotation 的支持. 这些支持不会在运行的时候做任何强制性的检查, 而仅仅为 IDE 辅助和文档生成服务的.

在 JetBrain 家的 IDEA 中, EmmyLua 提供了语法高亮, 类型系统, debugger 等支持.

在 VSCode 生态中, lua-language-server (LuaLS) 也提供了类似的功能.

因为我的主力 IDE 是 IDEA, 所以我就选择 EmmyLua 了.

Resource#

我的学习资料主要来自于问 ChatGPT 和 Claude AI. 另外我还发现一个 EmmyLua Annotations 的中文文档很不错.

Example 1#

type_annotation_1.lua
  1---@meta
  2---@diagnostic disable: undefined-global
  3---@diagnostic disable: lowercase-global
  4
  5--[[----------------------------------------------------------------------------
  6标注一个变量
  7--]]----------------------------------------------------------------------------
  8---type annotation 要在声明的前一行
  9---@type number
 10local a_number = 1
 11print(string.format("a_number = %s", a_number))
 12
 13---如果你要加一些描述, 直接在类型后面空格开始写即可
 14---@type string your description here
 15local a_string = "Alice"
 16print(string.format("a_string = %s", a_string))
 17
 18---@type boolean
 19local a_boolean = true
 20print(string.format("a_boolean = %s", a_boolean))
 21
 22--[[----------------------------------------------------------------------------
 23标注一个容器类型的 Table
 24--]]----------------------------------------------------------------------------
 25---@type table
 26local a_list = { 1, 2, 3 }
 27print(string.format("a_list = %s", a_list))
 28
 29---@type table
 30local a_dict = { a = 1, b = 2, c = 3 }
 31print(string.format("a_dict = %s", a_dict))
 32
 33---@type number[]
 34local a_list_of_number = { 1, 2, 3 }
 35print(string.format("a_list_of_number = %s", a_list_of_number))
 36
 37---@type table<string, number>
 38local a_dict_of_string_and_number = { a = 1, b = 2, c = 3 }
 39print(string.format("a_dict_of_string_and_number = %s", a_dict_of_string_and_number))
 40
 41--[[----------------------------------------------------------------------------
 42标注一个结构体类型的 Table
 43--]]----------------------------------------------------------------------------
 44---@class Person
 45---@field name string The person's name
 46---@field dob string Date of birth in string format
 47---@field age number The person's age
 48---@field occupation? string The person's occupation (optional)
 49---@field hobbies? string[] A list of the person's hobbies (optional)
 50local person = {
 51    name = "John Doe",
 52    dob = "1990-01-01",
 53    age = 33,
 54    occupation = "Engineer",
 55    hobbies = { "reading", "swimming" }
 56}
 57print(string.format("person = %s", person))
 58
 59---@type Person[]
 60local person_list = {
 61    {
 62        name = "John Doe",
 63        dob = "1990-01-01",
 64        age = 33,
 65        occupation = "Engineer",
 66        hobbies = { "reading", "swimming" },
 67        wrong_attribute = 123 -- 貌似 IDE 无法发现这里的错误
 68    },
 69    {
 70        name = "Alice",
 71        dob = "1990-01-01",
 72        age = 33,
 73        occupation = "Engineer",
 74        hobbies = { "reading", "swimming" }
 75    }
 76}
 77print(string.format("person_list = %s", person_list))
 78
 79--[[----------------------------------------------------------------------------
 80使用 Alias
 81
 82在上面的例子中我们定义了一个 Person 类型, 之后就可以用 Person[] 来标注一个 Person 类型的数组.
 83但是公布时每个类型都是一个 class, 对于不是 class 的任何比较复杂的类型, 我们都可以用一个 alias 来简化.
 84例如上面这个 Person 的例子等效于下面这个例子:
 85--]]----------------------------------------------------------------------------
 86---@alias AnotherPerson
 87---@field name string The person's name
 88---@field dob string Date of birth in string format
 89---@field age number The person's age
 90---@field occupation? string The person's occupation (optional)
 91---@field hobbies? string[] A list of the person's hobbies (optional)
 92
 93---@type AnotherPerson[]
 94local another_person_list = {
 95    {
 96        name = "John Doe",
 97        dob = "1990-01-01",
 98        age = 33,
 99        occupation = "Engineer",
100        hobbies = { "reading", "swimming" },
101        wrong_attribute = 123 -- 貌似 IDE 无法发现这里的错误
102    },
103    {
104        name = "Alice",
105        dob = "1990-01-01",
106        age = 33,
107        occupation = "Engineer",
108        hobbies = { "reading", "swimming" }
109    }
110}
111print(string.format("another_person_list = %s", another_person_list))
112
113--[[----------------------------------------------------------------------------
114标注一个函数
115--]]----------------------------------------------------------------------------
116---注: 这是最常见的声明函数的方法
117---@param x number description here
118---@param y number description here
119---@return number description here
120local function add_two_value_v1(x, y)
121    return x + y
122end
123print(string.format("res = %s", add_two_value_v1(1, 2)))
124
125---注: 这种声明方式本质上是创建了个匿名函数并赋值给了一个变量, 不太推荐使用
126---@type fun(x: number, y: number): number
127local add_two_value_v2 = function(x, y)
128    return x + y
129end
130print(string.format("res = %s", add_two_value_v2(1, 2)))
[2]:
import subprocess

subprocess.run(["lua", "type_annotation_1.lua"])
a_number = 1
a_string = Alice
[2]:
CompletedProcess(args=['lua', 'type_annotation_1.lua'], returncode=0)
a_boolean = true
a_list = table: 0x600001e54000
a_dict = table: 0x600001e54080
a_list_of_number = table: 0x600001e54100
a_dict_of_string_and_number = table: 0x600001e54140
person = table: 0x600001e54180
person_list = table: 0x600001e54240
another_person_list = table: 0x600001e54200
res = 3
res = 3
[ ]: