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,841 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MilkTea
4
+ class Linter
5
+ module LinterImportsPlatform
6
+ private
7
+
8
+ # ── unused-import ────────────────────────────────────────────────────
9
+
10
+ def check_unused_imports(source_file)
11
+ return if source_file.imports.empty?
12
+
13
+ used = collect_used_names(source_file)
14
+ used.merge(collect_method_only_import_uses(source_file))
15
+ source_file.imports.each do |import|
16
+ next if @unresolved_import_paths.include?(import.path.to_s)
17
+
18
+ local_name = import.alias_name || import.path.parts.last
19
+ next if ignored_binding_name?(local_name)
20
+ next if used.include?(local_name)
21
+
22
+ @warnings << Warning.new(
23
+ path: @path,
24
+ line: import.line,
25
+ column: import.column,
26
+ length: import.length,
27
+ code: "unused-import",
28
+ message: "unused import '#{local_name}'",
29
+ symbol_name: local_name
30
+ )
31
+ end
32
+ end
33
+
34
+ def check_platform_api_drift(source_file)
35
+ resolved_path = self.class.resolve_lint_path(@path)
36
+ return unless resolved_path&.end_with?(".mt")
37
+
38
+ sibling_paths = platform_variant_sibling_paths(resolved_path)
39
+ return if sibling_paths.empty?
40
+
41
+ current_surface_sites = exported_api_surface_sites(source_file)
42
+ current_surface = current_surface_sites.keys.to_set
43
+ sibling_paths.each do |sibling_path|
44
+ sibling_source_file = load_sibling_source_file(sibling_path)
45
+ next unless sibling_source_file
46
+ next unless sibling_source_file.module_name.to_s == source_file.module_name.to_s
47
+
48
+ sibling_surface = exported_api_surface(sibling_source_file)
49
+ next if sibling_surface == current_surface
50
+
51
+ missing = (sibling_surface - current_surface).to_a.sort
52
+ extra = (current_surface - sibling_surface).to_a.sort
53
+ anchor = platform_api_drift_anchor(source_file, current_surface_sites, extra:)
54
+ @warnings << Warning.new(
55
+ path: @path,
56
+ line: anchor[:line],
57
+ column: anchor[:column],
58
+ length: anchor[:length],
59
+ code: "platform-api-drift",
60
+ message: platform_api_drift_message(sibling_path, missing:, extra:),
61
+ symbol_name: anchor[:symbol_name],
62
+ )
63
+ end
64
+ end
65
+ def platform_api_drift_anchor(source_file, current_surface_sites, extra:)
66
+ extra.each do |surface_entry|
67
+ site = current_surface_sites[surface_entry]
68
+ return site if site
69
+ end
70
+
71
+ first_export_site = current_surface_sites.values.min_by do |site|
72
+ [site[:line] || Float::INFINITY, site[:column] || Float::INFINITY]
73
+ end
74
+ return first_export_site if first_export_site
75
+
76
+ first_declaration = source_file.declarations.find { |declaration| declaration.respond_to?(:line) && declaration.line }
77
+ return declaration_anchor(first_declaration) if first_declaration
78
+
79
+ { line: source_file.line || 1, column: 1, length: 1, symbol_name: nil }
80
+ end
81
+
82
+ def exported_api_surface_sites(source_file)
83
+ exported_type_names = exported_type_names(source_file)
84
+ source_file.declarations.each_with_object({}) do |declaration, sites|
85
+ case declaration
86
+ when AST::ConstDecl
87
+ sites[render_value_surface("const", declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
88
+ when AST::VarDecl
89
+ sites[render_value_surface("var", declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
90
+ when AST::EventDecl
91
+ sites[render_event_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
92
+ when AST::TypeAliasDecl
93
+ sites["type #{declaration.name} = #{render_type_surface(declaration.target)}"] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
94
+ when AST::StructDecl
95
+ sites[render_struct_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
96
+ when AST::UnionDecl
97
+ sites[render_union_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
98
+ when AST::EnumDecl
99
+ sites[render_enum_surface("enum", declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
100
+ when AST::FlagsDecl
101
+ sites[render_enum_surface("flags", declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
102
+ when AST::OpaqueDecl
103
+ sites[render_opaque_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
104
+ when AST::InterfaceDecl
105
+ sites[render_interface_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
106
+ when AST::VariantDecl
107
+ sites[render_variant_surface(declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
108
+ when AST::FunctionDef
109
+ sites[render_callable_surface("function", declaration)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
110
+ when AST::ExternFunctionDecl
111
+ sites[render_callable_surface("external function", declaration, variadic: declaration.variadic)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
112
+ when AST::ForeignFunctionDecl
113
+ sites[render_callable_surface("foreign function", declaration, variadic: declaration.variadic)] = declaration_anchor(declaration) if exported_declaration?(source_file, declaration)
114
+ when AST::ExtendingBlock
115
+ next unless exported_type_names.include?(declaration.type_name.to_s)
116
+
117
+ declaration.methods.each do |method|
118
+ next unless method.visibility == :public
119
+
120
+ sites[render_method_surface(declaration.type_name.to_s, method)] = declaration_anchor(method)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ def declaration_anchor(declaration)
127
+ return { line: 1, column: 1, length: 1, symbol_name: nil } unless declaration
128
+
129
+ symbol_name = declaration.respond_to?(:name) ? declaration.name : nil
130
+ length = symbol_name.to_s.empty? ? 1 : symbol_name.to_s.length
131
+ line = declaration.respond_to?(:line) && declaration.line ? declaration.line : 1
132
+
133
+ if declaration.respond_to?(:column) && declaration.column
134
+ return { line:, column: declaration.column, length:, symbol_name: }
135
+ end
136
+
137
+ if symbol_name && line
138
+ line_text = @source_lines[line - 1].to_s
139
+ index = line_text.index(symbol_name.to_s)
140
+ return { line:, column: index ? index + 1 : 1, length:, symbol_name: }
141
+ end
142
+
143
+ { line:, column: 1, length: 1, symbol_name: }
144
+ end
145
+
146
+ def platform_variant_sibling_paths(path)
147
+ current_platform = ModuleLoader.platform_suffix_for_path(path)
148
+ shared_path = if current_platform
149
+ path.sub(/\.#{Regexp.escape(current_platform.to_s)}\.mt\z/, ".mt")
150
+ else
151
+ path
152
+ end
153
+
154
+ [
155
+ shared_path,
156
+ *ModuleLoader::PLATFORM_SUFFIXES.keys.map { |platform_name| shared_path.delete_suffix(".mt") + ".#{platform_name}.mt" }
157
+ ].uniq.select { |candidate| candidate != path && File.file?(candidate) }
158
+ end
159
+
160
+ def load_sibling_source_file(path)
161
+ ModuleLoader.load_file(path)
162
+ rescue StandardError
163
+ nil
164
+ end
165
+
166
+ def exported_api_surface(source_file)
167
+ exported_type_names = exported_type_names(source_file)
168
+ source_file.declarations.each_with_object(Set.new) do |declaration, surface|
169
+ case declaration
170
+ when AST::ConstDecl
171
+ surface << render_value_surface("const", declaration) if exported_declaration?(source_file, declaration)
172
+ when AST::VarDecl
173
+ surface << render_value_surface("var", declaration) if exported_declaration?(source_file, declaration)
174
+ when AST::EventDecl
175
+ surface << render_event_surface(declaration) if exported_declaration?(source_file, declaration)
176
+ when AST::TypeAliasDecl
177
+ surface << "type #{declaration.name} = #{render_type_surface(declaration.target)}" if exported_declaration?(source_file, declaration)
178
+ when AST::StructDecl
179
+ surface << render_struct_surface(declaration) if exported_declaration?(source_file, declaration)
180
+ when AST::UnionDecl
181
+ surface << render_union_surface(declaration) if exported_declaration?(source_file, declaration)
182
+ when AST::EnumDecl
183
+ surface << render_enum_surface("enum", declaration) if exported_declaration?(source_file, declaration)
184
+ when AST::FlagsDecl
185
+ surface << render_enum_surface("flags", declaration) if exported_declaration?(source_file, declaration)
186
+ when AST::OpaqueDecl
187
+ surface << render_opaque_surface(declaration) if exported_declaration?(source_file, declaration)
188
+ when AST::InterfaceDecl
189
+ surface << render_interface_surface(declaration) if exported_declaration?(source_file, declaration)
190
+ when AST::VariantDecl
191
+ surface << render_variant_surface(declaration) if exported_declaration?(source_file, declaration)
192
+ when AST::FunctionDef
193
+ surface << render_callable_surface("function", declaration) if exported_declaration?(source_file, declaration)
194
+ when AST::ExternFunctionDecl
195
+ surface << render_callable_surface("external function", declaration, variadic: declaration.variadic) if exported_declaration?(source_file, declaration)
196
+ when AST::ForeignFunctionDecl
197
+ surface << render_callable_surface("foreign function", declaration, variadic: declaration.variadic) if exported_declaration?(source_file, declaration)
198
+ when AST::ExtendingBlock
199
+ next unless exported_type_names.include?(declaration.type_name.to_s)
200
+
201
+ declaration.methods.each do |method|
202
+ next unless method.visibility == :public
203
+
204
+ surface << render_method_surface(declaration.type_name.to_s, method)
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ def exported_type_names(source_file)
211
+ source_file.declarations.each_with_object(Set.new) do |declaration, names|
212
+ next unless declaration.respond_to?(:name)
213
+ next unless declaration.is_a?(AST::TypeAliasDecl) || declaration.is_a?(AST::StructDecl) ||
214
+ declaration.is_a?(AST::UnionDecl) || declaration.is_a?(AST::EnumDecl) ||
215
+ declaration.is_a?(AST::FlagsDecl) || declaration.is_a?(AST::OpaqueDecl) ||
216
+ declaration.is_a?(AST::InterfaceDecl) || declaration.is_a?(AST::VariantDecl)
217
+ next unless exported_declaration?(source_file, declaration)
218
+
219
+ names << declaration.name
220
+ end
221
+ end
222
+
223
+ def exported_declaration?(source_file, declaration)
224
+ return true if source_file.module_kind == :raw_module
225
+
226
+ declaration.respond_to?(:visibility) && declaration.visibility == :public
227
+ end
228
+
229
+ def render_value_surface(kind, declaration)
230
+ return "#{kind} #{declaration.name}" unless declaration.type
231
+
232
+ "#{kind} #{declaration.name}: #{render_type_surface(declaration.type)}"
233
+ end
234
+
235
+ def render_event_surface(declaration)
236
+ text = +"event #{declaration.name}[#{declaration.capacity}]"
237
+ text << "(#{render_type_surface(declaration.payload_type)})" if declaration.payload_type
238
+ text
239
+ end
240
+
241
+ def render_struct_surface(declaration)
242
+ prefix = render_attribute_applications_surface(declaration.attributes)
243
+ text = +""
244
+ text << "#{prefix} " unless prefix.empty?
245
+ text << "struct #{declaration.name}#{render_type_params_surface(declaration.type_params)}#{render_implements_surface(declaration.implements)}"
246
+ members = []
247
+ members.concat(declaration.fields.map { |field| "#{field.name}: #{render_type_surface(field.type)}" })
248
+ members.concat(declaration.events.map { |event| render_event_surface(event) })
249
+ text << " { #{members.join(', ')} }"
250
+ text
251
+ end
252
+
253
+ def render_union_surface(declaration)
254
+ "union #{declaration.name} { #{render_fields_surface(declaration.fields)} }"
255
+ end
256
+
257
+ def render_enum_surface(kind, declaration)
258
+ members = declaration.members.map(&:name).join(", ")
259
+ "#{kind} #{declaration.name}: #{render_type_surface(declaration.backing_type)} { #{members} }"
260
+ end
261
+
262
+ def render_opaque_surface(declaration)
263
+ "opaque #{declaration.name}#{render_implements_surface(declaration.implements)}"
264
+ end
265
+
266
+ def render_interface_surface(declaration)
267
+ methods = declaration.methods.map { |method| render_interface_method_surface(declaration.name, method) }.sort.join(", ")
268
+ "interface #{declaration.name} { #{methods} }"
269
+ end
270
+
271
+ def render_variant_surface(declaration)
272
+ arms = declaration.arms.map { |arm| render_variant_arm_surface(arm) }.join(", ")
273
+ "variant #{declaration.name}#{render_type_params_surface(declaration.type_params)} { #{arms} }"
274
+ end
275
+
276
+ def render_variant_arm_surface(arm)
277
+ return arm.name if arm.fields.empty?
278
+
279
+ "#{arm.name}(#{arm.fields.map { |field| render_type_surface(field.respond_to?(:type) ? field.type : field) }.join(', ')})"
280
+ end
281
+
282
+ def render_method_surface(receiver_name, declaration)
283
+ prefix = declaration.kind == :static ? "static method" : "method"
284
+ render_callable_surface("#{prefix} #{receiver_name}.", declaration, name_prefix: "")
285
+ end
286
+
287
+ def render_interface_method_surface(interface_name, declaration)
288
+ render_callable_surface("interface method #{interface_name}.", declaration, name_prefix: "")
289
+ end
290
+
291
+ def render_callable_surface(kind, declaration, variadic: false, name_prefix: nil)
292
+ params = declaration.params.map { |param| render_param_surface(param) }
293
+ params << "..." if variadic
294
+ text = +""
295
+ text << "async " if declaration.respond_to?(:async) && declaration.async
296
+ text << kind
297
+ text << (name_prefix.nil? ? " #{declaration.name}" : "#{name_prefix}#{declaration.name}")
298
+ text << render_type_params_surface(declaration.respond_to?(:type_params) ? declaration.type_params : [])
299
+ text << "(#{params.join(', ')})"
300
+ text << " -> #{render_type_surface(declaration.return_type)}" if declaration.return_type
301
+ text
302
+ end
303
+
304
+ def render_fields_surface(fields)
305
+ fields.map { |field| "#{field.name}: #{render_type_surface(field.type)}" }.join(", ")
306
+ end
307
+
308
+ def render_attribute_applications_surface(attributes)
309
+ attributes.map { |attribute| render_attribute_application_surface(attribute) }.join(' ')
310
+ end
311
+
312
+ def render_attribute_application_surface(attribute)
313
+ text = +"@[#{attribute.name}"
314
+ unless attribute.arguments.empty?
315
+ rendered_arguments = attribute.arguments.map do |argument|
316
+ value = case argument.value
317
+ when AST::IntegerLiteral, AST::FloatLiteral
318
+ argument.value.lexeme
319
+ when AST::StringLiteral
320
+ argument.value.lexeme
321
+ when AST::Identifier
322
+ argument.value.name
323
+ when AST::MemberAccess
324
+ "#{argument.value.receiver.name}.#{argument.value.member}"
325
+ else
326
+ "..."
327
+ end
328
+ argument.name ? "#{argument.name} = #{value}" : value
329
+ end
330
+ text << "(#{rendered_arguments.join(', ')})"
331
+ end
332
+ text << "]"
333
+ text
334
+ end
335
+
336
+ def render_param_surface(param)
337
+ if param.respond_to?(:boundary_type) && param.boundary_type
338
+ return "#{param.name}: #{render_type_surface(param.type)} as #{render_type_surface(param.boundary_type)}"
339
+ end
340
+
341
+ return param.name unless param.respond_to?(:type) && param.type
342
+
343
+ "#{param.name}: #{render_type_surface(param.type)}"
344
+ end
345
+
346
+ def render_type_surface(type)
347
+ case type
348
+ when AST::TypeRef
349
+ text = type.name.to_s
350
+ unless type.arguments.empty?
351
+ text += "[#{type.arguments.map { |argument| render_type_argument_surface(argument.value) }.join(', ')}]"
352
+ end
353
+ type.nullable ? "#{text}?" : text
354
+ when AST::FunctionType
355
+ "fn(#{type.params.map { |param| render_param_surface(param) }.join(', ')}) -> #{render_type_surface(type.return_type)}"
356
+ when AST::ProcType
357
+ "proc(#{type.params.map { |param| render_param_surface(param) }.join(', ')}) -> #{render_type_surface(type.return_type)}"
358
+ else
359
+ type.to_s
360
+ end
361
+ end
362
+
363
+ def render_type_argument_surface(argument)
364
+ case argument
365
+ when AST::IntegerLiteral, AST::FloatLiteral
366
+ argument.lexeme
367
+ else
368
+ render_type_surface(argument)
369
+ end
370
+ end
371
+
372
+ def render_type_params_surface(type_params)
373
+ return "" if type_params.nil? || type_params.empty?
374
+
375
+ rendered = type_params.map do |type_param|
376
+ next type_param.name if type_param.constraints.empty?
377
+
378
+ "#{type_param.name} #{render_type_param_constraints_surface(type_param.constraints)}"
379
+ end
380
+ "[#{rendered.join(', ')}]"
381
+ end
382
+
383
+ def render_type_param_constraints_surface(constraints)
384
+ parts = []
385
+ index = 0
386
+ while index < constraints.length
387
+ constraint = constraints[index]
388
+ if constraint.kind == :interface
389
+ interfaces = [constraint.interface_ref.to_s]
390
+ index += 1
391
+ while index < constraints.length && constraints[index].kind == :interface
392
+ interfaces << constraints[index].interface_ref.to_s
393
+ index += 1
394
+ end
395
+ parts << "implements #{interfaces.join(' and ')}"
396
+ else
397
+ raise "unsupported type parameter constraint #{constraint.kind}"
398
+ end
399
+ end
400
+
401
+ parts.join(" and ")
402
+ end
403
+
404
+ def render_implements_surface(implements)
405
+ return "" if implements.nil? || implements.empty?
406
+
407
+ " implements #{implements.map(&:to_s).sort.join(', ')}"
408
+ end
409
+
410
+ def platform_api_drift_message(sibling_path, missing:, extra:)
411
+ parts = []
412
+ parts << "missing #{summarize_platform_surface_entries(missing)}" unless missing.empty?
413
+ parts << "extra #{summarize_platform_surface_entries(extra)}" unless extra.empty?
414
+ "public API differs from #{File.basename(sibling_path)}: #{parts.join('; ')}"
415
+ end
416
+
417
+ def summarize_platform_surface_entries(entries, limit: 2)
418
+ shown = entries.first(limit).map { |entry| "'#{entry}'" }
419
+ remaining = entries.length - shown.length
420
+ return shown.join(", ") if remaining <= 0
421
+
422
+ "#{shown.join(', ')} (+#{remaining} more)"
423
+ end
424
+ # Returns a Set of every "root name" referenced in expressions and type
425
+ # positions across all declarations. Import usage is determined by checking
426
+ # whether the import's local binding name appears as such a root name.
427
+ def collect_used_names(source_file)
428
+ used = Set.new
429
+ source_file.declarations.each do |decl|
430
+ collect_names_from_declaration(decl, used)
431
+ end
432
+ used
433
+ end
434
+
435
+ def collect_method_only_import_uses(source_file)
436
+ return Set.new unless @sema_facts
437
+ return Set.new unless @sema_facts.respond_to?(:binding_resolution) && @sema_facts.binding_resolution
438
+
439
+ binding_resolution = @sema_facts.binding_resolution
440
+ import_module_names = @sema_facts.imports.each_with_object({}) do |(local_name, imported_module), map|
441
+ map[imported_module.name] = local_name
442
+ end
443
+
444
+ used = Set.new
445
+ source_file.declarations.each do |decl|
446
+ collect_indirect_import_uses_from_declaration(decl, used, binding_resolution, import_module_names)
447
+ end
448
+ used
449
+ end
450
+
451
+ def collect_indirect_import_uses_from_declaration(decl, used, binding_resolution, import_module_names)
452
+ case decl
453
+ when AST::FunctionDef, AST::MethodDef
454
+ decl.body&.each { |stmt| collect_indirect_import_uses_from_stmt(stmt, used, binding_resolution, import_module_names) }
455
+ when AST::ExtendingBlock
456
+ decl.methods.each { |m| collect_indirect_import_uses_from_declaration(m, used, binding_resolution, import_module_names) }
457
+ when AST::ConstDecl, AST::VarDecl
458
+ collect_indirect_import_uses_from_expr(decl.value, used, binding_resolution, import_module_names) if decl.value
459
+ when AST::ForeignFunctionDecl
460
+ collect_indirect_import_uses_from_expr(decl.mapping, used, binding_resolution, import_module_names) if decl.mapping
461
+ end
462
+ end
463
+
464
+ def collect_indirect_import_uses_from_stmt(stmt, used, binding_resolution, import_module_names)
465
+ case stmt
466
+ when AST::LocalDecl
467
+ collect_indirect_import_uses_from_expr(stmt.value, used, binding_resolution, import_module_names) if stmt.value
468
+ when AST::Assignment
469
+ collect_indirect_import_uses_from_expr(stmt.target, used, binding_resolution, import_module_names)
470
+ collect_indirect_import_uses_from_expr(stmt.value, used, binding_resolution, import_module_names)
471
+ when AST::IfStmt
472
+ stmt.branches.each do |branch|
473
+ collect_indirect_import_uses_from_expr(branch.condition, used, binding_resolution, import_module_names)
474
+ branch.body&.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
475
+ end
476
+ stmt.else_body&.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
477
+ when AST::MatchStmt
478
+ collect_indirect_import_uses_from_expr(stmt.expression, used, binding_resolution, import_module_names)
479
+ stmt.arms.each { |arm| arm.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) } }
480
+ when AST::ForStmt
481
+ stmt.iterables.each { |iterable| collect_indirect_import_uses_from_expr(iterable, used, binding_resolution, import_module_names) }
482
+ stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
483
+ when AST::WhileStmt
484
+ collect_indirect_import_uses_from_expr(stmt.condition, used, binding_resolution, import_module_names)
485
+ stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
486
+ when AST::UnsafeStmt
487
+ stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
488
+ when AST::DeferStmt
489
+ collect_indirect_import_uses_from_expr(stmt.expression, used, binding_resolution, import_module_names) if stmt.expression
490
+ stmt.body&.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
491
+ when AST::ReturnStmt
492
+ collect_indirect_import_uses_from_expr(stmt.value, used, binding_resolution, import_module_names) if stmt.value
493
+ when AST::ExpressionStmt
494
+ collect_indirect_import_uses_from_expr(stmt.expression, used, binding_resolution, import_module_names)
495
+ when AST::StaticAssert
496
+ collect_indirect_import_uses_from_expr(stmt.condition, used, binding_resolution, import_module_names)
497
+ when AST::WhenStmt
498
+ collect_indirect_import_uses_from_expr(stmt.discriminant, used, binding_resolution, import_module_names)
499
+ stmt.branches.each { |b| b.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) } }
500
+ stmt.else_body&.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) }
501
+ when AST::ErrorBlockStmt
502
+ stmt.body.each { |child| collect_indirect_import_uses_from_stmt(child, used, binding_resolution, import_module_names) } if stmt.body
503
+ end
504
+ end
505
+
506
+ def collect_indirect_import_uses_from_expr(expr, used, binding_resolution, import_module_names)
507
+ case expr
508
+ when nil then nil
509
+ when AST::MemberAccess
510
+ collect_indirect_import_uses_from_expr(expr.receiver, used, binding_resolution, import_module_names)
511
+ when AST::IndexAccess
512
+ collect_indirect_import_uses_from_expr(expr.receiver, used, binding_resolution, import_module_names)
513
+ collect_indirect_import_uses_from_expr(expr.index, used, binding_resolution, import_module_names)
514
+ when AST::Specialization
515
+ collect_hook_provider_import_uses(expr.callee, used)
516
+ collect_indirect_import_uses_from_expr(expr.callee, used, binding_resolution, import_module_names)
517
+ when AST::Call
518
+ if expr.callee.is_a?(AST::MemberAccess)
519
+ import_alias = receiver_import_alias(expr.callee.receiver, binding_resolution, import_module_names)
520
+ used << import_alias if import_alias
521
+ collect_method_provider_import_uses(expr.callee.receiver, expr.callee.member, used, binding_resolution)
522
+ end
523
+ collect_indirect_import_uses_from_expr(expr.callee, used, binding_resolution, import_module_names)
524
+ expr.arguments.each { |argument| collect_indirect_import_uses_from_expr(argument.value, used, binding_resolution, import_module_names) }
525
+ when AST::UnaryOp
526
+ collect_indirect_import_uses_from_expr(expr.operand, used, binding_resolution, import_module_names)
527
+ when AST::BinaryOp
528
+ collect_indirect_import_uses_from_expr(expr.left, used, binding_resolution, import_module_names)
529
+ collect_indirect_import_uses_from_expr(expr.right, used, binding_resolution, import_module_names)
530
+ when AST::RangeExpr
531
+ collect_indirect_import_uses_from_expr(expr.start_expr, used, binding_resolution, import_module_names)
532
+ collect_indirect_import_uses_from_expr(expr.end_expr, used, binding_resolution, import_module_names)
533
+ when AST::ExpressionList
534
+ expr.elements.each { |element| collect_indirect_import_uses_from_expr(element, used, binding_resolution, import_module_names) }
535
+ when AST::IfExpr
536
+ collect_indirect_import_uses_from_expr(expr.condition, used, binding_resolution, import_module_names)
537
+ collect_indirect_import_uses_from_expr(expr.then_expression, used, binding_resolution, import_module_names)
538
+ collect_indirect_import_uses_from_expr(expr.else_expression, used, binding_resolution, import_module_names)
539
+ when AST::MatchExpr
540
+ collect_indirect_import_uses_from_expr(expr.expression, used, binding_resolution, import_module_names)
541
+ expr.arms.each { |arm| collect_indirect_import_uses_from_expr(arm.value, used, binding_resolution, import_module_names) }
542
+ when AST::ProcExpr
543
+ expr.body.each { |stmt| collect_indirect_import_uses_from_stmt(stmt, used, binding_resolution, import_module_names) }
544
+ when AST::AwaitExpr
545
+ collect_indirect_import_uses_from_expr(expr.expression, used, binding_resolution, import_module_names)
546
+ when AST::UnsafeExpr
547
+ collect_indirect_import_uses_from_expr(expr.expression, used, binding_resolution, import_module_names)
548
+ when AST::PrefixCast
549
+ collect_indirect_import_uses_from_expr(expr.expression, used, binding_resolution, import_module_names)
550
+ when AST::FormatString
551
+ expr.parts.each { |part| collect_indirect_import_uses_from_expr(part.expression, used, binding_resolution, import_module_names) if part.is_a?(AST::FormatExprPart) }
552
+ when AST::SizeofExpr, AST::AlignofExpr, AST::OffsetofExpr
553
+ nil
554
+ end
555
+ end
556
+
557
+ def receiver_import_alias(receiver, binding_resolution, import_module_names)
558
+ case receiver
559
+ when AST::Identifier
560
+ binding_id = binding_resolution.identifier_binding_ids[receiver.object_id]
561
+ return nil unless binding_id
562
+
563
+ type = binding_resolution.binding_types[binding_id]
564
+ return nil unless type
565
+
566
+ module_name = type_module_name(type)
567
+ return nil unless module_name
568
+
569
+ import_module_names[module_name]
570
+ end
571
+ end
572
+
573
+ # Credits any imported module that provides the invoked method on the
574
+ # receiver's type. The receiver-type-module heuristic above only credits
575
+ # the module that *defines* the receiver type, so it misses extension
576
+ # methods (`extending str:`, `extending int:`, ...) declared in a
577
+ # different module on a builtin or foreign type.
578
+ # Canonical hook builtins (`hash[T]`, `equal[T]`, `order[T]`, `default[T]`)
579
+ # dispatch to `T.hash` / `T.equal` / `T.order` / `T.default` associated
580
+ # functions that are frequently supplied by an imported extension module
581
+ # (e.g. `import std.hash`, `import std.str`) with no module-qualified
582
+ # reference in the source. Because the type argument may be reflective
583
+ # (`hash[field.type]`) and thus unresolvable here, any import that provides
584
+ # a hook associated function is credited whenever a hook builtin is used.
585
+ # This keeps genuinely-needed hook-provider imports rather than risk
586
+ # auto-removing them.
587
+ HOOK_BUILTIN_NAMES = %w[hash equal order default].freeze
588
+
589
+ def collect_hook_provider_import_uses(callee, used)
590
+ return unless callee.is_a?(AST::Identifier)
591
+ return unless HOOK_BUILTIN_NAMES.include?(callee.name)
592
+ return unless @sema_facts.respond_to?(:imports)
593
+
594
+ @sema_facts.imports.each do |local_name, module_binding|
595
+ next unless module_binding.respond_to?(:methods) && module_binding.methods
596
+
597
+ used << local_name if import_module_provides_hook_method?(module_binding.methods)
598
+ end
599
+ end
600
+
601
+ def import_module_provides_hook_method?(method_map)
602
+ method_map.each_value.any? do |entries|
603
+ entries.keys.any? { |key| HOOK_BUILTIN_NAMES.include?(key.to_s.delete_prefix("static:")) }
604
+ end
605
+ end
606
+
607
+ def collect_method_provider_import_uses(receiver, member_name, used, binding_resolution)
608
+ return unless receiver.is_a?(AST::Identifier)
609
+ return unless @sema_facts.respond_to?(:imports)
610
+
611
+ receiver_type = identifier_binding_type(receiver, binding_resolution)
612
+ return unless receiver_type
613
+
614
+ @sema_facts.imports.each do |local_name, module_binding|
615
+ next unless module_binding.respond_to?(:methods)
616
+ next unless import_module_defines_method?(module_binding, receiver_type, member_name)
617
+
618
+ used << local_name
619
+ end
620
+ end
621
+
622
+ def identifier_binding_type(identifier, binding_resolution)
623
+ binding_id = binding_resolution.identifier_binding_ids[identifier.object_id]
624
+ return nil unless binding_id
625
+
626
+ binding_resolution.binding_types[binding_id]
627
+ end
628
+
629
+ def import_module_defines_method?(module_binding, receiver_type, member_name)
630
+ method_map = module_binding.methods
631
+ return false unless method_map
632
+
633
+ return true if method_map_defines_method?(method_map, receiver_type, member_name)
634
+
635
+ dispatch_type = lint_method_dispatch_receiver_type(receiver_type)
636
+ !dispatch_type.nil? && !dispatch_type.equal?(receiver_type) &&
637
+ method_map_defines_method?(method_map, dispatch_type, member_name)
638
+ end
639
+
640
+ def method_map_defines_method?(method_map, receiver_type, member_name)
641
+ entries = method_map.fetch(receiver_type, nil)
642
+ return false unless entries
643
+
644
+ entries.key?(member_name) || entries.key?("static:#{member_name}")
645
+ end
646
+
647
+ # Mirrors the semantic analyzer's dispatch-receiver erasure so that
648
+ # extension methods on generic instances (whose method maps are keyed by a
649
+ # type-variable form) are matched, not just exact receiver types.
650
+ def lint_method_dispatch_receiver_type(receiver_type)
651
+ case receiver_type
652
+ when Types::GenericInstance
653
+ Types::Registry.generic_instance(
654
+ receiver_type.name,
655
+ receiver_type.arguments.each_with_index.map do |argument, index|
656
+ argument.is_a?(Types::LiteralTypeArg) ? argument : Types::TypeVar.new("__lint_receiver_arg#{index}")
657
+ end,
658
+ )
659
+ when Types::StructInstance
660
+ receiver_type.definition
661
+ when Types::Nullable
662
+ base = receiver_type.base
663
+ base.is_a?(Types::StructInstance) ? Types::Registry.nullable(base.definition) : nil
664
+ end
665
+ rescue StandardError
666
+ nil
667
+ end
668
+
669
+ def collect_qualified_name_root(name, used)
670
+ return unless name.respond_to?(:parts) && name.parts.any?
671
+
672
+ used << name.parts.first
673
+ end
674
+
675
+ def type_module_name(type)
676
+ if type.respond_to?(:definition)
677
+ definition = type.definition
678
+ if definition.respond_to?(:module_name) && definition.module_name
679
+ return definition.module_name
680
+ end
681
+ end
682
+
683
+ type.respond_to?(:module_name) ? type.module_name : nil
684
+ end
685
+
686
+ def collect_names_from_declaration(decl, used)
687
+ case decl
688
+ when AST::FunctionDef, AST::MethodDef
689
+ decl.params.each { |p| collect_names_from_type(p.type, used) if p.respond_to?(:type) && p.type }
690
+ collect_names_from_type(decl.return_type, used) if decl.return_type
691
+ decl.body&.each { |stmt| collect_names_from_statement(stmt, used) }
692
+ when AST::ExtendingBlock
693
+ collect_names_from_type(decl.type_name, used)
694
+ decl.methods.each { |m| collect_names_from_declaration(m, used) }
695
+ when AST::StructDecl
696
+ decl.fields.each { |f| collect_names_from_type(f.type, used) }
697
+ decl.events.each { |event| collect_names_from_type(event.payload_type, used) if event.payload_type }
698
+ decl.implements&.each { |iface| collect_qualified_name_root(iface, used) }
699
+ when AST::UnionDecl
700
+ decl.fields.each { |f| collect_names_from_type(f.type, used) }
701
+ when AST::TypeAliasDecl
702
+ collect_names_from_type(decl.target, used)
703
+ when AST::EventDecl
704
+ collect_names_from_type(decl.payload_type, used) if decl.payload_type
705
+ when AST::ConstDecl, AST::VarDecl
706
+ collect_names_from_type(decl.type, used) if decl.type
707
+ collect_names_from_expr(decl.value, used) if decl.value
708
+ when AST::ExternFunctionDecl
709
+ decl.params.each { |p| collect_names_from_type(p.type, used) if p.respond_to?(:type) && p.type }
710
+ collect_names_from_type(decl.return_type, used) if decl.return_type
711
+ when AST::ForeignFunctionDecl
712
+ decl.params.each { |p| collect_names_from_type(p.type, used) if p.respond_to?(:type) && p.type }
713
+ collect_names_from_type(decl.return_type, used) if decl.return_type
714
+ collect_names_from_expr(decl.mapping, used) if decl.mapping
715
+ when AST::EnumDecl, AST::FlagsDecl
716
+ collect_names_from_type(decl.backing_type, used) if decl.backing_type
717
+ when AST::InterfaceDecl
718
+ decl.methods.each do |method|
719
+ method.params.each { |p| collect_names_from_type(p.type, used) if p.respond_to?(:type) && p.type }
720
+ collect_names_from_type(method.return_type, used) if method.return_type
721
+ end
722
+ when AST::VariantDecl
723
+ decl.arms.each do |arm|
724
+ arm.fields.each { |f| collect_names_from_type(f.type, used) }
725
+ end
726
+ when AST::OpaqueDecl
727
+ decl.implements&.each { |iface| collect_qualified_name_root(iface, used) }
728
+ end
729
+ end
730
+
731
+ def collect_names_from_statement(stmt, used)
732
+ case stmt
733
+ when AST::LocalDecl
734
+ collect_names_from_type(stmt.type, used) if stmt.type
735
+ collect_names_from_expr(stmt.value, used) if stmt.value
736
+ when AST::Assignment
737
+ collect_names_from_expr(stmt.target, used)
738
+ collect_names_from_expr(stmt.value, used)
739
+ when AST::IfStmt
740
+ stmt.branches.each do |b|
741
+ collect_names_from_expr(b.condition, used)
742
+ b.body.each { |s| collect_names_from_statement(s, used) }
743
+ end
744
+ stmt.else_body&.each { |s| collect_names_from_statement(s, used) }
745
+ when AST::MatchStmt
746
+ collect_names_from_expr(stmt.expression, used)
747
+ stmt.arms.each do |arm|
748
+ collect_names_from_expr(arm.pattern, used)
749
+ arm.body.each { |s| collect_names_from_statement(s, used) }
750
+ end
751
+ when AST::ForStmt
752
+ stmt.iterables.each { |iterable| collect_names_from_expr(iterable, used) }
753
+ stmt.body.each { |s| collect_names_from_statement(s, used) }
754
+ when AST::WhileStmt
755
+ collect_names_from_expr(stmt.condition, used)
756
+ stmt.body.each { |s| collect_names_from_statement(s, used) }
757
+ when AST::UnsafeStmt
758
+ stmt.body.each { |s| collect_names_from_statement(s, used) }
759
+ when AST::DeferStmt
760
+ collect_names_from_expr(stmt.expression, used) if stmt.expression
761
+ stmt.body&.each { |s| collect_names_from_statement(s, used) }
762
+ when AST::ReturnStmt
763
+ collect_names_from_expr(stmt.value, used) if stmt.value
764
+ when AST::ExpressionStmt
765
+ collect_names_from_expr(stmt.expression, used)
766
+ when AST::StaticAssert
767
+ collect_names_from_expr(stmt.condition, used)
768
+ when AST::WhenStmt
769
+ collect_names_from_expr(stmt.discriminant, used)
770
+ stmt.branches.each { |b| b.body.each { |s| collect_names_from_statement(s, used) } }
771
+ stmt.else_body&.each { |s| collect_names_from_statement(s, used) }
772
+ when AST::ErrorBlockStmt
773
+ stmt.body.each { |s| collect_names_from_statement(s, used) } if stmt.body
774
+ end
775
+ end
776
+
777
+ def collect_names_from_expr(expr, used)
778
+ case expr
779
+ when nil then nil
780
+ when AST::Identifier then used << expr.name
781
+ when AST::MemberAccess then collect_names_from_expr(expr.receiver, used)
782
+ when AST::IndexAccess
783
+ collect_names_from_expr(expr.receiver, used)
784
+ collect_names_from_expr(expr.index, used)
785
+ when AST::Specialization
786
+ collect_names_from_expr(expr.callee, used)
787
+ when AST::Call
788
+ collect_names_from_expr(expr.callee, used)
789
+ expr.arguments.each { |arg| collect_names_from_expr(arg.value, used) }
790
+ when AST::UnaryOp then collect_names_from_expr(expr.operand, used)
791
+ when AST::BinaryOp
792
+ collect_names_from_expr(expr.left, used)
793
+ collect_names_from_expr(expr.right, used)
794
+ when AST::RangeExpr
795
+ collect_names_from_expr(expr.start_expr, used)
796
+ collect_names_from_expr(expr.end_expr, used)
797
+ when AST::ExpressionList
798
+ expr.elements.each { |e| collect_names_from_expr(e, used) }
799
+ when AST::IfExpr
800
+ collect_names_from_expr(expr.condition, used)
801
+ collect_names_from_expr(expr.then_expression, used)
802
+ collect_names_from_expr(expr.else_expression, used)
803
+ when AST::MatchExpr
804
+ collect_names_from_expr(expr.expression, used)
805
+ expr.arms.each do |arm|
806
+ collect_names_from_expr(arm.pattern, used)
807
+ collect_names_from_expr(arm.value, used)
808
+ end
809
+ when AST::ProcExpr
810
+ expr.params.each { |p| collect_names_from_type(p.type, used) if p.respond_to?(:type) && p.type }
811
+ expr.body.each { |s| collect_names_from_statement(s, used) }
812
+ when AST::AwaitExpr then collect_names_from_expr(expr.expression, used)
813
+ when AST::UnsafeExpr then collect_names_from_expr(expr.expression, used)
814
+ when AST::PrefixCast
815
+ collect_names_from_type(expr.target_type, used)
816
+ collect_names_from_expr(expr.expression, used)
817
+ when AST::FormatString
818
+ expr.parts.each { |p| collect_names_from_expr(p.expression, used) if p.is_a?(AST::FormatExprPart) }
819
+ when AST::SizeofExpr, AST::AlignofExpr
820
+ collect_names_from_type(expr.type, used)
821
+ when AST::OffsetofExpr
822
+ collect_names_from_type(expr.type, used)
823
+ end
824
+ end
825
+
826
+ def collect_names_from_type(type, used)
827
+ case type
828
+ when nil then nil
829
+ when AST::TypeRef
830
+ # Record the root module/alias name (first part of qualified name)
831
+ used << type.name.parts.first if type.name.respond_to?(:parts) && type.name.parts.any?
832
+ type.arguments.each { |arg| collect_names_from_type(arg.value, used) }
833
+ when AST::FunctionType, AST::ProcType
834
+ type.params.each { |p| collect_names_from_type(p.respond_to?(:type) ? p.type : p, used) }
835
+ collect_names_from_type(type.return_type, used) if type.return_type
836
+ end
837
+ end
838
+
839
+ end
840
+ end
841
+ end