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,8 +3,6 @@
3
3
  module MilkTea
4
4
  class Linter
5
5
  module LinterVisitors
6
- private
7
-
8
6
  def visit_source_file(source_file)
9
7
  @declared_callable_names = declared_callable_names(source_file)
10
8
  @declared_directional_functions = declared_directional_functions(source_file)
@@ -29,14 +27,14 @@ module MilkTea
29
27
  end
30
28
  def seed_module_bindings(source_file)
31
29
  @module_bindings = {}
32
-
30
+
33
31
  import_names = source_file.imports.filter_map do |import|
34
32
  import.alias_name || import.path.parts.last
35
33
  end
36
34
  source_file.imports.each do |import|
37
35
  local_name = import.alias_name || import.path.parts.last
38
36
  next if ignored_binding_name?(local_name)
39
-
37
+
40
38
  declare_reserved_import_alias_module_binding(
41
39
  local_name,
42
40
  kind_label: "import alias",
@@ -45,7 +43,7 @@ module MilkTea
45
43
  unavailable_names: import_names,
46
44
  )
47
45
  end
48
-
46
+
49
47
  source_file.declarations.each do |declaration|
50
48
  kind_label = case declaration
51
49
  when AST::FunctionDef, AST::ExternFunctionDecl, AST::ForeignFunctionDecl
@@ -61,7 +59,7 @@ module MilkTea
61
59
  end
62
60
  next unless kind_label
63
61
  next if ignored_binding_name?(declaration.name)
64
-
62
+
65
63
  declare_reserved_primitive_module_binding(
66
64
  declaration.name,
67
65
  kind_label:,
@@ -87,7 +85,7 @@ module MilkTea
87
85
  source_file.declarations.each_with_object({}) do |declaration, functions|
88
86
  next unless declaration.is_a?(AST::ExternFunctionDecl) || declaration.is_a?(AST::ForeignFunctionDecl)
89
87
  next unless declaration.params.any? { |param| %i[in out inout].include?(param.mode) }
90
-
88
+
91
89
  functions[declaration.name] = declaration
92
90
  end
93
91
  end
@@ -134,7 +132,7 @@ module MilkTea
134
132
  terminated = false
135
133
  stmts.each do |stmt|
136
134
  next if terminated # skip visitation only; ControlFlow emits the warning
137
-
135
+
138
136
  visit_statement(stmt)
139
137
  terminated = true if terminator?(stmt)
140
138
  end
@@ -197,14 +195,20 @@ module MilkTea
197
195
  with_scope { visit_statement_list(statement.body) }
198
196
  @unsafe_depth -= 1
199
197
  when AST::ForStmt
198
+ @loop_depth ||= 0
199
+ @loop_depth += 1
200
200
  visit_expression(statement.iterable)
201
201
  with_scope do
202
202
  declare_local(statement.name, statement.line, column: statement.column, var: false) if statement.name
203
203
  visit_statement_list(statement.body)
204
204
  end
205
+ @loop_depth -= 1
205
206
  when AST::WhileStmt
207
+ @loop_depth ||= 0
208
+ @loop_depth += 1
206
209
  visit_expression(statement.condition)
207
210
  with_scope { visit_statement_list(statement.body) }
211
+ @loop_depth -= 1
208
212
  when AST::ReturnStmt
209
213
  visit_expression(statement.value) if statement.value
210
214
  flag_redundant_widening_cast(statement.value) if statement.value
@@ -293,7 +297,7 @@ module MilkTea
293
297
  @unsafe_depth -= 1
294
298
  when AST::ProcExpr
295
299
  with_scope do
296
- fallback_line = expression.respond_to?(:line) ? expression.line : nil
300
+ fallback_line = expression.line
297
301
  expression.params.each do |param|
298
302
  declare_param(
299
303
  param.name,
@@ -319,7 +323,7 @@ module MilkTea
319
323
  when AST::FormatString
320
324
  expression.parts.each do |part|
321
325
  next unless part.is_a?(AST::FormatExprPart)
322
-
326
+
323
327
  visit_expression(part.expression)
324
328
  end
325
329
  when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral, AST::BooleanLiteral, AST::NullLiteral
@@ -331,6 +335,7 @@ module MilkTea
331
335
  when AST::PrefixCast
332
336
  visit_expression(expression.expression)
333
337
  check_redundant_cast(expression)
338
+ check_redundant_cast_parens(expression)
334
339
  else
335
340
  nil
336
341
  end
@@ -363,28 +368,28 @@ module MilkTea
363
368
  end
364
369
  def mark_assignment_target_reads(target, operator)
365
370
  return if operator == "="
366
-
371
+
367
372
  mark_used(target.name, identifier: target) if target.is_a?(AST::Identifier)
368
373
  end
369
374
  def mark_mutated(target)
370
375
  return unless target.is_a?(AST::Identifier) || target.is_a?(AST::IndexAccess) || target.is_a?(AST::MemberAccess)
371
-
376
+
372
377
  # For direct identifier assignment (e.g., x = value), mark x as mutated.
373
378
  if target.is_a?(AST::Identifier)
374
379
  @scopes.reverse_each do |scope|
375
380
  binding = scope[target.name]
376
381
  next unless binding
377
-
382
+
378
383
  binding.mutated = true
379
384
  return
380
385
  end
381
386
  end
382
-
387
+
383
388
  # For index assignment (e.g., array[0] = value), mark the array as mutated.
384
389
  if target.is_a?(AST::IndexAccess)
385
390
  mark_mutated(target.receiver)
386
391
  end
387
-
392
+
388
393
  # For field assignment (e.g., rect.w = value), mark the struct variable as mutated.
389
394
  if target.is_a?(AST::MemberAccess)
390
395
  mark_mutated(target.receiver)
@@ -395,12 +400,12 @@ module MilkTea
395
400
  mark_mutated(expression)
396
401
  return
397
402
  end
398
-
403
+
399
404
  if expression.is_a?(AST::UnaryOp) && %w[out inout].include?(expression.operator)
400
405
  mark_mutated(expression.operand)
401
406
  return
402
407
  end
403
-
408
+
404
409
  # ref_of(x) and ptr_of(x) can expose writable aliases — treat as potential
405
410
  # mutation since callees may write through them (common C-FFI out-param pattern).
406
411
  if expression.is_a?(AST::Call) &&
@@ -412,10 +417,10 @@ module MilkTea
412
417
  end
413
418
  def mutating_argument_identifier?(expression)
414
419
  return false unless expression.is_a?(AST::Identifier)
415
-
420
+
416
421
  binding_resolution = @sema_facts&.binding_resolution
417
422
  return false unless binding_resolution
418
-
423
+
419
424
  binding_resolution.mutating_argument_identifier_ids&.key?(expression.object_id) ||
420
425
  binding_resolution.mutable_lvalue_argument_identifier_ids&.key?(expression.object_id)
421
426
  end
@@ -423,7 +428,7 @@ module MilkTea
423
428
  return unless expression.is_a?(AST::Call)
424
429
  return unless expression.callee.is_a?(AST::MemberAccess)
425
430
  return unless editable_receiver_expression?(expression.callee.receiver)
426
-
431
+
427
432
  mark_mutated(expression.callee.receiver)
428
433
  end
429
434
  def mark_alias_source_mutated(expression)
@@ -431,12 +436,12 @@ module MilkTea
431
436
  return unless expression.callee.is_a?(AST::Identifier)
432
437
  return unless %w[ref_of ptr_of].include?(expression.callee.name)
433
438
  return unless expression.arguments.length == 1
434
-
439
+
435
440
  mark_mutated(expression.arguments.first.value)
436
441
  end
437
442
  def editable_receiver_expression?(expression)
438
443
  return true unless @sema_facts
439
-
444
+
440
445
  @sema_facts&.binding_resolution&.editable_receiver_expression_ids&.key?(expression.object_id)
441
446
  end
442
447
  def with_scope
@@ -447,9 +452,9 @@ module MilkTea
447
452
  end
448
453
  def declare_local(name, line, column: nil, var: false)
449
454
  return if ignored_binding_name?(name)
450
-
455
+
451
456
  resolve_reserved_primitive_name_conflicts!(name)
452
-
457
+
453
458
  replacement_name = nil
454
459
  replacement_base_name = nil
455
460
  if RESERVED_VALUE_TYPE_NAMES.include?(name)
@@ -459,7 +464,7 @@ module MilkTea
459
464
  visible_binding_names(excluding_name: name),
460
465
  )
461
466
  end
462
-
467
+
463
468
  # shadow: check whether any outer scope already has a binding for this name
464
469
  if @scopes.length > 1
465
470
  @scopes[0..-2].each do |outer_scope|
@@ -473,7 +478,7 @@ module MilkTea
473
478
  end
474
479
  end
475
480
  end
476
-
481
+
477
482
  @scopes.last[name] = Binding.new(
478
483
  name:, line:, column:, used: false,
479
484
  binding_kind: :local,
@@ -486,9 +491,9 @@ module MilkTea
486
491
  end
487
492
  def declare_param(name, line: nil, column: nil)
488
493
  return if ignored_binding_name?(name)
489
-
494
+
490
495
  resolve_reserved_primitive_name_conflicts!(name)
491
-
496
+
492
497
  replacement_name = nil
493
498
  replacement_base_name = nil
494
499
  if RESERVED_VALUE_TYPE_NAMES.include?(name)
@@ -498,7 +503,7 @@ module MilkTea
498
503
  visible_binding_names(excluding_name: name),
499
504
  )
500
505
  end
501
-
506
+
502
507
  @scopes.last[name] = Binding.new(
503
508
  name:, line:, column:, used: false,
504
509
  binding_kind: :param,
@@ -776,6 +781,7 @@ module MilkTea
776
781
  return unless stmts.all? { |s| inline_simple_statement?(s) }
777
782
 
778
783
  return if visually_inline_if?(statement, stmts)
784
+ return if inline_if_too_wide?(statement, stmts)
779
785
 
780
786
  emit_conciseness_hint(
781
787
  "prefer-inline-if",
@@ -785,12 +791,57 @@ module MilkTea
785
791
  )
786
792
  end
787
793
 
794
+ def inline_if_too_wide?(statement, stmts)
795
+ return false if statement.branches.empty?
796
+
797
+ text = +""
798
+
799
+ statement.branches.each_with_index do |b, i|
800
+ cond_src = source_line_from(b.line, b.column)
801
+ return false unless cond_src
802
+
803
+ stmt_col = stmts[i]&.column
804
+ stmt_line = stmts[i]&.line
805
+ body_src = source_line_from(stmt_line, stmt_col)
806
+ return false unless body_src
807
+
808
+ text << cond_src
809
+ text << " "
810
+ text << body_src
811
+ text << " "
812
+ end
813
+
814
+ if statement.else_line
815
+ else_src = source_line_from(statement.else_line, statement.else_column)
816
+ return false unless else_src
817
+ text << else_src
818
+ text << " "
819
+ end
820
+
821
+ stmt_col = stmts.last&.column
822
+ stmt_line = stmts.last&.line
823
+ else_body = source_line_from(stmt_line, stmt_col)
824
+ return false unless else_body
825
+ text << else_body
826
+
827
+ text.strip.length > 120
828
+ end
829
+
830
+ def source_line_from(line, column)
831
+ return nil unless line
832
+ line_idx = Integer(line) - 1
833
+ return nil if line_idx < 0 || line_idx >= @source_lines.length
834
+ raw = @source_lines[line_idx].to_s
835
+ return raw.strip if column.nil?
836
+ raw[Integer(column) - 1..]&.strip
837
+ end
838
+
788
839
  def visually_inline_if?(statement, stmts)
789
840
  n = statement.branches.length
790
841
  statement.branches.each_with_index.all? { |b, i|
791
- stmts[i].respond_to?(:line) && stmts[i].line == b.line
842
+ stmts[i].line == b.line
792
843
  } && (
793
- stmts[n].respond_to?(:line) && statement.else_line && stmts[n].line == statement.else_line
844
+ stmts[n].line && statement.else_line && stmts[n].line == statement.else_line
794
845
  )
795
846
  end
796
847
 
@@ -801,6 +852,7 @@ module MilkTea
801
852
  return unless statement.else_body
802
853
 
803
854
  stmts = (statement.branches.map(&:body) + [statement.else_body]).map { |b| single_statement_body(b) }
855
+ return if inline_if_too_wide?(statement, stmts)
804
856
  report_conditional_expression(stmts, line: statement.line, column: statement.branches.first&.column, kind: "if")
805
857
  end
806
858
 
@@ -814,6 +866,7 @@ module MilkTea
814
866
 
815
867
  def report_conditional_expression(stmts, line:, column:, kind:)
816
868
  return if stmts.empty? || stmts.any?(&:nil?)
869
+ return if @loop_depth && @loop_depth > 0
817
870
 
818
871
  if stmts.all? { |s| s.is_a?(AST::ReturnStmt) && s.value }
819
872
  emit_conciseness_hint(
@@ -843,8 +896,8 @@ module MilkTea
843
896
  next unless node_fingerprint(body_of.call(first)) == node_fingerprint(body_of.call(second))
844
897
 
845
898
  pattern = second.pattern
846
- pattern_line = pattern.respond_to?(:line) ? pattern.line : second.binding_line
847
- pattern_column = pattern.respond_to?(:column) ? pattern.column : second.binding_column
899
+ pattern_line = pattern.line || second.binding_line
900
+ pattern_column = pattern.column || second.binding_column
848
901
  emit_conciseness_hint(
849
902
  "prefer-or-pattern",
850
903
  line: pattern_line,
@@ -881,8 +934,8 @@ module MilkTea
881
934
  source_text = expr_source_name(copied.first.value.receiver)
882
935
  emit_conciseness_hint(
883
936
  "prefer-struct-with",
884
- line: call.callee.respond_to?(:line) ? call.callee.line : nil,
885
- column: call.callee.respond_to?(:column) ? call.callee.column : nil,
937
+ line: call.callee.line,
938
+ column: call.callee.column,
886
939
  message: "copies #{copied.size} field(s) from `#{source_text}`; use `#{source_text}.with(#{changed_names.join(", ")} = …)`",
887
940
  )
888
941
  end
@@ -914,8 +967,8 @@ module MilkTea
914
967
 
915
968
  emit_conciseness_hint(
916
969
  "prefer-try",
917
- line: scrutinee.respond_to?(:line) ? scrutinee.line : nil,
918
- column: scrutinee.respond_to?(:column) ? scrutinee.column : nil,
970
+ line: scrutinee.line,
971
+ column: scrutinee.column,
919
972
  message: "this #{base} match only propagates the failure branch; consider `expr?`",
920
973
  )
921
974
  end
@@ -991,6 +1044,65 @@ module MilkTea
991
1044
  # check_boundary_widening_cast at the specific boundary sites.
992
1045
  end
993
1046
 
1047
+ # ── redundant cast parentheses ────────────────────────────────────────────────────
1048
+ # `T<-(x)` when `T<-x` would be equivalent because the inner expression needs
1049
+ # no precedence protection. Parens are required when the inner expression
1050
+ # contains a binary operator, range, or inline conditional.
1051
+
1052
+ def check_redundant_cast_parens(cast)
1053
+
1054
+ line_idx = Integer(cast.line) - 1
1055
+ return if line_idx < 0 || line_idx >= @source_lines.length
1056
+
1057
+ line = @source_lines[line_idx].to_s
1058
+ col = Integer(cast.column) - 1
1059
+ arrow = line.index("<-", col)
1060
+ return unless arrow
1061
+ arrow_end = arrow + 2
1062
+ return if arrow_end >= line.length
1063
+ return unless line[arrow_end] == "("
1064
+
1065
+ close = match_paren(line, arrow_end)
1066
+ return unless close
1067
+
1068
+ inner = line[arrow_end + 1...close].strip
1069
+ return if inner.empty?
1070
+ return if inner_has_top_level_operators?(inner)
1071
+
1072
+ emit_redundant_cast(cast, type_ref_name(cast.target_type) || "",
1073
+ "parentheses around cast expression are redundant")
1074
+ end
1075
+
1076
+ def match_paren(line, open_pos)
1077
+ depth = 0
1078
+ (open_pos...line.length).each do |i|
1079
+ case line[i]
1080
+ when "(" then depth += 1
1081
+ when ")" then depth -= 1; return i if depth == 0
1082
+ end
1083
+ end
1084
+ nil
1085
+ end
1086
+
1087
+ BINARY_OP_CHARS = %w[+ - * / % & | ^ < > = ! ? :]
1088
+
1089
+ def inner_has_top_level_operators?(inner)
1090
+ depth = 0
1091
+ i = 0
1092
+ while i < inner.length
1093
+ case inner[i]
1094
+ when "(", "[", "{"
1095
+ depth += 1
1096
+ when ")", "]", "}"
1097
+ depth -= 1 if depth > 0
1098
+ when *BINARY_OP_CHARS
1099
+ return true if depth == 0
1100
+ end
1101
+ i += 1
1102
+ end
1103
+ false
1104
+ end
1105
+
994
1106
  # Reports a widening cast as redundant only when it sits *directly* at a
995
1107
  # coercion slot: the RHS of a typed `let`/`var`, a `return` value, or the
996
1108
  # RHS of a plain `=` assignment. At those positions the slot type is
@@ -1034,8 +1146,8 @@ module MilkTea
1034
1146
  end
1035
1147
 
1036
1148
  def emit_redundant_cast(cast_expr, target_name, reason)
1037
- line = cast_expr.respond_to?(:line) ? cast_expr.line : expression_line(cast_expr)
1038
- column = cast_expr.respond_to?(:column) ? cast_expr.column : expression_column(cast_expr)
1149
+ line = cast_expr.line || expression_line(cast_expr)
1150
+ column = cast_expr.column || expression_column(cast_expr)
1039
1151
 
1040
1152
  @warnings << Warning.new(
1041
1153
  path: @path,
@@ -1092,15 +1204,15 @@ module MilkTea
1092
1204
  end
1093
1205
  end
1094
1206
  # ── borrow facts helpers ──────────────────────────────────────────────────
1095
-
1207
+
1096
1208
  BORROW_CALL_NAMES = %w[ref_of ptr_of].freeze
1097
-
1209
+
1098
1210
  def collect_borrowed_names(stmts)
1099
1211
  names = Set.new
1100
1212
  stmts.each { |s| collect_borrows_from_stmt(s, names) }
1101
1213
  names
1102
1214
  end
1103
-
1215
+
1104
1216
  def collect_borrows_from_stmt(stmt, names)
1105
1217
  case stmt
1106
1218
  when AST::LocalDecl
@@ -1136,7 +1248,7 @@ module MilkTea
1136
1248
  stmt.else_body&.each { |s| collect_borrows_from_stmt(s, names) }
1137
1249
  end
1138
1250
  end
1139
-
1251
+
1140
1252
  def collect_borrows_from_expr(expr, names, inside_call_argument: false)
1141
1253
  case expr
1142
1254
  when nil then nil
@@ -1168,13 +1280,13 @@ module MilkTea
1168
1280
  collect_borrows_from_expr(expr.index, names, inside_call_argument:)
1169
1281
  end
1170
1282
  end
1171
-
1283
+
1172
1284
  def collect_written_names(stmts)
1173
1285
  names = Set.new
1174
1286
  stmts.each { |s| collect_writes_from_stmt(s, names) }
1175
1287
  names
1176
1288
  end
1177
-
1289
+
1178
1290
  def collect_writes_from_stmt(stmt, names)
1179
1291
  case stmt
1180
1292
  when AST::Assignment
@@ -1198,15 +1310,15 @@ module MilkTea
1198
1310
  stmt.body&.each { |s| collect_writes_from_stmt(s, names) }
1199
1311
  end
1200
1312
  end
1201
-
1313
+
1202
1314
  def collect_writes_from_stmt_list(stmts, names)
1203
1315
  stmts.each { |s| collect_writes_from_stmt(s, names) }
1204
1316
  end
1205
-
1317
+
1206
1318
  def stmt_sub_stmts(_stmt)
1207
1319
  []
1208
1320
  end
1209
-
1321
+
1210
1322
  def find_borrow_location(stmts, name)
1211
1323
  stmts.each do |stmt|
1212
1324
  location = find_borrow_location_in_stmt(stmt, name)
@@ -1214,7 +1326,7 @@ module MilkTea
1214
1326
  end
1215
1327
  nil
1216
1328
  end
1217
-
1329
+
1218
1330
  def find_borrow_location_in_stmt(stmt, name)
1219
1331
  case stmt
1220
1332
  when AST::LocalDecl
@@ -1266,7 +1378,7 @@ module MilkTea
1266
1378
  nil
1267
1379
  end
1268
1380
  end
1269
-
1381
+
1270
1382
  def find_borrow_location_in_expr(expr, name)
1271
1383
  case expr
1272
1384
  when nil then nil
@@ -783,8 +783,6 @@ module MilkTea
783
783
  @reserved_primitive_name_fixes
784
784
  end
785
785
 
786
- private
787
-
788
786
  include LinterDocTags
789
787
  include LinterFlowRules
790
788
  include LinterImportsPlatform
@@ -31,8 +31,6 @@ module MilkTea
31
31
  )
32
32
  end
33
33
 
34
- private
35
-
36
34
  def validate_target!
37
35
  if File.exist?(@path) && !File.directory?(@path)
38
36
  raise ProjectScaffoldError, "project path is not a directory: #{@path}"
@@ -87,8 +87,6 @@ module MilkTea
87
87
  restore_signal_handlers(previous_handlers) if previous_handlers
88
88
  end
89
89
 
90
- private
91
-
92
90
  def handle_client(socket)
93
91
  request_line = socket.gets
94
92
  return unless request_line
@@ -38,8 +38,6 @@ module MilkTea
38
38
  end
39
39
  end
40
40
 
41
- private
42
-
43
41
  def extract_options!
44
42
  remaining = []
45
43
  until @argv.empty?
data/std/cli.mt CHANGED
@@ -485,11 +485,8 @@ function parse_long_option_impl(
485
485
 
486
486
  match inline_value:
487
487
  Option.some as inline_payload:
488
- match set_option_value(result, spec, inline_payload.value):
489
- Result.failure as set_error:
490
- return Result[ptr_uint, Error].failure(error= set_error.error)
491
- Result.success:
492
- return Result[ptr_uint, Error].success(value= consumed)
488
+ let _ = set_option_value(result, spec, inline_payload.value)?
489
+ return Result[ptr_uint, Error].success(value= consumed)
493
490
  Option.none:
494
491
  pass
495
492
 
@@ -497,12 +494,9 @@ function parse_long_option_impl(
497
494
  let missing = fmt.format(f"--#{spec.long_name}")
498
495
  return Result[ptr_uint, Error].failure(error= require_value_arg(missing.as_str()))
499
496
 
500
- match set_option_value(result, spec, value_arg):
501
- Result.failure as payload:
502
- return Result[ptr_uint, Error].failure(error= payload.error)
503
- Result.success:
504
- consumed = 2z
505
- return Result[ptr_uint, Error].success(value= consumed)
497
+ let _ = set_option_value(result, spec, value_arg)?
498
+ consumed = 2z
499
+ return Result[ptr_uint, Error].success(value= consumed)
506
500
 
507
501
  match inline_value:
508
502
  Option.some:
@@ -510,11 +504,8 @@ function parse_long_option_impl(
510
504
  Option.none:
511
505
  pass
512
506
 
513
- match set_option_flag(result, spec):
514
- Result.failure as payload:
515
- return Result[ptr_uint, Error].failure(error= payload.error)
516
- Result.success:
517
- return Result[ptr_uint, Error].success(value= 1z)
507
+ let _ = set_option_flag(result, spec)?
508
+ return Result[ptr_uint, Error].success(value= 1z)
518
509
 
519
510
 
520
511
  function parse_short_option_impl(
@@ -537,28 +528,18 @@ function parse_short_option_impl(
537
528
 
538
529
  if short_index + 1 < arg.len:
539
530
  let value = arg.slice(short_index + 1, arg.len - short_index - 1)
540
- match set_option_value(result, spec, value):
541
- Result.failure as payload:
542
- return Result[ptr_uint, Error].failure(error= payload.error)
543
- Result.success:
544
- return Result[ptr_uint, Error].success(value= consumed)
531
+ let _ = set_option_value(result, spec, value)?
532
+ return Result[ptr_uint, Error].success(value= consumed)
545
533
 
546
534
  let value_arg = next_arg else:
547
535
  let missing = fmt.format(f"-#{short_name}")
548
536
  return Result[ptr_uint, Error].failure(error= require_value_arg(missing.as_str()))
549
537
 
550
- match set_option_value(result, spec, value_arg):
551
- Result.failure as payload:
552
- return Result[ptr_uint, Error].failure(error= payload.error)
553
- Result.success:
554
- consumed = 2z
555
- return Result[ptr_uint, Error].success(value= consumed)
538
+ let _ = set_option_value(result, spec, value_arg)?
539
+ consumed = 2z
540
+ return Result[ptr_uint, Error].success(value= consumed)
556
541
 
557
- match set_option_flag(result, spec):
558
- Result.failure as payload:
559
- return Result[ptr_uint, Error].failure(error= payload.error)
560
- Result.success:
561
- pass
542
+ let _ = set_option_flag(result, spec)?
562
543
 
563
544
  short_index += 1
564
545