diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7b4de2ca7..30ed98e17 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -25,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    }
-          - { os: ubuntu-20.04,   target: linux,   platform: linux-arm64  }
+          - { os: ubuntu-22.04,   target: linux,   platform: linux-x64    }
+          - { os: ubuntu-22.04,   target: linux,   platform: linux-arm64  }
           - { os: macos-latest,   target: darwin,  platform: darwin-x64   }
           - { os: macos-latest,   target: darwin,  platform: darwin-arm64 }
           - { os: windows-latest, target: windows, platform: win32-ia32   }
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 12e614e5b..b0cdb6df7 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -6,7 +6,7 @@ jobs:
       fail-fast: false
       matrix:
         include:
-          - { os: ubuntu-20.04,   target: linux,   platform: linux-x64 }
+          - { os: ubuntu-22.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 }}
diff --git a/changelog.md b/changelog.md
index baa1210fc..3906c762a 100644
--- a/changelog.md
+++ b/changelog.md
@@ -3,6 +3,21 @@
 ## Unreleased
 <!-- Add all new changes here. They will be moved under a version at release -->
 
+## 3.13.7
+`2025-3-10`
+* `NEW` CLI: added `--help`.
+* `CHG` default path for `--doc_out_path` is the current directory
+* `FIX` incorrect argument skip pattern for `--check_out_path=`, which incorrectly skips the next argument
+* `FIX` incorrect error message for `--doc_update`.
+* `FIX` reimplement section `luals.config` in file doc.json
+* `FIX` incorrect file names in file doc.json
+* `FIX` remove extra `./` path prefix in the check report when using `--check=.`
+* `FIX` Narrowing of types with literal fields: [#3056](https://github.com/LuaLS/lua-language-server/issues/3056), [#3089](https://github.com/LuaLS/lua-language-server/issues/3089)
+* `FIX` correct lua version of `math.ult` and `math.type`
+* `FIX` incorrect links for `pattern` in `string` methods
+* `FIX` fix type annotations for bit module
+* `FIX` Another regression related to type narrow and generic param introduced since `v3.10.1` [#3087](https://github.com/LuaLS/lua-language-server/issues/3087)
+
 ## 3.13.6
 `2025-2-6`
 * `NEW` `---@class` supports attribute `partial`, which will not check missing inherited fields [#3023](https://github.com/LuaLS/lua-language-server/issues/3023)
@@ -22,7 +37,7 @@
 * `NEW` Added variable substitution support for vscode's `${workspaceFolder:x}` when resolving path placeholders [#2987](https://github.com/LuaLS/lua-language-server/issues/2987)
 * `NEW` Added `--check_format=json|pretty` for use with `--check` to output diagnostics in a human readable format.
 * `NEW` Test CLI: `--name=<testname>` `-n=<testname>`: run specify unit test
-* `CHG` [#3014] Generic pattern now supports definition after capture and optional, union, array
+* `CHG` Generic pattern now supports definition after capture and optional, union, array [#3014](https://github.com/LuaLS/lua-language-server/issues/3014) [#3031](https://github.com/LuaLS/lua-language-server/pull/3031)
   ```lua
   ---@generic T
   ---@param t `T`.Cat?
@@ -34,14 +49,14 @@
 * `FIX` Fixed the error that the configuration file pointed to by the `--configpath` option was not read and loaded.
 * `FIX` Don't truncate any output when running in `--doc` mode [#3049](https://github.com/LuaLS/lua-language-server/issues/3049)
 * `FIX` Generic return can be optional.
-* `FIX` Fixed the comment calculating in docs `---@param a string?Comment` - now its `Comment` instead of `omment`.
+* `FIX` Fixed the comment calculating in docs `---@param a string?Comment` - now its `Comment` instead of `omment` [#3028](https://github.com/LuaLS/lua-language-server/pull/3028)
 * `FIX` Fixed cannot bind variables using tail comment `@class` [#2673](https://github.com/LuaLS/lua-language-server/issues/2673)
 * `FIX` Fixed missing field completion for generic class object [#2196](https://github.com/LuaLS/lua-language-server/issues/2196) [#2945](https://github.com/LuaLS/lua-language-server/issues/2945) [#3041](https://github.com/LuaLS/lua-language-server/issues/3041)
 
 ## 3.13.5
 `2024-12-20`
-* `NEW` Setting: `Lua.hint.awaitPropagate`: When enabled, --@async propagates to the caller.
-* `CHG` Add server version information to `initialize` response #2996
+* `NEW` Setting: `Lua.hint.awaitPropagate`: When enabled, `--@async` propagates to the caller.
+* `CHG` Add server version information to `initialize` response [#2996](https://github.com/LuaLS/lua-language-server/pull/2996)
 * `CHG` If the `---@field` of the same name has a type of `fun`, the `duplicate-doc-field` check will not be performed.
 * `FIX` Incorrect infer for function array annotation on tables [#2367](https://github.com/LuaLS/lua-language-server/issues/2367)
 
diff --git a/locale/en-us/meta.lua b/locale/en-us/meta.lua
index e1ca55773..118384ccb 100644
--- a/locale/en-us/meta.lua
+++ b/locale/en-us/meta.lua
@@ -647,11 +647,13 @@ string.char                 =
 'Returns a string with length equal to the number of arguments, in which each character has the internal numeric code equal to its corresponding argument.'
 string.dump                 =
 'Returns a string containing a binary representation (a *binary chunk*) of the given function.'
-string.find                 =
+string.find['>5.2']         =
 'Looks for the first match of `pattern` (see §6.4.1) in the string.'
+string.find['=5.1']         =
+'Looks for the first match of `pattern` (see §5.4.1) in the string.'
 string.format               =
 'Returns a formatted version of its variable number of arguments following the description given in its first argument.'
-string.gmatch               =
+string.gmatch['>5.2']       =
 [[
 Returns an iterator function that, each time it is called, returns the next captures from `pattern` (see §6.4.1) over the string s.
 
@@ -664,17 +666,34 @@ As an example, the following loop will iterate over all the words from string s,
     end
 ```
 ]]
-string.gsub                 =
+string.gmatch['=5.1']       =
+[[
+Returns an iterator function that, each time it is called, returns the next captures from `pattern` (see §5.4.1) over the string s.
+
+As an example, the following loop will iterate over all the words from string s, printing one per line:
+```lua
+    s =
+"hello world from Lua"
+    for w in string.gmatch(s, "%a+") do
+        print(w)
+    end
+```
+]]
+string.gsub['>5.2']         =
 'Returns a copy of s in which all (or the first `n`, if given) occurrences of the `pattern` (see §6.4.1) have been replaced by a replacement string specified by `repl`.'
+string.gsub['=5.1']         =
+'Returns a copy of s in which all (or the first `n`, if given) occurrences of the `pattern` (see §5.4.1) have been replaced by a replacement string specified by `repl`.'
 string.len                  =
 'Returns its length.'
 string.lower                =
 'Returns a copy of this string with all uppercase letters changed to lowercase.'
-string.match                =
+string.match['>5.2']        =
 'Looks for the first match of `pattern` (see §6.4.1) in the string.'
-string.pack                 =
+string.match['=5.1']        =
+'Looks for the first match of `pattern` (see §5.4.1) in the string.'
+string.pack['>5.2']         =
 'Returns a binary string containing the values `v1`, `v2`, etc. packed (that is, serialized in binary form) according to the format string `fmt` (see §6.4.2) .'
-string.packsize             =
+string.packsize['>5.2']     =
 'Returns the size of a string resulting from `string.pack` with the given format string `fmt` (see §6.4.2) .'
 string.rep['>5.2']          =
 'Returns a string that is the concatenation of `n` copies of the string `s` separated by the string `sep`.'
diff --git a/meta/template/bit.lua b/meta/template/bit.lua
index e30d8c912..b13e25731 100644
--- a/meta/template/bit.lua
+++ b/meta/template/bit.lua
@@ -22,25 +22,22 @@ function bit.tohex(x, n) end
 function bit.bnot(x) end
 
 ---@param x   integer
----@param x2  integer
 ---@param ... integer
 ---@return integer y
 ---@nodiscard
-function bit.bor(x, x2, ...) end
+function bit.bor(x, ...) end
 
 ---@param x   integer
----@param x2  integer
 ---@param ... integer
 ---@return integer y
 ---@nodiscard
-function bit.band(x, x2, ...) end
+function bit.band(x, ...) end
 
 ---@param x   integer
----@param x2  integer
 ---@param ... integer
 ---@return integer y
 ---@nodiscard
-function bit.bxor(x, x2, ...) end
+function bit.bxor(x, ...) end
 
 ---@param x integer
 ---@param n integer
diff --git a/meta/template/math.lua b/meta/template/math.lua
index 37e1d5c74..707aa99c5 100644
--- a/meta/template/math.lua
+++ b/meta/template/math.lua
@@ -234,6 +234,7 @@ function math.tanh(x) end
 ---@nodiscard
 function math.tointeger(x) end
 
+---@version >5.3
 ---#DES 'math.type'
 ---@param x any
 ---@return
@@ -243,6 +244,7 @@ function math.tointeger(x) end
 ---@nodiscard
 function math.type(x) end
 
+---@version >5.3
 ---#DES 'math.ult'
 ---@param m integer
 ---@param n integer
diff --git a/meta/template/os.lua b/meta/template/os.lua
index 4dbffc6b2..996a5c479 100644
--- a/meta/template/os.lua
+++ b/meta/template/os.lua
@@ -9,7 +9,7 @@ os = {}
 ---@nodiscard
 function os.clock() end
 
----@class osdate
+---@class osdate:osdateparam
 ---#DES 'osdate.year'
 ---@field year  integer|string
 ---#DES 'osdate.month'
diff --git a/script/cli/check.lua b/script/cli/check.lua
index 7f38dfea4..d65c93580 100644
--- a/script/cli/check.lua
+++ b/script/cli/check.lua
@@ -21,7 +21,7 @@ local function buildArgs(exe, numThreads, threadId, format, quiet)
             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
+            if not arg:match('%-%-[%w_]*=') then
                 skipNext = true
             end
         else
diff --git a/script/cli/check_worker.lua b/script/cli/check_worker.lua
index 353926395..560739e30 100644
--- a/script/cli/check_worker.lua
+++ b/script/cli/check_worker.lua
@@ -187,7 +187,7 @@ function export.runCLI()
         return
     end
 
-    local rootPath = fs.absolute(fs.path(CHECK_WORKER)):string()
+    local rootPath = fs.canonical(fs.path(CHECK_WORKER)):string()
     local rootUri = furi.encode(rootPath)
     if not rootUri then
         print(lang.script('CLI_CHECK_ERROR_URI', rootPath))
diff --git a/script/cli/doc/export.lua b/script/cli/doc/export.lua
index 8d9b7d6f6..3e067576d 100644
--- a/script/cli/doc/export.lua
+++ b/script/cli/doc/export.lua
@@ -8,6 +8,8 @@ local getLabel = require 'core.hover.label'
 local jsonb    = require 'json-beautify'
 local util     = require 'utility'
 local markdown = require 'provider.markdown'
+local fs       = require 'bee.filesystem'
+local furi     = require 'file-uri'
 
 ---@alias doctype
 ---| 'doc.alias'
@@ -55,13 +57,14 @@ local markdown = require 'provider.markdown'
 local export = {}
 
 function export.getLocalPath(uri)
-    --remove uri root (and prefix)
-    local local_file_uri = uri
-    local i, j = local_file_uri:find(DOC)
-    if not j then
-        return '[FOREIGN] '..uri
+    local file_canonical = fs.canonical(furi.decode(uri)):string()
+    local doc_canonical = fs.canonical(DOC):string()
+    local relativePath = fs.relative(file_canonical, doc_canonical):string()
+    if relativePath == "" or relativePath:sub(1, 2) == '..' then
+        -- not under project directory
+        return '[FOREIGN] ' .. file_canonical
     end
-    return local_file_uri:sub( j + 1 )
+    return relativePath
 end
 
 function export.positionOf(rowcol)
@@ -214,10 +217,6 @@ export.makeDocObject['local'] = function(source, obj, has_seen)
     obj.name = source[1]
 end
 
-export.makeDocObject['luals.config'] = function(source, obj, has_seen)
-
-end
-
 export.makeDocObject['self'] = export.makeDocObject['local']
 
 export.makeDocObject['setfield'] = export.makeDocObject['doc.class']
@@ -284,17 +283,25 @@ end
 ---@param callback fun(i, max)
 function export.makeDocs(globals, callback)
     local docs = {}
-
     for i, global in ipairs(globals) do
         table.insert(docs, export.documentObject(global))
         callback(i, #globals)
     end
-
+    docs[#docs+1] = export.getLualsConfig()
     table.sort(docs, export.sortDoc)
-
     return docs
 end
 
+function export.getLualsConfig()
+    return {
+        name = 'LuaLS',
+        type = 'luals.config',
+        DOC = fs.canonical(fs.path(DOC)):string(),
+        defines = {},
+        fields = {}
+    }
+end
+
 ---takes the table from `makeDocs`, serializes it, and exports it
 ---@async
 ---@param docs table
diff --git a/script/cli/doc/init.lua b/script/cli/doc/init.lua
index 3492b3d9c..144c9911c 100644
--- a/script/cli/doc/init.lua
+++ b/script/cli/doc/init.lua
@@ -16,7 +16,7 @@ local doc = {}
 local function findDocJson()
     local doc_json_path
     if type(DOC_UPDATE) == 'string' then
-        doc_json_path = fs.absolute(fs.path(DOC_UPDATE)) .. '/doc.json'
+        doc_json_path = fs.canonical(fs.path(DOC_UPDATE)) .. '/doc.json'
     else
         doc_json_path = fs.current_path() .. '/doc.json'
     end
@@ -46,7 +46,7 @@ local function getPathDocUpdate()
         local doc_json_dir = doc_json_path:string():gsub('/doc.json', '')
         return doc_json_dir, doc_path
     else
-        error(string.format('Error: Cannot update "%s".', doc_json_path .. '/doc.json'))
+        error(string.format('Error: Cannot update "%s".', doc_json_path))
     end
 end
 
@@ -185,7 +185,7 @@ function doc.runCLI()
         return
     end
 
-    local rootUri = furi.encode(fs.absolute(fs.path(DOC)):string())
+    local rootUri = furi.encode(fs.canonical(fs.path(DOC)):string())
     if not rootUri then
         print(lang.script('CLI_CHECK_ERROR_URI', DOC))
         return
@@ -242,6 +242,10 @@ function doc.runCLI()
         end)
         io.write('\x0D')
 
+        if not DOC_OUT_PATH then
+            DOC_OUT_PATH = fs.current_path():string()
+        end
+
         local ok, outPaths, err = dirty_export.serializeAndExport(docs, DOC_OUT_PATH)
         print(lang.script('CLI_DOC_DONE'))
         for i, path in ipairs(outPaths) do
diff --git a/script/cli/help.lua b/script/cli/help.lua
new file mode 100644
index 000000000..6aa218afc
--- /dev/null
+++ b/script/cli/help.lua
@@ -0,0 +1,161 @@
+local util = require 'utility'
+
+--- @class cli.arg
+--- @field type? string|string[]
+--- @field description string Description of the argument in markdown format.
+--- @field example? string
+--- @field default? any
+
+--- @type table<string, cli.arg>
+local args = {
+    ['--help'] = {
+        description = [[
+            Print this message.
+        ]],
+    },
+    ['--check'] = {
+        type = 'string',
+        description = [[
+            Perform a "diagnosis report" where the results of the diagnosis are written to the logpath.
+        ]],
+        example = [[--check=C:\Users\Me\path\to\workspace]]
+    },
+    ['--checklevel'] = {
+        type = 'string',
+        description = [[
+            To be used with --check. The minimum level of diagnostic that should be logged.
+            Items with lower priority than the one listed here will not be written to the file.
+            Options include, in order of priority:
+
+                - Error
+                - Warning
+                - Information
+                - Hint
+        ]],
+        default = 'Warning',
+        example = [[--checklevel=Information]]
+    },
+    ['--check_format'] = {
+        type = { 'json',  'pretty' },
+        description = [[
+            Output format for the check results.
+            - 'pretty': results are displayed to stdout in a human-readable format.
+            - 'json': results are written to a file in JSON format. See --check_out_path
+        ]],
+        default = 'pretty'
+    },
+    ['--version'] = {
+        type = 'boolean',
+        description = [[
+            Get the version of the Lua language server.
+            This will print it to the command line and immediately exit.
+        ]],
+    },
+    ['--doc'] = {
+        type = 'string',
+        description = [[
+            Generate documentation from a workspace.
+            The files will be output in your log path.
+        ]],
+        example = [[--doc=C:/Users/Me/Documents/myLuaProject/]]
+    },
+    ['--doc_out_path'] = {
+        type = 'string',
+        description = [[
+            The path to output generated documentation at.
+            See --doc for more info.
+        ]],
+        example = [[--doc_out_path=C:/Users/Me/Documents/myLuaProjectDocumentation]]
+    },
+    ['--logpath'] = {
+        type = 'string',
+        description = [[
+            Where the log should be written to.
+        ]],
+        default = './log',
+        example = [[--logpath=D:/luaServer/logs]]
+    },
+    ['--loglevel'] = {
+        type = 'string',
+        description = [[
+            The minimum level of logging that should appear in the logfile.
+            Can be used to log more detailed info for debugging and error reporting.
+
+            Options:
+
+                - error
+                - warn
+                - info
+                - debug
+                - trace
+        ]],
+        example = [[--loglevel=trace]]
+    },
+    ['--metapath'] = {
+        type = 'string',
+        description = [[
+            Where the standard Lua library definition files should be generated to.
+        ]],
+        default = './meta',
+        example = [[--metapath=D:/sumnekoLua/metaDefintions]]
+    },
+    ['--locale'] = {
+        type = 'string',
+        description = [[
+            The language to use. Defaults to en-us.
+            Options can be found in locale/ .
+        ]],
+        example = [[--locale=zh-cn]]
+    },
+    ['--configpath'] = {
+        type = 'string',
+        description = [[
+            The location of the configuration file that will be loaded.
+            Can be relative to the workspace.
+            When provided, config files from elsewhere (such as from VS Code) will no longer be loaded.
+        ]],
+        example = [[--configpath=sumnekoLuaConfig.lua]]
+    },
+    ['--force-accept-workspace'] = {
+        type = 'boolean',
+        description = [[
+            Allows the use of root/home directory as the workspace.
+        ]]
+    },
+    ['--socket'] = {
+        type = 'number',
+        description = [[
+            Will communicate to a client over the specified TCP port instead of through stdio.
+        ]],
+        example = [[--socket=5050]]
+    },
+    ['--develop'] = {
+        type = 'boolean',
+        description = [[
+            Enables development mode. This allows plugins to write to the logpath.
+        ]]
+    }
+}
+
+for nm, attrs in util.sortPairs(args) do
+    if attrs.type == 'boolean' then
+        print(nm)
+    else
+        print(nm .. "=<value>")
+    end
+    if attrs.description then
+        local normalized_description = attrs.description:gsub("^%s+", ""):gsub("\n%s+", "\n"):gsub("%s+$", "")
+        print("\n    " .. normalized_description:gsub('\n', '\n    '))
+    end
+    local attr_type = attrs.type
+    if type(attr_type) == "table" then
+        print("\n    Values: " .. table.concat(attr_type, ', '))
+    end
+    if attrs.default then
+        print("\n    Default: " .. tostring(attrs.default))
+    end
+    if attrs.example then
+        print("\n    Example: " .. attrs.example)
+    end
+    print()
+end
diff --git a/script/cli/init.lua b/script/cli/init.lua
index 37554123e..17e6f61e3 100644
--- a/script/cli/init.lua
+++ b/script/cli/init.lua
@@ -1,3 +1,8 @@
+if _G['HELP'] then
+    require 'cli.help'
+    os.exit(0, true)
+end
+
 if _G['VERSION'] then
     require 'cli.version'
     os.exit(0, true)
diff --git a/script/global.d.lua b/script/global.d.lua
index f4e4b336b..bf4c25c72 100644
--- a/script/global.d.lua
+++ b/script/global.d.lua
@@ -109,3 +109,5 @@ THREAD_ID = 1
 CHECK_WORKER = ''
 
 QUIET = false
+
+HELP = false
diff --git a/script/meta/bee/filesystem.lua b/script/meta/bee/filesystem.lua
index 0c7e41a80..5dd3702ee 100644
--- a/script/meta/bee/filesystem.lua
+++ b/script/meta/bee/filesystem.lua
@@ -112,4 +112,10 @@ end
 function fs.current_path()
 end
 
+---@param path fs.path
+---@param base fs.path
+---@return fs.path
+function fs.relative(path, base)
+end
+
 return fs
diff --git a/script/vm/compiler.lua b/script/vm/compiler.lua
index cee5de55e..ed56e4508 100644
--- a/script/vm/compiler.lua
+++ b/script/vm/compiler.lua
@@ -641,20 +641,11 @@ local function matchCall(source)
         newNode.originNode = myNode
         vm.setNode(source, newNode, true)
         if call.args then
-            -- clear existing node caches of args to allow recomputation with the type narrowed call
+            -- recompile existing node caches of args to allow recomputation with the type narrowed call
             for _, arg in ipairs(call.args) do
                 if vm.getNode(arg) then
-                    vm.setNode(arg, vm.createNode(), true)
-                end
-            end
-            for n in newNode:eachObject() do
-                if n.type == 'function'
-                or n.type == 'doc.type.function' then
-                    for i, arg in ipairs(call.args) do
-                        if vm.getNode(arg) and n.args[i] then
-                            vm.setNode(arg, vm.compileNode(n.args[i]))
-                        end
-                    end
+                    vm.removeNode(arg)
+                    vm.compileNode(arg)
                 end
             end
         end
diff --git a/script/vm/tracer.lua b/script/vm/tracer.lua
index 90be14625..ee76da2ac 100644
--- a/script/vm/tracer.lua
+++ b/script/vm/tracer.lua
@@ -268,6 +268,11 @@ local function getNodeTypesWithLiteralField(uri, source, fieldName, literal)
         return
     end
 
+    -- Literal must has a value
+    if literal[1] == nil then
+        return
+    end
+
     local tys
 
     for _, c in ipairs(vm.compileNode(loc)) do
@@ -277,10 +282,10 @@ local function getNodeTypesWithLiteralField(uri, source, fieldName, literal)
                     for _, f in ipairs(set.fields) do
                         if f.field[1] == fieldName then
                             for _, t in ipairs(f.extends.types) do
-                                if t[1] == literal[1] then
-                                  tys = tys or {}
-                                  table.insert(tys, {set.class[1], #f.extends.types > 1})
-                                  break
+                                if guide.isLiteral(t) and t[1] ~= nil and t[1] == literal[1] then
+                                    tys = tys or {}
+                                    table.insert(tys, { set.class[1], #f.extends.types > 1 })
+                                    break
                                 end
                             end
                             break
diff --git a/test/diagnostics/need-check-nil.lua b/test/diagnostics/need-check-nil.lua
index c4e3bba65..3580d7cbb 100644
--- a/test/diagnostics/need-check-nil.lua
+++ b/test/diagnostics/need-check-nil.lua
@@ -66,3 +66,17 @@ end
 
 x()
 ]]
+
+-- #3056
+TEST [[
+---@class A
+---@field b string
+---@field c 'string'|string1'
+---@field d 0|1|2
+
+---@type A?
+local a
+
+if <!a!>.b == "string1" then end
+if <!a!>.b == "string" then end
+]]
diff --git a/test/type_inference/common.lua b/test/type_inference/common.lua
index 5f0fd81a6..8376396ac 100644
--- a/test/type_inference/common.lua
+++ b/test/type_inference/common.lua
@@ -4606,6 +4606,31 @@ local a = {}
 function a:func(<?x?>) end
 ]]
 
+-- #3089
+TEST 'fun(x: number, y: number)' [[
+---@class Person
+---@field age? number
+---@field foo fun(x: number, y: number)
+
+---@param person Person
+local function test(person)
+    if person.foo ~= nil then
+        local <?b?> = person.foo
+    end
+end
+]]
+
+-- #2952
+TEST 'A' [[
+---@class A
+---@field b {[C]:D}
+local A
+
+if A.b ~= {} then
+    local C = <?A?>
+end
+]]
+
 TEST 'A' [[
 ---@class A
 ---@field type 'a'
@@ -4718,6 +4743,46 @@ local function f(v) end
 local <?r?> = f('')
 ]]
 
+TEST 'A' [[
+---@class A
+local A = {}
+
+---@generic T
+---@param self T
+---@param s string
+---@return T
+function A:f(s) end
+
+---@generic T
+---@param self T
+---@param i integer
+---@return T
+function A:f(i) end
+
+local <?r?> = A:f('')
+]]
+
+TEST 'B' [[
+---@class A
+local A = {}
+
+---@generic T
+---@param self T
+---@param s string
+---@return T
+function A:f(s) end
+
+---@generic T
+---@param self T
+---@param i integer
+---@return T
+function A:f(i) end
+
+---@class B: A
+local B = {}
+local <?r?> = B:f('')
+]]
+
 TEST 'integer' [[
 local function F(...)
     local t = {...}
