diff options
author | CppCXY <812125110@qq.com> | 2024-07-01 14:14:48 +0800 |
---|---|---|
committer | CppCXY <812125110@qq.com> | 2024-07-01 14:14:48 +0800 |
commit | e30b59d91ddf7a81f72c5672039d65648e1c4bed (patch) | |
tree | 250ab9bc5bee49864e075d91fcce6cc65797030f | |
parent | ab2f9a77526330534d0baf4e01bd019b71ae45cf (diff) | |
parent | 3886d2ede3345296fbadc96c09d42cca96affa77 (diff) | |
download | lua-language-server-e30b59d91ddf7a81f72c5672039d65648e1c4bed.zip |
Merge branch 'master' of github.com:CppCXY/lua-language-server
90 files changed, 6315 insertions, 4696 deletions
diff --git a/.github/scripts/check-changelog.sh b/.github/scripts/check-changelog.sh new file mode 100755 index 00000000..ae3b8b11 --- /dev/null +++ b/.github/scripts/check-changelog.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -e + +CHANGELOG_FILE="changelog.md" + +git fetch origin $GITHUB_BASE_REF +git fetch + +# Check if the changelog file was modified in the PR +if git diff --name-only origin/$GITHUB_BASE_REF..remotes/pull/$GITHUB_SOURCE_REF | grep -q $CHANGELOG_FILE; then + echo "Thank you for updating the changelog!" + exit 0 +else + echo "Changelog has not been updated. Please update $CHANGELOG_FILE!" + exit 1 +fi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 66640e50..b6db21ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,7 @@ on: push: branches: - master + - 'v*.*.*' tags: - "*" pull_request: @@ -24,8 +25,8 @@ jobs: matrix: include: - { os: ubuntu-22.04, target: linux, platform: linux-x64, container: 'alpine:latest', libc: musl } - - { os: ubuntu-20.04, target: linux, platform: linux-x64, container: 'ubuntu:18.04' } - - { os: ubuntu-20.04, target: linux, platform: linux-arm64, container: 'ubuntu:18.04' } + - { os: ubuntu-20.04, target: linux, platform: linux-x64 } + - { 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,11 @@ jobs: container: image: ${{ matrix.container }} steps: - - name: Prepare container - if: ${{ matrix.target == 'linux' && matrix.libc != 'musl' }} - run: | - apt-get update - apt-get install -y software-properties-common - add-apt-repository -y ppa:ubuntu-toolchain-r/test # For gcc-9 and g++-9 - add-apt-repository -y ppa:git-core/ppa # For git>=2.18. - apt-get update - apt-get install -y sudo git gcc-9 g++-9 - - name: Install aarch64-linux-gnu if: ${{ matrix.platform == 'linux-arm64' && matrix.libc != 'musl' }} run: | - apt-get update - apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - - name: Prepare container env - if: ${{ matrix.target == 'linux' && matrix.libc != 'musl' }} - 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 + 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' }} @@ -62,21 +47,36 @@ jobs: apk update apk add git ninja bash build-base nodejs linux-headers - - uses: actions/checkout@v3 + - 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 @@ -114,7 +114,7 @@ jobs: echo PKG_PATH="${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT echo PKG_STAGING=${PKG_STAGING} >> $GITHUB_OUTPUT - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: ${{ steps.vars.outputs.PKG_BASENAME }} path: | diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000..b3995e05 --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,24 @@ +name: changelog + +on: + pull_request: + types: [opened, synchronize] + branches: + - master + +jobs: + check-changelog: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up environment + run: | + echo "GITHUB_SOURCE_REF=${{ github.ref_name }}" >> $GITHUB_ENV + echo "GITHUB_BASE_REF=${{ github.base_ref }}" >> $GITHUB_ENV + + - name: Check if changelog is updated + run: .github/scripts/check-changelog.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8ff3632..12e614e5 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/.luarc.json b/.luarc.json index fc02379f..7e366901 100644 --- a/.luarc.json +++ b/.luarc.json @@ -1,4 +1,5 @@ { + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", "diagnostics": { "disable": [ "close-non-object", diff --git a/.vscode/launch.json b/.vscode/launch.json index c90d7307..b9fca4dc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,9 @@ "print", "stderr", ], + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + } }, { "name": "🍄attach", @@ -48,6 +51,9 @@ "request": "launch", "stopOnEntry": false, "luaexe": "${workspaceFolder}/bin/lua-language-server", + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + }, "program": "${workspaceRoot}/tools/build-3rd-meta.lua", "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", "console": "integratedTerminal", @@ -67,6 +73,9 @@ "request": "launch", "stopOnEntry": false, "luaexe": "${workspaceFolder}/bin/lua-language-server", + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + }, "program": "${workspaceRoot}/tools/locale.lua", "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", "console": "integratedTerminal", @@ -86,6 +95,9 @@ "request": "launch", "stopOnEntry": false, "luaexe": "${workspaceFolder}/bin/lua-language-server", + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + }, "program": "${workspaceRoot}/tools/build-doc.lua", "cpath": "${workspaceFolder}/bin/?.dll;${workspaceFolder}/bin/?.so", "console": "integratedTerminal", @@ -106,6 +118,9 @@ "stopOnEntry": false, "program": "${workspaceRoot}/main.lua", "luaexe": "${workspaceFolder}/bin/lua-language-server", + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + }, "cpath": null, "arg": [ "--check", @@ -125,6 +140,9 @@ "stopOnEntry": false, "program": "${workspaceRoot}/main.lua", "luaexe": "${workspaceFolder}/bin/lua-language-server", + "windows": { + "luaexe": "${workspaceFolder}/bin/lua-language-server.exe" + }, "cpath": null, "arg": [ "--doc", diff --git a/3rd/bee.lua b/3rd/bee.lua -Subproject 1f01891c5dfcb0a740e2b573eeefd4ea4efd9a3 +Subproject 8c01c7d79612d47f47f17d80304e66ae14d7b95 diff --git a/3rd/love-api b/3rd/love-api -Subproject 728ba001f3398fd11b0a3909b919a7caf3e329a +Subproject 853639288547618dece86c3a8e52348fe304eba diff --git a/3rd/luamake b/3rd/luamake -Subproject ffa2770d1261bf12af2cfed5551b55df85c089d +Subproject c086f35cfad0236f74ba380d51f211c52a2c8ab diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ec57d0fe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Dockerfile + +FROM ubuntu:18.04 + +# Install necessary packages +RUN apt-get update && \ + apt-get install -y software-properties-common && \ + add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + add-apt-repository -y ppa:git-core/ppa && \ + apt-get install -y git gcc-9 g++-9 wget tar gzip rsync ninja-build + +# Use alternative gcc +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100 && \ + update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100 diff --git a/changelog.md b/changelog.md index 20110331..ef4a4017 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,112 @@ # changelog -## 3.7.5 +## Unreleased +<!-- Add all new changes here. They will be moved under a version at release --> +* `NEW` Add postfix snippet for `unpack` +* `FIX` `diagnostics.severity` defaulting to "Warning" when run using `--check` [#2730](https://github.com/LuaLS/lua-language-server/issues/2730) +* `NEW` Add support for lambda style functions, `|paramList| expr` is syntactic sugar for `function(paramList) return expr end` + +## 3.9.3 +`2024-6-11` +* `FIX` Sometimes providing incorrect autocompletion when chaining calls + +## 3.9.2 +`2024-6-6` +* `NEW` Reference workspace symbols in comments using `[some text](lua://symbolName)` syntax +* `FIX` Don't do diagnostics when the workspace is not ready +* `FIX` Autocompletion for enum values is not available in some cases + +## 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. + +## 3.8.2 +`2024-4-23` +* This is a fake version only for the new version of VSCode, with a core of 3.8.0. + +## 3.8.1 +`2024-4-23` +* This is a fake version only for the old version of VSCode, with a core of `3.7.4`. Starting from the next minor version, the version requirement for VSCode will be raised to prevent users still using the old version of VSCode from updating to the new version and experiencing compatibility issues. + +## 3.8.0 +`2024-4-22` +* `NEW` supports tuple type (@[lizho]) + ```lua + ---@type [string, number, boolean] + local t + + local x = t[1] --> x is `string` + local y = t[2] --> y is `number` + local z = t[3] --> z is `boolean` + ``` +* `NEW` generic pattern (@[fesily]) + ```lua + ---@generic T + ---@param t Cat.`T` + ---@return T + local function f(t) end + + local t = f('Smile') --> t is `Cat.Smile` + ``` +* `NEW` alias and enums supports attribute `partial` + ```lua + ---@alias Animal Cat + + ---@alias(partial) Animal Dog + + ---@type Animal + local animal --> animal is `Cat|Dog` here + ``` + + ```lua + ---@enum(key) ErrorCodes + local codes1 = { + OK = 0, + ERROR = 1, + FATAL = 2, + } + + ---@enum(key, partial) ErrorCodes + local codes2 = { + WARN = 3, + INFO = 4, + } + + ---@type ErrorCodes + local code + + code = 'ERROR' --> OK + code = 'WARN' --> OK + + ``` +* `NEW` plugin: add `OnTransFormAst` interface (@[fesily]) +* `NEW` plugin: add `OnNodeCompileFunctionParam` interface (@[fesily]) +* `NEW` plugin: add `ResolveRequire` interface (@[Artem Dzhemesiuk]) +* `NEW` plugin: support multi plugins (@[fesily]) + + setting: `Lua.runtime.plugin` can be `string|string[]` + + setting: `Lua.runtime.pluginArgs` can be `string[]|table<string, string>` +* `NEW` CLI: `--doc` add option `--doc_out_path <PATH>` (@[Andreas Matthias]) +* `NEW` CLI: `--doc_update`, update an existing `doc.json` without using `--doc` again (@[Andreas Matthias]) +* `NEW` CLI: `--trust_all_plugins`, this is potentially unsafe for normal use and meant for usage in CI environments only (@[Paul Emmerich]) +* `CHG` CLI: `--check` will run plugins (@[Daniel Farrell]) +* `FIX` diagnostic: `discard-returns` not works in some blocks (@clay-golem) * `FIX` rename in library files ## 3.7.4 @@ -1966,3 +2072,12 @@ f( -- view comments of `1` and `2` in completion `2020-11-9` * `NEW` implementation, NEW start! + +<!-- contributors --> +[lizho]: (https://github.com/lizho) +[fesily]: (https://github.com/fesily) +[Andreas Matthias]: (https://github.com/AndreasMatthias) +[Daniel Farrell]: (https://github.com/danpf) +[Paul Emmerich]: (https://github.com/emmericp) +[Artem Dzhemesiuk]: (https://github.com/zziger) +[clay-golem]: (https://github.com/clay-golem) diff --git a/doc/en-us/config.md b/doc/en-us/config.md index f57d4d64..caa3c3a8 100644 --- a/doc/en-us/config.md +++ b/doc/en-us/config.md @@ -406,6 +406,22 @@ Array<string> [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array<string> +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus Modify the diagnostic needed file status in a group. @@ -1813,6 +1829,7 @@ Array<string> * ``"!"`` * ``"!="`` * ``"continue"`` +* ``"|lambda|"`` ## default diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md index 7add2c98..acaed4dc 100644 --- a/doc/pt-br/config.md +++ b/doc/pt-br/config.md @@ -406,6 +406,22 @@ Array<string> [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array<string> +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus Modify the diagnostic needed file status in a group. @@ -1813,6 +1829,7 @@ Array<string> * ``"!"`` * ``"!="`` * ``"continue"`` +* ``"|lambda|"`` ## default diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md index ebb8325f..e7292b92 100644 --- a/doc/zh-cn/config.md +++ b/doc/zh-cn/config.md @@ -406,6 +406,22 @@ Array<string> [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array<string> +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus 批量修改一个组中的文件状态。 diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md index 8b01d78c..5e491a51 100644 --- a/doc/zh-tw/config.md +++ b/doc/zh-tw/config.md @@ -406,6 +406,22 @@ Array<string> [] ``` +# diagnostics.globalsRegex + +Find defined global variables using regex. + +## type + +```ts +Array<string> +``` + +## default + +```jsonc +[] +``` + # diagnostics.groupFileStatus 批量修改一個組中的檔案狀態。 diff --git a/locale/en-us/script.lua b/locale/en-us/script.lua index 20d858bb..6fc488d8 100644 --- a/locale/en-us/script.lua +++ b/locale/en-us/script.lua @@ -646,8 +646,12 @@ CLI_CHECK_INITING = 'Initializing ...' CLI_CHECK_SUCCESS = 'Diagnosis completed, no problems found' +CLI_CHECK_PROGRESS = +'Found {} problems in {} files' CLI_CHECK_RESULTS = 'Diagnosis complete, {} problems found, see {}' +CLI_CHECK_MULTIPLE_WORKERS = +'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = 'Loading documents ...' CLI_DOC_DONE = diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua index 9ef46b86..eae37eae 100644 --- a/locale/en-us/setting.lua +++ b/locale/en-us/setting.lua @@ -48,6 +48,8 @@ config.diagnostics.disable = "Disabled diagnostic (Use code in hover brackets)." config.diagnostics.globals = "Defined global variables." +config.diagnostics.globalsRegex = +"Find defined global variables using regex." config.diagnostics.severity = [[ Modify the diagnostic severity. @@ -440,3 +442,7 @@ command.addon_manager.open = 'Lua: Open Addon Manager ...' command.reloadFFIMeta = 'Lua: Reload luajit ffi meta' +command.startServer = +'Lua: (debug) Start Language Server' +command.stopServer = +'Lua: (debug) Stop Language Server' diff --git a/locale/pt-br/script.lua b/locale/pt-br/script.lua index 2357aa50..468812cc 100644 --- a/locale/pt-br/script.lua +++ b/locale/pt-br/script.lua @@ -646,8 +646,12 @@ CLI_CHECK_INITING = 'Inicializando ...' CLI_CHECK_SUCCESS = 'Diagnóstico completo, nenhum problema encontrado' +CLI_CHECK_PROGRESS = -- TODO: need translate! +'Found {} problems in {} files' CLI_CHECK_RESULTS = 'Diagnóstico completo, {} problemas encontrados, veja {}' +CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate! +'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = -- TODO: need translate! 'Loading documents ...' CLI_DOC_DONE = -- TODO: need translate! diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua index 6ececcd3..3f7ae657 100644 --- a/locale/pt-br/setting.lua +++ b/locale/pt-br/setting.lua @@ -48,6 +48,8 @@ config.diagnostics.disable = -- TODO: need translate! "Disabled diagnostic (Use code in hover brackets)." config.diagnostics.globals = -- TODO: need translate! "Defined global variables." +config.diagnostics.globalsRegex = -- TODO: need translate! +"Find defined global variables using regex." config.diagnostics.severity = -- TODO: need translate! [[ Modify the diagnostic severity. @@ -440,3 +442,7 @@ command.addon_manager.open = -- TODO: need translate! 'Lua: Open Addon Manager ...' command.reloadFFIMeta = -- TODO: need translate! 'Lua: Reload luajit ffi meta' +command.startServer = -- TODO: need translate! +'Lua: (debug) Start Language Server' +command.stopServer = -- TODO: need translate! +'Lua: (debug) Stop Language Server' diff --git a/locale/zh-cn/script.lua b/locale/zh-cn/script.lua index 6459a104..a4d20628 100644 --- a/locale/zh-cn/script.lua +++ b/locale/zh-cn/script.lua @@ -646,8 +646,12 @@ CLI_CHECK_INITING = '正在初始化...' CLI_CHECK_SUCCESS = '诊断完成,没有发现问题' +CLI_CHECK_PROGRESS = -- TODO: need translate! +'Found {} problems in {} files' CLI_CHECK_RESULTS = '诊断完成,共有 {} 个问题,请查看 {}' +CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate! +'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = '加载文档 ...' CLI_DOC_DONE = diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua index 78e7fb68..9c6e9a25 100644 --- a/locale/zh-cn/setting.lua +++ b/locale/zh-cn/setting.lua @@ -48,6 +48,8 @@ config.diagnostics.disable = "禁用的诊断(使用浮框括号内的代码)。" config.diagnostics.globals = "已定义的全局变量。" +config.diagnostics.globalsRegex = -- TODO: need translate! +"Find defined global variables using regex." config.diagnostics.severity = [[ 修改诊断等级。 @@ -439,3 +441,7 @@ command.addon_manager.open = 'Lua: 打开插件管理器...' command.reloadFFIMeta = 'Lua: 重新生成luajit的FFI模块C语言元数据' +command.startServer = +'Lua: (debug) 启动服务器' +command.stopServer = +'Lua: (debug) 停止服务器' diff --git a/locale/zh-tw/script.lua b/locale/zh-tw/script.lua index 43c064b2..c17c41fb 100644 --- a/locale/zh-tw/script.lua +++ b/locale/zh-tw/script.lua @@ -646,8 +646,12 @@ CLI_CHECK_INITING = '正在初始化...' CLI_CHECK_SUCCESS = '診斷完成,沒有發現問題' +CLI_CHECK_PROGRESS = -- TODO: need translate! +'Found {} problems in {} files' CLI_CHECK_RESULTS = '診斷完成,共有 {} 個問題,請查看 {}' +CLI_CHECK_MULTIPLE_WORKERS = -- TODO: need translate! +'Starting {} worker tasks, progress output will be disabled. This may take a few minutes.' CLI_DOC_INITING = -- TODO: need translate! 'Loading documents ...' CLI_DOC_DONE = -- TODO: need translate! diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua index 2b43e954..f15e2b4f 100644 --- a/locale/zh-tw/setting.lua +++ b/locale/zh-tw/setting.lua @@ -48,6 +48,8 @@ config.diagnostics.disable = "停用的診斷(使用浮框括號內的程式碼)。" config.diagnostics.globals = "已定義的全域變數。" +config.diagnostics.globalsRegex = -- TODO: need translate! +"Find defined global variables using regex." config.diagnostics.severity = [[ 修改診斷等級。 @@ -439,3 +441,7 @@ command.addon_manager.open = -- TODO: need translate! 'Lua: Open Addon Manager ...' command.reloadFFIMeta = -- TODO: need translate! 'Lua: Reload luajit ffi meta' +command.startServer = -- TODO: need translate! +'Lua: (debug) Start Language Server' +command.stopServer = -- TODO: need translate! +'Lua: (debug) Stop Language Server' @@ -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 @@ -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 +) @@ -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/meta/3rd/example/config.json b/meta/3rd/example/config.json index 79170468..e6a040d1 100644 --- a/meta/3rd/example/config.json +++ b/meta/3rd/example/config.json @@ -5,7 +5,7 @@ "words" : ["thisIsAnExampleWord%.ifItExistsInFile%.thenTryLoadThisLibrary"], // list or matched file names. `.lua`, `.dll` and `.so` only "files" : ["thisIsAnExampleFile%.ifItExistsInWorkSpace%.thenTryLoadThisLibrary%.lua"], - // lsit of settings to be changed + // list of settings to be changed "settings" : { "Lua.runtime.version" : "LuaJIT", "Lua.diagnostics.globals" : [ diff --git a/meta/template/basic.lua b/meta/template/basic.lua index 4a9360fd..75cd1dee 100644 --- a/meta/template/basic.lua +++ b/meta/template/basic.lua @@ -339,3 +339,10 @@ function xpcall(f, msgh, arg1, ...) end ---@return T ... ---@nodiscard function unpack(list, i, j) end + +---@version 5.1 +---@generic T1, T2, T3, T4, T5, T6, T7, T8, T9 +---@param list {[1]: T1, [2]: T2, [3]: T3, [4]: T4, [5]: T5, [6]: T6, [7]: T7, [8]: T8, [9]: T9 } +---@return T1, T2, T3, T4, T5, T6, T7, T8, T9 +---@nodiscard +function unpack(list) end diff --git a/script/await.lua b/script/await.lua index 22745570..f252197f 100644 --- a/script/await.lua +++ b/script/await.lua @@ -176,6 +176,28 @@ function m.delay() return coroutine.yield() end +local throttledDelayer = {} +throttledDelayer.__index = throttledDelayer + +---@async +function throttledDelayer:delay() + if not m._enable then + return + end + self.calls = self.calls + 1 + if self.calls == self.factor then + self.calls = 0 + return m.delay() + end +end + +function m.newThrottledDelayer(factor) + return setmetatable({ + factor = factor, + calls = 0, + }, throttledDelayer) +end + --- stop then close ---@async function m.stop() diff --git a/script/brave/work.lua b/script/brave/work.lua index a6a7a41e..5727ba11 100644 --- a/script/brave/work.lua +++ b/script/brave/work.lua @@ -52,9 +52,17 @@ brave.on('loadProtoBySocket', function (param) coroutine.resume(parser) end + function lsclient:on_error(...) + log.error(...) + end + function lsmaster:on_data(data) lsclient:write(data) - net.update() + --net.update() + end + + function lsmaster:on_error(...) + log.error(...) end while true do @@ -65,7 +73,7 @@ end) brave.on('timer', function (time) local thread = require 'bee.thread' while true do - thread.sleep(time) + thread.sleep(math.floor(time * 1000)) brave.push('wakeup') end end) diff --git a/script/cli/check.lua b/script/cli/check.lua index 5ac9ea13..8b314f24 100644 --- a/script/cli/check.lua +++ b/script/cli/check.lua @@ -1,112 +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 = {} ----@async -lclient():start(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) +if numThreads > 1 then + print(lang.script('CLI_CHECK_MULTIPLE_WORKERS', numThreads)) +end - 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) - if os.clock() - lastClock > 0.2 then - lastClock = os.clock() - 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) - io.flush() - end + if process then + procs[#procs + 1] = process end - io.write('\x0D') -end) +end -local count = 0 -for uri, result in pairs(results) do - count = count + #result - if #result == 0 then - results[uri] = nil - end +for _, process in ipairs(procs) do + process:wait() end -if count == 0 then - print(lang.script('CLI_CHECK_SUCCESS')) -else - local outpath = CHECK_OUT_PATH - if outpath == nil then - outpath = LOGPATH .. '/check.json' - end - util.saveFile(outpath, jsonb.beautify(results)) +local outpath = CHECK_OUT_PATH +if outpath == nil then + outpath = LOGPATH .. '/check.json' +end - print(lang.script('CLI_CHECK_RESULTS', count, outpath)) +if numThreads > 1 then + local mergedResults = {} + local count = 0 + for i = 1, numThreads do + local result = json.decode(util.loadFile(logFileForThread(i)) or '[]') + for k, v in pairs(result) do + local entries = mergedResults[k] or {} + mergedResults[k] = entries + for _, entry in ipairs(v) do + entries[#entries + 1] = entry + count = count + 1 + end + end + end + util.saveFile(outpath, jsonb.beautify(mergedResults)) + if count == 0 then + print(lang.script('CLI_CHECK_SUCCESS')) + else + print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + end end diff --git a/script/cli/check_worker.lua b/script/cli/check_worker.lua new file mode 100644 index 00000000..23c34b2c --- /dev/null +++ b/script/cli/check_worker.lua @@ -0,0 +1,168 @@ +local lclient = require 'lclient'() +local furi = require 'file-uri' +local ws = require 'workspace' +local files = require 'files' +local diag = require 'provider.diagnostic' +local util = require 'utility' +local jsonb = require 'json-beautify' +local lang = require 'language' +local define = require 'proto.define' +local protoDiag = require 'proto.diagnostic' +local config = require 'config.config' +local fs = require 'bee.filesystem' +local provider = require 'provider' +local await = require 'await' +require 'plugin' +require 'vm' + +lang(LOCALE) + +local numThreads = tonumber(NUM_THREADS or 1) +local threadId = tonumber(THREAD_ID or 1) + +if type(CHECK_WORKER) ~= 'string' then + print(lang.script('CLI_CHECK_ERROR_TYPE', type(CHECK_WORKER))) + return +end + +local rootPath = fs.absolute(fs.path(CHECK_WORKER)):string() +local rootUri = furi.encode(rootPath) +if not rootUri then + print(lang.script('CLI_CHECK_ERROR_URI', rootPath)) + return +end +rootUri = rootUri:gsub("/$", "") + +if CHECKLEVEL then + if not define.DiagnosticSeverity[CHECKLEVEL] then + print(lang.script('CLI_CHECK_ERROR_LEVEL', 'Error, Warning, Information, Hint')) + return + end +end +local checkLevel = define.DiagnosticSeverity[CHECKLEVEL] or define.DiagnosticSeverity.Warning + +util.enableCloseFunction() + +-- Hash function used to distribute work. +local function hashString(str) + local hash = 0 + for i = 1, #str do + hash = (hash * 37 & 0xFFFFFFFF) + str:byte(i, i) + end + return hash +end + +local lastClock = os.clock() +local results = {} + +local function errorhandler(err) + print(err) + print(debug.traceback()) +end + +---@async +xpcall(lclient.start, errorhandler, lclient, function (client) + await.disable() + client:registerFakers() + + client:initialize { + rootUri = rootUri, + } + + client:register('textDocument/publishDiagnostics', function (params) + results[params.uri] = params.diagnostics + end) + + if not QUIET then + io.write(lang.script('CLI_CHECK_INITING')) + end + + provider.updateConfig(rootUri) + + ws.awaitReady(rootUri) + + local disables = util.arrayToHash(config.get(rootUri, 'Lua.diagnostics.disable')) + for name, serverity in pairs(define.DiagnosticDefaultSeverity) do + serverity = config.get(rootUri, 'Lua.diagnostics.severity')[name] or serverity + if serverity:sub(-1) == '!' then + serverity = serverity:sub(1, -2) + end + if define.DiagnosticSeverity[serverity] > checkLevel then + disables[name] = true + end + end + config.set(rootUri, 'Lua.diagnostics.disable', util.getTableKeys(disables, true)) + + -- Downgrade file opened status to Opened for everything to avoid reporting during compilation on files that do not belong to this thread + local diagStatus = config.get(rootUri, 'Lua.diagnostics.neededFileStatus') + for diag, status in pairs(diagStatus) do + if status == 'Any' or status == 'Any!' then + diagStatus[diag] = 'Opened!' + end + end + for diag, status in pairs(protoDiag.getDefaultStatus()) do + if status == 'Any' or status == 'Any!' then + diagStatus[diag] = 'Opened!' + end + end + config.set(rootUri, 'Lua.diagnostics.neededFileStatus', diagStatus) + + local uris = files.getChildFiles(rootUri) + local max = #uris + for i, uri in ipairs(uris) do + local hash = hashString(uri) % numThreads + 1 + if hash == threadId then + files.open(uri) + diag.doDiagnostic(uri, true) + -- Print regularly but always print the last entry to ensure that logs written to files don't look incomplete. + if (os.clock() - lastClock > 0.2 or i == #uris) and not QUIET then + lastClock = os.clock() + client:update() + local output = '\x0D' + .. ('>'):rep(math.ceil(i / max * 20)) + .. ('='):rep(20 - math.ceil(i / max * 20)) + .. ' ' + .. ('0'):rep(#tostring(max) - #tostring(i)) + .. tostring(i) .. '/' .. tostring(max) + io.write(output) + local filesWithErrors = 0 + local errors = 0 + for _, diags in pairs(results) do + filesWithErrors = filesWithErrors + 1 + errors = errors + #diags + end + if errors > 0 then + local errorDetails = ' [' .. lang.script('CLI_CHECK_PROGRESS', errors, filesWithErrors) .. ']' + io.write(errorDetails) + end + io.flush() + end + end + end + if not QUIET then + io.write('\x0D') + end +end) + +local count = 0 +for uri, result in pairs(results) do + count = count + #result + if #result == 0 then + results[uri] = nil + end +end + +local outpath = CHECK_OUT_PATH +if outpath == nil then + outpath = LOGPATH .. '/check.json' +end +-- Always write result, even if it's empty to make sure no one accidentally looks at an old output after a successful run. +util.saveFile(outpath, jsonb.beautify(results)) + +if not QUIET then + if count == 0 then + print(lang.script('CLI_CHECK_SUCCESS')) + else + print(lang.script('CLI_CHECK_RESULTS', count, outpath)) + end +end diff --git a/script/cli/init.lua b/script/cli/init.lua index d37c50ae..65e7e102 100644 --- a/script/cli/init.lua +++ b/script/cli/init.lua @@ -8,6 +8,11 @@ if _G['CHECK'] then os.exit(0, true) end +if _G['CHECK_WORKER'] then + require 'cli.check_worker' + os.exit(0, true) +end + if _G['DOC_UPDATE'] then require 'cli.doc' .runCLI() os.exit(0, true) diff --git a/script/config/env.lua b/script/config/env.lua new file mode 100644 index 00000000..ef5b31f2 --- /dev/null +++ b/script/config/env.lua @@ -0,0 +1,67 @@ +-- Handles loading environment arguments + +---Convert a string to boolean +---@param v string +local function strToBool(v) + return v == "true" +end + +---ENV args are defined here. +---- `name` is the ENV arg name +---- `key` is the value used to index `_G` for setting the argument +---- `converter` if present, will be used to convert the string value into another type +---@type { name: string, key: string, converter: fun(value: string): any }[] +local vars = { + { + name = "LLS_CHECK_LEVEL", + key = "CHECKLEVEL", + }, + { + name = "LLS_CHECK_PATH", + key = "CHECK", + }, + { + name = "LLS_CONFIG_PATH", + key = "CONFIGPATH", + }, + { + name = "LLS_DOC_OUT_PATH", + key = "DOC_OUT_PATH", + }, + { + name = "LLS_DOC_PATH", + key = "DOC", + }, + { + name = "LLS_FORCE_ACCEPT_WORKSPACE", + key = "FORCE_ACCEPT_WORKSPACE", + converter = strToBool, + }, + { + name = "LLS_LOCALE", + key = "LOCALE", + }, + { + name = "LLS_LOG_LEVEL", + key = "LOGLEVEL", + }, + { + name = "LLS_LOG_PATH", + key = "LOGPATH", + }, + { + name = "LLS_META_PATH", + key = "METAPATH", + }, +} + +for _, var in ipairs(vars) do + local value = os.getenv(var.name) + if value then + if var.converter then + value = var.converter(value) + end + + _G[var.key] = value + end +end diff --git a/script/config/template.lua b/script/config/template.lua index 49907419..e74a9f9c 100644 --- a/script/config/template.lua +++ b/script/config/template.lua @@ -242,6 +242,7 @@ local template = { >> util.deepCopy(define.BuiltIn), ['Lua.diagnostics.enable'] = Type.Boolean >> true, ['Lua.diagnostics.globals'] = Type.Array(Type.String), + ['Lua.diagnostics.globalsRegex'] = Type.Array(Type.String), ['Lua.diagnostics.disable'] = Type.Array(Type.String << util.getTableKeys(diag.getDiagAndErrNameMap(), true)), ['Lua.diagnostics.severity'] = Type.Hash( Type.String << util.getTableKeys(define.DiagnosticDefaultNeededFileStatus, true), diff --git a/script/core/completion/completion.lua b/script/core/completion/completion.lua index d047dd56..3a76472a 100644 --- a/script/core/completion/completion.lua +++ b/script/core/completion/completion.lua @@ -1357,6 +1357,13 @@ local function insertEnum(state, pos, src, enums, isInArray, mark) kind = define.CompletionItemKind.Function, insertText = insertText, } + elseif src.type == 'doc.enum' then + ---@cast src parser.object + if vm.docHasAttr(src, 'key') then + insertDocEnumKey(state, pos, src, enums) + else + insertDocEnum(state, pos, src, enums) + end elseif isInArray and src.type == 'doc.type.array' then for i, d in ipairs(vm.getDefs(src.node)) do insertEnum(state, pos, d, enums, isInArray, mark) @@ -1364,11 +1371,7 @@ local function insertEnum(state, pos, src, enums, isInArray, mark) elseif src.type == 'global' and src.cate == 'type' then for _, set in ipairs(src:getSets(state.uri)) do if set.type == 'doc.enum' then - if vm.docHasAttr(set, 'key') then - insertDocEnumKey(state, pos, set, enums) - else - insertDocEnum(state, pos, set, enums) - end + insertEnum(state, pos, set, enums, isInArray, mark) end end end @@ -1546,7 +1549,7 @@ local function tryWord(state, position, triggerCharacter, results) local env = guide.getENV(state.ast, startPos) if env then checkGlobal(state, word, startPos, position, env, false, results) - checkModule(state, word, startPos, results) + checkModule(state, word, startPos, results) end end end @@ -1585,8 +1588,8 @@ end local function findCall(state, position) local call guide.eachSourceContain(state.ast, position, function (src) - if src.type == 'call' then - if not call or call.start < src.start then + if src.type == 'call' and src.node.finish <= position then + if not call or call.start < src.start then call = src end end @@ -2256,6 +2259,35 @@ local function tryluaDocOfFunction(doc, results, pad) } end +---Checks for a lua symbol reference in comment +---@async +local function trySymbolReference(state, position, results) + local doc = getLuaDoc(state, position) + if not doc then + return + end + + local line = doc.originalComment.text ---@type string + local col = select(2, guide.rowColOf(position)) - 2 ---@type integer + + -- User will ask for completion at end of symbol name so we need to perform a reverse match to see if they are in a symbol reference + -- Matching in reverse allows the symbol to be of any length and we can still match all the way back to `](lua://` from right to left + local symbol = string.match(string.reverse(line), "%)?([%w%s-_.*]*)//:aul%(%]", #line - col) + + if symbol then + -- flip it back the right way around + symbol = string.reverse(symbol) + + for _, match in ipairs(wssymbol(symbol)) do + results[#results+1] = { + label = match.name, + kind = define.CompletionItemKind.Class, + insertText = match.name + } + end + end +end + ---@async local function tryLuaDoc(state, position, results) local doc = getLuaDoc(state, position) @@ -2327,6 +2359,7 @@ end ---@async local function tryCompletions(state, position, triggerCharacter, results) if getComment(state, position) then + trySymbolReference(state, position, results) tryLuaDoc(state, position, results) tryComment(state, position, results) return diff --git a/script/core/completion/postfix.lua b/script/core/completion/postfix.lua index 1331a0e4..b5f33315 100644 --- a/script/core/completion/postfix.lua +++ b/script/core/completion/postfix.lua @@ -220,6 +220,24 @@ register 'pairs' { end } +register 'unpack' { + function (state, source, callback) + if source.type ~= 'getglobal' + and source.type ~= 'getfield' + and source.type ~= 'getmethod' + and source.type ~= 'getindex' + and source.type ~= 'getlocal' + and source.type ~= 'call' + and source.type ~= 'table' then + return + end + local subber = subString(state) + callback(string.format('unpack(%s)' + , subber(source.start + 1, source.finish) + )) + end +} + register 'insert' { function (state, source, callback) if source.type ~= 'getglobal' diff --git a/script/core/diagnostics/assign-type-mismatch.lua b/script/core/diagnostics/assign-type-mismatch.lua index 8472e87c..6fa26553 100644 --- a/script/core/diagnostics/assign-type-mismatch.lua +++ b/script/core/diagnostics/assign-type-mismatch.lua @@ -52,13 +52,14 @@ return function (uri, callback) return end + local delayer = await.newThrottledDelayer(15) ---@async guide.eachSourceTypes(state.ast, checkTypes, function (source) local value = source.value if not value then return end - await.delay() + delayer:delay() if source.type == 'setlocal' then local locNode = vm.compileNode(source.node) if not locNode.hasDefined then diff --git a/script/core/diagnostics/discard-returns.lua b/script/core/diagnostics/discard-returns.lua index cef7ece5..37d36214 100644 --- a/script/core/diagnostics/discard-returns.lua +++ b/script/core/diagnostics/discard-returns.lua @@ -12,9 +12,10 @@ return function (uri, callback) end ---@async guide.eachSourceType(state.ast, 'call', function (source) - local parent = source.parent - if parent.type ~= 'function' - and parent.type ~= 'main' then + if not guide.isBlockType(source.parent) then + return + end + if source.parent.filter == source then return end await.delay() diff --git a/script/core/diagnostics/duplicate-doc-alias.lua b/script/core/diagnostics/duplicate-doc-alias.lua index 360358e4..7f71b63b 100644 --- a/script/core/diagnostics/duplicate-doc-alias.lua +++ b/script/core/diagnostics/duplicate-doc-alias.lua @@ -15,6 +15,7 @@ return function (uri, callback) return end + local merged = {} local cache = {} for _, doc in ipairs(state.ast.docs) do if doc.type == 'doc.alias' @@ -36,10 +37,11 @@ return function (uri, callback) finish = otherDoc.finish, uri = guide.getUri(otherDoc), } + merged[name] = merged[name] or vm.docHasAttr(otherDoc, 'partial') end end end - if #cache[name] > 1 then + if not merged[name] and #cache[name] > 1 then callback { start = (doc.alias or doc.enum).start, finish = (doc.alias or doc.enum).finish, diff --git a/script/core/diagnostics/duplicate-doc-field.lua b/script/core/diagnostics/duplicate-doc-field.lua index 098b41a4..6066ae53 100644 --- a/script/core/diagnostics/duplicate-doc-field.lua +++ b/script/core/diagnostics/duplicate-doc-field.lua @@ -15,7 +15,7 @@ local function getFieldEventName(doc) if docFunc.type ~= 'doc.type.function' then return nil end - for i = 1, 2 do + for i = 1, #docFunc.args do local arg = docFunc.args[i] if arg and arg.extends diff --git a/script/core/diagnostics/global-element.lua b/script/core/diagnostics/global-element.lua index e9dd46ce..a30ebbc6 100644 --- a/script/core/diagnostics/global-element.lua +++ b/script/core/diagnostics/global-element.lua @@ -17,6 +17,20 @@ local function isDocClass(source) return false end +local function isGlobalRegex(name, definedGlobalRegex) + if not definedGlobalRegex then + return false + end + + for _, pattern in ipairs(definedGlobalRegex) do + if name:match(pattern) then + return true + end + end + + return false +end + -- If global elements are discouraged by coding convention, this diagnostic helps with reminding about that -- Exceptions may be added to Lua.diagnostics.globals return function (uri, callback) @@ -26,6 +40,7 @@ return function (uri, callback) end local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex') guide.eachSourceType(ast.ast, 'setglobal', function (source) local name = guide.getKeyName(source) @@ -36,6 +51,9 @@ return function (uri, callback) if isDocClass(source) then return end + if isGlobalRegex(name, definedGlobalRegex) then + return + end if definedGlobal[name] == nil then definedGlobal[name] = false local global = vm.getGlobal('variable', name) diff --git a/script/core/diagnostics/init.lua b/script/core/diagnostics/init.lua index bb0489a8..e31a19f3 100644 --- a/script/core/diagnostics/init.lua +++ b/script/core/diagnostics/init.lua @@ -94,8 +94,9 @@ end ---@param name string ---@param isScopeDiag boolean ---@param response async fun(result: any) +---@param ignoreFileOpenState? boolean ---@return boolean -local function check(uri, name, isScopeDiag, response) +local function check(uri, name, isScopeDiag, response, ignoreFileOpenState) local disables = config.get(uri, 'Lua.diagnostics.disable') if util.arrayHas(disables, name) then return false @@ -107,7 +108,7 @@ local function check(uri, name, isScopeDiag, response) return false end - if status == 'Opened' and not files.isOpen(uri) then + if not ignoreFileOpenState and status == 'Opened' and not files.isOpen(uri) then return false end @@ -167,7 +168,8 @@ end ---@param isScopeDiag boolean ---@param response async fun(result: any) ---@param checked? async fun(name: string) -return function (uri, isScopeDiag, response, checked) +---@param ignoreFileOpenState? boolean +return function (uri, isScopeDiag, response, checked, ignoreFileOpenState) local ast = files.getState(uri) if not ast then return nil @@ -176,7 +178,7 @@ return function (uri, isScopeDiag, response, checked) for _, name in ipairs(buildDiagList()) do await.delay() local clock = os.clock() - local suc = check(uri, name, isScopeDiag, response) + local suc = check(uri, name, isScopeDiag, response, ignoreFileOpenState) if suc then local cost = os.clock() - clock diagCosts[name] = (diagCosts[name] or 0) + cost diff --git a/script/core/diagnostics/lowercase-global.lua b/script/core/diagnostics/lowercase-global.lua index 68bec234..c7e9294d 100644 --- a/script/core/diagnostics/lowercase-global.lua +++ b/script/core/diagnostics/lowercase-global.lua @@ -17,6 +17,20 @@ local function isDocClass(source) return false end +local function isGlobalRegex(name, definedGlobalRegex) + if not definedGlobalRegex then + return false + end + + for _, pattern in ipairs(definedGlobalRegex) do + if name:match(pattern) then + return true + end + end + + return false +end + -- 不允许定义首字母小写的全局变量(很可能是拼错或者漏删) return function (uri, callback) local ast = files.getState(uri) @@ -25,6 +39,7 @@ return function (uri, callback) end local definedGlobal = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) + local definedGlobalRegex = config.get(uri, 'Lua.diagnostics.globalsRegex') guide.eachSourceType(ast.ast, 'setglobal', function (source) local name = guide.getKeyName(source) @@ -42,6 +57,9 @@ return function (uri, callback) if isDocClass(source) then return end + if isGlobalRegex(name, definedGlobalRegex) then + return + end if definedGlobal[name] == nil then definedGlobal[name] = false local global = vm.getGlobal('variable', name) diff --git a/script/core/diagnostics/need-check-nil.lua b/script/core/diagnostics/need-check-nil.lua index 9c86939a..fe72d4ba 100644 --- a/script/core/diagnostics/need-check-nil.lua +++ b/script/core/diagnostics/need-check-nil.lua @@ -11,9 +11,10 @@ return function (uri, callback) return end + local delayer = await.newThrottledDelayer(500) ---@async guide.eachSourceType(state.ast, 'getlocal', function (src) - await.delay() + delayer:delay() local checkNil local nxt = src.next if nxt then diff --git a/script/core/diagnostics/redundant-value.lua b/script/core/diagnostics/redundant-value.lua index 6f60303b..ceb090c9 100644 --- a/script/core/diagnostics/redundant-value.lua +++ b/script/core/diagnostics/redundant-value.lua @@ -11,8 +11,9 @@ return function (uri, callback) return end + local delayer = await.newThrottledDelayer(50000) guide.eachSource(state.ast, function (src) ---@async - await.delay() + delayer:delay() if src.redundant then callback { start = src.start, diff --git a/script/core/diagnostics/trailing-space.lua b/script/core/diagnostics/trailing-space.lua index 2e0398b2..1fdccf80 100644 --- a/script/core/diagnostics/trailing-space.lua +++ b/script/core/diagnostics/trailing-space.lua @@ -10,9 +10,10 @@ return function (uri, callback) if not state or not text then return end + local delayer = await.newThrottledDelayer(5000) local lines = state.lines for i = 0, #lines do - await.delay() + delayer:delay() local startOffset = lines[i] local finishOffset = text:find('[\r\n]', startOffset) or (#text + 1) local lastOffset = finishOffset - 1 diff --git a/script/core/diagnostics/unbalanced-assignments.lua b/script/core/diagnostics/unbalanced-assignments.lua index c21ca993..f72e1fd5 100644 --- a/script/core/diagnostics/unbalanced-assignments.lua +++ b/script/core/diagnostics/unbalanced-assignments.lua @@ -41,9 +41,10 @@ return function (uri, callback, code) end end + local delayer = await.newThrottledDelayer(1000) ---@async guide.eachSourceTypes(ast.ast, types, function (source) - await.delay() + delayer:delay() checkSet(source) end) end diff --git a/script/core/implementation.lua b/script/core/implementation.lua new file mode 100644 index 00000000..e48e2f73 --- /dev/null +++ b/script/core/implementation.lua @@ -0,0 +1,171 @@ +local workspace = require 'workspace' +local files = require 'files' +local vm = require 'vm' +local findSource = require 'core.find-source' +local guide = require 'parser.guide' +local rpath = require 'workspace.require-path' +local jumpSource = require 'core.jump-source' + +local function sortResults(results) + -- 先按照顺序排序 + table.sort(results, function (a, b) + local u1 = guide.getUri(a.target) + local u2 = guide.getUri(b.target) + if u1 == u2 then + return a.target.start < b.target.start + else + return u1 < u2 + end + end) + -- 如果2个结果处于嵌套状态,则取范围小的那个 + local lf, lu + for i = #results, 1, -1 do + local res = results[i].target + local f = res.finish + local uri = guide.getUri(res) + if lf and f > lf and uri == lu then + table.remove(results, i) + else + lu = uri + lf = f + end + end +end + +local accept = { + ['local'] = true, + ['setlocal'] = true, + ['getlocal'] = true, + ['label'] = true, + ['goto'] = true, + ['field'] = true, + ['method'] = true, + ['setglobal'] = true, + ['getglobal'] = true, + ['string'] = true, + ['boolean'] = true, + ['number'] = true, + ['integer'] = true, + ['...'] = true, + + ['doc.type.name'] = true, + ['doc.class.name'] = true, + ['doc.extends.name'] = true, + ['doc.alias.name'] = true, + ['doc.cast.name'] = true, + ['doc.enum.name'] = true, + ['doc.field.name'] = true, +} + +local function convertIndex(source) + if not source then + return + end + if source.type == 'string' + or source.type == 'boolean' + or source.type == 'number' + or source.type == 'integer' then + local parent = source.parent + if not parent then + return + end + if parent.type == 'setindex' + or parent.type == 'getindex' + or parent.type == 'tableindex' then + return parent + end + end + return source +end + +---@async +return function (uri, offset) + local ast = files.getState(uri) + if not ast then + return nil + end + + local source = convertIndex(findSource(ast, offset, accept)) + if not source then + return nil + end + + local results = {} + + local defs = vm.getRefs(source) + + for _, src in ipairs(defs) do + if not guide.isAssign(src) then + goto CONTINUE + end + if src.type == 'global' then + goto CONTINUE + end + local root = guide.getRoot(src) + if not root then + goto CONTINUE + end + if src.type == 'self' then + goto CONTINUE + end + src = src.field or src.method or src + if src.type == 'getindex' + or src.type == 'setindex' + or src.type == 'tableindex' then + src = src.index + if not src then + goto CONTINUE + end + if not guide.isLiteral(src) then + goto CONTINUE + end + end + if src.type == 'doc.type.function' + or src.type == 'doc.type.table' + or src.type == 'doc.type.boolean' + or src.type == 'doc.type.integer' + or src.type == 'doc.type.string' then + goto CONTINUE + end + if src.type == 'doc.class' then + goto CONTINUE + end + if src.type == 'doc.alias' then + goto CONTINUE + end + if src.type == 'doc.enum' then + goto CONTINUE + end + if src.type == 'doc.type.field' then + goto CONTINUE + end + if src.type == 'doc.class.name' + or src.type == 'doc.alias.name' + or src.type == 'doc.enum.name' + or src.type == 'doc.field.name' then + goto CONTINUE + end + if src.type == 'doc.generic.name' then + goto CONTINUE + end + if src.type == 'doc.param' then + goto CONTINUE + end + + results[#results+1] = { + target = src, + uri = root.uri, + source = source, + } + ::CONTINUE:: + end + + if #results == 0 then + return nil + end + + sortResults(results) + jumpSource(results) + + return results +end diff --git a/script/core/signature.lua b/script/core/signature.lua index 98018b21..c52dcff3 100644 --- a/script/core/signature.lua +++ b/script/core/signature.lua @@ -94,10 +94,7 @@ local function isEventNotMatch(call, src) return false end local literal, index - for i = 1, 2 do - if not call.args[i] then - break - end + for i = 1, #call.args do literal = guide.getLiteral(call.args[i]) if literal then index = i diff --git a/script/core/type-definition.lua b/script/core/type-definition.lua index 0a821f25..d9939eb0 100644 --- a/script/core/type-definition.lua +++ b/script/core/type-definition.lua @@ -52,8 +52,9 @@ local accept = { ['doc.class.name'] = true, ['doc.extends.name'] = true, ['doc.alias.name'] = true, + ['doc.cast.name'] = true, ['doc.enum.name'] = true, - ['doc.see.name'] = true, + ['doc.field.name'] = true, } local function checkRequire(source, offset) diff --git a/script/core/workspace-symbol.lua b/script/core/workspace-symbol.lua index a41cfc1d..f831aa03 100644 --- a/script/core/workspace-symbol.lua +++ b/script/core/workspace-symbol.lua @@ -55,7 +55,6 @@ local function searchFile(uri, key, results) end) end ----@async ---@param key string ---@param suri? uri ---@param results table[] @@ -88,12 +87,10 @@ local function searchGlobalAndClass(key, suri, results) source = set, } end - await.delay() end end end ----@async ---@param key string ---@param suri? uri ---@param results table[] @@ -137,7 +134,6 @@ local function searchClassField(key, suri, results) end) end ----@async ---@param key string ---@param suri? uri ---@param results table[] @@ -147,11 +143,9 @@ local function searchWords(key, suri, results) if #results > 1000 then break end - await.delay() end end ----@async ---@param key string ---@param suri? uri ---@param includeWords? boolean diff --git a/script/filewatch.lua b/script/filewatch.lua index d4850ca1..6520afe6 100644 --- a/script/filewatch.lua +++ b/script/filewatch.lua @@ -13,7 +13,7 @@ local function isExists(filename) if not suc or not exists then return false end - if plat.OS ~= 'Windows' then + if plat.os ~= 'windows' then return true end local res = fs.fullpath(path) diff --git a/script/global.d.lua b/script/global.d.lua index ead46ca9..daac5f6c 100644 --- a/script/global.d.lua +++ b/script/global.d.lua @@ -98,3 +98,11 @@ FORCE_ACCEPT_WORKSPACE = false -- Trust all plugins that are being loaded by workspace config files. -- This is potentially unsafe for normal use and meant for usage in CI environments only. TRUST_ALL_PLUGINS = false + +NUM_THREADS = 1 + +THREAD_ID = 1 + +CHECK_WORKER = '' + +QUIET = false diff --git a/script/library.lua b/script/library.lua index 4446797a..2e925e8d 100644 --- a/script/library.lua +++ b/script/library.lua @@ -365,7 +365,7 @@ local function loadSingle3rdConfig(libraryDir) end if cfg.files then for i, filename in ipairs(cfg.files) do - if plat.OS == 'Windows' then + if plat.os == 'windows' then filename = filename:gsub('/', '\\') else filename = filename:gsub('\\', '/') diff --git a/script/meta/bee/filesystem.lua b/script/meta/bee/filesystem.lua index c4267b97..0c7e41a8 100644 --- a/script/meta/bee/filesystem.lua +++ b/script/meta/bee/filesystem.lua @@ -24,7 +24,7 @@ end function fsPath:stem() end ----@return fs.path +---@return string function fsPath:extension() end diff --git a/script/meta/bee/socket.lua b/script/meta/bee/socket.lua index 1724cbb3..55c349a6 100644 --- a/script/meta/bee/socket.lua +++ b/script/meta/bee/socket.lua @@ -8,9 +8,13 @@ ---| 'udp6' ---@class bee.socket ----@overload fun(protocol: bee.socket.protocol): bee.socket.fd?, string? local socket = {} +---@param protocol bee.socket.protocol +---@return bee.socket.fd? +---@return string? +function socket.create(protocol) end + ---@param readfds? bee.socket.fd[] ---@param writefds? bee.socket.fd[] ---@param timeout number diff --git a/script/meta/bee/thread.lua b/script/meta/bee/thread.lua index 6b4323a4..a29ce971 100644 --- a/script/meta/bee/thread.lua +++ b/script/meta/bee/thread.lua @@ -1,4 +1,4 @@ ----@meta +---@meta bee.thread ---@class bee.thread local thread = {} @@ -17,6 +17,9 @@ function thread.channel(name) end ---@return bee.thread.thread function thread.thread(script) end +---@return string? +function thread.errlog() end + ---@class bee.thread.channel local channel = {} diff --git a/script/parser/compile.lua b/script/parser/compile.lua index 8dd772db..d5a2e162 100644 --- a/script/parser/compile.lua +++ b/script/parser/compile.lua @@ -2188,13 +2188,14 @@ local function parseActions() end end -local function parseParams(params) +local function parseParams(params, isLambda) local lastSep local hasDots + local endToken = isLambda and '|' or ')' while true do skipSpace() local token = Tokens[Index + 1] - if not token or token == ')' then + if not token or token == endToken then if lastSep then missName() end @@ -2269,7 +2270,7 @@ local function parseParams(params) Index = Index + 2 goto CONTINUE end - skipUnknownSymbol '%,%)%.' + skipUnknownSymbol ('%,%' .. endToken .. '%.') ::CONTINUE:: end return params @@ -2393,6 +2394,91 @@ local function parseFunction(isLocal, isAction) return func end +local function parseLambda(isDoublePipe) + local lambdaLeft = getPosition(Tokens[Index], 'left') + local lambdaRight = getPosition(Tokens[Index], 'right') + local lambda = { + type = 'function', + start = lambdaLeft, + finish = lambdaRight, + bstart = lambdaRight, + keyword = { + [1] = lambdaLeft, + [2] = lambdaRight, + }, + hasReturn = true + } + Index = Index + 2 + local pipeLeft = getPosition(Tokens[Index], 'left') + local pipeRight = getPosition(Tokens[Index], 'right') + skipSpace(true) + local params + local LastLocalCount = LocalCount + -- if nonstandardSymbol for '||' is true it is possible for token to be || when there are no params + if isDoublePipe then + params = { + start = pipeLeft, + finish = pipeRight, + parent = lambda, + type = 'funcargs' + } + else + -- fake chunk to store locals + pushChunk(lambda) + LocalCount = 0 + params = parseParams({}, true) + params.type = 'funcargs' + params.start = pipeLeft + params.finish = lastRightPosition() + params.parent = lambda + lambda.args = params + skipSpace() + if Tokens[Index + 1] == '|' then + pipeRight = getPosition(Tokens[Index], 'right') + lambda.finish = pipeRight + lambda.bstart = pipeRight + if params then + params.finish = pipeRight + end + Index = Index + 2 + skipSpace() + else + lambda.finish = lastRightPosition() + lambda.bstart = lambda.finish + if params then + params.finish = lambda.finish + end + missSymbol '|' + end + end + local child = parseExp() + + + -- don't want popChunk logic here as this is not a real chunk + Chunk[#Chunk] = nil + + if child then + -- create dummy return + local rtn = { + type = 'return', + start = child.start, + finish = child.finish, + parent = lambda, + [1] = child} + child.parent = rtn + lambda[1] = rtn + lambda.returns = {rtn} + lambda.finish = child.finish + lambda.keyword[3] = child.finish + lambda.keyword[4] = child.finish + else + lambda.finish = lastRightPosition() + missExp() + end + LocalCount = LastLocalCount + return lambda +end + local function checkNeedParen(source) local token = Tokens[Index + 1] if token ~= '.' @@ -2485,6 +2571,12 @@ local function parseExpUnit() return parseFunction() end + -- FIXME: Use something other than nonstandardSymbol to check for lambda support + if State.options.nonstandardSymbol['|lambda|'] and (token == '|' + or token == '||') then + return parseLambda(token == '||') + end + local node = parseName() if node then local nameNode = resolveName(node) diff --git a/script/parser/guide.lua b/script/parser/guide.lua index ac7a5ce0..e42f2acd 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -161,7 +161,7 @@ local childMap = { ['doc'] = {'#'}, ['doc.class'] = {'class', '#extends', '#signs', 'docAttr', 'comment'}, ['doc.type'] = {'#types', 'name', 'comment'}, - ['doc.alias'] = {'alias', 'extends', 'comment'}, + ['doc.alias'] = {'alias', 'docAttr', 'extends', 'comment'}, ['doc.enum'] = {'enum', 'extends', 'comment', 'docAttr'}, ['doc.param'] = {'param', 'extends', 'comment'}, ['doc.return'] = {'#returns', 'comment'}, diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index e18e34aa..7fd73c91 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -157,6 +157,7 @@ Symbol <- ({} { ---@field generics? parser.object[] ---@field generic? parser.object ---@field docAttr? parser.object +---@field pattern? string local function parseTokens(text, offset) Ci = 0 @@ -1025,6 +1026,7 @@ local docSwitch = util.switch() local result = { type = 'doc.alias', } + result.docAttr = parseDocAttr(result) result.alias = parseName('doc.alias.name', result) if not result.alias then pushWarning { diff --git a/script/plugin.lua b/script/plugin.lua index 26211a42..b8ecfb6a 100644 --- a/script/plugin.lua +++ b/script/plugin.lua @@ -27,7 +27,7 @@ function m.showError(scp, err) client.showMessage('Error', lang.script('PLUGIN_RUNTIME_ERROR', scp:get('pluginPath'), err)) end ----@alias plugin.event 'OnSetText' | 'OnTransformAst' +---@alias plugin.event 'OnSetText' | 'OnTransformAst' | 'ResolveRequire' ---@param event plugin.event function m.dispatch(event, uri, ...) @@ -156,7 +156,7 @@ local function initPlugin(uri) end for i, pluginConfigPath in ipairs(pluginConfigPaths) do local myArgs = args - if args then + if args and not args[1] then for k, v in pairs(args) do if pluginConfigPath:find(k, 1, true) then myArgs = v diff --git a/script/proto/proto.lua b/script/proto/proto.lua index b0d5d1a9..dff7063d 100644 --- a/script/proto/proto.lua +++ b/script/proto/proto.lua @@ -55,7 +55,6 @@ function m.send(data) io.write(buf) elseif m.mode == 'socket' then m.client:write(buf) - net.update() end end @@ -232,6 +231,7 @@ end function m.listen(mode, socketPort) m.mode = mode if mode == 'stdio' then + log.info('Listen Mode: stdio') if platform.os == 'windows' then local windows = require 'bee.windows' windows.filemode(io.stdin, 'b') @@ -247,6 +247,10 @@ function m.listen(mode, socketPort) local server = net.listen('unix', unixPath) + log.info('Listen Mode: socket') + log.info('Listen Port:', socketPort) + log.info('Listen Path:', unixPath) + assert(server) local dummyClient = { @@ -258,15 +262,14 @@ function m.listen(mode, socketPort) } m.client = dummyClient - local t = timer.loop(0.1, function () - net.update() - end) - - function server:on_accept(client) - t:remove() + function server:on_accepted(client) m.client = client client:write(dummyClient.buf) - net.update() + return true + end + + function server:on_error(...) + log.error(...) end pub.task('loadProtoBySocket', { diff --git a/script/provider/diagnostic.lua b/script/provider/diagnostic.lua index ebbe3e36..c52c7e89 100644 --- a/script/provider/diagnostic.lua +++ b/script/provider/diagnostic.lua @@ -246,6 +246,9 @@ local function isValid(uri) if not config.get(uri, 'Lua.diagnostics.enable') then return false end + if not ws.isReady(uri) then + return false + end if files.isLibrary(uri, true) then local status = config.get(uri, 'Lua.diagnostics.libraryFiles') if status == 'Disable' then @@ -275,7 +278,7 @@ local function isValid(uri) end ---@async -function m.doDiagnostic(uri, isScopeDiag) +function m.doDiagnostic(uri, isScopeDiag, ignoreFileState) if not isValid(uri) then return end @@ -348,7 +351,7 @@ function m.doDiagnostic(uri, isScopeDiag) lastDiag[#lastDiag] = nil end end - end) + end, ignoreFileState) lastDiag = nil pushResult() @@ -575,7 +578,7 @@ function m.awaitDiagnosticsScope(suri, callback) finished = true end -function m.diagnosticsScope(uri, force) +function m.diagnosticsScope(uri, force, ignoreFileOpenState) if not ws.isReady(uri) then return end @@ -592,7 +595,7 @@ function m.diagnosticsScope(uri, force) await.call(function () ---@async await.sleep(0.0) m.awaitDiagnosticsScope(uri, function (fileUri) - xpcall(m.doDiagnostic, log.error, fileUri, true) + xpcall(m.doDiagnostic, log.error, fileUri, true, ignoreFileOpenState) end) end, id) end diff --git a/script/provider/markdown.lua b/script/provider/markdown.lua index fe1b26b2..07c18ef9 100644 --- a/script/provider/markdown.lua +++ b/script/provider/markdown.lua @@ -1,3 +1,6 @@ +local wssymbol = require("core.workspace-symbol") +local guide = require("parser.guide") + ---@class markdown local mt = {} mt.__index = mt @@ -5,6 +8,33 @@ mt.__name = 'markdown' mt._splitLine = false +---Converts `[mySymbol](lua://mySymbol)` into a link that points to the origin of `mySymbol`. +---@param txt string +local function processSymbolReferences(txt) + local function replacer(linkText, symbol) + local source ---@type table + + for _, match in ipairs(wssymbol(symbol)) do + if match.name == symbol then + source = match.source + break + end + end + + if not source then + log.warn(string.format("Failed to find source of %q symbol in markdown comment", symbol)) + return + end + + local row, _ = guide.rowColOf(source.start) + local uri = string.format("%s#%i", guide.getUri(source), row + 1) + + return string.format("[%s](%s)", linkText, uri) + end + + return string.gsub(txt, "%[([^]]*)%]%(lua://([^)]+)%)", replacer) +end + function mt:__tostring() return self:string() end @@ -104,7 +134,7 @@ function mt:string(nl) end end end - lines[#lines+1] = obj.text + lines[#lines + 1] = processSymbolReferences(obj.text) language = obj.language end end diff --git a/script/provider/provider.lua b/script/provider/provider.lua index a791e980..15e78b9a 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -368,6 +368,39 @@ m.register 'textDocument/hover' { end } +local function convertDefinitionResult(state, result) + local response = {} + for i, info in ipairs(result) do + ---@type uri + local targetUri = info.uri + if targetUri then + local targetState = files.getState(targetUri) + if targetState then + if client.getAbility 'textDocument.definition.linkSupport' then + response[i] = converter.locationLink(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(targetState, info.target.start, info.target.finish) + , converter.packRange(state, info.source.start, info.source.finish) + ) + else + response[i] = converter.location(targetUri + , converter.packRange(targetState, info.target.start, info.target.finish) + ) + end + else + response[i] = converter.location( + targetUri, + converter.range( + converter.position(guide.rowColOf(info.target.start)), + converter.position(guide.rowColOf(info.target.finish)) + ) + ) + end + end + end + return response +end + m.register 'textDocument/definition' { capability = { definitionProvider = true, @@ -388,35 +421,7 @@ m.register 'textDocument/definition' { if not result then return nil end - local response = {} - for i, info in ipairs(result) do - ---@type uri - local targetUri = info.uri - if targetUri then - local targetState = files.getState(targetUri) - if targetState then - if client.getAbility 'textDocument.definition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(state, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - ) - end - else - response[i] = converter.location( - targetUri, - converter.range( - converter.position(guide.rowColOf(info.target.start)), - converter.position(guide.rowColOf(info.target.finish)) - ) - ) - end - end - end + local response = convertDefinitionResult(state, result) return response end } @@ -441,27 +446,32 @@ m.register 'textDocument/typeDefinition' { if not result then return nil end - local response = {} - for i, info in ipairs(result) do - ---@type uri - local targetUri = info.uri - if targetUri then - local targetState = files.getState(targetUri) - if targetState then - if client.getAbility 'textDocument.typeDefinition.linkSupport' then - response[i] = converter.locationLink(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(targetState, info.target.start, info.target.finish) - , converter.packRange(state, info.source.start, info.source.finish) - ) - else - response[i] = converter.location(targetUri - , converter.packRange(targetState, info.target.start, info.target.finish) - ) - end - end - end + local response = convertDefinitionResult(state, result) + return response + end +} + +m.register 'textDocument/implementation' { + capability = { + implementationProvider = true, + }, + abortByFileUpdate = true, + ---@async + function (params) + local uri = files.getRealUri(params.textDocument.uri) + workspace.awaitReady(uri) + local _ <close> = progress.create(uri, lang.script.WINDOW_PROCESSING_TYPE_DEFINITION, 0.5) + local state = files.getState(uri) + if not state then + return + end + local core = require 'core.implementation' + local pos = converter.unpackPosition(state, params.position) + local result = core(uri, pos) + if not result then + return nil end + local response = convertDefinitionResult(state, result) return response end } @@ -1209,7 +1219,7 @@ m.register '$/status/click' { if result == titleDiagnostic then local diagnostic = require 'provider.diagnostic' for _, scp in ipairs(workspace.folders) do - diagnostic.diagnosticsScope(scp.uri, true) + diagnostic.diagnosticsScope(scp.uri, true, true) end elseif result == 'Restart Server' then local diag = require 'provider.diagnostic' diff --git a/script/service/net.lua b/script/service/net.lua index 2019406e..bf77e7df 100644 --- a/script/service/net.lua +++ b/script/service/net.lua @@ -1,5 +1,7 @@ local socket = require "bee.socket" local select = require "bee.select" +local fs = require "bee.filesystem" + local selector = select.create() local SELECT_READ <const> = select.SELECT_READ local SELECT_WRITE <const> = select.SELECT_WRITE @@ -39,7 +41,7 @@ end local function on_event(self, name, ...) local f = self._event[name] if f then - f(self, ...) + return f(self, ...) end end @@ -90,69 +92,31 @@ local function close_write(self) close(self) end end -function stream:select_r() - local data = self._fd:recv() - if data == nil then - self:close() - elseif data == false then - else - on_event(self, "data", data) - end -end -function stream:select_w() - local n = self._fd:send(self._writebuf) - if n == nil then - self.shutdown_w = true - close_write(self) - elseif n == false then - else - self._writebuf = self._writebuf:sub(n + 1) - if self._writebuf == "" then - close_write(self) - end - end -end local function update_stream(s, event) if event & SELECT_READ ~= 0 then - s:select_r() + local data = s._fd:recv() + if data == nil then + s:close() + elseif data == false then + else + on_event(s, "data", data) + end end if event & SELECT_WRITE ~= 0 then - s:select_w() - end -end - -local function accept_stream(fd) - local s = setmetatable({ - _fd = fd, - _flags = SELECT_READ, - _event = {}, - _writebuf = "", - shutdown_r = false, - shutdown_w = false, - }, stream_mt) - selector:event_add(fd, SELECT_READ, function (event) - update_stream(s, event) - end) - return s -end -local function connect_stream(s) - setmetatable(s, stream_mt) - selector:event_del(s._fd) - if s._writebuf ~= "" then - s._flags = SELECT_READ | SELECT_WRITE - selector:event_add(s._fd, SELECT_READ | SELECT_WRITE, function (event) - update_stream(s, event) - end) - s:select_w() - else - s._flags = SELECT_READ - selector:event_add(s._fd, SELECT_READ, function (event) - update_stream(s, event) - end) + local n = s._fd:send(s._writebuf) + if n == nil then + s.shutdown_w = true + close_write(s) + elseif n == false then + else + s._writebuf = s._writebuf:sub(n + 1) + if s._writebuf == "" then + close_write(s) + end + end end end - local listen_mt = {} local listen = {} listen_mt.__index = listen @@ -168,32 +132,6 @@ function listen:close() self.shutdown_r = true close(self) end -local function new_listen(fd) - local s = { - _fd = fd, - _flags = SELECT_READ, - _event = {}, - shutdown_r = false, - shutdown_w = true, - } - selector:event_add(fd, SELECT_READ, function () - local newfd, err = fd:accept() - if not newfd then - on_event(s, "error", err) - return - end - local ok, err = newfd:status() - if not ok then - on_event(s, "error", err) - return - end - if newfd:status() then - local news = accept_stream(newfd) - on_event(s, "accept", news) - end - end) - return setmetatable(s, listen_mt) -end local connect_mt = {} local connect = {} @@ -216,7 +154,84 @@ function connect:close() self.shutdown_w = true close(self) end -local function new_connect(fd) + +local m = {} + +function m.listen(protocol, address, port) + local fd; do + local err + fd, err = socket.create(protocol) + if not fd then + return nil, err + end + if protocol == "unix" then + fs.remove(address) + end + end + do + local ok, err = fd:bind(address, port) + if not ok then + fd:close() + return nil, err + end + end + do + local ok, err = fd:listen() + if not ok then + fd:close() + return nil, err + end + end + local s = { + _fd = fd, + _flags = SELECT_READ, + _event = {}, + shutdown_r = false, + shutdown_w = true, + } + selector:event_add(fd, SELECT_READ, function () + local new_fd, err = fd:accept() + if new_fd == nil then + fd:close() + on_event(s, "error", err) + return + elseif new_fd == false then + else + local new_s = setmetatable({ + _fd = new_fd, + _flags = SELECT_READ, + _event = {}, + _writebuf = "", + shutdown_r = false, + shutdown_w = false, + }, stream_mt) + if on_event(s, "accepted", new_s) then + selector:event_add(new_fd, new_s._flags, function (event) + update_stream(new_s, event) + end) + else + new_fd:close() + end + end + end) + return setmetatable(s, listen_mt) +end + +function m.connect(protocol, address, port) + local fd; do + local err + fd, err = socket.create(protocol) + if not fd then + return nil, err + end + end + do + local ok, err = fd:connect(address, port) + if ok == nil then + fd:close() + return nil, err + end + end local s = { _fd = fd, _flags = SELECT_WRITE, @@ -228,51 +243,29 @@ local function new_connect(fd) selector:event_add(fd, SELECT_WRITE, function () local ok, err = fd:status() if ok then - connect_stream(s) - on_event(s, "connect") + on_event(s, "connected") + setmetatable(s, stream_mt) + if s._writebuf ~= "" then + update_stream(s, SELECT_WRITE) + if s._writebuf ~= "" then + s._flags = SELECT_READ | SELECT_WRITE + else + s._flags = SELECT_READ + end + else + s._flags = SELECT_READ + end + selector:event_add(s._fd, s._flags, function (event) + update_stream(s, event) + end) else - on_event(s, "error", err) s:close() + on_event(s, "error", err) end end) return setmetatable(s, connect_mt) end -local m = {} - -function m.listen(protocol, ...) - local fd, err = socket(protocol) - if not fd then - return nil, err - end - local ok - ok, err = fd:bind(...) - if not ok then - fd:close() - return nil, err - end - ok, err = fd:listen() - if not ok then - fd:close() - return nil, err - end - return new_listen(fd) -end - -function m.connect(protocol, ...) - local fd, err = socket(protocol) - if not fd then - return nil, err - end - local ok - ok, err = fd:connect(...) - if ok == nil then - fd:close() - return nil, err - end - return new_connect(fd) -end - function m.update(timeout) for func, event in selector:wait(timeout or 0) do func(event) diff --git a/script/service/service.lua b/script/service/service.lua index b6056390..c3afd4cf 100644 --- a/script/service/service.lua +++ b/script/service/service.lua @@ -12,6 +12,7 @@ local ws = require 'workspace' local time = require 'bee.time' local fw = require 'filewatch' local furi = require 'file-uri' +local net = require 'service.net' require 'jsonc' require 'json-beautify' @@ -168,6 +169,7 @@ function m.eventLoop() end local function doSomething() + net.update() timer.update() pub.step(false) if await.step() then @@ -180,7 +182,7 @@ function m.eventLoop() local function sleep() idle() for _ = 1, 10 do - thread.sleep(0.1) + net.update(100) if doSomething() then return end diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua index fc8f7c52..e1b1b43b 100644 --- a/script/vm/compiler.lua +++ b/script/vm/compiler.lua @@ -550,11 +550,14 @@ local function matchCall(source) or call.node ~= source then return end - local funcs = vm.getMatchedFunctions(source, call.args) local myNode = vm.getNode(source) if not myNode then return end + local funcs = vm.getExactMatchedFunctions(source, call.args) + if not funcs then + return + end local needRemove for n in myNode:eachObject() do if n.type == 'function' @@ -870,7 +873,7 @@ local function compileCallArgNode(arg, call, callNode, fixIndex, myIndex) ---@type integer?, table<any, boolean>? local eventIndex, eventMap if call.args then - for i = 1, 2 do + for i = 1, 10 do local eventArg = call.args[i + fixIndex] if not eventArg then break @@ -1055,7 +1058,7 @@ local function compileFunctionParam(func, source) local derviationParam = config.get(guide.getUri(func), 'Lua.type.inferParamType') if derviationParam and func.parent.type == 'local' and func.parent.ref then local refs = func.parent.ref - local found + local found for _, ref in ipairs(refs) do if ref.parent.type ~= 'call' then goto continue @@ -1069,13 +1072,13 @@ local function compileFunctionParam(func, source) local callerArg = caller.args[index] if callerArg then vm.setNode(source, vm.compileNode(callerArg)) - finded = true + found = true end end end ::continue:: end - return finded + return found end end diff --git a/script/vm/function.lua b/script/vm/function.lua index c6df6349..1e308317 100644 --- a/script/vm/function.lua +++ b/script/vm/function.lua @@ -1,6 +1,7 @@ ---@class vm local vm = require 'vm.vm' local guide = require 'parser.guide' +local util = require 'utility' ---@param arg parser.object ---@return parser.object? @@ -267,6 +268,9 @@ end ---@return integer def function vm.countReturnsOfCall(func, args, mark) local funcs = vm.getMatchedFunctions(func, args, mark) + if not funcs then + return 0, math.huge, 0 + end ---@type integer?, number?, integer? local min, max, def for _, f in ipairs(funcs) do @@ -329,22 +333,71 @@ function vm.countList(list, mark) return min, max, def end +---@param uri uri +---@param args parser.object[] +---@return boolean +local function isAllParamMatched(uri, args, params) + if not params then + return false + end + for i = 1, #args do + if not params[i] then + break + end + local argNode = vm.compileNode(args[i]) + local defNode = vm.compileNode(params[i]) + if not vm.canCastType(uri, defNode, argNode) then + return false + end + end + return true +end + ---@param func parser.object ----@param args parser.object[]? +---@param args? parser.object[] +---@return parser.object[]? +function vm.getExactMatchedFunctions(func, args) + local funcs = vm.getMatchedFunctions(func, args) + if not args or not funcs then + return funcs + end + if #funcs == 1 then + return funcs + end + local uri = guide.getUri(func) + local needRemove + for i, n in ipairs(funcs) do + if vm.isVarargFunctionWithOverloads(n) + or not isAllParamMatched(uri, args, n.args) then + if not needRemove then + needRemove = {} + end + needRemove[#needRemove+1] = i + end + end + if not needRemove then + return funcs + end + if #needRemove == #funcs then + return nil + end + util.tableMultiRemove(funcs, needRemove) + return funcs +end + +---@param func parser.object +---@param args? parser.object[] ---@param mark? table ----@return parser.object[] +---@return parser.object[]? function vm.getMatchedFunctions(func, args, mark) local funcs = {} local node = vm.compileNode(func) for n in node:eachObject() do - if (n.type == 'function' and not vm.isVarargFunctionWithOverloads(n)) + if n.type == 'function' or n.type == 'doc.type.function' then funcs[#funcs+1] = n end end - if #funcs <= 1 then - return funcs - end local amin, amax = vm.countList(args, mark) @@ -357,7 +410,7 @@ function vm.getMatchedFunctions(func, args, mark) end if #matched == 0 then - return funcs + return nil else return matched end @@ -372,23 +425,31 @@ function vm.isVarargFunctionWithOverloads(func) if not func.args then return false end + if func._varargFunction ~= nil then + return func._varargFunction + end if func.args[1] and func.args[1].type == 'self' then if not func.args[2] or func.args[2].type ~= '...' then + func._varargFunction = false return false end else if not func.args[1] or func.args[1].type ~= '...' then + func._varargFunction = false return false end end if not func.bindDocs then + func._varargFunction = false return false end for _, doc in ipairs(func.bindDocs) do if doc.type == 'doc.overload' then + func._varargFunction = true return true end end + func._varargFunction = false return false end diff --git a/script/vm/global.lua b/script/vm/global.lua index e830f6d8..ed2b4802 100644 --- a/script/vm/global.lua +++ b/script/vm/global.lua @@ -382,12 +382,14 @@ local compilerGlobalSwitch = util.switch() [1] = field.field[1], } elseif field.type == 'tableindex' then - source._enums[#source._enums+1] = { - type = 'doc.type.string', - start = field.index.start, - finish = field.index.finish, - [1] = field.index[1], - } + if field.index then + source._enums[#source._enums+1] = { + type = 'doc.type.string', + start = field.index.start, + finish = field.index.finish, + [1] = field.index[1], + } + end end end else @@ -539,21 +541,60 @@ function vm.hasGlobalSets(suri, cate, name) return true end +---@param uri uri +---@param key string +---@return boolean +local function checkIsGlobalRegex(uri, key) + local dglobalsregex = config.get(uri, 'Lua.diagnostics.globalsRegex') + if not dglobalsregex then + return false + end + + for _, pattern in ipairs(dglobalsregex) do + if key:match(pattern) then + return true + end + end + + return false +end + ---@param src parser.object local function checkIsUndefinedGlobal(src) + if src.type ~= 'getglobal' then + return false + end + local key = src[1] + if not key then + return false + end + + local node = src.node + if node.tag ~= '_ENV' then + return false + end local uri = guide.getUri(src) - local dglobals = util.arrayToHash(config.get(uri, 'Lua.diagnostics.globals')) local rspecial = config.get(uri, 'Lua.runtime.special') + if rspecial[key] then + return false + end - local node = src.node - return src.type == 'getglobal' and key and not ( - dglobals[key] or - rspecial[key] or - node.tag ~= '_ENV' or - vm.hasGlobalSets(uri, 'variable', key) - ) + if vm.hasGlobalSets(uri, 'variable', key) then + return false + end + + local dglobals = config.get(uri, 'Lua.diagnostics.globals') + if util.arrayHas(dglobals, key) then + return false + end + + if checkIsGlobalRegex(uri, key) then + return false + end + + return true end ---@param src parser.object diff --git a/script/vm/infer.lua b/script/vm/infer.lua index 3f3d0e3a..bb06ee3a 100644 --- a/script/vm/infer.lua +++ b/script/vm/infer.lua @@ -242,9 +242,6 @@ local viewNodeSwitch;viewNodeSwitch = util.switch() return vm.viewKey(source, uri) end) ----@class vm.node ----@field lastInfer? vm.infer - ---@param node? vm.node ---@return vm.infer local function createInfer(node) diff --git a/script/vm/node.lua b/script/vm/node.lua index bc1dfcb1..fae79cbc 100644 --- a/script/vm/node.lua +++ b/script/vm/node.lua @@ -16,6 +16,7 @@ vm.nodeCache = setmetatable({}, util.MODE_K) ---@field [vm.node.object] true ---@field fields? table<vm.node|string, vm.node> ---@field undefinedGlobal boolean? +---@field lastInfer? vm.infer local mt = {} mt.__index = mt mt.id = 0 @@ -31,6 +32,7 @@ function mt:merge(node) if not node then return self end + self.lastInfer = nil if node.type == 'vm.node' then if node == self then return self diff --git a/script/vm/type.lua b/script/vm/type.lua index 545d2de5..d2a859d0 100644 --- a/script/vm/type.lua +++ b/script/vm/type.lua @@ -65,8 +65,14 @@ local function checkParentEnum(parentName, child, uri, mark, errs) local enums for _, set in ipairs(parentClass:getSets(uri)) do if set.type == 'doc.enum' then - enums = vm.getEnums(set) - break + local denums = vm.getEnums(set) + if denums then + if enums then + enums = util.arrayMerge(enums, denums) + else + enums = denums + end + end end end if not enums then diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua index 1507183c..6553212d 100644 --- a/script/workspace/require-path.lua +++ b/script/workspace/require-path.lua @@ -5,6 +5,7 @@ local workspace = require "workspace" local config = require 'config' local scope = require 'workspace.scope' local util = require 'utility' +local plugin = require 'plugin' ---@class require-path local m = {} @@ -181,6 +182,11 @@ function mt:searchUrisByRequireName(name) local searcherMap = {} local excludes = {} + local pluginSuccess, pluginResults = plugin.dispatch('ResolveRequire', self.scp.uri, name) + if pluginSuccess and pluginResults ~= nil then + return pluginResults + end + for uri in files.eachFile(self.scp.uri) do if vm.isMetaFileRequireable(uri) then local metaName = vm.getMetaName(uri) @@ -54,6 +54,7 @@ local function testAll() test 'basic' test 'definition' test 'type_inference' + test 'implementation' test 'references' test 'hover' test 'completion' @@ -115,5 +116,5 @@ loadAllLibs() main() log.debug('test finish.') -require 'bee.thread'.sleep(1) +require 'bee.thread'.sleep(1000) os.exit() diff --git a/test/basic/filewatch.lua b/test/basic/filewatch.lua new file mode 100644 index 00000000..8b639052 --- /dev/null +++ b/test/basic/filewatch.lua @@ -0,0 +1 @@ +require 'bee.filewatch'.create():add('中文文件夹') diff --git a/test/basic/init.lua b/test/basic/init.lua index 8490d51c..9030cf6d 100644 --- a/test/basic/init.lua +++ b/test/basic/init.lua @@ -1 +1,2 @@ require 'basic.textmerger' +require 'basic.filewatch' diff --git a/test/completion/common.lua b/test/completion/common.lua index 3ea02ed7..ec2372a0 100644 --- a/test/completion/common.lua +++ b/test/completion/common.lua @@ -3842,6 +3842,35 @@ f(<??>) } TEST [[ +---@class optional +---@field enum enum + +---@enum(key) enum +local t = { + a = 1, + b = 2, +} + +---@param a optional +local function f(a) +end + +f { + enum = <??> +} +]] +{ + { + label = '"a"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"b"', + kind = define.CompletionItemKind.EnumMember, + }, +} + +TEST [[ -- <??> ]] @@ -4434,3 +4463,74 @@ new 'A' { } } +TEST [[ +---@enum(key) enum +local t = { + a = 1, + b = 2, + c = 3, +} + +---@class A +local M + +---@param optional enum +function M:optional(optional) +end + +---@return A +function M:self() + return self +end + +---@type A +local m + +m:self(<??>):optional() +]] +(nil) + +TEST [[ +---@enum(key) enum +local t = { + a = 1, + b = 2, + c = 3, +} + +---@class A +local M + +---@return A +function M.create() + return M +end + +---@param optional enum +---@return self +function M:optional(optional) + return self +end + +---@return A +function M:self() + return self +end + + +M.create():optional(<??>):self() +]] +{ + { + label = '"a"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"b"', + kind = define.CompletionItemKind.EnumMember, + }, + { + label = '"c"', + kind = define.CompletionItemKind.EnumMember, + }, +} diff --git a/test/crossfile/hover.lua b/test/crossfile/hover.lua index a18c714b..8acae6f5 100644 --- a/test/crossfile/hover.lua +++ b/test/crossfile/hover.lua @@ -95,7 +95,7 @@ TEST { * [a.lua](file:///a.lua) (搜索路径: `?.lua`)]], } -if require 'bee.platform'.OS == 'Windows' then +if require 'bee.platform'.os == 'windows' then TEST { { path = 'Folder/a.lua', diff --git a/test/diagnostics/discard-returns.lua b/test/diagnostics/discard-returns.lua index 2e348390..16ac8245 100644 --- a/test/diagnostics/discard-returns.lua +++ b/test/diagnostics/discard-returns.lua @@ -15,3 +15,259 @@ end X = f() ]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +for i = 1, 2 do + <!f()!> +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +for i = 1, 2 do + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +while true do + <!f()!> + break +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +while true do + local v = f() + break +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +repeat + <!f()!> + break +until true +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +repeat + local v = f() + break +until true +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +for index, value in ipairs({}) do + <!f()!> +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +for index, value in ipairs({}) do + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + <!f()!> +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + local v = f() +else + <!f()!> +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + local v = f() +else + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + local v = f() +elseif 1 == 2 then + <!f()!> +else + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if 1 == 1 then + local v = f() +elseif 1 == 2 then + local v = f() +else + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +local function bar(callback) +end + +bar(function () + <!f()!> +end) +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +local function bar(callback) +end + +bar(function () + local v = f() +end) +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +do + <!f()!> +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +do + local v = f() +end +]] + +TEST [[ +---@nodiscard +local function f() + return 2 +end + +for i = 1, f() do +end +]] + +TEST [[ +---@nodiscard +local function list_iter(t) + local i = 0 + local n = #t + return function () + i = i + 1 + if i <= n then return t[i] end + end +end + +local t = {10, 20, 30} +for element in list_iter(t) do +end +]] + +TEST [[ +---@nodiscard +local function f() + return 1 +end + +if f() then +end +]] diff --git a/test/diagnostics/duplicate-doc-alias.lua b/test/diagnostics/duplicate-doc-alias.lua index 0373fee9..e518ffc6 100644 --- a/test/diagnostics/duplicate-doc-alias.lua +++ b/test/diagnostics/duplicate-doc-alias.lua @@ -8,3 +8,14 @@ TEST [[ ---@class B ---@alias <!A!> B ]] + +TEST [[ +---@alias A integer +---@alias(partial) A integer + +---@enum B +---@enum(partial) B + +---@enum(key) C +---@enum(key, partial) C +]] diff --git a/test/diagnostics/param-type-mismatch.lua b/test/diagnostics/param-type-mismatch.lua index e31e9933..b11068db 100644 --- a/test/diagnostics/param-type-mismatch.lua +++ b/test/diagnostics/param-type-mismatch.lua @@ -246,3 +246,21 @@ local MyClass = {} local w = MyClass(<!1!>) ]] + +TEST [[ +---@enum(key) A +local t1 = { + x = 1, +} + +---@enum(key) A +local t2 = { + y = 1, +} + +---@param v A +local function f(v) end + +f 'x' +f 'y' +]] diff --git a/test/full/projects.lua b/test/full/projects.lua index 20ef6724..22b27443 100644 --- a/test/full/projects.lua +++ b/test/full/projects.lua @@ -23,7 +23,7 @@ local function doProjects(pathname) print('基准诊断目录:', path) fsu.scanDirectory(path, function (path) - if path:extension():string() ~= '.lua' then + if path:extension() ~= '.lua' then return end local uri = furi.encode(path:string()) diff --git a/test/full/self.lua b/test/full/self.lua index d118e034..69da54a0 100644 --- a/test/full/self.lua +++ b/test/full/self.lua @@ -14,7 +14,7 @@ local uris = {} files.reset() fsu.scanDirectory(path, function (path) - if path:extension():string() ~= '.lua' then + if path:extension() ~= '.lua' then return end local uri = furi.encode(path:string()) diff --git a/test/implementation/init.lua b/test/implementation/init.lua new file mode 100644 index 00000000..678cb23b --- /dev/null +++ b/test/implementation/init.lua @@ -0,0 +1,81 @@ +local core = require 'core.implementation' +local files = require 'files' +local vm = require 'vm' +local catch = require 'catch' + +rawset(_G, 'TEST', true) + +local function founded(targets, results) + if #targets ~= #results then + return false + end + for _, target in ipairs(targets) do + for _, result in ipairs(results) do + if target[1] == result[1] and target[2] == result[2] then + goto NEXT + end + end + do return false end + ::NEXT:: + end + return true +end + +---@async +function TEST(script) + local newScript, catched = catch(script, '!?') + + files.setText(TESTURI, newScript) + + local results = core(TESTURI, catched['?'][1][1]) + if results then + local positions = {} + for i, result in ipairs(results) do + if not vm.isMetaFile(result.uri) then + positions[#positions+1] = { result.target.start, result.target.finish } + end + end + assert(founded(catched['!'], positions)) + else assert(#catched['!'] == 0) + end + + files.remove(TESTURI) +end + +TEST [[ +---@class A +---@field x number +local M + +M.<!x!> = 1 + + +print(M.<?x?>) +]] + +TEST [[ +---@class A +---@field f fun() +local M + +function M.<!f!>() end + + +print(M.<?f?>) +]] + +TEST [[ +---@class A +local M + +function M:<!event!>(name) end + +---@class A +---@field event fun(self, name: 'ev1') +---@field event fun(self, name: 'ev2') + +---@type A +local m + +m:<?event?>('ev1') +]] diff --git a/test/other/filewatch.lua b/test/other/filewatch.lua index f5d5a03d..e751c715 100644 --- a/test/other/filewatch.lua +++ b/test/other/filewatch.lua @@ -17,7 +17,7 @@ fw.event(function (ev, filename) events[#events+1] = {ev, filename} end) -thread.sleep(1) +thread.sleep(1000) events = {} fw.update() assert(#events == 1) @@ -25,7 +25,7 @@ assert(events[1][1] == 'create') fsu.saveFile(path / 'test.txt', 'modify') -thread.sleep(1) +thread.sleep(1000) events = {} fw.update() assert(#events == 1) @@ -37,7 +37,7 @@ f:write('xxx') f:flush() f:close() -thread.sleep(1) +thread.sleep(1000) events = {} fw.update() assert(#events == 1) diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua new file mode 100644 index 00000000..5922832b --- /dev/null +++ b/test/type_inference/common.lua @@ -0,0 +1,4194 @@ +local config = require 'config' + +TEST 'nil' [[ +local <?t?> = nil +]] + +TEST 'string' [[ +local <?var?> = '111' +]] + +TEST 'boolean' [[ +local <?var?> = true +]] + +TEST 'integer' [[ +local <?var?> = 1 +]] + +TEST 'number' [[ +local <?var?> = 1.0 +]] + +TEST 'unknown' [[ +local <?var?> +]] + +TEST 'unknown' [[ +local <?var?> +var = y +]] + +TEST 'any' [[ +function f(<?x?>) + +end +]] + +TEST 'any' [[ +function f(<?x?>) + x = 1 +end +]] + +TEST 'string' [[ +local var = '111' +t.<?x?> = var +]] + +TEST 'string' [[ +local <?var?> +var = '111' +]] + +TEST 'string' [[ +local var +<?var?> = '111' +]] + +TEST 'string' [[ +local var +var = '111' +print(<?var?>) +]] + +TEST 'function' [[ +function <?xx?>() +end +]] + +TEST 'function' [[ +local function <?xx?>() +end +]] + +TEST 'function' [[ +local xx +<?xx?> = function () +end +]] + +TEST 'table' [[ +local <?t?> = {} +]] + +TEST 'unknown' [[ +<?x?>() +]] + +TEST 'boolean' [[ +<?x?> = not y +]] + +TEST 'integer' [[ +<?x?> = #y +]] + +TEST 'integer' [[ +<?x?> = #'aaaa' +]] + +TEST 'integer' [[ +<?x?> = #{} +]] + +TEST 'number' [[ +<?x?> = - y +]] + +TEST 'number' [[ +<?x?> = - 1.0 +]] + +TEST 'integer' [[ +<?x?> = ~ y +]] + +TEST 'integer' [[ +<?x?> = ~ 1 +]] + +TEST 'boolean' [[ +<?x?> = 1 < 2 +]] + +TEST 'integer' [[ +local a = true +local b = 1 +<?x?> = a and b +]] + +TEST 'integer' [[ +local a = false +local b = 1 +<?x?> = a or b +]] + +TEST 'boolean' [[ +<?x?> = a == b +]] + +TEST 'unknown' [[ +<?x?> = a << b +]] + +TEST 'integer' [[ +<?x?> = 1 << 2 +]] + +TEST 'unknown' [[ +<?x?> = a .. b +]] + +TEST 'string' [[ +<?x?> = 'a' .. 'b' +]] + +TEST 'string' [[ +<?x?> = 'a' .. 1 +]] + +TEST 'string' [[ +<?x?> = 'a' .. 1.0 +]] + +TEST 'unknown' [[ +<?x?> = a + b +]] + +TEST 'number' [[ +<?x?> = 1 + 2.0 +]] + +TEST 'integer' [[ +<?x?> = 1 + 2 +]] + +TEST 'integer' [[ +---@type integer +local a + +<?x?> = - a +]] + +TEST 'number' [[ +local a + +<?x?> = - a +]] + +TEST 'unknown' [[ +<?x?> = 1 + X +]] + +TEST 'unknown' [[ +<?x?> = 1.0 + X +]] + +TEST 'tablelib' [[ +---@class tablelib +table = {} + +<?table?>() +]] + +TEST 'string' [[ +_VERSION = 'Lua 5.4' + +<?x?> = _VERSION +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + +return ('x').<?xxx?> +]] + +TEST 'function' [[ +---@class stringlib +String = {} + +String.xxx = function () end + +return ('x').<?xxx?> +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + +<?x?> = ('x').xxx +]] + +TEST 'function' [[ +---@class stringlib +local string + +string.xxx = function () end + +_VERSION = 'Lua 5.4' + +<?x?> = _VERSION.xxx +]] + +TEST 'table' [[ +<?x?> = setmetatable({}) +]] + +TEST 'integer' [[ +local function x() + return 1 +end +<?y?> = x() +]] + +TEST 'integer|nil' [[ +local function x() + return 1 + return nil +end +<?y?> = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return a + return nil +end +<?y?> = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return nil + return f() +end +<?y?> = x() +]] + +TEST 'unknown|nil' [[ +local function x() + return nil + return f() +end +_, <?y?> = x() +]] + +TEST 'integer' [[ +local function x() + return 1 +end +_, <?y?> = pcall(x) +]] + +TEST 'integer' [[ +function x() + return 1 +end +_, <?y?> = pcall(x) +]] + +TEST 'integer' [[ +local function x() + return 1 +end +_, <?y?> = xpcall(x) +]] + +TEST 'A' [[ +---@class A + +---@return A +local function f2() end + +local function f() + return f2() +end + +local <?x?> = f() +]] + +-- 不根据调用者的输入参数来推测 +--TEST 'number' [[ +--local function x(a) +-- return <?a?> +--end +--x(1) +--]] + +--TEST 'table' [[ +--setmetatable(<?b?>) +--]] + +-- 不根据对方函数内的使用情况来推测 +TEST 'unknown' [[ +local function x(a) + _ = a + 1 +end +local b +x(<?b?>) +]] + +TEST 'unknown' [[ +local function x(a, ...) + local _, <?b?>, _ = ... +end +x(nil, 'xx', 1, true) +]] + +-- 引用不跨越参数 +TEST 'unknown' [[ +local function x(a, ...) + return true, 'ss', ... +end +local _, _, _, <?b?>, _ = x(nil, true, 1, 'yy') +]] + +TEST 'unknown' [[ +local <?x?> = next() +]] + +TEST 'unknown' [[ +local a, b +function a() + return b() +end +function b() + return a() +end +local <?x?> = a() +]] + +TEST 'class' [[ +---@class class +local <?x?> +]] + +TEST 'string' [[ +---@class string + +---@type string +local <?x?> +]] + +TEST '1' [[ +---@type 1 +local <?v?> +]] + +TEST 'string[]' [[ +---@class string + +---@type string[] +local <?x?> +]] + +TEST 'string|table' [[ +---@class string +---@class table + +---@type string | table +local <?x?> +]] + +TEST [['enum1'|'enum2']] [[ +---@type 'enum1' | 'enum2' +local <?x?> +]] + +TEST [["enum1"|"enum2"]] [[ +---@type "enum1" | "enum2" +local <?x?> +]] + +config.set(nil, 'Lua.hover.expandAlias', false) +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local <?x?> +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local <?x?> +]] + +TEST 'A' [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local <?x?> +]] +config.set(nil, 'Lua.hover.expandAlias', true) +TEST [['enum1'|'enum2']] [[ +---@alias A 'enum1' | 'enum2' + +---@type A +local <?x?> +]] + +TEST [['enum1'|'enum2']] [[ +---@alias A 'enum1' | 'enum2' | A + +---@type A +local <?x?> +]] + +TEST [['enum1'|'enum2'|B]] [[ +---@alias A 'enum1' | 'enum2' | B + +---@type A +local <?x?> +]] + +TEST '1|true' [[ +---@alias A 1 | true + +---@type A +local <?x?> +]] + +TEST 'fun()' [[ +---@type fun() +local <?x?> +]] + +TEST 'fun(a: string, b: any, ...any)' [[ +---@type fun(a: string, b, ...) +local <?x?> +]] + +TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[ +---@type fun(a: string, b, c?: boolean, ...):c, d?, ... +local <?x?> +]] + +TEST '{ [string]: string }' [[ +---@type { [string]: string } +local <?x?> +]] + +TEST 'table<string, number>' [[ +---@class string +---@class number + +---@type table<string, number> +local <?x?> +]] + +TEST 'A<string, number>' [[ +---@class A + +---@type A<string, number> +local <?x?> +]] + +TEST 'string' [[ +---@class string + +---@type string[] +local x +local <?y?> = x[1] +]] + +TEST 'string' [[ +---@class string + +---@return string[] +local function f() end +local x = f() +local <?y?> = x[1] +]] + +TEST 'table' [[ +local t = {} +local <?v?> = setmetatable(t) +]] + +TEST 'CCC' [[ +---@class CCC + +---@type table<string, CCC> +local t = {} + +print(t.<?a?>) +]] + +TEST [['aaa'|'bbb']] [[ +---@type table<string, 'aaa'|'bbb'> +local t = {} + +print(t.<?a?>) +]] + +TEST 'integer' [[ +---@generic K +---@type fun(a?: K):K +local f + +local <?n?> = f(1) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a?: K):K +local f + +local <?n?> = f(nil) +]] + +TEST 'unknown' [[ +---@generic K +---@type fun(a: K|integer):K +local f + +local <?n?> = f(1) +]] + +TEST 'integer' [[ +---@class integer + +---@generic T: table, V +---@param t T +---@return fun(table: V[], i?: integer):integer, V +---@return T +---@return integer i +local function ipairs() end + +for <?i?> in ipairs() do +end +]] + +TEST 'table<string, boolean>' [[ +---@generic K, V +---@param t table<K, V> +---@return K +---@return V +local function next(t) end + +---@type table<string, boolean> +local t +local k, v = next(<?t?>) +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@param t table<K, V> +---@return K +---@return V +local function next(t) end + +---@type table<string, boolean> +local t +local <?k?>, v = next(t) +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic K, V +---@param t table<K, V> +---@return K +---@return V +local function next(t) end + +---@type table<string, boolean> +local t +local k, <?v?> = next(t) +]] + +TEST 'boolean' [[ +---@generic K +---@type fun(arg: K):K +local f + +local <?r?> = f(true) +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@type fun(arg: table<K, V>):K, V +local f + +---@type table<string, boolean> +local t + +local <?k?>, v = f(t) +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic K, V +---@type fun(arg: table<K, V>):K, V +local f + +---@type table<string, boolean> +local t + +local k, <?v?> = f(t) +]] + +TEST 'fun()' [[ +---@return fun() +local function f() end + +local <?r?> = f() +]] + +TEST 'table<string, boolean>' [[ +---@return table<string, boolean> +local function f() end + +local <?r?> = f() +]] + +TEST 'string' [[ +---@class string + +---@generic K, V +---@return fun(arg: table<K, V>):K, V +local function f() end + +local f2 = f() + +---@type table<string, boolean> +local t + +local <?k?>, v = f2(t) +]] + +TEST 'fun(a: <V>):integer, <V>' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local <?f2?> = f(1) +]] + +TEST 'integer' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local f2 = f(1) +local <?i?>, v = f2(true) +]] + +TEST 'boolean' [[ +---@generic K, V +---@param a K +---@return fun(a: V):K, V +local function f(a) end + +local f2 = f(1) +local i, <?v?> = f2(true) +]] + +TEST 'fun(table: table<<K>, <V>>, index?: <K>):<K>, <V>' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local <?next?> = pairs(dummy) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local <?k?>, v = next(t) +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local k, <?v?> = next(t) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local <?k?>, v = next(t, nil) +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t +local k, <?v?> = next(t, nil) +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +---@return nil +local function pairs(t) end + +local next = pairs(dummy) + +---@type table<string, boolean> +local t + +for <?k?>, v in next, t do +end +]] + +TEST 'boolean' [[ +---@class boolean + +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +local function pairs(t) end + +local f = pairs(t) + +---@type table<string, boolean> +local t + +for k, <?v?> in f, t do +end +]] + +TEST 'string' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +local function pairs(t) end + +---@type table<string, boolean> +local t + +for <?k?>, v in pairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index: K):K, V +---@return T +---@return nil +local function pairs(t) end + +---@type table<string, boolean> +local t + +for k, <?v?> in pairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, V +---@param t T +---@return fun(table: V[], i?: integer):integer, V +---@return T +---@return integer i +local function ipairs(t) end + +---@type boolean[] +local t + +for _, <?v?> in ipairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, V +---@param t T +---@return fun(table: V[], i?: integer):integer, V +---@return T +---@return integer i +local function ipairs(t) end + +---@type table<integer, boolean> +local t + +for _, <?v?> in ipairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, V +---@param t T +---@return fun(table: V[], i?: integer):integer, V +---@return T +---@return integer i +local function ipairs(t) end + +---@class MyClass +---@field [integer] boolean +local t + +for _, <?v?> in ipairs(t) do +end +]] + +TEST 'boolean' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index: K):K, V +---@return T +---@return nil +local function pairs(t) end + +---@type boolean[] +local t + +for k, <?v?> in pairs(t) do +end +]] + +TEST 'integer' [[ +---@generic T: table, K, V +---@param t T +---@return fun(table: table<K, V>, index?: K):K, V +---@return T +local function pairs(t) end + +---@type boolean[] +local t + +for <?k?>, v in pairs(t) do +end +]] + +TEST 'E' [[ +---@class A +---@class B: A +---@class C: B +---@class D: C + +---@class E: D +local m + +function m:f() + return <?self?> +end +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls.new(self) return self end + +local <?test?> = Cls:new() +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls:new() return self end + +local <?test?> = Cls:new() +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls.new(self) return self end + +local <?test?> = Cls.new(Cls) +]] + +TEST 'Cls' [[ +---@class Cls +local Cls = {} + +---@generic T +---@param self T +---@return T +function Cls:new() return self end + +local <?test?> = Cls.new(Cls) +]] + +TEST 'Rct' [[ +---@class Obj +local Obj = {} + +---@generic T +---@param self T +---@return T +function Obj.new(self) return self end + + +---@class Pnt:Obj +local Pnt = {x = 0, y = 0} + + +---@class Rct:Pnt +local Rct = {w = 0, h = 0} + + +local <?test?> = Rct.new(Rct) + +-- local test = Rct:new() + +return test +]] + +TEST 'function' [[ +string.gsub():gsub():<?gsub?>() +]] + +config.set(nil, 'Lua.hover.enumsLimit', 5) +TEST [['a'|'b'|'c'|'d'|'e'...(+5)]] [[ +---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' +local <?t?> +]] + +config.set(nil, 'Lua.hover.enumsLimit', 1) +TEST [['a'...(+9)]] [[ +---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' +local <?t?> +]] + +config.set(nil, 'Lua.hover.enumsLimit', 0) +TEST '...(+10)' [[ +---@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' +local <?t?> +]] + +config.set(nil, 'Lua.hover.enumsLimit', 5) + +TEST 'string|fun():string' [[ +---@type string | fun(): string +local <?t?> +]] + +TEST 'string' [[ +local valids = { + ['Lua 5.1'] = false, + ['Lua 5.2'] = false, + ['Lua 5.3'] = false, + ['Lua 5.4'] = false, + ['LuaJIT'] = false, +} + +for <?k?>, v in pairs(valids) do +end +]] + +TEST 'boolean' [[ +local valids = { + ['Lua 5.1'] = false, + ['Lua 5.2'] = false, + ['Lua 5.3'] = false, + ['Lua 5.4'] = false, + ['LuaJIT'] = false, +} + +for k, <?v?> in pairs(valids) do +end +]] + +TEST 'string' [[ +local t = { + a = 1, + b = 1, +} + +for <?k?>, v in pairs(t) do +end +]] + +TEST 'integer' [[ +local t = {'a', 'b'} + +for <?k?>, v in pairs(t) do +end +]] + +TEST 'string' [[ +local t = {'a', 'b'} + +for k, <?v?> in pairs(t) do +end +]] + +TEST 'fun():number, boolean' [[ +---@type fun():number, boolean +local <?t?> +]] + + +TEST 'fun(value: Class)' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +work(<?function?> (value) +end) +]] + +TEST 'Class' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +work(function (<?value?>) +end) +]] + +TEST 'fun(value: Class)' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +pcall(work, <?function?> (value) +end) +]] + +TEST 'Class' [[ +---@class Class + +---@param callback fun(value: Class) +function work(callback) +end + +xpcall(work, debug.traceback, function (<?value?>) +end) +]] + +TEST 'string' [[ +---@generic T +---@param x T +---@return { x: T } +local function f(x) end + +local t = f('') + +print(t.<?x?>) +]] + +TEST 'string' [[ +---@generic T +---@param t T[] +---@param callback fun(v: T) +local function f(t, callback) end + +---@type string[] +local t + +f(t, function (<?v?>) end) +]] + +TEST 'unknown' [[ +---@generic T +---@param t T[] +---@param callback fun(v: T) +local function f(t, callback) end + +local t = {} + +f(t, function (<?v?>) end) +]] + +TEST 'table' [[ +local <?t?> = setmetatable({}, { __index = function () end }) +]] + +TEST 'player' [[ +---@class player +local t + +<?t?>:getOwner() +]] + +TEST 'string[][]' [[ +---@type string[][] +local <?t?> +]] + +TEST 'table' [[ +---@type {}[] +local t + +local <?v?> = t[1] +]] + +TEST 'string' [[ +---@type string[][] +local v = {} + +for _, a in ipairs(v) do + for i, <?b?> in ipairs(a) do + end +end +]] + +--TEST 'number' [[ +-----@param x number +--local f +-- +--f = function (<?x?>) end +--]] + +TEST 'fun(i: integer)' [[ +--- @class Emit +--- @field on fun(eventName: string, cb: function) +--- @field on fun(eventName: 'died', cb: fun(i: integer)) +--- @field on fun(eventName: 'won', cb: fun(s: string)) +local emit = {} + +emit.on("died", <?function?> (i) +end) +]] + +TEST 'integer' [[ +--- @class Emit +--- @field on fun(eventName: string, cb: function) +--- @field on fun(eventName: 'died', cb: fun(i: integer)) +--- @field on fun(eventName: 'won', cb: fun(s: string)) +local emit = {} + +emit.on("died", function (<?i?>) +end) +]] + +TEST 'integer' [[ +--- @class Emit +--- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: 'died', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: 'won', cb: fun(s: string)) +local emit = {} + +emit:on("died", function (<?i?>) +end) +]] + +TEST 'integer' [[ +--- @class Emit +--- @field on fun(self: Emit, eventName: string, cb: function) +--- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) +--- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) +local emit = {} + +emit.on(self, "died", function (<?i?>) +end) +]] + +TEST '👍' [[ +---@class 👍 +local <?x?> +]] + +TEST 'integer' [[ +---@type boolean +local x + +<?x?> = 1 +]] + +TEST 'integer' [[ +---@class Class +local x + +<?x?> = 1 +]] + +TEST 'unknown' [[ +---@return number +local function f(x) + local <?y?> = x() +end +]] + +TEST 'unknown' [[ +local mt + +---@return number +function mt:f() end + +local <?v?> = mt() +]] + +TEST 'unknown' [[ +local <?mt?> + +---@class X +function mt:f(x) end +]] + +TEST 'any' [[ +local mt + +---@class X +function mt:f(<?x?>) end +]] + +TEST 'unknown' [[ +local <?mt?> + +---@type number +function mt:f(x) end +]] + +TEST 'any' [[ +local mt + +---@type number +function mt:f(<?x?>) end +]] + +TEST 'Test' [[ +---@class Test +_G.<?Test?> = {} +]] + +TEST 'integer' [[ +local mt = {} + +---@param callback fun(i: integer) +function mt:loop(callback) end + +mt:loop(function (<?i?>) + +end) +]] + +TEST 'C' [[ +---@class D +---@field y integer # D comment + +---@class C +---@field x integer # C comment +---@field d D + +---@param c C +local function f(c) end + +f <?{?> + x = , +} +]] + +TEST 'integer' [[ +---@class D +---@field y integer # D comment + +---@class C +---@field x integer # C comment +---@field d D + +---@param c C +local function f(c) end + +f { + <?x?> = , +} +]] + +TEST 'integer' [[ +---@class D +---@field y integer # D comment + +---@class C +---@field x integer # C comment +---@field d D + +---@param c C +local function f(c) end + +f { + d = { + <?y?> = , + } +} +]] + +TEST 'integer' [[ +for <?i?> = a, b, c do end +]] + +TEST 'number' [[ +---@param x number +function F(<?x?>) end + +---@param x boolean +function F(x) end +]] + +TEST 'B' [[ +---@class A +local A + +---@return A +function A:x() end + +---@class B: A +local B + +---@return B +function B:x() end + +---@type B +local t + +local <?v?> = t.x() +]] + +TEST 'function' [[ +---@overload fun() +function <?f?>() end +]] + +TEST 'integer' [[ +---@type table<string, integer> +local t + +t.<?a?> +]] + +TEST '"a"|"b"|"c"' [[ +---@type table<string, "a"|"b"|"c"> +local t + +t.<?a?> +]] + +TEST 'integer' [[ +---@class A +---@field x integer + +---@type A +local t +t.<?x?> +]] + +TEST 'boolean' [[ +local <?var?> = true +var = 1 +var = 1.0 +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local <?x?> = f() +]] + +TEST 'unknown' [[ +---@return ... +local function f() end + +local _, <?x?> = f() +]] + +TEST 'unknown' [[ +local t = { + x = 1, + y = 2, +} + +local <?x?> = t[#t] +]] + +TEST 'string' [[ +local t = { + x = 1, + [1] = 'x', +} + +local <?x?> = t[#t] +]] + +TEST 'string' [[ +local t = { 'x' } + +local <?x?> = t[#t] +]] + +TEST '(string|integer)[]' [[ +---@type (string|integer)[] +local <?x?> +]] + +TEST 'boolean' [[ +---@type table<string, boolean> +local t + +---@alias uri string + +---@type string +local uri + +local <?v?> = t[uri] +]] + +TEST 'A' [[ +---@class A +G = {} + +<?G?>:A() +]] + +TEST 'A' [[ +---@type A +local <?x?> = nil +]] + +TEST 'A' [[ +---@class A +---@field b B +local mt + +function mt:f() + self.b:x() + print(<?self?>) +end +]] + +TEST 'string?' [[ +---@return string? +local function f() end + +local <?x?> = f() +]] + +TEST 'AA' [[ +---@class AA +---@overload fun():AA +local AAA + + +local <?x?> = AAA() +]] + +TEST 'AA' [[ +---@class AA +---@overload fun():AA +AAA = {} + + +local <?x?> = AAA() +]] + +TEST 'string' [[ +local <?x?> +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x +<?x?> = '1' +x = 1 +]] + +TEST 'integer' [[ +local x +x = '1' +<?x?> = 1 +]] + +TEST 'unknown' [[ +local x +print(<?x?>) +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x +x = '1' +print(<?x?>) +x = 1 +]] + +TEST 'integer' [[ +local x +x = '1' +x = 1 +print(<?x?>) +]] + +TEST 'unknown' [[ +local x + +function A() + print(<?x?>) +end +]] + +TEST 'string' [[ +local x + +function A() + print(<?x?>) +end + +x = '1' +x = 1 +]] + +TEST 'string' [[ +local x + +x = '1' + +function A() + print(<?x?>) +end + +x = 1 +]] + +TEST 'integer' [[ +local x + +x = '1' +x = 1 + +function A() + print(<?x?>) +end + +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + print(<?x?>) +end + +x = '1' +x = 1 +]] + +TEST 'unknown' [[ +local x + +function A() + x = true +end + +print(<?x?>) +x = '1' +x = 1 +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + function B() + print(<?x?>) + end +end + +x = '1' +x = 1 +]] + +TEST 'table' [[ +local x + +function A() + x = true + function B() + x = {} + print(<?x?>) + end +end + +x = '1' +x = 1 +]] + +TEST 'boolean' [[ +local x + +function A() + x = true + function B() + x = {} + end + print(<?x?>) +end + +x = '1' +x = 1 +]] + +TEST 'unknown' [[ +local x + +function A() + x = true + function B() + x = {} + end +end + +function C() + print(<?x?>) +end + +x = '1' +x = 1 +]] + +TEST 'integer' [[ +local x +x = true +do + x = 1 +end +print(<?x?>) +]] + +TEST 'boolean' [[ +local x +x = true +function XX() + do + x = 1 + end +end +print(<?x?>) +]] + +TEST 'integer?' [[ +---@type integer? +local <?x?> +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if <?x?> then + print(x) +end +]] +--[[ +context 0 integer? + +save copy 'block' +save copy 'out' +push 'block' +get +push copy +truthy +falsy ref 'out' +get +save HEAD 'final' +push 'out' + +push copy HEAD +merge 'final' +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x then + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x then + print(x) +end + +print(<?x?>) +]] + +TEST 'nil' [[ +---@type integer? +local x + +if not x then + print(<?x?>) +end + +print(x) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + x = 1 +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if xxx and x then + print(<?x?>) +end +]] + +TEST 'unknown' [[ +---@type integer? +local x + +if not x and x then + print(<?x?>) +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x and not mark[x] then + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if xxx and x then +end + +print(<?x?>) +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if xxx and x then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x ~= nil then + print(<?x?>) +end + +print(x) +]] + +TEST 'integer|nil' [[ +---@type integer? +local x + +if x ~= nil then + print(x) +end + +print(<?x?>) +]] + +TEST 'nil' [[ +---@type integer? +local x + +if x == nil then + print(<?x?>) +end + +print(x) +]] + +TEST 'integer|nil' [[ +---@type integer? +local x + +if x == nil then + print(x) +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +<?x?> = x or 1 +]] + +TEST 'integer' [[ +---@type integer? +local x + +<?x?> = x or y +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x then + goto ANYWHERE +end + +print(<?x?>) +]] + +TEST 'integer' [=[ +local x + +print(<?x?>--[[@as integer]]) +]=] + +TEST 'integer' [=[ +print(<?io?>--[[@as integer]]) +]=] + +TEST 'integer' [=[ +print(io.<?open?>--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local <?x?> = io['open']--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local <?x?> = 1 + 1--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local <?x?> = not 1--[[@as integer]]) +]=] + +TEST 'integer' [=[ +local <?x?> = ()--[[@as integer]]) +]=] + +TEST 'integer?' [[ +---@param x? integer +local function f(<?x?>) + +end +]] + +TEST 'integer' [[ +local x = 1 +x = <?x?> +]] + +TEST 'integer?' [[ +---@class A +---@field x? integer +local t + +t.<?x?> +]] + +TEST 'integer?' [[ +---@type { x?: integer } +local t + +t.<?x?> +]] + +TEST 'boolean' [[ +---@class A +---@field [integer] boolean +local t + +local <?x?> = t[1] +]] + +TEST 'unknown' [[ +local <?x?> = y and z +]] + +TEST 'integer' [[ +---@type integer? +local x + +assert(x) + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +assert(x ~= nil) + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer | nil +local x + +assert(x) + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer | nil +local x + +assert(x ~= nil) + +print(<?x?>) +]] + +TEST 'integer' [[ +local x + +assert(x == 1) + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if x and <?x?>.y then +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x and x.y then +end + +print(<?x?>) +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x and x.y then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x or <?x?>.y then +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if not x or x.y then + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x or x.y then + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x.y or x then + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +if x.y or not x then + print(<?x?>) +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not x or not y then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +if not y or not x then + return +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +while true do + if not x then + break + end + print(<?x?>) +end +]] + +TEST 'integer?' [[ +---@type integer? +local x + +while true do + if not x then + break + end +end + +print(<?x?>) +]] + +TEST 'integer' [[ +---@type integer? +local x + +while x do + print(<?x?>) +end +]] + +TEST 'integer' [[ +---@type fun():integer? +local iter + +for <?x?> in iter do +end +]] + +TEST 'integer' [[ +local x + +---@type integer +<?x?> = XXX +]] + +TEST 'unknown' [[ +for _ = 1, 999 do + local <?x?> +end +]] + +TEST 'integer' [[ +local x + +---@cast x integer + +print(<?x?>) +]] + +TEST 'unknown' [[ +local x + +---@cast x integer + +local x +print(<?x?>) +]] + +TEST 'unknown' [[ +local x + +if true then + local x + ---@cast x integer + print(x) +end + +print(<?x?>) +]] + +TEST 'boolean|integer' [[ +local x = 1 + +---@cast x +boolean + +print(<?x?>) +]] + +TEST 'boolean' [[ +---@type integer|boolean +local x + +---@cast x -integer + +print(<?x?>) +]] + +TEST 'boolean?' [[ +---@type boolean +local x + +---@cast x +? + +print(<?x?>) +]] + +TEST 'boolean' [[ +---@type boolean? +local x + +---@cast x -? + +print(<?x?>) +]] + +TEST 'nil' [[ +---@type string? +local x + +if x then + return +else + print(<?x?>) +end + +print(x) +]] + +TEST 'string' [[ +---@type string? +local x + +if not x then + return +else + print(<?x?>) +end + +print(x) +]] + +TEST 'string' [[ +---@type string? +local x + +if not x then + return +else + print(x) +end + +print(<?x?>) +]] + +TEST 'true' [[ +---@type boolean | nil +local x + +if not x then + return +end + +print(<?x?>) +]] + +TEST 'true' [[ +---@type boolean +local t + +if t then + print(<?t?>) + return +end + +print(t) +]] + +TEST 'false' [[ +---@type boolean +local t + +if t then + print(t) + return +end + +print(<?t?>) +]] + +TEST 'nil' [[ +---@type integer? +local t + +if t then +else + print(<?t?>) +end + +print(t) +]] + +TEST 'table' [[ +local function f() + if x then + return y + end + return {} +end + +local <?z?> = f() +]] + +TEST 'integer|table' [[ +local function returnI() + return 1 +end + +local function f() + if x then + return returnI() + end + return {} +end + +local <?z?> = f() +]] + +TEST 'number' [[ +for _ in _ do + ---@type number + local <?x?> +end +]] + +TEST 'unknown' [[ +for _ in _ do + ---@param x number + local <?x?> +end +]] + +TEST 'unknown' [[ +---@type number +for <?x?> in _ do +end +]] + +TEST 'number' [[ +---@param x number +for <?x?> in _ do +end +]] + +TEST 'table' [[ +---@alias tp table + +---@type tp +local <?x?> +]] + +TEST '{ name: boolean }' [[ +---@alias tp {name: boolean} + +---@type tp +local <?x?> +]] + +TEST 'boolean|{ name: boolean }' [[ +---@alias tp boolean | {name: boolean} + +---@type tp +local <?x?> +]] + +TEST '`1`|`true`' [[ +---@type `1` | `true` +local <?x?> +]] + +TEST 'function' [[ +local x + +function x() end + +print(<?x?>) +]] + +TEST 'unknown' [[ +local x + +if x.field == 'haha' then + print(<?x?>) +end +]] + +TEST 'string' [[ +---@type string? +local t + +if not t or xxx then + return +end + +print(<?t?>) +]] + +TEST 'table' [[ +---@type table|nil +local t + +return function () + if not t then + return + end + + print(<?t?>) +end +]] + +TEST 'table' [[ +---@type table|nil +local t + +f(function () + if not t then + return + end + + print(<?t?>) +end) +]] + +TEST 'table' [[ +---@type table? +local t + +t = t or {} + +print(<?t?>) +]] + +TEST 'unknown|nil' [[ +local x + +if x == nil then +end + +print(<?x?>) +]] + +TEST 'table<xxx, true>' [[ +---@alias xxx table<xxx, true> + +---@type xxx +local <?t?> +]] + +TEST 'xxx[][]' [[ +---@alias xxx xxx[] + +---@type xxx +local <?t?> +]] + +TEST 'fun(x: fun(x: xxx))' [[ +---@alias xxx fun(x: xxx) + +---@type xxx +local <?t?> +]] + +TEST 'table' [[ +---@type table|nil +local t + +while t do + print(<?t?>) +end +]] + +TEST 'table|nil' [[ +---@type table|nil +local t + +while <?t?> do + print(t) +end +]] + +TEST 'table' [[ +---@type table|nil +local t + +while t ~= nil do + print(<?t?>) +end +]] + +TEST 'table|nil' [[ +---@type table|nil +local t + +while <?t?> ~= nil do + print(t) +end +]] + +TEST 'integer' [[ +---@type integer? +local n + +if not n then + error('n is nil') +end + +print(<?n?>) +]] + +TEST 'integer' [[ +---@type integer? +local n + +if not n then + os.exit() +end + +print(<?n?>) +]] + +TEST 'table' [[ +---@type table? +local n + +print((n and <?n?>.x)) +]] + +TEST 'table' [[ +---@type table? +local n + +n = n and <?n?>.x or 1 +]] + +TEST 'table' [[ +---@type table? +local n + +n = ff[n and <?n?>.x] +]] + +TEST 'integer' [[ +local x + +if type(x) == 'integer' then + print(<?x?>) +end +]] + +TEST 'boolean|integer' [[ +local x + +if type(x) == 'integer' +or type(x) == 'boolean' then + print(<?x?>) +end +]] + +TEST 'fun()' [[ +---@type fun()? +local x + +if type(x) == 'function' then + print(<?x?>) +end +]] + +TEST 'function' [[ +local x + +if type(x) == 'function' then + print(<?x?>) +end +]] + +TEST 'integer' [[ +local x +local tp = type(x) + +if tp == 'integer' then + print(<?x?>) +end +]] + +TEST 'integer' [[ +---@type integer? +local x + +if (x == nil) then +else + print(<?x?>) +end +]] + +TEST 'B' [[ +---@class A +---@class B + +---@type A +local x + +---@type B +x = call(x) + +print(<?x?>) +]] + +TEST 'nil' [[ +local function f() +end + +local <?x?> = f() +]] + +TEST 'integer[]' [[ +---@type integer[] +local x +if not x then + return +end + +print(<?x?>) +]] + +TEST 'unknown' [[ +---@type string[] +local t + +local <?x?> = t.x +]] + +TEST 'integer|unknown' [[ +local function f() + return GG +end + +local t + +t.x = 1 +t.x = f() + +print(t.<?x?>) +]] + +TEST 'integer' [[ +local function f() + if X then + return X + else + return 1 + end +end + +local <?n?> = f() +]] + +TEST 'unknown' [[ +local function f() + return t[k] +end + +local <?n?> = f() +]] + +TEST 'integer|nil' [[ +local function f() + if x then + return + else + return 1 + end +end + +local <?n?> = f() +]] + +TEST 'integer' [[ +---@class A +---@field x integer +local m + +m.<?x?> = true + +print(m.x) +]] + +TEST 'integer' [[ +---@class A +---@field x integer +local m + +m.x = true + +print(m.<?x?>) +]] + +TEST 'integer' [[ +---@class A +---@field x integer --> 1st +local m = { + x = '' --> 2nd +} + +---@type boolean +m.x = true --> 3rd (with ---@type above) + +m.x = {} --> 4th + +print(m.<?x?>) +]] + +TEST 'string' [[ +---@class A +----@field x integer --> 1st +local m = { + x = '' --> 2nd +} + +---@type boolean +m.x = true --> 3rd (with ---@type above) + +m.x = {} --> 4th + +print(m.<?x?>) +]] + +TEST 'boolean' [[ +---@class A +----@field x integer --> 1st +local m = { + --x = '' --> 2nd +} + +---@type boolean +m.x = true --> 3rd (with ---@type above) + +m.x = {} --> 4th + +print(m.<?x?>) +]] + +TEST 'table' [[ +---@class A +----@field x integer --> 1st +local m = { + --x = '' --> 2nd +} + +---@type boolean +--m.x = true --> 3rd (with ---@type above) + +m.x = {} --> 4th + +print(m.<?x?>) +]] + +TEST 'boolean?' [[ +---@generic T +---@param x T +---@return T +local function echo(x) end + +---@type boolean? +local b + +local <?x?> = echo(b) +]] + +TEST 'boolean' [[ +---@generic T +---@param x T? +---@return T +local function echo(x) end + +---@type boolean? +local b + +local <?x?> = echo(b) +]] + +TEST 'boolean' [[ +---@generic T +---@param x? T +---@return T +local function echo(x) end + +---@type boolean? +local b + +local <?x?> = echo(b) +]] + +TEST 'boolean' [[ +---@type {[integer]: boolean, xx: integer} +local t + +local <?n?> = t[1] +]] + +TEST 'boolean' [[ +---@type integer +local i + +---@type {[integer]: boolean, xx: integer} +local t + +local <?n?> = t[i] +]] + +TEST 'string' [=[ +local x = true +local y = x--[[@as integer]] --is `integer` here +local z = <?x?>--[[@as string]] --is `true` here +]=] + +TEST 'integer' [[ +---@type integer +local x + +if type(x) == 'number' then + print(<?x?>) +end +]] + +TEST 'boolean' [[ +---@class A +---@field [integer] boolean +local mt + +function mt:f() + ---@type integer + local index + local <?x?> = self[index] +end +]] + +TEST 'boolean' [[ +---@class A +---@field [B] boolean + +---@class B + +---@type A +local a + +---@type B +local b + +local <?x?> = a[b] +]] + +TEST 'number' [[ +---@type {x: string ; y: boolean; z: number} +local t + +local <?z?> = t.z +]] + +TEST 'fun():number, boolean' [[ +---@type {f: fun():number, boolean} +local t + +local <?f?> = t.f +]] + +TEST 'fun():number' [[ +---@type {(f: fun():number), x: boolean} +local t + +local <?f?> = t.f +]] + +TEST 'boolean' [[ +---@param ... boolean +local function f(...) + local <?n?> = ... +end +]] + +TEST 'boolean' [[ +---@param ... boolean +local function f(...) + local _, <?n?> = ... +end +]] + +TEST 'boolean' [[ +---@return boolean ... +local function f() end + +local <?n?> = f() +]] + +TEST 'boolean' [[ +---@return boolean ... +local function f() end + +local _, <?n?> = f() +]] + +TEST 'boolean' [[ +---@type fun():name1: boolean, name2:number +local f + +local <?n?> = f() +]] + +TEST 'number' [[ +---@type fun():name1: boolean, name2:number +local f + +local _, <?n?> = f() +]] +TEST 'boolean' [[ +---@type fun():(name1: boolean, name2:number) +local f + +local <?n?> = f() +]] + +TEST 'number' [[ +---@type fun():(name1: boolean, name2:number) +local f + +local _, <?n?> = f() +]] + +TEST 'boolean' [[ +---@type fun():...: boolean +local f + +local _, <?n?> = f() +]] + +TEST 'string' [[ +local s +while true do + s = '' +end +print(<?s?>) +]] + +TEST 'string' [[ +local s +for _ in _ do + s = '' +end +print(<?s?>) +]] + +TEST 'A' [[ +---@class A: string + +---@type A +local <?s?> = '' +]] + +TEST 'number' [[ +---@return number +local function f() end +local x, <?y?> = 1, f() +]] + +TEST 'boolean' [[ +---@return number, boolean +local function f() end +local x, y, <?z?> = 1, f() +]] + +TEST 'number' [[ +---@return number, boolean +local function f() end +local x, y, <?z?> = 1, 2, f() +]] + +TEST 'unknown' [[ +local f + +print(<?f?>) + +function f() end +]] + +TEST 'unknown' [[ +local f + +do + print(<?f?>) +end + +function f() end +]] + +TEST 'function' [[ +local f + +function A() + print(<?f?>) +end + +function f() end +]] + +TEST 'number' [[ +---@type number|nil +local n + +local t = { + x = n and <?n?>, +} +]] + +TEST 'table' [[ +---@type table? +local n + +if not n or not <?n?>.x then +end +]] + +TEST 'table' [[ +---@type table? +local n + +if not n or not <?n?>[1] then +end +]] + +TEST 'number' [[ +---@type number|false +local n + +---@cast n -false + +print(<?n?>) +]] + +TEST 'table' [[ +---@type number|table +local n + +if n +---@cast n table +and <?n?>.type == 'xxx' then +end +]] + +TEST 'integer' [[ +---@type integer? +local n +if true then + n = 0 +end +local <?x?> = n or 0 +]] + +TEST 'number' [=[ +local <?x?> = F()--[[@as number]] +]=] + +TEST 'number' [=[ +local function f() + return F()--[[@as number]] +end + +local <?x?> = f() +]=] + +TEST 'number' [=[ +local <?x?> = X --[[@as number]] +]=] + +TEST 'number' [[ +---@return number?, number? +local function f() end + +for <?x?>, y in f do +end +]] + +TEST 'number' [[ +---@return number?, number? +local function f() end + +for x, <?y?> in f do +end +]] + +TEST 'number|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local <?c?> = a and b +]] + +TEST 'number|table|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local <?c?> = a or b +]] + +TEST 'number|table|nil' [[ +---@type table|nil +local a + +---@type number|nil +local b + +local c = a and b +local <?d?> = a or b +]] + +TEST 'number' [[ +local x + +---@return number +local function f() +end + +x = f() + +print(<?x?>) +]] + +TEST 'number' [[ +local x + +---@return number +local function f() +end + +_, x = pcall(f) + +print(<?x?>) +]] + +TEST 'string' [[ +---@type table<string|number, string> +local t + +---@type number +local n +---@type string +local s + +local <?test?> = t[n] +local test2 = t[s] --test and test2 are unknow +]] + +TEST 'string' [[ +---@type table<string|number, string> +local t + +---@type number +local n +---@type string +local s + +local test = t[n] +local <?test2?> = t[s] --test and test2 are unknow +]] + +TEST 'table<number, boolean>' [[ +---@type table<number, boolean> +local t + +<?t?> = {} +]] + +TEST 'integer' [[ +---@type integer[]|A +local t + +local <?x?> = t[1] +]] + +TEST 'integer' [[ +---@type integer +---@diagnostic disable +local <?t?> +]] + +TEST 'A' [[ +---@class A +---@diagnostic disable +local <?t?> +]] + +TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[ +---@type {[string]: number, [true]: string, [1]: boolean, tag: integer} +local <?t?> +]] + +TEST 'unknown' [[ +local mt = {} +mt.<?x?> = nil +]] + +TEST 'unknown' [[ +mt = {} +mt.<?x?> = nil +]] + +TEST 'A' [[ +---@class A +---@operator unm: A + +---@type A +local a +local <?b?> = -a +]] + +TEST 'A' [[ +---@class A +---@operator bnot: A + +---@type A +local a +local <?b?> = ~a +]] + +TEST 'A' [[ +---@class A +---@operator len: A + +---@type A +local a +local <?b?> = #a +]] + +TEST 'A' [[ +---@class A +---@operator add: A + +---@type A +local a +local <?b?> = a + 1 +]] + +TEST 'A' [[ +---@class A +---@operator sub: A + +---@type A +local a +local <?b?> = a - 1 +]] + +TEST 'A' [[ +---@class A +---@operator mul: A + +---@type A +local a +local <?b?> = a * 1 +]] + +TEST 'A' [[ +---@class A +---@operator div: A + +---@type A +local a +local <?b?> = a / 1 +]] + +TEST 'A' [[ +---@class A +---@operator mod: A + +---@type A +local a +local <?b?> = a % 1 +]] + +TEST 'A' [[ +---@class A +---@operator pow: A + +---@type A +local a +local <?b?> = a ^ 1 +]] + +TEST 'A' [[ +---@class A +---@operator idiv: A + +---@type A +local a +local <?b?> = a // 1 +]] + +TEST 'A' [[ +---@class A +---@operator band: A + +---@type A +local a +local <?b?> = a & 1 +]] + +TEST 'A' [[ +---@class A +---@operator bor: A + +---@type A +local a +local <?b?> = a | 1 +]] + +TEST 'A' [[ +---@class A +---@operator bxor: A + +---@type A +local a +local <?b?> = a ~ 1 +]] + +TEST 'A' [[ +---@class A +---@operator shl: A + +---@type A +local a +local <?b?> = a << 1 +]] + +TEST 'A' [[ +---@class A +---@operator shr: A + +---@type A +local a +local <?b?> = a >> 1 +]] + +TEST 'A' [[ +---@class A +---@operator concat: A + +---@type A +local a +local <?b?> = a .. 1 +]] + +TEST 'A' [[ +---@class A +---@operator add(boolean): boolean +---@operator add(integer): A + +---@type A +local a +local <?b?> = a + 1 +]] + +TEST 'boolean' [[ +---@class A +---@operator add(boolean): boolean +---@operator add(integer): A + +---@type A +local a +local <?b?> = a + true +]] + +TEST 'A' [[ +---@class A +---@operator call: A + +---@type A +local a +local <?b?> = a() +]] + +TEST 'A' [[ +---@class A +---@operator call: A + +---@type A +local a + +local t = { + <?x?> = a(), +} +]] + +TEST 'boolean' [[ +---@class A +---@field n number +---@field [string] boolean +local t + +local <?x?> = t.xx +]] + +TEST 'number' [[ +---@class A +---@field n number +---@field [string] boolean +local t + +local <?x?> = t.n +]] + +TEST 'string' [[ +---@class string +---@operator mod: string + +local <?b?> = '' % 1 +]] + +TEST 'string|integer' [[ +---@type boolean +local bool + +local <?x?> = bool and '' or 0 +]] + +TEST 'string|integer' [[ +local bool + +if X then + bool = true +else + bool = false +end + +local <?x?> = bool and '' or 0 +]] + +TEST 'boolean' [[ +---@type boolean|true|false +local <?b?> +]] + +TEST 'integer|false' [[ +local <?b?> = X == 1 and X == 1 and 1 +]] + +TEST 'unknown|nil' [[ +local function f() + if X then + return ({})[1] + end + return nil +end + +local <?n?> = f() +]] + +TEST 'integer' [[ +---@generic T +---@vararg T # ERROR +---@return T +local function test(...) + return ... +end + +local <?n?> = test(1) +]] + +TEST 'boolean' [[ +---@type boolean, number +local <?x?>, y +]] + +TEST 'number' [[ +---@type boolean, number +local x, <?y?> +]] + +TEST 'unknown' [[ +---@type _, number +local <?x?>, y +]] + +TEST 'number[]' [[ +local t +---@cast t number[]? + +local x = t and <?t?>[i] +]] + +TEST 'number?' [[ +---@type number[]? +local t + +local <?x?> = t and t[i] +]] + +TEST 'number' [[ +---@type number +local x + +if not <?x?>.y then + x = nil +end +]] + +TEST 'number' [[ +---@type number|nil +local x +while x == nil do + if x == nil then + return + end + + x = nil +end + +print(<?x?>) +]] + +TEST 'integer' [[ +local A = { + ---@class XXX + B = {} +} + +A.B.C = 1 + +print(A.B.<?C?>) +]] + +TEST '-2|-3|1' [[ +---@type 1|-2|-3 +local <?n?> +]] + +TEST 'table' [[ +---@enum A +local m = {} + +print(<?m?>) +]] + +TEST 'A' [[ +---@class A +---@overload fun():A +local m = {} + +---@return A +function m:init() + return <?self?> +end +]] + +TEST 'string' [[ +---@vararg string +function F(...) + local t = {...} + for k, <?v?> in pairs(t) do + end +end +]] + +TEST 'string' [[ +---@vararg string +function F(...) + local t = {...} + for k, <?v?> in ipairs(t) do + end +end +]] + +TEST 'integerA' [[ +---@type integerA +for <?i?> = 1, 10 do +end +]] + +TEST 'string' [[ +---@class A +---@field x string + +---@class B : A +local t = {} + +t.x = t.x + +print(t.<?x?>) +]] + +TEST 'unknown' [[ +local t = { + x = 1, +} + +local x + +local <?v?> = t[x] +]] + +TEST 'A|B' [[ +---@class A +---@class B: A + +---@type A|B +local <?t?> +]] + +TEST 'function' [[ +---@class myClass +local myClass = { has = { nested = {} } } + +function myClass.has.nested.fn() end + +---@type myClass +local class + +class.has.nested.<?fn?>() +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun(x: T) +---@return T[] +local function x(f) end + +---@param x integer +local <?arr?> = x(function (x) end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +local <?arr?> = x(function () + return 1 +end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@return integer +local <?arr?> = x(function () end) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun(x: T) +---@return T[] +local function x(f) end + +---@type fun(x: integer) +local cb + +local <?arr?> = x(cb) +]] + +TEST 'integer[]' [[ +---@generic T +---@param f fun():T +---@return T[] +local function x(f) end + +---@type fun(): integer +local cb + +local <?arr?> = x(cb) +]] + +TEST 'integer' [[ +---@return fun(x: integer) +local function f() + return function (<?x?>) + end +end +]] + +TEST 'string' [[ +---@class A +---@field f fun(x: string) + +---@type A +local t = { + f = function (<?x?>) end +} +]] + +config.set(nil, 'Lua.runtime.special', { + ['xx.assert'] = 'assert' +}) + +TEST 'number' [[ +---@type number? +local t + +xx.assert(t) + +print(<?t?>) +]] + +config.set(nil, 'Lua.runtime.special', nil) + +TEST 'A' [[ +---@class A +local mt + +---@return <?self?> +function mt:init() +end +]] + +TEST 'A' [[ +---@class A +local mt + +---@return self +function mt:init() +end + +local <?o?> = mt:init() +]] + +TEST 'A' [[ +---@class A +---@field x <?self?> +]] + +TEST 'A' [[ +---@class A +---@field x self + +---@type A +local o + +print(o.<?x?>) +]] + +TEST 'A' [[ +---@class A +---@overload fun(): self +local A + +local <?o?> = A() +]] + +TEST 'number' [[ +---@type table<'Test1', fun(x: number)> +local t = { + ["Test1"] = function(<?x?>) end, +} +]] + +TEST 'number' [[ +---@type table<5, fun(x: number)> +local t = { + [5] = function(<?x?>) end, +} +]] + +TEST 'number' [[ +---@type fun(x: number) +local function f(<?x?>) end +]] + +TEST 'boolean' [[ +---@generic T: string | boolean | table +---@param x T +---@return T +local function f(x) + return x +end + +local <?x?> = f(true) +]] + +TEST 'number' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local <?n?> = t[1] +]] + +TEST 'boolean' [[ +---@class A +---@field [1] number +---@field [2] boolean +local t + +local <?n?> = t[2] +]] + +TEST 'N' [[ +---@class N: number +local x + +if x == 0.1 then + print(<?x?>) +end +]] + +TEST 'vec3' [[ +---@class mat4 +---@operator mul(vec3): vec3 -- matrix * vector +---@operator mul(number): mat4 -- matrix * constant + +---@class vec3: number + +---@type mat4, vec3 +local m, v + +local <?r?> = m * v +]] + +TEST 'mat4' [[ +---@class mat4 +---@operator mul(number): mat4 -- matrix * constant +---@operator mul(vec3): vec3 -- matrix * vector + +---@class vec3: number + +---@type mat4, vec3 +local m, v + +local <?r?> = m * v +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +else + print(<?t?>) +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A +elseif <?t?> then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif <?t?> then +end +]] + +TEST 'A|B' [[ +---@class A +---@class B + +---@type A|B +local t + +if x then + ---@cast t A + print(t) +elseif <?t?> then + ---@cast t A + print(t) +end +]] + +TEST 'function' [[ +local function x() + print(<?x?>) +end +]] + +TEST 'number' [[ +---@type number? +local x + +do + if not x then + return + end +end + +print(<?x?>) +]] + +TEST 'number' [[ +---@type number[] +local xs + +---@type fun(x): number? +local f + +for _, <?x?> in ipairs(xs) do + x = f(x) +end +]] + +TEST 'number' [[ +---@type number? +X = Y + +if X then + print(<?X?>) +end +]] + +TEST 'number' [[ +---@type number|boolean +X = Y + +if type(X) == 'number' then + print(<?X?>) +end +]] + +TEST 'boolean' [[ +---@type number|boolean +X = Y + +if type(X) ~= 'number' then + print(<?X?>) +end +]] + +TEST 'boolean' [[ +---@type number +X = Y + +---@cast X boolean + +print(<?X?>) +]] + +TEST 'number' [[ +---@type number +local t + +if xxx == <?t?> then + print(t) +end +]] + +TEST 'V' [[ +---@class V +X = 1 + +print(<?X?>) +]] + +TEST 'V' [[ +---@class V +X.Y = 1 + +print(X.<?Y?>) +]] + +TEST 'integer' [[ +local x = {} + +x.y = 1 +local y = x.y +x.y = nil + +print(<?y?>) +]] + +TEST 'function' [[ +function X() + <?Y?>() +end + +function Y() +end +]] + +TEST 'A_Class' [[ +---@class A_Class +local A = { x = 5 } + +function A:func() + for i = 1, <?self?>.x do + print(i) + end + + self.y = 3 + self.y = self.y + 3 +end +]] + +TEST 'number' [[ +---@type number? +local n +local <?v?> = n or error('') +]] + +TEST 'Foo' [[ +---@class Foo +---@operator mul(Foo): Foo +---@operator mul(Bar): Foo +---@class Bar + +---@type Foo +local foo + +---@type Foo|Bar +local fooOrBar + +local <?b?> = foo * fooOrBar +]] + +TEST 'number' [[ +local a = 4; +local b = 2; + +local <?c?> = a / b; +]] + +TEST 'string' [[ +local a = '4'; +local b = '2'; + +local <?c?> = a .. b; +]] + +TEST 'number|{ [1]: string }' [[ +---@alias Some +---| { [1]: string } +---| number + +local x ---@type Some + +print(<?x?>) +]] + +TEST 'integer' [[ +---@class metatable : table +---@field __index table + +---@param table table +---@param metatable? metatable +---@return table +function setmetatable(table, metatable) end + +local m = setmetatable({},{ __index = { a = 1 } }) + +m.<?a?> +]] + +TEST 'integer' [[ +---@class metatable : table +---@field __index table + +---@param table table +---@param metatable? metatable +---@return table +function setmetatable(table, metatable) end + +local mt = {a = 1 } +local m = setmetatable({},{ __index = mt }) + +m.<?a?> +]] + +TEST 'integer' [[ +local x = 1 +repeat +until <?x?> +]] + +-- #2144 +TEST 'A' [=[ +local function f() + return {} --[[@as A]] +end + +local <?x?> = f() +]=] + +TEST 'A' [=[ +local function f() + ---@type A + return {} +end + +local <?x?> = f() +]=] + +TEST 'boolean|number' [[ +---@alias A number +---@alias(partial) A boolean + +---@type A +local <?x?> +]] diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua index 98a07ca3..35900bc3 100644 --- a/test/type_inference/init.lua +++ b/test/type_inference/init.lua @@ -44,4295 +44,5 @@ function TEST(wanted) end end -TEST 'nil' [[ -local <?t?> = nil -]] - -TEST 'string' [[ -local <?var?> = '111' -]] - -TEST 'boolean' [[ -local <?var?> = true -]] - -TEST 'integer' [[ -local <?var?> = 1 -]] - -TEST 'number' [[ -local <?var?> = 1.0 -]] - -TEST 'unknown' [[ -local <?var?> -]] - -TEST 'unknown' [[ -local <?var?> -var = y -]] - -TEST 'any' [[ -function f(<?x?>) - -end -]] - -TEST 'any' [[ -function f(<?x?>) - x = 1 -end -]] - -TEST 'string' [[ -local var = '111' -t.<?x?> = var -]] - -TEST 'string' [[ -local <?var?> -var = '111' -]] - -TEST 'string' [[ -local var -<?var?> = '111' -]] - -TEST 'string' [[ -local var -var = '111' -print(<?var?>) -]] - -TEST 'function' [[ -function <?xx?>() -end -]] - -TEST 'function' [[ -local function <?xx?>() -end -]] - -TEST 'function' [[ -local xx -<?xx?> = function () -end -]] - -TEST 'table' [[ -local <?t?> = {} -]] - -TEST 'unknown' [[ -<?x?>() -]] - -TEST 'boolean' [[ -<?x?> = not y -]] - -TEST 'integer' [[ -<?x?> = #y -]] - -TEST 'integer' [[ -<?x?> = #'aaaa' -]] - -TEST 'integer' [[ -<?x?> = #{} -]] - -TEST 'number' [[ -<?x?> = - y -]] - -TEST 'number' [[ -<?x?> = - 1.0 -]] - -TEST 'integer' [[ -<?x?> = ~ y -]] - -TEST 'integer' [[ -<?x?> = ~ 1 -]] - -TEST 'boolean' [[ -<?x?> = 1 < 2 -]] - -TEST 'integer' [[ -local a = true -local b = 1 -<?x?> = a and b -]] - -TEST 'integer' [[ -local a = false -local b = 1 -<?x?> = a or b -]] - -TEST 'boolean' [[ -<?x?> = a == b -]] - -TEST 'unknown' [[ -<?x?> = a << b -]] - -TEST 'integer' [[ -<?x?> = 1 << 2 -]] - -TEST 'unknown' [[ -<?x?> = a .. b -]] - -TEST 'string' [[ -<?x?> = 'a' .. 'b' -]] - -TEST 'string' [[ -<?x?> = 'a' .. 1 -]] - -TEST 'string' [[ -<?x?> = 'a' .. 1.0 -]] - -TEST 'unknown' [[ -<?x?> = a + b -]] - -TEST 'number' [[ -<?x?> = 1 + 2.0 -]] - -TEST 'integer' [[ -<?x?> = 1 + 2 -]] - -TEST 'integer' [[ ----@type integer -local a - -<?x?> = - a -]] - -TEST 'number' [[ -local a - -<?x?> = - a -]] - -TEST 'unknown' [[ -<?x?> = 1 + X -]] - -TEST 'unknown' [[ -<?x?> = 1.0 + X -]] - -TEST 'tablelib' [[ ----@class tablelib -table = {} - -<?table?>() -]] - -TEST 'string' [[ -_VERSION = 'Lua 5.4' - -<?x?> = _VERSION -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - -return ('x').<?xxx?> -]] - -TEST 'function' [[ ----@class stringlib -String = {} - -String.xxx = function () end - -return ('x').<?xxx?> -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - -<?x?> = ('x').xxx -]] - -TEST 'function' [[ ----@class stringlib -local string - -string.xxx = function () end - -_VERSION = 'Lua 5.4' - -<?x?> = _VERSION.xxx -]] - -TEST 'table' [[ -<?x?> = setmetatable({}) -]] - -TEST 'integer' [[ -local function x() - return 1 -end -<?y?> = x() -]] - -TEST 'integer|nil' [[ -local function x() - return 1 - return nil -end -<?y?> = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return a - return nil -end -<?y?> = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return nil - return f() -end -<?y?> = x() -]] - -TEST 'unknown|nil' [[ -local function x() - return nil - return f() -end -_, <?y?> = x() -]] - -TEST 'integer' [[ -local function x() - return 1 -end -_, <?y?> = pcall(x) -]] - -TEST 'integer' [[ -function x() - return 1 -end -_, <?y?> = pcall(x) -]] - -TEST 'integer' [[ -local function x() - return 1 -end -_, <?y?> = xpcall(x) -]] - -TEST 'A' [[ ----@class A - ----@return A -local function f2() end - -local function f() - return f2() -end - -local <?x?> = f() -]] - --- 不根据调用者的输入参数来推测 ---TEST 'number' [[ ---local function x(a) --- return <?a?> ---end ---x(1) ---]] - ---TEST 'table' [[ ---setmetatable(<?b?>) ---]] - --- 不根据对方函数内的使用情况来推测 -TEST 'unknown' [[ -local function x(a) - _ = a + 1 -end -local b -x(<?b?>) -]] - -TEST 'unknown' [[ -local function x(a, ...) - local _, <?b?>, _ = ... -end -x(nil, 'xx', 1, true) -]] - --- 引用不跨越参数 -TEST 'unknown' [[ -local function x(a, ...) - return true, 'ss', ... -end -local _, _, _, <?b?>, _ = x(nil, true, 1, 'yy') -]] - -TEST 'unknown' [[ -local <?x?> = next() -]] - -TEST 'unknown' [[ -local a, b -function a() - return b() -end -function b() - return a() -end -local <?x?> = a() -]] - -TEST 'class' [[ ----@class class -local <?x?> -]] - -TEST 'string' [[ ----@class string - ----@type string -local <?x?> -]] - -TEST '1' [[ ----@type 1 -local <?v?> -]] - -TEST 'string[]' [[ ----@class string - ----@type string[] -local <?x?> -]] - -TEST 'string|table' [[ ----@class string ----@class table - ----@type string | table -local <?x?> -]] - -TEST [['enum1'|'enum2']] [[ ----@type 'enum1' | 'enum2' -local <?x?> -]] - -TEST [["enum1"|"enum2"]] [[ ----@type "enum1" | "enum2" -local <?x?> -]] - -config.set(nil, 'Lua.hover.expandAlias', false) -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' - ----@type A -local <?x?> -]] - -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' | A - ----@type A -local <?x?> -]] - -TEST 'A' [[ ----@alias A 'enum1' | 'enum2' | B - ----@type A -local <?x?> -]] -config.set(nil, 'Lua.hover.expandAlias', true) -TEST [['enum1'|'enum2']] [[ ----@alias A 'enum1' | 'enum2' - ----@type A -local <?x?> -]] - -TEST [['enum1'|'enum2']] [[ ----@alias A 'enum1' | 'enum2' | A - ----@type A -local <?x?> -]] - -TEST [['enum1'|'enum2'|B]] [[ ----@alias A 'enum1' | 'enum2' | B - ----@type A -local <?x?> -]] - -TEST '1|true' [[ ----@alias A 1 | true - ----@type A -local <?x?> -]] - -TEST 'fun()' [[ ----@type fun() -local <?x?> -]] - -TEST 'fun(a: string, b: any, ...any)' [[ ----@type fun(a: string, b, ...) -local <?x?> -]] - -TEST 'fun(a: string, b: any, c?: boolean, ...any):c, d?, ...unknown' [[ ----@type fun(a: string, b, c?: boolean, ...):c, d?, ... -local <?x?> -]] - -TEST '{ [string]: string }' [[ ----@type { [string]: string } -local <?x?> -]] - -TEST 'table<string, number>' [[ ----@class string ----@class number - ----@type table<string, number> -local <?x?> -]] - -TEST 'A<string, number>' [[ ----@class A - ----@type A<string, number> -local <?x?> -]] - -TEST 'string' [[ ----@class string - ----@type string[] -local x -local <?y?> = x[1] -]] - -TEST 'string' [[ ----@class string - ----@return string[] -local function f() end -local x = f() -local <?y?> = x[1] -]] - -TEST 'table' [[ -local t = {} -local <?v?> = setmetatable(t) -]] - -TEST 'CCC' [[ ----@class CCC - ----@type table<string, CCC> -local t = {} - -print(t.<?a?>) -]] - -TEST [['aaa'|'bbb']] [[ ----@type table<string, 'aaa'|'bbb'> -local t = {} - -print(t.<?a?>) -]] - -TEST 'integer' [[ ----@generic K ----@type fun(a?: K):K -local f - -local <?n?> = f(1) -]] - -TEST 'unknown' [[ ----@generic K ----@type fun(a?: K):K -local f - -local <?n?> = f(nil) -]] - -TEST 'unknown' [[ ----@generic K ----@type fun(a: K|integer):K -local f - -local <?n?> = f(1) -]] - -TEST 'integer' [[ ----@class integer - ----@generic T: table, V ----@param t T ----@return fun(table: V[], i?: integer):integer, V ----@return T ----@return integer i -local function ipairs() end - -for <?i?> in ipairs() do -end -]] - -TEST 'table<string, boolean>' [[ ----@generic K, V ----@param t table<K, V> ----@return K ----@return V -local function next(t) end - ----@type table<string, boolean> -local t -local k, v = next(<?t?>) -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@param t table<K, V> ----@return K ----@return V -local function next(t) end - ----@type table<string, boolean> -local t -local <?k?>, v = next(t) -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic K, V ----@param t table<K, V> ----@return K ----@return V -local function next(t) end - ----@type table<string, boolean> -local t -local k, <?v?> = next(t) -]] - -TEST 'boolean' [[ ----@generic K ----@type fun(arg: K):K -local f - -local <?r?> = f(true) -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@type fun(arg: table<K, V>):K, V -local f - ----@type table<string, boolean> -local t - -local <?k?>, v = f(t) -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic K, V ----@type fun(arg: table<K, V>):K, V -local f - ----@type table<string, boolean> -local t - -local k, <?v?> = f(t) -]] - -TEST 'fun()' [[ ----@return fun() -local function f() end - -local <?r?> = f() -]] - -TEST 'table<string, boolean>' [[ ----@return table<string, boolean> -local function f() end - -local <?r?> = f() -]] - -TEST 'string' [[ ----@class string - ----@generic K, V ----@return fun(arg: table<K, V>):K, V -local function f() end - -local f2 = f() - ----@type table<string, boolean> -local t - -local <?k?>, v = f2(t) -]] - -TEST 'fun(a: <V>):integer, <V>' [[ ----@generic K, V ----@param a K ----@return fun(a: V):K, V -local function f(a) end - -local <?f2?> = f(1) -]] - -TEST 'integer' [[ ----@generic K, V ----@param a K ----@return fun(a: V):K, V -local function f(a) end - -local f2 = f(1) -local <?i?>, v = f2(true) -]] - -TEST 'boolean' [[ ----@generic K, V ----@param a K ----@return fun(a: V):K, V -local function f(a) end - -local f2 = f(1) -local i, <?v?> = f2(true) -]] - -TEST 'fun(table: table<<K>, <V>>, index?: <K>):<K>, <V>' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local <?next?> = pairs(dummy) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table<string, boolean> -local t -local <?k?>, v = next(t) -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table<string, boolean> -local t -local k, <?v?> = next(t) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table<string, boolean> -local t -local <?k?>, v = next(t, nil) -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table<string, boolean> -local t -local k, <?v?> = next(t, nil) -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T ----@return nil -local function pairs(t) end - -local next = pairs(dummy) - ----@type table<string, boolean> -local t - -for <?k?>, v in next, t do -end -]] - -TEST 'boolean' [[ ----@class boolean - ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T -local function pairs(t) end - -local f = pairs(t) - ----@type table<string, boolean> -local t - -for k, <?v?> in f, t do -end -]] - -TEST 'string' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T -local function pairs(t) end - ----@type table<string, boolean> -local t - -for <?k?>, v in pairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index: K):K, V ----@return T ----@return nil -local function pairs(t) end - ----@type table<string, boolean> -local t - -for k, <?v?> in pairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, V ----@param t T ----@return fun(table: V[], i?: integer):integer, V ----@return T ----@return integer i -local function ipairs(t) end - ----@type boolean[] -local t - -for _, <?v?> in ipairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, V ----@param t T ----@return fun(table: V[], i?: integer):integer, V ----@return T ----@return integer i -local function ipairs(t) end - ----@type table<integer, boolean> -local t - -for _, <?v?> in ipairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, V ----@param t T ----@return fun(table: V[], i?: integer):integer, V ----@return T ----@return integer i -local function ipairs(t) end - ----@class MyClass ----@field [integer] boolean -local t - -for _, <?v?> in ipairs(t) do -end -]] - -TEST 'boolean' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index: K):K, V ----@return T ----@return nil -local function pairs(t) end - ----@type boolean[] -local t - -for k, <?v?> in pairs(t) do -end -]] - -TEST 'integer' [[ ----@generic T: table, K, V ----@param t T ----@return fun(table: table<K, V>, index?: K):K, V ----@return T -local function pairs(t) end - ----@type boolean[] -local t - -for <?k?>, v in pairs(t) do -end -]] - -TEST 'E' [[ ----@class A ----@class B: A ----@class C: B ----@class D: C - ----@class E: D -local m - -function m:f() - return <?self?> -end -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls.new(self) return self end - -local <?test?> = Cls:new() -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls:new() return self end - -local <?test?> = Cls:new() -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls.new(self) return self end - -local <?test?> = Cls.new(Cls) -]] - -TEST 'Cls' [[ ----@class Cls -local Cls = {} - ----@generic T ----@param self T ----@return T -function Cls:new() return self end - -local <?test?> = Cls.new(Cls) -]] - -TEST 'Rct' [[ ----@class Obj -local Obj = {} - ----@generic T ----@param self T ----@return T -function Obj.new(self) return self end - - ----@class Pnt:Obj -local Pnt = {x = 0, y = 0} - - ----@class Rct:Pnt -local Rct = {w = 0, h = 0} - - -local <?test?> = Rct.new(Rct) - --- local test = Rct:new() - -return test -]] - -TEST 'function' [[ -string.gsub():gsub():<?gsub?>() -]] - -config.set(nil, 'Lua.hover.enumsLimit', 5) -TEST [['a'|'b'|'c'|'d'|'e'...(+5)]] [[ ----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' -local <?t?> -]] - -config.set(nil, 'Lua.hover.enumsLimit', 1) -TEST [['a'...(+9)]] [[ ----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' -local <?t?> -]] - -config.set(nil, 'Lua.hover.enumsLimit', 0) -TEST '...(+10)' [[ ----@type 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j' -local <?t?> -]] - -config.set(nil, 'Lua.hover.enumsLimit', 5) - -TEST 'string|fun():string' [[ ----@type string | fun(): string -local <?t?> -]] - -TEST 'string' [[ -local valids = { - ['Lua 5.1'] = false, - ['Lua 5.2'] = false, - ['Lua 5.3'] = false, - ['Lua 5.4'] = false, - ['LuaJIT'] = false, -} - -for <?k?>, v in pairs(valids) do -end -]] - -TEST 'boolean' [[ -local valids = { - ['Lua 5.1'] = false, - ['Lua 5.2'] = false, - ['Lua 5.3'] = false, - ['Lua 5.4'] = false, - ['LuaJIT'] = false, -} - -for k, <?v?> in pairs(valids) do -end -]] - -TEST 'string' [[ -local t = { - a = 1, - b = 1, -} - -for <?k?>, v in pairs(t) do -end -]] - -TEST 'integer' [[ -local t = {'a', 'b'} - -for <?k?>, v in pairs(t) do -end -]] - -TEST 'string' [[ -local t = {'a', 'b'} - -for k, <?v?> in pairs(t) do -end -]] - -TEST 'fun():number, boolean' [[ ----@type fun():number, boolean -local <?t?> -]] - - -TEST 'fun(value: Class)' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -work(<?function?> (value) -end) -]] - -TEST 'Class' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -work(function (<?value?>) -end) -]] - -TEST 'fun(value: Class)' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -pcall(work, <?function?> (value) -end) -]] - -TEST 'Class' [[ ----@class Class - ----@param callback fun(value: Class) -function work(callback) -end - -xpcall(work, debug.traceback, function (<?value?>) -end) -]] - -TEST 'string' [[ ----@generic T ----@param x T ----@return { x: T } -local function f(x) end - -local t = f('') - -print(t.<?x?>) -]] - -TEST 'string' [[ ----@generic T ----@param t T[] ----@param callback fun(v: T) -local function f(t, callback) end - ----@type string[] -local t - -f(t, function (<?v?>) end) -]] - -TEST 'unknown' [[ ----@generic T ----@param t T[] ----@param callback fun(v: T) -local function f(t, callback) end - -local t = {} - -f(t, function (<?v?>) end) -]] - -TEST 'table' [[ -local <?t?> = setmetatable({}, { __index = function () end }) -]] - -TEST 'player' [[ ----@class player -local t - -<?t?>:getOwner() -]] - -TEST 'string[][]' [[ ----@type string[][] -local <?t?> -]] - -TEST 'table' [[ ----@type {}[] -local t - -local <?v?> = t[1] -]] - -TEST 'string' [[ ----@type string[][] -local v = {} - -for _, a in ipairs(v) do - for i, <?b?> in ipairs(a) do - end -end -]] - ---TEST 'number' [[ ------@param x number ---local f --- ---f = function (<?x?>) end ---]] - -TEST 'fun(i: integer)' [[ ---- @class Emit ---- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: 'died', cb: fun(i: integer)) ---- @field on fun(eventName: 'won', cb: fun(s: string)) -local emit = {} - -emit.on("died", <?function?> (i) -end) -]] - -TEST 'integer' [[ ---- @class Emit ---- @field on fun(eventName: string, cb: function) ---- @field on fun(eventName: 'died', cb: fun(i: integer)) ---- @field on fun(eventName: 'won', cb: fun(s: string)) -local emit = {} - -emit.on("died", function (<?i?>) -end) -]] - -TEST 'integer' [[ ---- @class Emit ---- @field on fun(self: Emit, eventName: string, cb: function) ---- @field on fun(self: Emit, eventName: 'died', cb: fun(i: integer)) ---- @field on fun(self: Emit, eventName: 'won', cb: fun(s: string)) -local emit = {} - -emit:on("died", function (<?i?>) -end) -]] - -TEST 'integer' [[ ---- @class Emit ---- @field on fun(self: Emit, eventName: string, cb: function) ---- @field on fun(self: Emit, eventName: '"died"', cb: fun(i: integer)) ---- @field on fun(self: Emit, eventName: '"won"', cb: fun(s: string)) -local emit = {} - -emit.on(self, "died", function (<?i?>) -end) -]] - -TEST '👍' [[ ----@class 👍 -local <?x?> -]] - -TEST 'integer' [[ ----@type boolean -local x - -<?x?> = 1 -]] - -TEST 'integer' [[ ----@class Class -local x - -<?x?> = 1 -]] - -TEST 'unknown' [[ ----@return number -local function f(x) - local <?y?> = x() -end -]] - -TEST 'unknown' [[ -local mt - ----@return number -function mt:f() end - -local <?v?> = mt() -]] - -TEST 'unknown' [[ -local <?mt?> - ----@class X -function mt:f(x) end -]] - -TEST 'any' [[ -local mt - ----@class X -function mt:f(<?x?>) end -]] - -TEST 'unknown' [[ -local <?mt?> - ----@type number -function mt:f(x) end -]] - -TEST 'any' [[ -local mt - ----@type number -function mt:f(<?x?>) end -]] - -TEST 'Test' [[ ----@class Test -_G.<?Test?> = {} -]] - -TEST 'integer' [[ -local mt = {} - ----@param callback fun(i: integer) -function mt:loop(callback) end - -mt:loop(function (<?i?>) - -end) -]] - -TEST 'C' [[ ----@class D ----@field y integer # D comment - ----@class C ----@field x integer # C comment ----@field d D - ----@param c C -local function f(c) end - -f <?{?> - x = , -} -]] - -TEST 'integer' [[ ----@class D ----@field y integer # D comment - ----@class C ----@field x integer # C comment ----@field d D - ----@param c C -local function f(c) end - -f { - <?x?> = , -} -]] - -TEST 'integer' [[ ----@class D ----@field y integer # D comment - ----@class C ----@field x integer # C comment ----@field d D - ----@param c C -local function f(c) end - -f { - d = { - <?y?> = , - } -} -]] - -TEST 'integer' [[ -for <?i?> = a, b, c do end -]] - -TEST 'number' [[ ----@param x number -function F(<?x?>) end - ----@param x boolean -function F(x) end -]] - -TEST 'B' [[ ----@class A -local A - ----@return A -function A:x() end - ----@class B: A -local B - ----@return B -function B:x() end - ----@type B -local t - -local <?v?> = t.x() -]] - -TEST 'function' [[ ----@overload fun() -function <?f?>() end -]] - -TEST 'integer' [[ ----@type table<string, integer> -local t - -t.<?a?> -]] - -TEST '"a"|"b"|"c"' [[ ----@type table<string, "a"|"b"|"c"> -local t - -t.<?a?> -]] - -TEST 'integer' [[ ----@class A ----@field x integer - ----@type A -local t -t.<?x?> -]] - -TEST 'boolean' [[ -local <?var?> = true -var = 1 -var = 1.0 -]] - -TEST 'unknown' [[ ----@return ... -local function f() end - -local <?x?> = f() -]] - -TEST 'unknown' [[ ----@return ... -local function f() end - -local _, <?x?> = f() -]] - -TEST 'unknown' [[ -local t = { - x = 1, - y = 2, -} - -local <?x?> = t[#t] -]] - -TEST 'string' [[ -local t = { - x = 1, - [1] = 'x', -} - -local <?x?> = t[#t] -]] - -TEST 'string' [[ -local t = { 'x' } - -local <?x?> = t[#t] -]] - -TEST '(string|integer)[]' [[ ----@type (string|integer)[] -local <?x?> -]] - -TEST 'boolean' [[ ----@type table<string, boolean> -local t - ----@alias uri string - ----@type string -local uri - -local <?v?> = t[uri] -]] - -TEST 'A' [[ ----@class A -G = {} - -<?G?>:A() -]] - -TEST 'A' [[ ----@type A -local <?x?> = nil -]] - -TEST 'A' [[ ----@class A ----@field b B -local mt - -function mt:f() - self.b:x() - print(<?self?>) -end -]] - -TEST 'string?' [[ ----@return string? -local function f() end - -local <?x?> = f() -]] - -TEST 'AA' [[ ----@class AA ----@overload fun():AA -local AAA - - -local <?x?> = AAA() -]] - -TEST 'AA' [[ ----@class AA ----@overload fun():AA -AAA = {} - - -local <?x?> = AAA() -]] - -TEST 'string' [[ -local <?x?> -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x -<?x?> = '1' -x = 1 -]] - -TEST 'integer' [[ -local x -x = '1' -<?x?> = 1 -]] - -TEST 'unknown' [[ -local x -print(<?x?>) -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x -x = '1' -print(<?x?>) -x = 1 -]] - -TEST 'integer' [[ -local x -x = '1' -x = 1 -print(<?x?>) -]] - -TEST 'unknown' [[ -local x - -function A() - print(<?x?>) -end -]] - -TEST 'string' [[ -local x - -function A() - print(<?x?>) -end - -x = '1' -x = 1 -]] - -TEST 'string' [[ -local x - -x = '1' - -function A() - print(<?x?>) -end - -x = 1 -]] - -TEST 'integer' [[ -local x - -x = '1' -x = 1 - -function A() - print(<?x?>) -end - -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - print(<?x?>) -end - -x = '1' -x = 1 -]] - -TEST 'unknown' [[ -local x - -function A() - x = true -end - -print(<?x?>) -x = '1' -x = 1 -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - function B() - print(<?x?>) - end -end - -x = '1' -x = 1 -]] - -TEST 'table' [[ -local x - -function A() - x = true - function B() - x = {} - print(<?x?>) - end -end - -x = '1' -x = 1 -]] - -TEST 'boolean' [[ -local x - -function A() - x = true - function B() - x = {} - end - print(<?x?>) -end - -x = '1' -x = 1 -]] - -TEST 'unknown' [[ -local x - -function A() - x = true - function B() - x = {} - end -end - -function C() - print(<?x?>) -end - -x = '1' -x = 1 -]] - -TEST 'integer' [[ -local x -x = true -do - x = 1 -end -print(<?x?>) -]] - -TEST 'boolean' [[ -local x -x = true -function XX() - do - x = 1 - end -end -print(<?x?>) -]] - -TEST 'integer?' [[ ----@type integer? -local <?x?> -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if <?x?> then - print(x) -end -]] ---[[ -context 0 integer? - -save copy 'block' -save copy 'out' -push 'block' -get -push copy -truthy -falsy ref 'out' -get -save HEAD 'final' -push 'out' - -push copy HEAD -merge 'final' -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x then - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x then - print(x) -end - -print(<?x?>) -]] - -TEST 'nil' [[ ----@type integer? -local x - -if not x then - print(<?x?>) -end - -print(x) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - x = 1 -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if xxx and x then - print(<?x?>) -end -]] - -TEST 'unknown' [[ ----@type integer? -local x - -if not x and x then - print(<?x?>) -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x and not mark[x] then - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if xxx and x then -end - -print(<?x?>) -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if xxx and x then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x ~= nil then - print(<?x?>) -end - -print(x) -]] - -TEST 'integer|nil' [[ ----@type integer? -local x - -if x ~= nil then - print(x) -end - -print(<?x?>) -]] - -TEST 'nil' [[ ----@type integer? -local x - -if x == nil then - print(<?x?>) -end - -print(x) -]] - -TEST 'integer|nil' [[ ----@type integer? -local x - -if x == nil then - print(x) -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -<?x?> = x or 1 -]] - -TEST 'integer' [[ ----@type integer? -local x - -<?x?> = x or y -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x then - goto ANYWHERE -end - -print(<?x?>) -]] - -TEST 'integer' [=[ -local x - -print(<?x?>--[[@as integer]]) -]=] - -TEST 'integer' [=[ -print(<?io?>--[[@as integer]]) -]=] - -TEST 'integer' [=[ -print(io.<?open?>--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local <?x?> = io['open']--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local <?x?> = 1 + 1--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local <?x?> = not 1--[[@as integer]]) -]=] - -TEST 'integer' [=[ -local <?x?> = ()--[[@as integer]]) -]=] - -TEST 'integer?' [[ ----@param x? integer -local function f(<?x?>) - -end -]] - -TEST 'integer' [[ -local x = 1 -x = <?x?> -]] - -TEST 'integer?' [[ ----@class A ----@field x? integer -local t - -t.<?x?> -]] - -TEST 'integer?' [[ ----@type { x?: integer } -local t - -t.<?x?> -]] - -TEST 'boolean' [[ ----@class A ----@field [integer] boolean -local t - -local <?x?> = t[1] -]] - -TEST 'unknown' [[ -local <?x?> = y and z -]] - -TEST 'integer' [[ ----@type integer? -local x - -assert(x) - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -assert(x ~= nil) - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer | nil -local x - -assert(x) - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer | nil -local x - -assert(x ~= nil) - -print(<?x?>) -]] - -TEST 'integer' [[ -local x - -assert(x == 1) - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if x and <?x?>.y then -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x and x.y then -end - -print(<?x?>) -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x and x.y then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x or <?x?>.y then -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if not x or x.y then - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x or x.y then - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x.y or x then - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -if x.y or not x then - print(<?x?>) -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not x or not y then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -if not y or not x then - return -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -while true do - if not x then - break - end - print(<?x?>) -end -]] - -TEST 'integer?' [[ ----@type integer? -local x - -while true do - if not x then - break - end -end - -print(<?x?>) -]] - -TEST 'integer' [[ ----@type integer? -local x - -while x do - print(<?x?>) -end -]] - -TEST 'integer' [[ ----@type fun():integer? -local iter - -for <?x?> in iter do -end -]] - -TEST 'integer' [[ -local x - ----@type integer -<?x?> = XXX -]] - -TEST 'unknown' [[ -for _ = 1, 999 do - local <?x?> -end -]] - -TEST 'integer' [[ -local x - ----@cast x integer - -print(<?x?>) -]] - -TEST 'unknown' [[ -local x - ----@cast x integer - -local x -print(<?x?>) -]] - -TEST 'unknown' [[ -local x - -if true then - local x - ---@cast x integer - print(x) -end - -print(<?x?>) -]] - -TEST 'boolean|integer' [[ -local x = 1 - ----@cast x +boolean - -print(<?x?>) -]] - -TEST 'boolean' [[ ----@type integer|boolean -local x - ----@cast x -integer - -print(<?x?>) -]] - -TEST 'boolean?' [[ ----@type boolean -local x - ----@cast x +? - -print(<?x?>) -]] - -TEST 'boolean' [[ ----@type boolean? -local x - ----@cast x -? - -print(<?x?>) -]] - -TEST 'nil' [[ ----@type string? -local x - -if x then - return -else - print(<?x?>) -end - -print(x) -]] - -TEST 'string' [[ ----@type string? -local x - -if not x then - return -else - print(<?x?>) -end - -print(x) -]] - -TEST 'string' [[ ----@type string? -local x - -if not x then - return -else - print(x) -end - -print(<?x?>) -]] - -TEST 'true' [[ ----@type boolean | nil -local x - -if not x then - return -end - -print(<?x?>) -]] - -TEST 'true' [[ ----@type boolean -local t - -if t then - print(<?t?>) - return -end - -print(t) -]] - -TEST 'false' [[ ----@type boolean -local t - -if t then - print(t) - return -end - -print(<?t?>) -]] - -TEST 'nil' [[ ----@type integer? -local t - -if t then -else - print(<?t?>) -end - -print(t) -]] - -TEST 'table' [[ -local function f() - if x then - return y - end - return {} -end - -local <?z?> = f() -]] - -TEST 'integer|table' [[ -local function returnI() - return 1 -end - -local function f() - if x then - return returnI() - end - return {} -end - -local <?z?> = f() -]] - -TEST 'number' [[ -for _ in _ do - ---@type number - local <?x?> -end -]] - -TEST 'unknown' [[ -for _ in _ do - ---@param x number - local <?x?> -end -]] - -TEST 'unknown' [[ ----@type number -for <?x?> in _ do -end -]] - -TEST 'number' [[ ----@param x number -for <?x?> in _ do -end -]] - -TEST 'table' [[ ----@alias tp table - ----@type tp -local <?x?> -]] - -TEST '{ name: boolean }' [[ ----@alias tp {name: boolean} - ----@type tp -local <?x?> -]] - -TEST 'boolean|{ name: boolean }' [[ ----@alias tp boolean | {name: boolean} - ----@type tp -local <?x?> -]] - -TEST '`1`|`true`' [[ ----@type `1` | `true` -local <?x?> -]] - -TEST 'function' [[ -local x - -function x() end - -print(<?x?>) -]] - -TEST 'unknown' [[ -local x - -if x.field == 'haha' then - print(<?x?>) -end -]] - -TEST 'string' [[ ----@type string? -local t - -if not t or xxx then - return -end - -print(<?t?>) -]] - -TEST 'table' [[ ----@type table|nil -local t - -return function () - if not t then - return - end - - print(<?t?>) -end -]] - -TEST 'table' [[ ----@type table|nil -local t - -f(function () - if not t then - return - end - - print(<?t?>) -end) -]] - -TEST 'table' [[ ----@type table? -local t - -t = t or {} - -print(<?t?>) -]] - -TEST 'unknown|nil' [[ -local x - -if x == nil then -end - -print(<?x?>) -]] - -TEST 'table<xxx, true>' [[ ----@alias xxx table<xxx, true> - ----@type xxx -local <?t?> -]] - -TEST 'xxx[][]' [[ ----@alias xxx xxx[] - ----@type xxx -local <?t?> -]] - -TEST 'fun(x: fun(x: xxx))' [[ ----@alias xxx fun(x: xxx) - ----@type xxx -local <?t?> -]] - -TEST 'table' [[ ----@type table|nil -local t - -while t do - print(<?t?>) -end -]] - -TEST 'table|nil' [[ ----@type table|nil -local t - -while <?t?> do - print(t) -end -]] - -TEST 'table' [[ ----@type table|nil -local t - -while t ~= nil do - print(<?t?>) -end -]] - -TEST 'table|nil' [[ ----@type table|nil -local t - -while <?t?> ~= nil do - print(t) -end -]] - -TEST 'integer' [[ ----@type integer? -local n - -if not n then - error('n is nil') -end - -print(<?n?>) -]] - -TEST 'integer' [[ ----@type integer? -local n - -if not n then - os.exit() -end - -print(<?n?>) -]] - -TEST 'table' [[ ----@type table? -local n - -print((n and <?n?>.x)) -]] - -TEST 'table' [[ ----@type table? -local n - -n = n and <?n?>.x or 1 -]] - -TEST 'table' [[ ----@type table? -local n - -n = ff[n and <?n?>.x] -]] - -TEST 'integer' [[ -local x - -if type(x) == 'integer' then - print(<?x?>) -end -]] - -TEST 'boolean|integer' [[ -local x - -if type(x) == 'integer' -or type(x) == 'boolean' then - print(<?x?>) -end -]] - -TEST 'fun()' [[ ----@type fun()? -local x - -if type(x) == 'function' then - print(<?x?>) -end -]] - -TEST 'function' [[ -local x - -if type(x) == 'function' then - print(<?x?>) -end -]] - -TEST 'integer' [[ -local x -local tp = type(x) - -if tp == 'integer' then - print(<?x?>) -end -]] - -TEST 'integer' [[ ----@type integer? -local x - -if (x == nil) then -else - print(<?x?>) -end -]] - -TEST 'B' [[ ----@class A ----@class B - ----@type A -local x - ----@type B -x = call(x) - -print(<?x?>) -]] - -TEST 'nil' [[ -local function f() -end - -local <?x?> = f() -]] - -TEST 'integer[]' [[ ----@type integer[] -local x -if not x then - return -end - -print(<?x?>) -]] - -TEST 'unknown' [[ ----@type string[] -local t - -local <?x?> = t.x -]] - -TEST 'integer|unknown' [[ -local function f() - return GG -end - -local t - -t.x = 1 -t.x = f() - -print(t.<?x?>) -]] - -TEST 'integer' [[ -local function f() - if X then - return X - else - return 1 - end -end - -local <?n?> = f() -]] - -TEST 'unknown' [[ -local function f() - return t[k] -end - -local <?n?> = f() -]] - -TEST 'integer|nil' [[ -local function f() - if x then - return - else - return 1 - end -end - -local <?n?> = f() -]] - -TEST 'integer' [[ ----@class A ----@field x integer -local m - -m.<?x?> = true - -print(m.x) -]] - -TEST 'integer' [[ ----@class A ----@field x integer -local m - -m.x = true - -print(m.<?x?>) -]] - -TEST 'integer' [[ ----@class A ----@field x integer --> 1st -local m = { - x = '' --> 2nd -} - ----@type boolean -m.x = true --> 3rd (with ---@type above) - -m.x = {} --> 4th - -print(m.<?x?>) -]] - -TEST 'string' [[ ----@class A -----@field x integer --> 1st -local m = { - x = '' --> 2nd -} - ----@type boolean -m.x = true --> 3rd (with ---@type above) - -m.x = {} --> 4th - -print(m.<?x?>) -]] - -TEST 'boolean' [[ ----@class A -----@field x integer --> 1st -local m = { - --x = '' --> 2nd -} - ----@type boolean -m.x = true --> 3rd (with ---@type above) - -m.x = {} --> 4th - -print(m.<?x?>) -]] - -TEST 'table' [[ ----@class A -----@field x integer --> 1st -local m = { - --x = '' --> 2nd -} - ----@type boolean ---m.x = true --> 3rd (with ---@type above) - -m.x = {} --> 4th - -print(m.<?x?>) -]] - -TEST 'boolean?' [[ ----@generic T ----@param x T ----@return T -local function echo(x) end - ----@type boolean? -local b - -local <?x?> = echo(b) -]] - -TEST 'boolean' [[ ----@generic T ----@param x T? ----@return T -local function echo(x) end - ----@type boolean? -local b - -local <?x?> = echo(b) -]] - -TEST 'boolean' [[ ----@generic T ----@param x? T ----@return T -local function echo(x) end - ----@type boolean? -local b - -local <?x?> = echo(b) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -local <?x?> = f() -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -local <?x?> = f(1) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -function r0() - return -end - -local <?x?> = f(r0()) -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - -function r1() - return 1 -end - -local <?x?> = f(r1()) -]] - -TEST 'boolean' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - ----@type fun() -local r0 - -local <?x?> = f(r0()) -]] - -TEST 'number' [[ ----@overload fun():boolean ----@param x integer ----@return number -function f(x) -end - ----@type fun():integer -local r1 - -local <?x?> = f(r1()) -]] - -TEST 'boolean' [[ ----@overload fun(x: number, y: number):string ----@overload fun(x: number):number ----@return boolean -local function f() end - -local <?n1?> = f() -local n2 = f(0) -local n3 = f(0, 0) -]] - -TEST 'number' [[ ----@overload fun(x: number, y: number):string ----@overload fun(x: number):number ----@return boolean -local function f() end - -local n1 = f() -local <?n2?> = f(0) -local n3 = f(0, 0) -]] - -TEST 'string' [[ ----@overload fun(x: number, y: number):string ----@overload fun(x: number):number ----@return boolean -local function f() end - -local n1 = f() -local n2 = f(0) -local <?n3?> = f(0, 0) -]] - -TEST 'boolean' [[ ----@type {[integer]: boolean, xx: integer} -local t - -local <?n?> = t[1] -]] - -TEST 'boolean' [[ ----@type integer -local i - ----@type {[integer]: boolean, xx: integer} -local t - -local <?n?> = t[i] -]] - -TEST 'string' [=[ -local x = true -local y = x--[[@as integer]] --is `integer` here -local z = <?x?>--[[@as string]] --is `true` here -]=] - -TEST 'integer' [[ ----@type integer -local x - -if type(x) == 'number' then - print(<?x?>) -end -]] - -TEST 'boolean' [[ ----@class A ----@field [integer] boolean -local mt - -function mt:f() - ---@type integer - local index - local <?x?> = self[index] -end -]] - -TEST 'boolean' [[ ----@class A ----@field [B] boolean - ----@class B - ----@type A -local a - ----@type B -local b - -local <?x?> = a[b] -]] - -TEST 'number' [[ ----@type {x: string ; y: boolean; z: number} -local t - -local <?z?> = t.z -]] - -TEST 'fun():number, boolean' [[ ----@type {f: fun():number, boolean} -local t - -local <?f?> = t.f -]] - -TEST 'fun():number' [[ ----@type {(f: fun():number), x: boolean} -local t - -local <?f?> = t.f -]] - -TEST 'boolean' [[ ----@param ... boolean -local function f(...) - local <?n?> = ... -end -]] - -TEST 'boolean' [[ ----@param ... boolean -local function f(...) - local _, <?n?> = ... -end -]] - -TEST 'boolean' [[ ----@return boolean ... -local function f() end - -local <?n?> = f() -]] - -TEST 'boolean' [[ ----@return boolean ... -local function f() end - -local _, <?n?> = f() -]] - -TEST 'boolean' [[ ----@type fun():name1: boolean, name2:number -local f - -local <?n?> = f() -]] - -TEST 'number' [[ ----@type fun():name1: boolean, name2:number -local f - -local _, <?n?> = f() -]] -TEST 'boolean' [[ ----@type fun():(name1: boolean, name2:number) -local f - -local <?n?> = f() -]] - -TEST 'number' [[ ----@type fun():(name1: boolean, name2:number) -local f - -local _, <?n?> = f() -]] - -TEST 'boolean' [[ ----@type fun():...: boolean -local f - -local _, <?n?> = f() -]] - -TEST 'string' [[ -local s -while true do - s = '' -end -print(<?s?>) -]] - -TEST 'string' [[ -local s -for _ in _ do - s = '' -end -print(<?s?>) -]] - -TEST 'A' [[ ----@class A: string - ----@type A -local <?s?> = '' -]] - -TEST 'number' [[ ----@return number -local function f() end -local x, <?y?> = 1, f() -]] - -TEST 'boolean' [[ ----@return number, boolean -local function f() end -local x, y, <?z?> = 1, f() -]] - -TEST 'number' [[ ----@return number, boolean -local function f() end -local x, y, <?z?> = 1, 2, f() -]] - -TEST 'unknown' [[ -local f - -print(<?f?>) - -function f() end -]] - -TEST 'unknown' [[ -local f - -do - print(<?f?>) -end - -function f() end -]] - -TEST 'function' [[ -local f - -function A() - print(<?f?>) -end - -function f() end -]] - -TEST 'number' [[ ----@type number|nil -local n - -local t = { - x = n and <?n?>, -} -]] - -TEST 'table' [[ ----@type table? -local n - -if not n or not <?n?>.x then -end -]] - -TEST 'table' [[ ----@type table? -local n - -if not n or not <?n?>[1] then -end -]] - -TEST 'number' [[ ----@type number|false -local n - ----@cast n -false - -print(<?n?>) -]] - -TEST 'table' [[ ----@type number|table -local n - -if n ----@cast n table -and <?n?>.type == 'xxx' then -end -]] - -TEST 'integer' [[ ----@type integer? -local n -if true then - n = 0 -end -local <?x?> = n or 0 -]] - -TEST 'number' [=[ -local <?x?> = F()--[[@as number]] -]=] - -TEST 'number' [=[ -local function f() - return F()--[[@as number]] -end - -local <?x?> = f() -]=] - -TEST 'number' [=[ -local <?x?> = X --[[@as number]] -]=] - -TEST 'number' [[ ----@return number?, number? -local function f() end - -for <?x?>, y in f do -end -]] - -TEST 'number' [[ ----@return number?, number? -local function f() end - -for x, <?y?> in f do -end -]] - -TEST 'number|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local <?c?> = a and b -]] - -TEST 'number|table|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local <?c?> = a or b -]] - -TEST 'number|table|nil' [[ ----@type table|nil -local a - ----@type number|nil -local b - -local c = a and b -local <?d?> = a or b -]] - -TEST 'number' [[ -local x - ----@return number -local function f() -end - -x = f() - -print(<?x?>) -]] - -TEST 'number' [[ -local x - ----@return number -local function f() -end - -_, x = pcall(f) - -print(<?x?>) -]] - -TEST 'string' [[ ----@type table<string|number, string> -local t - ----@type number -local n ----@type string -local s - -local <?test?> = t[n] -local test2 = t[s] --test and test2 are unknow -]] - -TEST 'string' [[ ----@type table<string|number, string> -local t - ----@type number -local n ----@type string -local s - -local test = t[n] -local <?test2?> = t[s] --test and test2 are unknow -]] - -TEST 'table<number, boolean>' [[ ----@type table<number, boolean> -local t - -<?t?> = {} -]] - -TEST 'integer' [[ ----@type integer[]|A -local t - -local <?x?> = t[1] -]] - -TEST 'integer' [[ ----@type integer ----@diagnostic disable -local <?t?> -]] - -TEST 'A' [[ ----@class A ----@diagnostic disable -local <?t?> -]] - -TEST '{ [string]: number, [true]: string, [1]: boolean, tag: integer }' [[ ----@type {[string]: number, [true]: string, [1]: boolean, tag: integer} -local <?t?> -]] - -TEST 'unknown' [[ -local mt = {} -mt.<?x?> = nil -]] - -TEST 'unknown' [[ -mt = {} -mt.<?x?> = nil -]] - -TEST 'A' [[ ----@class A ----@operator unm: A - ----@type A -local a -local <?b?> = -a -]] - -TEST 'A' [[ ----@class A ----@operator bnot: A - ----@type A -local a -local <?b?> = ~a -]] - -TEST 'A' [[ ----@class A ----@operator len: A - ----@type A -local a -local <?b?> = #a -]] - -TEST 'A' [[ ----@class A ----@operator add: A - ----@type A -local a -local <?b?> = a + 1 -]] - -TEST 'A' [[ ----@class A ----@operator sub: A - ----@type A -local a -local <?b?> = a - 1 -]] - -TEST 'A' [[ ----@class A ----@operator mul: A - ----@type A -local a -local <?b?> = a * 1 -]] - -TEST 'A' [[ ----@class A ----@operator div: A - ----@type A -local a -local <?b?> = a / 1 -]] - -TEST 'A' [[ ----@class A ----@operator mod: A - ----@type A -local a -local <?b?> = a % 1 -]] - -TEST 'A' [[ ----@class A ----@operator pow: A - ----@type A -local a -local <?b?> = a ^ 1 -]] - -TEST 'A' [[ ----@class A ----@operator idiv: A - ----@type A -local a -local <?b?> = a // 1 -]] - -TEST 'A' [[ ----@class A ----@operator band: A - ----@type A -local a -local <?b?> = a & 1 -]] - -TEST 'A' [[ ----@class A ----@operator bor: A - ----@type A -local a -local <?b?> = a | 1 -]] - -TEST 'A' [[ ----@class A ----@operator bxor: A - ----@type A -local a -local <?b?> = a ~ 1 -]] - -TEST 'A' [[ ----@class A ----@operator shl: A - ----@type A -local a -local <?b?> = a << 1 -]] - -TEST 'A' [[ ----@class A ----@operator shr: A - ----@type A -local a -local <?b?> = a >> 1 -]] - -TEST 'A' [[ ----@class A ----@operator concat: A - ----@type A -local a -local <?b?> = a .. 1 -]] - -TEST 'A' [[ ----@class A ----@operator add(boolean): boolean ----@operator add(integer): A - ----@type A -local a -local <?b?> = a + 1 -]] - -TEST 'boolean' [[ ----@class A ----@operator add(boolean): boolean ----@operator add(integer): A - ----@type A -local a -local <?b?> = a + true -]] - -TEST 'A' [[ ----@class A ----@operator call: A - ----@type A -local a -local <?b?> = a() -]] - -TEST 'A' [[ ----@class A ----@operator call: A - ----@type A -local a - -local t = { - <?x?> = a(), -} -]] - -TEST 'boolean' [[ ----@class A ----@field n number ----@field [string] boolean -local t - -local <?x?> = t.xx -]] - -TEST 'number' [[ ----@class A ----@field n number ----@field [string] boolean -local t - -local <?x?> = t.n -]] - -TEST 'string' [[ ----@class string ----@operator mod: string - -local <?b?> = '' % 1 -]] - -TEST 'string|integer' [[ ----@type boolean -local bool - -local <?x?> = bool and '' or 0 -]] - -TEST 'string|integer' [[ -local bool - -if X then - bool = true -else - bool = false -end - -local <?x?> = bool and '' or 0 -]] - -TEST 'boolean' [[ ----@type boolean|true|false -local <?b?> -]] - -TEST 'integer|false' [[ -local <?b?> = X == 1 and X == 1 and 1 -]] - -TEST 'unknown|nil' [[ -local function f() - if X then - return ({})[1] - end - return nil -end - -local <?n?> = f() -]] - -TEST 'integer' [[ ----@generic T ----@vararg T # ERROR ----@return T -local function test(...) - return ... -end - -local <?n?> = test(1) -]] - -TEST 'boolean' [[ ----@type boolean, number -local <?x?>, y -]] - -TEST 'number' [[ ----@type boolean, number -local x, <?y?> -]] - -TEST 'unknown' [[ ----@type _, number -local <?x?>, y -]] - -TEST 'number[]' [[ -local t ----@cast t number[]? - -local x = t and <?t?>[i] -]] - -TEST 'number?' [[ ----@type number[]? -local t - -local <?x?> = t and t[i] -]] - -TEST 'number' [[ ----@type number -local x - -if not <?x?>.y then - x = nil -end -]] - -TEST 'number' [[ ----@type number|nil -local x -while x == nil do - if x == nil then - return - end - - x = nil -end - -print(<?x?>) -]] - -TEST 'integer' [[ -local A = { - ---@class XXX - B = {} -} - -A.B.C = 1 - -print(A.B.<?C?>) -]] - -TEST '-2|-3|1' [[ ----@type 1|-2|-3 -local <?n?> -]] - -TEST 'table' [[ ----@enum A -local m = {} - -print(<?m?>) -]] - -TEST 'A' [[ ----@class A ----@overload fun():A -local m = {} - ----@return A -function m:init() - return <?self?> -end -]] - -TEST 'string' [[ ----@vararg string -function F(...) - local t = {...} - for k, <?v?> in pairs(t) do - end -end -]] - -TEST 'string' [[ ----@vararg string -function F(...) - local t = {...} - for k, <?v?> in ipairs(t) do - end -end -]] - -TEST 'integerA' [[ ----@type integerA -for <?i?> = 1, 10 do -end -]] - -TEST 'string' [[ ----@class A ----@field x string - ----@class B : A -local t = {} - -t.x = t.x - -print(t.<?x?>) -]] - -TEST 'unknown' [[ -local t = { - x = 1, -} - -local x - -local <?v?> = t[x] -]] - -TEST 'A|B' [[ ----@class A ----@class B: A - ----@type A|B -local <?t?> -]] - -TEST 'function' [[ ----@class myClass -local myClass = { has = { nested = {} } } - -function myClass.has.nested.fn() end - ----@type myClass -local class - -class.has.nested.<?fn?>() -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun(x: T) ----@return T[] -local function x(f) end - ----@param x integer -local <?arr?> = x(function (x) end) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - -local <?arr?> = x(function () - return 1 -end) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - ----@return integer -local <?arr?> = x(function () end) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun(x: T) ----@return T[] -local function x(f) end - ----@type fun(x: integer) -local cb - -local <?arr?> = x(cb) -]] - -TEST 'integer[]' [[ ----@generic T ----@param f fun():T ----@return T[] -local function x(f) end - ----@type fun(): integer -local cb - -local <?arr?> = x(cb) -]] - -TEST 'integer' [[ ----@return fun(x: integer) -local function f() - return function (<?x?>) - end -end -]] - -TEST 'string' [[ ----@class A ----@field f fun(x: string) - ----@type A -local t = { - f = function (<?x?>) end -} -]] - -config.set(nil, 'Lua.runtime.special', { - ['xx.assert'] = 'assert' -}) - -TEST 'number' [[ ----@type number? -local t - -xx.assert(t) - -print(<?t?>) -]] - -config.set(nil, 'Lua.runtime.special', nil) - -TEST 'A' [[ ----@class A -local mt - ----@return <?self?> -function mt:init() -end -]] - -TEST 'A' [[ ----@class A -local mt - ----@return self -function mt:init() -end - -local <?o?> = mt:init() -]] - -TEST 'A' [[ ----@class A ----@field x <?self?> -]] - -TEST 'A' [[ ----@class A ----@field x self - ----@type A -local o - -print(o.<?x?>) -]] - -TEST 'A' [[ ----@class A ----@overload fun(): self -local A - -local <?o?> = A() -]] - -TEST 'number' [[ ----@type table<'Test1', fun(x: number)> -local t = { - ["Test1"] = function(<?x?>) end, -} -]] - -TEST 'number' [[ ----@type table<5, fun(x: number)> -local t = { - [5] = function(<?x?>) end, -} -]] - -TEST 'number' [[ ----@type fun(x: number) -local function f(<?x?>) end -]] - -TEST 'boolean' [[ ----@generic T: string | boolean | table ----@param x T ----@return T -local function f(x) - return x -end - -local <?x?> = f(true) -]] - -TEST 'number' [[ ----@class A ----@field [1] number ----@field [2] boolean -local t - -local <?n?> = t[1] -]] - -TEST 'boolean' [[ ----@class A ----@field [1] number ----@field [2] boolean -local t - -local <?n?> = t[2] -]] - -TEST 'N' [[ ----@class N: number -local x - -if x == 0.1 then - print(<?x?>) -end -]] - -TEST 'vec3' [[ ----@class mat4 ----@operator mul(vec3): vec3 -- matrix * vector ----@operator mul(number): mat4 -- matrix * constant - ----@class vec3: number - ----@type mat4, vec3 -local m, v - -local <?r?> = m * v -]] - -TEST 'mat4' [[ ----@class mat4 ----@operator mul(number): mat4 -- matrix * constant ----@operator mul(vec3): vec3 -- matrix * vector - ----@class vec3: number - ----@type mat4, vec3 -local m, v - -local <?r?> = m * v -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A -else - print(<?t?>) -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A -elseif <?t?> then -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A - print(t) -elseif <?t?> then -end -]] - -TEST 'A|B' [[ ----@class A ----@class B - ----@type A|B -local t - -if x then - ---@cast t A - print(t) -elseif <?t?> then - ---@cast t A - print(t) -end -]] - -TEST 'function' [[ -local function x() - print(<?x?>) -end -]] - -TEST 'number' [[ ----@type number? -local x - -do - if not x then - return - end -end - -print(<?x?>) -]] - -TEST 'number' [[ ----@type number[] -local xs - ----@type fun(x): number? -local f - -for _, <?x?> in ipairs(xs) do - x = f(x) -end -]] - -TEST 'number' [[ ----@type number? -X = Y - -if X then - print(<?X?>) -end -]] - -TEST 'number' [[ ----@type number|boolean -X = Y - -if type(X) == 'number' then - print(<?X?>) -end -]] - -TEST 'boolean' [[ ----@type number|boolean -X = Y - -if type(X) ~= 'number' then - print(<?X?>) -end -]] - -TEST 'boolean' [[ ----@type number -X = Y - ----@cast X boolean - -print(<?X?>) -]] - -TEST 'number' [[ ----@type number -local t - -if xxx == <?t?> then - print(t) -end -]] - -TEST 'V' [[ ----@class V -X = 1 - -print(<?X?>) -]] - -TEST 'V' [[ ----@class V -X.Y = 1 - -print(X.<?Y?>) -]] - -TEST 'integer' [[ -local x = {} - -x.y = 1 -local y = x.y -x.y = nil - -print(<?y?>) -]] - -TEST 'function' [[ -function X() - <?Y?>() -end - -function Y() -end -]] - -TEST 'A_Class' [[ ----@class A_Class -local A = { x = 5 } - -function A:func() - for i = 1, <?self?>.x do - print(i) - end - - self.y = 3 - self.y = self.y + 3 -end -]] - -TEST 'number' [[ ----@type number? -local n -local <?v?> = n or error('') -]] - -TEST 'Foo' [[ ----@class Foo ----@operator mul(Foo): Foo ----@operator mul(Bar): Foo ----@class Bar - ----@type Foo -local foo - ----@type Foo|Bar -local fooOrBar - -local <?b?> = foo * fooOrBar -]] - -TEST 'number' [[ -local a = 4; -local b = 2; - -local <?c?> = a / b; -]] - -TEST 'string' [[ -local a = '4'; -local b = '2'; - -local <?c?> = a .. b; -]] - -TEST 'number|{ [1]: string }' [[ ----@alias Some ----| { [1]: string } ----| number - -local x ---@type Some - -print(<?x?>) -]] - -TEST 'integer' [[ ----@class metatable : table ----@field __index table - ----@param table table ----@param metatable? metatable ----@return table -function setmetatable(table, metatable) end - -local m = setmetatable({},{ __index = { a = 1 } }) - -m.<?a?> -]] - -TEST 'integer' [[ ----@class metatable : table ----@field __index table - ----@param table table ----@param metatable? metatable ----@return table -function setmetatable(table, metatable) end - -local mt = {a = 1 } -local m = setmetatable({},{ __index = mt }) - -m.<?a?> -]] - -TEST 'integer' [[ -local x = 1 -repeat -until <?x?> -]] - --- #2144 -TEST 'A' [=[ -local function f() - return {} --[[@as A]] -end - -local <?x?> = f() -]=] - -TEST 'A' [=[ -local function f() - ---@type A - return {} -end - -local <?x?> = f() -]=] --- +require 'type_inference.common' +require 'type_inference.param_match' diff --git a/test/type_inference/param_match.lua b/test/type_inference/param_match.lua new file mode 100644 index 00000000..8ead05ef --- /dev/null +++ b/test/type_inference/param_match.lua @@ -0,0 +1,139 @@ + +TEST 'boolean' [[ +---@overload fun(x: number, y: number):string +---@overload fun(x: number):number +---@return boolean +local function f() end + +local <?n1?> = f() +local n2 = f(0) +local n3 = f(0, 0) +]] + +TEST 'number' [[ +---@overload fun(x: number, y: number):string +---@overload fun(x: number):number +---@return boolean +local function f() end + +local n1 = f() +local <?n2?> = f(0) +local n3 = f(0, 0) +]] + +TEST 'string' [[ +---@overload fun(x: number, y: number):string +---@overload fun(x: number):number +---@return boolean +local function f() end + +local n1 = f() +local n2 = f(0) +local <?n3?> = f(0, 0) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +local <?x?> = f() +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +local <?x?> = f(1) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +function r0() + return +end + +local <?x?> = f(r0()) +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +function r1() + return 1 +end + +local <?x?> = f(r1()) +]] + +TEST 'boolean' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +---@type fun() +local r0 + +local <?x?> = f(r0()) +]] + +TEST 'number' [[ +---@overload fun():boolean +---@param x integer +---@return number +function f(x) +end + +---@type fun():integer +local r1 + +local <?x?> = f(r1()) +]] + +TEST '1' [[ +---@overload fun(a: 'x'): 1 +---@overload fun(a: 'y'): 2 +local function f(...) end + +local <?r?> = f('x') +]] + +TEST '2' [[ +---@overload fun(a: 'x'): 1 +---@overload fun(a: 'y'): 2 +local function f(...) end + +local <?r?> = f('y') +]] + +TEST '1' [[ +---@overload fun(a: boolean): 1 +---@overload fun(a: number): 2 +local function f(...) end + +local <?r?> = f(true) +]] + +TEST '2' [[ +---@overload fun(a: boolean): 1 +---@overload fun(a: number): 2 +local function f(...) end + +local <?r?> = f(10) +]] |