1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
local vm = require 'vm'
local util = require 'utility'
local guide = require 'parser.guide'
local config = require 'config'
local lang = require 'language'
local function getKey(src)
if src.type == 'library' then
if src.name:sub(1, 1) == '@' then
return
end
end
local key = vm.getKeyName(src)
if not key or #key <= 2 then
if not src.index then
return '[any]'
end
local class = vm.getClass(src.index)
if class then
return ('[%s]'):format(class)
end
local tp = vm.getInferType(src.index)
if tp then
return ('[%s]'):format(tp)
end
return '[any]'
end
local ktype = key:sub(1, 2)
key = key:sub(3)
if ktype == 's|' then
if key:match '^[%a_][%w_]*$' then
return key
else
return ('[%s]'):format(util.viewLiteral(key))
end
end
return ('[%s]'):format(key)
end
local function getFieldFast(src)
local value = guide.getObjectValue(src) or src
if not value then
return 'any'
end
if value.library then
return value.type, util.viewLiteral(value.value)
end
if value.type == 'boolean' then
return value.type, util.viewLiteral(value[1])
end
if value.type == 'number'
or value.type == 'integer' then
if math.tointeger(value[1]) then
if config.config.runtime.version == 'Lua 5.3'
or config.config.runtime.version == 'Lua 5.4' then
return 'integer', util.viewLiteral(value[1])
end
end
return value.type, util.viewLiteral(value[1])
end
if value.type == 'table'
or value.type == 'function' then
return value.type
end
if value.type == 'string' then
local literal = value[1]
if type(literal) == 'string' and #literal >= 50 then
literal = literal:sub(1, 47) .. '...'
end
return value.type, util.viewLiteral(literal)
end
end
local function getFieldFull(src)
local tp = vm.getInferType(src)
local class = vm.getClass(src)
local literal = vm.getInferLiteral(src)
if type(literal) == 'string' and #literal >= 50 then
literal = literal:sub(1, 47) .. '...'
end
return class or tp, literal
end
local function getField(src, timeUp, mark, key)
if src.type == 'table'
or src.type == 'function' then
return nil
end
if src.parent then
if src.parent.type == 'tableindex'
or src.parent.type == 'setindex'
or src.parent.type == 'getindex' then
if src.parent.index == src then
src = src.parent
end
end
end
local tp, literal
tp, literal = getFieldFast(src)
if tp then
return tp, literal
end
if timeUp or mark[key] then
return nil
end
mark[key] = true
tp, literal = getFieldFull(src)
if tp then
return tp, literal
end
return nil
end
local function buildAsHash(classes, literals)
local keys = {}
for k in pairs(classes) do
keys[#keys+1] = k
end
table.sort(keys)
local lines = {}
lines[#lines+1] = '{'
for _, key in ipairs(keys) do
local class = classes[key]
local literal = literals[key]
if literal then
lines[#lines+1] = (' %s: %s = %s,'):format(key, class, literal)
else
lines[#lines+1] = (' %s: %s,'):format(key, class)
end
end
lines[#lines+1] = '}'
return table.concat(lines, '\n')
end
local function buildAsConst(classes, literals)
local keys = {}
for k in pairs(classes) do
keys[#keys+1] = k
end
table.sort(keys, function (a, b)
return tonumber(literals[a]) < tonumber(literals[b])
end)
local lines = {}
lines[#lines+1] = '{'
for _, key in ipairs(keys) do
local class = classes[key]
local literal = literals[key]
if literal then
lines[#lines+1] = (' %s: %s = %s,'):format(key, class, literal)
else
lines[#lines+1] = (' %s: %s,'):format(key, class)
end
end
lines[#lines+1] = '}'
return table.concat(lines, '\n')
end
local function mergeLiteral(literals)
local results = {}
local mark = {}
for _, value in ipairs(literals) do
if not mark[value] then
mark[value] = true
results[#results+1] = value
end
end
if #results == 0 then
return nil
end
table.sort(results)
return table.concat(results, '|')
end
local function mergeTypes(types)
local results = {}
local mark = {
-- 讲道理table的keyvalue不会是nil
['nil'] = true,
}
for _, tv in ipairs(types) do
for tp in tv:gmatch '[^|]+' do
if not mark[tp] then
mark[tp] = true
results[#results+1] = tp
end
end
end
return guide.mergeTypes(results)
end
local function clearClasses(classes)
local knownClasses = {
['any'] = true,
['nil'] = true,
}
local anyClasses = {}
local strClasses = {}
for key, class in pairs(classes) do
if key == '[any]' then
util.array2hash(class, anyClasses)
goto CONTINUE
elseif key == '[string]' then
util.array2hash(class, strClasses)
goto CONTINUE
end
util.array2hash(class, knownClasses)
::CONTINUE::
end
for c in pairs(knownClasses) do
anyClasses[c] = nil
strClasses[c] = nil
end
if next(anyClasses) then
classes['[any]'] = util.hash2array(anyClasses)
else
classes['[any]'] = nil
end
if next(strClasses) then
classes['[string]'] = util.hash2array(strClasses)
else
classes['[string]'] = nil
end
end
return function (source)
local literals = {}
local classes = {}
local clock = os.clock()
local timeUp
local mark = {}
local fields = vm.getFields(source, 'deep')
for _, src in ipairs(fields) do
local key = getKey(src)
if not key then
goto CONTINUE
end
if not classes[key] then
classes[key] = {}
end
if not literals[key] then
literals[key] = {}
end
if not TEST and os.clock() - clock > 3 then
timeUp = true
end
local class, literal = getField(src, timeUp, mark, key)
if literal == 'nil' then
literal = nil
end
classes[key][#classes[key]+1] = class
literals[key][#literals[key]+1] = literal
::CONTINUE::
end
clearClasses(classes)
for key, class in pairs(classes) do
literals[key] = mergeLiteral(literals[key])
classes[key] = mergeTypes(class)
end
if not next(classes) then
return '{}'
end
local intValue = true
for key, class in pairs(classes) do
if class ~= 'integer' or not tonumber(literals[key]) then
intValue = false
break
end
end
local result
if intValue then
result = buildAsConst(classes, literals)
else
result = buildAsHash(classes, literals)
end
if timeUp then
result = '\n--出于性能考虑,已禁用了部分类型推断。\n' .. result
result = ('\n--%s\n%s'):format(lang.script.HOVER_TABLE_TIME_UP, result)
end
return result
end
|