summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCppCXY <812125110@qq.com>2024-07-01 14:14:48 +0800
committerCppCXY <812125110@qq.com>2024-07-01 14:14:48 +0800
commite30b59d91ddf7a81f72c5672039d65648e1c4bed (patch)
tree250ab9bc5bee49864e075d91fcce6cc65797030f
parentab2f9a77526330534d0baf4e01bd019b71ae45cf (diff)
parent3886d2ede3345296fbadc96c09d42cca96affa77 (diff)
downloadlua-language-server-e30b59d91ddf7a81f72c5672039d65648e1c4bed.zip
Merge branch 'master' of github.com:CppCXY/lua-language-server
-rwxr-xr-x.github/scripts/check-changelog.sh17
-rw-r--r--.github/workflows/build.yml62
-rw-r--r--.github/workflows/changelog.yml24
-rw-r--r--.github/workflows/test.yml22
-rw-r--r--.luarc.json1
-rw-r--r--.vscode/launch.json18
m---------3rd/bee.lua0
m---------3rd/love-api0
m---------3rd/luamake0
-rw-r--r--Dockerfile14
-rw-r--r--changelog.md117
-rw-r--r--doc/en-us/config.md17
-rw-r--r--doc/pt-br/config.md17
-rw-r--r--doc/zh-cn/config.md16
-rw-r--r--doc/zh-tw/config.md16
-rw-r--r--locale/en-us/script.lua4
-rw-r--r--locale/en-us/setting.lua6
-rw-r--r--locale/pt-br/script.lua4
-rw-r--r--locale/pt-br/setting.lua6
-rw-r--r--locale/zh-cn/script.lua4
-rw-r--r--locale/zh-cn/setting.lua6
-rw-r--r--locale/zh-tw/script.lua4
-rw-r--r--locale/zh-tw/setting.lua6
-rw-r--r--main.lua2
-rw-r--r--make.bat7
-rwxr-xr-xmake.sh7
-rw-r--r--meta/3rd/example/config.json2
-rw-r--r--meta/template/basic.lua7
-rw-r--r--script/await.lua22
-rw-r--r--script/brave/work.lua12
-rw-r--r--script/cli/check.lua172
-rw-r--r--script/cli/check_worker.lua168
-rw-r--r--script/cli/init.lua5
-rw-r--r--script/config/env.lua67
-rw-r--r--script/config/template.lua1
-rw-r--r--script/core/completion/completion.lua49
-rw-r--r--script/core/completion/postfix.lua18
-rw-r--r--script/core/diagnostics/assign-type-mismatch.lua3
-rw-r--r--script/core/diagnostics/discard-returns.lua7
-rw-r--r--script/core/diagnostics/duplicate-doc-alias.lua4
-rw-r--r--script/core/diagnostics/duplicate-doc-field.lua2
-rw-r--r--script/core/diagnostics/global-element.lua18
-rw-r--r--script/core/diagnostics/init.lua10
-rw-r--r--script/core/diagnostics/lowercase-global.lua18
-rw-r--r--script/core/diagnostics/need-check-nil.lua3
-rw-r--r--script/core/diagnostics/redundant-value.lua3
-rw-r--r--script/core/diagnostics/trailing-space.lua3
-rw-r--r--script/core/diagnostics/unbalanced-assignments.lua3
-rw-r--r--script/core/implementation.lua171
-rw-r--r--script/core/signature.lua5
-rw-r--r--script/core/type-definition.lua3
-rw-r--r--script/core/workspace-symbol.lua6
-rw-r--r--script/filewatch.lua2
-rw-r--r--script/global.d.lua8
-rw-r--r--script/library.lua2
-rw-r--r--script/meta/bee/filesystem.lua2
-rw-r--r--script/meta/bee/socket.lua6
-rw-r--r--script/meta/bee/thread.lua5
-rw-r--r--script/parser/compile.lua98
-rw-r--r--script/parser/guide.lua2
-rw-r--r--script/parser/luadoc.lua2
-rw-r--r--script/plugin.lua4
-rw-r--r--script/proto/proto.lua19
-rw-r--r--script/provider/diagnostic.lua11
-rw-r--r--script/provider/markdown.lua32
-rw-r--r--script/provider/provider.lua110
-rw-r--r--script/service/net.lua237
-rw-r--r--script/service/service.lua4
-rw-r--r--script/vm/compiler.lua13
-rw-r--r--script/vm/function.lua75
-rw-r--r--script/vm/global.lua69
-rw-r--r--script/vm/infer.lua3
-rw-r--r--script/vm/node.lua2
-rw-r--r--script/vm/type.lua10
-rw-r--r--script/workspace/require-path.lua6
-rw-r--r--test.lua3
-rw-r--r--test/basic/filewatch.lua1
-rw-r--r--test/basic/init.lua1
-rw-r--r--test/completion/common.lua100
-rw-r--r--test/crossfile/hover.lua2
-rw-r--r--test/diagnostics/discard-returns.lua256
-rw-r--r--test/diagnostics/duplicate-doc-alias.lua11
-rw-r--r--test/diagnostics/param-type-mismatch.lua18
-rw-r--r--test/full/projects.lua2
-rw-r--r--test/full/self.lua2
-rw-r--r--test/implementation/init.lua81
-rw-r--r--test/other/filewatch.lua6
-rw-r--r--test/type_inference/common.lua4194
-rw-r--r--test/type_inference/init.lua4294
-rw-r--r--test/type_inference/param_match.lua139
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'
diff --git a/main.lua b/main.lua
index 8ecfd472..5dfbab36 100644
--- a/main.lua
+++ b/main.lua
@@ -2,6 +2,8 @@ local fs = require 'bee.filesystem'
local util = require 'utility'
local version = require 'version'
+require 'config.env'
+
local function getValue(value)
if value == 'true' or value == nil then
value = true
diff --git a/make.bat b/make.bat
index 01cb1ddc..2ab11bc3 100644
--- a/make.bat
+++ b/make.bat
@@ -1,5 +1,10 @@
git submodule update --init --recursive
cd 3rd\luamake
call compile\install.bat
+call compile\build.bat
cd ..\..
-call 3rd\luamake\luamake.exe rebuild
+IF "%~1"=="" (
+ call 3rd\luamake\luamake.exe rebuild
+) ELSE (
+ call 3rd\luamake\luamake.exe rebuild --platform %1
+)
diff --git a/make.sh b/make.sh
index 31cca369..da9505a4 100755
--- a/make.sh
+++ b/make.sh
@@ -2,6 +2,11 @@
git submodule update --init --recursive
pushd 3rd/luamake
+./compile/install.sh
./compile/build.sh
popd
-./3rd/luamake/luamake rebuild
+if [ -z "$1" ]; then
+ 3rd/luamake/luamake rebuild
+else
+ 3rd/luamake/luamake rebuild --platform "$1"
+fi
diff --git a/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)
diff --git a/test.lua b/test.lua
index 1036b816..fa49655f 100644
--- a/test.lua
+++ b/test.lua
@@ -54,6 +54,7 @@ local function testAll()
test 'basic'
test 'definition'
test 'type_inference'
+ test 'implementation'
test 'references'
test 'hover'
test 'completion'
@@ -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)
+]]