mt-lang 0.3.9 → 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 +8 -9
  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 +0 -2
  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 +112 -54
  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
@@ -139,7 +139,7 @@ module MilkTea
139
139
  next if analysis.module_kind == :raw_module
140
140
  next if modules.key?(analysis.module_name)
141
141
 
142
- prepare_analysis(analysis, source_path: path)
142
+ prepare_analysis(analysis, path: path)
143
143
  collect_structs
144
144
 
145
145
  synth_before_s = @artifacts.synthetic_structs.length
@@ -159,7 +159,7 @@ module MilkTea
159
159
  variants: lower_variants.dup,
160
160
  static_asserts: lower_static_asserts.dup,
161
161
  functions: [],
162
- source_path: path,
162
+ path: path,
163
163
  )
164
164
  per_module_funcs[analysis.module_name].concat(lower_functions)
165
165
 
@@ -178,7 +178,7 @@ module MilkTea
178
178
  ordered_analysis_pairs.each do |path, analysis|
179
179
  next if analysis.module_kind == :raw_module
180
180
 
181
- prepare_analysis(analysis, source_path: path)
181
+ prepare_analysis(analysis, path: path)
182
182
  ensure_events_for_analysis(analysis)
183
183
 
184
184
  synth_before_s = @artifacts.synthetic_structs.length
@@ -279,7 +279,7 @@ module MilkTea
279
279
  variants: all_variants,
280
280
  static_asserts: all_static_asserts,
281
281
  functions: all_functions,
282
- source_path: @program.root_path,
282
+ path: @program.root_path,
283
283
  )
284
284
  end
285
285
 
@@ -287,7 +287,7 @@ module MilkTea
287
287
  ordered_analysis_pairs.each do |path, analysis|
288
288
  next if analysis.module_kind == :raw_module
289
289
 
290
- prepare_analysis(analysis, source_path: path)
290
+ prepare_analysis(analysis, path: path)
291
291
  analysis.ast.declarations.grep(AST::EventDecl).each do |decl|
292
292
  event_type = analysis.values.fetch(decl.name).type
293
293
  ensure_event_runtime(event_type) if event_type.is_a?(Types::Event)
@@ -359,8 +359,6 @@ module MilkTea
359
359
  ordered
360
360
  end
361
361
 
362
- private
363
-
364
362
  include LowererScans
365
363
  include LowererDeclarations
366
364
  include LowererEvents
@@ -61,8 +61,6 @@ module MilkTea
61
61
  )
62
62
  end
63
63
 
64
- private
65
-
66
64
  def exported_declaration?(analysis, declaration)
67
65
  return true if analysis.module_kind == :raw_module
68
66
  return false unless declaration.respond_to?(:visibility)
@@ -2,10 +2,12 @@
2
2
 
3
3
  module MilkTea
4
4
  class ModuleLoadError < StandardError
5
- attr_reader :path
5
+ attr_reader :path, :line, :column
6
6
 
7
- def initialize(message, path:)
7
+ def initialize(message, path:, line: nil, column: nil)
8
8
  @path = path
9
+ @line = line
10
+ @column = column
9
11
  super("#{message}: #{path}")
10
12
  end
11
13
 
@@ -98,6 +98,7 @@ module MilkTea
98
98
  @collecting_analysis_cache = {}
99
99
  @collecting_path_errors = {}
100
100
  @checking_paths = []
101
+ @forward_bindings = {}
101
102
  @platform = self.class.normalize_platform_name(platform)
102
103
  @package_graph = package_graph
103
104
  @package_manifest_cache = {}
@@ -176,7 +177,59 @@ module MilkTea
176
177
  # Phase 3: Topological sort into independent levels
177
178
  levels = topo_sort_levels(graph)
178
179
 
179
- # Phase 4: Check each level (parallel within level, sequential across levels)
180
+ # Pre-register forward bindings for cycle members
181
+ all_checked = levels.flatten.to_set
182
+ cycle_members = graph.keys.reject { |p| all_checked.include?(p) }
183
+ cycle_members.each do |resolved_path|
184
+ ast = @parse_cache[resolved_path]
185
+ @forward_bindings[resolved_path] = create_forward_binding(ast)
186
+ end
187
+
188
+ # Phase 4: Check cycle members with iterative refinement
189
+ # Pass 1: Check with forward bindings — registers type declarations.
190
+ # SemanticError is expected (forward types lack fields/constructors)
191
+ # but ModuleLoadError indicates a broken dependency graph.
192
+ cycle_members.each do |resolved_path|
193
+ check_path(resolved_path)
194
+ rescue SemanticError
195
+ # Forward types can't satisfy constructors. Pass 2 will retry.
196
+ end
197
+
198
+ # Update forward type objects in-place with field/arm info from the
199
+ # real analyses. This way, all references to forward types (function
200
+ # return types, struct field types, etc.) automatically see the full
201
+ # type information without needing to replace the objects themselves.
202
+ cycle_members.each do |resolved_path|
203
+ analysis = @analysis_cache[resolved_path]
204
+ next unless analysis
205
+
206
+ fw_binding = @forward_bindings[resolved_path]
207
+ next unless fw_binding
208
+
209
+ analysis.types.each do |name, real_type|
210
+ fw_type = fw_binding.types[name]
211
+ next unless fw_type
212
+
213
+ if real_type.respond_to?(:fields) && fw_type.respond_to?(:define_fields)
214
+ fw_type.define_fields(real_type.fields) unless real_type.fields.empty?
215
+ end
216
+ if real_type.respond_to?(:arms) && fw_type.respond_to?(:define_arms)
217
+ fw_type.define_arms(real_type.arms) unless real_type.arms.empty?
218
+ end
219
+ end
220
+ end
221
+
222
+ # Pass 2: Re-check with populated types — now cycle members see
223
+ # complete type information for each other.
224
+ cycle_members.each do |resolved_path|
225
+ previous = @analysis_cache.delete(resolved_path)
226
+ check_path(resolved_path)
227
+ rescue SemanticError
228
+ @analysis_cache[resolved_path] = previous if previous
229
+ end
230
+ @forward_bindings.clear
231
+
232
+ # Phase 5: Check acyclic modules (they see fully-checked analyses for all imports)
180
233
  levels.each do |level_paths|
181
234
  if level_paths.length == 1
182
235
  check_path(level_paths.first)
@@ -189,10 +242,6 @@ module MilkTea
189
242
  def parse_all(resolved_path)
190
243
  return if @parse_cache.key?(resolved_path)
191
244
 
192
- if @checking_paths.include?(resolved_path)
193
- raise ModuleLoadError.new("cyclic import detected", path: resolved_path)
194
- end
195
-
196
245
  @checking_paths << resolved_path
197
246
  ast = load_file(resolved_path)
198
247
  @parse_cache[resolved_path] = ast
@@ -202,7 +251,7 @@ module MilkTea
202
251
  parse_all(import_path)
203
252
  end
204
253
  ensure
205
- @checking_paths.pop if @checking_paths.last == resolved_path
254
+ @checking_paths.pop
206
255
  end
207
256
 
208
257
  def topo_sort_levels(graph)
@@ -252,8 +301,13 @@ module MilkTea
252
301
 
253
302
  ast.imports.each do |import|
254
303
  import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
255
- import_analysis = check_path(import_path)
256
- modules[import.path.to_s] = @binder.module_binding(import_analysis)
304
+
305
+ if @forward_bindings.key?(import_path)
306
+ modules[import.path.to_s] = @forward_bindings[import_path]
307
+ else
308
+ import_analysis = check_path(import_path)
309
+ modules[import.path.to_s] = @binder.module_binding(import_analysis)
310
+ end
257
311
  end
258
312
 
259
313
  @async_runtime_installer.install_async_runtime_dependency!(ast, modules, importer_path:, collecting_errors: false)
@@ -291,8 +345,13 @@ module MilkTea
291
345
  ast.imports.each do |import|
292
346
  begin
293
347
  import_path = @path_resolver.resolve_module_path(import.path.to_s, importer_path:, importer_module_name: ast.module_name.to_s)
294
- import_analysis = check_path_collecting_errors(import_path)
295
- modules[import.path.to_s] = @binder.module_binding(import_analysis)
348
+
349
+ if @forward_bindings.key?(import_path)
350
+ modules[import.path.to_s] = @forward_bindings[import_path]
351
+ else
352
+ import_analysis = check_path_collecting_errors(import_path)
353
+ modules[import.path.to_s] = @binder.module_binding(import_analysis)
354
+ end
296
355
  rescue ModuleLoadError, PackageLockError, SemanticError => e
297
356
  errors << ImportResolutionError.new(import:, error: e)
298
357
  end
@@ -321,8 +380,6 @@ module MilkTea
321
380
  @collecting_path_errors
322
381
  end
323
382
 
324
- private
325
-
326
383
  def self.raise_platform_conflict!(path, pinned_platform, active_platform, error_class: nil)
327
384
  if error_class == ModuleLoadError
328
385
  raise ModuleLoadError.new("source file targets platform #{pinned_platform}; active platform is #{active_platform}", path:)
@@ -331,12 +388,16 @@ module MilkTea
331
388
  message = "source file #{path} targets platform #{pinned_platform}; active platform is #{active_platform}"
332
389
  raise(error_class || ArgumentError, message)
333
390
  end
334
- private_class_method :raise_platform_conflict!
335
391
 
336
392
  def check_path(path)
337
393
  resolved_path, ast, cached = check_module_cache(path)
338
394
  return cached if cached
339
395
 
396
+ if @checking_paths.include?(resolved_path) && !@forward_bindings.key?(resolved_path)
397
+ raise ModuleLoadError.new("circular import not resolvable: module #{@parse_cache[resolved_path]&.module_name || File.basename(path)}", path: resolved_path)
398
+ end
399
+
400
+ @checking_paths << resolved_path
340
401
  imported_modules = imported_modules_for_ast(ast, importer_path: resolved_path)
341
402
  global_index = build_global_import_index(ast)
342
403
  analysis = SemanticAnalyzer.check(ast, imported_modules:, path: resolved_path, global_import_index: global_index)
@@ -344,13 +405,18 @@ module MilkTea
344
405
  update_shared_cache(resolved_path, analysis)
345
406
  analysis
346
407
  ensure
347
- @checking_paths.pop if @checking_paths.last == resolved_path
408
+ @checking_paths.pop
348
409
  end
349
410
 
350
411
  def check_path_collecting_errors(path)
351
412
  resolved_path, ast, cached = check_module_cache(path, extra_cache: @collecting_analysis_cache)
352
413
  return cached if cached
353
414
 
415
+ if @checking_paths.include?(resolved_path) && !@forward_bindings.key?(resolved_path)
416
+ raise ModuleLoadError.new("circular import not resolvable: module #{@parse_cache[resolved_path]&.module_name || File.basename(path)}", path: resolved_path)
417
+ end
418
+
419
+ @checking_paths << resolved_path
354
420
  imported_modules = imported_modules_for_ast_collecting_errors(ast, importer_path: resolved_path).modules
355
421
  result = SemanticAnalyzer.check_collecting_errors(ast, imported_modules:, path: resolved_path)
356
422
  analysis = result[:analysis]
@@ -360,7 +426,7 @@ module MilkTea
360
426
  @collecting_analysis_cache[resolved_path] = analysis
361
427
  analysis
362
428
  ensure
363
- @checking_paths.pop if @checking_paths.last == resolved_path
429
+ @checking_paths.pop
364
430
  end
365
431
 
366
432
  def check_module_cache(path, extra_cache: nil)
@@ -380,9 +446,6 @@ module MilkTea
380
446
  end
381
447
  end
382
448
 
383
- raise ModuleLoadError.new("cyclic import detected", path: resolved_path) if @checking_paths.include?(resolved_path)
384
-
385
- @checking_paths << resolved_path
386
449
  ast = load_file(resolved_path)
387
450
  [resolved_path, ast, nil]
388
451
  end
@@ -433,8 +496,8 @@ module MilkTea
433
496
  end
434
497
 
435
498
  matching_root = @module_roots
436
- .select { |root| path_within_root?(path, root) }
437
- .max_by(&:length)
499
+ .select { |root| path_within_root?(path, root) }
500
+ .max_by(&:length)
438
501
  return module_name_for_path(path, matching_root) if matching_root
439
502
 
440
503
  File.basename(path).sub(/\.(linux|windows|wasm)\.mt\z/, ".mt").sub(/\.mt\z/, "")
@@ -453,6 +516,36 @@ module MilkTea
453
516
  normalized_path == normalized_root || normalized_path.start_with?(normalized_root + File::SEPARATOR)
454
517
  end
455
518
 
519
+ def create_forward_binding(ast)
520
+ module_name = ast.module_name.to_s
521
+ types = {}
522
+
523
+ ast.declarations.each do |decl|
524
+ case decl
525
+ when AST::StructDecl
526
+ types[decl.name] = Types::Struct.new(decl.name, module_name:)
527
+ when AST::VariantDecl
528
+ types[decl.name] = Types::Variant.new(decl.name, module_name:)
529
+ when AST::EnumDecl
530
+ types[decl.name] = Types::Enum.new(decl.name, module_name:)
531
+ when AST::FlagsDecl
532
+ types[decl.name] = Types::Flags.new(decl.name, module_name:)
533
+ when AST::OpaqueDecl
534
+ types[decl.name] = Types::Opaque.new(decl.name, module_name:, external: false)
535
+ end
536
+ end
537
+
538
+ ModuleBinding.new(
539
+ name: module_name, types:, type_declarations: {},
540
+ interfaces: {}, attributes: {}, attribute_applications: {},
541
+ values: {}, functions: {}, methods: {},
542
+ implemented_interfaces: {}, imports: {},
543
+ private_types: {}, private_interfaces: {}, private_attributes: {},
544
+ private_values: {}, private_functions: {}, private_methods: {},
545
+ private_implemented_interfaces: {},
546
+ )
547
+ end
548
+
456
549
  def use_shared_cache?
457
550
  @shared_cache && @source_overrides.empty?
458
551
  end
@@ -37,8 +37,6 @@ module MilkTea
37
37
  @source_overrides.key?(resolved_path) || File.file?(resolved_path)
38
38
  end
39
39
 
40
- private
41
-
42
40
  def resolve_package_module_path(module_name, importer_path: nil)
43
41
  return nil unless @package_graph && importer_path
44
42
 
@@ -2,9 +2,8 @@
2
2
 
3
3
  module MilkTea
4
4
  module ModuleRoots
5
- module_function
6
5
 
7
- def roots_for_path(path, env: ENV, locked: false)
6
+ def self.roots_for_path(path, env: ENV, locked: false)
8
7
  roots = []
9
8
 
10
9
  add_env_roots!(roots, env)
@@ -24,7 +23,7 @@ module MilkTea
24
23
  .select { |root| File.directory?(root) }
25
24
  end
26
25
 
27
- def package_roots_for_path(path, locked: false)
26
+ def self.package_roots_for_path(path, locked: false)
28
27
  PackageGraph.load(path, locked:).source_roots
29
28
  rescue PackageManifestError
30
29
  package_root = package_root_for_path(path)
@@ -33,7 +32,7 @@ module MilkTea
33
32
  [package_root]
34
33
  end
35
34
 
36
- def project_root_for_path(path)
35
+ def self.project_root_for_path(path)
37
36
  return nil unless path
38
37
 
39
38
  current = File.expand_path(File.directory?(path) ? path : File.dirname(path))
@@ -47,7 +46,7 @@ module MilkTea
47
46
  end
48
47
  end
49
48
 
50
- def package_root_for_path(path)
49
+ def self.package_root_for_path(path)
51
50
  return nil unless path
52
51
 
53
52
  current = File.expand_path(File.directory?(path) ? path : File.dirname(path))
@@ -61,7 +60,7 @@ module MilkTea
61
60
  end
62
61
  end
63
62
 
64
- def normalize_root(root)
63
+ def self.normalize_root(root)
65
64
  return nil if root.nil? || root.to_s.strip.empty?
66
65
 
67
66
  expanded = File.expand_path(root.to_s)
@@ -72,7 +71,7 @@ module MilkTea
72
71
  end
73
72
  end
74
73
 
75
- def add_env_roots!(roots, env)
74
+ def self.add_env_roots!(roots, env)
76
75
  env_keys = %w[MILK_TEA_MODULE_ROOT MILK_TEA_STD_ROOT]
77
76
  env_keys.each do |key|
78
77
  value = env[key]
@@ -83,6 +82,5 @@ module MilkTea
83
82
  end
84
83
  end
85
84
  end
86
- private_class_method :add_env_roots!
87
85
  end
88
86
  end
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Attributes
6
- private
7
-
8
6
  def parse_attribute_applications
9
7
  attributes = []
10
8
 
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Blocks
6
- private
7
-
8
6
  def parse_block
9
7
  consume(:colon, "expected ':' before block")
10
8
  consume(:newline, "expected newline before block")
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Declarations
6
- private
7
-
8
6
  def parse_import
9
7
  line = previous.line
10
8
  path = parse_qualified_name
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Expressions
6
- private
7
-
8
6
  def parse_expression
9
7
  return parse_if_expression if match(:if)
10
8
  return parse_match_expression if match(:match)
@@ -18,7 +16,7 @@ module MilkTea
18
16
  return expr unless match(:dot_dot)
19
17
 
20
18
  line = previous.line
21
- column = expr.respond_to?(:column) ? expr.column : 0
19
+ column = expr.column || 0
22
20
  end_expr = parse_or
23
21
  AST::RangeExpr.new(start_expr: expr, end_expr:, line:, column:)
24
22
  end
@@ -544,24 +542,25 @@ module MilkTea
544
542
  )
545
543
  end
546
544
 
547
- parser = self.class.send(:new, tokens, path: @path)
545
+ parser = self.class.new(tokens, path: @path)
548
546
  parser.instance_variable_set(:@known_type_names, @known_type_names.dup)
549
547
  parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
550
548
  parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
551
549
  parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)
552
550
 
553
- expression = parser.send(:parse_expression)
554
- parser.send(:skip_newlines)
555
- raise parser.send(:error, parser.send(:peek), "expected end of interpolation") unless parser.send(:eof?)
551
+ expression = parser.parse_expression
552
+ parser.skip_newlines
553
+ raise parser.error(parser.peek, "expected end of interpolation") unless parser.eof?
556
554
 
557
555
  expression
558
556
  end
559
557
 
560
558
  def parse_left_associative(operand_method, *operator_types)
561
- expression = send(operand_method)
559
+ operand = method(operand_method)
560
+ expression = operand.call
562
561
  while match(*operator_types)
563
562
  operator = previous.lexeme
564
- right = send(operand_method)
563
+ right = operand.call
565
564
  expression = AST::BinaryOp.new(operator:, left: expression, right:)
566
565
  end
567
566
  expression
@@ -8,8 +8,6 @@ module MilkTea
8
8
  opaque extending foreign async function event external static_assert link include compiler_flag
9
9
  ].freeze
10
10
 
11
- private
12
-
13
11
  def synchronize_to_top_level_boundary
14
12
  seen_newline = false
15
13
 
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Statements
6
- private
7
-
8
6
  def check_inline_stmt_start?
9
7
  return false unless check(:inline)
10
8
 
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  module Parse
5
5
  module Types
6
- private
7
-
8
6
  def parse_params(allow_variadic: false)
9
7
  parse_parameter_list(allow_variadic:) { parse_param }
10
8
  end
@@ -263,14 +261,14 @@ module MilkTea
263
261
  parts << consume_path_component("expected identifier after '.'").lexeme
264
262
  end
265
263
  type_arguments = if match(:lbracket)
266
- args = parse_comma_separated_until(:rbracket) do
267
- parse_type_ref
268
- end
269
- consume(:rbracket, "expected ']' after type arguments")
270
- args
271
- else
272
- []
273
- end
264
+ args = parse_comma_separated_until(:rbracket) do
265
+ parse_type_ref
266
+ end
267
+ consume(:rbracket, "expected ']' after type arguments")
268
+ args
269
+ else
270
+ []
271
+ end
274
272
  AST::QualifiedName.new(parts:, type_arguments:, line: first_token.line, column: first_token.column)
275
273
  end
276
274
  end
@@ -92,8 +92,6 @@ module MilkTea
92
92
  @recovery_errors = previous_recovery_errors
93
93
  end
94
94
 
95
- private
96
-
97
95
  BUILTIN_ATTRIBUTE_NAME_LEXEMES = %w[packed align].freeze
98
96
 
99
97
  def parse_source_file(errors: nil)
@@ -14,8 +14,6 @@ module MilkTea
14
14
  finish
15
15
  end
16
16
 
17
- private
18
-
19
17
  def build_comment_map(trivia)
20
18
  @blank_line_set = {}
21
19
  map = {}
@@ -146,7 +144,7 @@ module MilkTea
146
144
  end
147
145
 
148
146
  def emit_declaration(declaration)
149
- decl_line = declaration.respond_to?(:line) ? declaration.line : nil
147
+ decl_line = declaration.line
150
148
  flush_leading_comments_before(decl_line)
151
149
  header_idx = @lines.length
152
150
 
@@ -300,19 +298,19 @@ module MilkTea
300
298
 
301
299
  def render_function_signature(function, prefix: "")
302
300
  signature_prefix = if function.is_a?(AST::MethodDef)
303
- case function.kind
304
- when :editable
305
- "editable function "
306
- when :static
307
- "static function "
308
- else
309
- "function "
310
- end
311
- elsif function.is_a?(AST::ForeignFunctionDecl)
312
- "foreign function "
313
- else
314
- "function "
315
- end
301
+ case function.kind
302
+ when :editable
303
+ "editable function "
304
+ when :static
305
+ "static function "
306
+ else
307
+ "function "
308
+ end
309
+ elsif function.is_a?(AST::ForeignFunctionDecl)
310
+ "foreign function "
311
+ else
312
+ "function "
313
+ end
316
314
  async_prefix = function.respond_to?(:async) && function.async ? "async " : ""
317
315
  text = +"#{prefix}#{visibility_prefix(function)}#{async_prefix}#{signature_prefix}#{function.name}#{render_type_params(function.type_params)}(#{render_signature_params(function)})"
318
316
  text << " -> #{render_type(function.return_type)}" if function.return_type
@@ -323,10 +321,10 @@ module MilkTea
323
321
  return "" if @current_module_kind == :raw_module
324
322
 
325
323
  visibility = if declaration_or_visibility.is_a?(Symbol)
326
- declaration_or_visibility
327
- elsif declaration_or_visibility.respond_to?(:visibility)
328
- declaration_or_visibility.visibility
329
- end
324
+ declaration_or_visibility
325
+ elsif declaration_or_visibility.respond_to?(:visibility)
326
+ declaration_or_visibility.visibility
327
+ end
330
328
 
331
329
  visibility == :public ? "public " : ""
332
330
  end
@@ -414,11 +412,11 @@ module MilkTea
414
412
  prefix = +""
415
413
  prefix << "async " if method.async
416
414
  prefix << case method.kind
417
- when :editable
418
- "editable function "
419
- else
420
- "function "
421
- end
415
+ when :editable
416
+ "editable function "
417
+ else
418
+ "function "
419
+ end
422
420
 
423
421
  text = "#{prefix}#{method.name}(#{method.params.map { |param| render_param(param) }.join(', ')})"
424
422
  text << " -> #{render_type(method.return_type)}" if method.return_type
@@ -445,7 +443,7 @@ module MilkTea
445
443
  end
446
444
 
447
445
  def emit_statement(statement)
448
- stmt_line = statement.respond_to?(:line) ? statement.line : nil
446
+ stmt_line = statement.line
449
447
  flush_leading_comments_before(stmt_line)
450
448
  header_idx = @lines.length
451
449
 
@@ -663,8 +661,8 @@ module MilkTea
663
661
  end
664
662
 
665
663
  def unsafe_inline_trivia_conflict?(unsafe_stmt, nested)
666
- unsafe_line = unsafe_stmt.respond_to?(:line) ? unsafe_stmt.line : nil
667
- nested_line = nested.respond_to?(:line) ? nested.line : nil
664
+ unsafe_line = unsafe_stmt.line
665
+ nested_line = nested&.line
668
666
  return false unless unsafe_line && nested_line && nested_line > unsafe_line
669
667
 
670
668
  @comment_map.keys.any? { |line| line >= unsafe_line + 1 && line <= nested_line } ||
@@ -851,11 +849,11 @@ module MilkTea
851
849
  escape_format_text(part.value)
852
850
  when AST::FormatExprPart
853
851
  spec = case part.format_spec&.fetch(:kind)
854
- when :precision then ":.#{part.format_spec[:value]}"
855
- when :hex then part.format_spec[:uppercase] ? ":X" : ":x"
856
- when :oct then part.format_spec[:uppercase] ? ":O" : ":o"
857
- when :bin then part.format_spec[:uppercase] ? ":B" : ":b"
858
- end
852
+ when :precision then ":.#{part.format_spec[:value]}"
853
+ when :hex then part.format_spec[:uppercase] ? ":X" : ":x"
854
+ when :oct then part.format_spec[:uppercase] ? ":O" : ":o"
855
+ when :bin then part.format_spec[:uppercase] ? ":B" : ":b"
856
+ end
859
857
  "\#{#{render_expression(part.expression)}#{spec}}"
860
858
  else
861
859
  raise ArgumentError, "unsupported format string part #{part.class.name}"
@@ -10,8 +10,6 @@ module MilkTea
10
10
  @indent = 0
11
11
  end
12
12
 
13
- private
14
-
15
13
  def finish
16
14
  @lines.join("\n") + "\n"
17
15
  end
@@ -12,8 +12,6 @@ module MilkTea
12
12
  finish
13
13
  end
14
14
 
15
- private
16
-
17
15
  def emit_program(program)
18
16
  module_name = program.module_name || "(anonymous)"
19
17
  line("program #{module_name}")
@@ -3,8 +3,6 @@
3
3
  module MilkTea
4
4
  class SemanticAnalyzer
5
5
  class Checker
6
- private
7
-
8
6
  def with_unsafe
9
7
  @unsafe_depth += 1
10
8
  yield