mt-lang 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/lib/milk_tea/base.rb +1 -2
  3. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +0 -2
  4. data/lib/milk_tea/bindings/bindgen/declaration.rb +0 -2
  5. data/lib/milk_tea/bindings/bindgen/emitter.rb +0 -2
  6. data/lib/milk_tea/bindings/bindgen/overrides.rb +0 -2
  7. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +51 -53
  8. data/lib/milk_tea/bindings/imported_bindings/generator.rb +7 -9
  9. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +0 -2
  10. data/lib/milk_tea/bindings/imported_bindings/naming.rb +6 -8
  11. data/lib/milk_tea/bindings/imported_bindings.rb +0 -2
  12. data/lib/milk_tea/bindings/opengl_registry.rb +3 -7
  13. data/lib/milk_tea/bindings/steamworks.rb +12 -16
  14. data/lib/milk_tea/bindings/upstream_sources.rb +10 -14
  15. data/lib/milk_tea/bindings/vendored_box2d.rb +10 -12
  16. data/lib/milk_tea/bindings/vendored_cjson.rb +6 -8
  17. data/lib/milk_tea/bindings/vendored_flecs.rb +10 -12
  18. data/lib/milk_tea/bindings/vendored_glfw.rb +11 -13
  19. data/lib/milk_tea/bindings/vendored_libuv.rb +11 -13
  20. data/lib/milk_tea/bindings/vendored_pcre2.rb +11 -13
  21. data/lib/milk_tea/bindings/vendored_raylib.rb +6 -10
  22. data/lib/milk_tea/bindings/vendored_sdl3.rb +11 -13
  23. data/lib/milk_tea/bindings/vendored_steamworks.rb +1 -5
  24. data/lib/milk_tea/core/ast.rb +103 -45
  25. data/lib/milk_tea/core/async_runtime_installer.rb +0 -2
  26. data/lib/milk_tea/core/bindings/attribute_binding.rb +1 -3
  27. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +147 -84
  28. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +394 -396
  29. data/lib/milk_tea/core/c_backend/expressions.rb +489 -476
  30. data/lib/milk_tea/core/c_backend/feature_detection.rb +446 -437
  31. data/lib/milk_tea/core/c_backend/format_helpers.rb +0 -2
  32. data/lib/milk_tea/core/c_backend/reachability.rb +339 -341
  33. data/lib/milk_tea/core/c_backend/reinterpret.rb +231 -217
  34. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +531 -526
  35. data/lib/milk_tea/core/c_backend/statements.rb +528 -530
  36. data/lib/milk_tea/core/c_backend/type_collectors.rb +1344 -1346
  37. data/lib/milk_tea/core/c_backend/type_declaration.rb +215 -213
  38. data/lib/milk_tea/core/c_backend/type_system.rb +347 -349
  39. data/lib/milk_tea/core/c_backend.rb +17 -9
  40. data/lib/milk_tea/core/compatibility_helpers.rb +0 -2
  41. data/lib/milk_tea/core/compile_time/const_eval.rb +7 -12
  42. data/lib/milk_tea/core/compile_time.rb +32 -38
  43. data/lib/milk_tea/core/control_flow/builder.rb +3 -5
  44. data/lib/milk_tea/core/control_flow/constant_propagation.rb +14 -17
  45. data/lib/milk_tea/core/control_flow/definite_assignment.rb +0 -1
  46. data/lib/milk_tea/core/ir.rb +10 -10
  47. data/lib/milk_tea/core/lexer/character_classes.rb +5 -7
  48. data/lib/milk_tea/core/lexer/format_strings.rb +7 -9
  49. data/lib/milk_tea/core/lexer/heredocs.rb +22 -22
  50. data/lib/milk_tea/core/lexer/indentation.rb +0 -2
  51. data/lib/milk_tea/core/lexer/numbers.rb +0 -2
  52. data/lib/milk_tea/core/lexer/recovery.rb +0 -2
  53. data/lib/milk_tea/core/lexer/strings.rb +0 -2
  54. data/lib/milk_tea/core/lexer/symbols.rb +0 -2
  55. data/lib/milk_tea/core/lexer/trivia.rb +0 -2
  56. data/lib/milk_tea/core/lexer.rb +0 -2
  57. data/lib/milk_tea/core/lowering/async/analysis.rb +0 -2
  58. data/lib/milk_tea/core/lowering/async/lowering.rb +1264 -1265
  59. data/lib/milk_tea/core/lowering/async/normalization.rb +0 -2
  60. data/lib/milk_tea/core/lowering/async.rb +627 -630
  61. data/lib/milk_tea/core/lowering/block.rb +413 -416
  62. data/lib/milk_tea/core/lowering/calls.rb +1381 -1384
  63. data/lib/milk_tea/core/lowering/declarations.rb +180 -183
  64. data/lib/milk_tea/core/lowering/dyn.rb +48 -50
  65. data/lib/milk_tea/core/lowering/events.rb +983 -985
  66. data/lib/milk_tea/core/lowering/expressions.rb +1513 -1518
  67. data/lib/milk_tea/core/lowering/foreign_cstr.rb +181 -184
  68. data/lib/milk_tea/core/lowering/functions.rb +185 -188
  69. data/lib/milk_tea/core/lowering/loops.rb +1028 -1031
  70. data/lib/milk_tea/core/lowering/proc.rb +363 -366
  71. data/lib/milk_tea/core/lowering/resolve.rb +2133 -2135
  72. data/lib/milk_tea/core/lowering/scans.rb +179 -181
  73. data/lib/milk_tea/core/lowering/str_buffer.rb +102 -105
  74. data/lib/milk_tea/core/lowering/utils.rb +1040 -1044
  75. data/lib/milk_tea/core/lowering.rb +5 -7
  76. data/lib/milk_tea/core/module_binder.rb +0 -2
  77. data/lib/milk_tea/core/module_loader/errors.rb +4 -2
  78. data/lib/milk_tea/core/module_loader.rb +113 -20
  79. data/lib/milk_tea/core/module_path_resolver.rb +0 -2
  80. data/lib/milk_tea/core/module_roots.rb +6 -8
  81. data/lib/milk_tea/core/parser/attributes.rb +0 -2
  82. data/lib/milk_tea/core/parser/blocks.rb +0 -2
  83. data/lib/milk_tea/core/parser/declarations.rb +0 -2
  84. data/lib/milk_tea/core/parser/expressions.rb +31 -14
  85. data/lib/milk_tea/core/parser/recovery.rb +0 -2
  86. data/lib/milk_tea/core/parser/statements.rb +0 -2
  87. data/lib/milk_tea/core/parser/types.rb +8 -10
  88. data/lib/milk_tea/core/parser.rb +1 -3
  89. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +31 -33
  90. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +0 -2
  91. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +0 -2
  92. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +0 -2
  93. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +14 -16
  94. data/lib/milk_tea/core/semantic_analyzer/calls.rb +0 -2
  95. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1 -14
  96. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +18 -21
  97. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +0 -3
  98. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +12 -15
  99. data/lib/milk_tea/core/semantic_analyzer/generics.rb +57 -59
  100. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +0 -2
  101. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +5 -9
  102. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +0 -3
  103. data/lib/milk_tea/core/semantic_analyzer/statements.rb +35 -40
  104. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  105. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +0 -6
  106. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +102 -105
  107. data/lib/milk_tea/core/semantic_analyzer.rb +17 -8
  108. data/lib/milk_tea/core/types/layout.rb +21 -22
  109. data/lib/milk_tea/core/types/predicates.rb +0 -2
  110. data/lib/milk_tea/core/types/registry.rb +16 -18
  111. data/lib/milk_tea/core/types/visitor.rb +0 -4
  112. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -2
  113. data/lib/milk_tea/dap/protocol.rb +1 -1
  114. data/lib/milk_tea/dap/server/breakpoints.rb +0 -2
  115. data/lib/milk_tea/dap/server/debug_map.rb +0 -2
  116. data/lib/milk_tea/dap/server/handlers.rb +0 -2
  117. data/lib/milk_tea/dap/server/launch.rb +0 -2
  118. data/lib/milk_tea/dap/server/lldb_backend.rb +8 -10
  119. data/lib/milk_tea/dap/server/pause_diagnostics.rb +14 -16
  120. data/lib/milk_tea/dap/server/utilities.rb +0 -2
  121. data/lib/milk_tea/dap/server/wire.rb +0 -2
  122. data/lib/milk_tea/dap/server.rb +0 -2
  123. data/lib/milk_tea/lsp/diagnostics.rb +16 -16
  124. data/lib/milk_tea/lsp/server/call_hierarchy.rb +11 -12
  125. data/lib/milk_tea/lsp/server/code_actions.rb +355 -357
  126. data/lib/milk_tea/lsp/server/code_lens.rb +23 -25
  127. data/lib/milk_tea/lsp/server/completion.rb +873 -875
  128. data/lib/milk_tea/lsp/server/configuration.rb +129 -131
  129. data/lib/milk_tea/lsp/server/debug_info.rb +0 -2
  130. data/lib/milk_tea/lsp/server/definition.rb +564 -565
  131. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +191 -193
  132. data/lib/milk_tea/lsp/server/execute_command.rb +0 -2
  133. data/lib/milk_tea/lsp/server/folding_range.rb +0 -4
  134. data/lib/milk_tea/lsp/server/formatting.rb +478 -480
  135. data/lib/milk_tea/lsp/server/hover.rb +1282 -1284
  136. data/lib/milk_tea/lsp/server/inlay_hints.rb +179 -181
  137. data/lib/milk_tea/lsp/server/lifecycle.rb +192 -194
  138. data/lib/milk_tea/lsp/server/linked_editing_range.rb +0 -2
  139. data/lib/milk_tea/lsp/server/on_type_formatting.rb +0 -2
  140. data/lib/milk_tea/lsp/server/progress.rb +0 -2
  141. data/lib/milk_tea/lsp/server/references.rb +316 -318
  142. data/lib/milk_tea/lsp/server/rename.rb +445 -447
  143. data/lib/milk_tea/lsp/server/selection_range.rb +0 -4
  144. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1318 -1320
  145. data/lib/milk_tea/lsp/server/signature_help.rb +146 -148
  146. data/lib/milk_tea/lsp/server/text_documents.rb +99 -101
  147. data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -4
  148. data/lib/milk_tea/lsp/server/utilities.rb +397 -399
  149. data/lib/milk_tea/lsp/server.rb +0 -3
  150. data/lib/milk_tea/lsp/workspace/analysis.rb +40 -42
  151. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +4 -10
  152. data/lib/milk_tea/lsp/workspace/utilities.rb +19 -19
  153. data/lib/milk_tea/packages/atomic_write.rb +4 -5
  154. data/lib/milk_tea/packages/dependency_solver.rb +15 -15
  155. data/lib/milk_tea/packages/lock.rb +4 -4
  156. data/lib/milk_tea/packages/manager_cli.rb +1 -1
  157. data/lib/milk_tea/packages/manifest.rb +3 -3
  158. data/lib/milk_tea/packages/registry_store.rb +4 -6
  159. data/lib/milk_tea/packages/services.rb +16 -16
  160. data/lib/milk_tea/packages/source_cache.rb +4 -6
  161. data/lib/milk_tea/packages/source_resolver.rb +44 -44
  162. data/lib/milk_tea/packages/version.rb +8 -8
  163. data/lib/milk_tea/tooling/build.rb +3 -7
  164. data/lib/milk_tea/tooling/build_cache.rb +0 -2
  165. data/lib/milk_tea/tooling/debug_info_formatter.rb +31 -33
  166. data/lib/milk_tea/tooling/debug_map.rb +8 -10
  167. data/lib/milk_tea/tooling/docs_app.rb +0 -2
  168. data/lib/milk_tea/tooling/linter/doc_tags.rb +25 -27
  169. data/lib/milk_tea/tooling/linter/fix_engine.rb +87 -24
  170. data/lib/milk_tea/tooling/linter/flow_rules.rb +44 -46
  171. data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -16
  172. data/lib/milk_tea/tooling/linter/release_rules.rb +3 -5
  173. data/lib/milk_tea/tooling/linter/reserved_names.rb +23 -25
  174. data/lib/milk_tea/tooling/linter/rules.rb +87 -89
  175. data/lib/milk_tea/tooling/linter/source_helpers.rb +40 -42
  176. data/lib/milk_tea/tooling/linter/trailing_comma.rb +17 -19
  177. data/lib/milk_tea/tooling/linter/visitors.rb +162 -50
  178. data/lib/milk_tea/tooling/linter.rb +0 -2
  179. data/lib/milk_tea/tooling/project_scaffold.rb +0 -2
  180. data/lib/milk_tea/tooling/run.rb +0 -2
  181. data/lib/milk_tea/tooling/toolchain_cli.rb +0 -2
  182. data/std/cli.mt +13 -32
  183. data/std/color.mt +28 -28
  184. data/std/encoding.mt +4 -4
  185. data/std/graph.mt +1 -2
  186. data/std/hash.mt +14 -14
  187. data/std/http/server.mt +3 -3
  188. data/std/input.mt +1 -1
  189. data/std/json.mt +1 -1
  190. data/std/linear_algebra.mt +6 -6
  191. data/std/net/mux.mt +1 -1
  192. data/std/noise.mt +23 -23
  193. data/std/parse.mt +2 -2
  194. data/std/process.mt +3 -4
  195. data/std/sparse_set.mt +1 -1
  196. data/std/steering.mt +5 -5
  197. data/std/str.mt +1 -1
  198. data/std/tween.mt +4 -4
  199. data/std/utility.mt +3 -3
  200. metadata +2 -2
@@ -3,19 +3,17 @@
3
3
  module MilkTea
4
4
  class Linter
5
5
  module LinterRules
6
- private
7
-
8
6
  # ── missing-return ───────────────────────────────────────────────────
9
-
7
+
10
8
  def check_missing_return(function)
11
9
  return unless function.return_type # implicit void — no check
12
10
  return if void_return_type?(function.return_type)
13
11
  return if always_returns?(function.body)
14
-
12
+
15
13
  @warnings << Warning.new(
16
14
  path: @path,
17
15
  line: function.line,
18
- column: function.respond_to?(:column) ? function.column : nil,
16
+ column: function.column,
19
17
  length: function.name.length,
20
18
  code: "missing-return",
21
19
  message: "function '#{function.name}' does not always return a value",
@@ -23,15 +21,15 @@ module MilkTea
23
21
  symbol_name: function.name
24
22
  )
25
23
  end
26
-
24
+
27
25
  def emit_redundant_return_warnings(function)
28
26
  return unless function.return_type
29
27
  return unless void_return_type?(function.return_type)
30
-
28
+
31
29
  final_statement = function.body&.last
32
30
  return unless final_statement.is_a?(AST::ReturnStmt)
33
31
  return unless final_statement.value.nil?
34
-
32
+
35
33
  @warnings << Warning.new(
36
34
  path: @path,
37
35
  line: final_statement.line,
@@ -42,7 +40,7 @@ module MilkTea
42
40
  severity: :hint,
43
41
  )
44
42
  end
45
-
43
+
46
44
  def void_return_type?(return_type)
47
45
  case return_type
48
46
  when AST::TypeRef
@@ -54,7 +52,7 @@ module MilkTea
54
52
  false
55
53
  end
56
54
  end
57
-
55
+
58
56
  # Returns true if every execution path through `stmts` ends with a
59
57
  # guaranteed return. Conservative: only IfStmt with else + MatchStmt
60
58
  # with arms are considered exhaustive.
@@ -87,23 +85,23 @@ module MilkTea
87
85
  end
88
86
  end
89
87
  end
90
-
88
+
91
89
  def infinite_while_without_break?(stmt)
92
90
  stmt.condition.is_a?(AST::BooleanLiteral) &&
93
91
  stmt.condition.value == true &&
94
92
  !loop_body_can_break?(stmt.body)
95
93
  end
96
-
94
+
97
95
  def loop_body_can_break?(body)
98
96
  return false if body.nil? || body.empty?
99
-
97
+
100
98
  graph = ControlFlow::Builder.new.build_loop_body(body)
101
99
  reachability = ControlFlow::Reachability.solve(graph)
102
100
  graph.each_node.any? do |node|
103
101
  node.kind == :break_exit && reachability.reachable_ids.include?(node.id)
104
102
  end
105
103
  end
106
-
104
+
107
105
  def terminating_expression?(expression)
108
106
  case expression
109
107
  when AST::Call
@@ -122,7 +120,7 @@ module MilkTea
122
120
  first_arg = expression.arguments.first
123
121
  first_arg.is_a?(AST::BooleanLiteral) && first_arg.value == false
124
122
  end
125
-
123
+
126
124
  def terminating_callee?(callee)
127
125
  case callee
128
126
  when AST::Identifier
@@ -139,9 +137,9 @@ module MilkTea
139
137
  def check_redundant_else(stmt)
140
138
  return unless stmt.else_body && !stmt.else_body.empty?
141
139
  return unless stmt.branches.all? { |b| always_returns?(b.body) }
142
-
140
+
143
141
  # Use the line of the first else-body statement as the diagnostic anchor.
144
- else_line = stmt.else_body.first.respond_to?(:line) ? stmt.else_body.first.line : stmt.line
142
+ else_line = stmt.else_body.first.line || stmt.line
145
143
  @warnings << Warning.new(
146
144
  path: @path,
147
145
  line: stmt.else_line || else_line,
@@ -170,13 +168,13 @@ module MilkTea
170
168
  return if expr.is_a?(AST::UnaryOp) && expr.operator == "?"
171
169
  return if contains_side_effecting_expression?(expr)
172
170
 
173
- line = expression_line(expr) || (stmt.respond_to?(:line) ? stmt.line : nil)
171
+ line = expression_line(expr) || (stmt.line)
174
172
  column = expression_column(expr)
175
173
  length = expression_length(expr)
176
- if line && (!column || !length || !expr.respond_to?(:column) || expr.is_a?(AST::UnsafeExpr))
174
+ if line && (!column || !length || expr.is_a?(AST::UnsafeExpr))
177
175
  fallback_span = source_statement_span(line)
178
176
  column ||= fallback_span&.first
179
- length = fallback_span&.last if !length || !expr.respond_to?(:column) || expr.is_a?(AST::UnsafeExpr)
177
+ length = fallback_span&.last if !length || expr.is_a?(AST::UnsafeExpr)
180
178
  end
181
179
 
182
180
  @warnings << Warning.new(
@@ -206,12 +204,12 @@ module MilkTea
206
204
  end
207
205
  end
208
206
  # ── self-assignment ────────────────────────────────────────────────────
209
-
207
+
210
208
  def check_self_assignment(stmt)
211
209
  return unless stmt.operator == "="
212
210
  return unless stmt.target.is_a?(AST::Identifier) && stmt.value.is_a?(AST::Identifier)
213
211
  return unless stmt.target.name == stmt.value.name
214
-
212
+
215
213
  @warnings << Warning.new(
216
214
  path: @path,
217
215
  line: expression_line(stmt.target) || stmt.line,
@@ -223,14 +221,14 @@ module MilkTea
223
221
  symbol_name: stmt.target.name
224
222
  )
225
223
  end
226
-
224
+
227
225
  # ── self-comparison ────────────────────────────────────────────────────
228
-
226
+
229
227
  def check_self_comparison(expr)
230
228
  return unless %w[== !=].include?(expr.operator)
231
229
  return unless expr.left.is_a?(AST::Identifier) && expr.right.is_a?(AST::Identifier)
232
230
  return unless expr.left.name == expr.right.name
233
-
231
+
234
232
  line = expression_line(expr) || expr.left.line
235
233
  always = expr.operator == "==" ? "always true" : "always false"
236
234
  @warnings << Warning.new(
@@ -244,24 +242,24 @@ module MilkTea
244
242
  symbol_name: expr.left.name
245
243
  )
246
244
  end
247
-
245
+
248
246
  def check_redundant_bool_compare(expr)
249
247
  return unless %w[== !=].include?(expr.operator)
250
-
248
+
251
249
  left_bool = expr.left.is_a?(AST::BooleanLiteral)
252
250
  right_bool = expr.right.is_a?(AST::BooleanLiteral)
253
251
  return unless left_bool ^ right_bool
254
-
252
+
255
253
  literal = left_bool ? expr.left : expr.right
256
254
  compared = left_bool ? expr.right : expr.left
257
255
  return if compared.is_a?(AST::BooleanLiteral)
258
-
256
+
259
257
  suggestion = if expr.operator == "=="
260
258
  literal.value ? "use the expression directly" : "invert the expression with 'not'"
261
259
  else
262
260
  literal.value ? "invert the expression with 'not'" : "use the expression directly"
263
261
  end
264
-
262
+
265
263
  @warnings << Warning.new(
266
264
  path: @path,
267
265
  line: expression_line(expr),
@@ -272,38 +270,38 @@ module MilkTea
272
270
  severity: :hint,
273
271
  )
274
272
  end
275
-
273
+
276
274
  def redundant_bool_compare_length(expr)
277
275
  length = expression_length(expr)
278
276
  return length if length
279
-
277
+
280
278
  line = expression_line(expr)
281
279
  column = expression_column(expr)
282
280
  return nil unless line && column
283
-
281
+
284
282
  text = source_code_line(line)
285
283
  return nil unless text
286
-
284
+
287
285
  snippet = text[(column - 1)..]
288
286
  return nil unless snippet
289
-
287
+
290
288
  right_literal = snippet.match(/\A\s*(.+?)\s*(==|!=)\s*(true|false)\b/)
291
289
  return right_literal[0].rstrip.length if right_literal
292
-
290
+
293
291
  left_literal = snippet.match(/\A\s*(true|false)\s*(==|!=)\s*(.+?)(?=\s*(?::|\)|,|and\b|or\b|$))/)
294
292
  return left_literal[0].rstrip.length if left_literal
295
-
293
+
296
294
  nil
297
295
  end
298
-
296
+
299
297
  def check_duplicate_if_conditions(statement)
300
298
  return unless statement.is_a?(AST::IfStmt)
301
-
299
+
302
300
  seen_signatures = {}
303
301
  statement.branches.each do |branch|
304
302
  signature = expression_signature(branch.condition)
305
303
  next unless signature
306
-
304
+
307
305
  existing = seen_signatures[signature]
308
306
  if existing
309
307
  @warnings << Warning.new(
@@ -317,11 +315,11 @@ module MilkTea
317
315
  )
318
316
  next
319
317
  end
320
-
318
+
321
319
  seen_signatures[signature] = branch
322
320
  end
323
321
  end
324
-
322
+
325
323
  def expression_signature(expression)
326
324
  case expression
327
325
  when AST::Identifier
@@ -342,20 +340,20 @@ module MilkTea
342
340
  left = expression_signature(expression.left)
343
341
  right = expression_signature(expression.right)
344
342
  return nil unless left && right
345
-
343
+
346
344
  "binary:(#{left})#{expression.operator}(#{right})"
347
345
  else
348
346
  nil
349
347
  end
350
348
  end
351
-
349
+
352
350
  def check_noop_compound_assignment(statement)
353
351
  return unless statement.is_a?(AST::Assignment)
354
352
  return unless %w[+= -= *= /= |= ^= <<= >>=].include?(statement.operator)
355
-
353
+
356
354
  identity = noop_compound_identity_value(statement.operator, statement.value)
357
355
  return unless identity
358
-
356
+
359
357
  @warnings << Warning.new(
360
358
  path: @path,
361
359
  line: statement.line,
@@ -366,7 +364,7 @@ module MilkTea
366
364
  severity: :hint,
367
365
  )
368
366
  end
369
-
367
+
370
368
  def noop_compound_identity_value(operator, value)
371
369
  case operator
372
370
  when "+=", "-=", "|=", "^=", "<<=", ">>="
@@ -377,38 +375,38 @@ module MilkTea
377
375
  false
378
376
  end
379
377
  end
380
-
378
+
381
379
  def integer_literal_zero?(value)
382
380
  value.is_a?(AST::IntegerLiteral) && value.lexeme.gsub("_", "") == "0"
383
381
  end
384
-
382
+
385
383
  def numeric_literal_one?(value)
386
384
  return true if value.is_a?(AST::IntegerLiteral) && value.lexeme.gsub("_", "") == "1"
387
385
  return true if value.is_a?(AST::FloatLiteral) && %w[1.0 1.].include?(value.lexeme.gsub("_", ""))
388
-
386
+
389
387
  false
390
388
  end
391
389
  # ── prefer-let-else / prefer-var-else ──────────────────────────────
392
-
390
+
393
391
  def emit_prefer_let_else_warnings(stmts)
394
392
  emit_prefer_else_warnings(stmts, expected_kind: :let, code: "prefer-let-else")
395
393
  end
396
-
394
+
397
395
  def emit_prefer_var_else_warnings(stmts)
398
396
  emit_prefer_else_warnings(stmts, expected_kind: :var, code: "prefer-var-else")
399
397
  end
400
-
398
+
401
399
  def emit_prefer_else_warnings(stmts, expected_kind:, code:)
402
400
  return if stmts.nil? || stmts.empty?
403
-
401
+
404
402
  walk_statement_lists(stmts) do |statement_list|
405
403
  statement_list.each_with_index do |_statement, index|
406
404
  candidate = prefer_else_candidate(statement_list, index, expected_kind:)
407
405
  next unless candidate
408
-
406
+
409
407
  branch = candidate[:branch]
410
408
  line, column, length = condition_span(branch.condition, line: candidate[:if_stmt].line, keyword_pattern: "if")
411
-
409
+
412
410
  @warnings << Warning.new(
413
411
  path: @path,
414
412
  line:,
@@ -422,7 +420,7 @@ module MilkTea
422
420
  end
423
421
  end
424
422
  end
425
-
423
+
426
424
  def prefer_else_candidate(stmts, index, expected_kind:)
427
425
  declaration = stmts[index]
428
426
  if_stmt = stmts[index + 1]
@@ -433,39 +431,39 @@ module MilkTea
433
431
  return if ignored_binding_name?(declaration.name)
434
432
  return unless if_stmt.is_a?(AST::IfStmt)
435
433
  return unless if_stmt.else_body.nil? && if_stmt.branches.length == 1
436
-
434
+
437
435
  branch = if_stmt.branches.first
438
436
  identifier = null_equality_identifier(branch.condition)
439
437
  return unless identifier&.name == declaration.name
440
438
  return unless nullable_binding_declaration?(declaration)
441
439
  return if expected_kind == :var && !prefer_var_else_binding_mutated?(declaration)
442
440
  return unless ControlFlow::Termination.block_always_terminates?(branch.body, ignore_name: method(:ignored_binding_name?), binding_resolution: cfg_binding_resolution)
443
-
441
+
444
442
  { declaration:, if_stmt:, branch: }
445
443
  end
446
-
444
+
447
445
  def prefer_var_else_binding_mutated?(declaration)
448
446
  binding = @scopes.last[declaration.name]
449
447
  binding&.mutated == true
450
448
  end
451
-
449
+
452
450
  def null_equality_identifier(cond)
453
451
  return nil unless cond.is_a?(AST::BinaryOp) && cond.operator == "=="
454
-
452
+
455
453
  if cond.left.is_a?(AST::Identifier) && cond.right.is_a?(AST::NullLiteral)
456
454
  cond.left
457
455
  elsif cond.left.is_a?(AST::NullLiteral) && cond.right.is_a?(AST::Identifier)
458
456
  cond.right
459
457
  end
460
458
  end
461
-
459
+
462
460
  def nullable_binding_declaration?(statement)
463
461
  binding_type = binding_type_for_declaration(statement)
464
462
  return binding_type.is_a?(Types::Nullable) if binding_type
465
-
463
+
466
464
  nullable_initializer_without_binding_type?(statement.value)
467
465
  end
468
-
466
+
469
467
  def nullable_initializer_without_binding_type?(expression)
470
468
  case expression
471
469
  when AST::Identifier, AST::MemberAccess, AST::IndexAccess, AST::Call, AST::IfExpr, AST::MatchExpr
@@ -476,14 +474,14 @@ module MilkTea
476
474
  false
477
475
  end
478
476
  end
479
-
477
+
480
478
  def binding_type_for_declaration(statement)
481
479
  binding_resolution = @sema_facts&.binding_resolution
482
480
  return nil unless binding_resolution&.binding_types
483
-
481
+
484
482
  binding_id = binding_resolution.declaration_binding_ids[statement.object_id]
485
483
  return nil unless binding_id
486
-
484
+
487
485
  binding_resolution.binding_types[binding_id]
488
486
  end
489
487
  def pointer_like_cast_expression(expression)
@@ -495,25 +493,25 @@ module MilkTea
495
493
 
496
494
  { target_type:, source: expression.expression }
497
495
  end
498
-
496
+
499
497
  def pointer_like_type_ref?(type_ref)
500
498
  return false if type_ref.nullable
501
-
499
+
502
500
  %w[ptr const_ptr ref].include?(type_ref.name.to_s)
503
501
  end
504
502
  # ── directional-ffi-arg ──────────────────────────────────────────────
505
-
503
+
506
504
  def check_directional_ffi_call(expression)
507
505
  call = resolve_directional_ffi_call(expression.callee)
508
506
  return unless call
509
-
507
+
510
508
  call[:params].zip(expression.arguments).each do |parameter, argument|
511
509
  next unless parameter && argument
512
-
510
+
513
511
  passing_mode = parameter_passing_mode(parameter)
514
512
  next unless %i[in out inout].include?(passing_mode)
515
513
  next unless legacy_directional_argument_expression?(argument.value)
516
-
514
+
517
515
  @warnings << Warning.new(
518
516
  path: @path,
519
517
  line: expression_line(argument.value),
@@ -526,7 +524,7 @@ module MilkTea
526
524
  )
527
525
  end
528
526
  end
529
-
527
+
530
528
  def resolve_directional_ffi_call(callee)
531
529
  case callee
532
530
  when AST::Specialization
@@ -535,56 +533,56 @@ module MilkTea
535
533
  if @sema_facts && (binding = @sema_facts.functions[callee.name]) && directional_ffi_binding?(binding)
536
534
  return { name: binding.name, params: binding.type.params }
537
535
  end
538
-
536
+
539
537
  if (declaration = @declared_directional_functions[callee.name])
540
538
  return { name: declaration.name, params: declaration.params }
541
539
  end
542
-
540
+
543
541
  nil
544
542
  when AST::MemberAccess
545
543
  return nil unless callee.receiver.is_a?(AST::Identifier)
546
544
  return nil unless @sema_facts
547
-
545
+
548
546
  imported_module = @sema_facts.imports[callee.receiver.name]
549
547
  return nil unless imported_module
550
-
548
+
551
549
  binding = imported_module.functions[callee.member]
552
550
  return nil unless directional_ffi_binding?(binding)
553
-
551
+
554
552
  { name: binding.name, params: binding.type.params }
555
553
  else
556
554
  nil
557
555
  end
558
556
  end
559
-
557
+
560
558
  def directional_ffi_binding?(binding)
561
559
  return false unless binding
562
560
  return false unless binding.respond_to?(:ast) && (binding.ast.is_a?(AST::ExternFunctionDecl) || binding.ast.is_a?(AST::ForeignFunctionDecl))
563
-
561
+
564
562
  binding.type.params.any? { |parameter| %i[in out inout].include?(parameter.passing_mode) }
565
563
  end
566
-
564
+
567
565
  def parameter_passing_mode(parameter)
568
566
  parameter.respond_to?(:passing_mode) ? parameter.passing_mode : parameter.mode
569
567
  end
570
-
568
+
571
569
  def parameter_name(parameter)
572
570
  parameter.respond_to?(:name) ? parameter.name : "argument"
573
571
  end
574
-
572
+
575
573
  def legacy_directional_argument_expression?(expression)
576
574
  return true if expression.is_a?(AST::UnaryOp) && %w[in out inout].include?(expression.operator)
577
-
575
+
578
576
  if expression.is_a?(AST::Call) && expression.callee.is_a?(AST::Identifier) && %w[ptr_of ref_of].include?(expression.callee.name)
579
577
  return expression.arguments.length == 1
580
578
  end
581
-
579
+
582
580
  cast = pointer_like_cast_expression(expression)
583
581
  return false unless cast
584
-
582
+
585
583
  lvalue_expression?(cast[:source]) || legacy_directional_argument_expression?(cast[:source])
586
584
  end
587
-
585
+
588
586
  def lvalue_expression?(expression)
589
587
  expression.is_a?(AST::Identifier) || expression.is_a?(AST::MemberAccess) || expression.is_a?(AST::IndexAccess)
590
588
  end