rigortype 0.1.3 → 0.1.5

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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +154 -33
  3. data/lib/rigor/analysis/check_rules.rb +10 -18
  4. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
  5. data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
  6. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  7. data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
  8. data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
  9. data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
  10. data/lib/rigor/analysis/diagnostic.rb +0 -2
  11. data/lib/rigor/analysis/fact_store.rb +26 -6
  12. data/lib/rigor/analysis/result.rb +11 -3
  13. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  14. data/lib/rigor/analysis/run_stats.rb +193 -0
  15. data/lib/rigor/analysis/runner.rb +498 -12
  16. data/lib/rigor/analysis/worker_session.rb +327 -0
  17. data/lib/rigor/builtins/imported_refinements.rb +364 -55
  18. data/lib/rigor/builtins/regex_refinement.rb +17 -12
  19. data/lib/rigor/cache/descriptor.rb +1 -1
  20. data/lib/rigor/cache/rbs_descriptor.rb +3 -1
  21. data/lib/rigor/cache/store.rb +39 -6
  22. data/lib/rigor/cli/diff_command.rb +1 -1
  23. data/lib/rigor/cli/sig_gen_command.rb +173 -0
  24. data/lib/rigor/cli/type_of_command.rb +1 -1
  25. data/lib/rigor/cli/type_scan_renderer.rb +1 -1
  26. data/lib/rigor/cli/type_scan_report.rb +2 -2
  27. data/lib/rigor/cli.rb +61 -3
  28. data/lib/rigor/configuration/dependencies.rb +2 -2
  29. data/lib/rigor/configuration.rb +131 -6
  30. data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
  31. data/lib/rigor/environment/class_registry.rb +12 -3
  32. data/lib/rigor/environment/lockfile_resolver.rb +125 -0
  33. data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
  34. data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
  35. data/lib/rigor/environment/rbs_loader.rb +194 -6
  36. data/lib/rigor/environment/reflection.rb +152 -0
  37. data/lib/rigor/environment.rb +109 -6
  38. data/lib/rigor/flow_contribution/conflict.rb +2 -2
  39. data/lib/rigor/flow_contribution/element.rb +1 -1
  40. data/lib/rigor/flow_contribution/fact.rb +1 -1
  41. data/lib/rigor/flow_contribution/merge_result.rb +1 -1
  42. data/lib/rigor/flow_contribution/merger.rb +3 -3
  43. data/lib/rigor/flow_contribution.rb +2 -2
  44. data/lib/rigor/inference/acceptance.rb +35 -1
  45. data/lib/rigor/inference/block_parameter_binder.rb +0 -2
  46. data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
  47. data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
  48. data/lib/rigor/inference/coverage_scanner.rb +1 -1
  49. data/lib/rigor/inference/expression_typer.rb +77 -11
  50. data/lib/rigor/inference/fallback.rb +1 -1
  51. data/lib/rigor/inference/macro_block_self_type.rb +96 -0
  52. data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
  53. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
  54. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
  55. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
  56. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
  57. data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
  58. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
  59. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
  60. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
  61. data/lib/rigor/inference/method_dispatcher.rb +274 -5
  62. data/lib/rigor/inference/method_parameter_binder.rb +22 -14
  63. data/lib/rigor/inference/narrowing.rb +129 -12
  64. data/lib/rigor/inference/rbs_type_translator.rb +0 -2
  65. data/lib/rigor/inference/scope_indexer.rb +14 -9
  66. data/lib/rigor/inference/statement_evaluator.rb +7 -7
  67. data/lib/rigor/inference/synthetic_method.rb +86 -0
  68. data/lib/rigor/inference/synthetic_method_index.rb +82 -0
  69. data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
  70. data/lib/rigor/plugin/blueprint.rb +60 -0
  71. data/lib/rigor/plugin/io_boundary.rb +0 -2
  72. data/lib/rigor/plugin/loader.rb +5 -3
  73. data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
  74. data/lib/rigor/plugin/macro/external_file.rb +143 -0
  75. data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
  76. data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
  77. data/lib/rigor/plugin/macro.rb +31 -0
  78. data/lib/rigor/plugin/manifest.rb +102 -10
  79. data/lib/rigor/plugin/registry.rb +43 -2
  80. data/lib/rigor/plugin/services.rb +1 -1
  81. data/lib/rigor/plugin/type_node_resolver.rb +52 -0
  82. data/lib/rigor/plugin.rb +2 -0
  83. data/lib/rigor/rbs_extended/reporter.rb +91 -0
  84. data/lib/rigor/rbs_extended.rb +131 -32
  85. data/lib/rigor/scope.rb +25 -8
  86. data/lib/rigor/sig_gen/classification.rb +36 -0
  87. data/lib/rigor/sig_gen/generator.rb +1048 -0
  88. data/lib/rigor/sig_gen/layout_index.rb +108 -0
  89. data/lib/rigor/sig_gen/method_candidate.rb +62 -0
  90. data/lib/rigor/sig_gen/observation_collector.rb +391 -0
  91. data/lib/rigor/sig_gen/observed_call.rb +62 -0
  92. data/lib/rigor/sig_gen/path_mapper.rb +116 -0
  93. data/lib/rigor/sig_gen/renderer.rb +157 -0
  94. data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
  95. data/lib/rigor/sig_gen/write_result.rb +48 -0
  96. data/lib/rigor/sig_gen/writer.rb +530 -0
  97. data/lib/rigor/sig_gen.rb +25 -0
  98. data/lib/rigor/trinary.rb +15 -11
  99. data/lib/rigor/type/bot.rb +6 -3
  100. data/lib/rigor/type/bound_method.rb +79 -0
  101. data/lib/rigor/type/combinator.rb +207 -3
  102. data/lib/rigor/type/constant.rb +13 -0
  103. data/lib/rigor/type/hash_shape.rb +0 -2
  104. data/lib/rigor/type/integer_range.rb +7 -7
  105. data/lib/rigor/type/refined.rb +18 -12
  106. data/lib/rigor/type/top.rb +4 -3
  107. data/lib/rigor/type/union.rb +20 -1
  108. data/lib/rigor/type.rb +1 -0
  109. data/lib/rigor/type_node/generic.rb +68 -0
  110. data/lib/rigor/type_node/identifier.rb +38 -0
  111. data/lib/rigor/type_node/indexed_access.rb +41 -0
  112. data/lib/rigor/type_node/integer_literal.rb +29 -0
  113. data/lib/rigor/type_node/name_scope.rb +52 -0
  114. data/lib/rigor/type_node/resolver_chain.rb +56 -0
  115. data/lib/rigor/type_node/string_literal.rb +32 -0
  116. data/lib/rigor/type_node/symbol_literal.rb +28 -0
  117. data/lib/rigor/type_node/union.rb +42 -0
  118. data/lib/rigor/type_node.rb +29 -0
  119. data/lib/rigor/version.rb +1 -1
  120. data/lib/rigor.rb +2 -0
  121. data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
  122. data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
  123. data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
  124. data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
  125. data/sig/rigor/cli/diff_command.rbs +4 -0
  126. data/sig/rigor/cli/explain_command.rbs +4 -0
  127. data/sig/rigor/cli/sig_gen_command.rbs +4 -0
  128. data/sig/rigor/cli/type_scan_command.rbs +3 -0
  129. data/sig/rigor/environment.rbs +8 -2
  130. data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
  131. data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
  132. data/sig/rigor/inference/builtins.rbs +2 -0
  133. data/sig/rigor/plugin/access_denied_error.rbs +3 -0
  134. data/sig/rigor/plugin/base.rbs +6 -0
  135. data/sig/rigor/plugin/blueprint.rbs +7 -0
  136. data/sig/rigor/plugin/fact_store.rbs +11 -0
  137. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  138. data/sig/rigor/plugin/load_error.rbs +6 -0
  139. data/sig/rigor/plugin/loader.rbs +20 -0
  140. data/sig/rigor/plugin/manifest.rbs +9 -0
  141. data/sig/rigor/plugin/registry.rbs +16 -0
  142. data/sig/rigor/plugin/services.rbs +3 -0
  143. data/sig/rigor/plugin/trust_policy.rbs +4 -0
  144. data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
  145. data/sig/rigor/plugin.rbs +8 -0
  146. data/sig/rigor/scope.rbs +4 -2
  147. data/sig/rigor/type.rbs +28 -6
  148. data/sig/rigor.rbs +35 -2
  149. metadata +90 -1
@@ -57,9 +57,19 @@ module Rigor
57
57
  # `Scope#with_local` / `#with_fact` / `#with_self_type`
58
58
  # propagates it across every derived scope.
59
59
  declared_types, discovered_classes = build_declaration_artifacts(root)
60
+ # Merge the indexer's findings on top of whatever the
61
+ # base scope already carries so callers that seed
62
+ # cross-file class knowledge (e.g. the ADR-14
63
+ # `SigGen::ObservationCollector` pre-walking project
64
+ # `lib/` before scanning `spec/`) keep their seeds
65
+ # alongside the per-file declarations the indexer
66
+ # itself discovers. Indexer-found entries win on
67
+ # collision — same-file declarations are the most
68
+ # specific authority.
69
+ merged_classes = default_scope.discovered_classes.merge(discovered_classes)
60
70
  seeded_scope = default_scope
61
71
  .with_declared_types(declared_types)
62
- .with_discovered_classes(discovered_classes)
72
+ .with_discovered_classes(merged_classes)
63
73
 
64
74
  # Slice 7 phase 2. Pre-pass over every class/module body
65
75
  # to collect the per-class ivar accumulator. Seeded after
@@ -300,7 +310,7 @@ module Rigor
300
310
  accumulator.freeze
301
311
  end
302
312
 
303
- def walk_constant_writes(node, qualified_prefix, default_scope, accumulator) # rubocop:disable Metrics/CyclomaticComplexity
313
+ def walk_constant_writes(node, qualified_prefix, default_scope, accumulator)
304
314
  return unless node.is_a?(Prism::Node)
305
315
 
306
316
  case node
@@ -348,7 +358,7 @@ module Rigor
348
358
  accumulator.transform_values(&:freeze).freeze
349
359
  end
350
360
 
351
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize
361
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
352
362
  def walk_methods(node, qualified_prefix, in_singleton_class, accumulator)
353
363
  return unless node.is_a?(Prism::Node)
354
364
 
@@ -385,7 +395,7 @@ module Rigor
385
395
  walk_methods(child, qualified_prefix, in_singleton_class, accumulator)
386
396
  end
387
397
  end
388
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/AbcSize
398
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
389
399
 
390
400
  # v0.1.2 — when a `Const = Data.define(*sym) do ... end`
391
401
  # / `Const = Struct.new(*sym) do ... end` constant write
@@ -430,7 +440,6 @@ module Rigor
430
440
  accumulator.transform_values(&:freeze).freeze
431
441
  end
432
442
 
433
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
434
443
  def walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator)
435
444
  return unless node.is_a?(Prism::Node)
436
445
 
@@ -462,8 +471,6 @@ module Rigor
462
471
  walk_def_nodes(child, qualified_prefix, in_singleton_class, accumulator)
463
472
  end
464
473
  end
465
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
466
-
467
474
  # v0.0.3 A — sentinel key under which `record_def_node`
468
475
  # files DefNodes that live outside any class / module
469
476
  # body (top-level helpers, `def`s nested inside DSL
@@ -646,7 +653,6 @@ module Rigor
646
653
 
647
654
  # Builds a map `{class_name => {new_name_sym => old_name_sym}}` by
648
655
  # walking the tree for `AliasMethodNode` nodes inside class bodies.
649
- # rubocop:disable Metrics/CyclomaticComplexity
650
656
  def collect_class_alias_map(node, qualified_prefix, accumulator)
651
657
  return accumulator unless node.is_a?(Prism::Node)
652
658
 
@@ -667,7 +673,6 @@ module Rigor
667
673
  node.compact_child_nodes.each { |child| collect_class_alias_map(child, qualified_prefix, accumulator) }
668
674
  accumulator
669
675
  end
670
- # rubocop:enable Metrics/CyclomaticComplexity
671
676
 
672
677
  def record_alias_map_entry(alias_node, qualified_prefix, accumulator)
673
678
  return if qualified_prefix.empty?
@@ -889,7 +889,7 @@ module Rigor
889
889
  # or nil otherwise. Centralised so each per-matcher
890
890
  # decoder can short-circuit on a non-matching outer
891
891
  # call.
892
- def rspec_expectation_target(call_node) # rubocop:disable Metrics/CyclomaticComplexity
892
+ def rspec_expectation_target(call_node)
893
893
  receiver = call_node.receiver
894
894
  return nil unless receiver.is_a?(Prism::CallNode) && receiver.name == :expect
895
895
  return nil unless receiver.receiver.nil?
@@ -967,7 +967,7 @@ module Rigor
967
967
  method_def = resolve_call_method(call_node, current_scope)
968
968
  return current_scope if method_def.nil?
969
969
 
970
- contribution = RbsExtended.read_flow_contribution(method_def)
970
+ contribution = RbsExtended.read_flow_contribution(method_def, environment: current_scope.environment)
971
971
  return current_scope if contribution.nil?
972
972
 
973
973
  result = Rigor::FlowContribution::Merger.merge([contribution])
@@ -1034,7 +1034,7 @@ module Rigor
1034
1034
  end
1035
1035
  end
1036
1036
 
1037
- def resolve_call_method(call_node, current_scope) # rubocop:disable Metrics/PerceivedComplexity
1037
+ def resolve_call_method(call_node, current_scope)
1038
1038
  receiver_node = call_node.receiver
1039
1039
  receiver_type =
1040
1040
  if receiver_node
@@ -1120,7 +1120,7 @@ module Rigor
1120
1120
  end
1121
1121
  end
1122
1122
 
1123
- def lookup_post_return_arg(call_node, method_def, target_name) # rubocop:disable Metrics/CyclomaticComplexity
1123
+ def lookup_post_return_arg(call_node, method_def, target_name)
1124
1124
  # Plugin-source contributions arrive without an
1125
1125
  # authoritative method_def (the plugin recognised the
1126
1126
  # call shape directly). Parameter-targeting falls back
@@ -1394,6 +1394,8 @@ module Rigor
1394
1394
  .with_class_ivars(scope.class_ivars)
1395
1395
  .with_class_cvars(scope.class_cvars)
1396
1396
  .with_program_globals(scope.program_globals)
1397
+ .with_discovered_methods(scope.discovered_methods)
1398
+ .with_discovered_method_visibilities(scope.discovered_method_visibilities)
1397
1399
  end
1398
1400
 
1399
1401
  def singleton_def?(def_node)
@@ -1501,7 +1503,7 @@ module Rigor
1501
1503
  EXIT_CALL_NAMES = %i[raise throw exit abort fail].freeze
1502
1504
  private_constant :EXIT_CALL_NAMES
1503
1505
 
1504
- def branch_unconditionally_exits?(node) # rubocop:disable Metrics/CyclomaticComplexity
1506
+ def branch_unconditionally_exits?(node)
1505
1507
  return false if node.nil?
1506
1508
 
1507
1509
  case node
@@ -1607,7 +1609,6 @@ module Rigor
1607
1609
  # Returns an array of `[Symbol, Rigor::Type]` pairs for every
1608
1610
  # variable captured by `pattern`. Unrecognised pattern nodes
1609
1611
  # contribute no bindings (fail-soft).
1610
- # rubocop:disable Metrics/CyclomaticComplexity
1611
1612
  def collect_in_pattern_bindings(subject, pattern, scope)
1612
1613
  case pattern
1613
1614
  when Prism::CapturePatternNode
@@ -1629,7 +1630,6 @@ module Rigor
1629
1630
  []
1630
1631
  end
1631
1632
  end
1632
- # rubocop:enable Metrics/CyclomaticComplexity
1633
1633
 
1634
1634
  def collect_array_pattern_bindings(pattern, scope)
1635
1635
  bindings = [*pattern.requireds, *pattern.posts].flat_map do |elem|
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Inference
5
+ # ADR-16 Tier C output — one synthetic method declared by a
6
+ # plugin's `Plugin::Macro::HeredocTemplate` entry, after the
7
+ # pre-pass has interpolated the call-site literal symbol into
8
+ # the template name. Stored in {SyntheticMethodIndex} and
9
+ # consulted by {MethodDispatcher} below the RBS dispatch tier.
10
+ #
11
+ # Per ADR-16 § WD13 (cost-bounded best-effort): the v0.1.x
12
+ # delivery commitment is the floor — method names emit; their
13
+ # return types degrade to `Dynamic[T]` until slice 6
14
+ # (precision promotion) routes the recorded `return_type`
15
+ # string through ADR-13's `Plugin::TypeNodeResolver` chain.
16
+ # The string is preserved so the ceiling slice can resolve it
17
+ # without re-walking.
18
+ #
19
+ # The `provenance` Hash carries debug / `--explain` metadata:
20
+ # plugin id, the template's call shape, and the source
21
+ # location of the originating DSL call. Surfaced through the
22
+ # dispatcher's `macro.tier_c.*` provenance markers.
23
+ class SyntheticMethod
24
+ INSTANCE = :instance
25
+ SINGLETON = :singleton
26
+ VALID_KINDS = [INSTANCE, SINGLETON].freeze
27
+
28
+ attr_reader :class_name, :method_name, :return_type, :kind, :provenance
29
+
30
+ def initialize(class_name:, method_name:, return_type:, kind: INSTANCE, provenance: {})
31
+ validate!(class_name, method_name, return_type, kind, provenance)
32
+ @class_name = class_name.dup.freeze
33
+ @method_name = method_name.to_sym
34
+ @return_type = return_type.dup.freeze
35
+ @kind = kind
36
+ @provenance = provenance.transform_keys(&:to_sym).transform_values do |v|
37
+ v.is_a?(String) ? v.dup.freeze : v
38
+ end.freeze
39
+ freeze
40
+ end
41
+
42
+ def instance? = kind == INSTANCE
43
+ def singleton? = kind == SINGLETON
44
+
45
+ def to_h
46
+ {
47
+ "class_name" => class_name,
48
+ "method_name" => method_name.to_s,
49
+ "return_type" => return_type,
50
+ "kind" => kind.to_s,
51
+ "provenance" => provenance.transform_keys(&:to_s)
52
+ }
53
+ end
54
+
55
+ def ==(other)
56
+ other.is_a?(SyntheticMethod) && to_h == other.to_h
57
+ end
58
+ alias eql? ==
59
+
60
+ def hash
61
+ to_h.hash
62
+ end
63
+
64
+ private
65
+
66
+ def validate!(class_name, method_name, return_type, kind, provenance)
67
+ unless class_name.is_a?(String) && !class_name.empty?
68
+ raise ArgumentError, "SyntheticMethod#class_name must be non-empty String, got #{class_name.inspect}"
69
+ end
70
+ unless method_name.is_a?(Symbol) || (method_name.is_a?(String) && !method_name.empty?)
71
+ raise ArgumentError, "SyntheticMethod#method_name must be Symbol/non-empty String, got #{method_name.inspect}"
72
+ end
73
+ unless return_type.is_a?(String) && !return_type.empty?
74
+ raise ArgumentError, "SyntheticMethod#return_type must be non-empty String, got #{return_type.inspect}"
75
+ end
76
+ unless VALID_KINDS.include?(kind)
77
+ raise ArgumentError, "SyntheticMethod#kind must be one of #{VALID_KINDS.inspect}, got #{kind.inspect}"
78
+ end
79
+
80
+ return if provenance.is_a?(Hash)
81
+
82
+ raise ArgumentError, "SyntheticMethod#provenance must be a Hash, got #{provenance.inspect}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "synthetic_method"
4
+
5
+ module Rigor
6
+ module Inference
7
+ # Frozen, Ractor-shareable lookup table for the synthetic
8
+ # methods emitted by ADR-16 Tier C declarations during a
9
+ # single `Analysis::Runner#run`. Constructed by the pre-pass
10
+ # scanner (see {SyntheticMethodScanner}) and consulted by
11
+ # {MethodDispatcher} below `RbsDispatch.try_dispatch` (per WD13:
12
+ # user-authored RBS overrides substrate synthesis).
13
+ #
14
+ # The index is keyed by `(class_name, method_name, kind)`. A
15
+ # single key may resolve to multiple {SyntheticMethod} records
16
+ # if two plugins emit the same name (e.g. `rigor-dry-struct`
17
+ # and a hypothetical `rigor-dry-struct-extras` both registering
18
+ # the same attribute). Per ADR-16 WD11 / the WD-discussion in
19
+ # `## Open questions` the dispatcher uses first-wins by
20
+ # registration order; this index preserves that order in
21
+ # `lookup`'s return.
22
+ #
23
+ # ## Slice 2b — return-type precision posture
24
+ #
25
+ # The recorded `SyntheticMethod#return_type` is a String
26
+ # (e.g. `"ActiveStorage::Attached::One"`), preserved verbatim
27
+ # from the manifest's emit table. Slice 2b's engine wiring
28
+ # treats every match as returning `Dynamic[T]` per WD13's
29
+ # floor — the recorded string is the input to a later slice's
30
+ # precision promotion via ADR-13's `Plugin::TypeNodeResolver`.
31
+ class SyntheticMethodIndex
32
+ attr_reader :entries
33
+
34
+ def initialize(entries: [])
35
+ unless entries.is_a?(Array) && entries.all?(SyntheticMethod)
36
+ raise ArgumentError,
37
+ "SyntheticMethodIndex#entries must be an Array of SyntheticMethod, got #{entries.inspect}"
38
+ end
39
+
40
+ @entries = Ractor.make_shareable(entries.dup)
41
+ @by_instance = Ractor.make_shareable(bucket(entries, SyntheticMethod::INSTANCE))
42
+ @by_singleton = Ractor.make_shareable(bucket(entries, SyntheticMethod::SINGLETON))
43
+ freeze
44
+ end
45
+
46
+ def empty?
47
+ entries.empty?
48
+ end
49
+
50
+ # Returns an Array of matching {SyntheticMethod} records in
51
+ # plugin-registration order. Empty Array when no plugin has
52
+ # declared a Tier C entry that interpolates to this name.
53
+ def lookup_instance(class_name, method_name)
54
+ @by_instance.fetch([class_name, method_name.to_sym], EMPTY_ROW)
55
+ end
56
+
57
+ def lookup_singleton(class_name, method_name)
58
+ @by_singleton.fetch([class_name, method_name.to_sym], EMPTY_ROW)
59
+ end
60
+
61
+ def to_h
62
+ { "entries" => entries.map(&:to_h) }
63
+ end
64
+
65
+ EMPTY_ROW = [].freeze
66
+
67
+ def bucket(entries, kind)
68
+ h = {}
69
+ entries.each do |entry|
70
+ next unless entry.kind == kind
71
+
72
+ key = [entry.class_name, entry.method_name]
73
+ (h[key] ||= []) << entry
74
+ end
75
+ h.each_value(&:freeze).freeze
76
+ end
77
+ private :bucket
78
+
79
+ EMPTY = new(entries: []).freeze
80
+ end
81
+ end
82
+ end