mt-lang 0.3.9 → 0.3.11

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/docs/language-design.md +1 -2
  4. data/docs/language-manual.md +12 -1
  5. data/lib/milk_tea/base.rb +1 -2
  6. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +0 -2
  7. data/lib/milk_tea/bindings/bindgen/declaration.rb +0 -2
  8. data/lib/milk_tea/bindings/bindgen/emitter.rb +0 -2
  9. data/lib/milk_tea/bindings/bindgen/overrides.rb +0 -2
  10. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +51 -53
  11. data/lib/milk_tea/bindings/imported_bindings/generator.rb +7 -9
  12. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +0 -2
  13. data/lib/milk_tea/bindings/imported_bindings/naming.rb +6 -8
  14. data/lib/milk_tea/bindings/imported_bindings.rb +0 -2
  15. data/lib/milk_tea/bindings/opengl_registry.rb +3 -7
  16. data/lib/milk_tea/bindings/steamworks.rb +12 -16
  17. data/lib/milk_tea/bindings/upstream_sources.rb +10 -14
  18. data/lib/milk_tea/bindings/vendored_box2d.rb +10 -12
  19. data/lib/milk_tea/bindings/vendored_cjson.rb +6 -8
  20. data/lib/milk_tea/bindings/vendored_flecs.rb +10 -12
  21. data/lib/milk_tea/bindings/vendored_glfw.rb +11 -13
  22. data/lib/milk_tea/bindings/vendored_libuv.rb +11 -13
  23. data/lib/milk_tea/bindings/vendored_pcre2.rb +11 -13
  24. data/lib/milk_tea/bindings/vendored_raylib.rb +6 -10
  25. data/lib/milk_tea/bindings/vendored_sdl3.rb +11 -13
  26. data/lib/milk_tea/bindings/vendored_steamworks.rb +1 -5
  27. data/lib/milk_tea/core/ast.rb +109 -51
  28. data/lib/milk_tea/core/async_runtime_installer.rb +0 -2
  29. data/lib/milk_tea/core/bindings/attribute_binding.rb +1 -3
  30. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +147 -84
  31. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +394 -396
  32. data/lib/milk_tea/core/c_backend/expressions.rb +489 -476
  33. data/lib/milk_tea/core/c_backend/feature_detection.rb +446 -437
  34. data/lib/milk_tea/core/c_backend/format_helpers.rb +0 -2
  35. data/lib/milk_tea/core/c_backend/reachability.rb +339 -341
  36. data/lib/milk_tea/core/c_backend/reinterpret.rb +231 -217
  37. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +531 -526
  38. data/lib/milk_tea/core/c_backend/statements.rb +528 -530
  39. data/lib/milk_tea/core/c_backend/type_collectors.rb +1344 -1346
  40. data/lib/milk_tea/core/c_backend/type_declaration.rb +215 -213
  41. data/lib/milk_tea/core/c_backend/type_system.rb +347 -349
  42. data/lib/milk_tea/core/c_backend.rb +17 -9
  43. data/lib/milk_tea/core/compatibility_helpers.rb +0 -2
  44. data/lib/milk_tea/core/compile_time/const_eval.rb +7 -12
  45. data/lib/milk_tea/core/compile_time.rb +32 -38
  46. data/lib/milk_tea/core/control_flow/builder.rb +3 -5
  47. data/lib/milk_tea/core/control_flow/constant_propagation.rb +14 -17
  48. data/lib/milk_tea/core/control_flow/definite_assignment.rb +0 -1
  49. data/lib/milk_tea/core/ir.rb +10 -10
  50. data/lib/milk_tea/core/lexer/character_classes.rb +5 -7
  51. data/lib/milk_tea/core/lexer/format_strings.rb +7 -9
  52. data/lib/milk_tea/core/lexer/heredocs.rb +22 -22
  53. data/lib/milk_tea/core/lexer/indentation.rb +0 -2
  54. data/lib/milk_tea/core/lexer/numbers.rb +0 -2
  55. data/lib/milk_tea/core/lexer/recovery.rb +0 -2
  56. data/lib/milk_tea/core/lexer/strings.rb +0 -2
  57. data/lib/milk_tea/core/lexer/symbols.rb +0 -2
  58. data/lib/milk_tea/core/lexer/trivia.rb +0 -2
  59. data/lib/milk_tea/core/lexer.rb +0 -2
  60. data/lib/milk_tea/core/lowering/async/analysis.rb +0 -2
  61. data/lib/milk_tea/core/lowering/async/lowering.rb +1264 -1265
  62. data/lib/milk_tea/core/lowering/async/normalization.rb +5 -3
  63. data/lib/milk_tea/core/lowering/async.rb +627 -630
  64. data/lib/milk_tea/core/lowering/block.rb +413 -416
  65. data/lib/milk_tea/core/lowering/calls.rb +1381 -1384
  66. data/lib/milk_tea/core/lowering/declarations.rb +180 -183
  67. data/lib/milk_tea/core/lowering/dyn.rb +48 -50
  68. data/lib/milk_tea/core/lowering/events.rb +983 -985
  69. data/lib/milk_tea/core/lowering/expressions.rb +1513 -1518
  70. data/lib/milk_tea/core/lowering/foreign_cstr.rb +181 -184
  71. data/lib/milk_tea/core/lowering/functions.rb +185 -188
  72. data/lib/milk_tea/core/lowering/loops.rb +1028 -1031
  73. data/lib/milk_tea/core/lowering/proc.rb +363 -366
  74. data/lib/milk_tea/core/lowering/resolve.rb +2133 -2135
  75. data/lib/milk_tea/core/lowering/scans.rb +179 -181
  76. data/lib/milk_tea/core/lowering/str_buffer.rb +102 -105
  77. data/lib/milk_tea/core/lowering/utils.rb +1040 -1044
  78. data/lib/milk_tea/core/lowering.rb +5 -7
  79. data/lib/milk_tea/core/module_binder.rb +0 -2
  80. data/lib/milk_tea/core/module_loader/errors.rb +4 -2
  81. data/lib/milk_tea/core/module_loader.rb +113 -20
  82. data/lib/milk_tea/core/module_path_resolver.rb +0 -2
  83. data/lib/milk_tea/core/module_roots.rb +6 -8
  84. data/lib/milk_tea/core/parser/attributes.rb +0 -2
  85. data/lib/milk_tea/core/parser/blocks.rb +0 -2
  86. data/lib/milk_tea/core/parser/declarations.rb +7 -9
  87. data/lib/milk_tea/core/parser/expressions.rb +15 -10
  88. data/lib/milk_tea/core/parser/recovery.rb +0 -2
  89. data/lib/milk_tea/core/parser/statements.rb +8 -2
  90. data/lib/milk_tea/core/parser/types.rb +12 -14
  91. data/lib/milk_tea/core/parser.rb +0 -2
  92. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +31 -33
  93. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +0 -2
  94. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +0 -2
  95. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +0 -2
  96. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +14 -16
  97. data/lib/milk_tea/core/semantic_analyzer/calls.rb +0 -2
  98. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1 -14
  99. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +18 -21
  100. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +0 -3
  101. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +14 -17
  102. data/lib/milk_tea/core/semantic_analyzer/generics.rb +57 -59
  103. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +0 -2
  104. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +5 -9
  105. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +0 -3
  106. data/lib/milk_tea/core/semantic_analyzer/statements.rb +35 -40
  107. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +10 -12
  108. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +0 -6
  109. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +106 -109
  110. data/lib/milk_tea/core/semantic_analyzer.rb +17 -8
  111. data/lib/milk_tea/core/types/layout.rb +21 -22
  112. data/lib/milk_tea/core/types/predicates.rb +0 -2
  113. data/lib/milk_tea/core/types/registry.rb +16 -18
  114. data/lib/milk_tea/core/types/visitor.rb +0 -4
  115. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -2
  116. data/lib/milk_tea/dap/protocol.rb +1 -1
  117. data/lib/milk_tea/dap/server/breakpoints.rb +0 -2
  118. data/lib/milk_tea/dap/server/debug_map.rb +0 -2
  119. data/lib/milk_tea/dap/server/handlers.rb +0 -2
  120. data/lib/milk_tea/dap/server/launch.rb +0 -2
  121. data/lib/milk_tea/dap/server/lldb_backend.rb +8 -10
  122. data/lib/milk_tea/dap/server/pause_diagnostics.rb +14 -16
  123. data/lib/milk_tea/dap/server/utilities.rb +0 -2
  124. data/lib/milk_tea/dap/server/wire.rb +0 -2
  125. data/lib/milk_tea/dap/server.rb +0 -2
  126. data/lib/milk_tea/lsp/diagnostics.rb +16 -16
  127. data/lib/milk_tea/lsp/server/call_hierarchy.rb +11 -12
  128. data/lib/milk_tea/lsp/server/code_actions.rb +355 -357
  129. data/lib/milk_tea/lsp/server/code_lens.rb +23 -25
  130. data/lib/milk_tea/lsp/server/completion.rb +873 -875
  131. data/lib/milk_tea/lsp/server/configuration.rb +129 -131
  132. data/lib/milk_tea/lsp/server/debug_info.rb +0 -2
  133. data/lib/milk_tea/lsp/server/definition.rb +564 -565
  134. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +191 -193
  135. data/lib/milk_tea/lsp/server/execute_command.rb +0 -2
  136. data/lib/milk_tea/lsp/server/folding_range.rb +0 -4
  137. data/lib/milk_tea/lsp/server/formatting.rb +478 -480
  138. data/lib/milk_tea/lsp/server/hover.rb +1282 -1284
  139. data/lib/milk_tea/lsp/server/inlay_hints.rb +179 -181
  140. data/lib/milk_tea/lsp/server/lifecycle.rb +192 -194
  141. data/lib/milk_tea/lsp/server/linked_editing_range.rb +0 -2
  142. data/lib/milk_tea/lsp/server/on_type_formatting.rb +0 -2
  143. data/lib/milk_tea/lsp/server/progress.rb +0 -2
  144. data/lib/milk_tea/lsp/server/references.rb +316 -318
  145. data/lib/milk_tea/lsp/server/rename.rb +445 -447
  146. data/lib/milk_tea/lsp/server/selection_range.rb +0 -4
  147. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1376 -1314
  148. data/lib/milk_tea/lsp/server/signature_help.rb +146 -148
  149. data/lib/milk_tea/lsp/server/text_documents.rb +99 -101
  150. data/lib/milk_tea/lsp/server/type_hierarchy.rb +2 -4
  151. data/lib/milk_tea/lsp/server/utilities.rb +397 -399
  152. data/lib/milk_tea/lsp/server.rb +0 -3
  153. data/lib/milk_tea/lsp/workspace/analysis.rb +40 -42
  154. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +4 -10
  155. data/lib/milk_tea/lsp/workspace/utilities.rb +19 -19
  156. data/lib/milk_tea/packages/atomic_write.rb +4 -5
  157. data/lib/milk_tea/packages/dependency_solver.rb +15 -15
  158. data/lib/milk_tea/packages/lock.rb +4 -4
  159. data/lib/milk_tea/packages/manager_cli.rb +1 -1
  160. data/lib/milk_tea/packages/manifest.rb +3 -3
  161. data/lib/milk_tea/packages/registry_store.rb +4 -6
  162. data/lib/milk_tea/packages/services.rb +16 -16
  163. data/lib/milk_tea/packages/source_cache.rb +4 -6
  164. data/lib/milk_tea/packages/source_resolver.rb +44 -44
  165. data/lib/milk_tea/packages/version.rb +8 -8
  166. data/lib/milk_tea/tooling/build.rb +3 -7
  167. data/lib/milk_tea/tooling/build_cache.rb +0 -2
  168. data/lib/milk_tea/tooling/cli.rb +10 -4
  169. data/lib/milk_tea/tooling/debug_info_formatter.rb +31 -33
  170. data/lib/milk_tea/tooling/debug_map.rb +8 -10
  171. data/lib/milk_tea/tooling/docs_app.rb +0 -2
  172. data/lib/milk_tea/tooling/linter/doc_tags.rb +25 -27
  173. data/lib/milk_tea/tooling/linter/fix_engine.rb +87 -24
  174. data/lib/milk_tea/tooling/linter/flow_rules.rb +44 -46
  175. data/lib/milk_tea/tooling/linter/imports_platform.rb +14 -16
  176. data/lib/milk_tea/tooling/linter/release_rules.rb +3 -5
  177. data/lib/milk_tea/tooling/linter/reserved_names.rb +23 -25
  178. data/lib/milk_tea/tooling/linter/rules.rb +87 -89
  179. data/lib/milk_tea/tooling/linter/source_helpers.rb +40 -42
  180. data/lib/milk_tea/tooling/linter/trailing_comma.rb +17 -19
  181. data/lib/milk_tea/tooling/linter/visitors.rb +112 -54
  182. data/lib/milk_tea/tooling/linter.rb +0 -2
  183. data/lib/milk_tea/tooling/project_scaffold.rb +0 -2
  184. data/lib/milk_tea/tooling/run.rb +0 -2
  185. data/lib/milk_tea/tooling/toolchain_cli.rb +0 -2
  186. data/std/cli.mt +13 -32
  187. data/std/color.mt +28 -28
  188. data/std/encoding.mt +4 -4
  189. data/std/graph.mt +1 -2
  190. data/std/hash.mt +14 -14
  191. data/std/http/server.mt +3 -3
  192. data/std/input.mt +1 -1
  193. data/std/json.mt +1 -1
  194. data/std/linear_algebra.mt +6 -6
  195. data/std/net/mux.mt +1 -1
  196. data/std/noise.mt +23 -23
  197. data/std/parse.mt +2 -2
  198. data/std/process.mt +3 -4
  199. data/std/sparse_set.mt +1 -1
  200. data/std/steering.mt +5 -5
  201. data/std/str.mt +1 -1
  202. data/std/tween.mt +4 -4
  203. data/std/utility.mt +3 -3
  204. 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
@@ -374,10 +372,10 @@ module MilkTea
374
372
  break if check(:rbracket)
375
373
 
376
374
  if match(:at)
377
- name_token = consume_name("expected lifetime name after @")
375
+ name_token = consume_name_allowing_keywords("expected lifetime name after @")
378
376
  lifetime_params << "@#{name_token.lexeme}"
379
377
  else
380
- name_token = consume_name("expected type parameter name")
378
+ name_token = consume_name_allowing_keywords("expected type parameter name")
381
379
  if match(:colon)
382
380
  value_type = parse_type_ref
383
381
  type_params << AST::ValueTypeParam.new(
@@ -421,7 +419,7 @@ module MilkTea
421
419
 
422
420
  raise error(visibility_token, "public is only allowed on struct events") if visibility == :public
423
421
 
424
- field_token = consume_name("expected field name")
422
+ field_token = consume_name_allowing_keywords("expected field name")
425
423
  field_name = field_token.lexeme
426
424
  consume(:colon, "expected ':' after field name")
427
425
  field_type = parse_type_ref
@@ -442,7 +440,7 @@ module MilkTea
442
440
  name = name_token.lexeme
443
441
  c_name = parse_optional_explicit_c_name
444
442
  fields = parse_named_block do
445
- field_name = consume_name("expected field name").lexeme
443
+ field_name = consume_name_allowing_keywords("expected field name").lexeme
446
444
  consume(:colon, "expected ':' after field name")
447
445
  field_type = parse_type_ref
448
446
  consume_end_of_statement
@@ -473,7 +471,7 @@ module MilkTea
473
471
  members = []
474
472
  skip_newlines
475
473
  until check(:dedent) || eof?
476
- member_token = consume_name("expected member name")
474
+ member_token = consume_name_allowing_keywords("expected member name")
477
475
  member_name = member_token.lexeme
478
476
  if match(:equal)
479
477
  value = parse_expression
@@ -495,10 +493,10 @@ module MilkTea
495
493
  name = name_token.lexeme
496
494
  type_params = parse_declaration_type_params
497
495
  arms = parse_named_block do
498
- arm_name = consume_name("expected variant arm name").lexeme
496
+ arm_name = consume_name_allowing_keywords("expected variant arm name").lexeme
499
497
  fields = if match(:lparen)
500
498
  parsed = parse_comma_separated_until(:rparen) do
501
- field_name = consume_name("expected field name").lexeme
499
+ field_name = consume_name_allowing_keywords("expected field name").lexeme
502
500
  consume(:colon, "expected ':' after field name")
503
501
  field_type = parse_type_ref
504
502
  AST::Field.new(name: field_name, type: field_type)
@@ -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
@@ -107,6 +105,8 @@ module MilkTea
107
105
  binding_line: binding_token&.line,
108
106
  binding_column: binding_token&.column,
109
107
  value:,
108
+ line: pattern.line,
109
+ column: pattern.column,
110
110
  )
111
111
  end
112
112
  end
@@ -155,6 +155,8 @@ module MilkTea
155
155
  binding_line: nil,
156
156
  binding_column: nil,
157
157
  value: AST::BooleanLiteral.new(value: true),
158
+ line: arm_pattern.line,
159
+ column: arm_pattern.column,
158
160
  ),
159
161
  AST::MatchExprArm.new(
160
162
  pattern: AST::Identifier.new(name: "_", line:, column:),
@@ -162,6 +164,8 @@ module MilkTea
162
164
  binding_line: nil,
163
165
  binding_column: nil,
164
166
  value: AST::BooleanLiteral.new(value: false),
167
+ line:,
168
+ column:,
165
169
  ),
166
170
  ],
167
171
  line:,
@@ -340,7 +344,7 @@ module MilkTea
340
344
  end
341
345
 
342
346
  def parse_call_argument
343
- if check_name && check_next(:equal)
347
+ if (check_name || keyword_token?(peek)) && check_next(:equal)
344
348
  name = advance.lexeme
345
349
  consume(:equal, "expected '=' after named argument name")
346
350
  AST::Argument.new(name:, value: parse_expression)
@@ -544,24 +548,25 @@ module MilkTea
544
548
  )
545
549
  end
546
550
 
547
- parser = self.class.send(:new, tokens, path: @path)
551
+ parser = self.class.new(tokens, path: @path)
548
552
  parser.instance_variable_set(:@known_type_names, @known_type_names.dup)
549
553
  parser.instance_variable_set(:@known_import_aliases, @known_import_aliases.dup)
550
554
  parser.instance_variable_set(:@known_generic_callable_names, @known_generic_callable_names.dup)
551
555
  parser.instance_variable_set(:@current_type_param_names, @current_type_param_names.dup)
552
556
 
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?)
557
+ expression = parser.parse_expression
558
+ parser.skip_newlines
559
+ raise parser.error(parser.peek, "expected end of interpolation") unless parser.eof?
556
560
 
557
561
  expression
558
562
  end
559
563
 
560
564
  def parse_left_associative(operand_method, *operator_types)
561
- expression = send(operand_method)
565
+ operand = method(operand_method)
566
+ expression = operand.call
562
567
  while match(*operator_types)
563
568
  operator = previous.lexeme
564
- right = send(operand_method)
569
+ right = operand.call
565
570
  expression = AST::BinaryOp.new(operator:, left: expression, right:)
566
571
  end
567
572
  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
 
@@ -353,6 +351,8 @@ module MilkTea
353
351
  binding_line: binding_token&.line,
354
352
  binding_column: binding_token&.column,
355
353
  value:,
354
+ line: pattern.line,
355
+ column: pattern.column,
356
356
  )
357
357
  end
358
358
  else
@@ -364,6 +364,8 @@ module MilkTea
364
364
  binding_line: binding_token&.line,
365
365
  binding_column: binding_token&.column,
366
366
  body:,
367
+ line: pattern.line,
368
+ column: pattern.column,
367
369
  )
368
370
  end
369
371
  end
@@ -378,6 +380,8 @@ module MilkTea
378
380
  binding_line: binding_token&.line,
379
381
  binding_column: binding_token&.column,
380
382
  body: recovered_body,
383
+ line: patterns.first&.line,
384
+ column: patterns.first&.column,
381
385
  )] if recovered_body
382
386
 
383
387
  raise
@@ -617,6 +621,8 @@ module MilkTea
617
621
  binding_line: binding_token.line,
618
622
  binding_column: binding_token.column,
619
623
  body:,
624
+ line: pattern.line,
625
+ column: pattern.column,
620
626
  )
621
627
  skip_newlines
622
628
  end
@@ -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
@@ -94,7 +92,7 @@ module MilkTea
94
92
 
95
93
  first_token = peek
96
94
  if match(:at)
97
- lt_token = consume_name("expected lifetime name after @")
95
+ lt_token = consume_name_allowing_keywords("expected lifetime name after @")
98
96
  return AST::TypeRef.new(name: AST::QualifiedName.new(parts: ["@#{lt_token.lexeme}"]), arguments: [], nullable: false, lifetime: nil, line: first_token.line, column: first_token.column, length: lt_token.lexeme.length + 1)
99
97
  end
100
98
  name = parse_qualified_name
@@ -103,7 +101,7 @@ module MilkTea
103
101
  if match(:lbracket)
104
102
  if check(:at) && name.to_s == "ref"
105
103
  match(:at)
106
- lt_token = consume_name("expected lifetime name after @")
104
+ lt_token = consume_name_allowing_keywords("expected lifetime name after @")
107
105
  lifetime = "@#{lt_token.lexeme}"
108
106
  consume(:comma, "expected ',' after lifetime in type arguments")
109
107
  end
@@ -169,7 +167,7 @@ module MilkTea
169
167
  end
170
168
 
171
169
  def parse_function_type_param
172
- name_token = consume_name("expected function type parameter name")
170
+ name_token = consume_name_allowing_keywords("expected function type parameter name")
173
171
  name = name_token.lexeme
174
172
  consume(:colon, "expected ':' after function type parameter name")
175
173
  type = parse_type_ref
@@ -192,7 +190,7 @@ module MilkTea
192
190
  return [] unless match(:lbracket)
193
191
 
194
192
  params = parse_comma_separated_until(:rbracket) do
195
- name_token = consume_name("expected type parameter name")
193
+ name_token = consume_name_allowing_keywords("expected type parameter name")
196
194
  name = name_token.lexeme
197
195
  if match(:colon)
198
196
  value_type = parse_type_ref
@@ -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)