diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9d4b7599d2c887aeebd59102984cbc7b964019b0..3ff7c0a9fe33eb7a5dc9b89b6cf0bf0dbb24e54a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,12 +42,14 @@ jobs:
           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
+          update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 100
+          update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100
 
       - name: Install aarch64-linux-gnu
         if: ${{ matrix.platform == 'linux-arm64' }}
         run: |
-          sudo apt-get update
-          sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
+          apt-get update
+          apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
 
       - uses: actions/checkout@v3
         with:
diff --git a/3rd/EmmyLuaCodeStyle b/3rd/EmmyLuaCodeStyle
index 81e9248056b8405f9c348a3875d16f3bfdac6ae2..7bee43410dfd52597379643bb0486e4b89eeb2f4 160000
--- a/3rd/EmmyLuaCodeStyle
+++ b/3rd/EmmyLuaCodeStyle
@@ -1 +1 @@
-Subproject commit 81e9248056b8405f9c348a3875d16f3bfdac6ae2
+Subproject commit 7bee43410dfd52597379643bb0486e4b89eeb2f4
diff --git a/3rd/bee.lua b/3rd/bee.lua
index 3720ee171729e3b327038e1b102c2002636076fd..053641127f15c3274bea2d579379718c300bfdbe 160000
--- a/3rd/bee.lua
+++ b/3rd/bee.lua
@@ -1 +1 @@
-Subproject commit 3720ee171729e3b327038e1b102c2002636076fd
+Subproject commit 053641127f15c3274bea2d579379718c300bfdbe
diff --git a/3rd/lovr-api b/3rd/lovr-api
index 9d1c3840b94835245a395f162f2785d761dbc9d3..f08de6bc658f385dfa1b6d1fcba52da5f88b7853 160000
--- a/3rd/lovr-api
+++ b/3rd/lovr-api
@@ -1 +1 @@
-Subproject commit 9d1c3840b94835245a395f162f2785d761dbc9d3
+Subproject commit f08de6bc658f385dfa1b6d1fcba52da5f88b7853
diff --git a/3rd/luamake b/3rd/luamake
index 5e74765c88ad45d424e693ab412ea1a448592a86..f3ed619b1b91ee0e73f33fcfdd5fd826d0f954fd 160000
--- a/3rd/luamake
+++ b/3rd/luamake
@@ -1 +1 @@
-Subproject commit 5e74765c88ad45d424e693ab412ea1a448592a86
+Subproject commit f3ed619b1b91ee0e73f33fcfdd5fd826d0f954fd
diff --git a/README.md b/README.md
index 53d57716a7cc157ec26281353e7d71ebdba5af57..c1262add8113f6b6832c80a725553a5184da2c1f 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
 # lua-language-server
 
-![build](https://github.com/LuaLS/lua-language-server/workflows/build/badge.svg)
+[![build](https://github.com/LuaLS/lua-language-server/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/LuaLS/lua-language-server/actions/workflows/build.yml)
 [![version](https://vsmarketplacebadges.dev/version-short/sumneko.lua.svg)](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
 ![installs](https://vsmarketplacebadges.dev/installs-short/sumneko.lua.svg)
 ![downloads](https://vsmarketplacebadges.dev/downloads-short/sumneko.lua.svg)
-[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/LuaLS/lua-language-server.svg)](https://github.com/LuaLS/lua-language-server/issues "Average time to resolve an issue")
 
 
 ***Lua development just got a whole lot better*** 🧠
@@ -33,25 +32,25 @@ The Lua language server provides various language features for Lua to make devel
 The language server can easily be installed for use in VS Code, but it can also be used by other clients using the command line.
 
 ### Visual Studio Code
-[![Install in VS Code](https://img.shields.io/badge/Install%20For-VS%20Code-blue?style=for-the-badge&logo=visualstudiocode "Install in VS Code")](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
+[![Install in VS Code](https://img.shields.io/badge/VS%20Code-Install-blue?style=for-the-badge&logo=visualstudiocode "Install in VS Code")](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
 
 The language server and Visual Studio Code client can be installed from [the VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=sumneko.lua).
 
 ![](https://github.com/LuaLS/vscode-lua/raw/master/images//Install%20In%20VSCode.gif)
 
 ### Command Line
-[![Install for command line](https://img.shields.io/badge/Install%20For-Command%20Line-blue?style=for-the-badge&logo=windowsterminal "Install for command line")](https://github.com/LuaLS/lua-language-server/wiki/Getting-Started#command-line)
+[![Install for command line](https://img.shields.io/badge/Command%20Line-Install-blue?style=for-the-badge&logo=windowsterminal "Install for command line")](https://github.com/LuaLS/lua-language-server/wiki/Getting-Started#command-line)
 
-Check the [wiki for a guide](https://github.com/LuaLS/lua-language-server/wiki/Getting-Started#command-line) to install the language server for use on the command line. This allows the language server to be used for NeoVim and other clients that follow the language server protocol.
+Check the [wiki for a guide](https://github.com/LuaLS/lua-language-server/wiki/Getting-Started#command-line) to install the language server for use on the command line. This allows the language server to be used with NeoVim and [other clients](https://microsoft.github.io/language-server-protocol/implementors/tools/) that follow the [language server protocol](https://microsoft.github.io/language-server-protocol/overviews/lsp/overview/).
 
 ## Supported Lua Versions
-| Version |    Supported   |
+| Version |   Supported    |
 | :-----: | :------------: |
-| Lua 5.1 | ![][checkmark] |
-| Lua 5.2 | ![][checkmark] |
-| Lua 5.3 | ![][checkmark] |
-| Lua 5.4 | ![][checkmark] |
-| LuaJIT  | ![][checkmark] |
+| Lua 5.1 | ![✅][checkmark] |
+| Lua 5.2 | ![✅][checkmark] |
+| Lua 5.3 | ![✅][checkmark] |
+| Lua 5.4 | ![✅][checkmark] |
+| LuaJIT  | ![✅][checkmark] |
 
 ## Links
 - [Changelog](https://github.com/LuaLS/lua-language-server/blob/master/changelog.md)
@@ -76,14 +75,13 @@ Check the [wiki for a guide](https://github.com/LuaLS/lua-language-server/wiki/G
 - `pt-br` 🇧🇷
 
 
-> ℹ Note: All translations are provided and collaborated on by the community. If you find an inappropriate or harmful translation, [please report it immediately](https://github.com/LuaLS/lua-language-server/issues).
+> **Note**
+> All translations are provided and collaborated on by the community. If you find an inappropriate or harmful translation, [please report it immediately](https://github.com/LuaLS/lua-language-server/issues).
 
 Are you able to [provide a translation](https://github.com/LuaLS/lua-language-server/wiki/Translations)? It would be greatly appreciated!
 
 Thank you to [all contributors of translations](https://github.com/LuaLS/lua-language-server/commits/master/locale)!
 
-[en-US]: https://github.com/LuaLS/lua-language-server/tree/master/locale/en-us
-
 ## Configuration
 Configuration of the server can be done in a number of ways, which are explained more in-depth in the [wiki](https://github.com/LuaLS/lua-language-server/wiki/Configuration-File).
 
@@ -95,7 +93,7 @@ See the [configuration file wiki page](https://github.com/LuaLS/lua-language-ser
 
 
 ## Privacy
-This language server has **opt-in** telemetry that collects usage data and sends it to the development team to help improve the extension. Read our [privacy policy](https://github.com/LuaLS/lua-language-server/wiki/Home#privacy) to learn more.
+The language server had **opt-in** telemetry that collected usage data and sent it to the development team to help improve the extension. Read our [privacy policy](https://github.com/LuaLS/lua-language-server/wiki/Home#privacy) to learn more. Telemetry was removed in `v3.6.5` and is no longer part of the language server.
 
 
 ## Contributors
diff --git a/changelog.md b/changelog.md
index 9de56cf6375418e65ae2e258685c57633caeee46..5009ced8f0c60eb9f5851f5b6e3c57f389913f52 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,16 +1,51 @@
 # changelog
 
+## 3.6.17
+`2023-3-9`
+* `CHG` export documents: export global variables
+* `FIX` [#1715]
+* `FIX` [#1753]
+* `FIX` [#1914]
+* `FIX` [#1922]
+* `FIX` [#1924]
+* `FIX` [#1928]
+* `FIX` [#1945]
+* `FIX` [#1955]
+* `FIX` [#1978]
+
+[#1715]: https://github.com/LuaLS/lua-language-server/issues/1715
+[#1753]: https://github.com/LuaLS/lua-language-server/issues/1753
+[#1914]: https://github.com/LuaLS/lua-language-server/issues/1914
+[#1922]: https://github.com/LuaLS/lua-language-server/issues/1922
+[#1924]: https://github.com/LuaLS/lua-language-server/issues/1924
+[#1928]: https://github.com/LuaLS/lua-language-server/issues/1928
+[#1945]: https://github.com/LuaLS/lua-language-server/issues/1945
+[#1955]: https://github.com/LuaLS/lua-language-server/issues/1955
+[#1978]: https://github.com/LuaLS/lua-language-server/issues/1978
+
+## 3.6.13
+`2023-3-2`
+* `FIX` setting: `Lua.addonManager.enable` should be `true` by default
+* `FIX` failed to publish to Windows
+
+## 3.6.12
+`2023-3-2`
+* `NEW` [Addon Manager](https://github.com/LuaLS/lua-language-server/discussions/1607), try it with command `lua.addon_manager.open`. Thanks to [carsakiller](https://github.com/carsakiller)!
+
 ## 3.6.11
+`2023-2-13`
 * `CHG` completion: don't show loading process
 * `FIX` [#1886]
 * `FIX` [#1887]
 * `FIX` [#1889]
 * `FIX` [#1895]
+* `FIX` [#1902]
 
 [#1886]: https://github.com/LuaLS/lua-language-server/issues/1886
 [#1887]: https://github.com/LuaLS/lua-language-server/issues/1887
 [#1889]: https://github.com/LuaLS/lua-language-server/issues/1889
 [#1895]: https://github.com/LuaLS/lua-language-server/issues/1895
+[#1902]: https://github.com/LuaLS/lua-language-server/issues/1902
 
 ## 3.6.10
 `2023-2-7`
diff --git a/doc/en-us/config.md b/doc/en-us/config.md
index dab1a456159d4b33a35f8198413491c0d7d41461..2d519824347b1e7294d7af37152134e1a05212d0 100644
--- a/doc/en-us/config.md
+++ b/doc/en-us/config.md
@@ -2161,22 +2161,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-Provide language server for the Lua files of the following scheme.
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 Ignore files list in `.gitignore` .
@@ -2207,4 +2191,4 @@ Array<string>
 
 ```jsonc
 []
-```
+```
\ No newline at end of file
diff --git a/doc/pt-br/config.md b/doc/pt-br/config.md
index 4682fa0d1b2df18175f3a57918201ef1d19c4dc7..24cf8d3b35a410c1bee5ab999c1a5ef616b22462 100644
--- a/doc/pt-br/config.md
+++ b/doc/pt-br/config.md
@@ -698,7 +698,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1`
+    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1` 
     */
     "ambiguity-1": "Any",
     /*
@@ -946,7 +946,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1`
+    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1` 
     */
     "ambiguity-1": "Warning",
     /*
@@ -2161,22 +2161,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-Provide language server for the Lua files of the following scheme.
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 Ignore files list in `.gitignore` .
@@ -2207,4 +2191,4 @@ Array<string>
 
 ```jsonc
 []
-```
+```
\ No newline at end of file
diff --git a/doc/zh-cn/config.md b/doc/zh-cn/config.md
index 6bf64e3ee3fdd7c79b96877d77ec6ffd4222f549..991932b6f1e7ea51c43ccc956a14c013066b6bc6 100644
--- a/doc/zh-cn/config.md
+++ b/doc/zh-cn/config.md
@@ -698,7 +698,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1`
+    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1` 
     */
     "ambiguity-1": "Any",
     /*
@@ -945,7 +945,7 @@ object<string, string>
 ```jsonc
 {
     /*
-    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1`
+    优先级歧义,如:`num or 0 + 1`,推测用户的实际期望为 `(num or 0) + 1` 
     */
     "ambiguity-1": "Warning",
     /*
@@ -2160,22 +2160,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-为以下 scheme 的lua文件提供语言服务。
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 忽略 `.gitignore` 中列举的文件。
@@ -2206,4 +2190,4 @@ Array<string>
 
 ```jsonc
 []
-```
+```
\ No newline at end of file
diff --git a/doc/zh-tw/config.md b/doc/zh-tw/config.md
index 8084374b21cd76a1484796222ef335064229d403..83cef315bb12720f73d694ebe0de1205325960b1 100644
--- a/doc/zh-tw/config.md
+++ b/doc/zh-tw/config.md
@@ -2160,22 +2160,6 @@ integer
 500
 ```
 
-# workspace.supportScheme
-
-為以下 `scheme` 的lua檔案提供語言伺服。
-
-## type
-
-```ts
-Array<string>
-```
-
-## default
-
-```jsonc
-["file","untitled","git"]
-```
-
 # workspace.useGitIgnore
 
 忽略 `.gitignore` 中列舉的檔案。
@@ -2206,4 +2190,4 @@ Array<string>
 
 ```jsonc
 []
-```
+```
\ No newline at end of file
diff --git a/locale/en-us/setting.lua b/locale/en-us/setting.lua
index 3b900e87a22f5741a457234ffcb65e6693f0b1c8..18840e94fa956eefc0e6c8c7a654442bd5b33dfc 100644
--- a/locale/en-us/setting.lua
+++ b/locale/en-us/setting.lua
@@ -1,5 +1,7 @@
 ---@diagnostic disable: undefined-global
 
+config.addonManager.enable        =
+"Whether the addon manager is enabled or not."
 config.runtime.version            =
 "Lua runtime version."
 config.runtime.path               =
@@ -423,4 +425,6 @@ config.typeFormat.config.format_line        =
 'Controls if a line is formatted at all.'
 
 command.exportDocument =
-'Export Document ...'
+'Lua: Export Document ...'
+command.addon_manager.open =
+'Lua: Open Addon Manager ...'
diff --git a/locale/pt-br/setting.lua b/locale/pt-br/setting.lua
index 6bd6bf2826a1bb9fdef9e930324ac3d7e7203ef6..75904872573e7cd44c396ccc2b40cc6749e656a2 100644
--- a/locale/pt-br/setting.lua
+++ b/locale/pt-br/setting.lua
@@ -1,5 +1,7 @@
 ---@diagnostic disable: undefined-global
 
+config.addonManager.enable        = -- TODO: need translate!
+"Whether the addon manager is enabled or not."
 config.runtime.version            = -- TODO: need translate!
 "Lua runtime version."
 config.runtime.path               = -- TODO: need translate!
@@ -423,4 +425,6 @@ config.typeFormat.config.format_line        = -- TODO: need translate!
 'Controls if a line is formatted at all.'
 
 command.exportDocument = -- TODO: need translate!
-'Export Document ...'
+'Lua: Export Document ...'
+command.addon_manager.open = -- TODO: need translate!
+'Lua: Open Addon Manager ...'
diff --git a/locale/zh-cn/setting.lua b/locale/zh-cn/setting.lua
index a62b659844683746533dad6c085b42cd993c9166..d488ac3f16c210d7a55951888e477c3f6c732b90 100644
--- a/locale/zh-cn/setting.lua
+++ b/locale/zh-cn/setting.lua
@@ -1,5 +1,7 @@
 ---@diagnostic disable: undefined-global
 
+config.addonManager.enable        = -- TODO: need translate!
+"Whether the addon manager is enabled or not."
 config.runtime.version            =
 "Lua运行版本。"
 config.runtime.path               =
@@ -422,4 +424,6 @@ config.typeFormat.config.format_line        = -- TODO: need translate!
 'Controls if a line is formatted at all.'
 
 command.exportDocument =
-'导出文档...'
+'Lua: 导出文档...'
+command.addon_manager.open =
+'Lua: 打开插件管理器...'
diff --git a/locale/zh-tw/setting.lua b/locale/zh-tw/setting.lua
index bc216fc89005f7893eca4097703cfa67d4cc250f..8c74668da5e3e7d161b01190dc3400aed6412d62 100644
--- a/locale/zh-tw/setting.lua
+++ b/locale/zh-tw/setting.lua
@@ -1,5 +1,7 @@
 ---@diagnostic disable: undefined-global
 
+config.addonManager.enable        = -- TODO: need translate!
+"Whether the addon manager is enabled or not."
 config.runtime.version            =
 "Lua執行版本。"
 config.runtime.path               =
@@ -422,4 +424,6 @@ config.typeFormat.config.format_line        = -- TODO: need translate!
 'Controls if a line is formatted at all.'
 
 command.exportDocument = -- TODO: need translate!
-'Export Document ...'
+'Lua: Export Document ...'
+command.addon_manager.open = -- TODO: need translate!
+'Lua: Open Addon Manager ...'
diff --git a/main.lua b/main.lua
index b724264b42fb595b67704604b8b3223f6fe7c5b8..cbf27129a0b6d63786598e6b04af3c19c0789014 100644
--- a/main.lua
+++ b/main.lua
@@ -70,10 +70,11 @@ log.info('METAPATH:', METAPATH)
 log.info('VERSION:', version.getVersion())
 
 require 'tracy'
-require 'cli'
 
 xpcall(dofile, log.debug, (ROOT / 'debugger.lua'):string())
 
+require 'cli'
+
 local _, service = xpcall(require, log.error, 'service')
 
 service.start()
diff --git a/make.lua b/make.lua
index c34905d62b467fa43022d8b57820e870b5b8ed2f..983f63344f468f0cccb96e7ba6a1ccf77fd539ac 100644
--- a/make.lua
+++ b/make.lua
@@ -1,11 +1,18 @@
 local lm = require 'luamake'
 
-lm.bindir = "bin"
 lm.c = lm.compiler == 'msvc' and 'c89' or 'c11'
 lm.cxx = 'c++17'
 
----@diagnostic disable-next-line: codestyle-check
-lm.EXE_DIR = ""
+if lm.sanitize then
+    lm.mode = "debug"
+    lm.flags = "-fsanitize=address"
+    lm.gcc = {
+        ldflags = "-fsanitize=address"
+    }
+    lm.clang = {
+        ldflags = "-fsanitize=address"
+    }
+end
 
 local includeCodeFormat = true
 
@@ -47,19 +54,28 @@ lm:executable "lua-language-server" {
     }
 }
 
+local platform = require 'bee.platform'
+local exe      = platform.OS == 'Windows' and ".exe" or ""
+
+lm:copy "copy_lua-language-server" {
+    input = lm.bindir .. "/lua-language-server" .. exe,
+    output = "bin/lua-language-server" .. exe,
+}
+
 lm:copy "copy_bootstrap" {
     input = "make/bootstrap.lua",
-    output = lm.bindir .. "/main.lua",
+    output = "bin/main.lua",
 }
 
 lm:msvc_copydll 'copy_vcrt' {
     type = "vcrt",
-    output = lm.bindir,
+    output = "bin",
 }
 
 lm:phony "all" {
     deps = {
         "lua-language-server",
+        "copy_lua-language-server",
         "copy_bootstrap",
     },
     windows = {
@@ -76,26 +92,24 @@ if lm.notest then
     return
 end
 
-local platform = require 'bee.platform'
-local exe      = platform.OS == 'Windows' and ".exe" or ""
+lm:rule "runtest" {
+    "bin/lua-language-server" .. exe, "$in",
+    description = "Run test: $in.",
+    pool = "console",
+}
 
 lm:build "bee-test" {
-    lm.bindir .. "/lua-language-server" .. exe, "3rd/bee.lua/test/test.lua",
-    pool = "console",
-    deps = {
-        "all",
-    }
+    rule = "runtest",
+    deps = { "all" },
+    input = "3rd/bee.lua/test/test.lua",
 }
 
 lm:build 'unit-test' {
-    lm.bindir .. "/lua-language-server" .. exe, 'test.lua',
-    pool = "console",
-    deps = {
-        "bee-test",
-    }
+    rule = "runtest",
+    deps = { "bee-test", "all" },
+    input = "test.lua",
 }
 
 lm:default {
-    "bee-test",
     "unit-test",
 }
diff --git a/make/modules.cpp b/make/modules.cpp
index 802e69161b01bb9f86c711c45cffbf7dd2346319..8cd6d2d90645ea492aa523037ef3bf630b841783 100644
--- a/make/modules.cpp
+++ b/make/modules.cpp
@@ -1,4 +1,4 @@
-#include <bee/lua/binding.h>
+#include <binding/binding.h>
 
 extern "C" int luaopen_lpeglabel (lua_State *L);
 static ::bee::lua::callfunc _init(::bee::lua::register_module, "lpeglabel", luaopen_lpeglabel);
diff --git a/meta/3rd/lovr/library/lovr/filesystem.lua b/meta/3rd/lovr/library/lovr/filesystem.lua
index ff6f2ad9cd9ce3436eee083ec0d5a9d24105a88e..c2a1220ba615bb7cea2e3b43cc0a69247ce766c2 100644
--- a/meta/3rd/lovr/library/lovr/filesystem.lua
+++ b/meta/3rd/lovr/library/lovr/filesystem.lua
@@ -3,9 +3,7 @@
 ---
 ---The `lovr.filesystem` module provides access to the filesystem.
 ---
----
----### NOTE:
----LÖVR programs can only write to a single directory, called the save directory.
+---All files written will go in a special folder called the "save directory".
 ---
 ---The location of the save directory is platform-specific:
 ---
@@ -27,15 +25,25 @@
 ---    <td><code>/sdcard/Android/data/&lt;identity&gt;/files</code></td>
 ---  </tr> </table>
 ---
----`<identity>` should be a unique identifier for your app.
+---`<identity>` is a unique identifier for the project, and can be set in `lovr.conf`.
+---
+---On Android, the identity can not be changed and will always be the package id (e.g. `org.lovr.app`).
+---
+---When files are read, they will be searched for in multiple places.
+---
+---By default, the save directory is checked first, then the project source (folder or zip).
 ---
----It can be set either in `lovr.conf` or by using `lovr.filesystem.setIdentity`.
+---That way, when data is written to a file, any future reads will see the new data.
 ---
----On Android, the identity can not be changed and will always be the package id, like `org.lovr.app`.
+---The `t.saveprecedence` conf setting can be used to change this precedence.
 ---
----All filenames are relative to either the save directory or the directory containing the project source.
+---Conceptually, `lovr.filesystem` uses a "virtual filesystem", which is an ordered list of folders and zip files that are merged into a single filesystem hierarchy.
 ---
----Files in the save directory take precedence over files in the project.
+---Folders and archives in the list can be added and removed with `lovr.filesystem.mount` and `lovr.filesystem.unmount`.
+---
+---LÖVR extends Lua's `require` function to look for modules in the virtual filesystem.
+---
+---The search patterns can be changed with `lovr.filesystem.setRequirePath`, similar to `package.path`.
 ---
 ---@class lovr.filesystem
 lovr.filesystem = {}
@@ -283,7 +291,7 @@ function lovr.filesystem.setIdentity(identity) end
 ---
 ---Any question marks in the pattern will be replaced with the module that is being required.
 ---
----It is similar to Lua\'s `package.path` variable, but the main difference is that the patterns are relative to the save directory and the project directory.
+---It is similar to Lua\'s `package.path` variable, except the patterns will be checked using `lovr.filesystem` APIs. This allows `require` to work even when the project is packaged into a zip archive, or when the project is launched from a different directory.
 ---
 ---
 ---### NOTE:
@@ -306,7 +314,7 @@ function lovr.filesystem.setRequirePath(path) end
 function lovr.filesystem.unmount(path) end
 
 ---
----Write to a file.
+---Write to a file in the save directory.
 ---
 ---
 ---### NOTE:
@@ -314,6 +322,10 @@ function lovr.filesystem.unmount(path) end
 ---
 ---If the file already has data in it, it will be replaced with the new content.
 ---
+---If the path contains subdirectories, all of the parent directories need to exist first or the write will fail.
+---
+---Use `lovr.filesystem.createDirectory` to make sure they're created first.
+---
 ---@overload fun(filename: string, blob: lovr.Blob):boolean
 ---@param filename string # The file to write to.
 ---@param content string # A string to write to the file.
diff --git a/meta/3rd/lovr/library/lovr/graphics.lua b/meta/3rd/lovr/library/lovr/graphics.lua
index 36045f00e2ac9c0cbc86e0fb2b0a00912fd12365..8157bdb262970ae0dc2b51e5990f07cc5c5d44d5 100644
--- a/meta/3rd/lovr/library/lovr/graphics.lua
+++ b/meta/3rd/lovr/library/lovr/graphics.lua
@@ -526,8 +526,8 @@ function lovr.graphics.newTally(type, count, views) end
 ---### NOTE:
 ---If no `type` is provided in the options table, LÖVR will guess the `TextureType` of the Texture based on the number of layers:
 ---
----- If there's 1 layer, the type will be `2d`.
----- If there are 6 layers, the type will be `cube`.
+---- If there's only 1 layer, the type will be `2d`.
+---- If there are 6 images provided, the type will be `cube`.
 ---- Otherwise, the type will be `array`.
 ---
 ---Note that an Image can contain multiple layers and mipmaps.
@@ -1534,7 +1534,9 @@ function Pass:capsule(transform, segments) end
 ---
 ---
 ---### NOTE:
----The local origin of the circle is in its center, and the local z axis goes through the center.
+---The local origin of the circle is in its center.
+---
+---The local z axis is perpendicular to plane of the circle.
 ---
 ---@param transform lovr.Mat4 # The transform of the circle.  Can also be provided as position, radius, and rotation, using a mix of `Vectors` or numbers.
 ---@param style? lovr.DrawStyle # Whether the circle should be filled or outlined.
@@ -1987,6 +1989,7 @@ function Pass:setAlphaToCoverage(enable) end
 ---### NOTE:
 ---The default blend mode is `alpha` with the `alphamultiply` alpha mode.
 ---
+---@overload fun(self: lovr.Pass)
 ---@param blend lovr.BlendMode # The blend mode.
 ---@param alphaBlend lovr.BlendAlphaMode # The alpha blend mode, used to control premultiplied alpha.
 function Pass:setBlendMode(blend, alphaBlend) end
diff --git a/meta/template/basic.lua b/meta/template/basic.lua
index f2f6a49c3be51821ea4922aaa03fb70242a8a91f..c024b22262dfdbdffdc104daa27dd265723dbf95 100644
--- a/meta/template/basic.lua
+++ b/meta/template/basic.lua
@@ -128,7 +128,7 @@ function loadfile(filename, mode, env) end
 function loadstring(text, chunkname) end
 
 ---@version 5.1
----@param proxy boolean|table
+---@param proxy boolean|table|userdata
 ---@return userdata
 ---@nodiscard
 function newproxy(proxy) end
diff --git a/meta/template/math.lua b/meta/template/math.lua
index 07917a2b0bffa9299cf9d377c583f29a33ad4eca..37e1d5c748dca43742eabb308c433f806f83f7ba 100644
--- a/meta/template/math.lua
+++ b/meta/template/math.lua
@@ -15,8 +15,9 @@
 math = {}
 
 ---#DES 'math.abs'
----@param x number
----@return number
+---@generic Number: number
+---@param x Number
+---@return Number
 ---@nodiscard
 function math.abs(x) end
 
diff --git a/meta/template/table.lua b/meta/template/table.lua
index 40ba0ac1ec35c932795e3573be4a5fbc2f379fa5..a2e9580b4225638ed4f20ba64aff41092e3e1613 100644
--- a/meta/template/table.lua
+++ b/meta/template/table.lua
@@ -37,7 +37,7 @@ function table.maxn(table) end
 ---@return table a2
 function table.move(a1, f, e, t, a2) end
 
----@version >5.2
+---@version >5.2, JIT
 ---#DES 'table.pack'
 ---@return table
 ---@nodiscard
@@ -55,7 +55,7 @@ function table.remove(list, pos) end
 ---@param comp? fun(a: T, b: T):boolean
 function table.sort(list, comp) end
 
----@version >5.2
+---@version >5.2, JIT
 ---#DES 'table.unpack'
 ---@generic T
 ---@param list T[]
diff --git a/script/cli/check.lua b/script/cli/check.lua
index 37b6ad1562e5cf60751fab91fed3b4bec7760433..ea767fae5c0c2490fbef8bdd3930aa8ac9c2b048 100644
--- a/script/cli/check.lua
+++ b/script/cli/check.lua
@@ -8,6 +8,9 @@ local jsonb    = require 'json-beautify'
 local lang     = require 'language'
 local define   = require 'proto.define'
 local config   = require 'config.config'
+local fs       = require 'bee.filesystem'
+
+require 'vm'
 
 lang(LOCALE)
 
@@ -16,9 +19,10 @@ if type(CHECK) ~= 'string' then
     return
 end
 
-local rootUri = furi.encode(CHECK)
+local rootPath = fs.absolute(fs.path(CHECK)):string()
+local rootUri = furi.encode(rootPath)
 if not rootUri then
-    print(lang.script('CLI_CHECK_ERROR_URI', CHECK))
+    print(lang.script('CLI_CHECK_ERROR_URI', rootPath))
     return
 end
 
diff --git a/script/cli/doc.lua b/script/cli/doc.lua
index cbbad6b0882f19541953f9457704c58f33c7ebb0..ae821527b4f89e398333c1e8955289bbfc7f6a7b 100644
--- a/script/cli/doc.lua
+++ b/script/cli/doc.lua
@@ -100,12 +100,13 @@ end
 ---@async
 ---@param global vm.global
 ---@param results table
-local function collect(global, results)
+local function collectTypes(global, results)
     if guide.isBasicType(global.name) then
         return
     end
     local result = {
         name    = global.name,
+        type    = 'type',
         desc    = nil,
         defines = {},
         fields  = {},
@@ -206,6 +207,48 @@ local function collect(global, results)
     end)
 end
 
+---@async
+---@param global vm.global
+---@param results table
+local function collectVars(global, results)
+    local result = {
+        name    = global:getCodeName(),
+        type    = 'variable',
+        desc    = nil,
+        defines = {},
+    }
+    for _, set in ipairs(global:getSets(ws.rootUri)) do
+        local uri = guide.getUri(set)
+        if files.isLibrary(uri) then
+            goto CONTINUE
+        end
+        if set.type == 'setglobal'
+        or set.type == 'setfield'
+        or set.type == 'setmethod'
+        or set.type == 'setindex' then
+            result.defines[#result.defines+1] = {
+                type    = set.type,
+                file    = guide.getUri(set),
+                start   = set.start,
+                finish  = set.finish,
+                extends = packObject(set.value),
+            }
+            result.desc = result.desc or getDesc(set)
+        end
+        ::CONTINUE::
+    end
+    if #result.defines == 0 then
+        return
+    end
+    table.sort(result.defines, function (a, b)
+        if a.file ~= b.file then
+            return a.file < b.file
+        end
+        return a.start < b.start
+    end)
+    results[#results+1] = result
+end
+
 ---@async
 ---@param outputPath string
 function export.makeDoc(outputPath)
@@ -222,15 +265,23 @@ function export.makeDoc(outputPath)
     await.sleep(0.1)
 
     local prog <close> = progress.create(ws.rootUri, '正在生成文档...', 0)
-    local globals = vm.getGlobals 'type'
+    local globalTypes = vm.getGlobals 'type'
+    local globalVars  = vm.getGlobals 'variable'
 
-    local max  = #globals
-    for i, global in ipairs(globals) do
-        collect(global, results)
+    local max = #globalTypes + #globalVars
+
+    for i, global in ipairs(globalTypes) do
+        collectTypes(global, results)
         prog:setMessage(('%d/%d'):format(i, max))
         prog:setPercentage(i / max * 100)
     end
 
+    for i, global in ipairs(globalVars) do
+        collectVars(global, results)
+        prog:setMessage(('%d/%d'):format(i + #globalTypes, max))
+        prog:setPercentage((i + #globalTypes) / max * 100)
+    end
+
     table.sort(results, function (a, b)
         return a.name < b.name
     end)
@@ -284,7 +335,7 @@ function export.runCLI()
 
         local max  = #globals
         for i, global in ipairs(globals) do
-            collect(global, results)
+            collectTypes(global, results)
             if os.clock() - lastClock > 0.2 then
                 lastClock = os.clock()
                 local output = '\x0D'
@@ -310,8 +361,8 @@ function export.runCLI()
     local mdPath = doc2md.buildMD(LOGPATH)
 
     print(lang.script('CLI_DOC_DONE'
-        , ('[%s](%s)'):format(ws.normalize(docPath), furi.encode(docPath))
-        , ('[%s](%s)'):format(ws.normalize(mdPath),  furi.encode(mdPath))
+        , ('[%s](%s)'):format(files.normalize(docPath), furi.encode(docPath))
+        , ('[%s](%s)'):format(files.normalize(mdPath),  furi.encode(mdPath))
     ))
 end
 
diff --git a/script/cli/doc2md.lua b/script/cli/doc2md.lua
index 9cc6c38589ba15144e1aaec9743748a94ae0287d..70c1b2a0898137078e0c12f50c96cf57225c59db 100644
--- a/script/cli/doc2md.lua
+++ b/script/cli/doc2md.lua
@@ -18,16 +18,26 @@ function export.buildMD(outputPath)
         md:emptyLine()
         md:add('md', class.desc)
         md:emptyLine()
-        local mark = {}
-        for _, field in ipairs(class.fields) do
-            if not mark[field.name] then
-                mark[field.name] = true
-                md:add('md', '## ' .. field.name)
-                md:emptyLine()
-                md:add('lua', field.extends.view)
-                md:emptyLine()
-                md:add('md', field.desc)
-                md:emptyLine()
+        if class.defines then
+            for _, define in ipairs(class.defines) do
+                if define.extends then
+                    md:add('lua', define.extends.view)
+                    md:emptyLine()
+                end
+            end
+        end
+        if class.fields then
+            local mark = {}
+            for _, field in ipairs(class.fields) do
+                if not mark[field.name] then
+                    mark[field.name] = true
+                    md:add('md', '## ' .. field.name)
+                    md:emptyLine()
+                    md:add('lua', field.extends.view)
+                    md:emptyLine()
+                    md:add('md', field.desc)
+                    md:emptyLine()
+                end
             end
         end
         md:splitLine()
diff --git a/script/client.lua b/script/client.lua
index f39202a698f4b4f774d7985a48402de80f1bb2c3..a8eda9b8705eede7fc6edc68b584719b5d70fca7 100644
--- a/script/client.lua
+++ b/script/client.lua
@@ -59,7 +59,7 @@ function m.getAbility(name)
         end
         current = current[parent]
         if not current then
-            return nil
+            return current
         end
         if nextPos > #name then
             break
diff --git a/script/config/template.lua b/script/config/template.lua
index 7a4d3f1b6a6b277e220552f9866e62786af43d78..3d2a8d35d2400203399883db6876d52862081700 100644
--- a/script/config/template.lua
+++ b/script/config/template.lua
@@ -395,6 +395,7 @@ local template = {
     ['Lua.doc.packageName']                 = Type.Array(Type.String),
 
     -- VSCode
+    ["Lua.addonManager.enable"]             = Type.Boolean >> true,
     ['files.associations']                  = Type.Hash(Type.String, Type.String),
                                             -- copy from VSCode default
     ['files.exclude']                       = Type.Hash(Type.String, Type.Boolean) >> {
diff --git a/script/core/command/exportDocument.lua b/script/core/command/exportDocument.lua
index c35448290753040ecc0e3ef9515ad1ce107da334..39832856b25ef82a909ad59086413f4eaa126f3b 100644
--- a/script/core/command/exportDocument.lua
+++ b/script/core/command/exportDocument.lua
@@ -3,13 +3,14 @@ local client = require 'client'
 local furi   = require 'file-uri'
 local lang   = require 'language'
 local ws     = require 'workspace'
+local files  = require 'files'
 
 ---@async
 return function (args)
     local outputPath = args[1] and furi.decode(args[1]) or LOGPATH
     local docPath, mdPath = doc.makeDoc(outputPath)
     client.showMessage('Info', lang.script('CLI_DOC_DONE'
-        , ('[%s](%s)'):format(ws.normalize(docPath), furi.encode(docPath))
-        , ('[%s](%s)'):format(ws.normalize(mdPath),  furi.encode(mdPath))
+        , ('[%s](%s)'):format(files.normalize(docPath), furi.encode(docPath))
+        , ('[%s](%s)'):format(files.normalize(mdPath),  furi.encode(mdPath))
     ))
 end
diff --git a/script/core/completion/auto-require.lua b/script/core/completion/auto-require.lua
index cec7139c01aa995be400f170137db2dcf643977b..3139b9116bc87dbdb5c1da69ba161484c2c7a5d7 100644
--- a/script/core/completion/auto-require.lua
+++ b/script/core/completion/auto-require.lua
@@ -56,9 +56,12 @@ function m.check(state, word, position, callback)
                     : gsub("(%p)", "%%%1")
                     : gsub("%%%?", "(.-)")
 
-                stemName = relativePath
-                    : match(pattern)
-                    : match("[%a_][%w_]*$")
+                local stemPath = relativePath:match(pattern)
+                if not stemPath then
+                    goto INNER_CONTINUE
+                end
+
+                stemName = stemPath:match("[%a_][%w_]*$")
 
                 if not stemName or testedStem[stemName] then
                     goto INNER_CONTINUE
diff --git a/script/core/completion/keyword.lua b/script/core/completion/keyword.lua
index 5558106a80d848927b3ba67e8e9d64da63c550be..e6f502421e6f03d3975c5e63f9de0b892f8d24e4 100644
--- a/script/core/completion/keyword.lua
+++ b/script/core/completion/keyword.lua
@@ -1,6 +1,8 @@
 local define     = require 'proto.define'
 local files      = require 'files'
 local guide      = require 'parser.guide'
+local config     = require 'config'
+local util       = require 'utility'
 
 local keyWordMap = {
     { 'do', function(info, results)
@@ -324,6 +326,66 @@ end"
         end
         return true
     end },
+    { 'continue', function (info, results)
+        local nonstandardSymbol = config.get(info.uri, 'Lua.runtime.nonstandardSymbol')
+        if util.arrayHas(nonstandardSymbol, 'continue') then
+            return
+        end
+        local version = config.get(info.uri, 'Lua.runtime.version')
+        if version == 'Lua 5.1' then
+            return
+        end
+        local mostInsideBlock
+        guide.eachSourceContain(info.state.ast, info.start, function (src)
+            if src.type == 'while'
+            or src.type == 'in'
+            or src.type == 'loop'
+            or src.type == 'repeat' then
+                mostInsideBlock = src
+            end
+        end)
+        if not mostInsideBlock then
+            return
+        end
+        -- 找一下 end 的位置
+        local endPos
+        if mostInsideBlock.type == 'while' then
+            endPos = mostInsideBlock.keyword[5]
+        elseif mostInsideBlock.type == 'in' then
+            endPos = mostInsideBlock.keyword[7]
+        elseif mostInsideBlock.type == 'loop' then
+            endPos = mostInsideBlock.keyword[5]
+        elseif mostInsideBlock.type == 'repeat' then
+            endPos = mostInsideBlock.keyword[3]
+        end
+        if not endPos then
+            return
+        end
+        local endLine     = guide.rowColOf(endPos)
+        local tabStr = info.state.lua:sub(
+            info.state.lines[endLine],
+            guide.positionToOffset(info.state, endPos)
+        )
+        local newText
+        if tabStr:match '^[\t ]*$' then
+            newText = '    ::continue::\n' .. tabStr
+        else
+            newText = '::continue::'
+        end
+        results[#results+1] = {
+            label      = 'goto continue ..',
+            kind       = define.CompletionItemKind.Snippet,
+            insertText = "goto continue",
+            additionalTextEdits = {
+                {
+                    start   = endPos,
+                    finish  = endPos,
+                    newText = newText,
+                }
+            }
+        }
+        return true
+    end }
 }
 
 return keyWordMap
diff --git a/script/core/completion/postfix.lua b/script/core/completion/postfix.lua
index c5988ef605f00eb0066abdab09e81699f4fec4ca..1331a0e4c83e6428cefa603e88dd0d68151f31d7 100644
--- a/script/core/completion/postfix.lua
+++ b/script/core/completion/postfix.lua
@@ -133,6 +133,39 @@ register 'xpcall' {
     end
 }
 
+register 'ifcall' {
+    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' then
+            return
+        end
+        local subber = subString(state)
+        if source.type == 'call' then
+            if source.args and #source.args > 0 then
+                callback(string.format('if %s then %s(%s) end$0'
+                    , subber(source.node.start + 1, source.node.finish)
+                    , subber(source.node.start + 1, source.node.finish)
+                    , subber(source.args[1].start + 1, source.args[#source.args].finish)
+                ))
+            else
+                callback(string.format('if %s then %s() end$0'
+                    , subber(source.node.start + 1, source.node.finish)
+                    , subber(source.node.start + 1, source.node.finish)
+                ))
+            end
+        else
+            callback(string.format('if %s then %s($1) end$0'
+                , subber(source.node.start + 1, source.node.finish)
+                , subber(source.node.start + 1, source.node.finish)
+            ))
+        end
+    end
+}
+
 register 'local' {
     function (state, source, callback)
         if  source.type ~= 'getglobal'
diff --git a/script/core/diagnostics/global-in-nil-env.lua b/script/core/diagnostics/global-in-nil-env.lua
index e154080c78149174fe005d1535819dbce4efd717..daf1f99d56d6a0e36f40c468a1404472df4af71e 100644
--- a/script/core/diagnostics/global-in-nil-env.lua
+++ b/script/core/diagnostics/global-in-nil-env.lua
@@ -13,6 +13,9 @@ return function (uri, callback)
         if node.tag == '_ENV' then
             return
         end
+        if guide.isParam(node) then
+            return
+        end
 
         if not node.value or node.value.type == 'nil' then
             callback {
diff --git a/script/core/diagnostics/param-type-mismatch.lua b/script/core/diagnostics/param-type-mismatch.lua
index 607d4b0ecec17eb2b5183e4cb8f416cafb651644..da39c5e1d744f9cd6dd2ac7b5550f152d80924c7 100644
--- a/script/core/diagnostics/param-type-mismatch.lua
+++ b/script/core/diagnostics/param-type-mismatch.lua
@@ -87,9 +87,6 @@ return function (uri, callback)
         await.delay()
         local funcNode = vm.compileNode(source.node)
         for i, arg in ipairs(source.args) do
-            if i == 1 and source.node.type == 'getmethod' then
-                goto CONTINUE
-            end
             local refNode = vm.compileNode(arg)
             if not refNode then
                 goto CONTINUE
@@ -99,7 +96,8 @@ return function (uri, callback)
                 goto CONTINUE
             end
             if arg.type == 'getfield'
-            or arg.type == 'getindex' then
+            or arg.type == 'getindex'
+            or arg.type == 'self' then
                 -- 由于无法对字段进行类型收窄,
                 -- 因此将假值移除再进行检查
                 refNode = refNode:copy():setTruthy()
diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua
index 60a9428120d2b03a6bf9fec9467e6e03756a1d6d..3d322a3ea3c9cae1a84289369a851dce242afc8b 100644
--- a/script/core/semantic-tokens.lua
+++ b/script/core/semantic-tokens.lua
@@ -7,6 +7,7 @@ local guide          = require 'parser.guide'
 local converter      = require 'proto.converter'
 local config         = require 'config'
 local linkedTable    = require 'linked-table'
+local client         = require 'client'
 
 local Care = util.switch()
     : case 'getglobal'
@@ -794,7 +795,8 @@ local function solveMultilineAndOverlapping(state, results)
     for token in tokens:pairs() do
         local startPos = converter.packPosition(state, token.start)
         local endPos   = converter.packPosition(state, token.finish)
-        if endPos.line == startPos.line then
+        if endPos.line == startPos.line
+        or client.getAbility 'textDocument.semanticTokens.multilineTokenSupport' then
             new[#new+1] = {
                 start      = startPos,
                 finish     = endPos,
diff --git a/script/files.lua b/script/files.lua
index ece7f3a98ed12a63f22b37d7d117cda940c15b44..62f011361ba1852874a096af8d0a9fd8ee77055a 100644
--- a/script/files.lua
+++ b/script/files.lua
@@ -81,6 +81,9 @@ function m.getRealUri(uri)
     if platform.OS ~= 'Windows' then
         return furi.normalize(uri)
     end
+    if not furi.isValid(uri) then
+        return uri
+    end
     local filename = furi.decode(uri)
     -- normalize uri
     uri = furi.encode(filename)
@@ -887,6 +890,41 @@ function m.countStates()
     return n
 end
 
+---@param path string
+---@return string
+function m.normalize(path)
+    path = path:gsub('%$%{(.-)%}', function (key)
+        if key == '3rd' then
+            return (ROOT / 'meta' / '3rd'):string()
+        end
+        if key:sub(1, 4) == 'env:' then
+            local env = os.getenv(key:sub(5))
+            return env
+        end
+    end)
+    path = util.expandPath(path)
+    path = path:gsub('^%.[/\\]+', '')
+    for _ = 1, 1000 do
+        if path:sub(1, 2) == '..' then
+            break
+        end
+        local count
+        path, count = path:gsub('[^/\\]+[/\\]+%.%.[/\\]', '/', 1)
+        if count == 0 then
+            break
+        end
+    end
+    if platform.OS == 'Windows' then
+        path = path:gsub('[/\\]+', '\\')
+                   :gsub('[/\\]+$', '')
+                   :gsub('^(%a:)$', '%1\\')
+    else
+        path = path:gsub('[/\\]+', '/')
+                   :gsub('[/\\]+$', '')
+    end
+    return path
+end
+
 --- 注册事件
 ---@param callback async fun(ev: string, uri: uri)
 function m.watch(callback)
diff --git a/script/filewatch.lua b/script/filewatch.lua
index addf3496d515f1f43dfd571fc5a3219d8105693b..a8fa925f784514ef0fe2d36488a23ac6a4b3647e 100644
--- a/script/filewatch.lua
+++ b/script/filewatch.lua
@@ -2,6 +2,7 @@ local fw    = require 'bee.filewatch'
 local fs    = require 'bee.filesystem'
 local plat  = require 'bee.platform'
 local await = require 'await'
+local files = require 'files'
 
 local MODIFY = 1 << 0
 local RENAME = 1 << 1
@@ -93,6 +94,7 @@ function m.update()
             if not ev then
                 break
             end
+            path = files.normalize(path)
             log.debug('filewatch:', ev, path)
             if not collect then
                 collect = {}
diff --git a/script/parser/compile.lua b/script/parser/compile.lua
index 0ffe1fef7dcfc63fc3eb589432cb0b8dc8779a6d..470ae299966471c4a772826afc4b880fa7383d73 100644
--- a/script/parser/compile.lua
+++ b/script/parser/compile.lua
@@ -1989,6 +1989,12 @@ local function parseSimple(node, funcName)
     and node.node == lastMethod then
         lastMethod = nil
     end
+    if node.type == 'call' then
+        if node.node.special == 'error'
+        or node.node.special == 'os.exit' then
+            node.hasExit = true
+        end
+    end
     if node == lastMethod then
         if funcName then
             lastMethod = nil
@@ -2920,8 +2926,7 @@ local function compileExpAsAction(exp)
     end
 
     if exp.type == 'call' then
-        if exp.node.special == 'error'
-        or exp.node.special == 'os.exit' then
+        if exp.hasExit then
             for i = #Chunk, 1, -1 do
                 local block = Chunk[i]
                 if block.type == 'ifblock'
diff --git a/script/parser/guide.lua b/script/parser/guide.lua
index ec5746c13bccc8195bd86dc750cee758f7cd5da7..e7eb3751cb225544f568647970d884d930633cef 100644
--- a/script/parser/guide.lua
+++ b/script/parser/guide.lua
@@ -1276,4 +1276,17 @@ function m.getTopBlock(source)
     return nil
 end
 
+---@param source parser.object
+---@return boolean
+function m.isParam(source)
+    if  source.type ~= 'local'
+    and source.type ~= 'self' then
+        return false
+    end
+    if source.parent.type ~= 'funcargs' then
+        return false
+    end
+    return true
+end
+
 return m
diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua
index 97d947c910eb32a00241868d67364d0b98e4ebe4..8487ec56068077edd104c129a01495ca2152ea38 100644
--- a/script/vm/compiler.lua
+++ b/script/vm/compiler.lua
@@ -1357,6 +1357,7 @@ local compilerSwitch = util.switch()
             if src.type == 'doc.field'
             or src.type == 'doc.type.field'
             or src.type == 'doc.type.name'
+            or src.type == 'doc.type'
             or guide.isLiteral(src) then
                 hasMarkDoc = true
                 vm.setNode(source, vm.compileNode(src))
@@ -1908,10 +1909,9 @@ function vm.compileNode(source)
 
     ---@cast source parser.object
     vm.setNode(source, vm.createNode(), true)
-    if not vm.compileByGlobal(source) then
-        vm.compileByVariable(source)
-        compileByNode(source)
-    end
+    vm.compileByGlobal(source)
+    vm.compileByVariable(source)
+    compileByNode(source)
     compileByParentNode(source)
     matchCall(source)
 
diff --git a/script/vm/infer.lua b/script/vm/infer.lua
index 3045bbdbf2b101a0e95c6facf9f85a1e091519c7..94fdfd88799ca3927b850af9beb15266613e3fbd 100644
--- a/script/vm/infer.lua
+++ b/script/vm/infer.lua
@@ -243,6 +243,16 @@ local viewNodeSwitch;viewNodeSwitch = util.switch()
 ---@class vm.node
 ---@field lastInfer? vm.infer
 
+---@param node? vm.node
+---@return vm.infer
+local function createInfer(node)
+    local infer = setmetatable({
+        node  = node,
+        _drop = {},
+    }, mt)
+    return infer
+end
+
 ---@param source vm.node.object | vm.node
 ---@return vm.infer
 function vm.getInfer(source)
@@ -258,10 +268,7 @@ function vm.getInfer(source)
     if node.lastInfer then
         return node.lastInfer
     end
-    local infer = setmetatable({
-        node  = node,
-        _drop = {},
-    }, mt)
+    local infer = createInfer(node)
     node.lastInfer = infer
 
     return infer
@@ -311,9 +318,7 @@ function mt:_eraseAlias(uri)
                 if set.type == 'doc.alias' then
                     if expandAlias then
                         self._drop[n.name] = true
-                        local newInfer = setmetatable({
-                            _drop = {},
-                        }, mt)
+                        local newInfer = createInfer()
                         for _, ext in ipairs(set.extends.types) do
                             viewNodeSwitch(ext.type, ext, newInfer, uri)
                         end
@@ -322,7 +327,7 @@ function mt:_eraseAlias(uri)
                         end
                     else
                         for _, ext in ipairs(set.extends.types) do
-                            local view = viewNodeSwitch(ext.type, ext, {}, uri)
+                            local view = viewNodeSwitch(ext.type, ext, createInfer(), uri)
                             if view and view ~= n.name then
                                 self._drop[view] = true
                             end
@@ -546,9 +551,7 @@ end
 ---@param uri uri
 ---@return string?
 function vm.viewObject(source, uri)
-    local infer = setmetatable({
-        _drop = {},
-    }, mt)
+    local infer = createInfer()
     return viewNodeSwitch(source.type, source, infer, uri)
 end
 
diff --git a/script/vm/operator.lua b/script/vm/operator.lua
index cb27d33d2074463bbfb71c9468ec51810f0ae6fd..b2a3aa1095eb5c8b8010f8daa4abb5db5760475e 100644
--- a/script/vm/operator.lua
+++ b/script/vm/operator.lua
@@ -199,7 +199,10 @@ vm.binarySwitch = util.switch()
         elseif r1 == false then
             vm.setNode(source, node2)
         else
-            local node = node1:copy():setTruthy():merge(node2)
+            local node = node1:copy():setTruthy()
+            if not source[2].hasExit then
+                node:merge(node2)
+            end
             vm.setNode(source, node)
         end
     end)
diff --git a/script/vm/ref.lua b/script/vm/ref.lua
index 9c24868412ec4926a160670833a964c57313e3f0..89b9d3c0c77198520011a947d2bb54a73bc28486 100644
--- a/script/vm/ref.lua
+++ b/script/vm/ref.lua
@@ -107,29 +107,28 @@ local function searchWord(source, pushResult, defMap, fileNotify)
                     await.delay()
                 end
             end)
-        else
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getfield', 'setfield'}, function (src)
-                if src.field and src.field[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getmethod', 'setmethod'}, function (src)
-                if src.method and src.method[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
-            ---@async
-            guide.eachSourceTypes(state.ast, {'getindex', 'setindex'}, function (src)
-                if src.index and src.index.type == 'string' and src.index[1] == key then
-                    checkDef(src)
-                    await.delay()
-                end
-            end)
         end
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getfield', 'setfield'}, function (src)
+            if src.field and src.field[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getmethod', 'setmethod'}, function (src)
+            if src.method and src.method[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
+        ---@async
+        guide.eachSourceTypes(state.ast, {'getindex', 'setindex'}, function (src)
+            if src.index and src.index.type == 'string' and src.index[1] == key then
+                checkDef(src)
+                await.delay()
+            end
+        end)
     end
 
     searchInAllFiles(guide.getUri(source), findWord, fileNotify)
diff --git a/script/vm/tracer.lua b/script/vm/tracer.lua
index b526d0876da7a492d51fa941c0abe751065135ba..a8aa0c7c27c5a53b6a9ff70729c36ec486f494f7 100644
--- a/script/vm/tracer.lua
+++ b/script/vm/tracer.lua
@@ -641,7 +641,8 @@ local lookIntoChild = util.switch()
             and    handler.args[1]
             and    tracer.getMap[handler.args[1]] then
                 -- if type(x) == 'string' then
-                tracer:lookIntoChild(handler, topNode:copy())
+                tracer:lookIntoChild(handler, topNode)
+                topNode = topNode:copy()
                 if action.op.type == '==' then
                     topNode:narrow(tracer.uri, checker[1])
                     if outNode then
diff --git a/script/workspace/require-path.lua b/script/workspace/require-path.lua
index e51d121990c71c04bb384f36b62d8fc7f0881f36..c319cbad6ff4d126283ec0839ade1007a76e3be2 100644
--- a/script/workspace/require-path.lua
+++ b/script/workspace/require-path.lua
@@ -81,7 +81,7 @@ function mt:getRequireResultByPath(path)
     for _, searcher in ipairs(searchers) do
         local isAbsolute = searcher:match '^[/\\]'
                         or searcher:match '^%a+%:'
-        searcher = workspace.normalize(searcher)
+        searcher = files.normalize(searcher)
         if searcher:sub(1, 1) == '.' then
             strict = true
         end
@@ -158,7 +158,7 @@ function mt:getVisiblePath(path)
     and not self.scp:isLinkedUri(uri) then
         return {}
     end
-    path = workspace.normalize(path)
+    path = files.normalize(path)
     local result = self.visibleCache[path]
     if not result then
         result = self:getRequireResultByPath(path)
@@ -196,7 +196,7 @@ function mt:searchUrisByRequireName(name)
 
     for _, searcher in ipairs(searchers) do
         local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
-        fspath = workspace.normalize(fspath)
+        fspath = files.normalize(fspath)
         local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '')
         for uri in files.eachFile(self.scp.uri) do
             if  not searcherMap[uri]
@@ -212,7 +212,7 @@ function mt:searchUrisByRequireName(name)
                 or relative == '/'
                 or relative == '' then
                     results[#results+1] = uri
-                    searcherMap[uri] = workspace.normalize(relative .. searcher)
+                    searcherMap[uri] = files.normalize(relative .. searcher)
                 end
             end
         end
@@ -294,7 +294,7 @@ function m.isMatchedUri(suri, uri, name)
 
     for _, searcher in ipairs(searchers) do
         local fspath = searcher:gsub('%?', (path:gsub('%%', '%%%%')))
-        fspath = workspace.normalize(fspath)
+        fspath = files.normalize(fspath)
         local tail = '/' .. furi.encode(fspath):gsub('^file:[/]*', '')
         if util.stringEndWith(uri, tail) then
             local parentUri = files.getLibraryUri(suri, uri) or uri
diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua
index 9d701167cfe6cf3464eee642c75e4392553844c3..3e85e0fc9344516fdb7bb78ebbcb02c190a89e90 100644
--- a/script/workspace/workspace.lua
+++ b/script/workspace/workspace.lua
@@ -217,14 +217,14 @@ function m.getLibraryMatchers(scp)
     for _, path in ipairs(config.get(scp.uri, 'Lua.workspace.library')) do
         path = m.getAbsolutePath(scp.uri, path)
         if path then
-            librarys[m.normalize(path)] = true
+            librarys[files.normalize(path)] = true
         end
     end
     local metaPaths = scp:get 'metaPaths'
     log.debug('meta path:', inspect(metaPaths))
     if metaPaths then
         for _, metaPath in ipairs(metaPaths) do
-            librarys[m.normalize(metaPath)] = true
+            librarys[files.normalize(metaPath)] = true
         end
     end
 
@@ -326,7 +326,7 @@ function m.awaitPreload(scp)
 
     if scp.uri and not scp:get('bad root') then
         log.info('Scan files at:', scp:getName())
-        scp:gc(fw.watch(m.normalize(furi.decode(scp.uri)), true, function (path)
+        scp:gc(fw.watch(files.normalize(furi.decode(scp.uri)), true, function (path)
             local rpath = m.getRelativePath(path)
             if native(rpath) then
                 return false
@@ -401,52 +401,18 @@ function m.findUrisByFilePath(path)
     return results
 end
 
----@param path string
----@return string
-function m.normalize(path)
-    path = path:gsub('%$%{(.-)%}', function (key)
-        if key == '3rd' then
-            return (ROOT / 'meta' / '3rd'):string()
-        end
-        if key:sub(1, 4) == 'env:' then
-            local env = os.getenv(key:sub(5))
-            return env
-        end
-    end)
-    path = util.expandPath(path)
-    path = path:gsub('^%.[/\\]+', '')
-    for _ = 1, 1000 do
-        if path:sub(1, 2) == '..' then
-            break
-        end
-        local count
-        path, count = path:gsub('[^/\\]+[/\\]+%.%.[/\\]', '/', 1)
-        if count == 0 then
-            break
-        end
-    end
-    if platform.OS == 'Windows' then
-        path = path:gsub('[/\\]+', '\\')
-                   :gsub('[/\\]+$', '')
-                   :gsub('^(%a:)$', '%1\\')
-    else
-        path = path:gsub('[/\\]+', '/')
-                   :gsub('[/\\]+$', '')
-    end
-    return path
-end
 
 ---@param folderUri? uri
 ---@param path string
 ---@return string?
 function m.getAbsolutePath(folderUri, path)
-    path = m.normalize(path)
+    path = files.normalize(path)
     if fs.path(path):is_relative() then
         if not folderUri then
             return nil
         end
         local folderPath = furi.decode(folderUri)
-        path = m.normalize(folderPath .. '/' .. path)
+        path = files.normalize(folderPath .. '/' .. path)
     end
     return path
 end
@@ -465,14 +431,14 @@ function m.getRelativePath(uriOrPath)
     end
     local scp = scope.getScope(uri)
     if not scp.uri then
-        local relative = m.normalize(path)
+        local relative = files.normalize(path)
         return relative:gsub('^[/\\]+', ''), false
     end
-    local _, pos = m.normalize(path):find(furi.decode(scp.uri), 1, true)
+    local _, pos = files.normalize(path):find(furi.decode(scp.uri), 1, true)
     if pos then
-        return m.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true
+        return files.normalize(path:sub(pos + 1)):gsub('^[/\\]+', ''), true
     else
-        return m.normalize(path):gsub('^[/\\]+', ''), false
+        return files.normalize(path):gsub('^[/\\]+', ''), false
     end
 end
 
diff --git a/test/crossfile/completion.lua b/test/crossfile/completion.lua
index d613f6213b501b0e57acf162299558ba30348fe9..113b032725151d44dd17cc38ff0081def86495e0 100644
--- a/test/crossfile/completion.lua
+++ b/test/crossfile/completion.lua
@@ -124,6 +124,37 @@ local function WITH_CONFIG(cfg, f)
     end
 end
 
+TEST {
+    {
+        path = 'abc.lua',
+        content = [[
+            ---@meta
+
+            ---@class A
+            ---@field f1 integer
+            ---@field f2 boolean
+            
+            ---@type A[]
+            X = {}
+]],
+    },
+    {
+        path = 'test.lua',
+        content = [[ X[1].<??>]],
+        main = true,
+    },
+    completion = {
+        {
+            label = 'f1',
+            kind = CompletionItemKind.Field,
+        },
+        {
+            label = 'f2',
+            kind = CompletionItemKind.Field,
+        },
+    }
+}
+
 TEST {
     {
         path = 'abc.lua',
diff --git a/test/diagnostics/common.lua b/test/diagnostics/common.lua
index 89537771f074763535f0131cfae00735735efc5d..a1dbe819d34e0186c153fc94492bc64c168eeae0 100644
--- a/test/diagnostics/common.lua
+++ b/test/diagnostics/common.lua
@@ -2262,3 +2262,9 @@ else
     function X.f() end
 end
 ]]
+
+TESTWITH 'global-in-nil-env' [[
+local function foo(_ENV)
+    Joe = "human"
+end
+]]
diff --git a/test/diagnostics/init.lua b/test/diagnostics/init.lua
index 3c19e58d2202c7985e480b8212ef72d0bb36e2a2..827d4d08ec1a0d9ab1adcf136a99e85d6f887cb6 100644
--- a/test/diagnostics/init.lua
+++ b/test/diagnostics/init.lua
@@ -27,7 +27,7 @@ local function founded(targets, results)
 end
 
 ---@diagnostic disable: await-in-sync
-function TEST(script, ...)
+function TEST(script)
     local newScript, catched = catch(script, '!')
     files.setText(TESTURI, newScript)
     files.open(TESTURI)
@@ -53,5 +53,36 @@ function TEST(script, ...)
     end
 end
 
+function TESTWITH(code)
+    return function (script)
+        local newScript, catched = catch(script, '!')
+        files.setText(TESTURI, newScript)
+        files.open(TESTURI)
+        local origins = {}
+        local results = {}
+        core(TESTURI, false, function (result)
+            if code ~= result.code then
+                return
+            end
+            results[#results+1] = { result.start, result.finish }
+            origins[#origins+1] = result
+        end)
+
+        if results[1] then
+            if not founded(catched['!'] or {}, results) then
+                error(('%s\n%s'):format(util.dump(catched['!']), util.dump(results)))
+            end
+        else
+            assert(#catched['!'] == 0)
+        end
+
+        files.remove(TESTURI)
+
+        return function (callback)
+            callback(origins)
+        end
+    end
+end
+
 require 'diagnostics.common'
 require 'diagnostics.type-check'
diff --git a/test/diagnostics/type-check.lua b/test/diagnostics/type-check.lua
index 13df5a957993d6938c866263e00beab7db4b4e09..4abe585511c1c3977457362b37807cadb6c74d62 100644
--- a/test/diagnostics/type-check.lua
+++ b/test/diagnostics/type-check.lua
@@ -1201,6 +1201,43 @@ local function some_fn(some_param) return end
 some_fn { { "test" } } -- <- diagnostic: "Cannot assign `table` to `string`."
 ]]
 
+TEST [[
+---@param p integer|string
+local function get_val(p)
+    local is_number = type(p) == 'number'
+    return is_number and p or p
+end
+
+get_val('hi')
+]]
+
+TESTWITH 'param-type-mismatch' [[
+---@class Class
+local Class = {}
+
+---@param source string
+function Class.staticCreator(source)
+
+end
+
+Class.staticCreator(<!true!>)
+Class<!:!>staticCreator() -- Expecting a waring
+]]
+
+TESTWITH 'assign-type-mismatch' [[
+---@type string[]
+local arr = {
+    <!3!>,
+}
+]]
+
+TESTWITH 'assign-type-mismatch' [[
+---@type (string|boolean)[]
+local arr2 = {
+    <!3!>, -- no warnings
+}
+]]
+
 config.remove(nil, 'Lua.diagnostics.disable', 'unused-local')
 config.remove(nil, 'Lua.diagnostics.disable', 'unused-function')
 config.remove(nil, 'Lua.diagnostics.disable', 'undefined-global')
diff --git a/test/references/all.lua b/test/references/all.lua
index 9395df86170b953a6a052ced156ccecc6ed9dcc3..3c376c218dd2bd4774a6d8908e5c8414c06ad2af 100644
--- a/test/references/all.lua
+++ b/test/references/all.lua
@@ -126,3 +126,16 @@ end
 local v1 = Master:foobar("", Dog)
 v1.<!eat!>()
 ]]
+
+TEST [[
+---@class A
+A = {}
+
+function A:<~TestA~>()
+end
+
+---@param param A
+function TestB(param)
+    param:<!TestA!>()
+end
+]]
diff --git a/test/type_inference/init.lua b/test/type_inference/init.lua
index 87e6c4901f4342d511356f60bb08dce52018a0f7..597fb761810bdcbe27f0fb76da12f8035bdd5262 100644
--- a/test/type_inference/init.lua
+++ b/test/type_inference/init.lua
@@ -4233,3 +4233,9 @@ function A:func()
     self.y = self.y + 3
 end
 ]]
+
+TEST 'number' [[
+---@type number?
+local n
+local <?v?> = n or error('')
+]]
diff --git a/tools/configuration.lua b/tools/configuration.lua
index b152927f2f35a8160e63473a088e2392aeb5c993..10bf6e08a376329e168512aee82a5f2273e9bb20 100644
--- a/tools/configuration.lua
+++ b/tools/configuration.lua
@@ -32,8 +32,10 @@ local function getDefault(temp)
     if default == nil and temp.hasDefault then
         default = json.null
     end
-    if type(default) == 'table' and getType(temp) == 'object' then
-        setmetatable(default, json.object)
+    if  type(default) == 'table'
+    and not next(default)
+    and getType(temp) == 'object' then
+        default = json.createEmptyObject()
     end
     return default
 end