Class: Rigor::Plugin::Registry

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

Overview

Read-side query API over the plugins loaded for a single Analysis::Runner.run. Constructed by Loader.load and exposed downstream so the contribution merger (slice 3) and diagnostic provenance (slice 5) can iterate over loaded plugin instances in deterministic order.

The registry is read-only after construction; ordering is the order in which Loader resolved configuration entries, which is project-config order with plugin-id alphabetical as the tie-breaker.

ADR-15 Phase 3 — alongside the instantiated plugins, the registry carries blueprints: a frozen, Ractor-shareable Array<Blueprint> that records how to re-instantiate the same plugin set in a worker Ractor. The eventual Phase 4 pool ships blueprints across the boundary and calls Registry.materialize per-Ractor; the live plugins carriage on the coordinator registry stays unchanged.

Constant Summary collapse

EMPTY =

Assigned after the class body completes — Registry.new runs at assignment time and #initialize calls private helpers defined late in the body.

Registry.new.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plugins: [], load_errors: [], blueprints: []) ⇒ Registry

Returns a new instance of Registry.

Parameters:

  • plugins (Array<Rigor::Plugin::Base>) (defaults to: [])

    instantiated plugin instances in deterministic order.

  • load_errors (Array<Rigor::Plugin::LoadError>) (defaults to: [])

    failures surfaced during loading. Each error is also turned into a diagnostic by the runner.

  • blueprints (Array<Rigor::Plugin::Blueprint>) (defaults to: [])

    frozen, Ractor-shareable replay descriptors aligned 1:1 with plugins. The loader fills this in; callers that construct Registry manually MAY pass [] and accept that materialize cannot replay the set.

  • plugins: (Object) (defaults to: [])
  • load_errors: (Object) (defaults to: [])
  • blueprints: (Object) (defaults to: [])


226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/rigor/plugin/registry.rb', line 226

def initialize(plugins: [], load_errors: [], blueprints: [])
  @plugins = plugins.dup.freeze
  @load_errors = load_errors.dup.freeze
  @blueprints = blueprints.dup.freeze
  @contribution_index = ContributionIndex.new(@plugins)
  # ADR-52 WD1 — aggregate queries the engine issues per def / per diagnostic candidate / per path are
  # compiled once here (the registry is frozen, so the flat_map-on-every-call versions re-derived an
  # invariant). `@contracts_by_path` is a mutable per-path memo inside the frozen registry — safe
  # because the contract set and the glob semantics are fixed for the lifetime of the run.
  @additional_initializers = @plugins.flat_map { |p| safe_manifest(p)&.additional_initializers || [] }.freeze
  @open_receivers = @plugins.flat_map { |p| (safe_manifest(p)&.open_receivers || []).map(&:to_s) }.uniq.freeze
  @open_receivers_set = @open_receivers.to_set.freeze
  @protocol_contracts = @plugins.flat_map { |p| safe_protocol_contracts(p) }.freeze
  @contracts_by_path = {}
  # ADR-52 WD4 — the single engine-owned node-rule walk, compiled once per run from the node-rule
  # plugin subset (registry order). The runner reuses it for every file; it builds fresh per-file
  # state internally, so it is safe to freeze and share.
  @node_rule_walk = NodeRuleWalk.new(@plugins)
  freeze
end

Instance Attribute Details

#additional_initializersObject (readonly)

ADR-38 — flat, ordered list of every loaded plugin's manifest-declared Rigor::Plugin::AdditionalInitializer entries. Inference::ScopeIndexer consults the set at its read-before-write nil soundness gate: a def whose name an entry covers, on a class that equals or inherits from the entry's receiver_constraint, is treated like initialize. Compiled at construction (ADR-52 WD1) — consulted per def node, ×2 sites in ScopeIndexer.



336
337
338
# File 'lib/rigor/plugin/registry.rb', line 336

def additional_initializers
  @additional_initializers
end

#blueprintsObject (readonly)

Returns the value of attribute blueprints.

Returns:

  • (Object)


218
219
220
# File 'lib/rigor/plugin/registry.rb', line 218

def blueprints
  @blueprints
end

#contribution_indexObject (readonly)

Returns the value of attribute contribution_index.



218
219
220
# File 'lib/rigor/plugin/registry.rb', line 218

def contribution_index
  @contribution_index
end

#load_errorsObject (readonly)

Returns the value of attribute load_errors.

Returns:

  • (Object)


218
219
220
# File 'lib/rigor/plugin/registry.rb', line 218

def load_errors
  @load_errors
end

#node_rule_walkObject (readonly)

ADR-52 WD4 — the per-run node-rule walk (see NodeRuleWalk).



248
249
250
# File 'lib/rigor/plugin/registry.rb', line 248

def node_rule_walk
  @node_rule_walk
end

#open_receiversObject (readonly)

ADR-26 — the aggregate set of "open" receiver class names declared across loaded plugins (manifest open_receivers:). A class is open when a plugin vouches that it responds beyond its RBS-declared method surface. open_receiver? is the membership predicate Analysis::CheckRules consults to skip the call.undefined-method rule for such a class. Compiled at construction (ADR-52 WD1) — the predicate runs per undefined-method candidate, so the previous per-call flat_map re-derived a frozen invariant.



314
315
316
# File 'lib/rigor/plugin/registry.rb', line 314

def open_receivers
  @open_receivers
end

#pluginsObject (readonly)

Returns the value of attribute plugins.

Returns:

  • (Object)


218
219
220
# File 'lib/rigor/plugin/registry.rb', line 218

def plugins
  @plugins
end

#protocol_contractsObject (readonly)

ADR-28 — flat, ordered list of every loaded plugin's path-scoped method-protocol contracts, in plugin registration order. Read from each plugin's #protocol_contracts (which the manifest backs by default but a plugin MAY override to fold in per-project config). Consumed by Inference::MethodParameterBinder (the parameter-type provision) and by contributing plugins' #diagnostics_for_file hooks (the presence + return-type check). Compiled at construction (ADR-52 WD1); a plugin's config-folding #protocol_contracts override is stable for the run because config is injected at construction.



329
330
331
# File 'lib/rigor/plugin/registry.rb', line 329

def protocol_contracts
  @protocol_contracts
end

Class Method Details

.materialize(blueprints:, services:) ⇒ Rigor::Plugin::Registry

ADR-15 Phase 3 — build a fresh Registry from the supplied blueprint set by replaying Blueprint#materialize per entry against services. The returned registry carries NEW plugin instances (mutable per-Ractor accumulators included) and the same blueprint set, so a worker can hand the materialised registry to Environment without losing the replay handle. load_errors is intentionally empty: load-time failures already surfaced in the coordinator registry and don't repeat per worker.

Parameters:

  • blueprints: (Object)
  • services: (Object)

Returns:



256
257
258
259
# File 'lib/rigor/plugin/registry.rb', line 256

def self.materialize(blueprints:, services:)
  plugins = blueprints.map { |bp| bp.materialize(services: services) }
  new(plugins: plugins, blueprints: blueprints, load_errors: [])
end

Instance Method Details

#any_load_errors?Boolean

Returns:

  • (Boolean)


274
275
276
# File 'lib/rigor/plugin/registry.rb', line 274

def any_load_errors?
  !load_errors.empty?
end

#contracts_for_path(path) ⇒ Object

ADR-28 — the subset of protocol_contracts whose path_glob matches path. Contract globs are authored project-root-relative (lib/controller/**/*.rb); the analyzer may hand this method either a project-relative path (rigor check run from the project root) or an absolute one (run from elsewhere, or a spec tmpdir), so the glob is matched both directly and as a **/-prefixed path suffix. File::FNM_PATHNAME keeps * from crossing /; File::FNM_EXTGLOB enables {a,b} groups. Returns [] for a nil path so the binder can call this unconditionally. Memoised per path (ADR-52 WD1) — consulted per def node by MethodParameterBinder, and the fnmatch sweep over every contract is pure in (contract set, path).



346
347
348
349
350
351
352
# File 'lib/rigor/plugin/registry.rb', line 346

def contracts_for_path(path)
  return [] if path.nil?

  path_s = path.to_s
  @contracts_by_path[path_s] ||=
    protocol_contracts.select { |contract| path_matches_glob?(contract.path_glob, path_s) }.freeze
end

#empty?Boolean

Returns:

  • (Boolean)


270
271
272
# File 'lib/rigor/plugin/registry.rb', line 270

def empty?
  plugins.empty?
end

#find(id) ⇒ Object

Parameters:

  • id (String, Symbol)

Returns:

  • (Object)


261
262
263
264
# File 'lib/rigor/plugin/registry.rb', line 261

def find(id)
  id_s = id.to_s
  plugins.find { |plugin| plugin.manifest.id == id_s }
end

#hkt_overlay_registryObject

ADR-20 slice 6 — aggregate every loaded plugin's manifest-declared HKT registrations + definitions into a single Inference::HktRegistry overlay that Environment#hkt_registry merges on top of the bundled Builtins::HktBuiltins.registry. Last plugin to register a URI wins (registration order determined by the user's plugins: list); user .rbs overlays merge on top of this overlay last. Returns Inference::HktRegistry::EMPTY when no plugin contributes HKT entries so callers can skip the merge.



292
293
294
295
296
297
298
# File 'lib/rigor/plugin/registry.rb', line 292

def hkt_overlay_registry
  registrations = plugins.flat_map { |plugin| plugin.manifest.hkt_registrations }
  definitions = plugins.flat_map { |plugin| plugin.manifest.hkt_definitions }
  return Inference::HktRegistry::EMPTY if registrations.empty? && definitions.empty?

  Inference::HktRegistry.new(registrations: registrations, definitions: definitions)
end

#idsArray[String]

Returns:

  • (Array[String])


266
267
268
# File 'lib/rigor/plugin/registry.rb', line 266

def ids
  plugins.map { |plugin| plugin.manifest.id }
end

#open_receiver?(class_name) ⇒ Boolean

Returns:

  • (Boolean)


316
317
318
319
320
# File 'lib/rigor/plugin/registry.rb', line 316

def open_receiver?(class_name)
  return false if class_name.nil?

  @open_receivers_set.include?(class_name.to_s)
end

#signature_pathsObject

ADR-25 — flat, ordered list of every loaded plugin's resolved RBS signature directories (absolute paths), in plugin registration order. Environment.for_project merges these into the signature-path set fed to RbsLoader, alongside the configuration's signature_paths: and the bundler: / rbs_collection: discovery output.



304
305
306
# File 'lib/rigor/plugin/registry.rb', line 304

def signature_paths
  plugins.flat_map(&:signature_paths)
end

#source_rbs_synthesizersObject

ADR-32 WD4 + WD5 — flat ordered list of [plugin, callable] pairs for every loaded plugin that declares a source_rbs_synthesizer: in its manifest. The engine invokes each callable once per analysed Ruby source file at env-build time; non-nil return strings are merged into the RBS environment as virtual signature sources. The full plugin instance is carried alongside the callable so the engine's cache layer (WD5) can compose plugin.plugin_entry into its per-file descriptor — a config change to the plugin (e.g. flipping require_magic_comment:) invalidates the dependent synthesizer cache without any plugin-side bookkeeping.



361
362
363
364
365
366
367
368
# File 'lib/rigor/plugin/registry.rb', line 361

def source_rbs_synthesizers
  plugins.filter_map do |plugin|
    synthesizer = plugin.manifest.source_rbs_synthesizer
    next nil if synthesizer.nil?

    [plugin, synthesizer]
  end
end

#type_node_resolversArray[untyped]

ADR-13 — flat ordered list of every loaded plugin's manifest-declared TypeNodeResolver instances, in plugin registration order. Environment#build_name_scope builds a TypeNode::ResolverChain from this list (environment.rb). The first non-nil #resolve(node, scope) return wins per ADR-13 WD3 / WD5 — registration order is the user's lever.

Returns:

  • (Array[untyped])


282
283
284
# File 'lib/rigor/plugin/registry.rb', line 282

def type_node_resolvers
  plugins.flat_map { |plugin| plugin.manifest.type_node_resolvers }
end