mt-lang 0.2.0

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 (258) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/AUTHORS +3 -0
  4. data/Gemfile +19 -0
  5. data/Gemfile.lock +82 -0
  6. data/LICENSE +21 -0
  7. data/README.md +1208 -0
  8. data/Rakefile +332 -0
  9. data/bin/mtc +8 -0
  10. data/bin/profile-mtc-checks +133 -0
  11. data/bin/tracy-profiler +0 -0
  12. data/docs/build-guide.md +536 -0
  13. data/docs/index.html +2778 -0
  14. data/docs/language-design.md +1519 -0
  15. data/docs/language-manual.md +1534 -0
  16. data/lib/milk_tea/base.rb +49 -0
  17. data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
  18. data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
  19. data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
  20. data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
  21. data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
  22. data/lib/milk_tea/bindings/bindgen.rb +207 -0
  23. data/lib/milk_tea/bindings/cli.rb +121 -0
  24. data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
  25. data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
  26. data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
  27. data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
  28. data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
  29. data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
  30. data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
  31. data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
  32. data/lib/milk_tea/bindings/steamworks.rb +629 -0
  33. data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
  34. data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
  35. data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
  36. data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
  37. data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
  38. data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
  39. data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
  40. data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
  41. data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
  42. data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
  43. data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
  44. data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
  45. data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
  46. data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
  47. data/lib/milk_tea/bindings.rb +23 -0
  48. data/lib/milk_tea/core/ast.rb +311 -0
  49. data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
  50. data/lib/milk_tea/core/binding_types.rb +18 -0
  51. data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
  52. data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
  53. data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
  54. data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
  55. data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
  56. data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
  57. data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
  58. data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
  59. data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
  60. data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
  61. data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
  62. data/lib/milk_tea/core/c_backend/statements.rb +563 -0
  63. data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
  64. data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
  65. data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
  66. data/lib/milk_tea/core/c_backend.rb +287 -0
  67. data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
  68. data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
  69. data/lib/milk_tea/core/compile_time.rb +329 -0
  70. data/lib/milk_tea/core/control_flow/builder.rb +582 -0
  71. data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
  72. data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
  73. data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
  74. data/lib/milk_tea/core/control_flow/graph.rb +90 -0
  75. data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
  76. data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
  77. data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
  78. data/lib/milk_tea/core/control_flow/termination.rb +31 -0
  79. data/lib/milk_tea/core/control_flow.rb +34 -0
  80. data/lib/milk_tea/core/cst.rb +48 -0
  81. data/lib/milk_tea/core/cst_builder.rb +19 -0
  82. data/lib/milk_tea/core/ir.rb +85 -0
  83. data/lib/milk_tea/core/keywords.rb +97 -0
  84. data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
  85. data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
  86. data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
  87. data/lib/milk_tea/core/lexer/indentation.rb +62 -0
  88. data/lib/milk_tea/core/lexer/numbers.rb +96 -0
  89. data/lib/milk_tea/core/lexer/recovery.rb +32 -0
  90. data/lib/milk_tea/core/lexer/strings.rb +167 -0
  91. data/lib/milk_tea/core/lexer/symbols.rb +71 -0
  92. data/lib/milk_tea/core/lexer/trivia.rb +53 -0
  93. data/lib/milk_tea/core/lexer.rb +430 -0
  94. data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
  95. data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
  96. data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
  97. data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
  98. data/lib/milk_tea/core/lowering/async.rb +714 -0
  99. data/lib/milk_tea/core/lowering/block.rb +1052 -0
  100. data/lib/milk_tea/core/lowering/calls.rb +1565 -0
  101. data/lib/milk_tea/core/lowering/declarations.rb +214 -0
  102. data/lib/milk_tea/core/lowering/dyn.rb +206 -0
  103. data/lib/milk_tea/core/lowering/events.rb +1054 -0
  104. data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
  105. data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
  106. data/lib/milk_tea/core/lowering/functions.rb +242 -0
  107. data/lib/milk_tea/core/lowering/loops.rb +1087 -0
  108. data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
  109. data/lib/milk_tea/core/lowering/proc.rb +419 -0
  110. data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
  111. data/lib/milk_tea/core/lowering/scans.rb +220 -0
  112. data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
  113. data/lib/milk_tea/core/lowering/utils.rb +1453 -0
  114. data/lib/milk_tea/core/lowering.rb +378 -0
  115. data/lib/milk_tea/core/module_binder.rb +181 -0
  116. data/lib/milk_tea/core/module_loader/errors.rb +21 -0
  117. data/lib/milk_tea/core/module_loader.rb +479 -0
  118. data/lib/milk_tea/core/module_path_resolver.rb +153 -0
  119. data/lib/milk_tea/core/module_roots.rb +88 -0
  120. data/lib/milk_tea/core/parser/attributes.rb +71 -0
  121. data/lib/milk_tea/core/parser/blocks.rb +119 -0
  122. data/lib/milk_tea/core/parser/declarations.rb +749 -0
  123. data/lib/milk_tea/core/parser/expressions.rb +624 -0
  124. data/lib/milk_tea/core/parser/recovery.rb +131 -0
  125. data/lib/milk_tea/core/parser/statements.rb +756 -0
  126. data/lib/milk_tea/core/parser/types.rb +271 -0
  127. data/lib/milk_tea/core/parser.rb +400 -0
  128. data/lib/milk_tea/core/prelude_installer.rb +29 -0
  129. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
  130. data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
  131. data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
  132. data/lib/milk_tea/core/pretty_printer.rb +17 -0
  133. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
  134. data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
  135. data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
  136. data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
  137. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
  138. data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
  139. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
  140. data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
  141. data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
  142. data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
  143. data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
  144. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
  145. data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
  146. data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
  147. data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
  148. data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
  149. data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
  150. data/lib/milk_tea/core/token.rb +26 -0
  151. data/lib/milk_tea/core/token_stream.rb +30 -0
  152. data/lib/milk_tea/core/types/layout.rb +243 -0
  153. data/lib/milk_tea/core/types/predicates.rb +609 -0
  154. data/lib/milk_tea/core/types/registry.rb +83 -0
  155. data/lib/milk_tea/core/types/types.rb +1696 -0
  156. data/lib/milk_tea/core/types/visitor.rb +442 -0
  157. data/lib/milk_tea/core.rb +25 -0
  158. data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
  159. data/lib/milk_tea/dap/protocol.rb +58 -0
  160. data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
  161. data/lib/milk_tea/dap/server/debug_map.rb +291 -0
  162. data/lib/milk_tea/dap/server/handlers.rb +374 -0
  163. data/lib/milk_tea/dap/server/launch.rb +261 -0
  164. data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
  165. data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
  166. data/lib/milk_tea/dap/server/utilities.rb +160 -0
  167. data/lib/milk_tea/dap/server/wire.rb +55 -0
  168. data/lib/milk_tea/dap/server.rb +131 -0
  169. data/lib/milk_tea/dap/session.rb +152 -0
  170. data/lib/milk_tea/dap.rb +6 -0
  171. data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
  172. data/lib/milk_tea/lsp/diagnostics.rb +611 -0
  173. data/lib/milk_tea/lsp/protocol.rb +104 -0
  174. data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
  175. data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
  176. data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
  177. data/lib/milk_tea/lsp/server/completion.rb +1099 -0
  178. data/lib/milk_tea/lsp/server/configuration.rb +167 -0
  179. data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
  180. data/lib/milk_tea/lsp/server/definition.rb +779 -0
  181. data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
  182. data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
  183. data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
  184. data/lib/milk_tea/lsp/server/formatting.rb +575 -0
  185. data/lib/milk_tea/lsp/server/hover.rb +1465 -0
  186. data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
  187. data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
  188. data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
  189. data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
  190. data/lib/milk_tea/lsp/server/progress.rb +47 -0
  191. data/lib/milk_tea/lsp/server/references.rb +433 -0
  192. data/lib/milk_tea/lsp/server/rename.rb +598 -0
  193. data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
  194. data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
  195. data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
  196. data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
  197. data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
  198. data/lib/milk_tea/lsp/server/utilities.rb +520 -0
  199. data/lib/milk_tea/lsp/server.rb +415 -0
  200. data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
  201. data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
  202. data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
  203. data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
  204. data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
  205. data/lib/milk_tea/lsp/workspace/store.rb +154 -0
  206. data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
  207. data/lib/milk_tea/lsp/workspace.rb +127 -0
  208. data/lib/milk_tea/lsp.rb +13 -0
  209. data/lib/milk_tea/packages/atomic_write.rb +63 -0
  210. data/lib/milk_tea/packages/dependency_solver.rb +194 -0
  211. data/lib/milk_tea/packages/graph.rb +139 -0
  212. data/lib/milk_tea/packages/lock.rb +421 -0
  213. data/lib/milk_tea/packages/manager_cli.rb +485 -0
  214. data/lib/milk_tea/packages/manifest.rb +356 -0
  215. data/lib/milk_tea/packages/manifest_editor.rb +134 -0
  216. data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
  217. data/lib/milk_tea/packages/registry_store.rb +420 -0
  218. data/lib/milk_tea/packages/services.rb +41 -0
  219. data/lib/milk_tea/packages/source_cache.rb +71 -0
  220. data/lib/milk_tea/packages/source_fetcher.rb +124 -0
  221. data/lib/milk_tea/packages/source_resolver.rb +389 -0
  222. data/lib/milk_tea/packages/version.rb +164 -0
  223. data/lib/milk_tea/packages.rb +16 -0
  224. data/lib/milk_tea/tooling/asset_pack.rb +141 -0
  225. data/lib/milk_tea/tooling/build.rb +1124 -0
  226. data/lib/milk_tea/tooling/build_cache.rb +240 -0
  227. data/lib/milk_tea/tooling/cli.rb +2688 -0
  228. data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
  229. data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
  230. data/lib/milk_tea/tooling/debug_map.rb +248 -0
  231. data/lib/milk_tea/tooling/docs_app.rb +369 -0
  232. data/lib/milk_tea/tooling/error_formatter.rb +86 -0
  233. data/lib/milk_tea/tooling/formatter.rb +787 -0
  234. data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
  235. data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
  236. data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
  237. data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
  238. data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
  239. data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
  240. data/lib/milk_tea/tooling/linter/rules.rb +593 -0
  241. data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
  242. data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
  243. data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
  244. data/lib/milk_tea/tooling/linter.rb +798 -0
  245. data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
  246. data/lib/milk_tea/tooling/public/css/docs.css +166 -0
  247. data/lib/milk_tea/tooling/public/js/docs.js +94 -0
  248. data/lib/milk_tea/tooling/run.rb +408 -0
  249. data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
  250. data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
  251. data/lib/milk_tea/tooling/views/404.erb +7 -0
  252. data/lib/milk_tea/tooling/views/index.erb +87 -0
  253. data/lib/milk_tea/tooling/views/layout.erb +69 -0
  254. data/lib/milk_tea/tooling/views/module.erb +97 -0
  255. data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
  256. data/lib/milk_tea/tooling.rb +20 -0
  257. data/lib/milk_tea.rb +8 -0
  258. metadata +426 -0
@@ -0,0 +1,582 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ module ControlFlow
5
+ class Builder
6
+ def initialize(ignore_name: nil, binding_resolution: nil, strict_binding_ids: false, local_decl_without_initializer_writes: false)
7
+ @ignore_name = ignore_name || ->(_name) { false }
8
+ @binding_resolution = binding_resolution
9
+ @strict_binding_ids = strict_binding_ids
10
+ @local_decl_without_initializer_writes = local_decl_without_initializer_writes
11
+ end
12
+
13
+ def build(stmts)
14
+ @graph = Graph.new
15
+ @graph.exit_id = @graph.add_node(kind: :exit)
16
+ @graph.entry_id = build_block(stmts || [], @graph.exit_id, break_target: nil, continue_target: nil)
17
+ @graph
18
+ ensure
19
+ @graph = nil
20
+ end
21
+
22
+ def build_loop_body(stmts)
23
+ @graph = Graph.new
24
+ @graph.exit_id = @graph.add_node(kind: :exit) # fall-through (back-edge)
25
+ break_exit_id = @graph.add_node(kind: :break_exit) # break target
26
+ @graph.entry_id = build_block(stmts || [], @graph.exit_id, break_target: break_exit_id, continue_target: @graph.exit_id)
27
+ @graph
28
+ ensure
29
+ @graph = nil
30
+ end
31
+
32
+ def build_loop_branch(stmts)
33
+ @graph = Graph.new
34
+ @graph.exit_id = @graph.add_node(kind: :exit)
35
+ break_exit_id = @graph.add_node(kind: :break_exit)
36
+ continue_exit_id = @graph.add_node(kind: :continue_exit)
37
+ @graph.entry_id = build_block(
38
+ stmts || [],
39
+ @graph.exit_id,
40
+ break_target: break_exit_id,
41
+ continue_target: continue_exit_id,
42
+ )
43
+ @graph
44
+ ensure
45
+ @graph = nil
46
+ end
47
+
48
+ private
49
+
50
+ def build_block(stmts, next_id, break_target:, continue_target:)
51
+ current_next = next_id
52
+ stmts.reverse_each do |stmt|
53
+ current_next = build_statement(stmt, current_next, break_target:, continue_target:)
54
+ end
55
+ current_next
56
+ end
57
+
58
+ def build_statement(stmt, next_id, break_target:, continue_target:)
59
+ case stmt
60
+ when AST::LocalDecl
61
+ if stmt.else_body
62
+ reads, reads_info = read_identifiers_with_sites(stmt.value)
63
+ writes = Set.new
64
+ writes_info = []
65
+ declaration_key = declaration_binding_key(stmt, stmt.name)
66
+ if declaration_key && (stmt.value || @local_decl_without_initializer_writes)
67
+ writes << declaration_key
68
+ writes_info << { name: stmt.name, binding_key: declaration_key, line: stmt.line, column: stmt.column, origin: :declaration }
69
+ end
70
+
71
+ success_id = add_linear_node(:local_decl, stmt, next_id, writes:, writes_info:)
72
+ null_entry = build_block(stmt.else_body, next_id, break_target:, continue_target:)
73
+ condition_id = @graph.add_node(
74
+ kind: :local_decl_condition,
75
+ statement: stmt,
76
+ line: stmt.line,
77
+ reads:,
78
+ reads_info:,
79
+ )
80
+ @graph.add_edge(condition_id, success_id, label: :non_null)
81
+ @graph.add_edge(condition_id, null_entry, label: :null)
82
+ return condition_id
83
+ end
84
+
85
+ reads, reads_info = read_identifiers_with_sites(stmt.value)
86
+ writes = Set.new
87
+ writes_info = []
88
+ declaration_key = declaration_binding_key(stmt, stmt.name)
89
+ if declaration_key && (stmt.value || @local_decl_without_initializer_writes)
90
+ writes << declaration_key
91
+ writes_info << { name: stmt.name, binding_key: declaration_key, line: stmt.line, column: stmt.column, origin: :declaration }
92
+ end
93
+ add_linear_node(:local_decl, stmt, next_id, reads:, reads_info:, writes:, writes_info:)
94
+ when AST::Assignment
95
+ reads, reads_info = read_identifiers_with_sites(stmt.value)
96
+ merge_read_info!(reads, reads_info, *assignment_target_reads(stmt.target, stmt.operator))
97
+ merge_read_info!(reads, reads_info, *assignment_target_side_effect_reads(stmt.target))
98
+
99
+ writes = Set.new
100
+ writes_info = []
101
+ if stmt.target.is_a?(AST::Identifier)
102
+ target_key = identifier_binding_key(stmt.target)
103
+ if target_key
104
+ writes << target_key
105
+ writes_info << { name: stmt.target.name, binding_key: target_key, line: stmt.line, column: stmt.target.column, origin: :assignment }
106
+ end
107
+ end
108
+ add_linear_node(:assignment, stmt, next_id, reads:, reads_info:, writes:, writes_info:)
109
+ when AST::IfStmt
110
+ fallback_entry = stmt.else_body ? build_block(stmt.else_body, next_id, break_target:, continue_target:) : next_id
111
+ stmt.branches.reverse_each do |branch|
112
+ then_entry = build_block(branch.body, next_id, break_target:, continue_target:)
113
+ branch_reads, branch_reads_info = read_identifiers_with_sites(branch.condition)
114
+ cond_id = @graph.add_node(
115
+ kind: :if_condition,
116
+ statement: branch,
117
+ line: branch.respond_to?(:line) ? branch.line : stmt.line,
118
+ reads: branch_reads,
119
+ reads_info: branch_reads_info,
120
+ )
121
+ @graph.add_edge(cond_id, then_entry, label: :true_branch)
122
+ @graph.add_edge(cond_id, fallback_entry, label: :false_branch)
123
+ emit_null_test_refinements(cond_id, then_entry, fallback_entry, branch.condition)
124
+ fallback_entry = cond_id
125
+ end
126
+ fallback_entry
127
+ when AST::MatchStmt
128
+ match_reads, match_reads_info = read_identifiers_with_sites(stmt.expression)
129
+ match_id = @graph.add_node(
130
+ kind: :match_condition,
131
+ statement: stmt,
132
+ line: stmt.line,
133
+ reads: match_reads,
134
+ reads_info: match_reads_info,
135
+ )
136
+ if stmt.arms.empty?
137
+ @graph.add_edge(match_id, next_id)
138
+ else
139
+ stmt.arms.each do |arm|
140
+ arm_entry = build_block(arm.body, next_id, break_target:, continue_target:)
141
+ if arm.binding_name
142
+ binding_key = declaration_binding_key(arm, arm.binding_name)
143
+ if binding_key
144
+ bind_id = @graph.add_node(
145
+ kind: :match_binding,
146
+ statement: arm,
147
+ line: arm.binding_line || stmt.line,
148
+ writes: Set[binding_key],
149
+ writes_info: [{
150
+ name: arm.binding_name,
151
+ binding_key:,
152
+ line: arm.binding_line || stmt.line,
153
+ column: arm.binding_column,
154
+ origin: :match_binding,
155
+ }],
156
+ )
157
+ @graph.add_edge(bind_id, arm_entry)
158
+ arm_entry = bind_id
159
+ end
160
+ end
161
+ @graph.add_edge(match_id, arm_entry)
162
+ end
163
+ end
164
+ match_id
165
+ when AST::WhileStmt
166
+ condition_reads, condition_reads_info = read_identifiers_with_sites(stmt.condition)
167
+ cond_id = @graph.add_node(
168
+ kind: :while_condition,
169
+ statement: stmt,
170
+ line: stmt.line,
171
+ reads: condition_reads,
172
+ reads_info: condition_reads_info,
173
+ )
174
+ body_entry = build_block(stmt.body, cond_id, break_target: next_id, continue_target: cond_id)
175
+ @graph.add_edge(cond_id, body_entry, label: :true_branch)
176
+ @graph.add_edge(cond_id, next_id, label: :false_branch)
177
+ emit_null_test_refinements(cond_id, body_entry, next_id, stmt.condition)
178
+ cond_id
179
+ when AST::ForStmt
180
+ writes = Set.new
181
+ writes_info = []
182
+ if stmt.name
183
+ loop_key = declaration_binding_key(stmt, stmt.name)
184
+ if loop_key
185
+ writes << loop_key
186
+ writes_info << {
187
+ name: stmt.name,
188
+ binding_key: loop_key,
189
+ line: stmt.line,
190
+ column: stmt.column,
191
+ origin: :for_binding,
192
+ }
193
+ end
194
+ end
195
+ iterable_reads, iterable_reads_info = read_identifiers_with_sites(stmt.iterable)
196
+ header_id = @graph.add_node(
197
+ kind: :for_header,
198
+ statement: stmt,
199
+ line: stmt.line,
200
+ reads: iterable_reads,
201
+ reads_info: iterable_reads_info,
202
+ writes:,
203
+ writes_info:
204
+ )
205
+ body_entry = build_block(stmt.body, header_id, break_target: next_id, continue_target: header_id)
206
+ @graph.add_edge(header_id, body_entry)
207
+ @graph.add_edge(header_id, next_id)
208
+ header_id
209
+ when AST::UnsafeStmt
210
+ build_block(stmt.body, next_id, break_target:, continue_target:)
211
+ when AST::DeferStmt
212
+ body_entry = stmt.body ? build_block(stmt.body, next_id, break_target:, continue_target:) : next_id
213
+ expression_reads, expression_reads_info = stmt.expression ? read_identifiers_with_sites(stmt.expression) : [Set.new, []]
214
+ defer_id = @graph.add_node(
215
+ kind: :defer,
216
+ statement: stmt,
217
+ line: stmt.line,
218
+ reads: expression_reads,
219
+ reads_info: expression_reads_info,
220
+ )
221
+ @graph.add_edge(defer_id, body_entry)
222
+ defer_id
223
+ when AST::ReturnStmt
224
+ return_reads, return_reads_info = read_identifiers_with_sites(stmt.value)
225
+ @graph.add_node(
226
+ kind: :return,
227
+ statement: stmt,
228
+ line: stmt.line,
229
+ reads: return_reads,
230
+ reads_info: return_reads_info,
231
+ )
232
+ when AST::ExpressionStmt
233
+ reads, reads_info = read_identifiers_with_sites(stmt.expression)
234
+ writes, writes_info = write_targets_from_expression(stmt.expression, line: stmt.line)
235
+ if fatal_expression?(stmt.expression)
236
+ @graph.add_node(
237
+ kind: :fatal,
238
+ statement: stmt,
239
+ line: stmt.line,
240
+ reads:,
241
+ reads_info:,
242
+ writes:,
243
+ writes_info:
244
+ )
245
+ else
246
+ add_linear_node(:expression, stmt, next_id, reads:, reads_info:, writes:, writes_info:)
247
+ end
248
+ when AST::StaticAssert
249
+ reads, reads_info = read_identifiers_with_sites(stmt.condition)
250
+ add_linear_node(:static_assert, stmt, next_id, reads:, reads_info:)
251
+ when AST::PassStmt
252
+ add_linear_node(:pass, stmt, next_id)
253
+ when AST::BreakStmt
254
+ target = break_target || next_id
255
+ add_linear_node(:break, stmt, target)
256
+ when AST::ContinueStmt
257
+ target = continue_target || next_id
258
+ add_linear_node(:continue, stmt, target)
259
+ else
260
+ add_linear_node(:other, stmt, next_id)
261
+ end
262
+ end
263
+
264
+ def add_linear_node(kind, stmt, next_id, reads: Set.new, reads_info: [], writes: Set.new, writes_info: [])
265
+ node_id = @graph.add_node(kind:, statement: stmt, line: stmt.respond_to?(:line) ? stmt.line : nil, reads:, reads_info:, writes:, writes_info:)
266
+ @graph.add_edge(node_id, next_id)
267
+ node_id
268
+ end
269
+
270
+ def fatal_expression?(expression)
271
+ expression.is_a?(AST::Call) && expression.callee.is_a?(AST::Identifier) && expression.callee.name == "fatal"
272
+ end
273
+
274
+ def assignment_target_reads(target, operator)
275
+ return [Set.new, []] if operator == "="
276
+ return identifier_read_info(target) if target.is_a?(AST::Identifier)
277
+
278
+ assignment_target_side_effect_reads(target)
279
+ end
280
+
281
+ def assignment_target_side_effect_reads(target)
282
+ case target
283
+ when AST::Identifier
284
+ [Set.new, []]
285
+ when AST::MemberAccess
286
+ read_identifiers_with_sites(target.receiver)
287
+ when AST::IndexAccess
288
+ receiver_reads, receiver_info = read_identifiers_with_sites(target.receiver)
289
+ merge_read_info!(receiver_reads, receiver_info, *read_identifiers_with_sites(target.index))
290
+ [receiver_reads, receiver_info]
291
+ else
292
+ read_identifiers_with_sites(target)
293
+ end
294
+ end
295
+
296
+ def merge_read_info!(reads, reads_info, extra_reads, extra_reads_info)
297
+ reads.merge(extra_reads)
298
+ reads_info.concat(extra_reads_info)
299
+ end
300
+
301
+ def identifier_read_info(identifier)
302
+ key = identifier_binding_key(identifier)
303
+ return [Set.new, []] unless key
304
+
305
+ [Set[key], [ReadSite.new(binding_key: key, line: identifier.line, column: identifier.column, length: identifier.name.to_s.length)]]
306
+ end
307
+
308
+ def read_identifiers_with_sites(expression)
309
+ reads = Set.new
310
+ reads_info = []
311
+ read_identifiers(expression, reads, reads_info)
312
+ [reads, reads_info]
313
+ end
314
+
315
+ def read_identifiers(expression, names = Set.new, reads_info = [])
316
+ case expression
317
+ when nil
318
+ nil
319
+ when AST::Identifier
320
+ key = identifier_binding_key(expression)
321
+ if key
322
+ names << key
323
+ reads_info << ReadSite.new(binding_key: key, line: expression.line, column: expression.column, length: expression.name.to_s.length)
324
+ end
325
+ when AST::MemberAccess
326
+ read_identifiers(expression.receiver, names, reads_info)
327
+ when AST::IndexAccess
328
+ read_identifiers(expression.receiver, names, reads_info)
329
+ read_identifiers(expression.index, names, reads_info)
330
+ when AST::Specialization
331
+ read_identifiers(expression.callee, names, reads_info)
332
+ when AST::Call
333
+ read_identifiers(expression.callee, names, reads_info)
334
+ expression.arguments.each { |argument| read_identifiers(argument.value, names, reads_info) }
335
+ when AST::UnaryOp
336
+ read_identifiers(expression.operand, names, reads_info)
337
+ when AST::BinaryOp
338
+ read_identifiers(expression.left, names, reads_info)
339
+ read_identifiers(expression.right, names, reads_info)
340
+ when AST::RangeExpr
341
+ read_identifiers(expression.start_expr, names, reads_info)
342
+ read_identifiers(expression.end_expr, names, reads_info)
343
+ when AST::ExpressionList
344
+ expression.elements.each { |element| read_identifiers(element, names, reads_info) }
345
+ when AST::IfExpr
346
+ read_identifiers(expression.condition, names, reads_info)
347
+ read_identifiers(expression.then_expression, names, reads_info)
348
+ read_identifiers(expression.else_expression, names, reads_info)
349
+ when AST::MatchExpr
350
+ read_identifiers(expression.expression, names, reads_info)
351
+ expression.arms.each do |arm|
352
+ read_identifiers(arm.pattern, names, reads_info)
353
+ read_identifiers(arm.value, names, reads_info)
354
+ end
355
+ when AST::MatchStmt
356
+ read_identifiers(expression.expression, names, reads_info)
357
+ when AST::UnsafeExpr
358
+ read_identifiers(expression.expression, names, reads_info)
359
+ when AST::FormatString
360
+ expression.parts.each do |part|
361
+ read_identifiers(part.expression, names, reads_info) if part.is_a?(AST::FormatExprPart)
362
+ end
363
+ when AST::ProcExpr
364
+ nil
365
+ when AST::AwaitExpr
366
+ read_identifiers(expression.expression, names, reads_info)
367
+ when AST::IntegerLiteral, AST::FloatLiteral, AST::StringLiteral,
368
+ AST::BooleanLiteral, AST::NullLiteral,
369
+ AST::SizeofExpr, AST::AlignofExpr, AST::OffsetofExpr
370
+ nil
371
+ else
372
+ nil
373
+ end
374
+ names
375
+ end
376
+
377
+ def write_targets_from_expression(expression, line: nil, writes: Set.new, writes_info: [])
378
+ case expression
379
+ when nil
380
+ nil
381
+ when AST::MemberAccess
382
+ write_targets_from_expression(expression.receiver, line:, writes:, writes_info:)
383
+ when AST::IndexAccess
384
+ write_targets_from_expression(expression.receiver, line:, writes:, writes_info:)
385
+ write_targets_from_expression(expression.index, line:, writes:, writes_info:)
386
+ when AST::Specialization
387
+ write_targets_from_expression(expression.callee, line:, writes:, writes_info:)
388
+ when AST::Call
389
+ write_targets_from_expression(expression.callee, line:, writes:, writes_info:)
390
+ expression.arguments.each do |argument|
391
+ value = argument.value
392
+ target_identifier = nil
393
+ if value.is_a?(AST::UnaryOp) && %w[out inout].include?(value.operator) && value.operand.is_a?(AST::Identifier)
394
+ target_identifier = value.operand
395
+ elsif value.is_a?(AST::Identifier) && mutating_argument_identifier?(value)
396
+ target_identifier = value
397
+ else
398
+ target_identifier = call_argument_mutation_target(value)
399
+ end
400
+
401
+ if target_identifier
402
+ key = identifier_binding_key(target_identifier)
403
+ if key
404
+ writes << key
405
+ writes_info << {
406
+ name: target_identifier.name,
407
+ binding_key: key,
408
+ line: line,
409
+ column: nil,
410
+ origin: :call_argument,
411
+ }
412
+ end
413
+ end
414
+ write_targets_from_expression(value, line:, writes:, writes_info:)
415
+ end
416
+ when AST::UnaryOp
417
+ write_targets_from_expression(expression.operand, line:, writes:, writes_info:)
418
+ when AST::BinaryOp
419
+ write_targets_from_expression(expression.left, line:, writes:, writes_info:)
420
+ write_targets_from_expression(expression.right, line:, writes:, writes_info:)
421
+ when AST::RangeExpr
422
+ write_targets_from_expression(expression.start_expr, line:, writes:, writes_info:)
423
+ write_targets_from_expression(expression.end_expr, line:, writes:, writes_info:)
424
+ when AST::ExpressionList
425
+ expression.elements.each do |element|
426
+ write_targets_from_expression(element, line:, writes:, writes_info:)
427
+ end
428
+ when AST::IfExpr
429
+ write_targets_from_expression(expression.condition, line:, writes:, writes_info:)
430
+ write_targets_from_expression(expression.then_expression, line:, writes:, writes_info:)
431
+ write_targets_from_expression(expression.else_expression, line:, writes:, writes_info:)
432
+ when AST::MatchExpr
433
+ write_targets_from_expression(expression.expression, line:, writes:, writes_info:)
434
+ expression.arms.each do |arm|
435
+ write_targets_from_expression(arm.pattern, line:, writes:, writes_info:)
436
+ write_targets_from_expression(arm.value, line:, writes:, writes_info:)
437
+ end
438
+ when AST::UnsafeExpr
439
+ write_targets_from_expression(expression.expression, line:, writes:, writes_info:)
440
+ when AST::FormatString
441
+ expression.parts.each do |part|
442
+ write_targets_from_expression(part.expression, line:, writes:, writes_info:) if part.is_a?(AST::FormatExprPart)
443
+ end
444
+ when AST::AwaitExpr
445
+ write_targets_from_expression(expression.expression, line:, writes:, writes_info:)
446
+ when AST::ProcExpr,
447
+ AST::Identifier,
448
+ AST::IntegerLiteral,
449
+ AST::FloatLiteral,
450
+ AST::StringLiteral,
451
+ AST::BooleanLiteral,
452
+ AST::NullLiteral,
453
+ AST::SizeofExpr,
454
+ AST::AlignofExpr,
455
+ AST::OffsetofExpr
456
+ nil
457
+ else
458
+ nil
459
+ end
460
+
461
+ [writes, writes_info]
462
+ end
463
+
464
+ def mutating_argument_identifier?(expression)
465
+ return false unless expression.is_a?(AST::Identifier)
466
+
467
+ @binding_resolution&.mutating_argument_identifier_ids&.key?(expression.object_id)
468
+ end
469
+
470
+ def call_argument_mutation_target(expression)
471
+ return nil unless expression.is_a?(AST::Call)
472
+ return nil unless expression.callee.is_a?(AST::Identifier)
473
+
474
+ callee_name = expression.callee.name
475
+ return nil unless expression.arguments.length == 1
476
+
477
+ argument_value = expression.arguments.first.value
478
+
479
+ if callee_name == "ref_of"
480
+ return argument_value if argument_value.is_a?(AST::Identifier)
481
+ return nil
482
+ end
483
+
484
+ if callee_name == "ptr_of"
485
+ return argument_value if argument_value.is_a?(AST::Identifier)
486
+ return call_argument_mutation_target(argument_value)
487
+ end
488
+
489
+ if callee_name == "const_ptr_of"
490
+ return call_argument_mutation_target(argument_value)
491
+ end
492
+
493
+ nil
494
+ end
495
+
496
+ def identifier_binding_key(identifier_expression)
497
+ return nil unless identifier_expression.is_a?(AST::Identifier)
498
+ return nil if @ignore_name.call(identifier_expression.name)
499
+
500
+ if @binding_resolution && (id = @binding_resolution.identifier_binding_ids[identifier_expression.object_id])
501
+ return id
502
+ end
503
+
504
+ return nil if @strict_binding_ids
505
+
506
+ identifier_expression.name
507
+ end
508
+
509
+ def declaration_binding_key(node, name)
510
+ return nil if @ignore_name.call(name)
511
+
512
+ if @binding_resolution && (id = @binding_resolution.declaration_binding_ids[node.object_id])
513
+ return id
514
+ end
515
+
516
+ return nil if @strict_binding_ids
517
+
518
+ name
519
+ end
520
+
521
+ def emit_null_test_refinements(cond_id, true_succ, false_succ, condition)
522
+ pairs = null_check_pairs(condition, positive: true)
523
+ return if pairs.empty?
524
+
525
+ true_refs = {}
526
+ false_refs = {}
527
+ pairs.each do |key, direction|
528
+ if direction == :non_null_if_true
529
+ true_refs[key] = :non_null
530
+ false_refs[key] = :null
531
+ else # :null_if_true
532
+ true_refs[key] = :null
533
+ false_refs[key] = :non_null
534
+ end
535
+ end
536
+ @graph.set_edge_refinement(cond_id, true_succ, true_refs) unless true_refs.empty?
537
+ @graph.set_edge_refinement(cond_id, false_succ, false_refs) unless false_refs.empty?
538
+ end
539
+
540
+ def null_check_pairs(expr, positive:)
541
+ case expr
542
+ when AST::UnaryOp
543
+ return null_check_pairs(expr.operand, positive: !positive) if expr.operator == "not"
544
+ when AST::BinaryOp
545
+ case expr.operator
546
+ when "and"
547
+ return merge_null_pairs(null_check_pairs(expr.left, positive:), null_check_pairs(expr.right, positive:)) if positive
548
+ when "or"
549
+ return merge_null_pairs(null_check_pairs(expr.left, positive:), null_check_pairs(expr.right, positive:)) unless positive
550
+ when "=="
551
+ return single_null_pair(expr, positive:, null_on_true: true)
552
+ when "!="
553
+ return single_null_pair(expr, positive:, null_on_true: false)
554
+ end
555
+ end
556
+ {}
557
+ end
558
+
559
+ def single_null_pair(binary_expr, positive:, null_on_true:)
560
+ identifier =
561
+ if binary_expr.left.is_a?(AST::Identifier) && binary_expr.right.is_a?(AST::NullLiteral)
562
+ binary_expr.left
563
+ elsif binary_expr.left.is_a?(AST::NullLiteral) && binary_expr.right.is_a?(AST::Identifier)
564
+ binary_expr.right
565
+ end
566
+ return {} unless identifier
567
+
568
+ key = identifier_binding_key(identifier)
569
+ return {} unless key
570
+
571
+ direction = null_on_true == positive ? :null_if_true : :non_null_if_true
572
+ { key => direction }
573
+ end
574
+
575
+ def merge_null_pairs(left, right)
576
+ left.merge(right) do |_key, left_dir, right_dir|
577
+ left_dir == right_dir ? left_dir : nil
578
+ end.compact
579
+ end
580
+ end
581
+ end
582
+ end