Skip to content

Commit 1c285e0

Browse files
committed
Diagnostics for unnecessary assert
1 parent dc4c9f2 commit 1c285e0

File tree

16 files changed

+135
-0
lines changed

16 files changed

+135
-0
lines changed

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44
<!-- Add all new changes here. They will be moved under a version at release -->
5+
`2025-3-13`
6+
* `NEW` `unnecessary-assert` diagnostic warns when asserting values that are always truthy
57

68
## 3.13.9
79
`2025-3-13`

locale/en-us/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS =
3636
'This function expects a maximum of {:d} argument(s) but instead it is receiving {:d}.'
3737
DIAG_MISS_ARGS =
3838
'This function requires {:d} argument(s) but instead it is receiving {:d}.'
39+
DIAG_UNNECESSARY_ASSERT =
40+
'Unnecessary assert: this expression is always truthy.'
3941
DIAG_OVER_MAX_VALUES =
4042
'Only has {} variables, but you set {} values.'
4143
DIAG_AMBIGUITY_1 =

locale/en-us/setting.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] =
406406
'Enable diagnostics for return statements without values although the containing function declares returns.'
407407
config.diagnostics['need-check-nil'] =
408408
'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.'
409+
config.diagnostics['unnecessary-assert'] =
410+
'Enable diagnostics for redundant assertions on truthy values.'
409411
config.diagnostics['no-unknown'] =
410412
'Enable diagnostics for cases in which the type cannot be inferred.'
411413
config.diagnostics['not-yieldable'] =

locale/ja-jp/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS =
3636
'この関数は最大で {:d} 個の引数を受け取りますが、{:d} 個の引数が渡されています。'
3737
DIAG_MISS_ARGS =
3838
'この関数は少なくとも {:d} 個の引数を必要としますが、{:d} 個しか渡されていません。'
39+
DIAG_UNNECESSARY_ASSERT =
40+
'不要なアサーション: この式は常に真です。'
3941
DIAG_OVER_MAX_VALUES =
4042
'変数は {} 個しかありませんが、{} 個の値が設定されています。'
4143
DIAG_AMBIGUITY_1 =

locale/ja-jp/setting.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate!
406406
'Enable diagnostics for return statements without values although the containing function declares returns.'
407407
config.diagnostics['need-check-nil'] = -- TODO: need translate!
408408
'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.'
409+
config.diagnostics['unnecessary-assert'] = -- TODO: need translate!
410+
'Enable diagnostics for redundant assertions on truthy values.'
409411
config.diagnostics['no-unknown'] = -- TODO: need translate!
410412
'Enable diagnostics for cases in which the type cannot be inferred.'
411413
config.diagnostics['not-yieldable'] = -- TODO: need translate!

locale/pt-br/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS =
3636
'A função aceita apenas os parâmetros {:d}, mas você passou {:d}.'
3737
DIAG_MISS_ARGS =
3838
'A função recebe pelo menos {:d} argumentos, mas há {:d}.'
39+
DIAG_UNNECESSARY_ASSERT =
40+
'Asserção desnecessária: esta expressão é sempre verdadeira.'
3941
DIAG_OVER_MAX_VALUES =
4042
'Apenas há {} variáveis, mas você declarou {} valores.'
4143
DIAG_AMBIGUITY_1 =

locale/pt-br/setting.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate!
406406
'Enable diagnostics for return statements without values although the containing function declares returns.'
407407
config.diagnostics['need-check-nil'] = -- TODO: need translate!
408408
'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.'
409+
config.diagnostics['unnecessary-assert'] = -- TODO: need translate!
410+
'Enable diagnostics for redundant assertions on truthy values.'
409411
config.diagnostics['no-unknown'] = -- TODO: need translate!
410412
'Enable diagnostics for cases in which the type cannot be inferred.'
411413
config.diagnostics['not-yieldable'] = -- TODO: need translate!

locale/zh-cn/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS =
3636
'函数最多接收 {:d} 个参数,但获得了 {:d} 个。'
3737
DIAG_MISS_ARGS =
3838
'函数最少接收 {:d} 个参数,但获得了 {:d} 个。'
39+
DIAG_UNNECESSARY_ASSERT =
40+
'不必要的断言:此表达式始终为真值。'
3941
DIAG_OVER_MAX_VALUES =
4042
'只有 {} 个变量,但你设置了 {} 个值。'
4143
DIAG_AMBIGUITY_1 =

locale/zh-cn/setting.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ config.diagnostics['missing-return-value'] =
404404
'函数无值返回但函数使用`@return`标记了返回值'
405405
config.diagnostics['need-check-nil'] =
406406
'变量之前被赋值为`nil`或可选值(可能为 `nil`)'
407+
config.diagnostics['unnecessary-assert'] =
408+
'启用对冗余断言(针对始终为真值的表达式)的诊断'
407409
config.diagnostics['no-unknown'] =
408410
'变量的未知类型无法推断'
409411
config.diagnostics['not-yieldable'] =

locale/zh-tw/script.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ DIAG_OVER_MAX_ARGS =
3636
'函式最多接收 {:d} 個引數,但獲得了 {:d} 個。'
3737
DIAG_MISS_ARGS =
3838
'函式最少接收 {:d} 個引數,但獲得了 {:d} 個。'
39+
DIAG_UNNECESSARY_ASSERT =
40+
'不必要的斷言:此表達式始終為真值。'
3941
DIAG_OVER_MAX_VALUES =
4042
'只有 {} 個變數,但你設定了 {} 個值。'
4143
DIAG_AMBIGUITY_1 =

locale/zh-tw/setting.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ config.diagnostics['missing-return-value'] = -- TODO: need translate!
404404
'Enable diagnostics for return statements without values although the containing function declares returns.'
405405
config.diagnostics['need-check-nil'] = -- TODO: need translate!
406406
'Enable diagnostics for variable usages if `nil` or an optional (potentially `nil`) value was assigned to the variable before.'
407+
config.diagnostics['unnecessary-assert'] = -- TODO: need translate!
408+
'Enable diagnostics for redundant assertions on truthy values.'
407409
config.diagnostics['no-unknown'] = -- TODO: need translate!
408410
'Enable diagnostics for cases in which the type cannot be inferred.'
409411
config.diagnostics['not-yieldable'] = -- TODO: need translate!
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
local files = require 'files'
2+
local guide = require 'parser.guide'
3+
local vm = require 'vm'
4+
local lang = require 'language'
5+
local await = require 'await'
6+
7+
---@async
8+
return function (uri, callback)
9+
local state = files.getState(uri)
10+
if not state then
11+
return
12+
end
13+
14+
---@async
15+
guide.eachSourceType(state.ast, 'call', function (source)
16+
await.delay()
17+
local currentFunc = guide.getParentFunction(source)
18+
if currentFunc and source.node.special == 'assert' and source.args[1] then
19+
local argNode = vm.compileNode(source.args[1])
20+
if argNode:alwaysTruthy() then
21+
callback {
22+
start = source.node.start,
23+
finish = source.node.finish,
24+
message = lang.script('DIAG_UNNECESSARY_ASSERT'),
25+
}
26+
end
27+
end
28+
end)
29+
end

script/proto/diagnostic.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ m.register {
7878
'cast-type-mismatch',
7979
'return-type-mismatch',
8080
'inject-field',
81+
'unnecessary-assert',
8182
} {
8283
group = 'type-check',
8384
severity = 'Warning',

script/vm/node.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,32 @@ function mt:hasFalsy()
119119
return false
120120
end
121121

122+
---Almost an inverse of hasFalsy, but stricter about "any" and "unknown" types.
123+
---@return boolean
124+
function mt:alwaysTruthy()
125+
if self.optional then
126+
return false
127+
end
128+
if #self == 0 then
129+
return false
130+
end
131+
for _, c in ipairs(self) do
132+
if c.type == 'nil'
133+
or (c.type == 'global' and c.cate == 'type' and c.name == 'nil')
134+
or (c.type == 'global' and c.cate == 'type' and c.name == 'false')
135+
or (c.type == 'global' and c.cate == 'type' and c.name == 'any')
136+
or (c.type == 'global' and c.cate == 'type' and c.name == 'boolean')
137+
or (c.type == 'global' and c.cate == 'type' and c.name == 'doc.type.boolean')
138+
or (c.type == 'global' and c.cate == 'type' and c.name == 'unknown')
139+
or not self:hasKnownType()
140+
or (c.type == 'boolean' and c[1] == false)
141+
or (c.type == 'doc.type.boolean' and c[1] == false) then
142+
return false
143+
end
144+
end
145+
return true
146+
end
147+
122148
---@return boolean
123149
function mt:hasKnownType()
124150
for _, c in ipairs(self) do

test/diagnostics/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ check 'missing-parameter'
100100
check 'missing-return-value'
101101
check 'missing-return'
102102
check 'need-check-nil'
103+
check 'unnecessary-assert'
103104
check 'newfield-call'
104105
check 'newline-call'
105106
check 'not-yieldable'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
TEST [[
2+
local a
3+
assert(a)
4+
5+
---@type boolean
6+
local b
7+
assert(b)
8+
9+
---@type any
10+
local c
11+
assert(c)
12+
13+
---@type unknown
14+
local d
15+
assert(d)
16+
17+
---@type boolean
18+
local e
19+
assert(e)
20+
21+
---@type number?
22+
local f
23+
assert(f)
24+
25+
assert(false)
26+
27+
assert(nil and 5)
28+
29+
---@return string?, string?
30+
local function f() end
31+
32+
assert(f())
33+
]]
34+
35+
TEST [[
36+
<!assert!>(true)
37+
]]
38+
39+
TEST [[
40+
---@return integer
41+
local function hi()
42+
return 1
43+
end
44+
<!assert!>(hi(1))
45+
]]
46+
47+
TEST [[
48+
<!assert!>({}, 'hi')
49+
]]
50+
51+
TEST [[
52+
---@return string, string?
53+
local function f() end
54+
55+
<!assert!>(f())
56+
]]

0 commit comments

Comments
 (0)