• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / luacheck / 24662353255

20 Apr 2026 10:48AM UTC coverage: 97.095% (+0.07%) from 97.027%
24662353255

push

github

web-flow
fix(ci): build LuaJIT using system malloc for lualanes tests (#144)

6318 of 6507 relevant lines covered (97.1%)

26928.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.78
/src/luacheck/stages/linearize.lua
1
local parser = require "luacheck.parser"
870✔
2
local utils = require "luacheck.utils"
870✔
3

4
local stage = {}
870✔
5

6
local function redefined_warning(message_format)
7
   return {
7,830✔
8
      message_format = message_format,
7,830✔
9
      fields = {"name", "prev_line", "prev_column", "prev_end_column", "self"}
7,830✔
10
   }
7,830✔
11
end
12

13
stage.warnings = {
870✔
14
   ["411"] = redefined_warning("variable {name!} was previously defined on line {prev_line}"),
1,160✔
15
   ["412"] = redefined_warning("variable {name!} was previously defined as an argument on line {prev_line}"),
1,160✔
16
   ["413"] = redefined_warning("variable {name!} was previously defined as a loop variable on line {prev_line}"),
1,160✔
17
   ["421"] = redefined_warning("shadowing definition of variable {name!} on line {prev_line}"),
1,160✔
18
   ["422"] = redefined_warning("shadowing definition of argument {name!} on line {prev_line}"),
1,160✔
19
   ["423"] = redefined_warning("shadowing definition of loop variable {name!} on line {prev_line}"),
1,160✔
20
   ["431"] = redefined_warning("shadowing upvalue {name!} on line {prev_line}"),
1,160✔
21
   ["432"] = redefined_warning("shadowing upvalue argument {name!} on line {prev_line}"),
1,160✔
22
   ["433"] = redefined_warning("shadowing upvalue loop variable {name!} on line {prev_line}"),
1,160✔
23
   ["521"] = {message_format = "unused label {label!}", fields = {"label"}}
870✔
24
}
870✔
25

26
local type_codes = {
870✔
27
   var = "1",
580✔
28
   func = "1",
580✔
29
   arg = "2",
580✔
30
   loop = "3",
580✔
31
   loopi = "3"
580✔
32
}
33

34
local function warn_redefined(chstate, var, prev_var, is_same_scope)
35
   local code = "4" .. (is_same_scope and "1" or var.line == prev_var.line and "2" or "3") .. type_codes[prev_var.type]
342✔
36

37
   chstate:warn_var(code, var, {
684✔
38
      self = var.self and prev_var.self,
342✔
39
      prev_line = prev_var.node.line,
342✔
40
      prev_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.offset),
456✔
41
      prev_end_column = chstate:offset_to_column(prev_var.node.line, prev_var.node.end_offset)
456✔
42
   })
43
end
44

45
local function warn_unused_label(chstate, label)
46
   chstate:warn_range("521", label.range, {
24✔
47
      label = label.name
12✔
48
   })
49
end
50

51
local pseudo_labels = utils.array_to_set({"do", "else", "break", "end", "return"})
870✔
52

53
local Line = utils.class()
870✔
54

55
function Line:__init(node, parent, value)
870✔
56
   -- Maps variables to arrays of accessing items.
57
   self.accessed_upvalues = {}
3,078✔
58
   -- Maps variables to arrays of mutating items.
59
   self.mutated_upvalues = {}
3,078✔
60
   -- Maps variables to arrays of setting items.
61
   self.set_upvalues = {}
3,078✔
62
   self.lines = {}
3,078✔
63
   self.node = node
3,078✔
64
   self.parent = parent
3,078✔
65
   self.value = value
3,078✔
66
   self.items = utils.Stack()
4,104✔
67
end
68

69
-- Calls callback with line, index, item, ... for each item reachable from starting item.
70
-- `visited` is a set of already visited indexes.
71
-- Callback can return true to stop walking from current item.
72
function Line:walk(visited, index, callback, ...)
870✔
73
   if visited[index] then
116,778✔
74
      return
7,686✔
75
   end
76

77
   visited[index] = true
109,092✔
78

79
   local item = self.items[index]
109,092✔
80

81
   if callback(self, index, item, ...) then
145,456✔
82
      return
8,976✔
83
   end
84

85
   if not item then
100,116✔
86
      return
2,340✔
87
   elseif item.tag == "Jump" then
97,776✔
88
      return self:walk(visited, item.to, callback, ...)
13,020✔
89
   elseif item.tag == "Cjump" then
84,756✔
90
      self:walk(visited, item.to, callback, ...)
8,052✔
91
   end
92

93
   return self:walk(visited, index + 1, callback, ...)
84,756✔
94
end
95

96
local function new_scope(line)
97
   return {
10,944✔
98
      vars = {},
10,944✔
99
      labels = {},
10,944✔
100
      gotos = {},
10,944✔
101
      line = line
10,944✔
102
   }
10,944✔
103
end
104

105
local function new_var(line, node, type_)
106
   return {
6,006✔
107
      name = node[1],
6,006✔
108
      node = node,
6,006✔
109
      type = type_,
6,006✔
110
      self = node.implicit,
6,006✔
111
      line = line,
6,006✔
112
      hint_unused = type_ == "arg" and node[1]:match("^_%a"),
6,006✔
113
      scope_start = line.items.size + 1,
6,006✔
114
      values = {}
6,006✔
115
   }
6,006✔
116
end
117

118
local function new_value(var_node, value_node, item, is_init)
119
   local value = {
7,080✔
120
      var = var_node.var,
7,080✔
121
      var_node = var_node,
7,080✔
122
      type = is_init and var_node.var.type or "var",
7,080✔
123
      node = value_node,
7,080✔
124
      using_lines = {},
7,080✔
125
      empty = is_init and not value_node and (var_node.var.type == "var"),
7,080✔
126
      item = item
7,080✔
127
   }
128

129
   if value_node and value_node.tag == "Function" then
7,080✔
130
      value.type = "func"
654✔
131
      value_node.value = value
654✔
132
   end
133

134
   return value
7,080✔
135
end
136

137
local function new_label(line, name, range)
138
   return {
6,276✔
139
      name = name,
6,276✔
140
      range = range,
6,276✔
141
      index = line.items.size + 1
6,276✔
142
   }
6,276✔
143
end
144

145
local function new_goto(name, jump, range)
146
   return {
4,950✔
147
      name = name,
4,950✔
148
      jump = jump,
4,950✔
149
      range = range
4,950✔
150
   }
4,950✔
151
end
152

153
local function new_jump_item(is_conditional)
154
   return {
4,950✔
155
      tag = is_conditional and "Cjump" or "Jump"
4,950✔
156
   }
4,950✔
157
end
158

159
local function new_eval_item(node)
160
   return {
5,352✔
161
      tag = "Eval",
3,568✔
162
      node = node,
5,352✔
163
      accesses = {},
5,352✔
164
      used_values = {},
5,352✔
165
      lines = {}
5,352✔
166
   }
5,352✔
167
end
168

169
local function new_noop_item(node, loop_end)
170
   return {
4,020✔
171
      tag = "Noop",
2,680✔
172
      node = node,
4,020✔
173
      loop_end = loop_end
4,020✔
174
   }
4,020✔
175
end
176

177
local function new_local_item(node)
178
   return {
5,898✔
179
      tag = "Local",
3,932✔
180
      node = node,
5,898✔
181
      lhs = node[1],
5,898✔
182
      rhs = node[2],
5,898✔
183
      accesses = node[2] and {},
5,898✔
184
      used_values = node[2] and {},
5,898✔
185
      lines = node[2] and {}
5,898✔
186
   }
5,898✔
187
end
188

189
local function new_set_item(node)
190
   return {
2,532✔
191
      tag = "Set",
1,688✔
192
      node = node,
2,532✔
193
      lhs = node[1],
2,532✔
194
      rhs = node[2],
2,532✔
195
      accesses = {},
2,532✔
196
      mutations = {},
2,532✔
197
      used_values = {},
2,532✔
198
      lines = {}
2,532✔
199
   }
2,532✔
200
end
201

202
local function new_opset_item(node)
203
   return {
72✔
204
      tag = "OpSet",
48✔
205
      node = node,
72✔
206
      lhs = node[1],
72✔
207
      rhs = node[2],
72✔
208
      accesses = {},
72✔
209
      mutations = {},
72✔
210
      used_values = {},
72✔
211
      lines = {}
72✔
212
   }
72✔
213
end
214

215
local function is_unpacking(node)
216
   return node.tag == "Dots" or node.tag == "Call" or node.tag == "Invoke"
4,536✔
217
end
218

219
local LinState = utils.class()
870✔
220

221
function LinState:__init(chstate)
870✔
222
   self.chstate = chstate
1,530✔
223
   self.lines = utils.Stack()
2,040✔
224
   self.scopes = utils.Stack()
2,040✔
225
end
226

227
function LinState:enter_scope()
870✔
228
   self.scopes:push(new_scope(self.lines.top))
14,592✔
229
end
230

231
function LinState:leave_scope()
870✔
232
   local left_scope = self.scopes:pop()
10,812✔
233
   local prev_scope = self.scopes.top
10,812✔
234

235
   for _, goto_ in ipairs(left_scope.gotos) do
19,920✔
236
      local label = left_scope.labels[goto_.name]
9,132✔
237

238
      if label then
9,132✔
239
         goto_.jump.to = label.index
4,926✔
240
         label.used = true
4,926✔
241
      else
242
         if not prev_scope or prev_scope.line ~= self.lines.top then
4,206✔
243
            if goto_.name == "break" then
24✔
244
               parser.syntax_error("'break' is not inside a loop", goto_.range)
18✔
245
            else
246
               parser.syntax_error(("no visible label '%s'"):format(goto_.name), goto_.range)
6✔
247
            end
248
         end
249

250
         table.insert(prev_scope.gotos, goto_)
4,182✔
251
      end
252
   end
253

254
   for name, label in pairs(left_scope.labels) do
17,022✔
255
      if not label.used and not pseudo_labels[name] then
6,234✔
256
         warn_unused_label(self.chstate, label)
12✔
257
      end
258
   end
259

260
   for _, var in pairs(left_scope.vars) do
16,530✔
261
      var.scope_end = self.lines.top.items.size
5,742✔
262
   end
263
end
264

265
function LinState:register_var(node, type_)
870✔
266
   local var = new_var(self.lines.top, node, type_)
6,006✔
267
   local prev_var = self:resolve_var(var.name)
6,006✔
268

269
   if prev_var then
6,006✔
270
      local is_same_scope = self.scopes.top.vars[var.name]
570✔
271

272
      if var.name ~= "..." then
570✔
273
         warn_redefined(self.chstate, var, prev_var, is_same_scope)
342✔
274
      end
275

276
      if is_same_scope then
570✔
277
         prev_var.scope_end = self.lines.top.items.size
222✔
278
      end
279
   end
280

281
   self.scopes.top.vars[var.name] = var
6,006✔
282
   node.var = var
6,006✔
283
   return var
6,006✔
284
end
285

286
function LinState:register_vars(nodes, type_)
870✔
287
   for _, node in ipairs(nodes) do
10,392✔
288
      self:register_var(node, type_)
5,250✔
289
   end
290
end
291

292
function LinState:resolve_var(name)
870✔
293
   for _, scope in utils.ripairs(self.scopes) do
103,274✔
294
      local var = scope.vars[name]
63,312✔
295

296
      if var then
63,312✔
297
         return var
9,654✔
298
      end
299
   end
300
end
301

302
function LinState:check_var(node)
870✔
303
   if not node.var then
13,032✔
304
      node.var = self:resolve_var(node[1])
17,376✔
305
   end
306

307
   return node.var
13,032✔
308
end
309

310
function LinState:register_label(name, range)
870✔
311
   local prev_label = self.scopes.top.labels[name]
6,288✔
312

313
   if prev_label then
6,288✔
314
      assert(not pseudo_labels[name])
12✔
315
      parser.syntax_error(("label '%s' already defined on line %d"):format(
36✔
316
         name, prev_label.range.line), range, prev_label.range)
24✔
317
   end
318

319
   self.scopes.top.labels[name] = new_label(self.lines.top, name, range)
8,368✔
320
end
321

322
function LinState:emit(item)
870✔
323
   self.lines.top.items:push(item)
22,776✔
324
end
325

326
function LinState:emit_goto(name, is_conditional, range)
870✔
327
   local jump = new_jump_item(is_conditional)
4,950✔
328
   self:emit(jump)
4,950✔
329
   table.insert(self.scopes.top.gotos, new_goto(name, jump, range))
6,600✔
330
end
331

332
local tag_to_boolean = {
870✔
333
   Nil = false, False = false,
580✔
334
   True = true, Number = true, String = true, Table = true, Function = true
580✔
335
}
336

337
-- Emits goto that jumps to ::name:: if bool(cond_node) == false.
338
function LinState:emit_cond_goto(name, cond_node)
870✔
339
   local cond_bool = tag_to_boolean[cond_node.tag]
1,266✔
340

341
   if cond_bool ~= true then
1,266✔
342
      self:emit_goto(name, cond_bool ~= false)
1,200✔
343
   end
344
end
345

346
function LinState:emit_noop(node, loop_end)
870✔
347
   self:emit(new_noop_item(node, loop_end))
5,360✔
348
end
349

350
function LinState:emit_stmt(stmt)
870✔
351
   self["emit_stmt_" .. stmt.tag](self, stmt)
10,512✔
352
end
353

354
function LinState:emit_stmts(stmts)
870✔
355
   for _, stmt in ipairs(stmts) do
15,756✔
356
      self:emit_stmt(stmt)
10,512✔
357
   end
358
end
359

360
function LinState:emit_block(block)
870✔
361
   self:enter_scope()
1,740✔
362
   self:emit_stmts(block)
1,740✔
363
   self:leave_scope()
1,734✔
364
end
365

366
function LinState:emit_stmt_Do(node)
870✔
367
   self:emit_noop(node)
234✔
368
   self:emit_block(node)
234✔
369
end
370

371
function LinState:emit_stmt_While(node)
870✔
372
   self:emit_noop(node)
156✔
373
   self:enter_scope()
156✔
374
   self:register_label("do")
156✔
375
   self:emit_expr(node[1])
156✔
376
   self:emit_cond_goto("break", node[1])
156✔
377
   self:emit_block(node[2])
156✔
378
   self:emit_noop(node, true)
150✔
379
   self:emit_goto("do")
150✔
380
   self:register_label("break")
150✔
381
   self:leave_scope()
150✔
382
end
383

384
function LinState:emit_stmt_Repeat(node)
870✔
385
   self:emit_noop(node)
84✔
386
   self:enter_scope()
84✔
387
   self:register_label("do")
84✔
388
   self:enter_scope()
84✔
389
   self:emit_stmts(node[1])
84✔
390
   self:emit_expr(node[2])
84✔
391
   self:leave_scope()
84✔
392
   self:emit_cond_goto("do", node[2])
84✔
393
   self:register_label("break")
84✔
394
   self:leave_scope()
84✔
395
end
396

397
function LinState:emit_stmt_Fornum(node)
870✔
398
   self:emit_noop(node)
168✔
399
   self:emit_expr(node[2])
168✔
400
   self:emit_expr(node[3])
168✔
401

402
   if node[5] then
168✔
403
      self:emit_expr(node[4])
18✔
404
   end
405

406
   self:enter_scope()
168✔
407
   self:register_label("do")
168✔
408
   self:emit_goto("break", true)
168✔
409
   self:enter_scope()
168✔
410
   self:emit(new_local_item({{node[1]}}))
224✔
411
   self:register_var(node[1], "loopi")
168✔
412
   self:emit_stmts(node[5] or node[4])
168✔
413
   self:leave_scope()
168✔
414
   self:emit_noop(node, true)
168✔
415
   self:emit_goto("do")
168✔
416
   self:register_label("break")
168✔
417
   self:leave_scope()
168✔
418
end
419

420
function LinState:emit_stmt_Forin(node)
870✔
421
   self:emit_noop(node)
240✔
422
   self:emit_exprs(node[2])
240✔
423
   self:enter_scope()
240✔
424
   self:register_label("do")
240✔
425
   self:emit_goto("break", true)
240✔
426
   self:enter_scope()
240✔
427
   self:emit(new_local_item({node[1]}))
320✔
428
   self:register_vars(node[1], "loop")
240✔
429
   self:emit_stmts(node[3])
240✔
430
   self:leave_scope()
240✔
431
   self:emit_noop(node, true)
240✔
432
   self:emit_goto("do")
240✔
433
   self:register_label("break")
240✔
434
   self:leave_scope()
240✔
435
end
436

437
function LinState:emit_stmt_If(node)
870✔
438
   self:emit_noop(node)
882✔
439
   self:enter_scope()
882✔
440

441
   for i = 1, #node - 1, 2 do
1,908✔
442
      self:enter_scope()
1,026✔
443
      self:emit_expr(node[i])
1,026✔
444
      self:emit_cond_goto("else", node[i])
1,026✔
445
      self:emit_block(node[i + 1])
1,026✔
446
      self:emit_goto("end")
1,026✔
447
      self:register_label("else")
1,026✔
448
      self:leave_scope()
1,026✔
449
   end
450

451
   if #node % 2 == 1 then
882✔
452
      self:emit_block(node[#node])
324✔
453
   end
454

455
   self:register_label("end")
882✔
456
   self:leave_scope()
882✔
457
end
458

459
function LinState:emit_stmt_Label(node)
870✔
460
   self:register_label(node[1], node)
72✔
461
end
462

463
function LinState:emit_stmt_Goto(node)
870✔
464
   self:emit_noop(node)
42✔
465
   self:emit_goto(node[1], false, node)
42✔
466
end
467

468
function LinState:emit_stmt_Break(node)
870✔
469
   self:emit_goto("break", false, node)
78✔
470
end
471

472
function LinState:emit_stmt_Return(node)
870✔
473
   self:emit_noop(node)
1,656✔
474
   self:emit_exprs(node)
1,656✔
475
   self:emit_goto("return")
1,638✔
476
end
477

478
function LinState:emit_expr(node)
870✔
479
   local item = new_eval_item(node)
5,352✔
480
   self:scan_expr(item, node)
5,352✔
481
   self:emit(item)
5,328✔
482
end
483

484
function LinState:emit_exprs(exprs)
870✔
485
   for _, expr in ipairs(exprs) do
3,726✔
486
      self:emit_expr(expr)
1,848✔
487
   end
488
end
489

490
LinState.emit_stmt_Call = LinState.emit_expr
870✔
491
LinState.emit_stmt_Invoke = LinState.emit_expr
870✔
492

493
function LinState:emit_stmt_Local(node)
870✔
494
   local item = new_local_item(node)
1,824✔
495
   self:emit(item)
1,824✔
496

497
   if node[2] then
1,824✔
498
      self:scan_exprs(item, node[2])
1,368✔
499
   end
500

501
   self:register_vars(node[1], "var")
1,824✔
502
end
503

504
function LinState:emit_stmt_Localrec(node)
870✔
505
   local item = new_local_item(node)
588✔
506
   self:register_var(node[1][1], "var")
588✔
507
   self:emit(item)
588✔
508
   self:scan_expr(item, node[2][1])
588✔
509
end
510

511
function LinState:emit_stmt_Set(node)
870✔
512
   local item = new_set_item(node)
2,532✔
513
   self:scan_exprs(item, node[2])
2,532✔
514

515
   for _, expr in ipairs(node[1]) do
5,214✔
516
      if expr.tag == "Id" then
2,706✔
517
         local var = self:check_var(expr)
1,590✔
518

519
         if var then
1,590✔
520
            self:register_upvalue_action(item, var, "set_upvalues")
1,080✔
521
         end
522
      else
523
         assert(expr.tag == "Index")
1,116✔
524
         self:scan_lhs_index(item, expr)
1,116✔
525
      end
526
   end
527

528
   self:emit(item)
2,508✔
529
end
530

531
function LinState:emit_stmt_OpSet(node)
870✔
532
   local item = new_opset_item(node)
72✔
533
   self:scan_exprs(item, node[2])
72✔
534

535
   for _, expr in ipairs(node[1]) do
144✔
536
      if expr.tag == "Id" then
72✔
537
         local var = self:check_var(expr)
54✔
538

539
         if var then
54✔
540
            self:register_upvalue_action(item, var, "set_upvalues")
54✔
541
         end
542
      else
543
         assert(expr.tag == "Index")
18✔
544
         self:scan_lhs_index(item, expr)
18✔
545
      end
546
   end
547

548
   self:emit(item)
72✔
549
end
550

551
function LinState:scan_expr(item, node)
870✔
552
   local scanner = self["scan_expr_" .. node.tag]
29,478✔
553

554
   if scanner then
29,478✔
555
      scanner(self, item, node)
21,462✔
556
   end
557
end
558

559
function LinState:scan_exprs(item, nodes)
870✔
560
   for _, node in ipairs(nodes) do
29,052✔
561
      self:scan_expr(item, node)
17,826✔
562
   end
563
end
564

565
function LinState:register_upvalue_action(item, var, key)
870✔
566
   for _, line in utils.ripairs(self.lines) do
17,232✔
567
      if line == var.line then
10,644✔
568
         break
4,560✔
569
      end
570

571
      if not line[key][var] then
1,524✔
572
         line[key][var] = {}
1,134✔
573
      end
574

575
      table.insert(line[key][var], item)
1,524✔
576
   end
577
end
578

579
function LinState:mark_access(item, node)
870✔
580
   node.var.accessed = true
7,110✔
581

582
   if not item.accesses[node.var] then
7,110✔
583
      item.accesses[node.var] = {}
6,450✔
584
   end
585

586
   table.insert(item.accesses[node.var], node)
7,110✔
587
   self:register_upvalue_action(item, node.var, "accessed_upvalues")
7,110✔
588
end
589

590
function LinState:mark_mutation(item, node)
870✔
591
   node.var.mutated = true
876✔
592

593
   if not item.mutations[node.var] then
876✔
594
      item.mutations[node.var] = {}
870✔
595
   end
596

597
   table.insert(item.mutations[node.var], node)
876✔
598
   self:register_upvalue_action(item, node.var, "mutated_upvalues")
876✔
599
end
600

601
function LinState:scan_expr_Id(item, node)
870✔
602
   if self:check_var(node) then
12,968✔
603
      self:mark_access(item, node)
6,546✔
604
   end
605
end
606

607
function LinState:scan_expr_Dots(item, node)
870✔
608
   local dots = self:check_var(node)
528✔
609

610
   if not dots or dots.line ~= self.lines.top then
528✔
611
      parser.syntax_error("cannot use '...' outside a vararg function", node)
18✔
612
   end
613

614
   self:mark_access(item, node)
510✔
615
end
616

617
function LinState:scan_lhs_index(item, node)
870✔
618
   if node[1].tag == "Id" then
1,320✔
619
      if self:check_var(node[1]) then
1,512✔
620
         self:mark_mutation(item, node[1])
876✔
621
      end
622
   elseif node[1].tag == "Index" then
186✔
623
      self:scan_lhs_index(item, node[1])
248✔
624
   else
625
      self:scan_expr(item, node[1])
×
626
   end
627

628
   self:scan_expr(item, node[2])
1,320✔
629
end
630

631
LinState.scan_expr_Index = LinState.scan_exprs
870✔
632
LinState.scan_expr_Call = LinState.scan_exprs
870✔
633
LinState.scan_expr_Invoke = LinState.scan_exprs
870✔
634
LinState.scan_expr_Paren = LinState.scan_exprs
870✔
635
LinState.scan_expr_Table = LinState.scan_exprs
870✔
636
LinState.scan_expr_Pair = LinState.scan_exprs
870✔
637

638
function LinState:scan_expr_Op(item, node)
870✔
639
   self:scan_expr(item, node[2])
2,370✔
640

641
   if node[3] then
2,370✔
642
      self:scan_expr(item, node[3])
2,022✔
643
   end
644
end
645

646
LinState.scan_expr_OpSet = LinState.scan_expr_Op
870✔
647

648
-- Puts tables {var = value} into field `set_variables` of items in line which set values.
649
-- Registers set values in field `values` of variables.
650
function LinState:register_set_variables()
870✔
651
   local line = self.lines.top
2,994✔
652

653
   for _, item in ipairs(line.items) do
25,620✔
654
      if item.tag == "Local" or item.tag == "Set" or item.tag == "OpSet" then
22,626✔
655
         item.set_variables = {}
8,394✔
656

657
         local is_init = item.tag == "Local"
8,394✔
658
         local unpacking_item -- Rightmost item of rhs which may unpack into several lhs items.
659

660
         if item.rhs then
8,394✔
661
            local last_rhs_item = item.rhs[#item.rhs]
4,536✔
662

663
            if is_unpacking(last_rhs_item) then
6,048✔
664
               unpacking_item = last_rhs_item
402✔
665
            end
666
         end
667

668
         local secondaries -- Array of values unpacked from rightmost rhs item.
669

670
         if unpacking_item and (#item.lhs > #item.rhs) then
8,394✔
671
            secondaries = {}
114✔
672
         end
673

674
         for i, node in ipairs(item.lhs) do
17,118✔
675
            local value
676

677
            if node.var then
8,724✔
678
               -- OpSet also accesses
679
               if item.tag == "OpSet" then
7,080✔
680
                  self:mark_access(item, node)
54✔
681
               end
682
               value = new_value(node, item.rhs and item.rhs[i] or unpacking_item, item, is_init)
9,440✔
683
               item.set_variables[node.var] = value
7,080✔
684
               table.insert(node.var.values, value)
7,080✔
685
            end
686

687
            if secondaries and (i >= #item.rhs) then
8,724✔
688
               if value then
228✔
689
                  value.secondaries = secondaries
204✔
690
                  table.insert(secondaries, value)
204✔
691
               else
692
                  -- If one of secondary values is assigned to a global or index,
693
                  -- it is considered used.
694
                  secondaries.used = true
24✔
695
               end
696
            end
697
         end
698
      end
699
   end
700
end
701

702
function LinState:build_line(node)
870✔
703
   self.lines:push(Line(node, self.lines.top))
4,104✔
704
   self:enter_scope()
3,078✔
705
   self:emit(new_local_item({node[1]}))
4,104✔
706
   self:enter_scope()
3,078✔
707
   self:register_vars(node[1], "arg")
3,078✔
708
   self:emit_stmts(node[2])
3,078✔
709
   self:leave_scope()
3,018✔
710
   self:register_label("return")
3,018✔
711
   self:leave_scope()
3,018✔
712
   self:register_set_variables()
2,994✔
713
   local line = self.lines:pop()
2,994✔
714

715
   for _, prev_line in ipairs(self.lines) do
4,830✔
716
      table.insert(prev_line.lines, line)
1,836✔
717
   end
718

719
   return line
2,994✔
720
end
721

722
function LinState:scan_expr_Function(item, node)
870✔
723
   local line = self:build_line(node)
1,548✔
724
   table.insert(item.lines, line)
1,518✔
725

726
   for _, nested_line in ipairs(line.lines) do
1,836✔
727
      table.insert(item.lines, nested_line)
318✔
728
   end
729
end
730

731
-- Builds linear representation (line) of AST and assigns it as `chstate.top_line`.
732
-- Assigns an array of all lines as `chstate.lines`.
733
-- Adds warnings for redefined/shadowed locals and unused labels.
734
function stage.run(chstate)
870✔
735
   local linstate = LinState(chstate)
1,530✔
736
   chstate.top_line = linstate:build_line({{{tag = "Dots", "..."}}, chstate.ast})
2,022✔
737
   assert(linstate.lines.size == 0)
1,476✔
738
   assert(linstate.scopes.size == 0)
1,476✔
739

740
   chstate.lines = {chstate.top_line}
1,476✔
741

742
   for _, nested_line in ipairs(chstate.top_line.lines) do
2,994✔
743
      table.insert(chstate.lines, nested_line)
1,518✔
744
   end
745
end
746

747
return stage
870✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc