Skip to content

Commit bcbe3ca

Browse files
committed
feat(diagnostics): add implementation of no-unknown-operations for most function calls
1 parent fa2c778 commit bcbe3ca

File tree

4 files changed

+95
-43
lines changed

4 files changed

+95
-43
lines changed

locale/en-us/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ DIAG_COUNT_DOWN_LOOP =
9090
'Do you mean `{}` ?'
9191
DIAG_UNKNOWN =
9292
'Can not infer type.'
93+
DIAG_UNKNOWN_OPERATION_CALL =
94+
'Cannot infer call result for type {}.'
9395
DIAG_DEPRECATED =
9496
'Deprecated.'
9597
DIAG_DIFFERENT_REQUIRES =
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
local files = require 'files'
2+
local guide = require 'parser.guide'
3+
local lang = require 'language'
4+
local vm = require 'vm'
5+
local await = require 'await'
6+
7+
---@async
8+
return function (uri, callback)
9+
local state = files.getState(uri)
10+
if not state then return end
11+
-- TODO: no-unknown doesn't do this but missing-local-export-doc does, is this actually needed?
12+
if not state.ast then return end
13+
14+
-- calls are complicated because unknown arguments may or may not cause an introduction of an unknown type
15+
-- integer(unknown) :: unknown should count as introducing an unknown, but function(unknown) :: unknown should not. We can't directly
16+
-- check function because it might be overloaded or have a call operator defined.
17+
---@async
18+
guide.eachSourceType(state.ast, 'call', function (source)
19+
await.delay()
20+
local inferred = vm.getInfer(source):view(uri)
21+
if inferred ~= 'unknown' then return end
22+
local functionType = vm.getInfer(source.node)
23+
if functionType:view(uri) == 'unknown' then return end -- we can't say anything about what unknown types support
24+
local operators = vm.getOperators("call", source.node)
25+
local canCall = functionType:hasFunction(uri) or #operators ~= 0
26+
if canCall then return end
27+
callback {
28+
start = source.start,
29+
finish = source.finish,
30+
message = lang.script('DIAG_UNKNOWN_OPERATION_CALL', functionType:view(uri)),
31+
}
32+
end)
33+
end

script/proto/diagnostic.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ m.register {
160160

161161
m.register {
162162
'no-unknown',
163+
'no-unknown-operations',
163164
} {
164165
group = 'strong',
165166
severity = 'Warning',

script/vm/operator.lua

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---@class vm
2-
local vm = require 'vm.vm'
3-
local util = require 'utility'
4-
local guide = require 'parser.guide'
5-
local config = require 'config'
2+
local vm = require 'vm.vm'
3+
local util = require 'utility'
4+
local guide = require 'parser.guide'
5+
local config = require 'config'
66

7-
vm.UNARY_OP = {
7+
vm.UNARY_OP = {
88
'unm',
99
'bnot',
1010
'len',
1111
}
12-
vm.BINARY_OP = {
12+
vm.BINARY_OP = {
1313
'add',
1414
'sub',
1515
'mul',
@@ -24,17 +24,17 @@ vm.BINARY_OP = {
2424
'shr',
2525
'concat',
2626
}
27-
vm.OTHER_OP = {
27+
vm.OTHER_OP = {
2828
'call',
2929
}
3030

31-
local unaryMap = {
31+
local unaryMap = {
3232
['-'] = 'unm',
3333
['~'] = 'bnot',
3434
['#'] = 'len',
3535
}
3636

37-
local binaryMap = {
37+
local binaryMap = {
3838
['+'] = 'add',
3939
['-'] = 'sub',
4040
['*'] = 'mul',
@@ -50,7 +50,7 @@ local binaryMap = {
5050
['..'] = 'concat',
5151
}
5252

53-
local otherMap = {
53+
local otherMap = {
5454
['()'] = 'call',
5555
}
5656

@@ -96,12 +96,12 @@ end
9696

9797
---@param op string
9898
---@param exp parser.object
99-
---@param value? parser.object
100-
---@return vm.node?
101-
function vm.runOperator(op, exp, value)
99+
---@return parser.object[]
100+
function vm.getOperators(op, exp)
102101
local uri = guide.getUri(exp)
103102
local node = vm.compileNode(exp)
104-
local result
103+
---@type parser.object[]
104+
local operators = {}
105105
for c in node:eachObject() do
106106
if c.type == 'string'
107107
or c.type == 'doc.type.string' then
@@ -111,11 +111,27 @@ function vm.runOperator(op, exp, value)
111111
---@cast c vm.global
112112
for _, set in ipairs(c:getSets(uri)) do
113113
if set.operators and #set.operators > 0 then
114-
result = checkOperators(set.operators, op, value, result)
114+
for _, operator in ipairs(set.operators) do
115+
table.insert(operators, operator)
116+
end
115117
end
116118
end
117119
end
118120
end
121+
return operators
122+
end
123+
124+
---@param op string
125+
---@param exp parser.object
126+
---@param value? parser.object
127+
---@return vm.node?
128+
function vm.runOperator(op, exp, value)
129+
local operators = vm.getOperators(op, exp)
130+
---@type vm.node?
131+
local result
132+
for _, operator in ipairs(operators) do
133+
result = checkOperators(operators, op, value, result)
134+
end
119135
return result
120136
end
121137

@@ -247,10 +263,10 @@ vm.binarySwitch = util.switch()
247263
local op = source.op.type
248264
if a and b then
249265
local result = op == '<<' and a << b
250-
or op == '>>' and a >> b
251-
or op == '&' and a & b
252-
or op == '|' and a | b
253-
or op == '~' and a ~ b
266+
or op == '>>' and a >> b
267+
or op == '&' and a & b
268+
or op == '|' and a | b
269+
or op == '~' and a ~ b
254270
---@diagnostic disable-next-line: missing-fields
255271
vm.setNode(source, {
256272
type = 'integer',
@@ -281,18 +297,18 @@ vm.binarySwitch = util.switch()
281297
local b = vm.getNumber(source[2])
282298
local op = source.op.type
283299
local zero = b == 0
284-
and ( op == '%'
285-
or op == '/'
286-
or op == '//'
287-
)
300+
and (op == '%'
301+
or op == '/'
302+
or op == '//'
303+
)
288304
if a and b and not zero then
289-
local result = op == '+' and a + b
290-
or op == '-' and a - b
291-
or op == '*' and a * b
292-
or op == '/' and a / b
293-
or op == '%' and a % b
294-
or op == '//' and a // b
295-
or op == '^' and a ^ b
305+
local result = op == '+' and a + b
306+
or op == '-' and a - b
307+
or op == '*' and a * b
308+
or op == '/' and a / b
309+
or op == '%' and a % b
310+
or op == '//' and a // b
311+
or op == '^' and a ^ b
296312
---@diagnostic disable-next-line: missing-fields
297313
vm.setNode(source, {
298314
type = (op == '//' or math.type(result) == 'integer') and 'integer' or 'number',
@@ -353,10 +369,10 @@ vm.binarySwitch = util.switch()
353369
end)
354370
: case '..'
355371
: call(function (source)
356-
local a = vm.getString(source[1])
357-
or vm.getNumber(source[1])
358-
local b = vm.getString(source[2])
359-
or vm.getNumber(source[2])
372+
local a = vm.getString(source[1])
373+
or vm.getNumber(source[1])
374+
local b = vm.getString(source[2])
375+
or vm.getNumber(source[2])
360376
if a and b then
361377
if type(a) == 'number' or type(b) == 'number' then
362378
local uri = guide.getUri(source)
@@ -390,13 +406,13 @@ vm.binarySwitch = util.switch()
390406
local infer2 = vm.getInfer(source[2])
391407
if (
392408
infer1:hasType(uri, 'integer')
393-
or infer1:hasType(uri, 'number')
394-
or infer1:hasType(uri, 'string')
409+
or infer1:hasType(uri, 'number')
410+
or infer1:hasType(uri, 'string')
395411
)
396412
and (
397413
infer2:hasType(uri, 'integer')
398-
or infer2:hasType(uri, 'number')
399-
or infer2:hasType(uri, 'string')
414+
or infer2:hasType(uri, 'number')
415+
or infer2:hasType(uri, 'string')
400416
) then
401417
vm.setNode(source, vm.declareGlobal('type', 'string'))
402418
return
@@ -419,17 +435,17 @@ vm.binarySwitch = util.switch()
419435
local b = vm.getNumber(source[2])
420436
if a and b then
421437
local op = source.op.type
422-
local result = op == '>' and a > b
423-
or op == '<' and a < b
424-
or op == '>=' and a >= b
425-
or op == '<=' and a <= b
438+
local result = op == '>' and a > b
439+
or op == '<' and a < b
440+
or op == '>=' and a >= b
441+
or op == '<=' and a <= b
426442
---@diagnostic disable-next-line: missing-fields
427443
vm.setNode(source, {
428444
type = 'boolean',
429445
start = source.start,
430446
finish = source.finish,
431447
parent = source,
432-
[1] =result,
448+
[1] = result,
433449
})
434450
else
435451
vm.setNode(source, vm.declareGlobal('type', 'boolean'))

0 commit comments

Comments
 (0)