Class: Rigor::Plugin::ContributionIndex

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/plugin/registry.rb

Overview

ADR-52 WD1 — the compiled contribution table. Categorises a loaded plugin set by which per-call contribution paths each plugin actually implements, AND compiles the declarative gates (method names, ‘block_as_methods` verbs, `owns_receivers`) into frozen lookup structures, so the engine’s hot sites discover “no plugin cares about this call” in O(1) instead of O(plugins × rules) — a top hotspot on plugin-heavy projects (GitLab’s 11 plugins, of which only 2 implement any per-call path). Built once per Registry.

Ordering contract: the gates only PRUNE consultations that could not fire (every pruned rule would have failed its own ‘methods:` / `verbs:` check); the engine still iterates the plugin subsets in registry order and each plugin’s rules in declaration order, so the surviving contributions arrive in exactly the order the ungated walk produced — diagnostics stay byte-identical. The receiver-class ancestry match for ‘dynamic_return` still happens per-dispatch inside `Plugin::Base#dynamic_return_type`.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plugins) ⇒ ContributionIndex

Returns a new instance of ContributionIndex.



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/rigor/plugin/registry.rb', line 36

def initialize(plugins)
  compile_memberships(plugins)
  compile_gates
  @block_entries_by_verb = build_block_entries(plugins)
  @owns_receivers = plugins.flat_map { |p| manifest_for(p)&.owns_receivers || [] }.uniq.freeze
  # Per-run ancestry verdict memo, keyed by environment identity
  # then class name. Mutable inside the frozen index — sound
  # because the class graph is fixed for the lifetime of a run.
  @owns_receiver_memo = {}.compare_by_identity
  freeze
end

Instance Attribute Details

#for_file_diagnosticsObject (readonly)

Ordered (registry-order) subsets relevant to each collector, and the membership sets used to gate the paths within a plugin. ‘for_file_diagnostics` is the subset the runner’s per-file diagnostic loop visits: plugins overriding ‘#diagnostics_for_file` or declaring at least one `node_rule`.



31
32
33
# File 'lib/rigor/plugin/registry.rb', line 31

def for_file_diagnostics
  @for_file_diagnostics
end

#for_method_dispatchObject (readonly)

Ordered (registry-order) subsets relevant to each collector, and the membership sets used to gate the paths within a plugin. ‘for_file_diagnostics` is the subset the runner’s per-file diagnostic loop visits: plugins overriding ‘#diagnostics_for_file` or declaring at least one `node_rule`.



31
32
33
# File 'lib/rigor/plugin/registry.rb', line 31

def for_method_dispatch
  @for_method_dispatch
end

#for_statementObject (readonly)

Ordered (registry-order) subsets relevant to each collector, and the membership sets used to gate the paths within a plugin. ‘for_file_diagnostics` is the subset the runner’s per-file diagnostic loop visits: plugins overriding ‘#diagnostics_for_file` or declaring at least one `node_rule`.



31
32
33
# File 'lib/rigor/plugin/registry.rb', line 31

def for_statement
  @for_statement
end

Instance Method Details

#block_entries_for(verb) ⇒ Object

The ‘Macro::BlockAsMethod` entries whose `verbs` include `verb`, in (plugin registration, manifest declaration) order — the same first-match order the previous plugins × entries walk visited.



94
95
96
# File 'lib/rigor/plugin/registry.rb', line 94

def block_entries_for(verb)
  @block_entries_by_verb.fetch(verb, EMPTY_BLOCK_ENTRIES)
end

#dispatch_candidate?(method_name) ⇒ Boolean

O(1) “could any plugin contribute a return type for a call named ‘method_name`?” — false only when every `dynamic_return` rule is `methods:`-gated on other names, in which case the ungated walk would have produced zero contributions too.

Returns:

  • (Boolean)


55
56
57
58
59
# File 'lib/rigor/plugin/registry.rb', line 55

def dispatch_candidate?(method_name)
  return true if @dynamic_global_gate.nil?

  @dynamic_global_gate.include?(method_name)
end

#dynamic?(plugin) ⇒ Boolean

Returns:

  • (Boolean)


48
# File 'lib/rigor/plugin/registry.rb', line 48

def dynamic?(plugin) = @dynamic.include?(plugin)

#dynamic_candidate_for?(plugin, method_name) ⇒ Boolean

Per-plugin gate: false when the plugin declares no ‘dynamic_return` rules at all, or when every rule is `methods:`-gated and none lists `method_name` — i.e. when `#dynamic_return_type` would return nil without ever entering a rule block. Subsumes the old `dynamic?(plugin)` membership check at the collector’s call site.

Returns:

  • (Boolean)


75
76
77
78
79
80
# File 'lib/rigor/plugin/registry.rb', line 75

def dynamic_candidate_for?(plugin, method_name)
  return false unless @dynamic.include?(plugin)

  gate = @dynamic_gates[plugin]
  gate.nil? || gate.include?(method_name)
end

#owns_receiver?(class_name, environment) ⇒ Boolean

True when ‘class_name` equals or inherits from any plugin’s manifest-declared ‘owns_receivers:` entry. The union is compiled at build time (almost always empty → O(1) false) and per-class verdicts memoise per environment.

Returns:

  • (Boolean)


102
103
104
105
106
107
108
109
110
# File 'lib/rigor/plugin/registry.rb', line 102

def owns_receiver?(class_name, environment)
  return false if @owns_receivers.empty? || class_name.nil?

  memo = (@owns_receiver_memo[environment] ||= {})
  memo.fetch(class_name) do
    memo[class_name] =
      @owns_receivers.any? { |owner| class_matches_owner?(class_name, owner, environment) }
  end
end

#statement_candidate?(method_name) ⇒ Boolean

O(1) statement-path sibling of #dispatch_candidate? over the ‘type_specifier` rules (which are always `methods:`-gated).

Returns:

  • (Boolean)


63
64
65
66
67
# File 'lib/rigor/plugin/registry.rb', line 63

def statement_candidate?(method_name)
  return true if @type_specifier_global_gate.nil?

  @type_specifier_global_gate.include?(method_name)
end

#type_specifier?(plugin) ⇒ Boolean

Returns:

  • (Boolean)


49
# File 'lib/rigor/plugin/registry.rb', line 49

def type_specifier?(plugin) = @type_specifier.include?(plugin)

#type_specifier_candidate_for?(plugin, method_name) ⇒ Boolean

Per-plugin gate over ‘type_specifier` rules; same contract as #dynamic_candidate_for?.

Returns:

  • (Boolean)


84
85
86
87
88
89
# File 'lib/rigor/plugin/registry.rb', line 84

def type_specifier_candidate_for?(plugin, method_name)
  return false unless @type_specifier.include?(plugin)

  gate = @type_specifier_gates[plugin]
  gate.nil? || gate.include?(method_name)
end