@thigasdevelopment/luam 0.19.1 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -280,7 +280,10 @@ VSCodium and Windsurf. The language server itself is editor-agnostic and speaks
280
280
  - **A class is a type everywhere, a value from its declaration** — `extends` may
281
281
  name a parent written further down, a top-level `new` may not.
282
282
  - **The MTA catalog can lag a release** — a newer function stays `any`.
283
- - **No declared metamethods or generic classes.**
283
+ - **Three metamethods stay blocked** — `__index`, `__newindex` and `__call`.
284
+ Generic classes are supported.
285
+ - **A method the receiver does not declare is not reported** — `counter:missing()`
286
+ keeps returning `any`; annotate the receiver to have the call checked.
284
287
  - **The editor re-checks by declaration** — a declaration change re-analyzes every
285
288
  file that can see it, an edit inside a function body only its own file.
286
289
  - **An export is named, never verified** against the side that calls it.
package/lua/class.lua CHANGED
@@ -2,6 +2,22 @@ local classes = {}
2
2
 
3
3
  local constructors = {}
4
4
 
5
+ local ALLOWED_METAMETHODS = {
6
+ ['__tostring'] = true,
7
+ ['__eq'] = true,
8
+ ['__lt'] = true,
9
+ ['__le'] = true,
10
+ ['__len'] = true,
11
+ ['__concat'] = true,
12
+ ['__unm'] = true,
13
+ ['__add'] = true,
14
+ ['__sub'] = true,
15
+ ['__mul'] = true,
16
+ ['__div'] = true,
17
+ ['__mod'] = true,
18
+ ['__pow'] = true,
19
+ }
20
+
5
21
  local BLOCKED_METAMETHODS = {
6
22
  ['__call'] = true,
7
23
  ['__index'] = true,
@@ -140,19 +156,41 @@ function class(name)
140
156
  })
141
157
  end
142
158
 
143
- local function instanceMetatable(definition)
144
- local meta = { __index = definition }
145
-
146
- if not definition.__metamethods then
147
- return meta
148
- end
149
-
150
- for key, value in pairs(definition.__metamethods) do
159
+ local function installMetamethods(meta, source)
160
+ for key, value in pairs(source) do
151
161
  if BLOCKED_METAMETHODS[key] then
152
162
  error('Cannot override metamethod ' .. tostring(key) .. '.')
153
163
  end
154
164
 
155
- meta[key] = value
165
+ if ALLOWED_METAMETHODS[key] then
166
+ meta[key] = value
167
+ end
168
+ end
169
+ end
170
+
171
+ local function inheritedMetamethods(meta, definition)
172
+ local chain = {}
173
+ local current = definition.__super
174
+
175
+ while type(current) == 'table' do
176
+ table.insert(chain, current)
177
+
178
+ current = current.__super
179
+ end
180
+
181
+ for index = #chain, 1, -1 do
182
+ installMetamethods(meta, chain[index])
183
+ end
184
+ end
185
+
186
+ local function instanceMetatable(definition)
187
+ local meta = { __index = definition }
188
+
189
+ inheritedMetamethods(meta, definition)
190
+ installMetamethods(meta, definition)
191
+
192
+ if definition.__metamethods then
193
+ installMetamethods(meta, definition.__metamethods)
156
194
  end
157
195
 
158
196
  return meta
@@ -0,0 +1,239 @@
1
+ local MAX_DEPTH = 16
2
+ local MAX_ENTRIES = 4096
3
+ local MAX_STRING = 65536
4
+
5
+ local describe
6
+
7
+ local function joinPath(path, segment)
8
+ if path == '' then
9
+ return segment
10
+ end
11
+
12
+ return path .. '.' .. segment
13
+ end
14
+
15
+ local function describeUnion(descriptor)
16
+ local parts = {}
17
+
18
+ for index = 1, #descriptor.options do
19
+ parts[index] = describe(descriptor.options[index])
20
+ end
21
+
22
+ return table.concat(parts, ' | ')
23
+ end
24
+
25
+ describe = function(descriptor)
26
+ local kind = descriptor.kind
27
+
28
+ if kind == 'literal' then
29
+ if type(descriptor.value) == 'string' then
30
+ return "'" .. descriptor.value .. "'"
31
+ end
32
+
33
+ return tostring(descriptor.value)
34
+ end
35
+
36
+ if kind == 'optional' then
37
+ return describe(descriptor.element) .. '?'
38
+ end
39
+
40
+ if kind == 'array' then
41
+ return describe(descriptor.element) .. '[]'
42
+ end
43
+
44
+ if kind == 'map' then
45
+ return 'table<' .. describe(descriptor.key) .. ', ' .. describe(descriptor.value) .. '>'
46
+ end
47
+
48
+ if kind == 'union' then
49
+ return describeUnion(descriptor)
50
+ end
51
+
52
+ if kind == 'record' or kind == 'instance' then
53
+ return descriptor.name
54
+ end
55
+
56
+ return kind
57
+ end
58
+
59
+ local function fail(path, descriptor, reason)
60
+ local place = path == '' and 'value' or ('"' .. path .. '"')
61
+
62
+ if reason ~= nil then
63
+ error('luam-validate: ' .. place .. ' ' .. reason, 0)
64
+ end
65
+
66
+ error('luam-validate: ' .. place .. ' expected "' .. describe(descriptor) .. '"', 0)
67
+ end
68
+
69
+ local function countEntries(value, path, descriptor)
70
+ local total = 0
71
+
72
+ for _ in pairs(value) do
73
+ total = total + 1
74
+
75
+ if total > MAX_ENTRIES then
76
+ fail(path, descriptor, 'has more than ' .. MAX_ENTRIES .. ' entries')
77
+ end
78
+ end
79
+
80
+ return total
81
+ end
82
+
83
+ local function isInstanceOf(value, name)
84
+ local meta = getmetatable(value)
85
+ local definition = meta and meta.__index
86
+ local depth = 0
87
+
88
+ while type(definition) == 'table' and depth <= MAX_DEPTH do
89
+ if definition.__name == name then
90
+ return true
91
+ end
92
+
93
+ definition = definition.__super
94
+ depth = depth + 1
95
+ end
96
+
97
+ return false
98
+ end
99
+
100
+ local check
101
+
102
+ local function checkRecord(value, descriptor, path, depth)
103
+ countEntries(value, path, descriptor)
104
+
105
+ for index = 1, #descriptor.members do
106
+ local member = descriptor.members[index]
107
+
108
+ check(value[member.key], member.value, joinPath(path, member.key), depth + 1)
109
+ end
110
+
111
+ return value
112
+ end
113
+
114
+ local function checkArray(value, descriptor, path, depth)
115
+ local total = countEntries(value, path, descriptor)
116
+
117
+ for index = 1, total do
118
+ check(value[index], descriptor.element, path .. '[' .. index .. ']', depth + 1)
119
+ end
120
+
121
+ return value
122
+ end
123
+
124
+ local function checkMap(value, descriptor, path, depth)
125
+ countEntries(value, path, descriptor)
126
+
127
+ for key, entry in pairs(value) do
128
+ check(key, descriptor.key, joinPath(path, tostring(key)) .. ' (key)', depth + 1)
129
+ check(entry, descriptor.value, joinPath(path, tostring(key)), depth + 1)
130
+ end
131
+
132
+ return value
133
+ end
134
+
135
+ local function matches(value, descriptor, depth)
136
+ local ok = pcall(check, value, descriptor, '', depth)
137
+
138
+ return ok
139
+ end
140
+
141
+ check = function(value, descriptor, path, depth)
142
+ if depth > MAX_DEPTH then
143
+ fail(path, descriptor, 'is nested deeper than ' .. MAX_DEPTH .. ' levels')
144
+ end
145
+
146
+ local kind = descriptor.kind
147
+
148
+ if kind == 'any' then
149
+ return value
150
+ end
151
+
152
+ if kind == 'optional' then
153
+ if value == nil then
154
+ return value
155
+ end
156
+
157
+ return check(value, descriptor.element, path, depth)
158
+ end
159
+
160
+ if kind == 'nil' then
161
+ if value ~= nil then
162
+ fail(path, descriptor)
163
+ end
164
+
165
+ return value
166
+ end
167
+
168
+ if kind == 'literal' then
169
+ if value ~= descriptor.value then
170
+ fail(path, descriptor)
171
+ end
172
+
173
+ return value
174
+ end
175
+
176
+ if kind == 'string' or kind == 'number' or kind == 'boolean' or kind == 'userdata' or kind == 'thread' or kind == 'function' then
177
+ if type(value) ~= kind then
178
+ fail(path, descriptor)
179
+ end
180
+
181
+ if kind == 'string' and #value > MAX_STRING then
182
+ fail(path, descriptor, 'is longer than ' .. MAX_STRING .. ' characters')
183
+ end
184
+
185
+ return value
186
+ end
187
+
188
+ if kind == 'table' then
189
+ if type(value) ~= 'table' then
190
+ fail(path, descriptor)
191
+ end
192
+
193
+ return value
194
+ end
195
+
196
+ if kind == 'union' then
197
+ for index = 1, #descriptor.options do
198
+ if matches(value, descriptor.options[index], depth) then
199
+ return value
200
+ end
201
+ end
202
+
203
+ fail(path, descriptor)
204
+ end
205
+
206
+ if type(value) ~= 'table' then
207
+ fail(path, descriptor)
208
+ end
209
+
210
+ if kind == 'record' then
211
+ return checkRecord(value, descriptor, path, depth)
212
+ end
213
+
214
+ if kind == 'array' then
215
+ return checkArray(value, descriptor, path, depth)
216
+ end
217
+
218
+ if kind == 'map' then
219
+ return checkMap(value, descriptor, path, depth)
220
+ end
221
+
222
+ if kind == 'instance' then
223
+ if not isInstanceOf(value, descriptor.name) then
224
+ fail(path, descriptor)
225
+ end
226
+
227
+ return value
228
+ end
229
+
230
+ return value
231
+ end
232
+
233
+ function __luam_validate(value, descriptor)
234
+ return check(value, descriptor, '', 1)
235
+ end
236
+
237
+ function __luam_matches(value, descriptor)
238
+ return matches(value, descriptor, 1)
239
+ end