summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcarsakiller <carsakiller@gmail.com>2024-05-23 02:22:29 -0400
committerGitHub <noreply@github.com>2024-05-23 02:22:29 -0400
commit459ac5c06ecca35d135217ca0892cc7787a91d33 (patch)
tree6b4891e4c6dae06dfdd10d24f6c213bed24bd8fe
parentb9c9f9423ff31bb9160acc5d75feaec3c0bf777c (diff)
parent87abc4245f2a24e1cc35851b6464af9588934286 (diff)
downloadlua-language-server-459ac5c06ecca35d135217ca0892cc7787a91d33.zip
Merge branch 'master' into 2175-md-symbol-reference
-rw-r--r--.github/workflows/build.yml20
m---------3rd/bee.lua0
m---------3rd/love-api0
m---------3rd/luamake0
-rw-r--r--Dockerfile14
-rw-r--r--changelog.md13
-rw-r--r--doc/en-us/config.md16
-rw-r--r--doc/pt-br/config.md16
-rw-r--r--doc/zh-cn/config.md16
-rw-r--r--doc/zh-tw/config.md16
-rw-r--r--locale/en-us/script.lua2
-rw-r--r--locale/en-us/setting.lua6
-rw-r--r--locale/pt-br/script.lua2
-rw-r--r--locale/pt-br/setting.lua6
-rw-r--r--locale/zh-cn/script.lua2
-rw-r--r--locale/zh-cn/setting.lua6
-rw-r--r--locale/zh-tw/script.lua2
-rw-r--r--locale/zh-tw/setting.lua6
-rw-r--r--main.lua2
-rw-r--r--script/brave/brave.lua12
-rw-r--r--script/cli/check.lua188
-rw-r--r--script/cli/check_worker.lua168
-rw-r--r--script/cli/init.lua5
-rw-r--r--script/config/env.lua67
-rw-r--r--script/config/template.lua1
-rw-r--r--script/core/diagnostics/duplicate-doc-field.lua2
-rw-r--r--script/core/diagnostics/global-element.lua18
-rw-r--r--script/core/diagnostics/lowercase-global.lua18
-rw-r--r--script/core/implementation.lua171
-rw-r--r--script/core/signature.lua5
-rw-r--r--script/core/type-definition.lua3
-rw-r--r--script/files.lua3
-rw-r--r--script/filewatch.lua3
-rw-r--r--script/global.d.lua8
-rw-r--r--script/meta/bee/filesystem.lua7
-rw-r--r--script/meta/bee/filewatch.lua2
-rw-r--r--script/meta/bee/socket.lua2
-rw-r--r--script/meta/bee/sys.lua13
-rw-r--r--script/meta/bee/thread.lua9
-rw-r--r--script/provider/provider.lua108
-rw-r--r--script/pub/pub.lua11
-rw-r--r--script/vm/compiler.lua7
-rw-r--r--script/vm/function.lua75
-rw-r--r--script/vm/global.lua55
-rw-r--r--script/vm/infer.lua3
-rw-r--r--script/vm/node.lua2
-rw-r--r--test.lua4
-rw-r--r--test/implementation/init.lua81
-rw-r--r--test/type_inference/common.lua4194
-rw-r--r--test/type_inference/init.lua4301
-rw-r--r--test/type_inference/param_match.lua139
51 files changed, 5319 insertions, 4511 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 01b377ca..bfedcd50 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,7 +25,7 @@ jobs:
include:
- { os: ubuntu-22.04, target: linux, platform: linux-x64, container: 'alpine:latest', libc: musl }
- { os: ubuntu-20.04, target: linux, platform: linux-x64 }
- #- { os: ubuntu-20.04, target: linux, platform: linux-arm64 }
+ - { os: ubuntu-20.04, target: linux, platform: linux-arm64 }
- { os: macos-11, target: darwin, platform: darwin-x64 }
- { os: macos-11, target: darwin, platform: darwin-arm64 }
- { os: windows-latest, target: windows, platform: win32-ia32 }
@@ -34,6 +34,12 @@ jobs:
container:
image: ${{ matrix.container }}
steps:
+ - name: Install aarch64-linux-gnu
+ if: ${{ matrix.platform == 'linux-arm64' && matrix.libc != 'musl' }}
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
+
- name: Prepare container for musl
if: ${{ matrix.target == 'linux' && matrix.libc == 'musl' }}
run: |
@@ -45,17 +51,23 @@ jobs:
submodules: recursive
- name: Build for others step-1
- if: ${{ matrix.libc != 'musl' }}
+ if: ${{ matrix.platform != 'linux-x64' }}
uses: actboy168/setup-luamake@master
- name: Build for others step-2
- if: ${{ matrix.libc != 'musl' }}
+ if: ${{ matrix.platform != 'linux-x64' }}
run: luamake -platform ${{ matrix.platform }}
- name: Build for musl
- if: ${{ matrix.target == 'linux' && matrix.libc == 'musl' }}
+ if: ${{ matrix.platform == 'linux-x64' && matrix.libc == 'musl' }}
run: ./make.sh
+ - name: Build for x64 glibc
+ if: ${{ matrix.platform == 'linux-x64' && matrix.libc != 'musl' }}
+ run: |
+ docker build -t ubuntu-18.04 .
+ docker run --rm -v $(pwd):$(pwd) -w $(pwd) ubuntu-18.04 bash -c './make.sh'
+
- name: Setting up workflow variables
id: vars
shell: bash
diff --git a/3rd/bee.lua b/3rd/bee.lua
-Subproject de3f4c85856f31b795f91fe69c20f8ca82d5791
+Subproject b4fda4e0865d4369c845015ab2ad45573d6ba24
diff --git a/3rd/love-api b/3rd/love-api
-Subproject 728ba001f3398fd11b0a3909b919a7caf3e329a
+Subproject 853639288547618dece86c3a8e52348fe304eba
diff --git a/3rd/luamake b/3rd/luamake
-Subproject bc906eb2de88a6f169633023a227478da5e9f2a
+Subproject 62d729a6eb72dbb89547538ff9ab1876a5daf0f
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..ec57d0fe
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+# Dockerfile
+
+FROM ubuntu:18.04
+
+# Install necessary packages
+RUN apt-get update && \
+ apt-get install -y software-properties-common && \
+ add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
+ add-apt-repository -y ppa:git-core/ppa && \
+ apt-get install -y git gcc-9 g++-9 wget tar gzip rsync ninja-build
+
+# Use alternative gcc
+RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 && \
+ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100
diff --git a/changelog.md b/changelog.md
index 8f19fd0a..85c073d7 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,18 @@
# changelog
+## 3.9.0
+`2024-5-11`
+* `NEW` goto implementation
+* `NEW` narrow the function prototype based on the parameter type
+ ```lua
+ ---@overload fun(a: boolean): A
+ ---@overload fun(a: number): B
+ local function f(...) end
+
+ local r1 = f(true) --> r1 is `A`
+ local r2 = f(10) --> r2 is `B`
+ ```
+
## 3.8.3
`2024-4-23`
* `FIX` server may crash when the workspace is using a non-English path.
diff --git a/doc/en-us/config.md b/doc/en-us/config.md
index f57d4d64..8dd8c059 100644
--- a/doc/en-us/config.md
+++ b/doc/en-us/config.md
@@ -406,6 +406,22 @@ Array<string>
[]
```
+# diagnostics.globalsRegex
+
+Find defined global variables using regex.
+
+## type
+
+```ts
+Array<string>
+```
+
+## default
+
+```jsonc
+[]
+```
+
# diagnostics.groupFileStatus
Modify the diagnostic needed file status in a group.
diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md
index 7add2c98..7ee6c2ea 100644
--- a/doc/pt-br/config.md
+++ b/doc/pt-br/config.md
@@ -406,6 +406,22 @@ Array<string>
[]
```
+# diagnostics.globalsRegex
+
+Find defined global variables using regex.
+
+## type
+
+```ts
+Array<string>
+```
+
+## default
+
+```jsonc
+[]
+```
+
# diagnostics.groupFileStatus
Modify the diagnostic needed file status in a group.
diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md
index ebb8325f..e7292b92 100644
--- a/doc/zh-cn/config.md
+++ b/doc/zh-cn/config.md
@@ -406,6 +406,22 @@ Array<string>
[]
```
+# diagnostics.globalsRegex
+
+Find defined global variables using regex.
+
+## type
+
+```ts
+Array<string>
+```
+
+## default
+
+```jsonc
+[]
+```
+
# diagnostics.groupFileStatus
批量修改一个组中的文件状态。
diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md
index 8b01d78c..5e491a51 100644
--- a/doc/zh-tw/config.md
+++ b/doc/zh-tw/config.md
@@ -406,6 +406,22 @@ Array<string>
[]
```
+# diagnostics.globalsRegex
+
+Find defined global variables using regex.
+
+## type
+
+```ts
+Array<string>
+```
+
+## default
+
+```jsonc
+[]
+```
+
# diagnostics.groupFileStatus
批量修改一個組中的檔案狀態。
diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua
index 49751d52..6fc488d8 100644
--- a/locale/en-us/script.lua
+++ b/locale/en-us/script.lua
@@ -650,6 +650,8 @@ CLI_CHECK_PROGRESS =
'Found {} problems in {} files'
CLI_CHECK_RESULTS =
'Diagnosis complete, {} problems found, see {}'
+CLI_CHECK_MULTIPLE_WORKERS =
+'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.'
CLI_DOC_INITING =
'Loading documents ...'
CLI_DOC_DONE =
diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua
index 9ef46b86..eae37eae 100644
--- a/locale/en-us/setting.lua
+++ b/locale/en-us/setting.lua
@@ -48,6 +48,8 @@ config.diagnostics.disable =
"Disabled diagnostic (Use code in hover brackets)."
config.diagnostics.globals =
"Defined global variables."
+config.diagnostics.globalsRegex =
+"Find defined global variables using regex."
config.diagnostics.severity =
[[
Modify the diagnostic severity.
@@ -440,3 +442,7 @@ command.addon_manager.open =
'Lua: Open Addon Manager ...'
command.reloadFFIMeta =
'Lua: Reload luajit ffi meta'
+command.startServer =
+'Lua: (debug) Start Language Server'
+command.stopServer =
+'Lua: (debug) Stop Language Server'
diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua
index 7a114fef..468812cc 100644
--- a/locale/pt-br/script.lua
+++ b/locale/pt-br/script.lua
@@ -650,6 +650,8 @@ CLI_CHECK_PROGRESS = -- TODO: need translate!
'Found {} problems in {} files'
CLI_CHECK_RESULTS =
'Diagnóstico completo, {} problemas encontrados, veja {}'
+CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate!
+'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.'
CLI_DOC_INITING = -- TODO: need translate!
'Loading documents ...'
CLI_DOC_DONE = -- TODO: need translate!
diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua
index 6ececcd3..3f7ae657 100644
--- a/locale/pt-br/setting.lua
+++ b/locale/pt-br/setting.lua
@@ -48,6 +48,8 @@ config.diagnostics.disable = -- TODO: need translate!
"Disabled diagnostic (Use code in hover brackets)."
config.diagnostics.globals = -- TODO: need translate!
"Defined global variables."
+config.diagnostics.globalsRegex = -- TODO: need translate!
+"Find defined global variables using regex."
config.diagnostics.severity = -- TODO: need translate!
[[
Modify the diagnostic severity.
@@ -440,3 +442,7 @@ command.addon_manager.open = -- TODO: need translate!
'Lua: Open Addon Manager ...'
command.reloadFFIMeta = -- TODO: need translate!
'Lua: Reload luajit ffi meta'
+command.startServer = -- TODO: need translate!
+'Lua: (debug) Start Language Server'
+command.stopServer = -- TODO: need translate!
+'Lua: (debug) Stop Language Server'
diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua
index c16a764f..a4d20628 100644
--- a/locale/zh-cn/script.lua
+++ b/locale/zh-cn/script.lua
@@ -650,6 +650,8 @@ CLI_CHECK_PROGRESS = -- TODO: need translate!
'Found {} problems in {} files'
CLI_CHECK_RESULTS =
'诊断完成,共有 {} 个问题,请查看 {}'
+CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate!
+'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.'
CLI_DOC_INITING =
'加载文档 ...'
CLI_DOC_DONE =
diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua
index 78e7fb68..9c6e9a25 100644
--- a/locale/zh-cn/setting.lua
+++ b/locale/zh-cn/setting.lua
@@ -48,6 +48,8 @@ config.diagnostics.disable =
"禁用的诊断(使用浮框括号内的代码)。"
config.diagnostics.globals =
"已定义的全局变量。"
+config.diagnostics.globalsRegex = -- TODO: need translate!
+"Find defined global variables using regex."
config.diagnostics.severity =
[[
修改诊断等级。
@@ -439,3 +441,7 @@ command.addon_manager.open =
'Lua: 打开插件管理器...'
command.reloadFFIMeta =
'Lua: 重新生成luajit的FFI模块C语言元数据'
+command.startServer =
+'Lua: (debug) 启动服务器'
+command.stopServer =
+'Lua: (debug) 停止服务器'
diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua
index 1deb9877..c17c41fb 100644
--- a/locale/zh-tw/script.lua
+++ b/locale/zh-tw/script.lua
@@ -650,6 +650,8 @@ CLI_CHECK_PROGRESS = -- TODO: need translate!
'Found {} problems in {} files'
CLI_CHECK_RESULTS =
'診斷完成,共有 {} 個問題,請查看 {}'
+CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate!
+'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.'
CLI_DOC_INITING = -- TODO: need translate!
'Loading documents ...'
CLI_DOC_DONE = -- TODO: need translate!
diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua
index 2b43e954..f15e2b4f 100644
--- a/locale/zh-tw/setting.lua
+++ b/locale/zh-tw/setting.lua
@@ -48,6 +48,8 @@ config.diagnostics.disable =
"停用的診斷(使用浮框括號內的程式碼)。"
config.diagnostics.globals =
"已定義的全域變數。"
+config.diagnostics.globalsRegex = -- TODO: need translate!
+"Find defined global variables using regex."
config.diagnostics.severity =
[[
修改診斷等級。
@@ -439,3 +441,7 @@ command.addon_manager.open = -- TODO: need translate!
'Lua: Open Addon Manager ...'
command.reloadFFIMeta = -- TODO: need translate!
'Lua: Reload luajit ffi meta'
+command.startServer = -- TODO: need translate!
+'Lua: (debug) Start Language Server'
+command.stopServer = -- TODO: need translate!
+'Lua: (debug) Stop Language Server'
diff --git a/main.lua b/main.lua
index 8ecfd472..5dfbab36 100644
--- a/main.lua
+++ b/main.lua
@@ -2,6 +2,8 @@ local fs = require 'bee.filesystem'
local util = require 'utility'
local version = require 'version'
+require 'config.env'
+
local function getValue(value)
if value == 'true' or value == nil then
value = true
diff --git a/script/brave/brave.lua b/script/brave/brave.lua
index 5a15a6b2..34c92a72 100644
--- a/script/brave/brave.lua
+++ b/script/brave/brave.lua
@@ -1,5 +1,6 @@
local channel = require 'bee.channel'
local select = require 'bee.select'
+local thread = require 'bee.thread'
local function channel_init(chan)
local selector = select.create()
@@ -9,11 +10,14 @@ end
local function channel_bpop(ctx)
local selector, chan = ctx[1], ctx[2]
- for _ in selector:wait() do
- local r = table.pack(chan:pop())
- if r[1] == true then
- return table.unpack(r, 2)
+ while true do
+ for _ in selector:wait() do
+ local r = table.pack(chan:pop())
+ if r[1] == true then
+ return table.unpack(r, 2)
+ end
end
+ thread.sleep(10)
end
end
diff --git a/script/cli/check.lua b/script/cli/check.lua
index 3902c4aa..8b314f24 100644
--- a/script/cli/check.lua
+++ b/script/cli/check.lua
@@ -1,130 +1,96 @@
-local lclient = require 'lclient'()
-local furi = require 'file-uri'
-local ws = require 'workspace'
-local files = require 'files'
-local diag = require 'provider.diagnostic'
-local util = require 'utility'
-local jsonb = require 'json-beautify'
-local lang = require 'language'
-local define = require 'proto.define'
-local config = require 'config.config'
-local fs = require 'bee.filesystem'
-local provider = require 'provider'
+local lang = require 'language'
+local platform = require 'bee.platform'
+local subprocess = require 'bee.subprocess'
+local json = require 'json'
+local jsonb = require 'json-beautify'
+local util = require 'utility'
-require 'plugin'
-require 'vm'
-lang(LOCALE)
+local numThreads = tonumber(NUM_THREADS or 1)
-if type(CHECK) ~= 'string' then
- print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK)))
- return
+local exe = arg[-1]
+-- TODO: is this necessary? got it from the shell.lua helper in bee.lua tests
+if platform.os == 'windows' and not exe:match('%.[eE][xX][eE]$') then
+ exe = exe..'.exe'
end
-local rootPath = fs.absolute(fs.path(CHECK)):string()
-local rootUri = furi.encode(rootPath)
-if not rootUri then
- print(lang.script('CLI_CHECK_ERROR_URI', rootPath))
- return
+local function logFileForThread(threadId)
+ return LOGPATH .. '/check-partial-' .. threadId .. '.json'
end
-rootUri = rootUri:gsub("/$", "")
-if CHECKLEVEL then
- if not define.DiagnosticSeverity[CHECKLEVEL] then
- print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint'))
- return
+local function buildArgs(threadId)
+ local args = {exe}
+ local skipNext = false
+ for i = 1, #arg do
+ local arg = arg[i]
+ -- --check needs to be transformed into --check_worker
+ if arg:lower():match('^%-%-check$') or arg:lower():match('^%-%-check=') then
+ args[#args + 1] = arg:gsub('%-%-%w*', '--check_worker')
+ -- --check_out_path needs to be removed if we have more than one thread
+ elseif arg:lower():match('%-%-check_out_path') and numThreads > 1 then
+ if not arg:match('%-%-%w*=') then
+ skipNext = true
+ end
+ else
+ if skipNext then
+ skipNext = false
+ else
+ args[#args + 1] = arg
+ end
+ end
+ end
+ args[#args + 1] = '--thread_id'
+ args[#args + 1] = tostring(threadId)
+ if numThreads > 1 then
+ args[#args + 1] = '--quiet'
+ args[#args + 1] = '--check_out_path'
+ args[#args + 1] = logFileForThread(threadId)
end
+ return args
end
-local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning
-
-util.enableCloseFunction()
-local lastClock = os.clock()
-local results = {}
-
-local function errorhandler(err)
- print(err)
- print(debug.traceback())
+if numThreads > 1 then
+ print(lang.script('CLI_CHECK_MULTIPLE_WORKERS', numThreads))
end
----@async
-xpcall(lclient.start, errorhandler, lclient, function (client)
- client:registerFakers()
-
- client:initialize {
- rootUri = rootUri,
- }
-
- client:register('textDocument/publishDiagnostics', function (params)
- results[params.uri] = params.diagnostics
- end)
-
- io.write(lang.script('CLI_CHECK_INITING'))
-
- provider.updateConfig(rootUri)
-
- ws.awaitReady(rootUri)
-
- local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable'))
- for name, serverity in pairs(define.DiagnosticDefaultSeverity) do
- serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or 'Warning'
- if serverity:sub(-1) == '!' then
- serverity = serverity:sub(1, -2)
- end
- if define.DiagnosticSeverity[serverity] > checkLevel then
- disables[name] = true
- end
+local procs = {}
+for i = 1, numThreads do
+ local process, err = subprocess.spawn({buildArgs(i)})
+ if err then
+ print(err)
end
- config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true))
-
- local uris = files.getChildFiles(rootUri)
- local max = #uris
- for i, uri in ipairs(uris) do
- files.open(uri)
- diag.doDiagnostic(uri, true)
- -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete.
- if os.clock() - lastClock > 0.2 or i == #uris then
- lastClock = os.clock()
- client:update()
- local output = '\x0D'
- .. ('>'):rep(math.ceil(i / max * 20))
- .. ('='):rep(20 - math.ceil(i / max * 20))
- .. ' '
- .. ('0'):rep(#tostring(max) - #tostring(i))
- .. tostring(i) .. '/' .. tostring(max)
- io.write(output)
- local filesWithErrors = 0
- local errors = 0
- for _, diags in pairs(results) do
- filesWithErrors = filesWithErrors + 1
- errors = errors + #diags
- end
- if errors > 0 then
- local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']'
- io.write(errorDetails)
- end
- io.flush()
- end
+ if process then
+ procs[#procs + 1] = process
end
- io.write('\x0D')
-end)
+end
-local count = 0
-for uri, result in pairs(results) do
- count = count + #result
- if #result == 0 then
- results[uri] = nil
- end
+for _, process in ipairs(procs) do
+ process:wait()
end
-if count == 0 then
- print(lang.script('CLI_CHECK_SUCCESS'))
-else
- local outpath = CHECK_OUT_PATH
- if outpath == nil then
- outpath = LOGPATH .. '/check.json'
- end
- util.saveFile(outpath, jsonb.beautify(results))
+local outpath = CHECK_OUT_PATH
+if outpath == nil then
+ outpath = LOGPATH .. '/check.json'
+end
- print(lang.script('CLI_CHECK_RESULTS', count, outpath))
+if numThreads > 1 then
+ local mergedResults = {}
+ local count = 0
+ for i = 1, numThreads do
+ local result = json.decode(util.loadFile(logFileForThread(i)) or '[]')
+ for k, v in pairs(result) do
+ local entries = mergedResults[k] or {}
+ mergedResults[k] = entries
+ for _, entry in ipairs(v) do
+ entries[#entries + 1] = entry
+ count = count + 1
+ end
+ end
+ end
+ util.saveFile(outpath, jsonb.beautify(mergedResults))
+ if count == 0 then
+ print(lang.script('CLI_CHECK_SUCCESS'))
+ else
+ print(lang.script('CLI_CHECK_RESULTS', count, outpath))
+ end
end
diff --git a/script/cli/check_worker.lua b/script/cli/check_worker.lua
new file mode 100644
index 00000000..0a121344
--- /dev/null
+++ b/script/cli/check_worker.lua
@@ -0,0 +1,168 @@
+local lclient = require 'lclient'()
+local furi = require 'file-uri'
+local ws = require 'workspace'
+local files = require 'files'
+local diag = require 'provider.diagnostic'
+local util = require 'utility'
+local jsonb = require 'json-beautify'
+local lang = require 'language'
+local define = require 'proto.define'
+local protoDiag = require 'proto.diagnostic'
+local config = require 'config.config'
+local fs = require 'bee.filesystem'
+local provider = require 'provider'
+local await = require 'await'
+require 'plugin'
+require 'vm'
+
+lang(LOCALE)
+
+local numThreads = tonumber(NUM_THREADS or 1)
+local threadId = tonumber(THREAD_ID or 1)
+
+if type(CHECK_WORKER) ~= 'string' then
+ print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK_WORKER)))
+ return
+end
+
+local rootPath = fs.absolute(fs.path(CHECK_WORKER)):string()
+local rootUri = furi.encode(rootPath)
+if not rootUri then
+ print(lang.script('CLI_CHECK_ERROR_URI', rootPath))
+ return
+end
+rootUri = rootUri:gsub("/$", "")
+
+if CHECKLEVEL then
+ if not define.DiagnosticSeverity[CHECKLEVEL] then
+ print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint'))
+ return
+ end
+end
+local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning
+
+util.enableCloseFunction()
+
+-- Hash function used to distribute work.
+local function hashString(str)
+ local hash = 0
+ for i = 1, #str do
+ hash = (hash * 37 & 0xFFFFFFFF) + str:byte(i, i)
+ end
+ return hash
+end
+
+local lastClock = os.clock()
+local results = {}
+
+local function errorhandler(err)
+ print(err)
+ print(debug.traceback())
+end
+
+---@async
+xpcall(lclient.start, errorhandler, lclient, function (client)
+ await.disable()
+ client:registerFakers()
+
+ client:initialize {
+ rootUri = rootUri,
+ }
+
+ client:register('textDocument/publishDiagnostics', function (params)
+ results[params.uri] = params.diagnostics
+ end)
+
+ if not QUIET then
+ io.write(lang.script('CLI_CHECK_INITING'))
+ end
+
+ provider.updateConfig(rootUri)
+
+ ws.awaitReady(rootUri)
+
+ local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable'))
+ for name, serverity in pairs(define.DiagnosticDefaultSeverity) do
+ serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or 'Warning'
+ if serverity:sub(-1) == '!' then
+ serverity = serverity:sub(1, -2)
+ end
+ if define.DiagnosticSeverity[serverity] > checkLevel then
+ disables[name] = true
+ end
+ end
+ config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true))
+
+ -- Downgrade file opened status to Opened for everything to avoid reporting during compilation on files that do not belong to this thread
+ local diagStatus = config.get(rootUri, 'Lua.diagnostics.neededFileStatus')
+ for diag, status in pairs(diagStatus) do
+ if status == 'Any' or status == 'Any!' then
+ diagStatus[diag] = 'Opened!'
+ end
+ end
+ for diag, status in pairs(protoDiag.getDefaultStatus()) do
+ if status == 'Any' or status == 'Any!' then
+ diagStatus[diag] = 'Opened!'
+ end
+ end
+ config.set(rootUri, 'Lua.diagnostics.neededFileStatus', diagStatus)
+
+ local uris = files.getChildFiles(rootUri)
+ local max = #uris
+ for i, uri in ipairs(uris) do
+ local hash = hashString(uri) % numThreads + 1
+ if hash == threadId then
+ files.open(uri)
+ diag.doDiagnostic(uri, true)
+ -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete.
+ if (os.clock() - lastClock > 0.2 or i == #uris) and not QUIET then
+ lastClock = os.clock()
+ client:update()
+ local output = '\x0D'
+ .. ('>'):rep(math.ceil(i / max * 20))
+ .. ('='):rep(20 - math.ceil(i / max * 20))
+ .. ' '
+ .. ('0'):rep(#tostring(max) - #tostring(i))
+ .. tostring(i) .. '/' .. tostring(max)
+ io.write(output)
+ local filesWithErrors = 0
+ local errors = 0
+ for _, diags in pairs(results) do
+ filesWithErrors = filesWithErrors + 1
+ errors = errors + #diags
+ end
+ if errors > 0 then
+ local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']'
+ io.write(errorDetails)
+ end
+ io.flush()
+ end
+ end
+ end
+ if not QUIET then
+ io.write('\x0D')
+ end
+end)
+
+local count = 0
+for uri, result in pairs(results) do
+ count = count + #result
+ if #result == 0 then
+ results[uri] = nil
+ end
+end
+
+local outpath = CHECK_OUT_PATH
+if outpath == nil then
+ outpath = LOGPATH .. '/check.json'
+end
+-- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run.
+util.saveFile(outpath, jsonb.beautify(results))
+
+if not QUIET then
+ if count == 0 then
+ print(lang.script('CLI_CHECK_SUCCESS'))
+ else
+ print(lang.script('CLI_CHECK_RESULTS', count, outpath))
+ end
+end
diff --git a/script/cli/init.lua b/script/cli/init.lua
index d37c50ae..65e7e102 100644
--- a/script/cli/init.lua
+++ b/script/cli/init.lua
@@ -8,6 +8,11 @@ if _G['CHECK'] then
os.exit(0, true)
end
+if _G['CHECK_WORKER'] then
+ require 'cli.check_worker'
+ os.exit(0, true)
+end
+
if _G['DOC_UPDATE'] then
require 'cli.doc' .runCLI()
os.exit(0, true)
diff --git a/script/config/env.lua b/script/config/env.lua
new file mode 100644
index 00000000..ef5b31f2
--- /dev/null
+++ b/script/config/env.lua
@@ -0,0 +1,67 @@
+-- Handles loading environment arguments
+
+---Convert a string to boolean
+---@param v string
+local function strToBool(v)
+ return v == "true"
+end
+
+---ENV args are defined here.
+---- `name` is the ENV arg name
+---- `key` is the value used to index `_G` for setting the argument
+---- `converter` if present, will be used to convert the string value into another type
+---@type { name: string, key: string, converter: fun(value: string): any }[]
+local vars = {
+ {
+ name = "LLS_CHECK_LEVEL",
+ key = "CHECKLEVEL",
+ },
+ {
+ name = "LLS_CHECK_PATH",
+ key = "CHECK",
+ },
+ {
+ name = "LLS_CONFIG_PATH",
+ key = "CONFIGPATH",
+ },
+ {
+ name = "LLS_DOC_OUT_PATH",
+ key = "DOC_OUT_PATH",
+ },
+ {
+ name = "LLS_DOC_PATH",
+ key = "DOC",
+ },
+ {
+ name = "LLS_FORCE_ACCEPT_WORKSPACE",
+ key = "FORCE_ACCEPT_WORKSPACE",
+ converter = strToBool,
+ },
+ {
+ name = "LLS_LOCALE",
+ key = "LOCALE",
+ },
+ {
+ name = "LLS_LOG_LEVEL",
+ key = "LOGLEVEL",
+ },
+ {
+ name = "LLS_LOG_PATH",
+ key = "LOGPATH",
+ },
+ {
+ name = "LLS_META_PATH",
+ key = "METAPATH",
+ },
+}
+
+for _, var in ipairs(vars) do
+ local value = os.getenv(var.name)
+ if value then
+ if var.converter then
+ value = var.converter(value)
+ end
+
+ _G[var.key] = value
+ end
+end
diff --git a/script/config/template.lua b/script/config/template.lua
index 49907419..e74a9f9c 100644
--- a/script/config/template.lua
+++ b/script/config/template.lua
@@ -242,6 +242,7 @@ local template = {
>> util.deepCopy(define.BuiltIn),
['Lua.diagnostics.enable'] = Type.Boolean >> true,
['Lua.diagnostics.globals'] = Type.Array(Type.String),
+ ['Lua.diagnostics.globalsRegex'] = Type.Array(Type.String),
['Lua.diagnostics.disable'] = Type.Array(Type.String << util.getTableKeys(diag.getDiagAndErrNameMap(), true)),
['Lua.diagnostics.severity'] = Type.Hash(
Type.String << util.getTableKeys(define.DiagnosticDefaultNeededFileStatus, true),
diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua
index 098b41a4..6066ae53 100644
--- a/script/core/diagnostics/duplicate-doc-field.lua
+++ b/script/core/diagnostics/duplicate-doc-field.lua
@@ -15,7 +15,7 @@ local function getFieldEventName(doc)
if docFunc.type ~= 'doc.type.function' then
return nil
end
- for i = 1, 2 do
+ for i = 1, #docFunc.args do
local arg = docFunc.args[i]
if arg
and arg.extends
diff --git a/script/core/diagnostics/global-element.lua b/script/core/diagnostics/global-element.lua
index e9dd46ce..a30ebbc6 100644
--- a/script/core/diagnostics/global-element.lua
+++ b/script/core/diagnostics/global-element.lua
@@ -17,6 +17,20 @@ local function isDocClass(source)
return false
end
+local function isGlobalRegex(name, definedGlobalRegex)
+ if not definedGlobalRegex then
+ return false
+ end
+
+ for _, pattern in ipairs(definedGlobalRegex) do
+ if name:match(pattern) then
+ return true
+ end
+ end
+
+ return false
+end
+
-- If global elements are discouraged by coding convention, this diagnostic helps with reminding about that
-- Exceptions may be added to Lua.diagnostics.globals
return function (uri, callback)
@@ -26,6 +40,7 @@ return function (uri, callback)
end
local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
+ local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex')
guide.eachSourceType(ast.ast, 'setglobal', function (source)
local name = guide.getKeyName(source)
@@ -36,6 +51,9 @@ return function (uri, callback)
if isDocClass(source) then
return
end
+ if isGlobalRegex(name, definedGlobalRegex) then
+ return
+ end
if definedGlobal[name] == nil then
definedGlobal[name] = false
local global = vm.getGlobal('variable', name)
diff --git a/script/core/diagnostics/lowercase-global.lua b/script/core/diagnostics/lowercase-global.lua
index 68bec234..c7e9294d 100644
--- a/script/core/diagnostics/lowercase-global.lua
+++ b/script/core/diagnostics/lowercase-global.lua
@@ -17,6 +17,20 @@ local function isDocClass(source)
return false
end
+local function isGlobalRegex(name, definedGlobalRegex)
+ if not definedGlobalRegex then
+ return false
+ end
+
+ for _, pattern in ipairs(definedGlobalRegex) do
+ if name:match(pattern) then
+ return true
+ end
+ end
+
+ return false
+end
+
-- 不允许定义首字母小写的全局变量(很可能是拼错或者漏删)
return function (uri, callback)
local ast = files.getState(uri)
@@ -25,6 +39,7 @@ return function (uri, callback)
end
local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
+ local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex')
guide.eachSourceType(ast.ast, 'setglobal', function (source)
local name = guide.getKeyName(source)
@@ -42,6 +57,9 @@ return function (uri, callback)
if isDocClass(source) then
return
end
+ if isGlobalRegex(name, definedGlobalRegex) then
+ return
+ end
if definedGlobal[name] == nil then
definedGlobal[name] = false
local global = vm.getGlobal('variable', name)
diff --git a/script/core/implementation.lua b/script/core/implementation.lua
new file mode 100644
index 00000000..e48e2f73
--- /dev/null
+++ b/script/core/implementation.lua
@@ -0,0 +1,171 @@
+local workspace = require 'workspace'
+local files = require 'files'
+local vm = require 'vm'
+local findSource = require 'core.find-source'
+local guide = require 'parser.guide'
+local rpath = require 'workspace.require-path'
+local jumpSource = require 'core.jump-source'
+
+local function sortResults(results)
+ -- 先按照顺序排序
+ table.sort(results, function (a, b)
+ local u1 = guide.getUri(a.target)
+ local u2 = guide.getUri(b.target)
+ if u1 == u2 then
+ return a.target.start < b.target.start
+ else
+ return u1 < u2
+ end
+ end)
+ -- 如果2个结果处于嵌套状态,则取范围小的那个
+ local lf, lu
+ for i = #results, 1, -1 do
+ local res = results[i].target
+ local f = res.finish
+ local uri = guide.getUri(res)
+ if lf and f > lf and uri == lu then
+ table.remove(results, i)
+ else
+ lu = uri
+ lf = f
+ end
+ end
+end
+
+local accept = {
+ ['local'] = true,
+ ['setlocal'] = true,
+ ['getlocal'] = true,
+ ['label'] = true,
+ ['goto'] = true,
+ ['field'] = true,
+ ['method'] = true,
+ ['setglobal'] = true,
+ ['getglobal'] = true,
+ ['string'] = true,
+ ['boolean'] = true,
+ ['number'] = true,
+ ['integer'] = true,
+ ['...'] = true,
+
+ ['doc.type.name'] = true,
+ ['doc.class.name'] = true,
+ ['doc.extends.name'] = true,
+ ['doc.alias.name'] = true,
+ ['doc.cast.name'] = true,
+ ['doc.enum.name'] = true,
+ ['doc.field.name'] = true,
+}
+
+local function convertIndex(source)
+ if not source then
+ return
+ end
+ if source.type == 'string'
+ or source.type == 'boolean'
+ or source.type == 'number'
+ or source.type == 'integer' then
+ local parent = source.parent
+ if not parent then
+ return
+ end
+ if parent.type == 'setindex'
+ or parent.type == 'getindex'
+ or parent.type == 'tableindex' then
+ return parent
+ end
+ end
+ return source
+end
+
+---@async
+return function (uri, offset)
+ local ast = files.getState(uri)
+ if not ast then
+ return nil
+ end
+
+ local source = convertIndex(findSource(ast, offset, accept))
+ if not source then
+ return nil
+ end
+
+ local results = {}
+
+ local defs = vm.getRefs(source)
+
+ for _, src in ipairs(defs) do
+ if not guide.isAssign(src) then
+ goto CONTINUE
+ end
+ if src.type == 'global' then
+ goto CONTINUE
+ end
+ local root = guide.getRoot(src)
+ if not root then
+ goto CONTINUE
+ end
+ if src.type == 'self' then
+ goto CONTINUE
+ end
+ src = src.field or src.method or src
+ if src.type == 'getindex'
+ or src.type == 'setindex'
+ or src.type == 'tableindex' then
+ src = src.index
+ if not src then
+ goto CONTINUE
+ end
+ if not guide.isLiteral(src) then
+ goto CONTINUE
+ end
+ end
+ if src.type == 'doc.type.function'
+ or src.type == 'doc.type.table'
+ or src.type == 'doc.type.boolean'
+ or src.type == 'doc.type.integer'
+ or src.type == 'doc.type.string' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.class' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.alias' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.enum' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.type.field' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.class.name'
+ or src.type == 'doc.alias.name'
+ or src.type == 'doc.enum.name'
+ or src.type == 'doc.field.name' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.generic.name' then
+ goto CONTINUE
+ end
+ if src.type == 'doc.param' then
+ goto CONTINUE
+ end
+
+ results[#results+1] = {
+ target = src,
+ uri = root.uri,
+ source = source,
+ }
+ ::CONTINUE::
+ end
+
+ if #results == 0 then
+ return nil
+ end
+
+ sortResults(results)
+ jumpSource(results)
+
+ return results
+end
diff --git a/script/core/signature.lua b/script/core/signature.lua
index 98018b21..c52dcff3 100644
--- a/script/core/signature.lua
+++ b/script/core/signature.lua
@@ -94,10 +94,7 @@ local function isEventNotMatch(call, src)
return false
end
local literal, index
- for i = 1, 2 do
- if not call.args[i] then
- break
- end
+ for i = 1, #call.args do
literal = guide.getLiteral(call.args[i])
if literal then
index = i
diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua
index 0a821f25..d9939eb0 100644
--- a/script/core/type-definition.lua
+++ b/script/core/type-definition.lua
@@ -52,8 +52,9 @@ local accept = {
['doc.class.name'] = true,
['doc.extends.name'] = true,
['doc.alias.name'] = true,
+ ['doc.cast.name'] = true,
['doc.enum.name'] = true,
- ['doc.see.name'] = true,
+ ['doc.field.name'] = true,
}
local function checkRequire(source, offset)
diff --git a/script/files.lua b/script/files.lua
index b9df5695..68c3b8a5 100644
--- a/script/files.lua
+++ b/script/files.lua
@@ -1,5 +1,6 @@
local platform = require 'bee.platform'
local fs = require 'bee.filesystem'
+local sys = require 'bee.sys'
local config = require 'config'
local glob = require 'glob'
local furi = require 'file-uri'
@@ -70,7 +71,7 @@ local function getRealParent(path)
== path :string():gsub('^%w+:', string.lower) then
return path
end
- local res = fs.fullpath(path)
+ local res = sys.fullpath(path)
return getRealParent(parent) / res:filename()
end
diff --git a/script/filewatch.lua b/script/filewatch.lua
index 6520afe6..7fb3e605 100644
--- a/script/filewatch.lua
+++ b/script/filewatch.lua
@@ -1,6 +1,7 @@
local fw = require 'bee.filewatch'
local fs = require 'bee.filesystem'
local plat = require 'bee.platform'
+local sys = require 'bee.sys'
local await = require 'await'
local files = require 'files'
@@ -16,7 +17,7 @@ local function isExists(filename)
if plat.os ~= 'windows' then
return true
end
- local res = fs.fullpath(path)
+ local res = sys.fullpath(path)
if not res then
return false
end
diff --git a/script/global.d.lua b/script/global.d.lua
index ead46ca9..daac5f6c 100644
--- a/script/global.d.lua
+++ b/script/global.d.lua
@@ -98,3 +98,11 @@ FORCE_ACCEPT_WORKSPACE = false
-- Trust all plugins that are being loaded by workspace config files.
-- This is potentially unsafe for normal use and meant for usage in CI environments only.
TRUST_ALL_PLUGINS = false
+
+NUM_THREADS = 1
+
+THREAD_ID = 1
+
+CHECK_WORKER = ''
+
+QUIET = false
diff --git a/script/meta/bee/filesystem.lua b/script/meta/bee/filesystem.lua
index 0c7e41a8..2a353065 100644
--- a/script/meta/bee/filesystem.lua
+++ b/script/meta/bee/filesystem.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta bee.filesystem
---@class fs.path
---@operator div: fs.path
@@ -75,11 +75,6 @@ end
---@param path fs.path
---@return fs.path
-function fs.fullpath(path)
-end
-
----@param path fs.path
----@return fs.path
function fs.absolute(path)
end
diff --git a/script/meta/bee/filewatch.lua b/script/meta/bee/filewatch.lua
index b5211355..813548b2 100644
--- a/script/meta/bee/filewatch.lua
+++ b/script/meta/bee/filewatch.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta bee.filewatch
---@class bee.filewatch.instance
local instance = {}
diff --git a/script/meta/bee/socket.lua b/script/meta/bee/socket.lua
index 55c349a6..b5e3a7b2 100644
--- a/script/meta/bee/socket.lua
+++ b/script/meta/bee/socket.lua
@@ -1,4 +1,4 @@
----@meta
+---@meta bee.socket
---@alias bee.socket.protocol
---| 'tcp'
diff --git a/script/meta/bee/sys.lua b/script/meta/bee/sys.lua
new file mode 100644
index 00000000..ad14702c
--- /dev/null
+++ b/script/meta/bee/sys.lua
@@ -0,0 +1,13 @@
+---@meta bee.sys
+
+---@class bee.sys
+local sys = {}
+
+---@param path fs.path
+---@return fs.path
+function sys.fullpath(path) end
+
+---@return fs.path
+function sys.exe_path() end
+
+return sys
diff --git a/script/meta/bee/thread.lua b/script/meta/bee/thread.lua
index 6b4323a4..15955aff 100644
--- a/script/meta/bee/thread.lua
+++ b/script/meta/bee/thread.lua
@@ -1,9 +1,9 @@
----@meta
+---@meta bee.thread
---@class bee.thread
local thread = {}
----@param time number
+---@param time integer
function thread.sleep(time) end
---@param name string
@@ -15,7 +15,10 @@ function thread.channel(name) end
---@param script string
---@return bee.thread.thread
-function thread.thread(script) end
+function thread.create(script) end
+
+---@return string?
+function thread.errlog() end
---@class bee.thread.channel
local channel = {}
diff --git a/script/provider/provider.lua b/script/provider/provider.lua
index 69fb3263..15e78b9a 100644
--- a/script/provider/provider.lua
+++ b/script/provider/provider.lua
@@ -368,6 +368,39 @@ m.register 'textDocument/hover' {
end
}
+local function convertDefinitionResult(state, result)
+ local response = {}
+ for i, info in ipairs(result) do
+ ---@type uri
+ local targetUri = info.uri
+ if targetUri then
+ local targetState = files.getState(targetUri)
+ if targetState then
+ if client.getAbility 'textDocument.definition.linkSupport' then
+ response[i] = converter.locationLink(targetUri
+ , converter.packRange(targetState, info.target.start, info.target.finish)
+ , converter.packRange(targetState, info.target.start, info.target.finish)
+ , converter.packRange(state, info.source.start, info.source.finish)
+ )
+ else
+ response[i] = converter.location(targetUri
+ , converter.packRange(targetState, info.target.start, info.target.finish)
+ )
+ end
+ else
+ response[i] = converter.location(
+ targetUri,
+ converter.range(
+ converter.position(guide.rowColOf(info.target.start)),
+ converter.position(guide.rowColOf(info.target.finish))
+ )
+ )
+ end
+ end
+ end
+ return response
+end
+
m.register 'textDocument/definition' {
capability = {
definitionProvider = true,
@@ -388,35 +421,7 @@ m.register 'textDocument/definition' {
if not result then
return nil
end
- local response = {}
- for i, info in ipairs(result) do
- ---@type uri
- local targetUri = info.uri
- if targetUri then
- local targetState = files.getState(targetUri)
- if targetState then
- if client.getAbility 'textDocument.definition.linkSupport' then
- response[i] = converter.locationLink(targetUri
- , converter.packRange(targetState, info.target.start, info.target.finish)
- , converter.packRange(targetState, info.target.start, info.target.finish)
- , converter.packRange(state, info.source.start, info.source.finish)
- )
- else
- response[i] = converter.location(targetUri
- , converter.packRange(targetState, info.target.start, info.target.finish)
- )
- end
- else
- response[i] = converter.location(
- targetUri,
- converter.range(
- converter.position(guide.rowColOf(info.target.start)),
- converter.position(guide.rowColOf(info.target.finish))
- )
- )
- end
- end
- end
+ local response = convertDefinitionResult(state, result)
return response
end
}
@@ -441,27 +446,32 @@ m.register 'textDocument/typeDefinition' {
if not result then
return nil
end
- local response = {}
- for i, info in ipairs(result) do
- ---@type uri
- local targetUri = info.uri
- if targetUri then
- local targetState = files.getState(targetUri)
- if targetState then
- if client.getAbility 'textDocument.typeDefinition.linkSupport' then
- response[i] = converter.locationLink(targetUri
- , converter.packRange(targetState, info.target.start, info.target.finish)
- , converter.packRange(targetState, info.target.start, info.target.finish)
- , converter.packRange(state, info.source.start, info.source.finish)
- )
- else
- response[i] = converter.location(targetUri
- , converter.packRange(targetState, info.target.start, info.target.finish)
- )
- end
- end
- end
+ local response = convertDefinitionResult(state, result)
+ return response
+ end
+}
+
+m.register 'textDocument/implementation' {
+ capability = {
+ implementationProvider = true,
+ },
+ abortByFileUpdate = true,
+ ---@async
+ function (params)
+ local uri = files.getRealUri(params.textDocument.uri)
+ workspace.awaitReady(uri)
+ local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5)
+ local state = files.getState(uri)
+ if not state then
+ return
+ end
+ local core = require 'core.implementation'
+ local pos = converter.unpackPosition(state, params.position)
+ local result = core(uri, pos)
+ if not result then
+ return nil
end
+ local response = convertDefinitionResult(state, result)
return response
end
}
diff --git a/script/pub/pub.lua b/script/pub/pub.lua
index e8051d05..517c3fc7 100644
--- a/script/pub/pub.lua
+++ b/script/pub/pub.lua
@@ -12,11 +12,14 @@ end
local function channel_bpop(ctx)
local selector, chan = ctx[1], ctx[2]
- for _ in selector:wait() do
- local r = table.pack(chan:pop())
- if r[1] == true then
- return table.unpack(r, 2)
+ while true do
+ for _ in selector:wait() do
+ local r = table.pack(chan:pop())
+ if r[1] == true then
+ return table.unpack(r, 2)
+ end
end
+ thread.sleep(10)
end
end
diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua
index 11ba07ab..e1b1b43b 100644
--- a/script/vm/compiler.lua
+++ b/script/vm/compiler.lua
@@ -550,11 +550,14 @@ local function matchCall(source)
or call.node ~= source then
return
end
- local funcs = vm.getMatchedFunctions(source, call.args)
local myNode = vm.getNode(source)
if not myNode then
return
end
+ local funcs = vm.getExactMatchedFunctions(source, call.args)
+ if not funcs then
+ return
+ end
local needRemove
for n in myNode:eachObject() do
if n.type == 'function'
@@ -870,7 +873,7 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex)
---@type integer?, table<any, boolean>?
local eventIndex, eventMap
if call.args then
- for i = 1, 2 do
+ for i = 1, 10 do
local eventArg = call.args[i + fixIndex]
if not eventArg then
break
diff --git a/script/vm/function.lua b/script/vm/function.lua
index c6df6349..1e308317 100644
--- a/script/vm/function.lua
+++ b/script/vm/function.lua
@@ -1,6 +1,7 @@
---@class vm
local vm = require 'vm.vm'
local guide = require 'parser.guide'
+local util = require 'utility'
---@param arg parser.object
---@return parser.object?
@@ -267,6 +268,9 @@ end
---@return integer def
function vm.countReturnsOfCall(func, args, mark)
local funcs = vm.getMatchedFunctions(func, args, mark)
+ if not funcs then
+ return 0, math.huge, 0
+ end
---@type integer?, number?, integer?
local min, max, def
for _, f in ipairs(funcs) do
@@ -329,22 +333,71 @@ function vm.countList(list, mark)
return min, max, def
end
+---@param uri uri
+---@param args parser.object[]
+---@return boolean
+local function isAllParamMatched(uri, args, params)
+ if not params then
+ return false
+ end
+ for i = 1, #args do
+ if not params[i] then
+ break
+ end
+ local argNode = vm.compileNode(args[i])
+ local defNode = vm.compileNode(params[i])
+ if not vm.canCastType(uri, defNode, argNode) then
+ return false
+ end
+ end
+ return true
+end
+
---@param func parser.object
----@param args parser.object[]?
+---@param args? parser.object[]
+---@return parser.object[]?
+function vm.getExactMatchedFunctions(func, args)
+ local funcs = vm.getMatchedFunctions(func, args)
+ if not args or not funcs then
+ return funcs
+ end
+ if #funcs == 1 then
+ return funcs
+ end
+ local uri = guide.getUri(func)
+ local needRemove
+ for i, n in ipairs(funcs) do
+ if vm.isVarargFunctionWithOverloads(n)
+ or not isAllParamMatched(uri, args, n.args) then
+ if not needRemove then
+ needRemove = {}
+ end
+ needRemove[#needRemove+1] = i
+ end
+ end
+ if not needRemove then
+ return funcs
+ end
+ if #needRemove == #funcs then
+ return nil
+ end
+ util.tableMultiRemove(funcs, needRemove)
+ return funcs
+end
+
+---@param func parser.object
+---@param args? parser.object[]
---@param mark? table
----@return parser.object[]
+---@return parser.object[]?
function vm.getMatchedFunctions(func, args, mark)
local funcs = {}
local node = vm.compileNode(func)
for n in node:eachObject() do
- if (n.type == 'function' and not vm.isVarargFunctionWithOverloads(n))
+ if n.type == 'function'
or n.type == 'doc.type.function' then
funcs[#funcs+1] = n
end
end
- if #funcs <= 1 then
- return funcs
- end
local amin, amax = vm.countList(args, mark)
@@ -357,7 +410,7 @@ function vm.getMatchedFunctions(func, args, mark)
end
if #matched == 0 then
- return funcs
+ return nil
else
return matched
end
@@ -372,23 +425,31 @@ function vm.isVarargFunctionWithOverloads(func)
if not func.args then
return false
end
+ if func._varargFunction ~= nil then
+ return func._varargFunction
+ end
if func.args[1] and func.args[1].type == 'self' then
if not func.args[2] or func.args[2].type ~= '...' then
+ func._varargFunction = false
return false
end
else
if not func.args[1] or func.args[1].type ~= '...' then
+ func._varargFunction = false
return false
end
end
if not func.bindDocs then
+ func._varargFunction = false
return false
end
for _, doc in ipairs(func.bindDocs) do
if doc.type == 'doc.overload' then
+ func._varargFunction = true
return true
end
end
+ func._varargFunction = false
return false
end
diff --git a/script/vm/global.lua b/script/vm/global.lua
index e830f6d8..aa987cf4 100644
--- a/script/vm/global.lua
+++ b/script/vm/global.lua
@@ -539,21 +539,60 @@ function vm.hasGlobalSets(suri, cate, name)
return true
end
+---@param uri uri
+---@param key string
+---@return boolean
+local function checkIsGlobalRegex(uri, key)
+ local dglobalsregex = config.get(uri, 'Lua.diagnostics.globalsRegex')
+ if not dglobalsregex then
+ return false
+ end
+
+ for _, pattern in ipairs(dglobalsregex) do
+ if key:match(pattern) then
+ return true
+ end
+ end
+
+ return false
+end
+
---@param src parser.object
local function checkIsUndefinedGlobal(src)
+ if src.type ~= 'getglobal' then
+ return false
+ end
+
local key = src[1]
+ if not key then
+ return false
+ end
+
+ local node = src.node
+ if node.tag ~= '_ENV' then
+ return false
+ end
local uri = guide.getUri(src)
- local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals'))
local rspecial = config.get(uri, 'Lua.runtime.special')
+ if rspecial[key] then
+ return false
+ end
- local node = src.node
- return src.type == 'getglobal' and key and not (
- dglobals[key] or
- rspecial[key] or
- node.tag ~= '_ENV' or
- vm.hasGlobalSets(uri, 'variable', key)
- )
+ if vm.hasGlobalSets(uri, 'variable', key) then
+ return false
+ end
+
+ local dglobals = config.get(uri, 'Lua.diagnostics.globals')
+ if util.arrayHas(dglobals, key) then
+ return false
+ end
+
+ if checkIsGlobalRegex(uri, key) then
+ return false
+ end
+
+ return true
end
---@param src parser.object
diff --git a/script/vm/infer.lua b/script/vm/infer.lua
index 3f3d0e3a..bb06ee3a 100644
--- a/script/vm/infer.lua
+++ b/script/vm/infer.lua
@@ -242,9 +242,6 @@ local viewNodeSwitch;viewNodeSwitch = util.switch()
return vm.viewKey(source, uri)
end)
----@class vm.node
----@field lastInfer? vm.infer
-
---@param node? vm.node
---@return vm.infer
local function createInfer(node)
diff --git a/script/vm/node.lua b/script/vm/node.lua
index bc1dfcb1..fae79cbc 100644
--- a/script/vm/node.lua
+++ b/script/vm/node.lua
@@ -16,6 +16,7 @@ vm.nodeCache = setmetatable({}, util.MODE_K)
---@field [vm.node.object] true
---@field fields? table<vm.node|string, vm.node>
---@field undefinedGlobal boolean?
+---@field lastInfer? vm.infer
local mt = {}
mt.__index = mt
mt.id = 0
@@ -31,6 +32,7 @@ function mt:merge(node)
if not node then
return self
end
+ self.lastInfer = nil
if node.type == 'vm.node' then
if node == self then
return self
diff --git a/test.lua b/test.lua
index 5fd0fd85..aad95270 100644
--- a/test.lua
+++ b/test.lua
@@ -2,7 +2,8 @@ package.path = package.path
.. ';./test/?.lua'
.. ';./test/?/init.lua'
local fs = require 'bee.filesystem'
-local rootPath = fs.exe_path():parent_path():parent_path():string()
+local sys = require 'bee.sys'
+local rootPath = sys.exe_path():parent_path():parent_path():string()
ROOT = fs.path(rootPath)
TEST = true
DEVELOP = true
@@ -54,6 +55,7 @@ local function testAll()
test 'basic'
test 'definition'
test 'type_inference'
+ test 'implementation'
test 'references'
test 'hover'
test 'completion'
diff --git a/test/implementation/init.lua b/test/implementation/init.lua
new file mode 100644
index 00000000..678cb23b
--- /dev/null
+++ b/test/implementation/init.lua
@@ -0,0 +1,81 @@
+local core = require 'core.implementation'
+local files = require 'files'
+local vm = require 'vm'
+local catch = require 'catch'
+
+rawset(_G, 'TEST', true)
+
+local function founded(targets, results)
+ if #targets ~= #results then
+ return false
+ end
+ for _, target in ipairs(targets) do
+ for _, result in ipairs(results) do
+ if target[1] == result[1] and target[2] == result[2] then
+ goto NEXT
+ end
+ end
+ do return false end
+ ::NEXT::
+ end
+ return true
+end
+
+---@async
+function TEST(script)
+ local newScript, catched = catch(script, '!?')
+
+ files.setText(TESTURI, newScript)
+
+ local results = core(TESTURI, catched['?'][1][1])
+ if results then
+ local positions = {}
+ for i, result in ipairs(results) do
+ if not vm.isMetaFile(result.uri) then
+ positions[#positions+1] = { result.target.start, result.target.finish }
+ end
+ end
+ assert(founded(catched['!'], positions))
+ else assert(#catched['!'] == 0)
+ end
+
+ files.remove(TESTURI)
+end
+
+TEST [[
+---@class A
+---@field x number
+local M
+
+M.<!x!> = 1
+
+
+print(M.<?x?>)
+]]
+
+TEST [[
+---@class A
+---@field f fun()
+local M
+
+function M.<!f!>() end
+
+
+print(M.<?f?>)
+]]
+
+TEST [[
+---@class A
+local M
+
+function M:<!event!>(name) end
+
+---@class A
+---@field event fun(self, name: 'ev1')
+---@field event fun(self, name: 'ev2')
+
+---@type A
+local m
+
+m:<?event?>('ev1')
+]]
diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua
new file mode 100644
index 00000000..5922832b
--- /dev/null
+++ b/test/type_inference/common.lua
@@ -0,0 +1,4194 @@
+local config = require 'config'
+
+TEST 'nil' [[
+local <?t?> = nil
+]]
+
+TEST 'string' [[
+local <?var?> = '111'
+]]
+
+TEST 'boolean' [[
+local <?var?> = true
+]]
+
+TEST 'integer' [[
+local <?var?> = 1
+]]
+
+TEST 'number' [[
+local <?var?> = 1.0
+]]
+
+TEST 'unknown' [[
+local <?var?>
+]]
+
+TEST 'unknown' [[
+local <?var?>
+var = y
+]]
+
+TEST 'any' [[
+function f(<?x?>)
+
+end
+]]
+
+TEST 'any' [[
+function f(<?x?>)
+ x = 1
+end
+]]
+
+TEST 'string' [[
+local var = '111'
+t.<?x?> = var
+]]
+
+TEST 'string' [[
+local <?var?>
+var = '111'
+]]
+
+TEST 'string' [[
+local var
+<?var?> = '111'
+]]
+
+TEST 'string' [[
+local var
+var = '111'
+print(<?var?>)
+]]
+
+TEST 'function' [[
+function <?xx?>()
+end
+]]
+
+TEST 'function' [[
+local function <?xx?>()
+end
+]]
+
+TEST 'function' [[
+local xx
+<?xx?> = function ()
+end
+]]
+
+TEST 'table' [[
+local <?t?> = {}
+]]
+
+TEST 'unknown' [[
+<?x?>()
+]]
+
+TEST 'boolean' [[
+<?x?> = not y
+]]
+
+TEST 'integer' [[
+<?x?> = #y
+]]
+
+TEST 'integer' [[
+<?x?> = #'aaaa'
+]]
+
+TEST 'integer' [[
+<?x?> = #{}
+]]
+
+TEST 'number' [[
+<?x?> = - y
+]]
+
+TEST 'number' [[
+<?x?> = - 1.0
+]]
+
+TEST 'integer' [[
+<?x?> = ~ y
+]]
+
+TEST 'integer' [[
+<?x?> = ~ 1
+]]
+
+TEST 'boolean' [[
+<?x?> = 1 < 2
+]]
+
+TEST 'integer' [[
+local a = true
+local b = 1
+<?x?> = a and b
+]]
+
+TEST 'integer' [[
+local a = false
+local b = 1
+<?x?> = a or b
+]]
+
+TEST 'boolean' [[
+<?x?> = a == b
+]]
+
+TEST 'unknown' [[
+<?x?> = a << b
+]]
+
+TEST 'integer' [[
+<?x?> = 1 << 2
+]]
+
+TEST 'unknown' [[
+<?x?> = a .. b
+]]
+
+TEST 'string' [[
+<?x?> = 'a' .. 'b'
+]]
+
+TEST 'string' [[
+<?x?> = 'a' .. 1
+]]
+
+TEST 'string' [[
+<?x?> = 'a' .. 1.0
+]]
+
+TEST 'unknown' [[
+<?x?> = a + b
+]]
+
+TEST 'number' [[
+<?x?> = 1 + 2.0
+]]
+
+TEST 'integer' [[
+<?x?> = 1 + 2
+]]
+
+TEST 'integer' [[
+---@type integer
+local a
+
+<?x?> = - a
+]]
+
+TEST 'number' [[
+local a
+
+<?x?> = - a
+]]
+
+TEST 'unknown' [[
+<?x?> = 1 + X
+]]
+
+TEST 'unknown' [[
+<?x?> = 1.0 + X
+]]
+
+TEST 'tablelib' [[
+---@class tablelib
+table = {}
+
+<?table?>()
+]]
+
+TEST 'string' [[
+_VERSION = 'Lua 5.4'
+
+<?x?> = _VERSION
+]]
+
+TEST 'function' [[
+---@class stringlib
+local string
+
+string.xxx = function () end
+
+return ('x').<?xxx?>
+]]
+
+TEST 'function' [[
+---@class stringlib
+String = {}
+
+String.xxx = function () end
+
+return ('x').<?xxx?>
+]]
+
+TEST 'function' [[
+---@class stringlib
+local string
+
+string.xxx = function () end
+
+<?x?> = ('x').xxx
+]]
+
+TEST 'function' [[
+---@class stringlib
+local string
+
+string.xxx = function () end
+
+_VERSION = 'Lua 5.4'
+
+<?x?> = _VERSION.xxx
+]]
+
+TEST 'table' [[
+<?x?> = setmetatable({})
+]]
+
+TEST 'integer' [[
+local function x()
+ return 1
+end
+<?y?> = x()
+]]
+
+TEST 'integer|nil' [[
+local function x()
+ return 1
+ return nil
+end
+<?y?> = x()
+]]
+
+TEST 'unknown|nil' [[
+local function x()
+ return a
+ return nil
+end
+<?y?> = x()
+]]
+
+TEST 'unknown|nil' [[
+local function x()
+ return nil
+ return f()
+end
+<?y?> = x()
+]]
+
+TEST 'unknown|nil' [[
+local function x()
+ return nil
+ return f()
+end
+_, <?y?> = x()
+]]
+
+TEST 'integer' [[
+local function x()
+ return 1
+end
+_, <?y?> = pcall(x)
+]]
+
+TEST 'integer' [[
+function x()
+ return 1
+end
+_, <?y?> = pcall(x)
+]]
+
+TEST 'integer' [[
+local function x()
+ return 1
+end
+_, <?y?> = xpcall(x)
+]]
+
+TEST 'A' [[
+---@class A
+
+---@return A
+local function f2() end
+
+local function f()
+ return f2()
+end
+
+local <?x?> = f()
+]]
+
+-- 不根据调用者的输入参数来推测
+--TEST 'number' [[
+--local function x(a)
+-- return <?a?>
+--end
+--x(1)
+--]]
+
+--TEST 'table' [[
+--setmetatable(<?b?>)
+--]]
+
+-- 不根据对方函数内的使用情况来推测
+TEST 'unknown' [[
+local function x(a)
+ _ = a + 1
+end
+local b
+x(<?b?>)
+]]
+
+TEST 'unknown' [[
+local function x(a, ...)
+ local _, <?b?>, _ = ...
+end
+x(nil, 'xx', 1, true)
+]]
+
+-- 引用不跨越参数
+TEST 'unknown' [[
+local function x(a, ...)
+ return true, 'ss', ...
+end
+local _, _, _, <?b?>, _ = x(nil, true, 1, 'yy')
+]]
+
+TEST 'unknown' [[
+local <?x?> = next()
+]]
+
+TEST 'unknown' [[
+local a, b
+function a()
+ return b()
+end
+function b()
+ return a()
+end
+local <?x?> = a()
+]]
+
+TEST 'class' [[
+---@class class
+local <?x?>
+]]
+
+TEST 'string' [[
+---@class string
+
+---@type string
+local <?x?>
+]]
+
+TEST '1' [[
+---@type 1
+local <?v?>
+]]
+
+TEST 'string[]' [[
+---@class string
+
+---@type string[]
+local <?x?>
+]]
+
+TEST 'string|table' [[
+---@class string
+---@class table
+
+---@type string | table
+local <?x?>
+]]
+
+TEST [['enum1'|'enum2']] [[
+---@type 'enum1' | 'enum2'
+local <?x?>
+]]
+
+TEST [["enum1"|"enum2"]] [[
+---@type "enum1" | "enum2"
+local <?x?>
+]]
+
+config.set(nil, 'Lua.hover.expandAlias', false)
+TEST 'A' [[
+---@alias A 'enum1' | 'enum2'
+
+---@type A
+local <?x?>
+]]
+
+TEST 'A' [[
+---@alias A 'enum1' | 'enum2' | A
+
+---@type A
+local <?x?>
+]]
+
+TEST 'A' [[
+---@alias A 'enum1' | 'enum2' | B
+
+---@type A
+local <?x?>
+]]
+config.set(nil, 'Lua.hover.expandAlias', true)
+TEST [['enum1'|'enum2']] [[
+---@alias A 'enum1' | 'enum2'
+
+---@type A
+local <?x?>
+]]
+
+TEST [['enum1'|'enum2']] [[
+---@alias A 'enum1' | 'enum2' | A
+
+---@type A
+local <?x?>
+]]
+
+TEST [['enum1'|'enum2'|B]] [[
+---@alias A 'enum1' | 'enum2' | B
+
+---@type A
+local <?x?>
+]]
+
+TEST '1|true' [[
+---@alias A 1 | true
+
+---@type A
+local <?x?>
+]]
+
+TEST 'fun()' [[
+---@type fun()
+local <?x?>
+]]
+
+TEST 'fun(a: string, b: any, ...any)' [[
+---@type fun(a: string, b, ...)
+local <?x?>
+]]
+
+TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[
+---@type fun(a: string, b, c?: boolean, ...):c, d?, ...
+local <?x?>
+]]
+
+TEST '{ [string]: string }' [[
+---@type { [string]: string }
+local <?x?>
+]]
+
+TEST 'table<string, number>' [[
+---@class string
+---@class number
+
+---@type table<string, number>
+local <?x?>
+]]
+
+TEST 'A<string, number>' [[
+---@class A
+
+---@type A<string, number>
+local <?x?>
+]]
+
+TEST 'string' [[
+---@class string
+
+---@type string[]
+local x
+local <?y?> = x[1]
+]]
+
+TEST 'string' [[
+---@class string
+
+---@return string[]
+local function f() end
+local x = f()
+local <?y?> = x[1]
+]]
+
+TEST 'table' [[
+local t = {}
+local <?v?> = setmetatable(t)
+]]
+
+TEST 'CCC' [[
+---@class CCC
+
+---@type table<string, CCC>
+local t = {}
+
+print(t.<?a?>)
+]]
+
+TEST [['aaa'|'bbb']] [[
+---@type table<string, 'aaa'|'bbb'>
+local t = {}
+
+print(t.<?a?>)
+]]
+
+TEST 'integer' [[
+---@generic K
+---@type fun(a?: K):K
+local f
+
+local <?n?> = f(1)
+]]
+
+TEST 'unknown' [[
+---@generic K
+---@type fun(a?: K):K
+local f
+
+local <?n?> = f(nil)
+]]
+
+TEST 'unknown' [[
+---@generic K
+---@type fun(a: K|integer):K
+local f
+
+local <?n?> = f(1)
+]]
+
+TEST 'integer' [[
+---@class integer
+
+---@generic T: table, V
+---@param t T
+---@return fun(table: V[], i?: integer):integer, V
+---@return T
+---@return integer i
+local function ipairs() end
+
+for <?i?> in ipairs() do
+end
+]]
+
+TEST 'table<string, boolean>' [[
+---@generic K, V
+---@param t table<K, V>
+---@return K
+---@return V
+local function next(t) end
+
+---@type table<string, boolean>
+local t
+local k, v = next(<?t?>)
+]]
+
+TEST 'string' [[
+---@class string
+
+---@generic K, V
+---@param t table<K, V>
+---@return K
+---@return V
+local function next(t) end
+
+---@type table<string, boolean>
+local t
+local <?k?>, v = next(t)
+]]
+
+TEST 'boolean' [[
+---@class boolean
+
+---@generic K, V
+---@param t table<K, V>
+---@return K
+---@return V
+local function next(t) end
+
+---@type table<string, boolean>
+local t
+local k, <?v?> = next(t)
+]]
+
+TEST 'boolean' [[
+---@generic K
+---@type fun(arg: K):K
+local f
+
+local <?r?> = f(true)
+]]
+
+TEST 'string' [[
+---@class string
+
+---@generic K, V
+---@type fun(arg: table<K, V>):K, V
+local f
+
+---@type table<string, boolean>
+local t
+
+local <?k?>, v = f(t)
+]]
+
+TEST 'boolean' [[
+---@class boolean
+
+---@generic K, V
+---@type fun(arg: table<K, V>):K, V
+local f
+
+---@type table<string, boolean>
+local t
+
+local k, <?v?> = f(t)
+]]
+
+TEST 'fun()' [[
+---@return fun()
+local function f() end
+
+local <?r?> = f()
+]]
+
+TEST 'table<string, boolean>' [[
+---@return table<string, boolean>
+local function f() end
+
+local <?r?> = f()
+]]
+
+TEST 'string' [[
+---@class string
+
+---@generic K, V
+---@return fun(arg: table<K, V>):K, V
+local function f() end
+
+local f2 = f()
+
+---@type table<string, boolean>
+local t
+
+local <?k?>, v = f2(t)
+]]
+
+TEST 'fun(a: <V>):integer, <V>' [[
+---@generic K, V
+---@param a K
+---@return fun(a: V):K, V
+local function f(a) end
+
+local <?f2?> = f(1)
+]]
+
+TEST 'integer' [[
+---@generic K, V
+---@param a K
+---@return fun(a: V):K, V
+local function f(a) end
+
+local f2 = f(1)
+local <?i?>, v = f2(true)
+]]
+
+TEST 'boolean' [[
+---@generic K, V
+---@param a K
+---@return fun(a: V):K, V
+local function f(a) end
+
+local f2 = f(1)
+local i, <?v?> = f2(true)
+]]
+
+TEST 'fun(table: table<<K>, <V>>, index?: <K>):<K>, <V>' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local <?next?> = pairs(dummy)
+]]
+
+TEST 'string' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local next = pairs(dummy)
+
+---@type table<string, boolean>
+local t
+local <?k?>, v = next(t)
+]]
+
+TEST 'boolean' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local next = pairs(dummy)
+
+---@type table<string, boolean>
+local t
+local k, <?v?> = next(t)
+]]
+
+TEST 'string' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local next = pairs(dummy)
+
+---@type table<string, boolean>
+local t
+local <?k?>, v = next(t, nil)
+]]
+
+TEST 'boolean' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local next = pairs(dummy)
+
+---@type table<string, boolean>
+local t
+local k, <?v?> = next(t, nil)
+]]
+
+TEST 'string' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+local next = pairs(dummy)
+
+---@type table<string, boolean>
+local t
+
+for <?k?>, v in next, t do
+end
+]]
+
+TEST 'boolean' [[
+---@class boolean
+
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+local function pairs(t) end
+
+local f = pairs(t)
+
+---@type table<string, boolean>
+local t
+
+for k, <?v?> in f, t do
+end
+]]
+
+TEST 'string' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+local function pairs(t) end
+
+---@type table<string, boolean>
+local t
+
+for <?k?>, v in pairs(t) do
+end
+]]
+
+TEST 'boolean' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+---@type table<string, boolean>
+local t
+
+for k, <?v?> in pairs(t) do
+end
+]]
+
+TEST 'boolean' [[
+---@generic T: table, V
+---@param t T
+---@return fun(table: V[], i?: integer):integer, V
+---@return T
+---@return integer i
+local function ipairs(t) end
+
+---@type boolean[]
+local t
+
+for _, <?v?> in ipairs(t) do
+end
+]]
+
+TEST 'boolean' [[
+---@generic T: table, V
+---@param t T
+---@return fun(table: V[], i?: integer):integer, V
+---@return T
+---@return integer i
+local function ipairs(t) end
+
+---@type table<integer, boolean>
+local t
+
+for _, <?v?> in ipairs(t) do
+end
+]]
+
+TEST 'boolean' [[
+---@generic T: table, V
+---@param t T
+---@return fun(table: V[], i?: integer):integer, V
+---@return T
+---@return integer i
+local function ipairs(t) end
+
+---@class MyClass
+---@field [integer] boolean
+local t
+
+for _, <?v?> in ipairs(t) do
+end
+]]
+
+TEST 'boolean' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index: K):K, V
+---@return T
+---@return nil
+local function pairs(t) end
+
+---@type boolean[]
+local t
+
+for k, <?v?> in pairs(t) do
+end
+]]
+
+TEST 'integer' [[
+---@generic T: table, K, V
+---@param t T
+---@return fun(table: table<K, V>, index?: K):K, V
+---@return T
+local function pairs(t) end
+
+---@type boolean[]
+local t
+
+for <?k?>, v in pairs(t) do
+end
+]]
+
+TEST 'E' [[
+---@class A
+---@class B: A
+---@class C: B
+---@class D: C
+
+---@class E: D
+local m
+
+function m:f()
+ return <?self?>
+end
+]]
+
+TEST 'Cls' [[
+---@class Cls
+local Cls = {}
+
+---@generic T
+---@param self T
+---@return T
+function Cls.new(self) return self end
+
+local <?test?> = Cls:new()
+]]
+
+TEST 'Cls' [[
+---@class Cls
+local Cls = {}
+
+---@generic T
+---@param self T
+---@return T
+function Cls:new() return self end
+
+local <?test?> = Cls:new()
+]]
+
+TEST 'Cls' [[
+---@class Cls
+local Cls = {}
+
+---@generic T
+---@param self T
+---@return T
+function Cls.new(self) return self end
+
+local <?test?> = Cls.new(Cls)
+]]
+
+TEST 'Cls' [[
+---@class Cls
+local Cls = {}
+
+---@generic T
+---@param self T
+---@return T
+function Cls:new() return self end
+
+local <?test?> = Cls.new(Cls)
+]]
+
+TEST 'Rct' [[
+---@class Obj
+local Obj = {}
+
+---@generic T
+---@param self T
+---@return T
+function Obj.new(self) return self end
+
+
+---@class Pnt:Obj
+local Pnt = {x = 0, y = 0}
+
+
+---@class Rct:Pnt
+local Rct = {w = 0, h = 0}
+
+
+local <?test?> = Rct.new(Rct)
+
+-- local test = Rct:new()
+
+return test
+]]
+
+TEST 'function' [[
+string.gsub():gsub():<?gsub?>()
+]]
+
+config.set(nil, 'Lua.hover.enumsLimit', 5)
+TEST [['a'|'b'|'c'|'d'|'e'...(+5)]] [[
+---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
+local <?t?>
+]]
+
+config.set(nil, 'Lua.hover.enumsLimit', 1)
+TEST [['a'...(+9)]] [[
+---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
+local <?t?>
+]]
+
+config.set(nil, 'Lua.hover.enumsLimit', 0)
+TEST '...(+10)' [[
+---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
+local <?t?>
+]]
+
+config.set(nil, 'Lua.hover.enumsLimit', 5)
+
+TEST 'string|fun():string' [[
+---@type string | fun(): string
+local <?t?>
+]]
+
+TEST 'string' [[
+local valids = {
+ ['Lua 5.1'] = false,
+ ['Lua 5.2'] = false,
+ ['Lua 5.3'] = false,
+ ['Lua 5.4'] = false,
+ ['LuaJIT'] = false,
+}
+
+for <?k?>, v in pairs(valids) do
+end
+]]
+
+TEST 'boolean' [[
+local valids = {
+ ['Lua 5.1'] = false,
+ ['Lua 5.2'] = false,
+ ['Lua 5.3'] = false,
+ ['Lua 5.4'] = false,
+ ['LuaJIT'] = false,
+}
+
+for k, <?v?> in pairs(valids) do
+end
+]]
+
+TEST 'string' [[
+local t = {
+ a = 1,
+ b = 1,
+}
+
+for <?k?>, v in pairs(t) do
+end
+]]
+
+TEST 'integer' [[
+local t = {'a', 'b'}
+
+for <?k?>, v in pairs(t) do
+end
+]]
+
+TEST 'string' [[
+local t = {'a', 'b'}
+
+for k, <?v?> in pairs(t) do
+end
+]]
+
+TEST 'fun():number, boolean' [[
+---@type fun():number, boolean
+local <?t?>
+]]
+
+
+TEST 'fun(value: Class)' [[
+---@class Class
+
+---@param callback fun(value: Class)
+function work(callback)
+end
+
+work(<?function?> (value)
+end)
+]]
+
+TEST 'Class' [[
+---@class Class
+
+---@param callback fun(value: Class)
+function work(callback)
+end
+
+work(function (<?value?>)
+end)
+]]
+
+TEST 'fun(value: Class)' [[
+---@class Class
+
+---@param callback fun(value: Class)
+function work(callback)
+end
+
+pcall(work, <?function?> (value)
+end)
+]]
+
+TEST 'Class' [[
+---@class Class
+
+---@param callback fun(value: Class)
+function work(callback)
+end
+
+xpcall(work, debug.traceback, function (<?value?>)
+end)
+]]
+
+TEST 'string' [[
+---@generic T
+---@param x T
+---@return { x: T }
+local function f(x) end
+
+local t = f('')
+
+print(t.<?x?>)
+]]
+
+TEST 'string' [[
+---@generic T
+---@param t T[]
+---@param callback fun(v: T)
+local function f(t, callback) end
+
+---@type string[]
+local t
+
+f(t, function (<?v?>) end)
+]]
+
+TEST 'unknown' [[
+---@generic T
+---@param t T[]
+---@param callback fun(v: T)
+local function f(t, callback) end
+
+local t = {}
+
+f(t, function (<?v?>) end)
+]]
+
+TEST 'table' [[
+local <?t?> = setmetatable({}, { __index = function () end })
+]]
+
+TEST 'player' [[
+---@class player
+local t
+
+<?t?>:getOwner()
+]]
+
+TEST 'string[][]' [[
+---@type string[][]
+local <?t?>
+]]
+
+TEST 'table' [[
+---@type {}[]
+local t
+
+local <?v?> = t[1]
+]]
+
+TEST 'string' [[
+---@type string[][]
+local v = {}
+
+for _, a in ipairs(v) do
+ for i, <?b?> in ipairs(a) do
+ end
+end
+]]
+
+--TEST 'number' [[
+-----@param x number
+--local f
+--
+--f = function (<?x?>) end
+--]]
+
+TEST 'fun(i: integer)' [[
+--- @class Emit
+--- @field on fun(eventName: string, cb: function)
+--- @field on fun(eventName: 'died', cb: fun(i: integer))
+--- @field on fun(eventName: 'won', cb: fun(s: string))
+local emit = {}
+
+emit.on("died", <?function?> (i)
+end)
+]]
+
+TEST 'integer' [[
+--- @class Emit
+--- @field on fun(eventName: string, cb: function)
+--- @field on fun(eventName: 'died', cb: fun(i: integer))
+--- @field on fun(eventName: 'won', cb: fun(s: string))
+local emit = {}
+
+emit.on("died", function (<?i?>)
+end)
+]]
+
+TEST 'integer' [[
+--- @class Emit
+--- @field on fun(self: Emit, eventName: string, cb: function)
+--- @field on fun(self: Emit, eventName: 'died', cb: fun(i: integer))
+--- @field on fun(self: Emit, eventName: 'won', cb: fun(s: string))
+local emit = {}
+
+emit:on("died", function (<?i?>)
+end)
+]]
+
+TEST 'integer' [[
+--- @class Emit
+--- @field on fun(self: Emit, eventName: string, cb: function)
+--- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer))
+--- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string))
+local emit = {}
+
+emit.on(self, "died", function (<?i?>)
+end)
+]]
+
+TEST '👍' [[
+---@class 👍
+local <?x?>
+]]
+
+TEST 'integer' [[
+---@type boolean
+local x
+
+<?x?> = 1
+]]
+
+TEST 'integer' [[
+---@class Class
+local x
+
+<?x?> = 1
+]]
+
+TEST 'unknown' [[
+---@return number
+local function f(x)
+ local <?y?> = x()
+end
+]]
+
+TEST 'unknown' [[
+local mt
+
+---@return number
+function mt:f() end
+
+local <?v?> = mt()
+]]
+
+TEST 'unknown' [[
+local <?mt?>
+
+---@class X
+function mt:f(x) end
+]]
+
+TEST 'any' [[
+local mt
+
+---@class X
+function mt:f(<?x?>) end
+]]
+
+TEST 'unknown' [[
+local <?mt?>
+
+---@type number
+function mt:f(x) end
+]]
+
+TEST 'any' [[
+local mt
+
+---@type number
+function mt:f(<?x?>) end
+]]
+
+TEST 'Test' [[
+---@class Test
+_G.<?Test?> = {}
+]]
+
+TEST 'integer' [[
+local mt = {}
+
+---@param callback fun(i: integer)
+function mt:loop(callback) end
+
+mt:loop(function (<?i?>)
+
+end)
+]]
+
+TEST 'C' [[
+---@class D
+---@field y integer # D comment
+
+---@class C
+---@field x integer # C comment
+---@field d D
+
+---@param c C
+local function f(c) end
+
+f <?{?>
+ x = ,
+}
+]]
+
+TEST 'integer' [[
+---@class D
+---@field y integer # D comment
+
+---@class C
+---@field x integer # C comment
+---@field d D
+
+---@param c C
+local function f(c) end
+
+f {
+ <?x?> = ,
+}
+]]
+
+TEST 'integer' [[
+---@class D
+---@field y integer # D comment
+
+---@class C
+---@field x integer # C comment
+---@field d D
+
+---@param c C
+local function f(c) end
+
+f {
+ d = {
+ <?y?> = ,
+ }
+}
+]]
+
+TEST 'integer' [[
+for <?i?> = a, b, c do end
+]]
+
+TEST 'number' [[
+---@param x number
+function F(<?x?>) end
+
+---@param x boolean
+function F(x) end
+]]
+
+TEST 'B' [[
+---@class A
+local A
+
+---@return A
+function A:x() end
+
+---@class B: A
+local B
+
+---@return B
+function B:x() end
+
+---@type B
+local t
+
+local <?v?> = t.x()
+]]
+
+TEST 'function' [[
+---@overload fun()
+function <?f?>() end
+]]
+
+TEST 'integer' [[
+---@type table<string, integer>
+local t
+
+t.<?a?>
+]]
+
+TEST '"a"|"b"|"c"' [[
+---@type table<string, "a"|"b"|"c">
+local t
+
+t.<?a?>
+]]
+
+TEST 'integer' [[
+---@class A
+---@field x integer
+
+---@type A
+local t
+t.<?x?>
+]]
+
+TEST 'boolean' [[
+local <?var?> = true
+var = 1
+var = 1.0
+]]
+
+TEST 'unknown' [[
+---@return ...
+local function f() end
+
+local <?x?> = f()
+]]
+
+TEST 'unknown' [[
+---@return ...
+local function f() end
+
+local _, <?x?> = f()
+]]
+
+TEST 'unknown' [[
+local t = {
+ x = 1,
+ y = 2,
+}
+
+local <?x?> = t[#t]
+]]
+
+TEST 'string' [[
+local t = {
+ x = 1,
+ [1] = 'x',
+}
+
+local <?x?> = t[#t]
+]]
+
+TEST 'string' [[
+local t = { 'x' }
+
+local <?x?> = t[#t]
+]]
+
+TEST '(string|integer)[]' [[
+---@type (string|integer)[]
+local <?x?>
+]]
+
+TEST 'boolean' [[
+---@type table<string, boolean>
+local t
+
+---@alias uri string
+
+---@type string
+local uri
+
+local <?v?> = t[uri]
+]]
+
+TEST 'A' [[
+---@class A
+G = {}
+
+<?G?>:A()
+]]
+
+TEST 'A' [[
+---@type A
+local <?x?> = nil
+]]
+
+TEST 'A' [[
+---@class A
+---@field b B
+local mt
+
+function mt:f()
+ self.b:x()
+ print(<?self?>)
+end
+]]
+
+TEST 'string?' [[
+---@return string?
+local function f() end
+
+local <?x?> = f()
+]]
+
+TEST 'AA' [[
+---@class AA
+---@overload fun():AA
+local AAA
+
+
+local <?x?> = AAA()
+]]
+
+TEST 'AA' [[
+---@class AA
+---@overload fun():AA
+AAA = {}
+
+
+local <?x?> = AAA()
+]]
+
+TEST 'string' [[
+local <?x?>
+x = '1'
+x = 1
+]]
+
+TEST 'string' [[
+local x
+<?x?> = '1'
+x = 1
+]]
+
+TEST 'integer' [[
+local x
+x = '1'
+<?x?> = 1
+]]
+
+TEST 'unknown' [[
+local x
+print(<?x?>)
+x = '1'
+x = 1
+]]
+
+TEST 'string' [[
+local x
+x = '1'
+print(<?x?>)
+x = 1
+]]
+
+TEST 'integer' [[
+local x
+x = '1'
+x = 1
+print(<?x?>)
+]]
+
+TEST 'unknown' [[
+local x
+
+function A()
+ print(<?x?>)
+end
+]]
+
+TEST 'string' [[
+local x
+
+function A()
+ print(<?x?>)
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'string' [[
+local x
+
+x = '1'
+
+function A()
+ print(<?x?>)
+end
+
+x = 1
+]]
+
+TEST 'integer' [[
+local x
+
+x = '1'
+x = 1
+
+function A()
+ print(<?x?>)
+end
+
+]]
+
+TEST 'boolean' [[
+local x
+
+function A()
+ x = true
+ print(<?x?>)
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'unknown' [[
+local x
+
+function A()
+ x = true
+end
+
+print(<?x?>)
+x = '1'
+x = 1
+]]
+
+TEST 'boolean' [[
+local x
+
+function A()
+ x = true
+ function B()
+ print(<?x?>)
+ end
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'table' [[
+local x
+
+function A()
+ x = true
+ function B()
+ x = {}
+ print(<?x?>)
+ end
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'boolean' [[
+local x
+
+function A()
+ x = true
+ function B()
+ x = {}
+ end
+ print(<?x?>)
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'unknown' [[
+local x
+
+function A()
+ x = true
+ function B()
+ x = {}
+ end
+end
+
+function C()
+ print(<?x?>)
+end
+
+x = '1'
+x = 1
+]]
+
+TEST 'integer' [[
+local x
+x = true
+do
+ x = 1
+end
+print(<?x?>)
+]]
+
+TEST 'boolean' [[
+local x
+x = true
+function XX()
+ do
+ x = 1
+ end
+end
+print(<?x?>)
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local <?x?>
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if <?x?> then
+ print(x)
+end
+]]
+--[[
+context 0 integer?
+
+save copy 'block'
+save copy 'out'
+push 'block'
+get
+push copy
+truthy
+falsy ref 'out'
+get
+save HEAD 'final'
+push 'out'
+
+push copy HEAD
+merge 'final'
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if x then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x then
+ print(x)
+end
+
+print(<?x?>)
+]]
+
+TEST 'nil' [[
+---@type integer?
+local x
+
+if not x then
+ print(<?x?>)
+end
+
+print(x)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x then
+ x = 1
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if xxx and x then
+ print(<?x?>)
+end
+]]
+
+TEST 'unknown' [[
+---@type integer?
+local x
+
+if not x and x then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if x and not mark[x] then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if xxx and x then
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if xxx and x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if x ~= nil then
+ print(<?x?>)
+end
+
+print(x)
+]]
+
+TEST 'integer|nil' [[
+---@type integer?
+local x
+
+if x ~= nil then
+ print(x)
+end
+
+print(<?x?>)
+]]
+
+TEST 'nil' [[
+---@type integer?
+local x
+
+if x == nil then
+ print(<?x?>)
+end
+
+print(x)
+]]
+
+TEST 'integer|nil' [[
+---@type integer?
+local x
+
+if x == nil then
+ print(x)
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+<?x?> = x or 1
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+<?x?> = x or y
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x then
+ goto ANYWHERE
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [=[
+local x
+
+print(<?x?>--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+print(<?io?>--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+print(io.<?open?>--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+local <?x?> = io['open']--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+local <?x?> = 1 + 1--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+local <?x?> = not 1--[[@as integer]])
+]=]
+
+TEST 'integer' [=[
+local <?x?> = ()--[[@as integer]])
+]=]
+
+TEST 'integer?' [[
+---@param x? integer
+local function f(<?x?>)
+
+end
+]]
+
+TEST 'integer' [[
+local x = 1
+x = <?x?>
+]]
+
+TEST 'integer?' [[
+---@class A
+---@field x? integer
+local t
+
+t.<?x?>
+]]
+
+TEST 'integer?' [[
+---@type { x?: integer }
+local t
+
+t.<?x?>
+]]
+
+TEST 'boolean' [[
+---@class A
+---@field [integer] boolean
+local t
+
+local <?x?> = t[1]
+]]
+
+TEST 'unknown' [[
+local <?x?> = y and z
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+assert(x)
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+assert(x ~= nil)
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer | nil
+local x
+
+assert(x)
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer | nil
+local x
+
+assert(x ~= nil)
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+local x
+
+assert(x == 1)
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if x and <?x?>.y then
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x and x.y then
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x and x.y then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x or <?x?>.y then
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if not x or x.y then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x or x.y then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x.y or x then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+if x.y or not x then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not x or not y then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if not y or not x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+while true do
+ if not x then
+ break
+ end
+ print(<?x?>)
+end
+]]
+
+TEST 'integer?' [[
+---@type integer?
+local x
+
+while true do
+ if not x then
+ break
+ end
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+while x do
+ print(<?x?>)
+end
+]]
+
+TEST 'integer' [[
+---@type fun():integer?
+local iter
+
+for <?x?> in iter do
+end
+]]
+
+TEST 'integer' [[
+local x
+
+---@type integer
+<?x?> = XXX
+]]
+
+TEST 'unknown' [[
+for _ = 1, 999 do
+ local <?x?>
+end
+]]
+
+TEST 'integer' [[
+local x
+
+---@cast x integer
+
+print(<?x?>)
+]]
+
+TEST 'unknown' [[
+local x
+
+---@cast x integer
+
+local x
+print(<?x?>)
+]]
+
+TEST 'unknown' [[
+local x
+
+if true then
+ local x
+ ---@cast x integer
+ print(x)
+end
+
+print(<?x?>)
+]]
+
+TEST 'boolean|integer' [[
+local x = 1
+
+---@cast x +boolean
+
+print(<?x?>)
+]]
+
+TEST 'boolean' [[
+---@type integer|boolean
+local x
+
+---@cast x -integer
+
+print(<?x?>)
+]]
+
+TEST 'boolean?' [[
+---@type boolean
+local x
+
+---@cast x +?
+
+print(<?x?>)
+]]
+
+TEST 'boolean' [[
+---@type boolean?
+local x
+
+---@cast x -?
+
+print(<?x?>)
+]]
+
+TEST 'nil' [[
+---@type string?
+local x
+
+if x then
+ return
+else
+ print(<?x?>)
+end
+
+print(x)
+]]
+
+TEST 'string' [[
+---@type string?
+local x
+
+if not x then
+ return
+else
+ print(<?x?>)
+end
+
+print(x)
+]]
+
+TEST 'string' [[
+---@type string?
+local x
+
+if not x then
+ return
+else
+ print(x)
+end
+
+print(<?x?>)
+]]
+
+TEST 'true' [[
+---@type boolean | nil
+local x
+
+if not x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'true' [[
+---@type boolean
+local t
+
+if t then
+ print(<?t?>)
+ return
+end
+
+print(t)
+]]
+
+TEST 'false' [[
+---@type boolean
+local t
+
+if t then
+ print(t)
+ return
+end
+
+print(<?t?>)
+]]
+
+TEST 'nil' [[
+---@type integer?
+local t
+
+if t then
+else
+ print(<?t?>)
+end
+
+print(t)
+]]
+
+TEST 'table' [[
+local function f()
+ if x then
+ return y
+ end
+ return {}
+end
+
+local <?z?> = f()
+]]
+
+TEST 'integer|table' [[
+local function returnI()
+ return 1
+end
+
+local function f()
+ if x then
+ return returnI()
+ end
+ return {}
+end
+
+local <?z?> = f()
+]]
+
+TEST 'number' [[
+for _ in _ do
+ ---@type number
+ local <?x?>
+end
+]]
+
+TEST 'unknown' [[
+for _ in _ do
+ ---@param x number
+ local <?x?>
+end
+]]
+
+TEST 'unknown' [[
+---@type number
+for <?x?> in _ do
+end
+]]
+
+TEST 'number' [[
+---@param x number
+for <?x?> in _ do
+end
+]]
+
+TEST 'table' [[
+---@alias tp table
+
+---@type tp
+local <?x?>
+]]
+
+TEST '{ name: boolean }' [[
+---@alias tp {name: boolean}
+
+---@type tp
+local <?x?>
+]]
+
+TEST 'boolean|{ name: boolean }' [[
+---@alias tp boolean | {name: boolean}
+
+---@type tp
+local <?x?>
+]]
+
+TEST '`1`|`true`' [[
+---@type `1` | `true`
+local <?x?>
+]]
+
+TEST 'function' [[
+local x
+
+function x() end
+
+print(<?x?>)
+]]
+
+TEST 'unknown' [[
+local x
+
+if x.field == 'haha' then
+ print(<?x?>)
+end
+]]
+
+TEST 'string' [[
+---@type string?
+local t
+
+if not t or xxx then
+ return
+end
+
+print(<?t?>)
+]]
+
+TEST 'table' [[
+---@type table|nil
+local t
+
+return function ()
+ if not t then
+ return
+ end
+
+ print(<?t?>)
+end
+]]
+
+TEST 'table' [[
+---@type table|nil
+local t
+
+f(function ()
+ if not t then
+ return
+ end
+
+ print(<?t?>)
+end)
+]]
+
+TEST 'table' [[
+---@type table?
+local t
+
+t = t or {}
+
+print(<?t?>)
+]]
+
+TEST 'unknown|nil' [[
+local x
+
+if x == nil then
+end
+
+print(<?x?>)
+]]
+
+TEST 'table<xxx, true>' [[
+---@alias xxx table<xxx, true>
+
+---@type xxx
+local <?t?>
+]]
+
+TEST 'xxx[][]' [[
+---@alias xxx xxx[]
+
+---@type xxx
+local <?t?>
+]]
+
+TEST 'fun(x: fun(x: xxx))' [[
+---@alias xxx fun(x: xxx)
+
+---@type xxx
+local <?t?>
+]]
+
+TEST 'table' [[
+---@type table|nil
+local t
+
+while t do
+ print(<?t?>)
+end
+]]
+
+TEST 'table|nil' [[
+---@type table|nil
+local t
+
+while <?t?> do
+ print(t)
+end
+]]
+
+TEST 'table' [[
+---@type table|nil
+local t
+
+while t ~= nil do
+ print(<?t?>)
+end
+]]
+
+TEST 'table|nil' [[
+---@type table|nil
+local t
+
+while <?t?> ~= nil do
+ print(t)
+end
+]]
+
+TEST 'integer' [[
+---@type integer?
+local n
+
+if not n then
+ error('n is nil')
+end
+
+print(<?n?>)
+]]
+
+TEST 'integer' [[
+---@type integer?
+local n
+
+if not n then
+ os.exit()
+end
+
+print(<?n?>)
+]]
+
+TEST 'table' [[
+---@type table?
+local n
+
+print((n and <?n?>.x))
+]]
+
+TEST 'table' [[
+---@type table?
+local n
+
+n = n and <?n?>.x or 1
+]]
+
+TEST 'table' [[
+---@type table?
+local n
+
+n = ff[n and <?n?>.x]
+]]
+
+TEST 'integer' [[
+local x
+
+if type(x) == 'integer' then
+ print(<?x?>)
+end
+]]
+
+TEST 'boolean|integer' [[
+local x
+
+if type(x) == 'integer'
+or type(x) == 'boolean' then
+ print(<?x?>)
+end
+]]
+
+TEST 'fun()' [[
+---@type fun()?
+local x
+
+if type(x) == 'function' then
+ print(<?x?>)
+end
+]]
+
+TEST 'function' [[
+local x
+
+if type(x) == 'function' then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer' [[
+local x
+local tp = type(x)
+
+if tp == 'integer' then
+ print(<?x?>)
+end
+]]
+
+TEST 'integer' [[
+---@type integer?
+local x
+
+if (x == nil) then
+else
+ print(<?x?>)
+end
+]]
+
+TEST 'B' [[
+---@class A
+---@class B
+
+---@type A
+local x
+
+---@type B
+x = call(x)
+
+print(<?x?>)
+]]
+
+TEST 'nil' [[
+local function f()
+end
+
+local <?x?> = f()
+]]
+
+TEST 'integer[]' [[
+---@type integer[]
+local x
+if not x then
+ return
+end
+
+print(<?x?>)
+]]
+
+TEST 'unknown' [[
+---@type string[]
+local t
+
+local <?x?> = t.x
+]]
+
+TEST 'integer|unknown' [[
+local function f()
+ return GG
+end
+
+local t
+
+t.x = 1
+t.x = f()
+
+print(t.<?x?>)
+]]
+
+TEST 'integer' [[
+local function f()
+ if X then
+ return X
+ else
+ return 1
+ end
+end
+
+local <?n?> = f()
+]]
+
+TEST 'unknown' [[
+local function f()
+ return t[k]
+end
+
+local <?n?> = f()
+]]
+
+TEST 'integer|nil' [[
+local function f()
+ if x then
+ return
+ else
+ return 1
+ end
+end
+
+local <?n?> = f()
+]]
+
+TEST 'integer' [[
+---@class A
+---@field x integer
+local m
+
+m.<?x?> = true
+
+print(m.x)
+]]
+
+TEST 'integer' [[
+---@class A
+---@field x integer
+local m
+
+m.x = true
+
+print(m.<?x?>)
+]]
+
+TEST 'integer' [[
+---@class A
+---@field x integer --> 1st
+local m = {
+ x = '' --> 2nd
+}
+
+---@type boolean
+m.x = true --> 3rd (with ---@type above)
+
+m.x = {} --> 4th
+
+print(m.<?x?>)
+]]
+
+TEST 'string' [[
+---@class A
+----@field x integer --> 1st
+local m = {
+ x = '' --> 2nd
+}
+
+---@type boolean
+m.x = true --> 3rd (with ---@type above)
+
+m.x = {} --> 4th
+
+print(m.<?x?>)
+]]
+
+TEST 'boolean' [[
+---@class A
+----@field x integer --> 1st
+local m = {
+ --x = '' --> 2nd
+}
+
+---@type boolean
+m.x = true --> 3rd (with ---@type above)
+
+m.x = {} --> 4th
+
+print(m.<?x?>)
+]]
+
+TEST 'table' [[
+---@class A
+----@field x integer --> 1st
+local m = {
+ --x = '' --> 2nd
+}
+
+---@type boolean
+--m.x = true --> 3rd (with ---@type above)
+
+m.x = {} --> 4th
+
+print(m.<?x?>)
+]]
+
+TEST 'boolean?' [[
+---@generic T
+---@param x T
+---@return T
+local function echo(x) end
+
+---@type boolean?
+local b
+
+local <?x?> = echo(b)
+]]
+
+TEST 'boolean' [[
+---@generic T
+---@param x T?
+---@return T
+local function echo(x) end
+
+---@type boolean?
+local b
+
+local <?x?> = echo(b)
+]]
+
+TEST 'boolean' [[
+---@generic T
+---@param x? T
+---@return T
+local function echo(x) end
+
+---@type boolean?
+local b
+
+local <?x?> = echo(b)
+]]
+
+TEST 'boolean' [[
+---@type {[integer]: boolean, xx: integer}
+local t
+
+local <?n?> = t[1]
+]]
+
+TEST 'boolean' [[
+---@type integer
+local i
+
+---@type {[integer]: boolean, xx: integer}
+local t
+
+local <?n?> = t[i]
+]]
+
+TEST 'string' [=[
+local x = true
+local y = x--[[@as integer]] --is `integer` here
+local z = <?x?>--[[@as string]] --is `true` here
+]=]
+
+TEST 'integer' [[
+---@type integer
+local x
+
+if type(x) == 'number' then
+ print(<?x?>)
+end
+]]
+
+TEST 'boolean' [[
+---@class A
+---@field [integer] boolean
+local mt
+
+function mt:f()
+ ---@type integer
+ local index
+ local <?x?> = self[index]
+end
+]]
+
+TEST 'boolean' [[
+---@class A
+---@field [B] boolean
+
+---@class B
+
+---@type A
+local a
+
+---@type B
+local b
+
+local <?x?> = a[b]
+]]
+
+TEST 'number' [[
+---@type {x: string ; y: boolean; z: number}
+local t
+
+local <?z?> = t.z
+]]
+
+TEST 'fun():number, boolean' [[
+---@type {f: fun():number, boolean}
+local t
+
+local <?f?> = t.f
+]]
+
+TEST 'fun():number' [[
+---@type {(f: fun():number), x: boolean}
+local t
+
+local <?f?> = t.f
+]]
+
+TEST 'boolean' [[
+---@param ... boolean
+local function f(...)
+ local <?n?> = ...
+end
+]]
+
+TEST 'boolean' [[
+---@param ... boolean
+local function f(...)
+ local _, <?n?> = ...
+end
+]]
+
+TEST 'boolean' [[
+---@return boolean ...
+local function f() end
+
+local <?n?> = f()
+]]
+
+TEST 'boolean' [[
+---@return boolean ...
+local function f() end
+
+local _, <?n?> = f()
+]]
+
+TEST 'boolean' [[
+---@type fun():name1: boolean, name2:number
+local f
+
+local <?n?> = f()
+]]
+
+TEST 'number' [[
+---@type fun():name1: boolean, name2:number
+local f
+
+local _, <?n?> = f()
+]]
+TEST 'boolean' [[
+---@type fun():(name1: boolean, name2:number)
+local f
+
+local <?n?> = f()
+]]
+
+TEST 'number' [[
+---@type fun():(name1: boolean, name2:number)
+local f
+
+local _, <?n?> = f()
+]]
+
+TEST 'boolean' [[
+---@type fun():...: boolean
+local f
+
+local _, <?n?> = f()
+]]
+
+TEST 'string' [[
+local s
+while true do
+ s = ''
+end
+print(<?s?>)
+]]
+
+TEST 'string' [[
+local s
+for _ in _ do
+ s = ''
+end
+print(<?s?>)
+]]
+
+TEST 'A' [[
+---@class A: string
+
+---@type A
+local <?s?> = ''
+]]
+
+TEST 'number' [[
+---@return number
+local function f() end
+local x, <?y?> = 1, f()
+]]
+
+TEST 'boolean' [[
+---@return number, boolean
+local function f() end
+local x, y, <?z?> = 1, f()
+]]
+
+TEST 'number' [[
+---@return number, boolean
+local function f() end
+local x, y, <?z?> = 1, 2, f()
+]]
+
+TEST 'unknown' [[
+local f
+
+print(<?f?>)
+
+function f() end
+]]
+
+TEST 'unknown' [[
+local f
+
+do
+ print(<?f?>)
+end
+
+function f() end
+]]
+
+TEST 'function' [[
+local f
+
+function A()
+ print(<?f?>)
+end
+
+function f() end
+]]
+
+TEST 'number' [[
+---@type number|nil
+local n
+
+local t = {
+ x = n and <?n?>,
+}
+]]
+
+TEST 'table' [[
+---@type table?
+local n
+
+if not n or not <?n?>.x then
+end
+]]
+
+TEST 'table' [[
+---@type table?
+local n
+
+if not n or not <?n?>[1] then
+end
+]]
+
+TEST 'number' [[
+---@type number|false
+local n
+
+---@cast n -false
+
+print(<?n?>)
+]]
+
+TEST 'table' [[
+---@type number|table
+local n
+
+if n
+---@cast n table
+and <?n?>.type == 'xxx' then
+end
+]]
+
+TEST 'integer' [[
+---@type integer?
+local n
+if true then
+ n = 0
+end
+local <?x?> = n or 0
+]]
+
+TEST 'number' [=[
+local <?x?> = F()--[[@as number]]
+]=]
+
+TEST 'number' [=[
+local function f()
+ return F()--[[@as number]]
+end
+
+local <?x?> = f()
+]=]
+
+TEST 'number' [=[
+local <?x?> = X --[[@as number]]
+]=]
+
+TEST 'number' [[
+---@return number?, number?
+local function f() end
+
+for <?x?>, y in f do
+end
+]]
+
+TEST 'number' [[
+---@return number?, number?
+local function f() end
+
+for x, <?y?> in f do
+end
+]]
+
+TEST 'number|nil' [[
+---@type table|nil
+local a
+
+---@type number|nil
+local b
+
+local <?c?> = a and b
+]]
+
+TEST 'number|table|nil' [[
+---@type table|nil
+local a
+
+---@type number|nil
+local b
+
+local <?c?> = a or b
+]]
+
+TEST 'number|table|nil' [[
+---@type table|nil
+local a
+
+---@type number|nil
+local b
+
+local c = a and b
+local <?d?> = a or b
+]]
+
+TEST 'number' [[
+local x
+
+---@return number
+local function f()
+end
+
+x = f()
+
+print(<?x?>)
+]]
+
+TEST 'number' [[
+local x
+
+---@return number
+local function f()
+end
+
+_, x = pcall(f)
+
+print(<?x?>)
+]]
+
+TEST 'string' [[
+---@type table<string|number, string>
+local t
+
+---@type number
+local n
+---@type string
+local s
+
+local <?test?> = t[n]
+local test2 = t[s] --test and test2 are unknow
+]]
+
+TEST 'string' [[
+---@type table<string|number, string>
+local t
+
+---@type number
+local n
+---@type string
+local s
+
+local test = t[n]
+local <?test2?> = t[s] --test and test2 are unknow
+]]
+
+TEST 'table<number, boolean>' [[
+---@type table<number, boolean>
+local t
+
+<?t?> = {}
+]]
+
+TEST 'integer' [[
+---@type integer[]|A
+local t
+
+local <?x?> = t[1]
+]]
+
+TEST 'integer' [[
+---@type integer
+---@diagnostic disable
+local <?t?>
+]]
+
+TEST 'A' [[
+---@class A
+---@diagnostic disable
+local <?t?>
+]]
+
+TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[
+---@type {[string]: number, [true]: string, [1]: boolean, tag: integer}
+local <?t?>
+]]
+
+TEST 'unknown' [[
+local mt = {}
+mt.<?x?> = nil
+]]
+
+TEST 'unknown' [[
+mt = {}
+mt.<?x?> = nil
+]]
+
+TEST 'A' [[
+---@class A
+---@operator unm: A
+
+---@type A
+local a
+local <?b?> = -a
+]]
+
+TEST 'A' [[
+---@class A
+---@operator bnot: A
+
+---@type A
+local a
+local <?b?> = ~a
+]]
+
+TEST 'A' [[
+---@class A
+---@operator len: A
+
+---@type A
+local a
+local <?b?> = #a
+]]
+
+TEST 'A' [[
+---@class A
+---@operator add: A
+
+---@type A
+local a
+local <?b?> = a + 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator sub: A
+
+---@type A
+local a
+local <?b?> = a - 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator mul: A
+
+---@type A
+local a
+local <?b?> = a * 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator div: A
+
+---@type A
+local a
+local <?b?> = a / 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator mod: A
+
+---@type A
+local a
+local <?b?> = a % 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator pow: A
+
+---@type A
+local a
+local <?b?> = a ^ 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator idiv: A
+
+---@type A
+local a
+local <?b?> = a // 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator band: A
+
+---@type A
+local a
+local <?b?> = a & 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator bor: A
+
+---@type A
+local a
+local <?b?> = a | 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator bxor: A
+
+---@type A
+local a
+local <?b?> = a ~ 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator shl: A
+
+---@type A
+local a
+local <?b?> = a << 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator shr: A
+
+---@type A
+local a
+local <?b?> = a >> 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator concat: A
+
+---@type A
+local a
+local <?b?> = a .. 1
+]]
+
+TEST 'A' [[
+---@class A
+---@operator add(boolean): boolean
+---@operator add(integer): A
+
+---@type A
+local a
+local <?b?> = a + 1
+]]
+
+TEST 'boolean' [[
+---@class A
+---@operator add(boolean): boolean
+---@operator add(integer): A
+
+---@type A
+local a
+local <?b?> = a + true
+]]
+
+TEST 'A' [[
+---@class A
+---@operator call: A
+
+---@type A
+local a
+local <?b?> = a()
+]]
+
+TEST 'A' [[
+---@class A
+---@operator call: A
+
+---@type A
+local a
+
+local t = {
+ <?x?> = a(),
+}
+]]
+
+TEST 'boolean' [[
+---@class A
+---@field n number
+---@field [string] boolean
+local t
+
+local <?x?> = t.xx
+]]
+
+TEST 'number' [[
+---@class A
+---@field n number
+---@field [string] boolean
+local t
+
+local <?x?> = t.n
+]]
+
+TEST 'string' [[
+---@class string
+---@operator mod: string
+
+local <?b?> = '' % 1
+]]
+
+TEST 'string|integer' [[
+---@type boolean
+local bool
+
+local <?x?> = bool and '' or 0
+]]
+
+TEST 'string|integer' [[
+local bool
+
+if X then
+ bool = true
+else
+ bool = false
+end
+
+local <?x?> = bool and '' or 0
+]]
+
+TEST 'boolean' [[
+---@type boolean|true|false
+local <?b?>
+]]
+
+TEST 'integer|false' [[
+local <?b?> = X == 1 and X == 1 and 1
+]]
+
+TEST 'unknown|nil' [[
+local function f()
+ if X then
+ return ({})[1]
+ end
+ return nil
+end
+
+local <?n?> = f()
+]]
+
+TEST 'integer' [[
+---@generic T
+---@vararg T # ERROR
+---@return T
+local function test(...)
+ return ...
+end
+
+local <?n?> = test(1)
+]]
+
+TEST 'boolean' [[
+---@type boolean, number
+local <?x?>, y
+]]
+
+TEST 'number' [[
+---@type boolean, number
+local x, <?y?>
+]]
+
+TEST 'unknown' [[
+---@type _, number
+local <?x?>, y
+]]
+
+TEST 'number[]' [[
+local t
+---@cast t number[]?
+
+local x = t and <?t?>[i]
+]]
+
+TEST 'number?' [[
+---@type number[]?
+local t
+
+local <?x?> = t and t[i]
+]]
+
+TEST 'number' [[
+---@type number
+local x
+
+if not <?x?>.y then
+ x = nil
+end
+]]
+
+TEST 'number' [[
+---@type number|nil
+local x
+while x == nil do
+ if x == nil then
+ return
+ end
+
+ x = nil
+end
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+local A = {
+ ---@class XXX
+ B = {}
+}
+
+A.B.C = 1
+
+print(A.B.<?C?>)
+]]
+
+TEST '-2|-3|1' [[
+---@type 1|-2|-3
+local <?n?>
+]]
+
+TEST 'table' [[
+---@enum A
+local m = {}
+
+print(<?m?>)
+]]
+
+TEST 'A' [[
+---@class A
+---@overload fun():A
+local m = {}
+
+---@return A
+function m:init()
+ return <?self?>
+end
+]]
+
+TEST 'string' [[
+---@vararg string
+function F(...)
+ local t = {...}
+ for k, <?v?> in pairs(t) do
+ end
+end
+]]
+
+TEST 'string' [[
+---@vararg string
+function F(...)
+ local t = {...}
+ for k, <?v?> in ipairs(t) do
+ end
+end
+]]
+
+TEST 'integerA' [[
+---@type integerA
+for <?i?> = 1, 10 do
+end
+]]
+
+TEST 'string' [[
+---@class A
+---@field x string
+
+---@class B : A
+local t = {}
+
+t.x = t.x
+
+print(t.<?x?>)
+]]
+
+TEST 'unknown' [[
+local t = {
+ x = 1,
+}
+
+local x
+
+local <?v?> = t[x]
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B: A
+
+---@type A|B
+local <?t?>
+]]
+
+TEST 'function' [[
+---@class myClass
+local myClass = { has = { nested = {} } }
+
+function myClass.has.nested.fn() end
+
+---@type myClass
+local class
+
+class.has.nested.<?fn?>()
+]]
+
+TEST 'integer[]' [[
+---@generic T
+---@param f fun(x: T)
+---@return T[]
+local function x(f) end
+
+---@param x integer
+local <?arr?> = x(function (x) end)
+]]
+
+TEST 'integer[]' [[
+---@generic T
+---@param f fun():T
+---@return T[]
+local function x(f) end
+
+local <?arr?> = x(function ()
+ return 1
+end)
+]]
+
+TEST 'integer[]' [[
+---@generic T
+---@param f fun():T
+---@return T[]
+local function x(f) end
+
+---@return integer
+local <?arr?> = x(function () end)
+]]
+
+TEST 'integer[]' [[
+---@generic T
+---@param f fun(x: T)
+---@return T[]
+local function x(f) end
+
+---@type fun(x: integer)
+local cb
+
+local <?arr?> = x(cb)
+]]
+
+TEST 'integer[]' [[
+---@generic T
+---@param f fun():T
+---@return T[]
+local function x(f) end
+
+---@type fun(): integer
+local cb
+
+local <?arr?> = x(cb)
+]]
+
+TEST 'integer' [[
+---@return fun(x: integer)
+local function f()
+ return function (<?x?>)
+ end
+end
+]]
+
+TEST 'string' [[
+---@class A
+---@field f fun(x: string)
+
+---@type A
+local t = {
+ f = function (<?x?>) end
+}
+]]
+
+config.set(nil, 'Lua.runtime.special', {
+ ['xx.assert'] = 'assert'
+})
+
+TEST 'number' [[
+---@type number?
+local t
+
+xx.assert(t)
+
+print(<?t?>)
+]]
+
+config.set(nil, 'Lua.runtime.special', nil)
+
+TEST 'A' [[
+---@class A
+local mt
+
+---@return <?self?>
+function mt:init()
+end
+]]
+
+TEST 'A' [[
+---@class A
+local mt
+
+---@return self
+function mt:init()
+end
+
+local <?o?> = mt:init()
+]]
+
+TEST 'A' [[
+---@class A
+---@field x <?self?>
+]]
+
+TEST 'A' [[
+---@class A
+---@field x self
+
+---@type A
+local o
+
+print(o.<?x?>)
+]]
+
+TEST 'A' [[
+---@class A
+---@overload fun(): self
+local A
+
+local <?o?> = A()
+]]
+
+TEST 'number' [[
+---@type table<'Test1', fun(x: number)>
+local t = {
+ ["Test1"] = function(<?x?>) end,
+}
+]]
+
+TEST 'number' [[
+---@type table<5, fun(x: number)>
+local t = {
+ [5] = function(<?x?>) end,
+}
+]]
+
+TEST 'number' [[
+---@type fun(x: number)
+local function f(<?x?>) end
+]]
+
+TEST 'boolean' [[
+---@generic T: string | boolean | table
+---@param x T
+---@return T
+local function f(x)
+ return x
+end
+
+local <?x?> = f(true)
+]]
+
+TEST 'number' [[
+---@class A
+---@field [1] number
+---@field [2] boolean
+local t
+
+local <?n?> = t[1]
+]]
+
+TEST 'boolean' [[
+---@class A
+---@field [1] number
+---@field [2] boolean
+local t
+
+local <?n?> = t[2]
+]]
+
+TEST 'N' [[
+---@class N: number
+local x
+
+if x == 0.1 then
+ print(<?x?>)
+end
+]]
+
+TEST 'vec3' [[
+---@class mat4
+---@operator mul(vec3): vec3 -- matrix * vector
+---@operator mul(number): mat4 -- matrix * constant
+
+---@class vec3: number
+
+---@type mat4, vec3
+local m, v
+
+local <?r?> = m * v
+]]
+
+TEST 'mat4' [[
+---@class mat4
+---@operator mul(number): mat4 -- matrix * constant
+---@operator mul(vec3): vec3 -- matrix * vector
+
+---@class vec3: number
+
+---@type mat4, vec3
+local m, v
+
+local <?r?> = m * v
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+ ---@cast t A
+else
+ print(<?t?>)
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+ ---@cast t A
+elseif <?t?> then
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+ ---@cast t A
+ print(t)
+elseif <?t?> then
+end
+]]
+
+TEST 'A|B' [[
+---@class A
+---@class B
+
+---@type A|B
+local t
+
+if x then
+ ---@cast t A
+ print(t)
+elseif <?t?> then
+ ---@cast t A
+ print(t)
+end
+]]
+
+TEST 'function' [[
+local function x()
+ print(<?x?>)
+end
+]]
+
+TEST 'number' [[
+---@type number?
+local x
+
+do
+ if not x then
+ return
+ end
+end
+
+print(<?x?>)
+]]
+
+TEST 'number' [[
+---@type number[]
+local xs
+
+---@type fun(x): number?
+local f
+
+for _, <?x?> in ipairs(xs) do
+ x = f(x)
+end
+]]
+
+TEST 'number' [[
+---@type number?
+X = Y
+
+if X then
+ print(<?X?>)
+end
+]]
+
+TEST 'number' [[
+---@type number|boolean
+X = Y
+
+if type(X) == 'number' then
+ print(<?X?>)
+end
+]]
+
+TEST 'boolean' [[
+---@type number|boolean
+X = Y
+
+if type(X) ~= 'number' then
+ print(<?X?>)
+end
+]]
+
+TEST 'boolean' [[
+---@type number
+X = Y
+
+---@cast X boolean
+
+print(<?X?>)
+]]
+
+TEST 'number' [[
+---@type number
+local t
+
+if xxx == <?t?> then
+ print(t)
+end
+]]
+
+TEST 'V' [[
+---@class V
+X = 1
+
+print(<?X?>)
+]]
+
+TEST 'V' [[
+---@class V
+X.Y = 1
+
+print(X.<?Y?>)
+]]
+
+TEST 'integer' [[
+local x = {}
+
+x.y = 1
+local y = x.y
+x.y = nil
+
+print(<?y?>)
+]]
+
+TEST 'function' [[
+function X()
+ <?Y?>()
+end
+
+function Y()
+end
+]]
+
+TEST 'A_Class' [[
+---@class A_Class
+local A = { x = 5 }
+
+function A:func()
+ for i = 1, <?self?>.x do
+ print(i)
+ end
+
+ self.y = 3
+ self.y = self.y + 3
+end
+]]
+
+TEST 'number' [[
+---@type number?
+local n
+local <?v?> = n or error('')
+]]
+
+TEST 'Foo' [[
+---@class Foo
+---@operator mul(Foo): Foo
+---@operator mul(Bar): Foo
+---@class Bar
+
+---@type Foo
+local foo
+
+---@type Foo|Bar
+local fooOrBar
+
+local <?b?> = foo * fooOrBar
+]]
+
+TEST 'number' [[
+local a = 4;
+local b = 2;
+
+local <?c?> = a / b;
+]]
+
+TEST 'string' [[
+local a = '4';
+local b = '2';
+
+local <?c?> = a .. b;
+]]
+
+TEST 'number|{ [1]: string }' [[
+---@alias Some
+---| { [1]: string }
+---| number
+
+local x ---@type Some
+
+print(<?x?>)
+]]
+
+TEST 'integer' [[
+---@class metatable : table
+---@field __index table
+
+---@param table table
+---@param metatable? metatable
+---@return table
+function setmetatable(table, metatable) end
+
+local m = setmetatable({},{ __index = { a = 1 } })
+
+m.<?a?>
+]]
+
+TEST 'integer' [[
+---@class metatable : table
+---@field __index table
+
+---@param table table
+---@param metatable? metatable
+---@return table
+function setmetatable(table, metatable) end
+
+local mt = {a = 1 }
+local m = setmetatable({},{ __index = mt })
+
+m.<?a?>
+]]
+
+TEST 'integer' [[
+local x = 1
+repeat
+until <?x?>
+]]
+
+-- #2144
+TEST 'A' [=[
+local function f()
+ return {} --[[@as A]]
+end
+
+local <?x?> = f()
+]=]
+
+TEST 'A' [=[
+local function f()
+ ---@type A
+ return {}
+end
+
+local <?x?> = f()
+]=]
+
+TEST 'boolean|number' [[
+---@alias A number
+---@alias(partial) A boolean
+
+---@type A
+local <?x?>
+]]
diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua
index da4590a6..35900bc3 100644
--- a/test/type_inference/init.lua
+++ b/test/type_inference/init.lua
@@ -44,4302 +44,5 @@ function TEST(wanted)
end
end
-TEST 'nil' [[
-local <?t?> = nil
-]]
-
-TEST 'string' [[
-local <?var?> = '111'
-]]
-
-TEST 'boolean' [[
-local <?var?> = true
-]]
-
-TEST 'integer' [[
-local <?var?> = 1
-]]
-
-TEST 'number' [[
-local <?var?> = 1.0
-]]
-
-TEST 'unknown' [[
-local <?var?>
-]]
-
-TEST 'unknown' [[
-local <?var?>
-var = y
-]]
-
-TEST 'any' [[
-function f(<?x?>)
-
-end
-]]
-
-TEST 'any' [[
-function f(<?x?>)
- x = 1
-end
-]]
-
-TEST 'string' [[
-local var = '111'
-t.<?x?> = var
-]]
-
-TEST 'string' [[
-local <?var?>
-var = '111'
-]]
-
-TEST 'string' [[
-local var
-<?var?> = '111'
-]]
-
-TEST 'string' [[
-local var
-var = '111'
-print(<?var?>)
-]]
-
-TEST 'function' [[
-function <?xx?>()
-end
-]]
-
-TEST 'function' [[
-local function <?xx?>()
-end
-]]
-
-TEST 'function' [[
-local xx
-<?xx?> = function ()
-end
-]]
-
-TEST 'table' [[
-local <?t?> = {}
-]]
-
-TEST 'unknown' [[
-<?x?>()
-]]
-
-TEST 'boolean' [[
-<?x?> = not y
-]]
-
-TEST 'integer' [[
-<?x?> = #y
-]]
-
-TEST 'integer' [[
-<?x?> = #'aaaa'
-]]
-
-TEST 'integer' [[
-<?x?> = #{}
-]]
-
-TEST 'number' [[
-<?x?> = - y
-]]
-
-TEST 'number' [[
-<?x?> = - 1.0
-]]
-
-TEST 'integer' [[
-<?x?> = ~ y
-]]
-
-TEST 'integer' [[
-<?x?> = ~ 1
-]]
-
-TEST 'boolean' [[
-<?x?> = 1 < 2
-]]
-
-TEST 'integer' [[
-local a = true
-local b = 1
-<?x?> = a and b
-]]
-
-TEST 'integer' [[
-local a = false
-local b = 1
-<?x?> = a or b
-]]
-
-TEST 'boolean' [[
-<?x?> = a == b
-]]
-
-TEST 'unknown' [[
-<?x?> = a << b
-]]
-
-TEST 'integer' [[
-<?x?> = 1 << 2
-]]
-
-TEST 'unknown' [[
-<?x?> = a .. b
-]]
-
-TEST 'string' [[
-<?x?> = 'a' .. 'b'
-]]
-
-TEST 'string' [[
-<?x?> = 'a' .. 1
-]]
-
-TEST 'string' [[
-<?x?> = 'a' .. 1.0
-]]
-
-TEST 'unknown' [[
-<?x?> = a + b
-]]
-
-TEST 'number' [[
-<?x?> = 1 + 2.0
-]]
-
-TEST 'integer' [[
-<?x?> = 1 + 2
-]]
-
-TEST 'integer' [[
----@type integer
-local a
-
-<?x?> = - a
-]]
-
-TEST 'number' [[
-local a
-
-<?x?> = - a
-]]
-
-TEST 'unknown' [[
-<?x?> = 1 + X
-]]
-
-TEST 'unknown' [[
-<?x?> = 1.0 + X
-]]
-
-TEST 'tablelib' [[
----@class tablelib
-table = {}
-
-<?table?>()
-]]
-
-TEST 'string' [[
-_VERSION = 'Lua 5.4'
-
-<?x?> = _VERSION
-]]
-
-TEST 'function' [[
----@class stringlib
-local string
-
-string.xxx = function () end
-
-return ('x').<?xxx?>
-]]
-
-TEST 'function' [[
----@class stringlib
-String = {}
-
-String.xxx = function () end
-
-return ('x').<?xxx?>
-]]
-
-TEST 'function' [[
----@class stringlib
-local string
-
-string.xxx = function () end
-
-<?x?> = ('x').xxx
-]]
-
-TEST 'function' [[
----@class stringlib
-local string
-
-string.xxx = function () end
-
-_VERSION = 'Lua 5.4'
-
-<?x?> = _VERSION.xxx
-]]
-
-TEST 'table' [[
-<?x?> = setmetatable({})
-]]
-
-TEST 'integer' [[
-local function x()
- return 1
-end
-<?y?> = x()
-]]
-
-TEST 'integer|nil' [[
-local function x()
- return 1
- return nil
-end
-<?y?> = x()
-]]
-
-TEST 'unknown|nil' [[
-local function x()
- return a
- return nil
-end
-<?y?> = x()
-]]
-
-TEST 'unknown|nil' [[
-local function x()
- return nil
- return f()
-end
-<?y?> = x()
-]]
-
-TEST 'unknown|nil' [[
-local function x()
- return nil
- return f()
-end
-_, <?y?> = x()
-]]
-
-TEST 'integer' [[
-local function x()
- return 1
-end
-_, <?y?> = pcall(x)
-]]
-
-TEST 'integer' [[
-function x()
- return 1
-end
-_, <?y?> = pcall(x)
-]]
-
-TEST 'integer' [[
-local function x()
- return 1
-end
-_, <?y?> = xpcall(x)
-]]
-
-TEST 'A' [[
----@class A
-
----@return A
-local function f2() end
-
-local function f()
- return f2()
-end
-
-local <?x?> = f()
-]]
-
--- 不根据调用者的输入参数来推测
---TEST 'number' [[
---local function x(a)
--- return <?a?>
---end
---x(1)
---]]
-
---TEST 'table' [[
---setmetatable(<?b?>)
---]]
-
--- 不根据对方函数内的使用情况来推测
-TEST 'unknown' [[
-local function x(a)
- _ = a + 1
-end
-local b
-x(<?b?>)
-]]
-
-TEST 'unknown' [[
-local function x(a, ...)
- local _, <?b?>, _ = ...
-end
-x(nil, 'xx', 1, true)
-]]
-
--- 引用不跨越参数
-TEST 'unknown' [[
-local function x(a, ...)
- return true, 'ss', ...
-end
-local _, _, _, <?b?>, _ = x(nil, true, 1, 'yy')
-]]
-
-TEST 'unknown' [[
-local <?x?> = next()
-]]
-
-TEST 'unknown' [[
-local a, b
-function a()
- return b()
-end
-function b()
- return a()
-end
-local <?x?> = a()
-]]
-
-TEST 'class' [[
----@class class
-local <?x?>
-]]
-
-TEST 'string' [[
----@class string
-
----@type string
-local <?x?>
-]]
-
-TEST '1' [[
----@type 1
-local <?v?>
-]]
-
-TEST 'string[]' [[
----@class string
-
----@type string[]
-local <?x?>
-]]
-
-TEST 'string|table' [[
----@class string
----@class table
-
----@type string | table
-local <?x?>
-]]
-
-TEST [['enum1'|'enum2']] [[
----@type 'enum1' | 'enum2'
-local <?x?>
-]]
-
-TEST [["enum1"|"enum2"]] [[
----@type "enum1" | "enum2"
-local <?x?>
-]]
-
-config.set(nil, 'Lua.hover.expandAlias', false)
-TEST 'A' [[
----@alias A 'enum1' | 'enum2'
-
----@type A
-local <?x?>
-]]
-
-TEST 'A' [[
----@alias A 'enum1' | 'enum2' | A
-
----@type A
-local <?x?>
-]]
-
-TEST 'A' [[
----@alias A 'enum1' | 'enum2' | B
-
----@type A
-local <?x?>
-]]
-config.set(nil, 'Lua.hover.expandAlias', true)
-TEST [['enum1'|'enum2']] [[
----@alias A 'enum1' | 'enum2'
-
----@type A
-local <?x?>
-]]
-
-TEST [['enum1'|'enum2']] [[
----@alias A 'enum1' | 'enum2' | A
-
----@type A
-local <?x?>
-]]
-
-TEST [['enum1'|'enum2'|B]] [[
----@alias A 'enum1' | 'enum2' | B
-
----@type A
-local <?x?>
-]]
-
-TEST '1|true' [[
----@alias A 1 | true
-
----@type A
-local <?x?>
-]]
-
-TEST 'fun()' [[
----@type fun()
-local <?x?>
-]]
-
-TEST 'fun(a: string, b: any, ...any)' [[
----@type fun(a: string, b, ...)
-local <?x?>
-]]
-
-TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[
----@type fun(a: string, b, c?: boolean, ...):c, d?, ...
-local <?x?>
-]]
-
-TEST '{ [string]: string }' [[
----@type { [string]: string }
-local <?x?>
-]]
-
-TEST 'table<string, number>' [[
----@class string
----@class number
-
----@type table<string, number>
-local <?x?>
-]]
-
-TEST 'A<string, number>' [[
----@class A
-
----@type A<string, number>
-local <?x?>
-]]
-
-TEST 'string' [[
----@class string
-
----@type string[]
-local x
-local <?y?> = x[1]
-]]
-
-TEST 'string' [[
----@class string
-
----@return string[]
-local function f() end
-local x = f()
-local <?y?> = x[1]
-]]
-
-TEST 'table' [[
-local t = {}
-local <?v?> = setmetatable(t)
-]]
-
-TEST 'CCC' [[
----@class CCC
-
----@type table<string, CCC>
-local t = {}
-
-print(t.<?a?>)
-]]
-
-TEST [['aaa'|'bbb']] [[
----@type table<string, 'aaa'|'bbb'>
-local t = {}
-
-print(t.<?a?>)
-]]
-
-TEST 'integer' [[
----@generic K
----@type fun(a?: K):K
-local f
-
-local <?n?> = f(1)
-]]
-
-TEST 'unknown' [[
----@generic K
----@type fun(a?: K):K
-local f
-
-local <?n?> = f(nil)
-]]
-
-TEST 'unknown' [[
----@generic K
----@type fun(a: K|integer):K
-local f
-
-local <?n?> = f(1)
-]]
-
-TEST 'integer' [[
----@class integer
-
----@generic T: table, V
----@param t T
----@return fun(table: V[], i?: integer):integer, V
----@return T
----@return integer i
-local function ipairs() end
-
-for <?i?> in ipairs() do
-end
-]]
-
-TEST 'table<string, boolean>' [[
----@generic K, V
----@param t table<K, V>
----@return K
----@return V
-local function next(t) end
-
----@type table<string, boolean>
-local t
-local k, v = next(<?t?>)
-]]
-
-TEST 'string' [[
----@class string
-
----@generic K, V
----@param t table<K, V>
----@return K
----@return V
-local function next(t) end
-
----@type table<string, boolean>
-local t
-local <?k?>, v = next(t)
-]]
-
-TEST 'boolean' [[
----@class boolean
-
----@generic K, V
----@param t table<K, V>
----@return K
----@return V
-local function next(t) end
-
----@type table<string, boolean>
-local t
-local k, <?v?> = next(t)
-]]
-
-TEST 'boolean' [[
----@generic K
----@type fun(arg: K):K
-local f
-
-local <?r?> = f(true)
-]]
-
-TEST 'string' [[
----@class string
-
----@generic K, V
----@type fun(arg: table<K, V>):K, V
-local f
-
----@type table<string, boolean>
-local t
-
-local <?k?>, v = f(t)
-]]
-
-TEST 'boolean' [[
----@class boolean
-
----@generic K, V
----@type fun(arg: table<K, V>):K, V
-local f
-
----@type table<string, boolean>
-local t
-
-local k, <?v?> = f(t)
-]]
-
-TEST 'fun()' [[
----@return fun()
-local function f() end
-
-local <?r?> = f()
-]]
-
-TEST 'table<string, boolean>' [[
----@return table<string, boolean>
-local function f() end
-
-local <?r?> = f()
-]]
-
-TEST 'string' [[
----@class string
-
----@generic K, V
----@return fun(arg: table<K, V>):K, V
-local function f() end
-
-local f2 = f()
-
----@type table<string, boolean>
-local t
-
-local <?k?>, v = f2(t)
-]]
-
-TEST 'fun(a: <V>):integer, <V>' [[
----@generic K, V
----@param a K
----@return fun(a: V):K, V
-local function f(a) end
-
-local <?f2?> = f(1)
-]]
-
-TEST 'integer' [[
----@generic K, V
----@param a K
----@return fun(a: V):K, V
-local function f(a) end
-
-local f2 = f(1)
-local <?i?>, v = f2(true)
-]]
-
-TEST 'boolean' [[
----@generic K, V
----@param a K
----@return fun(a: V):K, V
-local function f(a) end
-
-local f2 = f(1)
-local i, <?v?> = f2(true)
-]]
-
-TEST 'fun(table: table<<K>, <V>>, index?: <K>):<K>, <V>' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local <?next?> = pairs(dummy)
-]]
-
-TEST 'string' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local next = pairs(dummy)
-
----@type table<string, boolean>
-local t
-local <?k?>, v = next(t)
-]]
-
-TEST 'boolean' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local next = pairs(dummy)
-
----@type table<string, boolean>
-local t
-local k, <?v?> = next(t)
-]]
-
-TEST 'string' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local next = pairs(dummy)
-
----@type table<string, boolean>
-local t
-local <?k?>, v = next(t, nil)
-]]
-
-TEST 'boolean' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local next = pairs(dummy)
-
----@type table<string, boolean>
-local t
-local k, <?v?> = next(t, nil)
-]]
-
-TEST 'string' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
-local next = pairs(dummy)
-
----@type table<string, boolean>
-local t
-
-for <?k?>, v in next, t do
-end
-]]
-
-TEST 'boolean' [[
----@class boolean
-
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
-local function pairs(t) end
-
-local f = pairs(t)
-
----@type table<string, boolean>
-local t
-
-for k, <?v?> in f, t do
-end
-]]
-
-TEST 'string' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
-local function pairs(t) end
-
----@type table<string, boolean>
-local t
-
-for <?k?>, v in pairs(t) do
-end
-]]
-
-TEST 'boolean' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
----@type table<string, boolean>
-local t
-
-for k, <?v?> in pairs(t) do
-end
-]]
-
-TEST 'boolean' [[
----@generic T: table, V
----@param t T
----@return fun(table: V[], i?: integer):integer, V
----@return T
----@return integer i
-local function ipairs(t) end
-
----@type boolean[]
-local t
-
-for _, <?v?> in ipairs(t) do
-end
-]]
-
-TEST 'boolean' [[
----@generic T: table, V
----@param t T
----@return fun(table: V[], i?: integer):integer, V
----@return T
----@return integer i
-local function ipairs(t) end
-
----@type table<integer, boolean>
-local t
-
-for _, <?v?> in ipairs(t) do
-end
-]]
-
-TEST 'boolean' [[
----@generic T: table, V
----@param t T
----@return fun(table: V[], i?: integer):integer, V
----@return T
----@return integer i
-local function ipairs(t) end
-
----@class MyClass
----@field [integer] boolean
-local t
-
-for _, <?v?> in ipairs(t) do
-end
-]]
-
-TEST 'boolean' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index: K):K, V
----@return T
----@return nil
-local function pairs(t) end
-
----@type boolean[]
-local t
-
-for k, <?v?> in pairs(t) do
-end
-]]
-
-TEST 'integer' [[
----@generic T: table, K, V
----@param t T
----@return fun(table: table<K, V>, index?: K):K, V
----@return T
-local function pairs(t) end
-
----@type boolean[]
-local t
-
-for <?k?>, v in pairs(t) do
-end
-]]
-
-TEST 'E' [[
----@class A
----@class B: A
----@class C: B
----@class D: C
-
----@class E: D
-local m
-
-function m:f()
- return <?self?>
-end
-]]
-
-TEST 'Cls' [[
----@class Cls
-local Cls = {}
-
----@generic T
----@param self T
----@return T
-function Cls.new(self) return self end
-
-local <?test?> = Cls:new()
-]]
-
-TEST 'Cls' [[
----@class Cls
-local Cls = {}
-
----@generic T
----@param self T
----@return T
-function Cls:new() return self end
-
-local <?test?> = Cls:new()
-]]
-
-TEST 'Cls' [[
----@class Cls
-local Cls = {}
-
----@generic T
----@param self T
----@return T
-function Cls.new(self) return self end
-
-local <?test?> = Cls.new(Cls)
-]]
-
-TEST 'Cls' [[
----@class Cls
-local Cls = {}
-
----@generic T
----@param self T
----@return T
-function Cls:new() return self end
-
-local <?test?> = Cls.new(Cls)
-]]
-
-TEST 'Rct' [[
----@class Obj
-local Obj = {}
-
----@generic T
----@param self T
----@return T
-function Obj.new(self) return self end
-
-
----@class Pnt:Obj
-local Pnt = {x = 0, y = 0}
-
-
----@class Rct:Pnt
-local Rct = {w = 0, h = 0}
-
-
-local <?test?> = Rct.new(Rct)
-
--- local test = Rct:new()
-
-return test
-]]
-
-TEST 'function' [[
-string.gsub():gsub():<?gsub?>()
-]]
-
-config.set(nil, 'Lua.hover.enumsLimit', 5)
-TEST [['a'|'b'|'c'|'d'|'e'...(+5)]] [[
----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
-local <?t?>
-]]
-
-config.set(nil, 'Lua.hover.enumsLimit', 1)
-TEST [['a'...(+9)]] [[
----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
-local <?t?>
-]]
-
-config.set(nil, 'Lua.hover.enumsLimit', 0)
-TEST '...(+10)' [[
----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'
-local <?t?>
-]]
-
-config.set(nil, 'Lua.hover.enumsLimit', 5)
-
-TEST 'string|fun():string' [[
----@type string | fun(): string
-local <?t?>
-]]
-
-TEST 'string' [[
-local valids = {
- ['Lua 5.1'] = false,
- ['Lua 5.2'] = false,
- ['Lua 5.3'] = false,
- ['Lua 5.4'] = false,
- ['LuaJIT'] = false,
-}
-
-for <?k?>, v in pairs(valids) do
-end
-]]
-
-TEST 'boolean' [[
-local valids = {
- ['Lua 5.1'] = false,
- ['Lua 5.2'] = false,
- ['Lua 5.3'] = false,
- ['Lua 5.4'] = false,
- ['LuaJIT'] = false,
-}
-
-for k, <?v?> in pairs(valids) do
-end
-]]
-
-TEST 'string' [[
-local t = {
- a = 1,
- b = 1,
-}
-
-for <?k?>, v in pairs(t) do
-end
-]]
-
-TEST 'integer' [[
-local t = {'a', 'b'}
-
-for <?k?>, v in pairs(t) do
-end
-]]
-
-TEST 'string' [[
-local t = {'a', 'b'}
-
-for k, <?v?> in pairs(t) do
-end
-]]
-
-TEST 'fun():number, boolean' [[
----@type fun():number, boolean
-local <?t?>
-]]
-
-
-TEST 'fun(value: Class)' [[
----@class Class
-
----@param callback fun(value: Class)
-function work(callback)
-end
-
-work(<?function?> (value)
-end)
-]]
-
-TEST 'Class' [[
----@class Class
-
----@param callback fun(value: Class)
-function work(callback)
-end
-
-work(function (<?value?>)
-end)
-]]
-
-TEST 'fun(value: Class)' [[
----@class Class
-
----@param callback fun(value: Class)
-function work(callback)
-end
-
-pcall(work, <?function?> (value)
-end)
-]]
-
-TEST 'Class' [[
----@class Class
-
----@param callback fun(value: Class)
-function work(callback)
-end
-
-xpcall(work, debug.traceback, function (<?value?>)
-end)
-]]
-
-TEST 'string' [[
----@generic T
----@param x T
----@return { x: T }
-local function f(x) end
-
-local t = f('')
-
-print(t.<?x?>)
-]]
-
-TEST 'string' [[
----@generic T
----@param t T[]
----@param callback fun(v: T)
-local function f(t, callback) end
-
----@type string[]
-local t
-
-f(t, function (<?v?>) end)
-]]
-
-TEST 'unknown' [[
----@generic T
----@param t T[]
----@param callback fun(v: T)
-local function f(t, callback) end
-
-local t = {}
-
-f(t, function (<?v?>) end)
-]]
-
-TEST 'table' [[
-local <?t?> = setmetatable({}, { __index = function () end })
-]]
-
-TEST 'player' [[
----@class player
-local t
-
-<?t?>:getOwner()
-]]
-
-TEST 'string[][]' [[
----@type string[][]
-local <?t?>
-]]
-
-TEST 'table' [[
----@type {}[]
-local t
-
-local <?v?> = t[1]
-]]
-
-TEST 'string' [[
----@type string[][]
-local v = {}
-
-for _, a in ipairs(v) do
- for i, <?b?> in ipairs(a) do
- end
-end
-]]
-
---TEST 'number' [[
------@param x number
---local f
---
---f = function (<?x?>) end
---]]
-
-TEST 'fun(i: integer)' [[
---- @class Emit
---- @field on fun(eventName: string, cb: function)
---- @field on fun(eventName: 'died', cb: fun(i: integer))
---- @field on fun(eventName: 'won', cb: fun(s: string))
-local emit = {}
-
-emit.on("died", <?function?> (i)
-end)
-]]
-
-TEST 'integer' [[
---- @class Emit
---- @field on fun(eventName: string, cb: function)
---- @field on fun(eventName: 'died', cb: fun(i: integer))
---- @field on fun(eventName: 'won', cb: fun(s: string))
-local emit = {}
-
-emit.on("died", function (<?i?>)
-end)
-]]
-
-TEST 'integer' [[
---- @class Emit
---- @field on fun(self: Emit, eventName: string, cb: function)
---- @field on fun(self: Emit, eventName: 'died', cb: fun(i: integer))
---- @field on fun(self: Emit, eventName: 'won', cb: fun(s: string))
-local emit = {}
-
-emit:on("died", function (<?i?>)
-end)
-]]
-
-TEST 'integer' [[
---- @class Emit
---- @field on fun(self: Emit, eventName: string, cb: function)
---- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer))
---- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string))
-local emit = {}
-
-emit.on(self, "died", function (<?i?>)
-end)
-]]
-
-TEST '👍' [[
----@class 👍
-local <?x?>
-]]
-
-TEST 'integer' [[
----@type boolean
-local x
-
-<?x?> = 1
-]]
-
-TEST 'integer' [[
----@class Class
-local x
-
-<?x?> = 1
-]]
-
-TEST 'unknown' [[
----@return number
-local function f(x)
- local <?y?> = x()
-end
-]]
-
-TEST 'unknown' [[
-local mt
-
----@return number
-function mt:f() end
-
-local <?v?> = mt()
-]]
-
-TEST 'unknown' [[
-local <?mt?>
-
----@class X
-function mt:f(x) end
-]]
-
-TEST 'any' [[
-local mt
-
----@class X
-function mt:f(<?x?>) end
-]]
-
-TEST 'unknown' [[
-local <?mt?>
-
----@type number
-function mt:f(x) end
-]]
-
-TEST 'any' [[
-local mt
-
----@type number
-function mt:f(<?x?>) end
-]]
-
-TEST 'Test' [[
----@class Test
-_G.<?Test?> = {}
-]]
-
-TEST 'integer' [[
-local mt = {}
-
----@param callback fun(i: integer)
-function mt:loop(callback) end
-
-mt:loop(function (<?i?>)
-
-end)
-]]
-
-TEST 'C' [[
----@class D
----@field y integer # D comment
-
----@class C
----@field x integer # C comment
----@field d D
-
----@param c C
-local function f(c) end
-
-f <?{?>
- x = ,
-}
-]]
-
-TEST 'integer' [[
----@class D
----@field y integer # D comment
-
----@class C
----@field x integer # C comment
----@field d D
-
----@param c C
-local function f(c) end
-
-f {
- <?x?> = ,
-}
-]]
-
-TEST 'integer' [[
----@class D
----@field y integer # D comment
-
----@class C
----@field x integer # C comment
----@field d D
-
----@param c C
-local function f(c) end
-
-f {
- d = {
- <?y?> = ,
- }
-}
-]]
-
-TEST 'integer' [[
-for <?i?> = a, b, c do end
-]]
-
-TEST 'number' [[
----@param x number
-function F(<?x?>) end
-
----@param x boolean
-function F(x) end
-]]
-
-TEST 'B' [[
----@class A
-local A
-
----@return A
-function A:x() end
-
----@class B: A
-local B
-
----@return B
-function B:x() end
-
----@type B
-local t
-
-local <?v?> = t.x()
-]]
-
-TEST 'function' [[
----@overload fun()
-function <?f?>() end
-]]
-
-TEST 'integer' [[
----@type table<string, integer>
-local t
-
-t.<?a?>
-]]
-
-TEST '"a"|"b"|"c"' [[
----@type table<string, "a"|"b"|"c">
-local t
-
-t.<?a?>
-]]
-
-TEST 'integer' [[
----@class A
----@field x integer
-
----@type A
-local t
-t.<?x?>
-]]
-
-TEST 'boolean' [[
-local <?var?> = true
-var = 1
-var = 1.0
-]]
-
-TEST 'unknown' [[
----@return ...
-local function f() end
-
-local <?x?> = f()
-]]
-
-TEST 'unknown' [[
----@return ...
-local function f() end
-
-local _, <?x?> = f()
-]]
-
-TEST 'unknown' [[
-local t = {
- x = 1,
- y = 2,
-}
-
-local <?x?> = t[#t]
-]]
-
-TEST 'string' [[
-local t = {
- x = 1,
- [1] = 'x',
-}
-
-local <?x?> = t[#t]
-]]
-
-TEST 'string' [[
-local t = { 'x' }
-
-local <?x?> = t[#t]
-]]
-
-TEST '(string|integer)[]' [[
----@type (string|integer)[]
-local <?x?>
-]]
-
-TEST 'boolean' [[
----@type table<string, boolean>
-local t
-
----@alias uri string
-
----@type string
-local uri
-
-local <?v?> = t[uri]
-]]
-
-TEST 'A' [[
----@class A
-G = {}
-
-<?G?>:A()
-]]
-
-TEST 'A' [[
----@type A
-local <?x?> = nil
-]]
-
-TEST 'A' [[
----@class A
----@field b B
-local mt
-
-function mt:f()
- self.b:x()
- print(<?self?>)
-end
-]]
-
-TEST 'string?' [[
----@return string?
-local function f() end
-
-local <?x?> = f()
-]]
-
-TEST 'AA' [[
----@class AA
----@overload fun():AA
-local AAA
-
-
-local <?x?> = AAA()
-]]
-
-TEST 'AA' [[
----@class AA
----@overload fun():AA
-AAA = {}
-
-
-local <?x?> = AAA()
-]]
-
-TEST 'string' [[
-local <?x?>
-x = '1'
-x = 1
-]]
-
-TEST 'string' [[
-local x
-<?x?> = '1'
-x = 1
-]]
-
-TEST 'integer' [[
-local x
-x = '1'
-<?x?> = 1
-]]
-
-TEST 'unknown' [[
-local x
-print(<?x?>)
-x = '1'
-x = 1
-]]
-
-TEST 'string' [[
-local x
-x = '1'
-print(<?x?>)
-x = 1
-]]
-
-TEST 'integer' [[
-local x
-x = '1'
-x = 1
-print(<?x?>)
-]]
-
-TEST 'unknown' [[
-local x
-
-function A()
- print(<?x?>)
-end
-]]
-
-TEST 'string' [[
-local x
-
-function A()
- print(<?x?>)
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'string' [[
-local x
-
-x = '1'
-
-function A()
- print(<?x?>)
-end
-
-x = 1
-]]
-
-TEST 'integer' [[
-local x
-
-x = '1'
-x = 1
-
-function A()
- print(<?x?>)
-end
-
-]]
-
-TEST 'boolean' [[
-local x
-
-function A()
- x = true
- print(<?x?>)
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'unknown' [[
-local x
-
-function A()
- x = true
-end
-
-print(<?x?>)
-x = '1'
-x = 1
-]]
-
-TEST 'boolean' [[
-local x
-
-function A()
- x = true
- function B()
- print(<?x?>)
- end
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'table' [[
-local x
-
-function A()
- x = true
- function B()
- x = {}
- print(<?x?>)
- end
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'boolean' [[
-local x
-
-function A()
- x = true
- function B()
- x = {}
- end
- print(<?x?>)
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'unknown' [[
-local x
-
-function A()
- x = true
- function B()
- x = {}
- end
-end
-
-function C()
- print(<?x?>)
-end
-
-x = '1'
-x = 1
-]]
-
-TEST 'integer' [[
-local x
-x = true
-do
- x = 1
-end
-print(<?x?>)
-]]
-
-TEST 'boolean' [[
-local x
-x = true
-function XX()
- do
- x = 1
- end
-end
-print(<?x?>)
-]]
-
-TEST 'integer?' [[
----@type integer?
-local <?x?>
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if <?x?> then
- print(x)
-end
-]]
---[[
-context 0 integer?
-
-save copy 'block'
-save copy 'out'
-push 'block'
-get
-push copy
-truthy
-falsy ref 'out'
-get
-save HEAD 'final'
-push 'out'
-
-push copy HEAD
-merge 'final'
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if x then
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x then
- print(x)
-end
-
-print(<?x?>)
-]]
-
-TEST 'nil' [[
----@type integer?
-local x
-
-if not x then
- print(<?x?>)
-end
-
-print(x)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x then
- x = 1
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if xxx and x then
- print(<?x?>)
-end
-]]
-
-TEST 'unknown' [[
----@type integer?
-local x
-
-if not x and x then
- print(<?x?>)
-end
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if x and not mark[x] then
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if xxx and x then
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if xxx and x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if x ~= nil then
- print(<?x?>)
-end
-
-print(x)
-]]
-
-TEST 'integer|nil' [[
----@type integer?
-local x
-
-if x ~= nil then
- print(x)
-end
-
-print(<?x?>)
-]]
-
-TEST 'nil' [[
----@type integer?
-local x
-
-if x == nil then
- print(<?x?>)
-end
-
-print(x)
-]]
-
-TEST 'integer|nil' [[
----@type integer?
-local x
-
-if x == nil then
- print(x)
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-<?x?> = x or 1
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-<?x?> = x or y
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x then
- goto ANYWHERE
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [=[
-local x
-
-print(<?x?>--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-print(<?io?>--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-print(io.<?open?>--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-local <?x?> = io['open']--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-local <?x?> = 1 + 1--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-local <?x?> = not 1--[[@as integer]])
-]=]
-
-TEST 'integer' [=[
-local <?x?> = ()--[[@as integer]])
-]=]
-
-TEST 'integer?' [[
----@param x? integer
-local function f(<?x?>)
-
-end
-]]
-
-TEST 'integer' [[
-local x = 1
-x = <?x?>
-]]
-
-TEST 'integer?' [[
----@class A
----@field x? integer
-local t
-
-t.<?x?>
-]]
-
-TEST 'integer?' [[
----@type { x?: integer }
-local t
-
-t.<?x?>
-]]
-
-TEST 'boolean' [[
----@class A
----@field [integer] boolean
-local t
-
-local <?x?> = t[1]
-]]
-
-TEST 'unknown' [[
-local <?x?> = y and z
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-assert(x)
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-assert(x ~= nil)
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer | nil
-local x
-
-assert(x)
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer | nil
-local x
-
-assert(x ~= nil)
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
-local x
-
-assert(x == 1)
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if x and <?x?>.y then
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x and x.y then
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x and x.y then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x or <?x?>.y then
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if not x or x.y then
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x or x.y then
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x.y or x then
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-if x.y or not x then
- print(<?x?>)
-end
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not x or not y then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if not y or not x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-while true do
- if not x then
- break
- end
- print(<?x?>)
-end
-]]
-
-TEST 'integer?' [[
----@type integer?
-local x
-
-while true do
- if not x then
- break
- end
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-while x do
- print(<?x?>)
-end
-]]
-
-TEST 'integer' [[
----@type fun():integer?
-local iter
-
-for <?x?> in iter do
-end
-]]
-
-TEST 'integer' [[
-local x
-
----@type integer
-<?x?> = XXX
-]]
-
-TEST 'unknown' [[
-for _ = 1, 999 do
- local <?x?>
-end
-]]
-
-TEST 'integer' [[
-local x
-
----@cast x integer
-
-print(<?x?>)
-]]
-
-TEST 'unknown' [[
-local x
-
----@cast x integer
-
-local x
-print(<?x?>)
-]]
-
-TEST 'unknown' [[
-local x
-
-if true then
- local x
- ---@cast x integer
- print(x)
-end
-
-print(<?x?>)
-]]
-
-TEST 'boolean|integer' [[
-local x = 1
-
----@cast x +boolean
-
-print(<?x?>)
-]]
-
-TEST 'boolean' [[
----@type integer|boolean
-local x
-
----@cast x -integer
-
-print(<?x?>)
-]]
-
-TEST 'boolean?' [[
----@type boolean
-local x
-
----@cast x +?
-
-print(<?x?>)
-]]
-
-TEST 'boolean' [[
----@type boolean?
-local x
-
----@cast x -?
-
-print(<?x?>)
-]]
-
-TEST 'nil' [[
----@type string?
-local x
-
-if x then
- return
-else
- print(<?x?>)
-end
-
-print(x)
-]]
-
-TEST 'string' [[
----@type string?
-local x
-
-if not x then
- return
-else
- print(<?x?>)
-end
-
-print(x)
-]]
-
-TEST 'string' [[
----@type string?
-local x
-
-if not x then
- return
-else
- print(x)
-end
-
-print(<?x?>)
-]]
-
-TEST 'true' [[
----@type boolean | nil
-local x
-
-if not x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'true' [[
----@type boolean
-local t
-
-if t then
- print(<?t?>)
- return
-end
-
-print(t)
-]]
-
-TEST 'false' [[
----@type boolean
-local t
-
-if t then
- print(t)
- return
-end
-
-print(<?t?>)
-]]
-
-TEST 'nil' [[
----@type integer?
-local t
-
-if t then
-else
- print(<?t?>)
-end
-
-print(t)
-]]
-
-TEST 'table' [[
-local function f()
- if x then
- return y
- end
- return {}
-end
-
-local <?z?> = f()
-]]
-
-TEST 'integer|table' [[
-local function returnI()
- return 1
-end
-
-local function f()
- if x then
- return returnI()
- end
- return {}
-end
-
-local <?z?> = f()
-]]
-
-TEST 'number' [[
-for _ in _ do
- ---@type number
- local <?x?>
-end
-]]
-
-TEST 'unknown' [[
-for _ in _ do
- ---@param x number
- local <?x?>
-end
-]]
-
-TEST 'unknown' [[
----@type number
-for <?x?> in _ do
-end
-]]
-
-TEST 'number' [[
----@param x number
-for <?x?> in _ do
-end
-]]
-
-TEST 'table' [[
----@alias tp table
-
----@type tp
-local <?x?>
-]]
-
-TEST '{ name: boolean }' [[
----@alias tp {name: boolean}
-
----@type tp
-local <?x?>
-]]
-
-TEST 'boolean|{ name: boolean }' [[
----@alias tp boolean | {name: boolean}
-
----@type tp
-local <?x?>
-]]
-
-TEST '`1`|`true`' [[
----@type `1` | `true`
-local <?x?>
-]]
-
-TEST 'function' [[
-local x
-
-function x() end
-
-print(<?x?>)
-]]
-
-TEST 'unknown' [[
-local x
-
-if x.field == 'haha' then
- print(<?x?>)
-end
-]]
-
-TEST 'string' [[
----@type string?
-local t
-
-if not t or xxx then
- return
-end
-
-print(<?t?>)
-]]
-
-TEST 'table' [[
----@type table|nil
-local t
-
-return function ()
- if not t then
- return
- end
-
- print(<?t?>)
-end
-]]
-
-TEST 'table' [[
----@type table|nil
-local t
-
-f(function ()
- if not t then
- return
- end
-
- print(<?t?>)
-end)
-]]
-
-TEST 'table' [[
----@type table?
-local t
-
-t = t or {}
-
-print(<?t?>)
-]]
-
-TEST 'unknown|nil' [[
-local x
-
-if x == nil then
-end
-
-print(<?x?>)
-]]
-
-TEST 'table<xxx, true>' [[
----@alias xxx table<xxx, true>
-
----@type xxx
-local <?t?>
-]]
-
-TEST 'xxx[][]' [[
----@alias xxx xxx[]
-
----@type xxx
-local <?t?>
-]]
-
-TEST 'fun(x: fun(x: xxx))' [[
----@alias xxx fun(x: xxx)
-
----@type xxx
-local <?t?>
-]]
-
-TEST 'table' [[
----@type table|nil
-local t
-
-while t do
- print(<?t?>)
-end
-]]
-
-TEST 'table|nil' [[
----@type table|nil
-local t
-
-while <?t?> do
- print(t)
-end
-]]
-
-TEST 'table' [[
----@type table|nil
-local t
-
-while t ~= nil do
- print(<?t?>)
-end
-]]
-
-TEST 'table|nil' [[
----@type table|nil
-local t
-
-while <?t?> ~= nil do
- print(t)
-end
-]]
-
-TEST 'integer' [[
----@type integer?
-local n
-
-if not n then
- error('n is nil')
-end
-
-print(<?n?>)
-]]
-
-TEST 'integer' [[
----@type integer?
-local n
-
-if not n then
- os.exit()
-end
-
-print(<?n?>)
-]]
-
-TEST 'table' [[
----@type table?
-local n
-
-print((n and <?n?>.x))
-]]
-
-TEST 'table' [[
----@type table?
-local n
-
-n = n and <?n?>.x or 1
-]]
-
-TEST 'table' [[
----@type table?
-local n
-
-n = ff[n and <?n?>.x]
-]]
-
-TEST 'integer' [[
-local x
-
-if type(x) == 'integer' then
- print(<?x?>)
-end
-]]
-
-TEST 'boolean|integer' [[
-local x
-
-if type(x) == 'integer'
-or type(x) == 'boolean' then
- print(<?x?>)
-end
-]]
-
-TEST 'fun()' [[
----@type fun()?
-local x
-
-if type(x) == 'function' then
- print(<?x?>)
-end
-]]
-
-TEST 'function' [[
-local x
-
-if type(x) == 'function' then
- print(<?x?>)
-end
-]]
-
-TEST 'integer' [[
-local x
-local tp = type(x)
-
-if tp == 'integer' then
- print(<?x?>)
-end
-]]
-
-TEST 'integer' [[
----@type integer?
-local x
-
-if (x == nil) then
-else
- print(<?x?>)
-end
-]]
-
-TEST 'B' [[
----@class A
----@class B
-
----@type A
-local x
-
----@type B
-x = call(x)
-
-print(<?x?>)
-]]
-
-TEST 'nil' [[
-local function f()
-end
-
-local <?x?> = f()
-]]
-
-TEST 'integer[]' [[
----@type integer[]
-local x
-if not x then
- return
-end
-
-print(<?x?>)
-]]
-
-TEST 'unknown' [[
----@type string[]
-local t
-
-local <?x?> = t.x
-]]
-
-TEST 'integer|unknown' [[
-local function f()
- return GG
-end
-
-local t
-
-t.x = 1
-t.x = f()
-
-print(t.<?x?>)
-]]
-
-TEST 'integer' [[
-local function f()
- if X then
- return X
- else
- return 1
- end
-end
-
-local <?n?> = f()
-]]
-
-TEST 'unknown' [[
-local function f()
- return t[k]
-end
-
-local <?n?> = f()
-]]
-
-TEST 'integer|nil' [[
-local function f()
- if x then
- return
- else
- return 1
- end
-end
-
-local <?n?> = f()
-]]
-
-TEST 'integer' [[
----@class A
----@field x integer
-local m
-
-m.<?x?> = true
-
-print(m.x)
-]]
-
-TEST 'integer' [[
----@class A
----@field x integer
-local m
-
-m.x = true
-
-print(m.<?x?>)
-]]
-
-TEST 'integer' [[
----@class A
----@field x integer --> 1st
-local m = {
- x = '' --> 2nd
-}
-
----@type boolean
-m.x = true --> 3rd (with ---@type above)
-
-m.x = {} --> 4th
-
-print(m.<?x?>)
-]]
-
-TEST 'string' [[
----@class A
-----@field x integer --> 1st
-local m = {
- x = '' --> 2nd
-}
-
----@type boolean
-m.x = true --> 3rd (with ---@type above)
-
-m.x = {} --> 4th
-
-print(m.<?x?>)
-]]
-
-TEST 'boolean' [[
----@class A
-----@field x integer --> 1st
-local m = {
- --x = '' --> 2nd
-}
-
----@type boolean
-m.x = true --> 3rd (with ---@type above)
-
-m.x = {} --> 4th
-
-print(m.<?x?>)
-]]
-
-TEST 'table' [[
----@class A
-----@field x integer --> 1st
-local m = {
- --x = '' --> 2nd
-}
-
----@type boolean
---m.x = true --> 3rd (with ---@type above)
-
-m.x = {} --> 4th
-
-print(m.<?x?>)
-]]
-
-TEST 'boolean?' [[
----@generic T
----@param x T
----@return T
-local function echo(x) end
-
----@type boolean?
-local b
-
-local <?x?> = echo(b)
-]]
-
-TEST 'boolean' [[
----@generic T
----@param x T?
----@return T
-local function echo(x) end
-
----@type boolean?
-local b
-
-local <?x?> = echo(b)
-]]
-
-TEST 'boolean' [[
----@generic T
----@param x? T
----@return T
-local function echo(x) end
-
----@type boolean?
-local b
-
-local <?x?> = echo(b)
-]]
-
-TEST 'boolean' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
-local <?x?> = f()
-]]
-
-TEST 'number' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
-local <?x?> = f(1)
-]]
-
-TEST 'boolean' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
-function r0()
- return
-end
-
-local <?x?> = f(r0())
-]]
-
-TEST 'number' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
-function r1()
- return 1
-end
-
-local <?x?> = f(r1())
-]]
-
-TEST 'boolean' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
----@type fun()
-local r0
-
-local <?x?> = f(r0())
-]]
-
-TEST 'number' [[
----@overload fun():boolean
----@param x integer
----@return number
-function f(x)
-end
-
----@type fun():integer
-local r1
-
-local <?x?> = f(r1())
-]]
-
-TEST 'boolean' [[
----@overload fun(x: number, y: number):string
----@overload fun(x: number):number
----@return boolean
-local function f() end
-
-local <?n1?> = f()
-local n2 = f(0)
-local n3 = f(0, 0)
-]]
-
-TEST 'number' [[
----@overload fun(x: number, y: number):string
----@overload fun(x: number):number
----@return boolean
-local function f() end
-
-local n1 = f()
-local <?n2?> = f(0)
-local n3 = f(0, 0)
-]]
-
-TEST 'string' [[
----@overload fun(x: number, y: number):string
----@overload fun(x: number):number
----@return boolean
-local function f() end
-
-local n1 = f()
-local n2 = f(0)
-local <?n3?> = f(0, 0)
-]]
-
-TEST 'boolean' [[
----@type {[integer]: boolean, xx: integer}
-local t
-
-local <?n?> = t[1]
-]]
-
-TEST 'boolean' [[
----@type integer
-local i
-
----@type {[integer]: boolean, xx: integer}
-local t
-
-local <?n?> = t[i]
-]]
-
-TEST 'string' [=[
-local x = true
-local y = x--[[@as integer]] --is `integer` here
-local z = <?x?>--[[@as string]] --is `true` here
-]=]
-
-TEST 'integer' [[
----@type integer
-local x
-
-if type(x) == 'number' then
- print(<?x?>)
-end
-]]
-
-TEST 'boolean' [[
----@class A
----@field [integer] boolean
-local mt
-
-function mt:f()
- ---@type integer
- local index
- local <?x?> = self[index]
-end
-]]
-
-TEST 'boolean' [[
----@class A
----@field [B] boolean
-
----@class B
-
----@type A
-local a
-
----@type B
-local b
-
-local <?x?> = a[b]
-]]
-
-TEST 'number' [[
----@type {x: string ; y: boolean; z: number}
-local t
-
-local <?z?> = t.z
-]]
-
-TEST 'fun():number, boolean' [[
----@type {f: fun():number, boolean}
-local t
-
-local <?f?> = t.f
-]]
-
-TEST 'fun():number' [[
----@type {(f: fun():number), x: boolean}
-local t
-
-local <?f?> = t.f
-]]
-
-TEST 'boolean' [[
----@param ... boolean
-local function f(...)
- local <?n?> = ...
-end
-]]
-
-TEST 'boolean' [[
----@param ... boolean
-local function f(...)
- local _, <?n?> = ...
-end
-]]
-
-TEST 'boolean' [[
----@return boolean ...
-local function f() end
-
-local <?n?> = f()
-]]
-
-TEST 'boolean' [[
----@return boolean ...
-local function f() end
-
-local _, <?n?> = f()
-]]
-
-TEST 'boolean' [[
----@type fun():name1: boolean, name2:number
-local f
-
-local <?n?> = f()
-]]
-
-TEST 'number' [[
----@type fun():name1: boolean, name2:number
-local f
-
-local _, <?n?> = f()
-]]
-TEST 'boolean' [[
----@type fun():(name1: boolean, name2:number)
-local f
-
-local <?n?> = f()
-]]
-
-TEST 'number' [[
----@type fun():(name1: boolean, name2:number)
-local f
-
-local _, <?n?> = f()
-]]
-
-TEST 'boolean' [[
----@type fun():...: boolean
-local f
-
-local _, <?n?> = f()
-]]
-
-TEST 'string' [[
-local s
-while true do
- s = ''
-end
-print(<?s?>)
-]]
-
-TEST 'string' [[
-local s
-for _ in _ do
- s = ''
-end
-print(<?s?>)
-]]
-
-TEST 'A' [[
----@class A: string
-
----@type A
-local <?s?> = ''
-]]
-
-TEST 'number' [[
----@return number
-local function f() end
-local x, <?y?> = 1, f()
-]]
-
-TEST 'boolean' [[
----@return number, boolean
-local function f() end
-local x, y, <?z?> = 1, f()
-]]
-
-TEST 'number' [[
----@return number, boolean
-local function f() end
-local x, y, <?z?> = 1, 2, f()
-]]
-
-TEST 'unknown' [[
-local f
-
-print(<?f?>)
-
-function f() end
-]]
-
-TEST 'unknown' [[
-local f
-
-do
- print(<?f?>)
-end
-
-function f() end
-]]
-
-TEST 'function' [[
-local f
-
-function A()
- print(<?f?>)
-end
-
-function f() end
-]]
-
-TEST 'number' [[
----@type number|nil
-local n
-
-local t = {
- x = n and <?n?>,
-}
-]]
-
-TEST 'table' [[
----@type table?
-local n
-
-if not n or not <?n?>.x then
-end
-]]
-
-TEST 'table' [[
----@type table?
-local n
-
-if not n or not <?n?>[1] then
-end
-]]
-
-TEST 'number' [[
----@type number|false
-local n
-
----@cast n -false
-
-print(<?n?>)
-]]
-
-TEST 'table' [[
----@type number|table
-local n
-
-if n
----@cast n table
-and <?n?>.type == 'xxx' then
-end
-]]
-
-TEST 'integer' [[
----@type integer?
-local n
-if true then
- n = 0
-end
-local <?x?> = n or 0
-]]
-
-TEST 'number' [=[
-local <?x?> = F()--[[@as number]]
-]=]
-
-TEST 'number' [=[
-local function f()
- return F()--[[@as number]]
-end
-
-local <?x?> = f()
-]=]
-
-TEST 'number' [=[
-local <?x?> = X --[[@as number]]
-]=]
-
-TEST 'number' [[
----@return number?, number?
-local function f() end
-
-for <?x?>, y in f do
-end
-]]
-
-TEST 'number' [[
----@return number?, number?
-local function f() end
-
-for x, <?y?> in f do
-end
-]]
-
-TEST 'number|nil' [[
----@type table|nil
-local a
-
----@type number|nil
-local b
-
-local <?c?> = a and b
-]]
-
-TEST 'number|table|nil' [[
----@type table|nil
-local a
-
----@type number|nil
-local b
-
-local <?c?> = a or b
-]]
-
-TEST 'number|table|nil' [[
----@type table|nil
-local a
-
----@type number|nil
-local b
-
-local c = a and b
-local <?d?> = a or b
-]]
-
-TEST 'number' [[
-local x
-
----@return number
-local function f()
-end
-
-x = f()
-
-print(<?x?>)
-]]
-
-TEST 'number' [[
-local x
-
----@return number
-local function f()
-end
-
-_, x = pcall(f)
-
-print(<?x?>)
-]]
-
-TEST 'string' [[
----@type table<string|number, string>
-local t
-
----@type number
-local n
----@type string
-local s
-
-local <?test?> = t[n]
-local test2 = t[s] --test and test2 are unknow
-]]
-
-TEST 'string' [[
----@type table<string|number, string>
-local t
-
----@type number
-local n
----@type string
-local s
-
-local test = t[n]
-local <?test2?> = t[s] --test and test2 are unknow
-]]
-
-TEST 'table<number, boolean>' [[
----@type table<number, boolean>
-local t
-
-<?t?> = {}
-]]
-
-TEST 'integer' [[
----@type integer[]|A
-local t
-
-local <?x?> = t[1]
-]]
-
-TEST 'integer' [[
----@type integer
----@diagnostic disable
-local <?t?>
-]]
-
-TEST 'A' [[
----@class A
----@diagnostic disable
-local <?t?>
-]]
-
-TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[
----@type {[string]: number, [true]: string, [1]: boolean, tag: integer}
-local <?t?>
-]]
-
-TEST 'unknown' [[
-local mt = {}
-mt.<?x?> = nil
-]]
-
-TEST 'unknown' [[
-mt = {}
-mt.<?x?> = nil
-]]
-
-TEST 'A' [[
----@class A
----@operator unm: A
-
----@type A
-local a
-local <?b?> = -a
-]]
-
-TEST 'A' [[
----@class A
----@operator bnot: A
-
----@type A
-local a
-local <?b?> = ~a
-]]
-
-TEST 'A' [[
----@class A
----@operator len: A
-
----@type A
-local a
-local <?b?> = #a
-]]
-
-TEST 'A' [[
----@class A
----@operator add: A
-
----@type A
-local a
-local <?b?> = a + 1
-]]
-
-TEST 'A' [[
----@class A
----@operator sub: A
-
----@type A
-local a
-local <?b?> = a - 1
-]]
-
-TEST 'A' [[
----@class A
----@operator mul: A
-
----@type A
-local a
-local <?b?> = a * 1
-]]
-
-TEST 'A' [[
----@class A
----@operator div: A
-
----@type A
-local a
-local <?b?> = a / 1
-]]
-
-TEST 'A' [[
----@class A
----@operator mod: A
-
----@type A
-local a
-local <?b?> = a % 1
-]]
-
-TEST 'A' [[
----@class A
----@operator pow: A
-
----@type A
-local a
-local <?b?> = a ^ 1
-]]
-
-TEST 'A' [[
----@class A
----@operator idiv: A
-
----@type A
-local a
-local <?b?> = a // 1
-]]
-
-TEST 'A' [[
----@class A
----@operator band: A
-
----@type A
-local a
-local <?b?> = a & 1
-]]
-
-TEST 'A' [[
----@class A
----@operator bor: A
-
----@type A
-local a
-local <?b?> = a | 1
-]]
-
-TEST 'A' [[
----@class A
----@operator bxor: A
-
----@type A
-local a
-local <?b?> = a ~ 1
-]]
-
-TEST 'A' [[
----@class A
----@operator shl: A
-
----@type A
-local a
-local <?b?> = a << 1
-]]
-
-TEST 'A' [[
----@class A
----@operator shr: A
-
----@type A
-local a
-local <?b?> = a >> 1
-]]
-
-TEST 'A' [[
----@class A
----@operator concat: A
-
----@type A
-local a
-local <?b?> = a .. 1
-]]
-
-TEST 'A' [[
----@class A
----@operator add(boolean): boolean
----@operator add(integer): A
-
----@type A
-local a
-local <?b?> = a + 1
-]]
-
-TEST 'boolean' [[
----@class A
----@operator add(boolean): boolean
----@operator add(integer): A
-
----@type A
-local a
-local <?b?> = a + true
-]]
-
-TEST 'A' [[
----@class A
----@operator call: A
-
----@type A
-local a
-local <?b?> = a()
-]]
-
-TEST 'A' [[
----@class A
----@operator call: A
-
----@type A
-local a
-
-local t = {
- <?x?> = a(),
-}
-]]
-
-TEST 'boolean' [[
----@class A
----@field n number
----@field [string] boolean
-local t
-
-local <?x?> = t.xx
-]]
-
-TEST 'number' [[
----@class A
----@field n number
----@field [string] boolean
-local t
-
-local <?x?> = t.n
-]]
-
-TEST 'string' [[
----@class string
----@operator mod: string
-
-local <?b?> = '' % 1
-]]
-
-TEST 'string|integer' [[
----@type boolean
-local bool
-
-local <?x?> = bool and '' or 0
-]]
-
-TEST 'string|integer' [[
-local bool
-
-if X then
- bool = true
-else
- bool = false
-end
-
-local <?x?> = bool and '' or 0
-]]
-
-TEST 'boolean' [[
----@type boolean|true|false
-local <?b?>
-]]
-
-TEST 'integer|false' [[
-local <?b?> = X == 1 and X == 1 and 1
-]]
-
-TEST 'unknown|nil' [[
-local function f()
- if X then
- return ({})[1]
- end
- return nil
-end
-
-local <?n?> = f()
-]]
-
-TEST 'integer' [[
----@generic T
----@vararg T # ERROR
----@return T
-local function test(...)
- return ...
-end
-
-local <?n?> = test(1)
-]]
-
-TEST 'boolean' [[
----@type boolean, number
-local <?x?>, y
-]]
-
-TEST 'number' [[
----@type boolean, number
-local x, <?y?>
-]]
-
-TEST 'unknown' [[
----@type _, number
-local <?x?>, y
-]]
-
-TEST 'number[]' [[
-local t
----@cast t number[]?
-
-local x = t and <?t?>[i]
-]]
-
-TEST 'number?' [[
----@type number[]?
-local t
-
-local <?x?> = t and t[i]
-]]
-
-TEST 'number' [[
----@type number
-local x
-
-if not <?x?>.y then
- x = nil
-end
-]]
-
-TEST 'number' [[
----@type number|nil
-local x
-while x == nil do
- if x == nil then
- return
- end
-
- x = nil
-end
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
-local A = {
- ---@class XXX
- B = {}
-}
-
-A.B.C = 1
-
-print(A.B.<?C?>)
-]]
-
-TEST '-2|-3|1' [[
----@type 1|-2|-3
-local <?n?>
-]]
-
-TEST 'table' [[
----@enum A
-local m = {}
-
-print(<?m?>)
-]]
-
-TEST 'A' [[
----@class A
----@overload fun():A
-local m = {}
-
----@return A
-function m:init()
- return <?self?>
-end
-]]
-
-TEST 'string' [[
----@vararg string
-function F(...)
- local t = {...}
- for k, <?v?> in pairs(t) do
- end
-end
-]]
-
-TEST 'string' [[
----@vararg string
-function F(...)
- local t = {...}
- for k, <?v?> in ipairs(t) do
- end
-end
-]]
-
-TEST 'integerA' [[
----@type integerA
-for <?i?> = 1, 10 do
-end
-]]
-
-TEST 'string' [[
----@class A
----@field x string
-
----@class B : A
-local t = {}
-
-t.x = t.x
-
-print(t.<?x?>)
-]]
-
-TEST 'unknown' [[
-local t = {
- x = 1,
-}
-
-local x
-
-local <?v?> = t[x]
-]]
-
-TEST 'A|B' [[
----@class A
----@class B: A
-
----@type A|B
-local <?t?>
-]]
-
-TEST 'function' [[
----@class myClass
-local myClass = { has = { nested = {} } }
-
-function myClass.has.nested.fn() end
-
----@type myClass
-local class
-
-class.has.nested.<?fn?>()
-]]
-
-TEST 'integer[]' [[
----@generic T
----@param f fun(x: T)
----@return T[]
-local function x(f) end
-
----@param x integer
-local <?arr?> = x(function (x) end)
-]]
-
-TEST 'integer[]' [[
----@generic T
----@param f fun():T
----@return T[]
-local function x(f) end
-
-local <?arr?> = x(function ()
- return 1
-end)
-]]
-
-TEST 'integer[]' [[
----@generic T
----@param f fun():T
----@return T[]
-local function x(f) end
-
----@return integer
-local <?arr?> = x(function () end)
-]]
-
-TEST 'integer[]' [[
----@generic T
----@param f fun(x: T)
----@return T[]
-local function x(f) end
-
----@type fun(x: integer)
-local cb
-
-local <?arr?> = x(cb)
-]]
-
-TEST 'integer[]' [[
----@generic T
----@param f fun():T
----@return T[]
-local function x(f) end
-
----@type fun(): integer
-local cb
-
-local <?arr?> = x(cb)
-]]
-
-TEST 'integer' [[
----@return fun(x: integer)
-local function f()
- return function (<?x?>)
- end
-end
-]]
-
-TEST 'string' [[
----@class A
----@field f fun(x: string)
-
----@type A
-local t = {
- f = function (<?x?>) end
-}
-]]
-
-config.set(nil, 'Lua.runtime.special', {
- ['xx.assert'] = 'assert'
-})
-
-TEST 'number' [[
----@type number?
-local t
-
-xx.assert(t)
-
-print(<?t?>)
-]]
-
-config.set(nil, 'Lua.runtime.special', nil)
-
-TEST 'A' [[
----@class A
-local mt
-
----@return <?self?>
-function mt:init()
-end
-]]
-
-TEST 'A' [[
----@class A
-local mt
-
----@return self
-function mt:init()
-end
-
-local <?o?> = mt:init()
-]]
-
-TEST 'A' [[
----@class A
----@field x <?self?>
-]]
-
-TEST 'A' [[
----@class A
----@field x self
-
----@type A
-local o
-
-print(o.<?x?>)
-]]
-
-TEST 'A' [[
----@class A
----@overload fun(): self
-local A
-
-local <?o?> = A()
-]]
-
-TEST 'number' [[
----@type table<'Test1', fun(x: number)>
-local t = {
- ["Test1"] = function(<?x?>) end,
-}
-]]
-
-TEST 'number' [[
----@type table<5, fun(x: number)>
-local t = {
- [5] = function(<?x?>) end,
-}
-]]
-
-TEST 'number' [[
----@type fun(x: number)
-local function f(<?x?>) end
-]]
-
-TEST 'boolean' [[
----@generic T: string | boolean | table
----@param x T
----@return T
-local function f(x)
- return x
-end
-
-local <?x?> = f(true)
-]]
-
-TEST 'number' [[
----@class A
----@field [1] number
----@field [2] boolean
-local t
-
-local <?n?> = t[1]
-]]
-
-TEST 'boolean' [[
----@class A
----@field [1] number
----@field [2] boolean
-local t
-
-local <?n?> = t[2]
-]]
-
-TEST 'N' [[
----@class N: number
-local x
-
-if x == 0.1 then
- print(<?x?>)
-end
-]]
-
-TEST 'vec3' [[
----@class mat4
----@operator mul(vec3): vec3 -- matrix * vector
----@operator mul(number): mat4 -- matrix * constant
-
----@class vec3: number
-
----@type mat4, vec3
-local m, v
-
-local <?r?> = m * v
-]]
-
-TEST 'mat4' [[
----@class mat4
----@operator mul(number): mat4 -- matrix * constant
----@operator mul(vec3): vec3 -- matrix * vector
-
----@class vec3: number
-
----@type mat4, vec3
-local m, v
-
-local <?r?> = m * v
-]]
-
-TEST 'A|B' [[
----@class A
----@class B
-
----@type A|B
-local t
-
-if x then
- ---@cast t A
-else
- print(<?t?>)
-end
-]]
-
-TEST 'A|B' [[
----@class A
----@class B
-
----@type A|B
-local t
-
-if x then
- ---@cast t A
-elseif <?t?> then
-end
-]]
-
-TEST 'A|B' [[
----@class A
----@class B
-
----@type A|B
-local t
-
-if x then
- ---@cast t A
- print(t)
-elseif <?t?> then
-end
-]]
-
-TEST 'A|B' [[
----@class A
----@class B
-
----@type A|B
-local t
-
-if x then
- ---@cast t A
- print(t)
-elseif <?t?> then
- ---@cast t A
- print(t)
-end
-]]
-
-TEST 'function' [[
-local function x()
- print(<?x?>)
-end
-]]
-
-TEST 'number' [[
----@type number?
-local x
-
-do
- if not x then
- return
- end
-end
-
-print(<?x?>)
-]]
-
-TEST 'number' [[
----@type number[]
-local xs
-
----@type fun(x): number?
-local f
-
-for _, <?x?> in ipairs(xs) do
- x = f(x)
-end
-]]
-
-TEST 'number' [[
----@type number?
-X = Y
-
-if X then
- print(<?X?>)
-end
-]]
-
-TEST 'number' [[
----@type number|boolean
-X = Y
-
-if type(X) == 'number' then
- print(<?X?>)
-end
-]]
-
-TEST 'boolean' [[
----@type number|boolean
-X = Y
-
-if type(X) ~= 'number' then
- print(<?X?>)
-end
-]]
-
-TEST 'boolean' [[
----@type number
-X = Y
-
----@cast X boolean
-
-print(<?X?>)
-]]
-
-TEST 'number' [[
----@type number
-local t
-
-if xxx == <?t?> then
- print(t)
-end
-]]
-
-TEST 'V' [[
----@class V
-X = 1
-
-print(<?X?>)
-]]
-
-TEST 'V' [[
----@class V
-X.Y = 1
-
-print(X.<?Y?>)
-]]
-
-TEST 'integer' [[
-local x = {}
-
-x.y = 1
-local y = x.y
-x.y = nil
-
-print(<?y?>)
-]]
-
-TEST 'function' [[
-function X()
- <?Y?>()
-end
-
-function Y()
-end
-]]
-
-TEST 'A_Class' [[
----@class A_Class
-local A = { x = 5 }
-
-function A:func()
- for i = 1, <?self?>.x do
- print(i)
- end
-
- self.y = 3
- self.y = self.y + 3
-end
-]]
-
-TEST 'number' [[
----@type number?
-local n
-local <?v?> = n or error('')
-]]
-
-TEST 'Foo' [[
----@class Foo
----@operator mul(Foo): Foo
----@operator mul(Bar): Foo
----@class Bar
-
----@type Foo
-local foo
-
----@type Foo|Bar
-local fooOrBar
-
-local <?b?> = foo * fooOrBar
-]]
-
-TEST 'number' [[
-local a = 4;
-local b = 2;
-
-local <?c?> = a / b;
-]]
-
-TEST 'string' [[
-local a = '4';
-local b = '2';
-
-local <?c?> = a .. b;
-]]
-
-TEST 'number|{ [1]: string }' [[
----@alias Some
----| { [1]: string }
----| number
-
-local x ---@type Some
-
-print(<?x?>)
-]]
-
-TEST 'integer' [[
----@class metatable : table
----@field __index table
-
----@param table table
----@param metatable? metatable
----@return table
-function setmetatable(table, metatable) end
-
-local m = setmetatable({},{ __index = { a = 1 } })
-
-m.<?a?>
-]]
-
-TEST 'integer' [[
----@class metatable : table
----@field __index table
-
----@param table table
----@param metatable? metatable
----@return table
-function setmetatable(table, metatable) end
-
-local mt = {a = 1 }
-local m = setmetatable({},{ __index = mt })
-
-m.<?a?>
-]]
-
-TEST 'integer' [[
-local x = 1
-repeat
-until <?x?>
-]]
-
--- #2144
-TEST 'A' [=[
-local function f()
- return {} --[[@as A]]
-end
-
-local <?x?> = f()
-]=]
-
-TEST 'A' [=[
-local function f()
- ---@type A
- return {}
-end
-
-local <?x?> = f()
-]=]
-
-TEST 'boolean|number' [[
----@alias A number
----@alias(partial) A boolean
-
----@type A
-local <?x?>
-]]
+require 'type_inference.common'
+require 'type_inference.param_match'
diff --git a/test/type_inference/param_match.lua b/test/type_inference/param_match.lua
new file mode 100644
index 00000000..8ead05ef
--- /dev/null
+++ b/test/type_inference/param_match.lua
@@ -0,0 +1,139 @@
+
+TEST 'boolean' [[
+---@overload fun(x: number, y: number):string
+---@overload fun(x: number):number
+---@return boolean
+local function f() end
+
+local <?n1?> = f()
+local n2 = f(0)
+local n3 = f(0, 0)
+]]
+
+TEST 'number' [[
+---@overload fun(x: number, y: number):string
+---@overload fun(x: number):number
+---@return boolean
+local function f() end
+
+local n1 = f()
+local <?n2?> = f(0)
+local n3 = f(0, 0)
+]]
+
+TEST 'string' [[
+---@overload fun(x: number, y: number):string
+---@overload fun(x: number):number
+---@return boolean
+local function f() end
+
+local n1 = f()
+local n2 = f(0)
+local <?n3?> = f(0, 0)
+]]
+
+TEST 'boolean' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+local <?x?> = f()
+]]
+
+TEST 'number' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+local <?x?> = f(1)
+]]
+
+TEST 'boolean' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+function r0()
+ return
+end
+
+local <?x?> = f(r0())
+]]
+
+TEST 'number' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+function r1()
+ return 1
+end
+
+local <?x?> = f(r1())
+]]
+
+TEST 'boolean' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+---@type fun()
+local r0
+
+local <?x?> = f(r0())
+]]
+
+TEST 'number' [[
+---@overload fun():boolean
+---@param x integer
+---@return number
+function f(x)
+end
+
+---@type fun():integer
+local r1
+
+local <?x?> = f(r1())
+]]
+
+TEST '1' [[
+---@overload fun(a: 'x'): 1
+---@overload fun(a: 'y'): 2
+local function f(...) end
+
+local <?r?> = f('x')
+]]
+
+TEST '2' [[
+---@overload fun(a: 'x'): 1
+---@overload fun(a: 'y'): 2
+local function f(...) end
+
+local <?r?> = f('y')
+]]
+
+TEST '1' [[
+---@overload fun(a: boolean): 1
+---@overload fun(a: number): 2
+local function f(...) end
+
+local <?r?> = f(true)
+]]
+
+TEST '2' [[
+---@overload fun(a: boolean): 1
+---@overload fun(a: number): 2
+local function f(...) end
+
+local <?r?> = f(10)
+]]