PAYATHON 2026

Luau 中 Signal.new 的返回类型为何不匹配?

数据阿明

结论

报错是因为 Signal 被声明为普通结构化表类型,而 setmetatable 返回的是带有 Signal 元表的表。两者的类型形状不一致,导致返回类型检查失败。:: Signal 也不能将这两个不相关的类型强行转换。

要让 Signal 实例类型包含 Signal 元表,可以先定义实例字段,再通过 typeof(setmetatable(...)) 声明导出的类型:

--!strict

local Signal = {}
Signal.__index = Signal

type SignalData = {
	_callbacks: { (any) -> () },
	_waiting: { thread },
}

export type Connection = {
	Disconnect: () -> (),
}

export type Signal = typeof(setmetatable({} :: SignalData, Signal))

function Signal.new(): Signal
	return setmetatable({
		_callbacks = {},
		_waiting = {},
	}, Signal)
end

这样,Signal 表示带有 Signal 元表的实例,Signal.new() 的返回类型也与函数标注一致。原代码中的 Connect、Disconnect、Fire 和 Wait 等方法仍可定义在 Signal 上,实例会通过 __index 使用这些方法。

为什么 :: Signal 没有解决问题

原先的 export type Signal = { ... } 描述普通表,并在字段中列出了方法。实际表达式的类型则带有 @metatable Signal。类型检查器无法对应这两种类型,因此类型断言也无法消除它们之间的不兼容,最终报告 types are unrelated。

在类型定义中说明实例使用的 metatable 后,就可以移除返回值上的 :: Signal,让 setmetatable 的结果直接通过返回类型检查。

检查要点

  • SignalData 描述实例自身存储的字段,例如 _callbacks 和 _waiting。
  • Signal.__index = Signal 指定实例查找方法时使用 Signal 表。
  • typeof(setmetatable({} :: SignalData, Signal)) 将字段类型与 metatable 类型结合起来。
  • 后续修改字段或方法签名时,也要检查相关类型定义,确保实例结构与方法实现一致。

备注:内容仅供参考。