diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 01b377ca1..b6db21ca3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,7 @@ on: push: branches: - master + - 'v*.*.*' tags: - "*" pull_request: @@ -25,7 +26,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,27 +35,48 @@ 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: | apk update apk add git ninja bash build-base nodejs linux-headers + - name: Prepare for Linux + if: ${{ matrix.target == 'linux' && matrix.libc != 'musl' }} + run: | + sudo apt update + sudo apt install ninja-build + - uses: actions/checkout@v4 with: submodules: recursive - - name: Build for others step-1 - if: ${{ matrix.libc != 'musl' }} - uses: actboy168/setup-luamake@master + - name: Build for Windows + if: ${{ matrix.target == 'windows' }} + run: .\make.bat ${{ matrix.platform }} - - name: Build for others step-2 - if: ${{ matrix.libc != 'musl' }} - run: luamake -platform ${{ matrix.platform }} + - name: Build for Linux + if: ${{ matrix.target == 'linux' }} + run: | + ./make.sh ${{ matrix.platform }} + + - name: Build for macOS + if: ${{ matrix.target == 'darwin' }} + run: | + brew install ninja + ./make.sh ${{ matrix.platform }} - - name: Build for musl - if: ${{ matrix.target == 'linux' && 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8ff3632d..12e614e5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,13 +6,25 @@ jobs: fail-fast: false matrix: include: - - { os: ubuntu-20.04, platform: linux-x64 } - - { os: macos-14, platform: darwin-arm64 } - - { os: windows-latest, platform: win32-x64 } + - { os: ubuntu-20.04, target: linux, platform: linux-x64 } + - { os: macos-14, target: darwin, platform: darwin-arm64 } + - { os: windows-latest, target: windows, platform: win32-x64 } runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 with: submodules: recursive - - uses: actboy168/setup-luamake@master - - run: luamake -platform ${{ matrix.platform }} + - name: Build for Windows + if: ${{ matrix.target == 'windows' }} + run: .\make.bat + - name: Build for Linux + if: ${{ matrix.target == 'linux' }} + run: | + sudo apt update + sudo apt install ninja-build + ./make.sh + - name: Build for macOS + if: ${{ matrix.target == 'darwin' }} + run: | + brew install ninja + ./make.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..ec57d0fee --- /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 8f19fd0a4..97b1cda77 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,22 @@ # changelog +## 3.9.1 +`2024-5-14` +* revert extension runtime + +## 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 f57d4d64e..8dd8c059e 100644 --- a/doc/en-us/config.md +++ b/doc/en-us/config.md @@ -406,6 +406,22 @@ Array [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array +``` + +## 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 7add2c982..7ee6c2ea1 100644 --- a/doc/pt-br/config.md +++ b/doc/pt-br/config.md @@ -406,6 +406,22 @@ Array [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array +``` + +## 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 ebb8325f4..e7292b929 100644 --- a/doc/zh-cn/config.md +++ b/doc/zh-cn/config.md @@ -406,6 +406,22 @@ Array [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus 批量修改一个组中的文件状态。 diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md index 8b01d78cc..5e491a516 100644 --- a/doc/zh-tw/config.md +++ b/doc/zh-tw/config.md @@ -406,6 +406,22 @@ Array [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus 批量修改一個組中的檔案狀態。 diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 49751d52d..6fc488d82 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 9ef46b869..eae37eae9 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 7a114fefe..468812cc1 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 6ececcd34..3f7ae6573 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 c16a764ff..a4d206287 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 78e7fb685..9c6e9a258 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 1deb9877d..c17c41fb1 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 2b43e954c..f15e2b4f0 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 8ecfd472f..5dfbab362 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/make.bat b/make.bat index 01cb1ddc3..2ab11bc36 100644 --- a/make.bat +++ b/make.bat @@ -1,5 +1,10 @@ git submodule update --init --recursive cd 3rd\luamake call compile\install.bat +call compile\build.bat cd ..\.. -call 3rd\luamake\luamake.exe rebuild +IF "%~1"=="" ( + call 3rd\luamake\luamake.exe rebuild +) ELSE ( + call 3rd\luamake\luamake.exe rebuild --platform %1 +) diff --git a/make.sh b/make.sh index 31cca3690..da9505a47 100755 --- a/make.sh +++ b/make.sh @@ -2,6 +2,11 @@ git submodule update --init --recursive pushd 3rd/luamake +./compile/install.sh ./compile/build.sh popd -./3rd/luamake/luamake rebuild +if [ -z "$1" ]; then + 3rd/luamake/luamake rebuild +else + 3rd/luamake/luamake rebuild --platform "$1" +fi diff --git a/script/cli/check.lua b/script/cli/check.lua index 3902c4aae..8b314f244 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 000000000..f8be88d63 --- /dev/null +++ b/script/cli/check_worker.lua @@ -0,0 +1,166 @@ +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' +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) + 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 d37c50ae1..65e7e102a 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 000000000..ef5b31f28 --- /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 499074198..e74a9f9cd 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 098b41a48..6066ae531 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 e9dd46ce9..a30ebbc6d 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 68bec2349..c7e9294dd 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 000000000..e48e2f736 --- /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 98018b215..c52dcff31 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 0a821f25f..d9939eb0e 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/global.d.lua b/script/global.d.lua index ead46ca9a..daac5f6c8 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/provider/provider.lua b/script/provider/provider.lua index 69fb3263a..15e78b9ac 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 _ = 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/vm/compiler.lua b/script/vm/compiler.lua index 11ba07abe..e1b1b43b8 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? 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 c6df63498..1e3083172 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 e830f6d8e..aa987cf49 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 3f3d0e3a8..bb06ee3a7 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 bc1dfcb10..fae79cbc2 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 ---@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 5fd0fd850..fa49655f7 100644 --- a/test.lua +++ b/test.lua @@ -54,6 +54,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 000000000..678cb23b6 --- /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. = 1 + + +print(M.) +]] + +TEST [[ +---@class A +---@field f fun() +local M + +function M.() end + + +print(M.) +]] + +TEST [[ +---@class A +local M + +function M:(name) end + +---@class A +---@field event fun(self, name: 'ev1') +---@field event fun(self, name: 'ev2') + +---@type A +local m + +m:('ev1') +]] diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua new file mode 100644 index 000000000..5922832b9 --- /dev/null +++ b/test/type_inference/common.lua @@ -0,0 +1,4194 @@ +local config = require 'config' + +TEST 'nil' [[ +local = nil +]] + +TEST 'string' [[ +local = '111' +]] + +TEST 'boolean' [[ +local = true +]] + +TEST 'integer' [[ +local = 1 +]] + +TEST 'number' [[ +local = 1.0 +]] + +TEST 'unknown' [[ +local +]] + +TEST 'unknown' [[ +local +var = y +]] + +TEST 'any' [[ +function f() + +end +]] + +TEST 'any' [[ +function f() + x = 1 +end +]] + +TEST 'string' [[ +local var = '111' +t. = var +]] + +TEST 'string' [[ +local +var = '111' +]] + +TEST 'string' [[ +local var + = '111' +]] + +TEST 'string' [[ +local var +var = '111' +print() +]] + +TEST 'function' [[ +function () +end +]] + +TEST 'function' [[ +local function () +end +]] + +TEST 'function' [[ +local xx + = function () +end +]] + +TEST 'table' [[ +local = {} +]] + +TEST 'unknown' [[ +() +]] + +TEST 'boolean' [[ + = not y +]] + +TEST 'integer' [[ + = #y +]] + +TEST 'integer' [[ + = #'aaaa' +]] + +TEST 'integer' [[ + = #{} +]] + +TEST 'number' [[ + = - y +]] + +TEST 'number' [[ + = - 1.0 +]] + +TEST 'integer' [[ + = ~ y +]] + +TEST 'integer' [[ + = ~ 1 +]] + +TEST 'boolean' [[ + = 1 < 2 +]] + +TEST 'integer' [[ +local a = true +local b = 1 + = a and b +]] + +TEST 'integer' [[ +local a = false +local b = 1 + = a or b +]] + +TEST 'boolean' [[ + = a == b +]] + +TEST 'unknown' [[ + = a << b +]] + +TEST 'integer' [[ + = 1 << 2 +]] + +TEST 'unknown' [[ + = a .. b +]] + +TEST 'string' [[ + = 'a' .. 'b' +]] + +TEST 'string' [[ + = 'a' .. 1 +]] + +TEST 'string' [[ + = 'a' .. 1.0 +]] + +TEST 'unknown' [[ + = a + b +]] + +TEST 'number' [[ + = 1 + 2.0 +]] + +TEST 'integer' [[ + = 1 + 2 +]] + +TEST 'integer' [[ +---@type integer +local a + + = - a +]] + +TEST 'number' [[ +local a + + = - a +]] + +TEST 'unknown' [[ + = 1 + X +]] + +TEST 'unknown' [[ + = 1.0 + X +]] + +TEST 'tablelib' [[ +---@class tablelib +table = {} + +() +]] + +TEST 'string' [[ +_VERSION = 'Lua 5.4' + + = _VERSION +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + +return ('x'). +]] + +TEST 'function' [[ +---@class stringlib +String = {} + +String.xxx = function () end + +return ('x'). +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + + = ('x').xxx +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + +_VERSION = 'Lua 5.4' + + = _VERSION.xxx +]] + +TEST 'table' [[ + = setmetatable({}) +]] + +TEST 'integer' [[ +local function x() + return 1 +end + = x() +]] + +TEST 'integer|nil' [[ +local function x() + return 1 + return nil +end + = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return a + return nil +end + = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return nil + return f() +end + = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return nil + return f() +end +_, = x() +]] + +TEST 'integer' [[ +local function x() + return 1 +end +_, = pcall(x) +]] + +TEST 'integer' [[ +function x() + return 1 +end +_, = pcall(x) +]] + +TEST 'integer' [[ +local function x() + return 1 +end +_, = xpcall(x) +]] + +TEST 'A' [[ +---@class A + +---@return A +local function f2() end + +local function f() + return f2() +end + +local = f() +]] + +-- 不根据调用者的输入参数来推测 +--TEST 'number' [[ +--local function x(a) +-- return +--end +--x(1) +--]] + +--TEST 'table' [[ +--setmetatable() +--]] + +-- 不根据对方函数内的使用情况来推测 +TEST 'unknown' [[ +local function x(a) + _ = a + 1 +end +local b +x() +]] + +TEST 'unknown' [[ +local function x(a, ...) + local _, , _ = ... +end +x(nil, 'xx', 1, true) +]] + +-- 引用不跨越参数 +TEST 'unknown' [[ +local function x(a, ...) + return true, 'ss', ... +end +local _, _, _, , _ = x(nil, true, 1, 'yy') +]] + +TEST 'unknown' [[ +local = next() +]] + +TEST 'unknown' [[ +local a, b +function a() + return b() +end +function b() + return a() +end +local = a() +]] + +TEST 'class' [[ +---@class class +local +]] + +TEST 'string' [[ +---@class string + +---@type string +local +]] + +TEST '1' [[ +---@type 1 +local +]] + +TEST 'string[]' [[ +---@class string + +---@type string[] +local +]] + +TEST 'string|table' [[ +---@class string +---@class table + +---@type string | table +local +]] + +TEST [['enum1'|'enum2']] [[ +---@type 'enum1' | 'enum2' +local +]] + +TEST [["enum1"|"enum2"]] [[ +---@type "enum1" | "enum2" +local +]] + +config.set(nil, 'Lua.hover.expandAlias', false) +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local +]] +config.set(nil, 'Lua.hover.expandAlias', true) +TEST [['enum1'|'enum2']] [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local +]] + +TEST [['enum1'|'enum2']] [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local +]] + +TEST [['enum1'|'enum2'|B]] [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local +]] + +TEST '1|true' [[ +---@alias A 1 | true + +---@type A +local +]] + +TEST 'fun()' [[ +---@type fun() +local +]] + +TEST 'fun(a: string, b: any, ...any)' [[ +---@type fun(a: string, b, ...) +local +]] + +TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[ +---@type fun(a: string, b, c?: boolean, ...):c, d?, ... +local +]] + +TEST '{ [string]: string }' [[ +---@type { [string]: string } +local +]] + +TEST 'table' [[ +---@class string +---@class number + +---@type table +local +]] + +TEST 'A' [[ +---@class A + +---@type A +local +]] + +TEST 'string' [[ +---@class string + +---@type string[] +local x +local = x[1] +]] + +TEST 'string' [[ +---@class string + +---@return string[] +local function f() end +local x = f() +local = x[1] +]] + +TEST 'table' [[ +local t = {} +local = setmetatable(t) +]] + +TEST 'CCC' [[ +---@class CCC + +---@type table +local t = {} + +print(t.) +]] + +TEST [['aaa'|'bbb']] [[ +---@type table +local t = {} + +print(t.) +]] + +TEST 'integer' [[ +---@generic K +---@type fun(a?: K):K +local f + +local = f(1) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a?: K):K +local f + +local = f(nil) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a: K|integer):K +local f + +local = 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 in ipairs() do +end +]] + +TEST 'table' [[ +---@generic K, V +---@param t table +---@return K +---@return V +local function next(t) end + +---@type table +local t +local k, v = next() +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@param t table +---@return K +---@return V +local function next(t) end + +---@type table +local t +local , v = next(t) +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic K, V +---@param t table +---@return K +---@return V +local function next(t) end + +---@type table +local t +local k, = next(t) +]] + +TEST 'boolean' [[ +---@generic K +---@type fun(arg: K):K +local f + +local = f(true) +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@type fun(arg: table):K, V +local f + +---@type table +local t + +local , v = f(t) +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic K, V +---@type fun(arg: table):K, V +local f + +---@type table +local t + +local k, = f(t) +]] + +TEST 'fun()' [[ +---@return fun() +local function f() end + +local = f() +]] + +TEST 'table' [[ +---@return table +local function f() end + +local = f() +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@return fun(arg: table):K, V +local function f() end + +local f2 = f() + +---@type table +local t + +local , v = f2(t) +]] + +TEST 'fun(a: ):integer, ' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local = 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 , 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, = f2(true) +]] + +TEST 'fun(table: table<, >, index?: ):, ' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local = pairs(dummy) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table +local t +local , v = next(t) +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table +local t +local k, = next(t) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table +local t +local , v = next(t, nil) +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table +local t +local k, = next(t, nil) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table +local t + +for , v in next, t do +end +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +local function pairs(t) end + +local f = pairs(t) + +---@type table +local t + +for k, in f, t do +end +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +local function pairs(t) end + +---@type table +local t + +for , v in pairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index: K):K, V +---@return T +---@return nil +local function pairs(t) end + +---@type table +local t + +for k, 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 _, 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 +local t + +for _, 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 _, in ipairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index: K):K, V +---@return T +---@return nil +local function pairs(t) end + +---@type boolean[] +local t + +for k, in pairs(t) do +end +]] + +TEST 'integer' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table, index?: K):K, V +---@return T +local function pairs(t) end + +---@type boolean[] +local t + +for , 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 +end +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls.new(self) return self end + +local = Cls:new() +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls:new() return self end + +local = Cls:new() +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls.new(self) return self end + +local = Cls.new(Cls) +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls:new() return self end + +local = 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 = Rct.new(Rct) + +-- local test = Rct:new() + +return test +]] + +TEST 'function' [[ +string.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 +]] + +config.set(nil, 'Lua.hover.enumsLimit', 1) +TEST [['a'...(+9)]] [[ +---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' +local +]] + +config.set(nil, 'Lua.hover.enumsLimit', 0) +TEST '...(+10)' [[ +---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' +local +]] + +config.set(nil, 'Lua.hover.enumsLimit', 5) + +TEST 'string|fun():string' [[ +---@type string | fun(): string +local +]] + +TEST 'string' [[ +local valids = { + ['Lua 5.1'] = false, + ['Lua 5.2'] = false, + ['Lua 5.3'] = false, + ['Lua 5.4'] = false, + ['LuaJIT'] = false, +} + +for , 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, in pairs(valids) do +end +]] + +TEST 'string' [[ +local t = { + a = 1, + b = 1, +} + +for , v in pairs(t) do +end +]] + +TEST 'integer' [[ +local t = {'a', 'b'} + +for , v in pairs(t) do +end +]] + +TEST 'string' [[ +local t = {'a', 'b'} + +for k, in pairs(t) do +end +]] + +TEST 'fun():number, boolean' [[ +---@type fun():number, boolean +local +]] + + +TEST 'fun(value: Class)' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +work( (value) +end) +]] + +TEST 'Class' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +work(function () +end) +]] + +TEST 'fun(value: Class)' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +pcall(work, (value) +end) +]] + +TEST 'Class' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +xpcall(work, debug.traceback, function () +end) +]] + +TEST 'string' [[ +---@generic T +---@param x T +---@return { x: T } +local function f(x) end + +local t = f('') + +print(t.) +]] + +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 () end) +]] + +TEST 'unknown' [[ +---@generic T +---@param t T[] +---@param callback fun(v: T) +local function f(t, callback) end + +local t = {} + +f(t, function () end) +]] + +TEST 'table' [[ +local = setmetatable({}, { __index = function () end }) +]] + +TEST 'player' [[ +---@class player +local t + +:getOwner() +]] + +TEST 'string[][]' [[ +---@type string[][] +local +]] + +TEST 'table' [[ +---@type {}[] +local t + +local = t[1] +]] + +TEST 'string' [[ +---@type string[][] +local v = {} + +for _, a in ipairs(v) do + for i, in ipairs(a) do + end +end +]] + +--TEST 'number' [[ +-----@param x number +--local f +-- +--f = function () 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", (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 () +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 () +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 () +end) +]] + +TEST '👍' [[ +---@class 👍 +local +]] + +TEST 'integer' [[ +---@type boolean +local x + + = 1 +]] + +TEST 'integer' [[ +---@class Class +local x + + = 1 +]] + +TEST 'unknown' [[ +---@return number +local function f(x) + local = x() +end +]] + +TEST 'unknown' [[ +local mt + +---@return number +function mt:f() end + +local = mt() +]] + +TEST 'unknown' [[ +local + +---@class X +function mt:f(x) end +]] + +TEST 'any' [[ +local mt + +---@class X +function mt:f() end +]] + +TEST 'unknown' [[ +local + +---@type number +function mt:f(x) end +]] + +TEST 'any' [[ +local mt + +---@type number +function mt:f() end +]] + +TEST 'Test' [[ +---@class Test +_G. = {} +]] + +TEST 'integer' [[ +local mt = {} + +---@param callback fun(i: integer) +function mt:loop(callback) end + +mt:loop(function () + +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 { + = , +} +]] + +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 = { + = , + } +} +]] + +TEST 'integer' [[ +for = a, b, c do end +]] + +TEST 'number' [[ +---@param x number +function F() 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 = t.x() +]] + +TEST 'function' [[ +---@overload fun() +function () end +]] + +TEST 'integer' [[ +---@type table +local t + +t. +]] + +TEST '"a"|"b"|"c"' [[ +---@type table +local t + +t. +]] + +TEST 'integer' [[ +---@class A +---@field x integer + +---@type A +local t +t. +]] + +TEST 'boolean' [[ +local = true +var = 1 +var = 1.0 +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local = f() +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local _, = f() +]] + +TEST 'unknown' [[ +local t = { + x = 1, + y = 2, +} + +local = t[#t] +]] + +TEST 'string' [[ +local t = { + x = 1, + [1] = 'x', +} + +local = t[#t] +]] + +TEST 'string' [[ +local t = { 'x' } + +local = t[#t] +]] + +TEST '(string|integer)[]' [[ +---@type (string|integer)[] +local +]] + +TEST 'boolean' [[ +---@type table +local t + +---@alias uri string + +---@type string +local uri + +local = t[uri] +]] + +TEST 'A' [[ +---@class A +G = {} + +:A() +]] + +TEST 'A' [[ +---@type A +local = nil +]] + +TEST 'A' [[ +---@class A +---@field b B +local mt + +function mt:f() + self.b:x() + print() +end +]] + +TEST 'string?' [[ +---@return string? +local function f() end + +local = f() +]] + +TEST 'AA' [[ +---@class AA +---@overload fun():AA +local AAA + + +local = AAA() +]] + +TEST 'AA' [[ +---@class AA +---@overload fun():AA +AAA = {} + + +local = AAA() +]] + +TEST 'string' [[ +local +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x + = '1' +x = 1 +]] + +TEST 'integer' [[ +local x +x = '1' + = 1 +]] + +TEST 'unknown' [[ +local x +print() +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x +x = '1' +print() +x = 1 +]] + +TEST 'integer' [[ +local x +x = '1' +x = 1 +print() +]] + +TEST 'unknown' [[ +local x + +function A() + print() +end +]] + +TEST 'string' [[ +local x + +function A() + print() +end + +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x + +x = '1' + +function A() + print() +end + +x = 1 +]] + +TEST 'integer' [[ +local x + +x = '1' +x = 1 + +function A() + print() +end + +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + print() +end + +x = '1' +x = 1 +]] + +TEST 'unknown' [[ +local x + +function A() + x = true +end + +print() +x = '1' +x = 1 +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + function B() + print() + end +end + +x = '1' +x = 1 +]] + +TEST 'table' [[ +local x + +function A() + x = true + function B() + x = {} + print() + end +end + +x = '1' +x = 1 +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + function B() + x = {} + end + print() +end + +x = '1' +x = 1 +]] + +TEST 'unknown' [[ +local x + +function A() + x = true + function B() + x = {} + end +end + +function C() + print() +end + +x = '1' +x = 1 +]] + +TEST 'integer' [[ +local x +x = true +do + x = 1 +end +print() +]] + +TEST 'boolean' [[ +local x +x = true +function XX() + do + x = 1 + end +end +print() +]] + +TEST 'integer?' [[ +---@type integer? +local +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if 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() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x then + print(x) +end + +print() +]] + +TEST 'nil' [[ +---@type integer? +local x + +if not x then + print() +end + +print(x) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + x = 1 +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if xxx and x then + print() +end +]] + +TEST 'unknown' [[ +---@type integer? +local x + +if not x and x then + print() +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x and not mark[x] then + print() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if xxx and x then +end + +print() +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if xxx and x then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x ~= nil then + print() +end + +print(x) +]] + +TEST 'integer|nil' [[ +---@type integer? +local x + +if x ~= nil then + print(x) +end + +print() +]] + +TEST 'nil' [[ +---@type integer? +local x + +if x == nil then + print() +end + +print(x) +]] + +TEST 'integer|nil' [[ +---@type integer? +local x + +if x == nil then + print(x) +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + + = x or 1 +]] + +TEST 'integer' [[ +---@type integer? +local x + + = x or y +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + goto ANYWHERE +end + +print() +]] + +TEST 'integer' [=[ +local x + +print(--[[@as integer]]) +]=] + +TEST 'integer' [=[ +print(--[[@as integer]]) +]=] + +TEST 'integer' [=[ +print(io.--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local = io['open']--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local = 1 + 1--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local = not 1--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local = ()--[[@as integer]]) +]=] + +TEST 'integer?' [[ +---@param x? integer +local function f() + +end +]] + +TEST 'integer' [[ +local x = 1 +x = +]] + +TEST 'integer?' [[ +---@class A +---@field x? integer +local t + +t. +]] + +TEST 'integer?' [[ +---@type { x?: integer } +local t + +t. +]] + +TEST 'boolean' [[ +---@class A +---@field [integer] boolean +local t + +local = t[1] +]] + +TEST 'unknown' [[ +local = y and z +]] + +TEST 'integer' [[ +---@type integer? +local x + +assert(x) + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +assert(x ~= nil) + +print() +]] + +TEST 'integer' [[ +---@type integer | nil +local x + +assert(x) + +print() +]] + +TEST 'integer' [[ +---@type integer | nil +local x + +assert(x ~= nil) + +print() +]] + +TEST 'integer' [[ +local x + +assert(x == 1) + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x and .y then +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x and x.y then +end + +print() +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x and x.y then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x or .y then +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if not x or x.y then + print() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x or x.y then + print() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x.y or x then + print() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x.y or not x then + print() +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x or not y then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not y or not x then + return +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +while true do + if not x then + break + end + print() +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +while true do + if not x then + break + end +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local x + +while x do + print() +end +]] + +TEST 'integer' [[ +---@type fun():integer? +local iter + +for in iter do +end +]] + +TEST 'integer' [[ +local x + +---@type integer + = XXX +]] + +TEST 'unknown' [[ +for _ = 1, 999 do + local +end +]] + +TEST 'integer' [[ +local x + +---@cast x integer + +print() +]] + +TEST 'unknown' [[ +local x + +---@cast x integer + +local x +print() +]] + +TEST 'unknown' [[ +local x + +if true then + local x + ---@cast x integer + print(x) +end + +print() +]] + +TEST 'boolean|integer' [[ +local x = 1 + +---@cast x +boolean + +print() +]] + +TEST 'boolean' [[ +---@type integer|boolean +local x + +---@cast x -integer + +print() +]] + +TEST 'boolean?' [[ +---@type boolean +local x + +---@cast x +? + +print() +]] + +TEST 'boolean' [[ +---@type boolean? +local x + +---@cast x -? + +print() +]] + +TEST 'nil' [[ +---@type string? +local x + +if x then + return +else + print() +end + +print(x) +]] + +TEST 'string' [[ +---@type string? +local x + +if not x then + return +else + print() +end + +print(x) +]] + +TEST 'string' [[ +---@type string? +local x + +if not x then + return +else + print(x) +end + +print() +]] + +TEST 'true' [[ +---@type boolean | nil +local x + +if not x then + return +end + +print() +]] + +TEST 'true' [[ +---@type boolean +local t + +if t then + print() + return +end + +print(t) +]] + +TEST 'false' [[ +---@type boolean +local t + +if t then + print(t) + return +end + +print() +]] + +TEST 'nil' [[ +---@type integer? +local t + +if t then +else + print() +end + +print(t) +]] + +TEST 'table' [[ +local function f() + if x then + return y + end + return {} +end + +local = f() +]] + +TEST 'integer|table' [[ +local function returnI() + return 1 +end + +local function f() + if x then + return returnI() + end + return {} +end + +local = f() +]] + +TEST 'number' [[ +for _ in _ do + ---@type number + local +end +]] + +TEST 'unknown' [[ +for _ in _ do + ---@param x number + local +end +]] + +TEST 'unknown' [[ +---@type number +for in _ do +end +]] + +TEST 'number' [[ +---@param x number +for in _ do +end +]] + +TEST 'table' [[ +---@alias tp table + +---@type tp +local +]] + +TEST '{ name: boolean }' [[ +---@alias tp {name: boolean} + +---@type tp +local +]] + +TEST 'boolean|{ name: boolean }' [[ +---@alias tp boolean | {name: boolean} + +---@type tp +local +]] + +TEST '`1`|`true`' [[ +---@type `1` | `true` +local +]] + +TEST 'function' [[ +local x + +function x() end + +print() +]] + +TEST 'unknown' [[ +local x + +if x.field == 'haha' then + print() +end +]] + +TEST 'string' [[ +---@type string? +local t + +if not t or xxx then + return +end + +print() +]] + +TEST 'table' [[ +---@type table|nil +local t + +return function () + if not t then + return + end + + print() +end +]] + +TEST 'table' [[ +---@type table|nil +local t + +f(function () + if not t then + return + end + + print() +end) +]] + +TEST 'table' [[ +---@type table? +local t + +t = t or {} + +print() +]] + +TEST 'unknown|nil' [[ +local x + +if x == nil then +end + +print() +]] + +TEST 'table' [[ +---@alias xxx table + +---@type xxx +local +]] + +TEST 'xxx[][]' [[ +---@alias xxx xxx[] + +---@type xxx +local +]] + +TEST 'fun(x: fun(x: xxx))' [[ +---@alias xxx fun(x: xxx) + +---@type xxx +local +]] + +TEST 'table' [[ +---@type table|nil +local t + +while t do + print() +end +]] + +TEST 'table|nil' [[ +---@type table|nil +local t + +while do + print(t) +end +]] + +TEST 'table' [[ +---@type table|nil +local t + +while t ~= nil do + print() +end +]] + +TEST 'table|nil' [[ +---@type table|nil +local t + +while ~= nil do + print(t) +end +]] + +TEST 'integer' [[ +---@type integer? +local n + +if not n then + error('n is nil') +end + +print() +]] + +TEST 'integer' [[ +---@type integer? +local n + +if not n then + os.exit() +end + +print() +]] + +TEST 'table' [[ +---@type table? +local n + +print((n and .x)) +]] + +TEST 'table' [[ +---@type table? +local n + +n = n and .x or 1 +]] + +TEST 'table' [[ +---@type table? +local n + +n = ff[n and .x] +]] + +TEST 'integer' [[ +local x + +if type(x) == 'integer' then + print() +end +]] + +TEST 'boolean|integer' [[ +local x + +if type(x) == 'integer' +or type(x) == 'boolean' then + print() +end +]] + +TEST 'fun()' [[ +---@type fun()? +local x + +if type(x) == 'function' then + print() +end +]] + +TEST 'function' [[ +local x + +if type(x) == 'function' then + print() +end +]] + +TEST 'integer' [[ +local x +local tp = type(x) + +if tp == 'integer' then + print() +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if (x == nil) then +else + print() +end +]] + +TEST 'B' [[ +---@class A +---@class B + +---@type A +local x + +---@type B +x = call(x) + +print() +]] + +TEST 'nil' [[ +local function f() +end + +local = f() +]] + +TEST 'integer[]' [[ +---@type integer[] +local x +if not x then + return +end + +print() +]] + +TEST 'unknown' [[ +---@type string[] +local t + +local = t.x +]] + +TEST 'integer|unknown' [[ +local function f() + return GG +end + +local t + +t.x = 1 +t.x = f() + +print(t.) +]] + +TEST 'integer' [[ +local function f() + if X then + return X + else + return 1 + end +end + +local = f() +]] + +TEST 'unknown' [[ +local function f() + return t[k] +end + +local = f() +]] + +TEST 'integer|nil' [[ +local function f() + if x then + return + else + return 1 + end +end + +local = f() +]] + +TEST 'integer' [[ +---@class A +---@field x integer +local m + +m. = true + +print(m.x) +]] + +TEST 'integer' [[ +---@class A +---@field x integer +local m + +m.x = true + +print(m.) +]] + +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.) +]] + +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.) +]] + +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.) +]] + +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.) +]] + +TEST 'boolean?' [[ +---@generic T +---@param x T +---@return T +local function echo(x) end + +---@type boolean? +local b + +local = echo(b) +]] + +TEST 'boolean' [[ +---@generic T +---@param x T? +---@return T +local function echo(x) end + +---@type boolean? +local b + +local = echo(b) +]] + +TEST 'boolean' [[ +---@generic T +---@param x? T +---@return T +local function echo(x) end + +---@type boolean? +local b + +local = echo(b) +]] + +TEST 'boolean' [[ +---@type {[integer]: boolean, xx: integer} +local t + +local = t[1] +]] + +TEST 'boolean' [[ +---@type integer +local i + +---@type {[integer]: boolean, xx: integer} +local t + +local = t[i] +]] + +TEST 'string' [=[ +local x = true +local y = x--[[@as integer]] --is `integer` here +local z = --[[@as string]] --is `true` here +]=] + +TEST 'integer' [[ +---@type integer +local x + +if type(x) == 'number' then + print() +end +]] + +TEST 'boolean' [[ +---@class A +---@field [integer] boolean +local mt + +function mt:f() + ---@type integer + local index + local = self[index] +end +]] + +TEST 'boolean' [[ +---@class A +---@field [B] boolean + +---@class B + +---@type A +local a + +---@type B +local b + +local = a[b] +]] + +TEST 'number' [[ +---@type {x: string ; y: boolean; z: number} +local t + +local = t.z +]] + +TEST 'fun():number, boolean' [[ +---@type {f: fun():number, boolean} +local t + +local = t.f +]] + +TEST 'fun():number' [[ +---@type {(f: fun():number), x: boolean} +local t + +local = t.f +]] + +TEST 'boolean' [[ +---@param ... boolean +local function f(...) + local = ... +end +]] + +TEST 'boolean' [[ +---@param ... boolean +local function f(...) + local _, = ... +end +]] + +TEST 'boolean' [[ +---@return boolean ... +local function f() end + +local = f() +]] + +TEST 'boolean' [[ +---@return boolean ... +local function f() end + +local _, = f() +]] + +TEST 'boolean' [[ +---@type fun():name1: boolean, name2:number +local f + +local = f() +]] + +TEST 'number' [[ +---@type fun():name1: boolean, name2:number +local f + +local _, = f() +]] +TEST 'boolean' [[ +---@type fun():(name1: boolean, name2:number) +local f + +local = f() +]] + +TEST 'number' [[ +---@type fun():(name1: boolean, name2:number) +local f + +local _, = f() +]] + +TEST 'boolean' [[ +---@type fun():...: boolean +local f + +local _, = f() +]] + +TEST 'string' [[ +local s +while true do + s = '' +end +print() +]] + +TEST 'string' [[ +local s +for _ in _ do + s = '' +end +print() +]] + +TEST 'A' [[ +---@class A: string + +---@type A +local = '' +]] + +TEST 'number' [[ +---@return number +local function f() end +local x, = 1, f() +]] + +TEST 'boolean' [[ +---@return number, boolean +local function f() end +local x, y, = 1, f() +]] + +TEST 'number' [[ +---@return number, boolean +local function f() end +local x, y, = 1, 2, f() +]] + +TEST 'unknown' [[ +local f + +print() + +function f() end +]] + +TEST 'unknown' [[ +local f + +do + print() +end + +function f() end +]] + +TEST 'function' [[ +local f + +function A() + print() +end + +function f() end +]] + +TEST 'number' [[ +---@type number|nil +local n + +local t = { + x = n and , +} +]] + +TEST 'table' [[ +---@type table? +local n + +if not n or not .x then +end +]] + +TEST 'table' [[ +---@type table? +local n + +if not n or not [1] then +end +]] + +TEST 'number' [[ +---@type number|false +local n + +---@cast n -false + +print() +]] + +TEST 'table' [[ +---@type number|table +local n + +if n +---@cast n table +and .type == 'xxx' then +end +]] + +TEST 'integer' [[ +---@type integer? +local n +if true then + n = 0 +end +local = n or 0 +]] + +TEST 'number' [=[ +local = F()--[[@as number]] +]=] + +TEST 'number' [=[ +local function f() + return F()--[[@as number]] +end + +local = f() +]=] + +TEST 'number' [=[ +local = X --[[@as number]] +]=] + +TEST 'number' [[ +---@return number?, number? +local function f() end + +for , y in f do +end +]] + +TEST 'number' [[ +---@return number?, number? +local function f() end + +for x, in f do +end +]] + +TEST 'number|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local = a and b +]] + +TEST 'number|table|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local = a or b +]] + +TEST 'number|table|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local c = a and b +local = a or b +]] + +TEST 'number' [[ +local x + +---@return number +local function f() +end + +x = f() + +print() +]] + +TEST 'number' [[ +local x + +---@return number +local function f() +end + +_, x = pcall(f) + +print() +]] + +TEST 'string' [[ +---@type table +local t + +---@type number +local n +---@type string +local s + +local = t[n] +local test2 = t[s] --test and test2 are unknow +]] + +TEST 'string' [[ +---@type table +local t + +---@type number +local n +---@type string +local s + +local test = t[n] +local = t[s] --test and test2 are unknow +]] + +TEST 'table' [[ +---@type table +local t + + = {} +]] + +TEST 'integer' [[ +---@type integer[]|A +local t + +local = t[1] +]] + +TEST 'integer' [[ +---@type integer +---@diagnostic disable +local +]] + +TEST 'A' [[ +---@class A +---@diagnostic disable +local +]] + +TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[ +---@type {[string]: number, [true]: string, [1]: boolean, tag: integer} +local +]] + +TEST 'unknown' [[ +local mt = {} +mt. = nil +]] + +TEST 'unknown' [[ +mt = {} +mt. = nil +]] + +TEST 'A' [[ +---@class A +---@operator unm: A + +---@type A +local a +local = -a +]] + +TEST 'A' [[ +---@class A +---@operator bnot: A + +---@type A +local a +local = ~a +]] + +TEST 'A' [[ +---@class A +---@operator len: A + +---@type A +local a +local = #a +]] + +TEST 'A' [[ +---@class A +---@operator add: A + +---@type A +local a +local = a + 1 +]] + +TEST 'A' [[ +---@class A +---@operator sub: A + +---@type A +local a +local = a - 1 +]] + +TEST 'A' [[ +---@class A +---@operator mul: A + +---@type A +local a +local = a * 1 +]] + +TEST 'A' [[ +---@class A +---@operator div: A + +---@type A +local a +local = a / 1 +]] + +TEST 'A' [[ +---@class A +---@operator mod: A + +---@type A +local a +local = a % 1 +]] + +TEST 'A' [[ +---@class A +---@operator pow: A + +---@type A +local a +local = a ^ 1 +]] + +TEST 'A' [[ +---@class A +---@operator idiv: A + +---@type A +local a +local = a // 1 +]] + +TEST 'A' [[ +---@class A +---@operator band: A + +---@type A +local a +local = a & 1 +]] + +TEST 'A' [[ +---@class A +---@operator bor: A + +---@type A +local a +local = a | 1 +]] + +TEST 'A' [[ +---@class A +---@operator bxor: A + +---@type A +local a +local = a ~ 1 +]] + +TEST 'A' [[ +---@class A +---@operator shl: A + +---@type A +local a +local = a << 1 +]] + +TEST 'A' [[ +---@class A +---@operator shr: A + +---@type A +local a +local = a >> 1 +]] + +TEST 'A' [[ +---@class A +---@operator concat: A + +---@type A +local a +local = a .. 1 +]] + +TEST 'A' [[ +---@class A +---@operator add(boolean): boolean +---@operator add(integer): A + +---@type A +local a +local = a + 1 +]] + +TEST 'boolean' [[ +---@class A +---@operator add(boolean): boolean +---@operator add(integer): A + +---@type A +local a +local = a + true +]] + +TEST 'A' [[ +---@class A +---@operator call: A + +---@type A +local a +local = a() +]] + +TEST 'A' [[ +---@class A +---@operator call: A + +---@type A +local a + +local t = { + = a(), +} +]] + +TEST 'boolean' [[ +---@class A +---@field n number +---@field [string] boolean +local t + +local = t.xx +]] + +TEST 'number' [[ +---@class A +---@field n number +---@field [string] boolean +local t + +local = t.n +]] + +TEST 'string' [[ +---@class string +---@operator mod: string + +local = '' % 1 +]] + +TEST 'string|integer' [[ +---@type boolean +local bool + +local = bool and '' or 0 +]] + +TEST 'string|integer' [[ +local bool + +if X then + bool = true +else + bool = false +end + +local = bool and '' or 0 +]] + +TEST 'boolean' [[ +---@type boolean|true|false +local +]] + +TEST 'integer|false' [[ +local = X == 1 and X == 1 and 1 +]] + +TEST 'unknown|nil' [[ +local function f() + if X then + return ({})[1] + end + return nil +end + +local = f() +]] + +TEST 'integer' [[ +---@generic T +---@vararg T # ERROR +---@return T +local function test(...) + return ... +end + +local = test(1) +]] + +TEST 'boolean' [[ +---@type boolean, number +local , y +]] + +TEST 'number' [[ +---@type boolean, number +local x, +]] + +TEST 'unknown' [[ +---@type _, number +local , y +]] + +TEST 'number[]' [[ +local t +---@cast t number[]? + +local x = t and [i] +]] + +TEST 'number?' [[ +---@type number[]? +local t + +local = t and t[i] +]] + +TEST 'number' [[ +---@type number +local x + +if not .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() +]] + +TEST 'integer' [[ +local A = { + ---@class XXX + B = {} +} + +A.B.C = 1 + +print(A.B.) +]] + +TEST '-2|-3|1' [[ +---@type 1|-2|-3 +local +]] + +TEST 'table' [[ +---@enum A +local m = {} + +print() +]] + +TEST 'A' [[ +---@class A +---@overload fun():A +local m = {} + +---@return A +function m:init() + return +end +]] + +TEST 'string' [[ +---@vararg string +function F(...) + local t = {...} + for k, in pairs(t) do + end +end +]] + +TEST 'string' [[ +---@vararg string +function F(...) + local t = {...} + for k, in ipairs(t) do + end +end +]] + +TEST 'integerA' [[ +---@type integerA +for = 1, 10 do +end +]] + +TEST 'string' [[ +---@class A +---@field x string + +---@class B : A +local t = {} + +t.x = t.x + +print(t.) +]] + +TEST 'unknown' [[ +local t = { + x = 1, +} + +local x + +local = t[x] +]] + +TEST 'A|B' [[ +---@class A +---@class B: A + +---@type A|B +local +]] + +TEST 'function' [[ +---@class myClass +local myClass = { has = { nested = {} } } + +function myClass.has.nested.fn() end + +---@type myClass +local class + +class.has.nested.() +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun(x: T) +---@return T[] +local function x(f) end + +---@param x integer +local = x(function (x) end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +local = x(function () + return 1 +end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@return integer +local = 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 = x(cb) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@type fun(): integer +local cb + +local = x(cb) +]] + +TEST 'integer' [[ +---@return fun(x: integer) +local function f() + return function () + end +end +]] + +TEST 'string' [[ +---@class A +---@field f fun(x: string) + +---@type A +local t = { + f = function () end +} +]] + +config.set(nil, 'Lua.runtime.special', { + ['xx.assert'] = 'assert' +}) + +TEST 'number' [[ +---@type number? +local t + +xx.assert(t) + +print() +]] + +config.set(nil, 'Lua.runtime.special', nil) + +TEST 'A' [[ +---@class A +local mt + +---@return +function mt:init() +end +]] + +TEST 'A' [[ +---@class A +local mt + +---@return self +function mt:init() +end + +local = mt:init() +]] + +TEST 'A' [[ +---@class A +---@field x +]] + +TEST 'A' [[ +---@class A +---@field x self + +---@type A +local o + +print(o.) +]] + +TEST 'A' [[ +---@class A +---@overload fun(): self +local A + +local = A() +]] + +TEST 'number' [[ +---@type table<'Test1', fun(x: number)> +local t = { + ["Test1"] = function() end, +} +]] + +TEST 'number' [[ +---@type table<5, fun(x: number)> +local t = { + [5] = function() end, +} +]] + +TEST 'number' [[ +---@type fun(x: number) +local function f() end +]] + +TEST 'boolean' [[ +---@generic T: string | boolean | table +---@param x T +---@return T +local function f(x) + return x +end + +local = f(true) +]] + +TEST 'number' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local = t[1] +]] + +TEST 'boolean' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local = t[2] +]] + +TEST 'N' [[ +---@class N: number +local x + +if x == 0.1 then + print() +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 = 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 = m * v +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +else + print() +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +elseif then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif then + ---@cast t A + print(t) +end +]] + +TEST 'function' [[ +local function x() + print() +end +]] + +TEST 'number' [[ +---@type number? +local x + +do + if not x then + return + end +end + +print() +]] + +TEST 'number' [[ +---@type number[] +local xs + +---@type fun(x): number? +local f + +for _, in ipairs(xs) do + x = f(x) +end +]] + +TEST 'number' [[ +---@type number? +X = Y + +if X then + print() +end +]] + +TEST 'number' [[ +---@type number|boolean +X = Y + +if type(X) == 'number' then + print() +end +]] + +TEST 'boolean' [[ +---@type number|boolean +X = Y + +if type(X) ~= 'number' then + print() +end +]] + +TEST 'boolean' [[ +---@type number +X = Y + +---@cast X boolean + +print() +]] + +TEST 'number' [[ +---@type number +local t + +if xxx == then + print(t) +end +]] + +TEST 'V' [[ +---@class V +X = 1 + +print() +]] + +TEST 'V' [[ +---@class V +X.Y = 1 + +print(X.) +]] + +TEST 'integer' [[ +local x = {} + +x.y = 1 +local y = x.y +x.y = nil + +print() +]] + +TEST 'function' [[ +function X() + () +end + +function Y() +end +]] + +TEST 'A_Class' [[ +---@class A_Class +local A = { x = 5 } + +function A:func() + for i = 1, .x do + print(i) + end + + self.y = 3 + self.y = self.y + 3 +end +]] + +TEST 'number' [[ +---@type number? +local n +local = 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 = foo * fooOrBar +]] + +TEST 'number' [[ +local a = 4; +local b = 2; + +local = a / b; +]] + +TEST 'string' [[ +local a = '4'; +local b = '2'; + +local = a .. b; +]] + +TEST 'number|{ [1]: string }' [[ +---@alias Some +---| { [1]: string } +---| number + +local x ---@type Some + +print() +]] + +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. +]] + +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. +]] + +TEST 'integer' [[ +local x = 1 +repeat +until +]] + +-- #2144 +TEST 'A' [=[ +local function f() + return {} --[[@as A]] +end + +local = f() +]=] + +TEST 'A' [=[ +local function f() + ---@type A + return {} +end + +local = f() +]=] + +TEST 'boolean|number' [[ +---@alias A number +---@alias(partial) A boolean + +---@type A +local +]] diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index da4590a63..35900bc3f 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 = nil -]] - -TEST 'string' [[ -local = '111' -]] - -TEST 'boolean' [[ -local = true -]] - -TEST 'integer' [[ -local = 1 -]] - -TEST 'number' [[ -local = 1.0 -]] - -TEST 'unknown' [[ -local -]] - -TEST 'unknown' [[ -local -var = y -]] - -TEST 'any' [[ -function f() - -end -]] - -TEST 'any' [[ -function f() - x = 1 -end -]] - -TEST 'string' [[ -local var = '111' -t. = var -]] - -TEST 'string' [[ -local -var = '111' -]] - -TEST 'string' [[ -local var - = '111' -]] - -TEST 'string' [[ -local var -var = '111' -print() -]] - -TEST 'function' [[ -function () -end -]] - -TEST 'function' [[ -local function () -end -]] - -TEST 'function' [[ -local xx - = function () -end -]] - -TEST 'table' [[ -local = {} -]] - -TEST 'unknown' [[ -() -]] - -TEST 'boolean' [[ - = not y -]] - -TEST 'integer' [[ - = #y -]] - -TEST 'integer' [[ - = #'aaaa' -]] - -TEST 'integer' [[ - = #{} -]] - -TEST 'number' [[ - = - y -]] - -TEST 'number' [[ - = - 1.0 -]] - -TEST 'integer' [[ - = ~ y -]] - -TEST 'integer' [[ - = ~ 1 -]] - -TEST 'boolean' [[ - = 1 < 2 -]] - -TEST 'integer' [[ -local a = true -local b = 1 - = a and b -]] - -TEST 'integer' [[ -local a = false -local b = 1 - = a or b -]] - -TEST 'boolean' [[ - = a == b -]] - -TEST 'unknown' [[ - = a << b -]] - -TEST 'integer' [[ - = 1 << 2 -]] - -TEST 'unknown' [[ - = a .. b -]] - -TEST 'string' [[ - = 'a' .. 'b' -]] - -TEST 'string' [[ - = 'a' .. 1 -]] - -TEST 'string' [[ - = 'a' .. 1.0 -]] - -TEST 'unknown' [[ - = a + b -]] - -TEST 'number' [[ - = 1 + 2.0 -]] - -TEST 'integer' [[ - = 1 + 2 -]] - -TEST 'integer' [[ ----@type integer -local a - - = - a -]] - -TEST 'number' [[ -local a - - = - a -]] - -TEST 'unknown' [[ - = 1 + X -]] - -TEST 'unknown' [[ - = 1.0 + X -]] - -TEST 'tablelib' [[ ----@class tablelib -table = {} - -() -]] - -TEST 'string' [[ -_VERSION = 'Lua 5.4' - - = _VERSION -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - -return ('x'). -]] - -TEST 'function' [[ ----@class stringlib -String = {} - -String.xxx = function () end - -return ('x'). -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - - = ('x').xxx -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - -_VERSION = 'Lua 5.4' - - = _VERSION.xxx -]] - -TEST 'table' [[ - = setmetatable({}) -]] - -TEST 'integer' [[ -local function x() - return 1 -end - = x() -]] - -TEST 'integer|nil' [[ -local function x() - return 1 - return nil -end - = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return a - return nil -end - = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return nil - return f() -end - = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return nil - return f() -end -_, = x() -]] - -TEST 'integer' [[ -local function x() - return 1 -end -_, = pcall(x) -]] - -TEST 'integer' [[ -function x() - return 1 -end -_, = pcall(x) -]] - -TEST 'integer' [[ -local function x() - return 1 -end -_, = xpcall(x) -]] - -TEST 'A' [[ ----@class A - ----@return A -local function f2() end - -local function f() - return f2() -end - -local = f() -]] - --- 不根据调用者的输入参数来推测 ---TEST 'number' [[ ---local function x(a) --- return ---end ---x(1) ---]] - ---TEST 'table' [[ ---setmetatable() ---]] - --- 不根据对方函数内的使用情况来推测 -TEST 'unknown' [[ -local function x(a) - _ = a + 1 -end -local b -x() -]] - -TEST 'unknown' [[ -local function x(a, ...) - local _, , _ = ... -end -x(nil, 'xx', 1, true) -]] - --- 引用不跨越参数 -TEST 'unknown' [[ -local function x(a, ...) - return true, 'ss', ... -end -local _, _, _, , _ = x(nil, true, 1, 'yy') -]] - -TEST 'unknown' [[ -local = next() -]] - -TEST 'unknown' [[ -local a, b -function a() - return b() -end -function b() - return a() -end -local = a() -]] - -TEST 'class' [[ ----@class class -local -]] - -TEST 'string' [[ ----@class string - ----@type string -local -]] - -TEST '1' [[ ----@type 1 -local -]] - -TEST 'string[]' [[ ----@class string - ----@type string[] -local -]] - -TEST 'string|table' [[ ----@class string ----@class table - ----@type string | table -local -]] - -TEST [['enum1'|'enum2']] [[ ----@type 'enum1' | 'enum2' -local -]] - -TEST [["enum1"|"enum2"]] [[ ----@type "enum1" | "enum2" -local -]] - -config.set(nil, 'Lua.hover.expandAlias', false) -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' - ----@type A -local -]] - -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' | A - ----@type A -local -]] - -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' | B - ----@type A -local -]] -config.set(nil, 'Lua.hover.expandAlias', true) -TEST [['enum1'|'enum2']] [[ ----@alias A 'enum1' | 'enum2' - ----@type A -local -]] - -TEST [['enum1'|'enum2']] [[ ----@alias A 'enum1' | 'enum2' | A - ----@type A -local -]] - -TEST [['enum1'|'enum2'|B]] [[ ----@alias A 'enum1' | 'enum2' | B - ----@type A -local -]] - -TEST '1|true' [[ ----@alias A 1 | true - ----@type A -local -]] - -TEST 'fun()' [[ ----@type fun() -local -]] - -TEST 'fun(a: string, b: any, ...any)' [[ ----@type fun(a: string, b, ...) -local -]] - -TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[ ----@type fun(a: string, b, c?: boolean, ...):c, d?, ... -local -]] - -TEST '{ [string]: string }' [[ ----@type { [string]: string } -local -]] - -TEST 'table' [[ ----@class string ----@class number - ----@type table -local -]] - -TEST 'A' [[ ----@class A - ----@type A -local -]] - -TEST 'string' [[ ----@class string - ----@type string[] -local x -local = x[1] -]] - -TEST 'string' [[ ----@class string - ----@return string[] -local function f() end -local x = f() -local = x[1] -]] - -TEST 'table' [[ -local t = {} -local = setmetatable(t) -]] - -TEST 'CCC' [[ ----@class CCC - ----@type table -local t = {} - -print(t.) -]] - -TEST [['aaa'|'bbb']] [[ ----@type table -local t = {} - -print(t.) -]] - -TEST 'integer' [[ ----@generic K ----@type fun(a?: K):K -local f - -local = f(1) -]] - -TEST 'unknown' [[ ----@generic K ----@type fun(a?: K):K -local f - -local = f(nil) -]] - -TEST 'unknown' [[ ----@generic K ----@type fun(a: K|integer):K -local f - -local = 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 in ipairs() do -end -]] - -TEST 'table' [[ ----@generic K, V ----@param t table ----@return K ----@return V -local function next(t) end - ----@type table -local t -local k, v = next() -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@param t table ----@return K ----@return V -local function next(t) end - ----@type table -local t -local , v = next(t) -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic K, V ----@param t table ----@return K ----@return V -local function next(t) end - ----@type table -local t -local k, = next(t) -]] - -TEST 'boolean' [[ ----@generic K ----@type fun(arg: K):K -local f - -local = f(true) -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@type fun(arg: table):K, V -local f - ----@type table -local t - -local , v = f(t) -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic K, V ----@type fun(arg: table):K, V -local f - ----@type table -local t - -local k, = f(t) -]] - -TEST 'fun()' [[ ----@return fun() -local function f() end - -local = f() -]] - -TEST 'table' [[ ----@return table -local function f() end - -local = f() -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@return fun(arg: table):K, V -local function f() end - -local f2 = f() - ----@type table -local t - -local , v = f2(t) -]] - -TEST 'fun(a: ):integer, ' [[ ----@generic K, V ----@param a K ----@return fun(a: V):K, V -local function f(a) end - -local = 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 , 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, = f2(true) -]] - -TEST 'fun(table: table<, >, index?: ):, ' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local = pairs(dummy) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table -local t -local , v = next(t) -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table -local t -local k, = next(t) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table -local t -local , v = next(t, nil) -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table -local t -local k, = next(t, nil) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table -local t - -for , v in next, t do -end -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T -local function pairs(t) end - -local f = pairs(t) - ----@type table -local t - -for k, in f, t do -end -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T -local function pairs(t) end - ----@type table -local t - -for , v in pairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index: K):K, V ----@return T ----@return nil -local function pairs(t) end - ----@type table -local t - -for k, 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 _, 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 -local t - -for _, 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 _, in ipairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index: K):K, V ----@return T ----@return nil -local function pairs(t) end - ----@type boolean[] -local t - -for k, in pairs(t) do -end -]] - -TEST 'integer' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table, index?: K):K, V ----@return T -local function pairs(t) end - ----@type boolean[] -local t - -for , 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 -end -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls.new(self) return self end - -local = Cls:new() -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls:new() return self end - -local = Cls:new() -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls.new(self) return self end - -local = Cls.new(Cls) -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls:new() return self end - -local = 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 = Rct.new(Rct) - --- local test = Rct:new() - -return test -]] - -TEST 'function' [[ -string.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 -]] - -config.set(nil, 'Lua.hover.enumsLimit', 1) -TEST [['a'...(+9)]] [[ ----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' -local -]] - -config.set(nil, 'Lua.hover.enumsLimit', 0) -TEST '...(+10)' [[ ----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' -local -]] - -config.set(nil, 'Lua.hover.enumsLimit', 5) - -TEST 'string|fun():string' [[ ----@type string | fun(): string -local -]] - -TEST 'string' [[ -local valids = { - ['Lua 5.1'] = false, - ['Lua 5.2'] = false, - ['Lua 5.3'] = false, - ['Lua 5.4'] = false, - ['LuaJIT'] = false, -} - -for , 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, in pairs(valids) do -end -]] - -TEST 'string' [[ -local t = { - a = 1, - b = 1, -} - -for , v in pairs(t) do -end -]] - -TEST 'integer' [[ -local t = {'a', 'b'} - -for , v in pairs(t) do -end -]] - -TEST 'string' [[ -local t = {'a', 'b'} - -for k, in pairs(t) do -end -]] - -TEST 'fun():number, boolean' [[ ----@type fun():number, boolean -local -]] - - -TEST 'fun(value: Class)' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -work( (value) -end) -]] - -TEST 'Class' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -work(function () -end) -]] - -TEST 'fun(value: Class)' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -pcall(work, (value) -end) -]] - -TEST 'Class' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -xpcall(work, debug.traceback, function () -end) -]] - -TEST 'string' [[ ----@generic T ----@param x T ----@return { x: T } -local function f(x) end - -local t = f('') - -print(t.) -]] - -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 () end) -]] - -TEST 'unknown' [[ ----@generic T ----@param t T[] ----@param callback fun(v: T) -local function f(t, callback) end - -local t = {} - -f(t, function () end) -]] - -TEST 'table' [[ -local = setmetatable({}, { __index = function () end }) -]] - -TEST 'player' [[ ----@class player -local t - -:getOwner() -]] - -TEST 'string[][]' [[ ----@type string[][] -local -]] - -TEST 'table' [[ ----@type {}[] -local t - -local = t[1] -]] - -TEST 'string' [[ ----@type string[][] -local v = {} - -for _, a in ipairs(v) do - for i, in ipairs(a) do - end -end -]] - ---TEST 'number' [[ ------@param x number ---local f --- ---f = function () 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", (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 () -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 () -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 () -end) -]] - -TEST '👍' [[ ----@class 👍 -local -]] - -TEST 'integer' [[ ----@type boolean -local x - - = 1 -]] - -TEST 'integer' [[ ----@class Class -local x - - = 1 -]] - -TEST 'unknown' [[ ----@return number -local function f(x) - local = x() -end -]] - -TEST 'unknown' [[ -local mt - ----@return number -function mt:f() end - -local = mt() -]] - -TEST 'unknown' [[ -local - ----@class X -function mt:f(x) end -]] - -TEST 'any' [[ -local mt - ----@class X -function mt:f() end -]] - -TEST 'unknown' [[ -local - ----@type number -function mt:f(x) end -]] - -TEST 'any' [[ -local mt - ----@type number -function mt:f() end -]] - -TEST 'Test' [[ ----@class Test -_G. = {} -]] - -TEST 'integer' [[ -local mt = {} - ----@param callback fun(i: integer) -function mt:loop(callback) end - -mt:loop(function () - -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 { - = , -} -]] - -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 = { - = , - } -} -]] - -TEST 'integer' [[ -for = a, b, c do end -]] - -TEST 'number' [[ ----@param x number -function F() 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 = t.x() -]] - -TEST 'function' [[ ----@overload fun() -function () end -]] - -TEST 'integer' [[ ----@type table -local t - -t. -]] - -TEST '"a"|"b"|"c"' [[ ----@type table -local t - -t. -]] - -TEST 'integer' [[ ----@class A ----@field x integer - ----@type A -local t -t. -]] - -TEST 'boolean' [[ -local = true -var = 1 -var = 1.0 -]] - -TEST 'unknown' [[ ----@return ... -local function f() end - -local = f() -]] - -TEST 'unknown' [[ ----@return ... -local function f() end - -local _, = f() -]] - -TEST 'unknown' [[ -local t = { - x = 1, - y = 2, -} - -local = t[#t] -]] - -TEST 'string' [[ -local t = { - x = 1, - [1] = 'x', -} - -local = t[#t] -]] - -TEST 'string' [[ -local t = { 'x' } - -local = t[#t] -]] - -TEST '(string|integer)[]' [[ ----@type (string|integer)[] -local -]] - -TEST 'boolean' [[ ----@type table -local t - ----@alias uri string - ----@type string -local uri - -local = t[uri] -]] - -TEST 'A' [[ ----@class A -G = {} - -:A() -]] - -TEST 'A' [[ ----@type A -local = nil -]] - -TEST 'A' [[ ----@class A ----@field b B -local mt - -function mt:f() - self.b:x() - print() -end -]] - -TEST 'string?' [[ ----@return string? -local function f() end - -local = f() -]] - -TEST 'AA' [[ ----@class AA ----@overload fun():AA -local AAA - - -local = AAA() -]] - -TEST 'AA' [[ ----@class AA ----@overload fun():AA -AAA = {} - - -local = AAA() -]] - -TEST 'string' [[ -local -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x - = '1' -x = 1 -]] - -TEST 'integer' [[ -local x -x = '1' - = 1 -]] - -TEST 'unknown' [[ -local x -print() -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x -x = '1' -print() -x = 1 -]] - -TEST 'integer' [[ -local x -x = '1' -x = 1 -print() -]] - -TEST 'unknown' [[ -local x - -function A() - print() -end -]] - -TEST 'string' [[ -local x - -function A() - print() -end - -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x - -x = '1' - -function A() - print() -end - -x = 1 -]] - -TEST 'integer' [[ -local x - -x = '1' -x = 1 - -function A() - print() -end - -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - print() -end - -x = '1' -x = 1 -]] - -TEST 'unknown' [[ -local x - -function A() - x = true -end - -print() -x = '1' -x = 1 -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - function B() - print() - end -end - -x = '1' -x = 1 -]] - -TEST 'table' [[ -local x - -function A() - x = true - function B() - x = {} - print() - end -end - -x = '1' -x = 1 -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - function B() - x = {} - end - print() -end - -x = '1' -x = 1 -]] - -TEST 'unknown' [[ -local x - -function A() - x = true - function B() - x = {} - end -end - -function C() - print() -end - -x = '1' -x = 1 -]] - -TEST 'integer' [[ -local x -x = true -do - x = 1 -end -print() -]] - -TEST 'boolean' [[ -local x -x = true -function XX() - do - x = 1 - end -end -print() -]] - -TEST 'integer?' [[ ----@type integer? -local -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if 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() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x then - print(x) -end - -print() -]] - -TEST 'nil' [[ ----@type integer? -local x - -if not x then - print() -end - -print(x) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - x = 1 -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if xxx and x then - print() -end -]] - -TEST 'unknown' [[ ----@type integer? -local x - -if not x and x then - print() -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x and not mark[x] then - print() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if xxx and x then -end - -print() -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if xxx and x then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x ~= nil then - print() -end - -print(x) -]] - -TEST 'integer|nil' [[ ----@type integer? -local x - -if x ~= nil then - print(x) -end - -print() -]] - -TEST 'nil' [[ ----@type integer? -local x - -if x == nil then - print() -end - -print(x) -]] - -TEST 'integer|nil' [[ ----@type integer? -local x - -if x == nil then - print(x) -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - - = x or 1 -]] - -TEST 'integer' [[ ----@type integer? -local x - - = x or y -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - goto ANYWHERE -end - -print() -]] - -TEST 'integer' [=[ -local x - -print(--[[@as integer]]) -]=] - -TEST 'integer' [=[ -print(--[[@as integer]]) -]=] - -TEST 'integer' [=[ -print(io.--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local = io['open']--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local = 1 + 1--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local = not 1--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local = ()--[[@as integer]]) -]=] - -TEST 'integer?' [[ ----@param x? integer -local function f() - -end -]] - -TEST 'integer' [[ -local x = 1 -x = -]] - -TEST 'integer?' [[ ----@class A ----@field x? integer -local t - -t. -]] - -TEST 'integer?' [[ ----@type { x?: integer } -local t - -t. -]] - -TEST 'boolean' [[ ----@class A ----@field [integer] boolean -local t - -local = t[1] -]] - -TEST 'unknown' [[ -local = y and z -]] - -TEST 'integer' [[ ----@type integer? -local x - -assert(x) - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -assert(x ~= nil) - -print() -]] - -TEST 'integer' [[ ----@type integer | nil -local x - -assert(x) - -print() -]] - -TEST 'integer' [[ ----@type integer | nil -local x - -assert(x ~= nil) - -print() -]] - -TEST 'integer' [[ -local x - -assert(x == 1) - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x and .y then -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x and x.y then -end - -print() -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x and x.y then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x or .y then -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if not x or x.y then - print() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x or x.y then - print() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x.y or x then - print() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x.y or not x then - print() -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x or not y then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not y or not x then - return -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -while true do - if not x then - break - end - print() -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -while true do - if not x then - break - end -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local x - -while x do - print() -end -]] - -TEST 'integer' [[ ----@type fun():integer? -local iter - -for in iter do -end -]] - -TEST 'integer' [[ -local x - ----@type integer - = XXX -]] - -TEST 'unknown' [[ -for _ = 1, 999 do - local -end -]] - -TEST 'integer' [[ -local x - ----@cast x integer - -print() -]] - -TEST 'unknown' [[ -local x - ----@cast x integer - -local x -print() -]] - -TEST 'unknown' [[ -local x - -if true then - local x - ---@cast x integer - print(x) -end - -print() -]] - -TEST 'boolean|integer' [[ -local x = 1 - ----@cast x +boolean - -print() -]] - -TEST 'boolean' [[ ----@type integer|boolean -local x - ----@cast x -integer - -print() -]] - -TEST 'boolean?' [[ ----@type boolean -local x - ----@cast x +? - -print() -]] - -TEST 'boolean' [[ ----@type boolean? -local x - ----@cast x -? - -print() -]] - -TEST 'nil' [[ ----@type string? -local x - -if x then - return -else - print() -end - -print(x) -]] - -TEST 'string' [[ ----@type string? -local x - -if not x then - return -else - print() -end - -print(x) -]] - -TEST 'string' [[ ----@type string? -local x - -if not x then - return -else - print(x) -end - -print() -]] - -TEST 'true' [[ ----@type boolean | nil -local x - -if not x then - return -end - -print() -]] - -TEST 'true' [[ ----@type boolean -local t - -if t then - print() - return -end - -print(t) -]] - -TEST 'false' [[ ----@type boolean -local t - -if t then - print(t) - return -end - -print() -]] - -TEST 'nil' [[ ----@type integer? -local t - -if t then -else - print() -end - -print(t) -]] - -TEST 'table' [[ -local function f() - if x then - return y - end - return {} -end - -local = f() -]] - -TEST 'integer|table' [[ -local function returnI() - return 1 -end - -local function f() - if x then - return returnI() - end - return {} -end - -local = f() -]] - -TEST 'number' [[ -for _ in _ do - ---@type number - local -end -]] - -TEST 'unknown' [[ -for _ in _ do - ---@param x number - local -end -]] - -TEST 'unknown' [[ ----@type number -for in _ do -end -]] - -TEST 'number' [[ ----@param x number -for in _ do -end -]] - -TEST 'table' [[ ----@alias tp table - ----@type tp -local -]] - -TEST '{ name: boolean }' [[ ----@alias tp {name: boolean} - ----@type tp -local -]] - -TEST 'boolean|{ name: boolean }' [[ ----@alias tp boolean | {name: boolean} - ----@type tp -local -]] - -TEST '`1`|`true`' [[ ----@type `1` | `true` -local -]] - -TEST 'function' [[ -local x - -function x() end - -print() -]] - -TEST 'unknown' [[ -local x - -if x.field == 'haha' then - print() -end -]] - -TEST 'string' [[ ----@type string? -local t - -if not t or xxx then - return -end - -print() -]] - -TEST 'table' [[ ----@type table|nil -local t - -return function () - if not t then - return - end - - print() -end -]] - -TEST 'table' [[ ----@type table|nil -local t - -f(function () - if not t then - return - end - - print() -end) -]] - -TEST 'table' [[ ----@type table? -local t - -t = t or {} - -print() -]] - -TEST 'unknown|nil' [[ -local x - -if x == nil then -end - -print() -]] - -TEST 'table' [[ ----@alias xxx table - ----@type xxx -local -]] - -TEST 'xxx[][]' [[ ----@alias xxx xxx[] - ----@type xxx -local -]] - -TEST 'fun(x: fun(x: xxx))' [[ ----@alias xxx fun(x: xxx) - ----@type xxx -local -]] - -TEST 'table' [[ ----@type table|nil -local t - -while t do - print() -end -]] - -TEST 'table|nil' [[ ----@type table|nil -local t - -while do - print(t) -end -]] - -TEST 'table' [[ ----@type table|nil -local t - -while t ~= nil do - print() -end -]] - -TEST 'table|nil' [[ ----@type table|nil -local t - -while ~= nil do - print(t) -end -]] - -TEST 'integer' [[ ----@type integer? -local n - -if not n then - error('n is nil') -end - -print() -]] - -TEST 'integer' [[ ----@type integer? -local n - -if not n then - os.exit() -end - -print() -]] - -TEST 'table' [[ ----@type table? -local n - -print((n and .x)) -]] - -TEST 'table' [[ ----@type table? -local n - -n = n and .x or 1 -]] - -TEST 'table' [[ ----@type table? -local n - -n = ff[n and .x] -]] - -TEST 'integer' [[ -local x - -if type(x) == 'integer' then - print() -end -]] - -TEST 'boolean|integer' [[ -local x - -if type(x) == 'integer' -or type(x) == 'boolean' then - print() -end -]] - -TEST 'fun()' [[ ----@type fun()? -local x - -if type(x) == 'function' then - print() -end -]] - -TEST 'function' [[ -local x - -if type(x) == 'function' then - print() -end -]] - -TEST 'integer' [[ -local x -local tp = type(x) - -if tp == 'integer' then - print() -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if (x == nil) then -else - print() -end -]] - -TEST 'B' [[ ----@class A ----@class B - ----@type A -local x - ----@type B -x = call(x) - -print() -]] - -TEST 'nil' [[ -local function f() -end - -local = f() -]] - -TEST 'integer[]' [[ ----@type integer[] -local x -if not x then - return -end - -print() -]] - -TEST 'unknown' [[ ----@type string[] -local t - -local = t.x -]] - -TEST 'integer|unknown' [[ -local function f() - return GG -end - -local t - -t.x = 1 -t.x = f() - -print(t.) -]] - -TEST 'integer' [[ -local function f() - if X then - return X - else - return 1 - end -end - -local = f() -]] - -TEST 'unknown' [[ -local function f() - return t[k] -end - -local = f() -]] - -TEST 'integer|nil' [[ -local function f() - if x then - return - else - return 1 - end -end - -local = f() -]] - -TEST 'integer' [[ ----@class A ----@field x integer -local m - -m. = true - -print(m.x) -]] - -TEST 'integer' [[ ----@class A ----@field x integer -local m - -m.x = true - -print(m.) -]] - -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.) -]] - -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.) -]] - -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.) -]] - -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.) -]] - -TEST 'boolean?' [[ ----@generic T ----@param x T ----@return T -local function echo(x) end - ----@type boolean? -local b - -local = echo(b) -]] - -TEST 'boolean' [[ ----@generic T ----@param x T? ----@return T -local function echo(x) end - ----@type boolean? -local b - -local = echo(b) -]] - -TEST 'boolean' [[ ----@generic T ----@param x? T ----@return T -local function echo(x) end - ----@type boolean? -local b - -local = echo(b) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -local = f() -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -local = f(1) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -function r0() - return -end - -local = f(r0()) -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -function r1() - return 1 -end - -local = f(r1()) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - ----@type fun() -local r0 - -local = f(r0()) -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - ----@type fun():integer -local r1 - -local = f(r1()) -]] - -TEST 'boolean' [[ ----@overload fun(x: number, y: number):string ----@overload fun(x: number):number ----@return boolean -local function f() end - -local = 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 = 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 = f(0, 0) -]] - -TEST 'boolean' [[ ----@type {[integer]: boolean, xx: integer} -local t - -local = t[1] -]] - -TEST 'boolean' [[ ----@type integer -local i - ----@type {[integer]: boolean, xx: integer} -local t - -local = t[i] -]] - -TEST 'string' [=[ -local x = true -local y = x--[[@as integer]] --is `integer` here -local z = --[[@as string]] --is `true` here -]=] - -TEST 'integer' [[ ----@type integer -local x - -if type(x) == 'number' then - print() -end -]] - -TEST 'boolean' [[ ----@class A ----@field [integer] boolean -local mt - -function mt:f() - ---@type integer - local index - local = self[index] -end -]] - -TEST 'boolean' [[ ----@class A ----@field [B] boolean - ----@class B - ----@type A -local a - ----@type B -local b - -local = a[b] -]] - -TEST 'number' [[ ----@type {x: string ; y: boolean; z: number} -local t - -local = t.z -]] - -TEST 'fun():number, boolean' [[ ----@type {f: fun():number, boolean} -local t - -local = t.f -]] - -TEST 'fun():number' [[ ----@type {(f: fun():number), x: boolean} -local t - -local = t.f -]] - -TEST 'boolean' [[ ----@param ... boolean -local function f(...) - local = ... -end -]] - -TEST 'boolean' [[ ----@param ... boolean -local function f(...) - local _, = ... -end -]] - -TEST 'boolean' [[ ----@return boolean ... -local function f() end - -local = f() -]] - -TEST 'boolean' [[ ----@return boolean ... -local function f() end - -local _, = f() -]] - -TEST 'boolean' [[ ----@type fun():name1: boolean, name2:number -local f - -local = f() -]] - -TEST 'number' [[ ----@type fun():name1: boolean, name2:number -local f - -local _, = f() -]] -TEST 'boolean' [[ ----@type fun():(name1: boolean, name2:number) -local f - -local = f() -]] - -TEST 'number' [[ ----@type fun():(name1: boolean, name2:number) -local f - -local _, = f() -]] - -TEST 'boolean' [[ ----@type fun():...: boolean -local f - -local _, = f() -]] - -TEST 'string' [[ -local s -while true do - s = '' -end -print() -]] - -TEST 'string' [[ -local s -for _ in _ do - s = '' -end -print() -]] - -TEST 'A' [[ ----@class A: string - ----@type A -local = '' -]] - -TEST 'number' [[ ----@return number -local function f() end -local x, = 1, f() -]] - -TEST 'boolean' [[ ----@return number, boolean -local function f() end -local x, y, = 1, f() -]] - -TEST 'number' [[ ----@return number, boolean -local function f() end -local x, y, = 1, 2, f() -]] - -TEST 'unknown' [[ -local f - -print() - -function f() end -]] - -TEST 'unknown' [[ -local f - -do - print() -end - -function f() end -]] - -TEST 'function' [[ -local f - -function A() - print() -end - -function f() end -]] - -TEST 'number' [[ ----@type number|nil -local n - -local t = { - x = n and , -} -]] - -TEST 'table' [[ ----@type table? -local n - -if not n or not .x then -end -]] - -TEST 'table' [[ ----@type table? -local n - -if not n or not [1] then -end -]] - -TEST 'number' [[ ----@type number|false -local n - ----@cast n -false - -print() -]] - -TEST 'table' [[ ----@type number|table -local n - -if n ----@cast n table -and .type == 'xxx' then -end -]] - -TEST 'integer' [[ ----@type integer? -local n -if true then - n = 0 -end -local = n or 0 -]] - -TEST 'number' [=[ -local = F()--[[@as number]] -]=] - -TEST 'number' [=[ -local function f() - return F()--[[@as number]] -end - -local = f() -]=] - -TEST 'number' [=[ -local = X --[[@as number]] -]=] - -TEST 'number' [[ ----@return number?, number? -local function f() end - -for , y in f do -end -]] - -TEST 'number' [[ ----@return number?, number? -local function f() end - -for x, in f do -end -]] - -TEST 'number|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local = a and b -]] - -TEST 'number|table|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local = a or b -]] - -TEST 'number|table|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local c = a and b -local = a or b -]] - -TEST 'number' [[ -local x - ----@return number -local function f() -end - -x = f() - -print() -]] - -TEST 'number' [[ -local x - ----@return number -local function f() -end - -_, x = pcall(f) - -print() -]] - -TEST 'string' [[ ----@type table -local t - ----@type number -local n ----@type string -local s - -local = t[n] -local test2 = t[s] --test and test2 are unknow -]] - -TEST 'string' [[ ----@type table -local t - ----@type number -local n ----@type string -local s - -local test = t[n] -local = t[s] --test and test2 are unknow -]] - -TEST 'table' [[ ----@type table -local t - - = {} -]] - -TEST 'integer' [[ ----@type integer[]|A -local t - -local = t[1] -]] - -TEST 'integer' [[ ----@type integer ----@diagnostic disable -local -]] - -TEST 'A' [[ ----@class A ----@diagnostic disable -local -]] - -TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[ ----@type {[string]: number, [true]: string, [1]: boolean, tag: integer} -local -]] - -TEST 'unknown' [[ -local mt = {} -mt. = nil -]] - -TEST 'unknown' [[ -mt = {} -mt. = nil -]] - -TEST 'A' [[ ----@class A ----@operator unm: A - ----@type A -local a -local = -a -]] - -TEST 'A' [[ ----@class A ----@operator bnot: A - ----@type A -local a -local = ~a -]] - -TEST 'A' [[ ----@class A ----@operator len: A - ----@type A -local a -local = #a -]] - -TEST 'A' [[ ----@class A ----@operator add: A - ----@type A -local a -local = a + 1 -]] - -TEST 'A' [[ ----@class A ----@operator sub: A - ----@type A -local a -local = a - 1 -]] - -TEST 'A' [[ ----@class A ----@operator mul: A - ----@type A -local a -local = a * 1 -]] - -TEST 'A' [[ ----@class A ----@operator div: A - ----@type A -local a -local = a / 1 -]] - -TEST 'A' [[ ----@class A ----@operator mod: A - ----@type A -local a -local = a % 1 -]] - -TEST 'A' [[ ----@class A ----@operator pow: A - ----@type A -local a -local = a ^ 1 -]] - -TEST 'A' [[ ----@class A ----@operator idiv: A - ----@type A -local a -local = a // 1 -]] - -TEST 'A' [[ ----@class A ----@operator band: A - ----@type A -local a -local = a & 1 -]] - -TEST 'A' [[ ----@class A ----@operator bor: A - ----@type A -local a -local = a | 1 -]] - -TEST 'A' [[ ----@class A ----@operator bxor: A - ----@type A -local a -local = a ~ 1 -]] - -TEST 'A' [[ ----@class A ----@operator shl: A - ----@type A -local a -local = a << 1 -]] - -TEST 'A' [[ ----@class A ----@operator shr: A - ----@type A -local a -local = a >> 1 -]] - -TEST 'A' [[ ----@class A ----@operator concat: A - ----@type A -local a -local = a .. 1 -]] - -TEST 'A' [[ ----@class A ----@operator add(boolean): boolean ----@operator add(integer): A - ----@type A -local a -local = a + 1 -]] - -TEST 'boolean' [[ ----@class A ----@operator add(boolean): boolean ----@operator add(integer): A - ----@type A -local a -local = a + true -]] - -TEST 'A' [[ ----@class A ----@operator call: A - ----@type A -local a -local = a() -]] - -TEST 'A' [[ ----@class A ----@operator call: A - ----@type A -local a - -local t = { - = a(), -} -]] - -TEST 'boolean' [[ ----@class A ----@field n number ----@field [string] boolean -local t - -local = t.xx -]] - -TEST 'number' [[ ----@class A ----@field n number ----@field [string] boolean -local t - -local = t.n -]] - -TEST 'string' [[ ----@class string ----@operator mod: string - -local = '' % 1 -]] - -TEST 'string|integer' [[ ----@type boolean -local bool - -local = bool and '' or 0 -]] - -TEST 'string|integer' [[ -local bool - -if X then - bool = true -else - bool = false -end - -local = bool and '' or 0 -]] - -TEST 'boolean' [[ ----@type boolean|true|false -local -]] - -TEST 'integer|false' [[ -local = X == 1 and X == 1 and 1 -]] - -TEST 'unknown|nil' [[ -local function f() - if X then - return ({})[1] - end - return nil -end - -local = f() -]] - -TEST 'integer' [[ ----@generic T ----@vararg T # ERROR ----@return T -local function test(...) - return ... -end - -local = test(1) -]] - -TEST 'boolean' [[ ----@type boolean, number -local , y -]] - -TEST 'number' [[ ----@type boolean, number -local x, -]] - -TEST 'unknown' [[ ----@type _, number -local , y -]] - -TEST 'number[]' [[ -local t ----@cast t number[]? - -local x = t and [i] -]] - -TEST 'number?' [[ ----@type number[]? -local t - -local = t and t[i] -]] - -TEST 'number' [[ ----@type number -local x - -if not .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() -]] - -TEST 'integer' [[ -local A = { - ---@class XXX - B = {} -} - -A.B.C = 1 - -print(A.B.) -]] - -TEST '-2|-3|1' [[ ----@type 1|-2|-3 -local -]] - -TEST 'table' [[ ----@enum A -local m = {} - -print() -]] - -TEST 'A' [[ ----@class A ----@overload fun():A -local m = {} - ----@return A -function m:init() - return -end -]] - -TEST 'string' [[ ----@vararg string -function F(...) - local t = {...} - for k, in pairs(t) do - end -end -]] - -TEST 'string' [[ ----@vararg string -function F(...) - local t = {...} - for k, in ipairs(t) do - end -end -]] - -TEST 'integerA' [[ ----@type integerA -for = 1, 10 do -end -]] - -TEST 'string' [[ ----@class A ----@field x string - ----@class B : A -local t = {} - -t.x = t.x - -print(t.) -]] - -TEST 'unknown' [[ -local t = { - x = 1, -} - -local x - -local = t[x] -]] - -TEST 'A|B' [[ ----@class A ----@class B: A - ----@type A|B -local -]] - -TEST 'function' [[ ----@class myClass -local myClass = { has = { nested = {} } } - -function myClass.has.nested.fn() end - ----@type myClass -local class - -class.has.nested.() -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun(x: T) ----@return T[] -local function x(f) end - ----@param x integer -local = x(function (x) end) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - -local = x(function () - return 1 -end) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - ----@return integer -local = 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 = x(cb) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - ----@type fun(): integer -local cb - -local = x(cb) -]] - -TEST 'integer' [[ ----@return fun(x: integer) -local function f() - return function () - end -end -]] - -TEST 'string' [[ ----@class A ----@field f fun(x: string) - ----@type A -local t = { - f = function () end -} -]] - -config.set(nil, 'Lua.runtime.special', { - ['xx.assert'] = 'assert' -}) - -TEST 'number' [[ ----@type number? -local t - -xx.assert(t) - -print() -]] - -config.set(nil, 'Lua.runtime.special', nil) - -TEST 'A' [[ ----@class A -local mt - ----@return -function mt:init() -end -]] - -TEST 'A' [[ ----@class A -local mt - ----@return self -function mt:init() -end - -local = mt:init() -]] - -TEST 'A' [[ ----@class A ----@field x -]] - -TEST 'A' [[ ----@class A ----@field x self - ----@type A -local o - -print(o.) -]] - -TEST 'A' [[ ----@class A ----@overload fun(): self -local A - -local = A() -]] - -TEST 'number' [[ ----@type table<'Test1', fun(x: number)> -local t = { - ["Test1"] = function() end, -} -]] - -TEST 'number' [[ ----@type table<5, fun(x: number)> -local t = { - [5] = function() end, -} -]] - -TEST 'number' [[ ----@type fun(x: number) -local function f() end -]] - -TEST 'boolean' [[ ----@generic T: string | boolean | table ----@param x T ----@return T -local function f(x) - return x -end - -local = f(true) -]] - -TEST 'number' [[ ----@class A ----@field [1] number ----@field [2] boolean -local t - -local = t[1] -]] - -TEST 'boolean' [[ ----@class A ----@field [1] number ----@field [2] boolean -local t - -local = t[2] -]] - -TEST 'N' [[ ----@class N: number -local x - -if x == 0.1 then - print() -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 = 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 = m * v -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A -else - print() -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A -elseif then -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A - print(t) -elseif then -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A - print(t) -elseif then - ---@cast t A - print(t) -end -]] - -TEST 'function' [[ -local function x() - print() -end -]] - -TEST 'number' [[ ----@type number? -local x - -do - if not x then - return - end -end - -print() -]] - -TEST 'number' [[ ----@type number[] -local xs - ----@type fun(x): number? -local f - -for _, in ipairs(xs) do - x = f(x) -end -]] - -TEST 'number' [[ ----@type number? -X = Y - -if X then - print() -end -]] - -TEST 'number' [[ ----@type number|boolean -X = Y - -if type(X) == 'number' then - print() -end -]] - -TEST 'boolean' [[ ----@type number|boolean -X = Y - -if type(X) ~= 'number' then - print() -end -]] - -TEST 'boolean' [[ ----@type number -X = Y - ----@cast X boolean - -print() -]] - -TEST 'number' [[ ----@type number -local t - -if xxx == then - print(t) -end -]] - -TEST 'V' [[ ----@class V -X = 1 - -print() -]] - -TEST 'V' [[ ----@class V -X.Y = 1 - -print(X.) -]] - -TEST 'integer' [[ -local x = {} - -x.y = 1 -local y = x.y -x.y = nil - -print() -]] - -TEST 'function' [[ -function X() - () -end - -function Y() -end -]] - -TEST 'A_Class' [[ ----@class A_Class -local A = { x = 5 } - -function A:func() - for i = 1, .x do - print(i) - end - - self.y = 3 - self.y = self.y + 3 -end -]] - -TEST 'number' [[ ----@type number? -local n -local = 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 = foo * fooOrBar -]] - -TEST 'number' [[ -local a = 4; -local b = 2; - -local = a / b; -]] - -TEST 'string' [[ -local a = '4'; -local b = '2'; - -local = a .. b; -]] - -TEST 'number|{ [1]: string }' [[ ----@alias Some ----| { [1]: string } ----| number - -local x ---@type Some - -print() -]] - -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. -]] - -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. -]] - -TEST 'integer' [[ -local x = 1 -repeat -until -]] - --- #2144 -TEST 'A' [=[ -local function f() - return {} --[[@as A]] -end - -local = f() -]=] - -TEST 'A' [=[ -local function f() - ---@type A - return {} -end - -local = f() -]=] - -TEST 'boolean|number' [[ ----@alias A number ----@alias(partial) A boolean - ----@type A -local -]] +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 000000000..8ead05ef3 --- /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 = 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 = 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 = f(0, 0) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +local = f() +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +local = f(1) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +function r0() + return +end + +local = f(r0()) +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +function r1() + return 1 +end + +local = f(r1()) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +---@type fun() +local r0 + +local = f(r0()) +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +---@type fun():integer +local r1 + +local = f(r1()) +]] + +TEST '1' [[ +---@overload fun(a: 'x'): 1 +---@overload fun(a: 'y'): 2 +local function f(...) end + +local = f('x') +]] + +TEST '2' [[ +---@overload fun(a: 'x'): 1 +---@overload fun(a: 'y'): 2 +local function f(...) end + +local = f('y') +]] + +TEST '1' [[ +---@overload fun(a: boolean): 1 +---@overload fun(a: number): 2 +local function f(...) end + +local = f(true) +]] + +TEST '2' [[ +---@overload fun(a: boolean): 1 +---@overload fun(a: number): 2 +local function f(...) end + +local = f(10) +]]