From 7a05912c3bc3a95ef0ec7379d215604b7ea7e521 Mon Sep 17 00:00:00 2001 From: Vladimir Davydov Date: Wed, 2 Apr 2025 18:43:39 +0300 Subject: [PATCH 01/14] cmake: bump min version to 3.5 Compatibility with CMake < 3.5 has been removed from CMake. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2025061..afae120 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.6 FATAL_ERROR) +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(checks NONE) From 1c7a8346fe573f65172b522206b5940fe5a71e23 Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Wed, 2 Apr 2025 09:33:45 +0300 Subject: [PATCH 02/14] ci: bump ubuntu version This patch bumps actions to use ubuntu-24.04 for fixing the following GitHub warning: The Ubuntu 20.04 Actions runner image will begin deprecation on 2025-02-01. Part of #TNTP-1918 Closes #64 --- .github/workflows/packaging.yml | 10 ++++++---- .github/workflows/push_rockspec.yml | 6 +++--- .github/workflows/reusable_testing.yml | 4 ++-- .github/workflows/test.yml | 12 ++++++------ CMakeLists.txt | 2 +- debian/prebuild.sh | 11 ++++++++++- 6 files changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index 1bf02ea..8c6fee2 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -15,7 +15,7 @@ jobs: if: | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Check module version if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }} @@ -32,7 +32,7 @@ jobs: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository needs: version-check - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -41,6 +41,7 @@ jobs: - { os: 'debian', dist: 'stretch' } - { os: 'debian', dist: 'buster' } - { os: 'debian', dist: 'bullseye' } + - { os: 'debian', dist: 'bookworm' } - { os: 'el', dist: '7' } - { os: 'el', dist: '8' } - { os: 'fedora', dist: '30' } @@ -54,6 +55,7 @@ jobs: - { os: 'ubuntu', dist: 'bionic' } - { os: 'ubuntu', dist: 'focal' } - { os: 'ubuntu', dist: 'jammy' } + - { os: 'ubuntu', dist: 'noble' } env: OS: ${{ matrix.platform.os }} @@ -61,12 +63,12 @@ jobs: steps: - name: Clone the module - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Clone the packpack tool - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: packpack/packpack path: packpack diff --git a/.github/workflows/push_rockspec.yml b/.github/workflows/push_rockspec.yml index ae3e383..381c689 100644 --- a/.github/workflows/push_rockspec.yml +++ b/.github/workflows/push_rockspec.yml @@ -17,7 +17,7 @@ jobs: version-check: # We need this job to run only on push with tag. if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Check module version uses: tarantool/actions/check-module-version@master @@ -27,7 +27,7 @@ jobs: require('test.rock_utils').assert_nonbuiltin('checks') push-scm-rockspec: - runs-on: [ ubuntu-20.04 ] + runs-on: [ ubuntu-24.04 ] if: github.ref == 'refs/heads/master' && github.event.workflow_run.conclusion == 'success' steps: - uses: actions/checkout@master @@ -38,7 +38,7 @@ jobs: files: ${{ env.ROCK_NAME }}-scm-1.rockspec push-tagged-rockspec: - runs-on: [ ubuntu-20.04 ] + runs-on: [ ubuntu-24.04 ] if: startsWith(github.ref, 'refs/tags') needs: version-check steps: diff --git a/.github/workflows/reusable_testing.yml b/.github/workflows/reusable_testing.yml index efe987f..518408a 100644 --- a/.github/workflows/reusable_testing.yml +++ b/.github/workflows/reusable_testing.yml @@ -5,13 +5,13 @@ on: inputs: artifact_name: description: The name of the tarantool build artifact - default: ubuntu-focal + default: ubuntu-jammy required: false type: string jobs: run_tests: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - name: Clone the checks module uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e46f23..5c489c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,12 +8,12 @@ jobs: strategy: fail-fast: false matrix: - tarantool: ['1.10', '2.5', '2.6', '2.7', '2.8', '2.10'] + tarantool: ['1.10','2.11', '3.3'] - runs-on: [ubuntu-20.04] + runs-on: [ubuntu-22.04] steps: - - uses: actions/checkout@v3 - - uses: tarantool/setup-tarantool@v2 + - uses: actions/checkout@v4 + - uses: tarantool/setup-tarantool@v3 with: tarantool-version: ${{ matrix.tarantool }} @@ -36,9 +36,9 @@ jobs: strategy: fail-fast: false - runs-on: [ubuntu-20.04] + runs-on: [ubuntu-22.04] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup LuaJIT run: sudo apt install -y luajit diff --git a/CMakeLists.txt b/CMakeLists.txt index afae120..ff0de26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +cmake_minimum_required(VERSION 3.20 FATAL_ERROR) project(checks NONE) diff --git a/debian/prebuild.sh b/debian/prebuild.sh index ecc64d2..3f2a4be 100755 --- a/debian/prebuild.sh +++ b/debian/prebuild.sh @@ -2,4 +2,13 @@ set -e -o pipefail -curl -LsSf https://www.tarantool.io/release/1.10/installer.sh | sudo bash +if [[ $DIST == "noble" ]] || [[ $DIST == "bookworm" ]]; then + curl -LsSf https://www.tarantool.io/release/3/installer.sh | sudo bash +elif [[ $DIST == "impish" ]] || [[ $DIST == "jammy" ]]; then + curl -LsSf https://www.tarantool.io/release/2/installer.sh | sudo bash +else + curl -LsSf https://www.tarantool.io/release/1.10/installer.sh | sudo bash +fi + +sudo apt install -y tt +tt version From 990334c2c6484806be17a70d44bd8d33f23bee63 Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Fri, 4 Apr 2025 16:09:28 +0300 Subject: [PATCH 03/14] ci: set cmake policy version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff0de26..ef330dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +cmake_minimum_required(VERSION 3.10...3.31 FATAL_ERROR) project(checks NONE) From c3641e71505c313b8f101ac0a0267d56f0e1b5ce Mon Sep 17 00:00:00 2001 From: Alexey Potapenko Date: Tue, 21 Oct 2025 11:58:04 +0300 Subject: [PATCH 04/14] Release 3.4.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ checks/version.lua | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b66ea7b..903bb80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +### Changed + +### Fixed + +### Removed + +## [3.4.0] - 2025-10-21 + +This release introduces bumped CMake version to avoid compatibility +problem with CMake 4.x. + +### Changed + +- Bump CMake version to avoid compatibility problem with CMake 4.x (#66). + ## [3.3.0] - 2023-04-19 ### Added @@ -16,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Override built-in checks, if installed. ## [3.2.0] - 2023-01-27 + ### Added - "error" type supported. diff --git a/checks/version.lua b/checks/version.lua index 37ef5c9..a37cd51 100644 --- a/checks/version.lua +++ b/checks/version.lua @@ -1,4 +1,4 @@ -- Сontains the module version. -- Requires manual update in case of release commit. -return '3.3.0' +return '3.4.0' From 70567395ba43ae58569c3184a358b17b192db937 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 17:21:41 +0300 Subject: [PATCH 05/14] test: update luacheckrc The patch updates luacheckrc to suppress warnings. Follows up the commit 49fa005dab08a1b206771e89e5231f779cccf914 ("test: add linting stage"). --- .luacheckrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.luacheckrc b/.luacheckrc index 57c02f0..7f11a70 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -2,3 +2,12 @@ include_files = {'**/*.lua', '*.luacheckrc', '*.rockspec'} exclude_files = {'.rocks/', 'tmp/', 'build/'} max_line_length = 120 redefined = false +globals = { + "box", +} +ignore = { + -- Accessing an undefined field of a global variable . + "143/table", + -- Unused variable with `_` prefix. + "212/_.*", +} From 41af70f5fbf5100e7c580ca7567f6cc3a160b313 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 12:49:22 +0300 Subject: [PATCH 06/14] doc: document public Lua functions with EmmyLua The patch adds comments for functions with descriptions and argument types in EmmyLua format. Needed for tarantool/tarantool#13020 --- checks.lua | 111 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 103 insertions(+), 8 deletions(-) diff --git a/checks.lua b/checks.lua index 35060a8..434e1e4 100644 --- a/checks.lua +++ b/checks.lua @@ -1,3 +1,15 @@ +---The `checks` module provides the ability to check the types of arguments +---passed to a Lua function. It is designed to reveal mistakes in code, not to +---validate user input. +--- +---The module table itself is callable: `checks(type_1, ...)` is equivalent to +---`checks.checks(type_1, ...)`. +--- +---@alias checks.qualifier string|table +--- +---@class checks +---@field checks fun(...: checks.qualifier) Checks the arguments of the calling function. +---@field _VERSION string The module version. local ffi = require('ffi') ffi.cdef[[ @@ -18,14 +30,14 @@ local _qualifiers_cache = { -- }, } ---- Check that string (or substring) starts with given string --- Optionally restricting the matching with the given offsets --- @function startswith --- @string inp original string --- @string head the substring to check against --- @int[opt] _start start index of matching boundary --- @int[opt] _end end index of matching boundary --- @returns boolean +---Check that the given string (or substring) starts with the given string, +---optionally restricting the matching with the given offsets. +--- +---@param inp string The original string. +---@param head string The substring to check against. +---@param _start? integer Start index of the matching boundary (default: `1`). +---@param _end? integer End index of the matching boundary (default: `#inp`). +---@return boolean `true` if `inp` starts with `head`, `false` otherwise. local function startswith(inp, head, _start, _end) if type(inp) ~= 'string' then error(err_string_arg:format(1, 'string.startswith', 'string', @@ -69,6 +81,13 @@ local function startswith(inp, head, _start, _end) end +---Check that a value conforms to the given string type qualifier. +--- +---@param value any The value to check. +---@param expected_type string The string type qualifier, e.g. `'string'`, +--- `'?number|string'`, or `'?'` for any type. +---@return boolean? `true` if the value conforms to the type qualifier. +---@return string? error_message The formatted error message if the check fails. local function check_string_type(value, expected_type) -- 1. Check any value. if expected_type == '?' then @@ -123,6 +142,11 @@ local function check_string_type(value, expected_type) ) end +---Format a table key for an error message: `.key` for strings and `[n]` +---for numbers. +--- +---@param key any The table key. +---@return string The formatted key name. local function keyname_fmt(key) if type(key) == 'string' then return string.format('.%s', key) @@ -133,6 +157,12 @@ local function keyname_fmt(key) end end +---Check that a table conforms to the given table type qualifier. +--- +---@param tbl table? The table to check. +---@param expected_fields table The table type qualifier. +---@return boolean? `true` if the table conforms to the type qualifier. +---@return string? error_message The formatted error message if the check fails. local function check_table_type(tbl, expected_fields) if tbl == nil then tbl = nil @@ -187,6 +217,17 @@ local function check_table_type(tbl, expected_fields) return true end +---Checks that the arguments of the calling function conform to the specified +---types. Must be called at the top of the function being checked; each type +---qualifier corresponds to the argument at the same position. +--- +---String qualifiers check Lua types, Tarantool-specific types (`uint64`, +---`int64`, `decimal`, `uuid`, etc.), metatable `__type` values, and custom +---checker names; they can be combined into union types (`'number|string'`) and +---made optional (`'?string'`). Table qualifiers validate the values of a table +---argument. +--- +---@param ... checks.qualifier Type qualifiers, one per argument to check. local function checks(...) local skip = 0 @@ -249,15 +290,41 @@ local function checks(...) end end +---The `checks` function is also available as a global, so it can be used +---without loading the module (Tarantool 2.11.0 and later). rawset(_G, 'checks', checks) +---The `checkers` global table provides access to checkers for different types. +---It can be extended with custom checkers that perform arbitrary validations. +--- +---@class checks.checkers +---@field datetime fun(arg: any): boolean Check that the value is a datetime object. +---@field decimal fun(arg: any): boolean Check that the value has the decimal type. +---@field error fun(arg: any): boolean Check that the value is an error object. +---@field int64 fun(arg: any): boolean Check that the value is an int64 value. +---@field interval fun(arg: any): boolean Check that the value is an interval object. +---@field tuple fun(arg: any): boolean Check that the value is a tuple. +---@field uint64 fun(arg: any): boolean Check that the value is a uint64 value. +---@field uuid fun(arg: any): boolean Check that the value is a uuid object. +---@field uuid_bin fun(arg: any): boolean Check that the value is a uuid as a 16-byte binary string. +---@field uuid_str fun(arg: any): boolean Check that the value is a uuid as a 36-byte hexadecimal string. local checkers = rawget(_G, 'checkers') or {} rawset(_G, 'checkers', checkers) +---When set to `true`, substitutes `nil` table arguments with empty tables for +---backward compatibility with v2.1. +---@type boolean local _checks_v2_compatible = rawget(_G, '_checks_v2_compatible') or false rawset(_G, '_checks_v2_compatible', _checks_v2_compatible) local ffi = require('ffi') + +---Check whether the specified value is a `uint64` value: an integer Lua number +---in the range from 0 to 2^53-1 (inclusive), a cdata `ctype`, or a +---cdata `ctype` in the range from 0 to `LLONG_MAX`. +--- +---@param arg any The value to check. +---@return boolean `true` if the value is a `uint64` value, `false` otherwise. function checkers.uint64(arg) if type(arg) == 'number' then -- Double floating point format has 52 fraction bits @@ -277,6 +344,13 @@ function checkers.uint64(arg) return false end +---Check whether the specified value is an `int64` value: an integer Lua +---number in the range from -2^53+1 to 2^53-1 (inclusive), a cdata +---`ctype`, or a cdata `ctype` in the range from 0 to +---`LLONG_MAX`. +--- +---@param arg any The value to check. +---@return boolean `true` if the value is an `int64` value, `false` otherwise. function checkers.int64(arg) if type(arg) == 'number' then return (arg > -2^53) and (arg < 2^53) and (math.floor(arg) == arg) @@ -293,6 +367,7 @@ function checkers.int64(arg) return false end +---Check whether the specified value is a tuple. local has_box = rawget(_G, 'box') ~= nil if has_box and box.tuple ~= nil then checkers.tuple = box.tuple.is @@ -303,11 +378,19 @@ if has_decimal then -- There is a decimal.is_decimal check since 2.4, but we -- reimplement it here to support older versions which have decimal. local cdata_t = ffi.typeof(decimal.new(0)) + ---Check whether the specified value has the decimal type. + --- + ---@param arg any The value to check. + ---@return boolean `true` if the value has the decimal type, `false` otherwise. checkers.decimal = function(arg) return ffi.istype(cdata_t, arg) end end +---Register a checker for a cdata type checked via FFI. +--- +---@param checks_type string The name of the checker to register in `checkers`. +---@param c_type string The C type name, e.g. `'struct tt_uuid'`. local function add_ffi_type_checker(checks_type, c_type) local has_cdata_t, cdata_t = pcall(ffi.typeof, c_type) if has_cdata_t then @@ -322,6 +405,11 @@ end -- https://github.com/tarantool/tarantool/blob/7682d34162be34648172d91008e9185301bce8f6/src/lua/uuid.lua#L29 add_ffi_type_checker('uuid', 'struct tt_uuid') +---Check whether the specified value is a uuid represented by a 36-byte +---hexadecimal string. +--- +---@param arg any The value to check. +---@return boolean `true` if the value is a uuid string, `false` otherwise. function checkers.uuid_str(arg) if type(arg) == 'string' and #arg == 36 then local match = arg:match( @@ -339,6 +427,11 @@ function checkers.uuid_str(arg) end end +---Check whether the specified value is a uuid represented by a 16-byte binary +---string. +--- +---@param arg any The value to check. +---@return boolean `true` if the value is a uuid binary string, `false` otherwise. function checkers.uuid_bin(arg) if type(arg) == 'string' and #arg == 16 then return true @@ -349,6 +442,7 @@ end add_ffi_type_checker('error', 'struct error') +---Check whether the specified value is a datetime object. local has_datetime, datetime = pcall(require, 'datetime') if has_datetime then checkers.datetime = datetime.is_datetime @@ -356,6 +450,7 @@ end add_ffi_type_checker('interval', 'struct interval') +---@type checks return setmetatable( { checks = checks, From 4cbbc929a3e619b4ac4629be4be3970cdd983ec2 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 12:54:11 +0300 Subject: [PATCH 07/14] emmyrc: initial config The patch introduces an initial configuration file .emmyrc for emmylua_check, it is a modern static analuzer for Lua. The configuration file has the same excluded directories as in .luacheckrc and config disables a number of rules. Most of them will be enabled back in the following commits except the following rules: - "unresolved-require" (7 warnings) is disabled because most modules are Tarantool builtins: `json`, `log`, `uuid`; no source code is included in the project. - "need-check-nil" (22 warnings) is disabled because these warnings are not critical and I don't want to add extra checks to the code. - "redefined-local" is disabled because it was disabled for luacheck in the commit 49fa005dab08a1b206771e89e5231f779cccf914 ("test: add linting stage"). --- .emmyrc.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .emmyrc.json diff --git a/.emmyrc.json b/.emmyrc.json new file mode 100644 index 0000000..b5b80ab --- /dev/null +++ b/.emmyrc.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json", + "workspace": { + "ignoreDir": [ + ".rocks", + "build", + "tmp" + ] + }, + "runtime": { + "version": "LuaJIT" + }, + "diagnostics": { + "disable": [ + "assign-type-mismatch", + "call-non-callable", + "duplicate-require", + "need-check-nil", + "param-type-mismatch", + "redefined-local", + "undefined-field", + "unnecessary-if", + "unresolved-require", + "unused" + ], + "globals": [ + "box" + ] + } +} From 3430a0b462c09a32ced42ae25b3adc096a597fe8 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 13:22:43 +0300 Subject: [PATCH 08/14] emmyrc: fix assign-type-mismatch The patch rule `assign-type-mismatch` in the .emmyrc and fixes three warnings: - @class checks moved to the module table - no longer sticks to local ffi. - `checks(...)`: vararg typed as `checks.qualifier|number`, level annotated with ---@type number, stack level retrieved via narrowed local first. - @field checks field consistent with type `fun(...: checks.qualifier|number)`. --- .emmyrc.json | 1 - checks.lua | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index b5b80ab..435cd6d 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -12,7 +12,6 @@ }, "diagnostics": { "disable": [ - "assign-type-mismatch", "call-non-callable", "duplicate-require", "need-check-nil", diff --git a/checks.lua b/checks.lua index 434e1e4..f4d07ff 100644 --- a/checks.lua +++ b/checks.lua @@ -6,10 +6,6 @@ ---`checks.checks(type_1, ...)`. --- ---@alias checks.qualifier string|table ---- ----@class checks ----@field checks fun(...: checks.qualifier) Checks the arguments of the calling function. ----@field _VERSION string The module version. local ffi = require('ffi') ffi.cdef[[ @@ -227,13 +223,16 @@ end ---made optional (`'?string'`). Table qualifiers validate the values of a table ---argument. --- ----@param ... checks.qualifier Type qualifiers, one per argument to check. +---@param ... checks.qualifier|number Type qualifiers, one per argument to +--- check. The first argument may also be a stack level (used internally). local function checks(...) local skip = 0 + ---@type number local level = 1 - if type(...) == 'number' then - level = ... + local first = ... + if type(first) == 'number' then + level = first skip = 1 end level = level + 1 -- escape the checks level @@ -450,8 +449,10 @@ end add_ffi_type_checker('interval', 'struct interval') ----@type checks -return setmetatable( +---@class checks +---@field checks fun(...: checks.qualifier|number) Checks the arguments of the calling function. +---@field _VERSION string The module version. +local M = setmetatable( { checks = checks, _VERSION = require('checks.version'), @@ -463,3 +464,5 @@ return setmetatable( end } ) + +return M From 39f6abaccb0f5a5de22ad42f2ead5e11f8a4ffa6 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 13:55:31 +0300 Subject: [PATCH 09/14] emmyrc: fix call-non-callable The patch enables rule `call-non-callable` and fixes warnings produced by this rule: removed class checks/@field from the modul table, now the `require('checks')` type is inferred from setmetatable (callable via variadic `__call`, checks/_VERSION fields are referenced). Field documentation has been moved to the module header. --- .emmyrc.json | 1 - checks.lua | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index 435cd6d..36de642 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -12,7 +12,6 @@ }, "diagnostics": { "disable": [ - "call-non-callable", "duplicate-require", "need-check-nil", "param-type-mismatch", diff --git a/checks.lua b/checks.lua index f4d07ff..31fd1c8 100644 --- a/checks.lua +++ b/checks.lua @@ -2,8 +2,9 @@ ---passed to a Lua function. It is designed to reveal mistakes in code, not to ---validate user input. --- ----The module table itself is callable: `checks(type_1, ...)` is equivalent to ----`checks.checks(type_1, ...)`. +---The returned module table is callable: `checks(type_1, ...)` is equivalent +---to `checks.checks(type_1, ...)`. It exposes the `checks` function and the +---`_VERSION` string field. --- ---@alias checks.qualifier string|table local ffi = require('ffi') @@ -449,9 +450,6 @@ end add_ffi_type_checker('interval', 'struct interval') ----@class checks ----@field checks fun(...: checks.qualifier|number) Checks the arguments of the calling function. ----@field _VERSION string The module version. local M = setmetatable( { checks = checks, From 6f0eb99da4bc76984cd4074e89165eba77c8736d Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 13:59:04 +0300 Subject: [PATCH 10/14] emmyrc: fix duplicate-require The patch enables rule `duplicate-require` in the .emmyrc and removes duplicate require for `ffi` module. --- .emmyrc.json | 1 - checks.lua | 2 -- 2 files changed, 3 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index 36de642..9ae76a2 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -12,7 +12,6 @@ }, "diagnostics": { "disable": [ - "duplicate-require", "need-check-nil", "param-type-mismatch", "redefined-local", diff --git a/checks.lua b/checks.lua index 31fd1c8..758cb04 100644 --- a/checks.lua +++ b/checks.lua @@ -317,8 +317,6 @@ rawset(_G, 'checkers', checkers) local _checks_v2_compatible = rawget(_G, '_checks_v2_compatible') or false rawset(_G, '_checks_v2_compatible', _checks_v2_compatible) -local ffi = require('ffi') - ---Check whether the specified value is a `uint64` value: an integer Lua number ---in the range from 0 to 2^53-1 (inclusive), a cdata `ctype`, or a ---cdata `ctype` in the range from 0 to `LLONG_MAX`. From 0e222a1e1e51eca7c957d3d3c6ad39453036f0e0 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 14:48:33 +0300 Subject: [PATCH 11/14] emmyrc: fix param-type-mismatch The patch enables rule `param-type-mismatch` in the .emmyrc and fixes 20 warnings produced by this rule. --- .emmyrc.json | 1 - checks.lua | 12 +++++++++++- test/test.lua | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index 9ae76a2..8553638 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -13,7 +13,6 @@ "diagnostics": { "disable": [ "need-check-nil", - "param-type-mismatch", "redefined-local", "undefined-field", "unnecessary-if", diff --git a/checks.lua b/checks.lua index 758cb04..f1e2a44 100644 --- a/checks.lua +++ b/checks.lua @@ -171,11 +171,13 @@ local function check_table_type(tbl, expected_fields) if type(expected_type) == 'string' then local ok, efmt = check_string_type(value, expected_type) if not ok then + ---@cast efmt string return nil, string.format(efmt, '%s'..keyname_fmt(expected_key), '%s') end elseif type(expected_type) == 'table' then local ok, efmt = check_string_type(value, '?table') if not ok then + ---@cast efmt string return nil, string.format(efmt, '%s'..keyname_fmt(expected_key), '%s') end @@ -186,6 +188,7 @@ local function check_table_type(tbl, expected_fields) local ok, efmt = check_table_type(value, expected_type) if not ok then + ---@cast efmt string return nil, string.format(efmt, '%s'..keyname_fmt(expected_key), '%s') end else @@ -229,10 +232,11 @@ end local function checks(...) local skip = 0 - ---@type number + ---@type integer local level = 1 local first = ... if type(first) == 'number' then + ---@cast first integer level = first skip = 1 end @@ -257,6 +261,7 @@ local function checks(...) local ok, efmt = check_string_type(value, expected_type) if not ok then local info = debug.getinfo(level, 'nl') + ---@cast efmt string local err = string.format(efmt, '#'..tostring(i), info.name) error(err, level) end @@ -265,18 +270,23 @@ local function checks(...) local ok, efmt = check_string_type(value, '?table') if not ok then local info = debug.getinfo(level, 'nl') + ---@cast efmt string local err = string.format(efmt, '#'..tostring(i), info.name) error(err, level) end if rawget(_G, '_checks_v2_compatible') and value == nil then value = {} + -- In emmylua-check 0.22.0 the debug.setlocal index + -- parameter is mistyped as `string`; it is an integer. + ---@diagnostic disable-next-line: param-type-mismatch debug.setlocal(level, i, value) end local ok, efmt = check_table_type(value, expected_type) if not ok then local info = debug.getinfo(level, 'nl') + ---@cast efmt string local err = string.format(efmt, argname, info.name) error(err, level) end diff --git a/test/test.lua b/test/test.lua index 1f55aa3..89ae5db 100755 --- a/test/test.lua +++ b/test/test.lua @@ -456,6 +456,7 @@ for _, case in pairs(err_cases) do g[name] = function(_) local fn = loadstring(case.code) + ---@cast fn function local ok, err = pcall(fn) if case.error == nil then @@ -1095,6 +1096,7 @@ for _, case in pairs(ret_cases) do t.skip_if(case.skip, "type unsupported") local fn = loadstring(case.code) + ---@cast fn function local ok, err = pcall(fn) t.assert_equals(case.ok, ok, err) end From 9c6a3682d77e957a69c244946e89d9cb50ed1cbb Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 15:04:29 +0300 Subject: [PATCH 12/14] emmyrc: fix undefined-field The patch enables rule `undefined-field` in the .emmyrc and fixes errors produced by this rule. The error is in using `table.deepcopy()` in test/test.lua because Tarantool API is unknown to the analyzer. The patch created a definition for `table.deepcopy()` in a file test/types.lua. --- .emmyrc.json | 1 - test/types.lua | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/types.lua diff --git a/.emmyrc.json b/.emmyrc.json index 8553638..089c271 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -14,7 +14,6 @@ "disable": [ "need-check-nil", "redefined-local", - "undefined-field", "unnecessary-if", "unresolved-require", "unused" diff --git a/test/types.lua b/test/types.lua new file mode 100644 index 0000000..07e6b2b --- /dev/null +++ b/test/types.lua @@ -0,0 +1,6 @@ +---@meta +--- +--- Tarantool extensions to the standard Lua `table` library. +--- +---@class tablelib +---@field deepcopy fun(t: table): table Deep-copies a table. From 6151c8dff419961ecbdba1e1279dfdc0eb77742b Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 15:13:37 +0300 Subject: [PATCH 13/14] emmyrc: fix unnecessary-if The patch enables `unnecessary-if` in the .emmyrc and fixes a warning produced by this rule. The reason of warning is an unknown type of variable `qualifier`. The analyzer infers the `optional` field as literal `false` and doesn't track the assignment `qualifier.optional = true` inside the `gmatch` loop, so `unnecessary-if` considers the condition is always `false`. This is a false positive - at runtime, the condition is reachable (e.g., '?string'). The patch adds the annotation to the local variable `qualifier` - the `optional` field is now typed as boolean, and the analyzer no longer considers the condition to always be false. --- .emmyrc.json | 1 - checks.lua | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.emmyrc.json b/.emmyrc.json index 089c271..c418dba 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -14,7 +14,6 @@ "disable": [ "need-check-nil", "redefined-local", - "unnecessary-if", "unresolved-require", "unused" ], diff --git a/checks.lua b/checks.lua index f1e2a44..c61997b 100644 --- a/checks.lua +++ b/checks.lua @@ -92,6 +92,7 @@ local function check_string_type(value, expected_type) end -- 2. Parse type qualifier + ---@type { [integer]: string, optional: boolean }? local qualifier = _qualifiers_cache[expected_type] if qualifier == nil then qualifier = { optional = false } From 35ed1d97294e81fca8e36fcbfea9d31eaabda4ac Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Fri, 7 Aug 2026 16:17:13 +0300 Subject: [PATCH 14/14] emmyrc: fix unused The patch enables rule `unused` and fixes a warnings produced by this rule by adding an underscore to the prefix of variables that are not used. --- .emmyrc.json | 3 +- test/perftest.lua | 2 +- test/test.lua | 84 +++++++++++++++++++++++------------------------ 3 files changed, 44 insertions(+), 45 deletions(-) diff --git a/.emmyrc.json b/.emmyrc.json index c418dba..0e12414 100644 --- a/.emmyrc.json +++ b/.emmyrc.json @@ -14,8 +14,7 @@ "disable": [ "need-check-nil", "redefined-local", - "unresolved-require", - "unused" + "unresolved-require" ], "globals": [ "box" diff --git a/test/perftest.lua b/test/perftest.lua index 13502e7..1a83176 100755 --- a/test/perftest.lua +++ b/test/perftest.lua @@ -134,7 +134,7 @@ for _, case in pairs(cases) do local name = ('test_%s_%s'):format(json.encode(case.check), case.argtype):gsub('%.', '_') g[name] = function(_) - local fn = function(arg) -- luacheck: no unused args + local fn = function(_arg) checks(case.check) end diff --git a/test/test.lua b/test/test.lua index 89ae5db..652d5b6 100755 --- a/test/test.lua +++ b/test/test.lua @@ -19,41 +19,41 @@ end local testdata = {} local _l_number_optstring = 2 + debug.getinfo(1).currentline -function testdata.fn_number_optstring(arg1, arg2) -- luacheck: no unused args +function testdata.fn_number_optstring(_arg1, _arg2) checks('number', '?string') end local _l_number_or_string = 2 + debug.getinfo(1).currentline -function testdata.fn_number_or_string(arg1) -- luacheck: no unused args +function testdata.fn_number_or_string(_arg1) checks('number|string') end local _l_positive_number = 2 + debug.getinfo(1).currentline -function testdata.fn_positive_number(arg1) -- luacheck: no unused args +function testdata.fn_positive_number(_arg1) checks('positive_number') end -function testdata.fn_anytype(arg1) -- luacheck: no unused args +function testdata.fn_anytype(_arg1) checks('?') end local _l_nil_or_number_or_string = 2 + debug.getinfo(1).currentline -function testdata.fn_nil_or_number_or_string(arg1) -- luacheck: no unused args +function testdata.fn_nil_or_number_or_string(_arg1) checks('nil|number|string') end local _l_optnumber_or_optstring = 2 + debug.getinfo(1).currentline -function testdata.fn_optnumber_or_optstring(arg1) -- luacheck: no unused args +function testdata.fn_optnumber_or_optstring(_arg1) checks('?number|?string') end local _l_varargs = 2 + debug.getinfo(1).currentline -function testdata.fn_varargs(arg1, ...) -- luacheck: no unused args +function testdata.fn_varargs(_arg1, ...) -- luacheck: ignore 212 checks('string') end local _l_options = 2 + debug.getinfo(1).currentline -function testdata.fn_options(options) -- luacheck: no unused args +function testdata.fn_options(_options) checks({ mystring = '?string', mynumber = '?number', @@ -61,17 +61,17 @@ function testdata.fn_options(options) -- luacheck: no unused args end local _l_array = 2 + debug.getinfo(1).currentline -function testdata.fn_array(array) -- luacheck: no unused args +function testdata.fn_array(_array) checks({'number', 'number'}) end local _l_table = 2 + debug.getinfo(1).currentline -function testdata.fn_table(table) -- luacheck: no unused args +function testdata.fn_table(_table) checks({mykey = 'number'}) end local _l_inception = 2 + debug.getinfo(1).currentline -function testdata.fn_inception(options) -- luacheck: no unused args +function testdata.fn_inception(_options) checks({ we = { need = { @@ -90,27 +90,27 @@ local function deepchecks() end local _l_deepcheck = 2 + debug.getinfo(1).currentline -function testdata.fn_deepcheck(arg1) -- luacheck: no unused args +function testdata.fn_deepcheck(_arg1) deepchecks() end local _l_excess_checks = 2 + debug.getinfo(1).currentline -function testdata.fn_excess_checks(arg1) -- luacheck: no unused args +function testdata.fn_excess_checks(_arg1) checks('?number', '?string') end local _l_missing_checks = 2 + debug.getinfo(1).currentline -function testdata.fn_missing_checks(arg1, arg2) -- luacheck: no unused args +function testdata.fn_missing_checks(_arg1, _arg2) checks('?number') end local _l_bad_check_type_1 = 2 + debug.getinfo(1).currentline -function testdata.bad_check_type_1(arg1, arg2) -- luacheck: no unused args +function testdata.bad_check_type_1(_arg1, _arg2) checks("?string", 5) end local _l_bad_check_type_2 = 2 + debug.getinfo(1).currentline -function testdata.bad_check_type_2(arg1, arg2) -- luacheck: no unused args +function testdata.bad_check_type_2(_arg1, _arg2) checks({param = 5}) end @@ -294,12 +294,12 @@ local err_cases = { { code = 'fn_options({mynumber = "bad"})', line = _l_options, - error = 'bad argument options.mynumber to fn_options (?number expected, got string)', + error = 'bad argument _options.mynumber to fn_options (?number expected, got string)', }, { code = 'fn_options({badfield = "bad"})', line = _l_options, - error = 'unexpected argument options.badfield to fn_options', + error = 'unexpected argument _options.badfield to fn_options', }, -- fn_array @@ -311,32 +311,32 @@ local err_cases = { { code = 'fn_array()', line = _l_array, - error = 'bad argument array[1] to fn_array (number expected, got nil)', + error = 'bad argument _array[1] to fn_array (number expected, got nil)', }, { code = 'fn_array(nil)', line = _l_array, - error = 'bad argument array[1] to fn_array (number expected, got nil)', + error = 'bad argument _array[1] to fn_array (number expected, got nil)', }, { code = 'fn_array(box.NULL)', line = _l_array, - error = 'bad argument array[1] to fn_array (number expected, got nil)', + error = 'bad argument _array[1] to fn_array (number expected, got nil)', }, { code = 'fn_array({})', line = _l_array, - error = 'bad argument array[1] to fn_array (number expected, got nil)', + error = 'bad argument _array[1] to fn_array (number expected, got nil)', }, { code = 'fn_array({"str1"})', line = _l_array, - error = 'bad argument array[1] to fn_array (number expected, got string)', + error = 'bad argument _array[1] to fn_array (number expected, got string)', }, { code = 'fn_array({1})', line = _l_array, - error = 'bad argument array[2] to fn_array (number expected, got nil)', + error = 'bad argument _array[2] to fn_array (number expected, got nil)', }, { code = 'fn_array({1, 2})', @@ -344,7 +344,7 @@ local err_cases = { { code = 'fn_array({1, 2, 3})', line = _l_array, - error = 'unexpected argument array[3] to fn_array', + error = 'unexpected argument _array[3] to fn_array', }, -- fn_table @@ -356,17 +356,17 @@ local err_cases = { { code = 'fn_table()', line = _l_table, - error = 'bad argument table.mykey to fn_table (number expected, got nil)', + error = 'bad argument _table.mykey to fn_table (number expected, got nil)', }, { code = 'fn_table({})', line = _l_table, - error = 'bad argument table.mykey to fn_table (number expected, got nil)', + error = 'bad argument _table.mykey to fn_table (number expected, got nil)', }, { code = 'fn_table({mykey = "str"})', line = _l_table, - error = 'bad argument table.mykey to fn_table (number expected, got string)', + error = 'bad argument _table.mykey to fn_table (number expected, got string)', }, { code = 'fn_table({mykey = 0})', @@ -374,7 +374,7 @@ local err_cases = { { code = 'fn_table({mykey = 0, excess = 1})', line = _l_table, - error = 'unexpected argument table.excess to fn_table', + error = 'unexpected argument _table.excess to fn_table', }, -- fn_inception @@ -387,7 +387,7 @@ local err_cases = { { code = 'fn_inception({we = false})', line = _l_inception, - error = 'bad argument options.we to fn_inception (?table expected, got boolean)', + error = 'bad argument _options.we to fn_inception (?table expected, got boolean)', }, { code = 'fn_inception({we = {}})', @@ -407,7 +407,7 @@ local err_cases = { { code = 'fn_inception({we = {need = {to = {go = {deeper = {}}}}}})', line = _l_inception, - error = 'bad argument options.we.need.to.go.deeper to fn_inception (?number expected, got table)', + error = 'bad argument _options.we.need.to.go.deeper to fn_inception (?number expected, got table)', }, -- fn_deepcheck @@ -429,7 +429,7 @@ local err_cases = { { code = 'fn_missing_checks()', line = _l_missing_checks, - error = 'checks: argument "arg2" is not checked', + error = 'checks: argument "_arg2" is not checked', }, { code = 'bad_check_type_1()', @@ -583,34 +583,34 @@ for _, case in pairs(options_v2_cases) do end ------------------------------------------------------------------------------ -function testdata.fn_int64(arg) -- luacheck: no unused args +function testdata.fn_int64(_arg) checks('int64') end -function testdata.fn_uint64(arg) -- luacheck: no unused args +function testdata.fn_uint64(_arg) checks('uint64') end local uuid = require('uuid') testdata.myid = uuid() -function testdata.fn_uuid(arg) -- luacheck: no unused args +function testdata.fn_uuid(_arg) checks('uuid') end -function testdata.fn_uuid_str(arg) -- luacheck: no unused args +function testdata.fn_uuid_str(_arg) checks('uuid_str') end -function testdata.fn_uuid_bin(arg) -- luacheck: no unused args +function testdata.fn_uuid_bin(_arg) checks('uuid_bin') end -function testdata.fn_tuple(arg) -- luacheck: no unused args +function testdata.fn_tuple(_arg) checks('tuple') end -function testdata.fn_decimal(arg) -- luacheck: no unused args +function testdata.fn_decimal(_arg) checks('decimal') end @@ -619,13 +619,13 @@ if has_decimal then testdata.decimal = decimal end -function testdata.fn_error(arg) -- luacheck: no unused args +function testdata.fn_error(_arg) checks('error') end local has_error = (box.error ~= nil) and (box.error.new ~= nil) -function testdata.fn_datetime(arg) -- luacheck: no unused args +function testdata.fn_datetime(_arg) checks('datetime') end @@ -634,7 +634,7 @@ if has_datetime then testdata.datetime = datetime end -function testdata.fn_interval(arg) -- luacheck: no unused args +function testdata.fn_interval(_arg) checks('interval') end