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.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/AUTHORS +3 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +21 -0
- data/README.md +1208 -0
- data/Rakefile +332 -0
- data/bin/mtc +8 -0
- data/bin/profile-mtc-checks +133 -0
- data/bin/tracy-profiler +0 -0
- data/docs/build-guide.md +536 -0
- data/docs/index.html +2778 -0
- data/docs/language-design.md +1519 -0
- data/docs/language-manual.md +1534 -0
- data/lib/milk_tea/base.rb +49 -0
- data/lib/milk_tea/bindings/bindgen/ast_parser.rb +398 -0
- data/lib/milk_tea/bindings/bindgen/declaration.rb +319 -0
- data/lib/milk_tea/bindings/bindgen/emitter.rb +387 -0
- data/lib/milk_tea/bindings/bindgen/overrides.rb +134 -0
- data/lib/milk_tea/bindings/bindgen/type_mapper.rb +622 -0
- data/lib/milk_tea/bindings/bindgen.rb +207 -0
- data/lib/milk_tea/bindings/cli.rb +121 -0
- data/lib/milk_tea/bindings/imported_bindings/defaults.rb +210 -0
- data/lib/milk_tea/bindings/imported_bindings/generator.rb +1114 -0
- data/lib/milk_tea/bindings/imported_bindings/method_source.rb +190 -0
- data/lib/milk_tea/bindings/imported_bindings/naming.rb +286 -0
- data/lib/milk_tea/bindings/imported_bindings.rb +215 -0
- data/lib/milk_tea/bindings/opengl_registry.rb +547 -0
- data/lib/milk_tea/bindings/raw_bindings/defaults.rb +1338 -0
- data/lib/milk_tea/bindings/raw_bindings.rb +269 -0
- data/lib/milk_tea/bindings/steamworks.rb +629 -0
- data/lib/milk_tea/bindings/upstream_sources.rb +352 -0
- data/lib/milk_tea/bindings/vendored_box2d.rb +79 -0
- data/lib/milk_tea/bindings/vendored_c_library.rb +322 -0
- data/lib/milk_tea/bindings/vendored_cjson.rb +52 -0
- data/lib/milk_tea/bindings/vendored_flecs.rb +78 -0
- data/lib/milk_tea/bindings/vendored_glfw.rb +77 -0
- data/lib/milk_tea/bindings/vendored_libuv.rb +75 -0
- data/lib/milk_tea/bindings/vendored_pcre2.rb +84 -0
- data/lib/milk_tea/bindings/vendored_raylib.rb +131 -0
- data/lib/milk_tea/bindings/vendored_sdl3.rb +78 -0
- data/lib/milk_tea/bindings/vendored_steamworks.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tool.rb +71 -0
- data/lib/milk_tea/bindings/vendored_tools.rb +23 -0
- data/lib/milk_tea/bindings/vendored_tracy.rb +37 -0
- data/lib/milk_tea/bindings.rb +23 -0
- data/lib/milk_tea/core/ast.rb +311 -0
- data/lib/milk_tea/core/async_runtime_installer.rb +28 -0
- data/lib/milk_tea/core/binding_types.rb +18 -0
- data/lib/milk_tea/core/bindings/attribute_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/function_binding.rb +5 -0
- data/lib/milk_tea/core/bindings/module_binding.rb +58 -0
- data/lib/milk_tea/core/bindings/value_binding.rb +21 -0
- data/lib/milk_tea/core/c_backend/aggregate_utils.rb +103 -0
- data/lib/milk_tea/core/c_backend/control_flow_emission.rb +412 -0
- data/lib/milk_tea/core/c_backend/expressions.rb +468 -0
- data/lib/milk_tea/core/c_backend/feature_detection.rb +483 -0
- data/lib/milk_tea/core/c_backend/reachability.rb +398 -0
- data/lib/milk_tea/core/c_backend/reinterpret.rb +226 -0
- data/lib/milk_tea/core/c_backend/runtime_helpers.rb +1080 -0
- data/lib/milk_tea/core/c_backend/statements.rb +563 -0
- data/lib/milk_tea/core/c_backend/type_collectors.rb +1545 -0
- data/lib/milk_tea/core/c_backend/type_declaration.rb +223 -0
- data/lib/milk_tea/core/c_backend/type_system.rb +345 -0
- data/lib/milk_tea/core/c_backend.rb +287 -0
- data/lib/milk_tea/core/compatibility_helpers.rb +79 -0
- data/lib/milk_tea/core/compile_time/const_eval.rb +187 -0
- data/lib/milk_tea/core/compile_time.rb +329 -0
- data/lib/milk_tea/core/control_flow/builder.rb +582 -0
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +143 -0
- data/lib/milk_tea/core/control_flow/dataflow.rb +74 -0
- data/lib/milk_tea/core/control_flow/definite_assignment.rb +79 -0
- data/lib/milk_tea/core/control_flow/graph.rb +90 -0
- data/lib/milk_tea/core/control_flow/liveness.rb +24 -0
- data/lib/milk_tea/core/control_flow/nullability_flow.rb +43 -0
- data/lib/milk_tea/core/control_flow/reachability.rb +22 -0
- data/lib/milk_tea/core/control_flow/termination.rb +31 -0
- data/lib/milk_tea/core/control_flow.rb +34 -0
- data/lib/milk_tea/core/cst.rb +48 -0
- data/lib/milk_tea/core/cst_builder.rb +19 -0
- data/lib/milk_tea/core/ir.rb +85 -0
- data/lib/milk_tea/core/keywords.rb +97 -0
- data/lib/milk_tea/core/lexer/character_classes.rb +60 -0
- data/lib/milk_tea/core/lexer/format_strings.rb +225 -0
- data/lib/milk_tea/core/lexer/heredocs.rb +199 -0
- data/lib/milk_tea/core/lexer/indentation.rb +62 -0
- data/lib/milk_tea/core/lexer/numbers.rb +96 -0
- data/lib/milk_tea/core/lexer/recovery.rb +32 -0
- data/lib/milk_tea/core/lexer/strings.rb +167 -0
- data/lib/milk_tea/core/lexer/symbols.rb +71 -0
- data/lib/milk_tea/core/lexer/trivia.rb +53 -0
- data/lib/milk_tea/core/lexer.rb +430 -0
- data/lib/milk_tea/core/lowering/artifacts.rb +26 -0
- data/lib/milk_tea/core/lowering/async/analysis.rb +245 -0
- data/lib/milk_tea/core/lowering/async/lowering.rb +1399 -0
- data/lib/milk_tea/core/lowering/async/normalization.rb +459 -0
- data/lib/milk_tea/core/lowering/async.rb +714 -0
- data/lib/milk_tea/core/lowering/block.rb +1052 -0
- data/lib/milk_tea/core/lowering/calls.rb +1565 -0
- data/lib/milk_tea/core/lowering/declarations.rb +214 -0
- data/lib/milk_tea/core/lowering/dyn.rb +206 -0
- data/lib/milk_tea/core/lowering/events.rb +1054 -0
- data/lib/milk_tea/core/lowering/expressions.rb +1645 -0
- data/lib/milk_tea/core/lowering/foreign_cstr.rb +206 -0
- data/lib/milk_tea/core/lowering/functions.rb +242 -0
- data/lib/milk_tea/core/lowering/loops.rb +1087 -0
- data/lib/milk_tea/core/lowering/lowering_context.rb +80 -0
- data/lib/milk_tea/core/lowering/proc.rb +419 -0
- data/lib/milk_tea/core/lowering/resolve.rb +2516 -0
- data/lib/milk_tea/core/lowering/scans.rb +220 -0
- data/lib/milk_tea/core/lowering/str_buffer.rb +125 -0
- data/lib/milk_tea/core/lowering/utils.rb +1453 -0
- data/lib/milk_tea/core/lowering.rb +378 -0
- data/lib/milk_tea/core/module_binder.rb +181 -0
- data/lib/milk_tea/core/module_loader/errors.rb +21 -0
- data/lib/milk_tea/core/module_loader.rb +479 -0
- data/lib/milk_tea/core/module_path_resolver.rb +153 -0
- data/lib/milk_tea/core/module_roots.rb +88 -0
- data/lib/milk_tea/core/parser/attributes.rb +71 -0
- data/lib/milk_tea/core/parser/blocks.rb +119 -0
- data/lib/milk_tea/core/parser/declarations.rb +749 -0
- data/lib/milk_tea/core/parser/expressions.rb +624 -0
- data/lib/milk_tea/core/parser/recovery.rb +131 -0
- data/lib/milk_tea/core/parser/statements.rb +756 -0
- data/lib/milk_tea/core/parser/types.rb +271 -0
- data/lib/milk_tea/core/parser.rb +400 -0
- data/lib/milk_tea/core/prelude_installer.rb +29 -0
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +917 -0
- data/lib/milk_tea/core/pretty_printer/base_formatter.rb +87 -0
- data/lib/milk_tea/core/pretty_printer/ir_formatter.rb +300 -0
- data/lib/milk_tea/core/pretty_printer.rb +17 -0
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +314 -0
- data/lib/milk_tea/core/semantic_analyzer/attributes.rb +184 -0
- data/lib/milk_tea/core/semantic_analyzer/calls.rb +992 -0
- data/lib/milk_tea/core/semantic_analyzer/expressions.rb +1735 -0
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +355 -0
- data/lib/milk_tea/core/semantic_analyzer/foreign_functions.rb +155 -0
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +354 -0
- data/lib/milk_tea/core/semantic_analyzer/generics.rb +383 -0
- data/lib/milk_tea/core/semantic_analyzer/interface_conformance.rb +78 -0
- data/lib/milk_tea/core/semantic_analyzer/module_context.rb +35 -0
- data/lib/milk_tea/core/semantic_analyzer/name_resolution.rb +1438 -0
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +421 -0
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +1308 -0
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +588 -0
- data/lib/milk_tea/core/semantic_analyzer/type_compatibility.rb +307 -0
- data/lib/milk_tea/core/semantic_analyzer/type_declaration.rb +851 -0
- data/lib/milk_tea/core/semantic_analyzer.rb +327 -0
- data/lib/milk_tea/core/token.rb +26 -0
- data/lib/milk_tea/core/token_stream.rb +30 -0
- data/lib/milk_tea/core/types/layout.rb +243 -0
- data/lib/milk_tea/core/types/predicates.rb +609 -0
- data/lib/milk_tea/core/types/registry.rb +83 -0
- data/lib/milk_tea/core/types/types.rb +1696 -0
- data/lib/milk_tea/core/types/visitor.rb +442 -0
- data/lib/milk_tea/core.rb +25 -0
- data/lib/milk_tea/dap/backends/lldb_dap.rb +158 -0
- data/lib/milk_tea/dap/protocol.rb +58 -0
- data/lib/milk_tea/dap/server/breakpoints.rb +66 -0
- data/lib/milk_tea/dap/server/debug_map.rb +291 -0
- data/lib/milk_tea/dap/server/handlers.rb +374 -0
- data/lib/milk_tea/dap/server/launch.rb +261 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +380 -0
- data/lib/milk_tea/dap/server/pause_diagnostics.rb +109 -0
- data/lib/milk_tea/dap/server/utilities.rb +160 -0
- data/lib/milk_tea/dap/server/wire.rb +55 -0
- data/lib/milk_tea/dap/server.rb +131 -0
- data/lib/milk_tea/dap/session.rb +152 -0
- data/lib/milk_tea/dap.rb +6 -0
- data/lib/milk_tea/lsp/dependency_resolution.rb +52 -0
- data/lib/milk_tea/lsp/diagnostics.rb +611 -0
- data/lib/milk_tea/lsp/protocol.rb +104 -0
- data/lib/milk_tea/lsp/server/call_hierarchy.rb +274 -0
- data/lib/milk_tea/lsp/server/code_actions.rb +446 -0
- data/lib/milk_tea/lsp/server/code_lens.rb +97 -0
- data/lib/milk_tea/lsp/server/completion.rb +1099 -0
- data/lib/milk_tea/lsp/server/configuration.rb +167 -0
- data/lib/milk_tea/lsp/server/debug_info.rb +45 -0
- data/lib/milk_tea/lsp/server/definition.rb +779 -0
- data/lib/milk_tea/lsp/server/diagnostics_scheduling.rb +239 -0
- data/lib/milk_tea/lsp/server/execute_command.rb +25 -0
- data/lib/milk_tea/lsp/server/folding_range.rb +153 -0
- data/lib/milk_tea/lsp/server/formatting.rb +575 -0
- data/lib/milk_tea/lsp/server/hover.rb +1465 -0
- data/lib/milk_tea/lsp/server/inlay_hints.rb +204 -0
- data/lib/milk_tea/lsp/server/lifecycle.rb +234 -0
- data/lib/milk_tea/lsp/server/linked_editing_range.rb +43 -0
- data/lib/milk_tea/lsp/server/on_type_formatting.rb +73 -0
- data/lib/milk_tea/lsp/server/progress.rb +47 -0
- data/lib/milk_tea/lsp/server/references.rb +433 -0
- data/lib/milk_tea/lsp/server/rename.rb +598 -0
- data/lib/milk_tea/lsp/server/selection_range.rb +130 -0
- data/lib/milk_tea/lsp/server/semantic_tokens.rb +1745 -0
- data/lib/milk_tea/lsp/server/signature_help.rb +200 -0
- data/lib/milk_tea/lsp/server/text_documents.rb +125 -0
- data/lib/milk_tea/lsp/server/type_hierarchy.rb +167 -0
- data/lib/milk_tea/lsp/server/utilities.rb +520 -0
- data/lib/milk_tea/lsp/server.rb +415 -0
- data/lib/milk_tea/lsp/workspace/analysis.rb +209 -0
- data/lib/milk_tea/lsp/workspace/caches.rb +260 -0
- data/lib/milk_tea/lsp/workspace/collection.rb +98 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +184 -0
- data/lib/milk_tea/lsp/workspace/dependency_graph.rb +282 -0
- data/lib/milk_tea/lsp/workspace/store.rb +154 -0
- data/lib/milk_tea/lsp/workspace/utilities.rb +490 -0
- data/lib/milk_tea/lsp/workspace.rb +127 -0
- data/lib/milk_tea/lsp.rb +13 -0
- data/lib/milk_tea/packages/atomic_write.rb +63 -0
- data/lib/milk_tea/packages/dependency_solver.rb +194 -0
- data/lib/milk_tea/packages/graph.rb +139 -0
- data/lib/milk_tea/packages/lock.rb +421 -0
- data/lib/milk_tea/packages/manager_cli.rb +485 -0
- data/lib/milk_tea/packages/manifest.rb +356 -0
- data/lib/milk_tea/packages/manifest_editor.rb +134 -0
- data/lib/milk_tea/packages/registry_metadata_provider.rb +34 -0
- data/lib/milk_tea/packages/registry_store.rb +420 -0
- data/lib/milk_tea/packages/services.rb +41 -0
- data/lib/milk_tea/packages/source_cache.rb +71 -0
- data/lib/milk_tea/packages/source_fetcher.rb +124 -0
- data/lib/milk_tea/packages/source_resolver.rb +389 -0
- data/lib/milk_tea/packages/version.rb +164 -0
- data/lib/milk_tea/packages.rb +16 -0
- data/lib/milk_tea/tooling/asset_pack.rb +141 -0
- data/lib/milk_tea/tooling/build.rb +1124 -0
- data/lib/milk_tea/tooling/build_cache.rb +240 -0
- data/lib/milk_tea/tooling/cli.rb +2688 -0
- data/lib/milk_tea/tooling/cst_formatter.rb +13 -0
- data/lib/milk_tea/tooling/debug_info_formatter.rb +456 -0
- data/lib/milk_tea/tooling/debug_map.rb +248 -0
- data/lib/milk_tea/tooling/docs_app.rb +369 -0
- data/lib/milk_tea/tooling/error_formatter.rb +86 -0
- data/lib/milk_tea/tooling/formatter.rb +787 -0
- data/lib/milk_tea/tooling/linter/doc_tags.rb +212 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +237 -0
- data/lib/milk_tea/tooling/linter/flow_rules.rb +380 -0
- data/lib/milk_tea/tooling/linter/imports_platform.rb +841 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +744 -0
- data/lib/milk_tea/tooling/linter/reserved_names.rb +199 -0
- data/lib/milk_tea/tooling/linter/rules.rb +593 -0
- data/lib/milk_tea/tooling/linter/source_helpers.rb +407 -0
- data/lib/milk_tea/tooling/linter/trailing_comma.rb +139 -0
- data/lib/milk_tea/tooling/linter/visitors.rb +1286 -0
- data/lib/milk_tea/tooling/linter.rb +798 -0
- data/lib/milk_tea/tooling/project_scaffold.rb +82 -0
- data/lib/milk_tea/tooling/public/css/docs.css +166 -0
- data/lib/milk_tea/tooling/public/js/docs.js +94 -0
- data/lib/milk_tea/tooling/run.rb +408 -0
- data/lib/milk_tea/tooling/templates/wasm_shell.html +48 -0
- data/lib/milk_tea/tooling/toolchain_cli.rb +157 -0
- data/lib/milk_tea/tooling/views/404.erb +7 -0
- data/lib/milk_tea/tooling/views/index.erb +87 -0
- data/lib/milk_tea/tooling/views/layout.erb +69 -0
- data/lib/milk_tea/tooling/views/module.erb +97 -0
- data/lib/milk_tea/tooling/views/stdlib.erb +21 -0
- data/lib/milk_tea/tooling.rb +20 -0
- data/lib/milk_tea.rb +8 -0
- 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
|