-
-
Notifications
You must be signed in to change notification settings - Fork 389
No unknown operations diagnostic #3303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
bcbe3ca
fcc9046
0945478
04bd571
46ebddf
de31a1a
4b2a927
39105bb
67bca18
e82df04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| local files = require 'files' | ||
| local guide = require 'parser.guide' | ||
| local lang = require 'language' | ||
| local vm = require 'vm' | ||
| local await = require 'await' | ||
|
|
||
| ---@async | ||
| return function (uri, callback) | ||
| local state = files.getState(uri) | ||
| if not state then return end | ||
| -- TODO: no-unknown doesn't do this but missing-local-export-doc does, is this actually needed? | ||
| if not state.ast then return end | ||
|
|
||
| -- calls are complicated because unknown arguments may or may not cause an introduction of an unknown type | ||
| -- integer(unknown) :: unknown should count as introducing an unknown, but function(unknown) :: unknown should not. We can't directly | ||
| -- check function because it might be overloaded or have a call operator defined. | ||
| ---@async | ||
| guide.eachSourceType(state.ast, 'call', function (source) | ||
| await.delay() | ||
|
|
||
| local resultInfer = vm.getInfer(source):view(uri) | ||
| if resultInfer ~= 'unknown' then return end | ||
| local functionType = vm.getInfer(source.node) | ||
| if functionType:view(uri) == 'unknown' then return end -- we can't say anything about what unknown types support | ||
| if functionType:isCallable(uri) then return end | ||
| callback { | ||
| start = source.start, | ||
| finish = source.finish, | ||
| message = lang.script('DIAG_UNKNOWN_OPERATION_CALL', functionType:view(uri)), | ||
| } | ||
| end) | ||
|
|
||
| -- binary operators are quite similar to function calls, they introduce an unknown if the result is unknown and none of the | ||
| -- parameters are unknown, or if the left side is known to not implement the operator | ||
|
|
||
| ---@async | ||
| guide.eachSourceType(state.ast, 'binary', function (source) | ||
| await.delay() | ||
|
|
||
| local resultInfer = vm.getInfer(source) | ||
| if resultInfer:view(uri) ~= 'unknown' then return end | ||
| local left, right = source[1], source[2] | ||
| local leftInfer, rightInfer = vm.getInfer(left), vm.getInfer(right) | ||
| if leftInfer:view(uri) == 'unknown' then return end | ||
| if rightInfer:view(uri) ~= 'unknown' then | ||
| -- the operator doesn't work for these types | ||
| callback { | ||
| start = source.start, | ||
| finish = source.finish, | ||
| message = lang.script('DIAG_UNKNOWN_OPERATION_OPERATOR', source.op.type, leftInfer:view(uri), rightInfer:view(uri)), | ||
| } | ||
| return | ||
| end | ||
|
|
||
| -- TODO: it seems that if the operator is defined and the other arg is unkown it is always inferred as the | ||
| -- return type of the operator, so we can't check that case currently | ||
|
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| end) | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| TEST [[ | ||
| local x = 5 | ||
| <!x()!> | ||
|
|
||
| ---@overload fun(): string | ||
| local x = 5 | ||
| x() | ||
|
|
||
| ---@class x | ||
| ---@operator call(): string | ||
| local x = {} | ||
| x() | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| ---@type nil | ||
| local x | ||
| <!x()!> | ||
|
|
||
| ---@overload fun(): string | ||
| local x | ||
| x() | ||
|
|
||
| ---@class x | ||
| ---@operator call(): string | ||
| local x | ||
| x() | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| ---@type unknown | ||
| local x | ||
| x() | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| local function f() | ||
| end | ||
| f() | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| <!(0)()!> | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| <!("")()!> | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| (function() end)() | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| local _ = 1 + 2 | ||
| local _ = <!"1" + "2"!> | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| local _ = "a" .. "b" | ||
| ---@class Vec2 | ||
|
|
||
| ---@type Vec2, Vec2 | ||
| local a, b = {}, {} | ||
| local _ = <!a .. b!> | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| ---@type unknown | ||
| local a | ||
| local b = a + 2 | ||
| ]] | ||
|
|
||
| TEST [[ | ||
| ---@param name string | ||
| ---@return string | ||
| local function greet(name) | ||
| return "Hello " .. <!name "!"!> | ||
| end | ||
| ]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
TODOhere questions the necessity of theif not state.ast then return endcheck. It's a good practice to keep this check for robustness. It acts as a safeguard against potential crashes iffiles.getState(uri)returns a state object that, for some reason (like a severe parsing error), lacks an AST. Keeping this check makes the diagnostic more resilient. I'd recommend removing the TODO comment.