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
@@ -9,9 +9,7 @@ module MilkTea
9
9
  FormatString FormatTextPart FormatExprPart
10
10
  ].to_set.freeze
11
11
 
12
- module_function
13
-
14
- def format_all(content:, tokens:, ast:, parse_errors:, facts:, snapshot:, path:, semantic_entries: nil)
12
+ def self.format_all(content:, tokens:, ast:, parse_errors:, facts:, snapshot:, path:, semantic_entries: nil)
15
13
  sections = []
16
14
  sections << format_header(content, path)
17
15
  sections << format_tokens(tokens, semantic_entries:)
@@ -25,7 +23,7 @@ module MilkTea
25
23
 
26
24
  # ── Header ────────────────────────────────────────────────────────────────────
27
25
 
28
- def format_header(content, path)
26
+ def self.format_header(content, path)
29
27
  lines = content.count("\n") + 1
30
28
  bytes = content.bytesize
31
29
  ["File: #{path}", "Content: #{lines} lines, #{bytes} bytes"].join("\n")
@@ -33,7 +31,7 @@ module MilkTea
33
31
 
34
32
  # ── Tokens ─────────────────────────────────────────────────────────────────────
35
33
 
36
- def format_tokens(tokens, semantic_entries: nil)
34
+ def self.format_tokens(tokens, semantic_entries: nil)
37
35
  return '── Tokens 0 ──' if tokens.nil? || tokens.empty?
38
36
 
39
37
  max_len = tokens.reject { |t| [:newline, :indent, :dedent].include?(t.type) }
@@ -69,7 +67,7 @@ module MilkTea
69
67
  lines.join("\n")
70
68
  end
71
69
 
72
- def build_sema_map(entries)
70
+ def self.build_sema_map(entries)
73
71
  return {} unless entries.is_a?(Array) && !entries.empty?
74
72
 
75
73
  entries.each_with_object({}) do |e, map|
@@ -80,7 +78,7 @@ module MilkTea
80
78
 
81
79
  # ── AST ────────────────────────────────────────────────────────────────────────
82
80
 
83
- def format_ast(ast)
81
+ def self.format_ast(ast)
84
82
  return '── AST (not available) ──' unless ast
85
83
 
86
84
  counts = %w[imports directives declarations].filter_map do |m|
@@ -93,7 +91,7 @@ module MilkTea
93
91
  buf.join("\n")
94
92
  end
95
93
 
96
- def format_ast_node(node, prefix:, is_last:, buf:)
94
+ def self.format_ast_node(node, prefix:, is_last:, buf:)
97
95
  return if node.nil?
98
96
 
99
97
  if node.is_a?(Array)
@@ -152,11 +150,11 @@ module MilkTea
152
150
  end
153
151
  end
154
152
 
155
- def compact_ast_node?(node)
153
+ def self.compact_ast_node?(node)
156
154
  COMPACT_AST_TYPES.include?(node.class.name.split('::').last)
157
155
  end
158
156
 
159
- def compact_ast_repr(node)
157
+ def self.compact_ast_repr(node)
160
158
  kind = node.class.name.split('::').last
161
159
  case kind
162
160
  when 'QualifiedName'
@@ -197,17 +195,17 @@ module MilkTea
197
195
  end
198
196
  end
199
197
 
200
- def type_arg_str(arg)
198
+ def self.type_arg_str(arg)
201
199
  return value_repr(arg) unless arg.respond_to?(:value)
202
200
 
203
201
  val = arg.value
204
202
  ast_node?(val) && compact_ast_node?(val) ? compact_ast_repr(val) : value_repr(val)
205
203
  end
206
204
 
207
- def ast_node_header(node)
205
+ def self.ast_node_header(node)
208
206
  name_attr = node.respond_to?(:name) ? node.name : nil
209
- loc = if node.respond_to?(:line) && node.line
210
- col = node.respond_to?(:column) && node.column ? ", Col #{node.column}" : ''
207
+ loc = if node.line
208
+ col = node.column ? ", Col #{node.column}" : ''
211
209
  " [Ln #{node.line}#{col}]"
212
210
  else
213
211
  ''
@@ -216,21 +214,21 @@ module MilkTea
216
214
  name_attr ? "#{kind} ◆ #{name_attr}#{loc}" : "#{kind}#{loc}"
217
215
  end
218
216
 
219
- def ast_node?(val)
217
+ def self.ast_node?(val)
220
218
  val.class.name.to_s.start_with?('MilkTea::AST::')
221
219
  rescue StandardError
222
220
  false
223
221
  end
224
222
 
225
- def array_of_ast_nodes?(val)
223
+ def self.array_of_ast_nodes?(val)
226
224
  val.is_a?(Array) && val.any? { |e| ast_node?(e) }
227
225
  end
228
226
 
229
- def skip_child_value?(val)
227
+ def self.skip_child_value?(val)
230
228
  val.nil? || val == false || val == [] || val == {} || val == :private
231
229
  end
232
230
 
233
- def keep_child?(val)
231
+ def self.keep_child?(val)
234
232
  return false if skip_child_value?(val)
235
233
  return true if val.is_a?(Array) && val.any?
236
234
  return true if ast_node?(val) || val.class.name.to_s.start_with?('MilkTea::')
@@ -238,19 +236,19 @@ module MilkTea
238
236
  true
239
237
  end
240
238
 
241
- def symbol_icon(count)
239
+ def self.symbol_icon(count)
242
240
  count > 0 ? '◆' : '◇'
243
241
  end
244
242
 
245
- def compact_array_repr(arr)
243
+ def self.compact_array_repr(arr)
246
244
  arr.map { |e| value_repr(e) }.join(', ')
247
245
  end
248
246
 
249
- def simple_value_array?(arr)
247
+ def self.simple_value_array?(arr)
250
248
  arr.all? { |e| e.is_a?(String) || e.is_a?(Symbol) || e.is_a?(Numeric) || e.nil? }
251
249
  end
252
250
 
253
- def value_repr(val)
251
+ def self.value_repr(val)
254
252
  case val
255
253
  when nil then 'nil'
256
254
  when true then 'true'
@@ -275,13 +273,13 @@ module MilkTea
275
273
 
276
274
  # ── Parse Errors ────────────────────────────────────────────────────────────────
277
275
 
278
- def format_parse_errors(errors)
276
+ def self.format_parse_errors(errors)
279
277
  return '── Parse Errors 0 ──' if errors.nil? || errors.empty?
280
278
 
281
279
  buf = ["── Parse Errors #{errors.length} " + '─' * 55]
282
280
  errors.each do |e|
283
- line = e.respond_to?(:line) ? e.line : '?'
284
- col = e.respond_to?(:column) ? e.column : '?'
281
+ line = e.line || '?'
282
+ col = e.column || '?'
285
283
  buf << " ERROR [Ln #{line}, Col #{col}]: #{e.message}"
286
284
  end
287
285
  buf.join("\n")
@@ -289,7 +287,7 @@ module MilkTea
289
287
 
290
288
  # ── Facts ───────────────────────────────────────────────────────────────────────
291
289
 
292
- def format_facts(facts)
290
+ def self.format_facts(facts)
293
291
  return '── Facts (not available) ──' unless facts
294
292
 
295
293
  counts = []
@@ -356,7 +354,7 @@ module MilkTea
356
354
  buf.join("\n")
357
355
  end
358
356
 
359
- def format_function_binding(name, binding, indent:)
357
+ def self.format_function_binding(name, binding, indent:)
360
358
  buf = []
361
359
  buf << "#{indent}#{name}"
362
360
  buf << "#{indent} type: #{type_repr(binding.type)}"
@@ -377,14 +375,14 @@ module MilkTea
377
375
  buf
378
376
  end
379
377
 
380
- def type_repr(type)
378
+ def self.type_repr(type)
381
379
  return 'nil' unless type
382
380
  return type.to_s if type.respond_to?(:to_s)
383
381
 
384
382
  type.class.name&.split('::')&.last || type.class.to_s
385
383
  end
386
384
 
387
- def value_binding_repr(vb)
385
+ def self.value_binding_repr(vb)
388
386
  mutable = vb.mutable ? 'mut' : ' '
389
387
  kind = vb.kind.to_s
390
388
  type = vb.flow_type || vb.storage_type
@@ -393,7 +391,7 @@ module MilkTea
393
391
 
394
392
  # ── Bindings ────────────────────────────────────────────────────────────────────
395
393
 
396
- def format_bindings(facts)
394
+ def self.format_bindings(facts)
397
395
  return '── Bindings (not available) ──' unless facts&.binding_resolution
398
396
 
399
397
  res = facts.binding_resolution
@@ -427,7 +425,7 @@ module MilkTea
427
425
 
428
426
  # ── Diagnostics ─────────────────────────────────────────────────────────────────
429
427
 
430
- def format_diagnostics(snapshot)
428
+ def self.format_diagnostics(snapshot)
431
429
  return '── Diagnostics (not available) ──' unless snapshot
432
430
 
433
431
  diags = snapshot.diagnostics
@@ -445,8 +443,8 @@ module MilkTea
445
443
  diags.each do |d|
446
444
  severity = d.respond_to?(:severity) ? d.severity.to_s.upcase : '?'
447
445
  code = d.respond_to?(:code) ? d.code : '?'
448
- line = d.respond_to?(:line) ? d.line : '?'
449
- col = d.respond_to?(:column) ? d.column : '?'
446
+ line = d.line || '?'
447
+ col = d.column || '?'
450
448
  msg = d.respond_to?(:message) ? d.message : d.to_s
451
449
  buf << " #{severity[0]} #{severity.ljust(7)} #{code} [Ln #{line}, Col #{col}]: #{msg}"
452
450
  end
@@ -19,7 +19,7 @@ module MilkTea
19
19
  end
20
20
  end
21
21
 
22
- Function = Data.define(:name, :linkage_name, :source_path, :line, :params, :locals) do
22
+ Function = Data.define(:name, :linkage_name, :path, :line, :params, :locals) do
23
23
  def to_h(base_dir: nil)
24
24
  payload = {
25
25
  "name" => name,
@@ -27,7 +27,7 @@ module MilkTea
27
27
  "params" => params.map(&:to_h),
28
28
  "locals" => locals.map(&:to_h),
29
29
  }
30
- serialized_source_path = DebugMap.path_for_payload(source_path, base_dir)
30
+ serialized_source_path = DebugMap.path_for_payload(self.path, base_dir)
31
31
  payload["sourcePath"] = serialized_source_path if serialized_source_path
32
32
  payload["line"] = line if line
33
33
  payload
@@ -57,7 +57,7 @@ module MilkTea
57
57
  Function.new(
58
58
  name: function.fetch("name"),
59
59
  linkage_name: function.fetch("cName"),
60
- source_path: path_from_payload(function["sourcePath"], base_dir),
60
+ path: path_from_payload(function["sourcePath"], base_dir),
61
61
  line: function["line"],
62
62
  params: load_entries(function["params"]),
63
63
  locals: load_entries(function["locals"])
@@ -82,18 +82,18 @@ module MilkTea
82
82
 
83
83
  def self.from_ir(ir_program, binary_path:)
84
84
  functions = ir_program.functions.map do |function|
85
- source_path = first_statement_source_path(function.body) || ir_program.source_path
85
+ source_path = first_statement_source_path(function.body) || ir_program.path
86
86
  Function.new(
87
87
  name: function.name.to_s,
88
88
  linkage_name: function.linkage_name.to_s,
89
- source_path: source_path ? File.expand_path(source_path) : nil,
89
+ path: source_path ? File.expand_path(source_path) : nil,
90
90
  line: first_statement_line(function.body),
91
91
  params: function.params.map { |param| Entry.new(name: param.name.to_s, linkage_name: param.linkage_name.to_s, line: nil) },
92
92
  locals: collect_locals(function.body)
93
93
  )
94
94
  end
95
95
 
96
- new(binary_path:, program_source_path: ir_program.source_path, functions:)
96
+ new(binary_path:, program_source_path: ir_program.path, functions:)
97
97
  end
98
98
 
99
99
  def write(path)
@@ -165,8 +165,6 @@ module MilkTea
165
165
  end
166
166
 
167
167
  class << self
168
- private
169
-
170
168
  def load_entries(entries)
171
169
  Array(entries).map do |entry|
172
170
  Entry.new(name: entry.fetch("name"), linkage_name: entry.fetch("cName"), line: entry["line"])
@@ -199,8 +197,8 @@ module MilkTea
199
197
 
200
198
  def first_statement_source_path(statements)
201
199
  Array(statements).each do |statement|
202
- if statement.respond_to?(:source_path) && statement.source_path
203
- return statement.source_path
200
+ if statement.respond_to?(:path) && statement.path
201
+ return statement.path
204
202
  end
205
203
 
206
204
  nested = nested_statements(statement)
@@ -88,8 +88,6 @@ module MilkTea
88
88
  html
89
89
  end
90
90
 
91
- private
92
-
93
91
  def snapshot_script_path
94
92
  return @snapshot_script_path if defined?(@snapshot_script_path)
95
93
 
@@ -3,14 +3,12 @@
3
3
  module MilkTea
4
4
  class Linter
5
5
  module LinterDocTags
6
- private
7
-
8
6
  def emit_doc_tag_warnings(source_file)
9
7
  return if @source_lines.empty?
10
-
8
+
11
9
  each_doc_comment_definition(source_file) do |declaration, doc_lines|
12
10
  parsed = parse_doc_tag_block(doc_lines)
13
-
11
+
14
12
  parsed[:errors].each do |error|
15
13
  @warnings << Warning.new(
16
14
  path: @path,
@@ -22,7 +20,7 @@ module MilkTea
22
20
  severity: :hint,
23
21
  )
24
22
  end
25
-
23
+
26
24
  validate_doc_tags_for_declaration(declaration, parsed[:tags])
27
25
  end
28
26
  end
@@ -35,34 +33,34 @@ module MilkTea
35
33
  declaration
36
34
  end
37
35
  end
38
-
36
+
39
37
  declarations.each do |declaration|
40
38
  docs = collect_doc_comment_block_for_line(declaration.line)
41
39
  next if docs.nil? || docs.empty?
42
-
40
+
43
41
  yield declaration, docs
44
42
  end
45
43
  end
46
-
44
+
47
45
  def collect_doc_comment_block_for_line(line)
48
46
  index = line.to_i - 2
49
47
  return nil if index.negative?
50
-
48
+
51
49
  docs = []
52
50
  while index >= 0
53
51
  stripped = @source_lines[index].to_s.strip
54
52
  break if stripped.empty?
55
53
  break unless stripped.start_with?('##')
56
-
54
+
57
55
  docs << {
58
56
  line: index + 1,
59
57
  text: stripped.sub(/\A##\s?/, ''),
60
58
  }
61
59
  index -= 1
62
60
  end
63
-
61
+
64
62
  return nil if docs.empty?
65
-
63
+
66
64
  docs.reverse
67
65
  end
68
66
  def parse_doc_tag_block(lines)
@@ -73,16 +71,16 @@ module MilkTea
73
71
  see: [],
74
72
  }
75
73
  errors = []
76
-
74
+
77
75
  lines.each do |entry|
78
76
  text = entry[:text].to_s
79
77
  match = text.match(/\A\s*@([A-Za-z_][A-Za-z0-9_-]*)(?:\s+(.*))?\z/)
80
78
  next unless match
81
-
79
+
82
80
  tag = match[1].to_s.downcase
83
81
  payload = match[2].to_s.strip
84
82
  column = doc_tag_column(entry[:line])
85
-
83
+
86
84
  case tag
87
85
  when 'param'
88
86
  if payload.empty?
@@ -94,7 +92,7 @@ module MilkTea
94
92
  }
95
93
  next
96
94
  end
97
-
95
+
98
96
  name_match = payload.match(/\A([A-Za-z_][A-Za-z0-9_]*)(?:\s+(.*))?\z/)
99
97
  unless name_match
100
98
  errors << {
@@ -105,7 +103,7 @@ module MilkTea
105
103
  }
106
104
  next
107
105
  end
108
-
106
+
109
107
  tags[:params] << {
110
108
  name: name_match[1],
111
109
  text: name_match[2].to_s.strip,
@@ -139,7 +137,7 @@ module MilkTea
139
137
  }
140
138
  end
141
139
  end
142
-
140
+
143
141
  {
144
142
  tags: tags,
145
143
  errors: errors,
@@ -149,7 +147,7 @@ module MilkTea
149
147
  callable = declaration.respond_to?(:params) && declaration.respond_to?(:return_type)
150
148
  param_tags = Array(tags[:params])
151
149
  return_tag = tags[:returns]
152
-
150
+
153
151
  unless callable
154
152
  ([*param_tags, return_tag].compact).each do |tag_entry|
155
153
  @warnings << Warning.new(
@@ -164,14 +162,14 @@ module MilkTea
164
162
  end
165
163
  return
166
164
  end
167
-
165
+
168
166
  params = Array(declaration.params)
169
167
  param_names = params.map(&:name)
170
168
  param_name_set = param_names.to_set
171
-
169
+
172
170
  param_tags.each do |tag_entry|
173
171
  next if param_name_set.include?(tag_entry[:name])
174
-
172
+
175
173
  @warnings << Warning.new(
176
174
  path: @path,
177
175
  line: tag_entry[:line],
@@ -183,12 +181,12 @@ module MilkTea
183
181
  symbol_name: tag_entry[:name],
184
182
  )
185
183
  end
186
-
184
+
187
185
  return unless return_tag
188
-
186
+
189
187
  return_type_text = declaration.return_type.to_s
190
188
  return unless return_type_text == 'void'
191
-
189
+
192
190
  @warnings << Warning.new(
193
191
  path: @path,
194
192
  line: return_tag[:line],
@@ -199,12 +197,12 @@ module MilkTea
199
197
  severity: :hint,
200
198
  )
201
199
  end
202
-
200
+
203
201
  def doc_tag_column(line)
204
202
  text = @source_lines[line.to_i - 1].to_s
205
203
  at_index = text.index('@')
206
204
  return 1 if at_index.nil?
207
-
205
+
208
206
  at_index + 1
209
207
  end
210
208
  end
@@ -5,9 +5,8 @@ module MilkTea
5
5
  FixEdit = Data.define(:start_line, :start_char, :end_line, :end_char, :new_text)
6
6
 
7
7
  module FixEngine
8
- module_function
9
8
 
10
- def edits_for_rule(code, lines, warning)
9
+ def self.edits_for_rule(code, lines, warning)
11
10
  case code
12
11
  when "prefer-let" then prefer_let_edits(lines, warning)
13
12
  when "redundant-ignored-match-binding" then redundant_ignored_match_binding_edits(lines, warning)
@@ -24,8 +23,8 @@ module MilkTea
24
23
  end
25
24
  end
26
25
 
27
- def apply_fix_edits(lines, edits)
28
- edits.sort_by { |e| [-e.start_line, -e.start_char] }.reverse_each do |edit|
26
+ def self.apply_fix_edits(lines, edits)
27
+ edits.sort_by { |e| [-e.start_line, -e.start_char] }.each do |edit|
29
28
  if edit.start_line == edit.end_line
30
29
  line = lines[edit.start_line]
31
30
  next unless line
@@ -40,7 +39,7 @@ module MilkTea
40
39
  lines
41
40
  end
42
41
 
43
- def edits_to_lsp_text_edits(edits, uri)
42
+ def self.edits_to_lsp_text_edits(edits, uri)
44
43
  edits.map do |edit|
45
44
  {
46
45
  range: {
@@ -54,7 +53,7 @@ module MilkTea
54
53
 
55
54
  # ── per-rule edit generators ──────────────────────────────────────────
56
55
 
57
- def prefer_let_edits(lines, warning)
56
+ def self.prefer_let_edits(lines, warning)
58
57
  return [] unless warning.line
59
58
 
60
59
  line_idx = warning.line - 1
@@ -65,7 +64,7 @@ module MilkTea
65
64
  [FixEdit.new(start_line: line_idx, start_char: 0, end_line: line_idx + 1, end_char: 0, new_text: new_line)]
66
65
  end
67
66
 
68
- def redundant_ignored_match_binding_edits(lines, warning)
67
+ def self.redundant_ignored_match_binding_edits(lines, warning)
69
68
  return [] unless warning.line && warning.column
70
69
 
71
70
  line_idx = warning.line - 1
@@ -78,7 +77,7 @@ module MilkTea
78
77
  [FixEdit.new(start_line: line_idx, start_char: span[:start_char], end_line: line_idx, end_char: span[:end_char], new_text: "")]
79
78
  end
80
79
 
81
- def prefer_let_else_edits(lines, warning)
80
+ def self.prefer_let_else_edits(lines, warning)
82
81
  return [] unless warning.line
83
82
 
84
83
  fix = Linter.build_prefer_let_else_fix(lines, warning.line - 1, symbol_name: warning.symbol_name)
@@ -87,11 +86,11 @@ module MilkTea
87
86
  [FixEdit.new(start_line: fix[:start_line_idx], start_char: 0, end_line: fix[:end_line_idx] + 1, end_char: 0, new_text: fix[:new_text])]
88
87
  end
89
88
 
90
- def prefer_var_else_edits(lines, warning)
89
+ def self.prefer_var_else_edits(lines, warning)
91
90
  prefer_let_else_edits(lines, warning)
92
91
  end
93
92
 
94
- def redundant_bool_compare_edits(lines, warning)
93
+ def self.redundant_bool_compare_edits(lines, warning)
95
94
  return [] unless warning.line && warning.column && warning.length
96
95
 
97
96
  line_idx = warning.line - 1
@@ -109,7 +108,7 @@ module MilkTea
109
108
  [FixEdit.new(start_line: line_idx, start_char:, end_line: line_idx, end_char:, new_text: replacement)]
110
109
  end
111
110
 
112
- def redundant_else_edits(lines, warning)
111
+ def self.redundant_else_edits(lines, warning)
113
112
  return [] unless warning.line
114
113
 
115
114
  diag_idx = warning.line - 1
@@ -144,7 +143,7 @@ module MilkTea
144
143
  [FixEdit.new(start_line: else_idx, start_char: 0, end_line: body_end_idx + 1, end_char: 0, new_text: new_body)]
145
144
  end
146
145
 
147
- def redundant_return_edits(lines, warning)
146
+ def self.redundant_return_edits(lines, warning)
148
147
  return [] unless warning.line
149
148
 
150
149
  line_idx = warning.line - 1
@@ -153,7 +152,7 @@ module MilkTea
153
152
  [FixEdit.new(start_line: line_idx, start_char: 0, end_line: line_idx + 1, end_char: 0, new_text: "")]
154
153
  end
155
154
 
156
- def unused_import_edits(lines, warning)
155
+ def self.unused_import_edits(lines, warning)
157
156
  return [] unless warning.line
158
157
 
159
158
  line_idx = warning.line - 1
@@ -162,7 +161,7 @@ module MilkTea
162
161
  [FixEdit.new(start_line: line_idx, start_char: 0, end_line: line_idx + 1, end_char: 0, new_text: "")]
163
162
  end
164
163
 
165
- def trailing_list_comma_edits(lines, warning)
164
+ def self.trailing_list_comma_edits(lines, warning)
166
165
  return [] unless warning.line && warning.column
167
166
 
168
167
  line_idx = warning.line - 1
@@ -176,7 +175,7 @@ module MilkTea
176
175
  [FixEdit.new(start_line: line_idx, start_char: char_idx, end_line: line_idx, end_char: char_idx + 1, new_text: "")]
177
176
  end
178
177
 
179
- def redundant_type_annotation_edits(lines, warning)
178
+ def self.redundant_type_annotation_edits(lines, warning)
180
179
  return [] unless warning.line
181
180
 
182
181
  line_idx = warning.line - 1
@@ -198,7 +197,7 @@ module MilkTea
198
197
  [FixEdit.new(start_line: line_idx, start_char: 0, end_line: line_idx + 1, end_char: 0, new_text: new_line)]
199
198
  end
200
199
 
201
- def redundant_cast_edits(lines, warning)
200
+ def self.redundant_cast_edits(lines, warning)
202
201
  return [] unless warning.line && warning.column
203
202
 
204
203
  line_idx = warning.line - 1
@@ -208,20 +207,34 @@ module MilkTea
208
207
  start = warning.column - 1
209
208
  return [] unless start >= 0 && start < line.length
210
209
 
211
- # The warning column points at the cast target type. Delete the
212
- # `TargetType<-` prefix, keeping the inner expression as-is. This
213
- # covers both bare (`ulong<-x`) and parenthesized (`uint<-(a - b)`)
214
- # forms; type refs never contain `<`, so the first `<-` at/after the
215
- # column is the cast arrow.
216
210
  arrow = line.index("<-", start)
217
211
  return [] unless arrow
218
212
 
219
213
  type_ref = line[start...arrow]
220
214
  return [] unless type_ref.match?(/\A[A-Za-z_][A-Za-z0-9_\[\]\.,\s@]*\z/)
221
215
 
222
- # When the cast is inside an `unsafe:` expression, also remove the
223
- # `unsafe: ` wrapper. Look backward from the cast for `unsafe:`.
216
+ arrow_end = arrow + 2
217
+
218
+ # When only the parentheses are redundant (the cast itself is still
219
+ # needed), remove just the `(` and matching `)`.
220
+ if warning.message&.include?("parentheses")
221
+ return [] unless arrow_end < line.length && line[arrow_end] == "("
222
+
223
+ open_paren = arrow_end
224
+ close_paren = match_paren(line, open_paren)
225
+ return [] unless close_paren
226
+
227
+ return [FixEdit.new(start_line: line_idx, start_char: open_paren, end_line: line_idx, end_char: open_paren + 1, new_text: ""),
228
+ FixEdit.new(start_line: line_idx, start_char: close_paren, end_line: line_idx, end_char: close_paren + 1, new_text: "")]
229
+ end
230
+
231
+ # Cast to same type / own->ptr / widening are redundant — remove the
232
+ # `TargetType<-` prefix. When the inner expression is then left
233
+ # parenthesized with no operators, also strip the orphaned parens.
224
234
  new_start = start
235
+
236
+ # When the cast is inside an `unsafe:` expression, also remove the
237
+ # `unsafe: ` wrapper.
225
238
  unsafe_keyword = line.rindex("unsafe:", new_start)
226
239
  if unsafe_keyword
227
240
  prefix = line[unsafe_keyword...new_start]
@@ -230,7 +243,57 @@ module MilkTea
230
243
  end
231
244
  end
232
245
 
233
- [FixEdit.new(start_line: line_idx, start_char: new_start, end_line: line_idx, end_char: arrow + 2, new_text: "")]
246
+ edits = [FixEdit.new(start_line: line_idx, start_char: new_start, end_line: line_idx, end_char: arrow_end, new_text: "")]
247
+
248
+ if arrow_end < line.length && line[arrow_end] == "("
249
+ close_paren = match_paren(line, arrow_end)
250
+ if close_paren && !inner_has_top_level_operators?(line[arrow_end + 1...close_paren])
251
+ edits << FixEdit.new(start_line: line_idx, start_char: arrow_end, end_line: line_idx, end_char: arrow_end + 1, new_text: "")
252
+ edits << FixEdit.new(start_line: line_idx, start_char: close_paren, end_line: line_idx, end_char: close_paren + 1, new_text: "")
253
+ end
254
+ end
255
+
256
+ edits
257
+ end
258
+
259
+ def self.match_paren(line, open_pos)
260
+ depth = 0
261
+ (open_pos...line.length).each do |i|
262
+ case line[i]
263
+ when "(" then depth += 1
264
+ when ")" then depth -= 1; return i if depth == 0
265
+ end
266
+ end
267
+ nil
268
+ end
269
+
270
+ BINARY_OP_CHARS = %w[+ - * / % & | ^ < > = ! ? :].freeze
271
+
272
+ def self.inner_has_top_level_operators?(inner)
273
+ depth = 0
274
+ i = 0
275
+ while i < inner.length
276
+ case inner[i]
277
+ when "(", "[", "{"
278
+ depth += 1
279
+ when ")", "]", "}"
280
+ depth -= 1 if depth > 0
281
+ when *BINARY_OP_CHARS
282
+ if depth == 0 && (inner[i] == "-" || inner[i] == "~") && start_of_expression?(inner, i)
283
+ i += 1
284
+ next
285
+ end
286
+ return true if depth == 0
287
+ end
288
+ i += 1
289
+ end
290
+ false
291
+ end
292
+
293
+ def self.start_of_expression?(inner, pos)
294
+ return true if pos == 0
295
+ prev = pos - 1
296
+ prev >= 0 && (inner[prev] == " " || inner[prev] == "\t" || inner[prev] == "(")
234
297
  end
235
298
  end
236
299
  end