Class: Rigor::Environment

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/environment.rb,
lib/rigor/environment/reporters.rb,
lib/rigor/environment/rbs_loader.rb,
lib/rigor/environment/reflection.rb,
lib/rigor/environment/rbs_hierarchy.rb,
lib/rigor/environment/class_registry.rb,
lib/rigor/environment/lockfile_resolver.rb,
lib/rigor/environment/hkt_registry_holder.rb,
lib/rigor/environment/rbs_coverage_report.rb,
lib/rigor/environment/bundle_sig_discovery.rb,
lib/rigor/environment/rbs_collection_discovery.rb,
lib/rigor/environment/constant_type_cache_holder.rb,
lib/rigor/environment/missing_gem_constant_index.rb,
sig/rigor/environment.rbs

Overview

The engine's view of the type universe outside the current scope. Slice 1 exposed only the class registry; Slice 4 added the RBS loader, which threads through ExpressionTyper and MethodDispatcher to type constant references and method calls that the literal-typer and constant-folding tiers cannot answer.

See docs/internal-spec/inference-engine.md for the binding contract.

Defined Under Namespace

Modules: BundleSigDiscovery, LockfileResolver, MissingGemConstantIndex, RbsCollectionDiscovery, RbsCoverageReport Classes: ClassRegistry, ConstantTypeCacheHolder, HktRegistryHolder, RbsHierarchy, RbsLoader, Reflection, Reporters

Constant Summary collapse

DEFAULT_LIBRARIES =

Slice A stdlib expansion. Stdlib libraries that Environment.for_project loads on top of RBS core unless the caller passes an explicit libraries: array. Each entry MUST be a stdlib library name accepted by RBS::EnvironmentLoader#has_library?; unknown libraries MUST fail-soft (RbsLoader#build_env already filters through has_library?). The default set covers the common stdlib surface a Ruby program is likely to import (pathname, optparse, json, yaml, fileutils, tempfile, uri, logger, date) plus the analyzer-adjacent gems shipping their own RBS in this bundle (prism, rbs). On hosts where one of these libraries is not installed, the loader silently drops it.

Callers MAY add to the default by passing libraries: %w[csv ...]; the explicit list is appended to DEFAULT_LIBRARIES and de-duplicated. Callers that need a strictly RBS-core view MUST construct an RbsLoader directly instead of going through for_project.

Returns:

  • (Array[String])
%w[
  pathname optparse json yaml fileutils tempfile tmpdir
  stringio forwardable digest securerandom
  uri logger date
  pp delegate observable abbrev find tsort singleton
  shellwords benchmark base64 did_you_mean
  monitor mutex_m timeout
  open3 erb etc ipaddr bigdecimal bigdecimal-math
  prettyprint random-formatter time open-uri resolv
  csv pstore objspace io-console cgi cgi-escape
  strscan
  prism rbs
].freeze
GEM_OVERLAY_PLUGIN_IDS =

ADR-72 — a Gemfile.lock gem name mapped to the opt-in plugin id that ships the SAME core-ext RBS. When that plugin is loaded the auto-overlay for the gem stands down, so the two never both declare the methods (which would raise a duplicate-declaration error). Keyed on the gem name RbsCoverageReport reports.

{ "activesupport" => "activesupport-core-ext" }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(class_registry: ClassRegistry.default, rbs_loader: nil, plugin_registry: nil, dependency_source_index: nil, rbs_extended_reporter: nil, boundary_cross_reporter: nil, source_rbs_synthesis_reporter: nil, synthetic_method_index: nil, project_patched_methods: nil, hkt_registry: nil, missing_rbs_gems: [], missing_rbs_bundle_path: nil) ⇒ Environment

Returns a new instance of Environment.

Parameters:

  • class_registry (Rigor::Environment::ClassRegistry) (defaults to: ClassRegistry.default)
  • rbs_loader (Rigor::Environment::RbsLoader, nil) (defaults to: nil)

    when nil the environment is "RBS-blind"; useful in tests that want to assert how the engine behaves without RBS data. The default Environment wires the shared core loader, which is itself lazy: requesting an environment instance does NOT load RBS until a method or class query actually consults the loader.

  • plugin_registry (Rigor::Plugin::Registry, nil) (defaults to: nil)

    v0.1.1 Track 2 slice 7. The per-run plugin registry the inference engine consults at call sites for plugin dynamic_return rules. When nil (the default), no plugin-level return-type contribution participates — useful for tests, the Environment.default facade, and analyses that don't load plugins.

  • dependency_source_index (Rigor::Analysis::DependencySourceInference::Index, nil) (defaults to: nil)

    ADR-10 slice 2b-ii. The per-run index of opt-in gem sources the dispatcher consults BELOW RBS dispatch. When nil (the default), no dep-source contribution participates and the dispatcher tier is a no-op.

  • class_registry: (ClassRegistry) (defaults to: ClassRegistry.default)
  • rbs_loader: (RbsLoader, nil) (defaults to: nil)
  • plugin_registry: (Object, nil) (defaults to: nil)
  • dependency_source_index: (Object, nil) (defaults to: nil)
  • rbs_extended_reporter: (Object, nil) (defaults to: nil)
  • boundary_cross_reporter: (Object, nil) (defaults to: nil)
  • source_rbs_synthesis_reporter: (Object, nil) (defaults to: nil)
  • synthetic_method_index: (Object, nil) (defaults to: nil)
  • project_patched_methods: (Object, nil) (defaults to: nil)


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rigor/environment.rb', line 83

def initialize(class_registry: ClassRegistry.default, rbs_loader: nil, # rubocop:disable Metrics/ParameterLists
               plugin_registry: nil, dependency_source_index: nil,
               rbs_extended_reporter: nil, boundary_cross_reporter: nil,
               source_rbs_synthesis_reporter: nil,
               synthetic_method_index: nil, project_patched_methods: nil,
               hkt_registry: nil, missing_rbs_gems: [], missing_rbs_bundle_path: nil)
  @class_registry = class_registry
  @rbs_loader = rbs_loader
  @plugin_registry = plugin_registry
  @dependency_source_index = dependency_source_index
  # ADR-pending — reporters live in a mutable container so long-lived integrations (LSP `ProjectContext`)
  # can swap them per `Runner.run` without rebuilding the env. The existing `#rbs_extended_reporter` /
  # `#boundary_cross_reporter` accessors below preserve the public lookup shape.
  @reporters = Reporters.new(
    rbs_extended: rbs_extended_reporter,
    boundary_cross: boundary_cross_reporter,
    source_rbs_synthesis: source_rbs_synthesis_reporter
  )
  @synthetic_method_index = synthetic_method_index || Inference::SyntheticMethodIndex::EMPTY
  @project_patched_methods = project_patched_methods || Inference::ProjectPatchedMethods::EMPTY
  # ADR-20 slice 2c + 2e — the per-env HKT registry consulted by the reducer when resolving `Type::App`
  # carriers. Defaults to {Inference::HktRegistry::EMPTY}; the {.default} / {.for_project} class methods
  # seed it with the bundled builtins (`json::value`, …) plus any `%a{rigor:v1:hkt_register /
  # hkt_define}` annotations the RBS loader exposes. The hkt_registry getter (defined below) MEMOIZES
  # the result of merging the base with the RBS scan so the scan is paid at most once per Environment
  # lifetime — and only when first consulted, leaving fast paths like `rigor check --cache-stats
  # --no-stats` from doing the RBS env build at all.
  @hkt_registry_base = hkt_registry || Inference::HktRegistry::EMPTY
  @hkt_registry_holder = HktRegistryHolder.new
  @constant_type_cache = ConstantTypeCacheHolder.new
  # ADR-82 WD9 — `[gem_name, version]` pairs for the locked gems with no resolvable RBS. The
  # root-constant ownership index over them is built lazily (first unresolved constant read) so runs
  # whose constants all resolve never pay the entry-file scan.
  @missing_rbs_gems = missing_rbs_gems.freeze
  @missing_rbs_bundle_path = missing_rbs_bundle_path
  @missing_rbs_gem_constants_holder = HktRegistryHolder.new
  @name_scope = build_name_scope
  freeze
end

Instance Attribute Details

#class_registryClassRegistry (readonly)

Returns the value of attribute class_registry.

Returns:



67
68
69
# File 'lib/rigor/environment.rb', line 67

def class_registry
  @class_registry
end

#dependency_source_indexObject? (readonly)

Returns the value of attribute dependency_source_index.

Returns:

  • (Object, nil)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def dependency_source_index
  @dependency_source_index
end

#hkt_registryObject? (readonly)

ADR-20 slices 2e + 6 — lazy HKT registry getter. Merge order on first call: builtins (base) ← plugin manifest aggregation ← RBS env scan. Last-write-wins on URI collisions so user-authored .rbs overlays beat plugin entries, which beat the bundled JSON_VALUE. Memoised; single-threaded use only (under the Ractor pool path each worker has its own Environment so cross-worker mutation is impossible; the LSP single-publish-at-a-time invariant serialises here).

Returns:

  • (Object, nil)


128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/rigor/environment.rb', line 128

def hkt_registry
  @hkt_registry_holder.fetch do
    with_plugin_overlay = if @plugin_registry.respond_to?(:hkt_overlay_registry)
                            @hkt_registry_base.merge(@plugin_registry.hkt_overlay_registry)
                          else
                            @hkt_registry_base
                          end
    Inference::HktRegistry.scan_rbs_loader(
      @rbs_loader,
      base: with_plugin_overlay,
      reporter: rbs_extended_reporter
    )
  end
end

#name_scopeObject? (readonly)

Returns the value of attribute name_scope.

Returns:

  • (Object, nil)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def name_scope
  @name_scope
end

#plugin_registryObject? (readonly)

Returns the value of attribute plugin_registry.

Returns:

  • (Object, nil)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def plugin_registry
  @plugin_registry
end

#project_patched_methodsObject? (readonly)

Returns the value of attribute project_patched_methods.

Returns:

  • (Object, nil)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def project_patched_methods
  @project_patched_methods
end

#rbs_loaderRbsLoader? (readonly)

Returns the value of attribute rbs_loader.

Returns:



67
68
69
# File 'lib/rigor/environment.rb', line 67

def rbs_loader
  @rbs_loader
end

#reportersObject (readonly)

Returns the value of attribute reporters.

Returns:

  • (Object)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def reporters
  @reporters
end

#synthetic_method_indexObject? (readonly)

Returns the value of attribute synthetic_method_index.

Returns:

  • (Object, nil)


67
68
69
# File 'lib/rigor/environment.rb', line 67

def synthetic_method_index
  @synthetic_method_index
end

Class Method Details

.defaultEnvironment

Returns:



192
193
194
195
196
197
# File 'lib/rigor/environment.rb', line 192

def default
  @default ||= new(
    rbs_loader: RbsLoader.default,
    hkt_registry: Builtins::HktBuiltins.registry
  ).freeze
end

.for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil, plugin_registry: nil, dependency_source_index: nil, rbs_extended_reporter: nil, boundary_cross_reporter: nil, source_rbs_synthesis_reporter: nil, bundler_bundle_path: nil, bundler_auto_detect: false, bundler_lockfile: nil, rbs_collection_lockfile: nil, rbs_collection_auto_detect: false, synthetic_method_index: nil, project_patched_methods: nil, source_files: []) ⇒ Rigor::Environment

Builds an Environment that consults the project's local signatures and any opt-in stdlib libraries on top of RBS core.

rubocop:disable Metrics/MethodLength, Metrics/ParameterLists

Parameters:

  • root (String, Pathname) (defaults to: Dir.pwd)

    project root used to auto-detect the default signature path. Defaults to the current working directory.

  • libraries (Array<String, Symbol>) (defaults to: [])

    additional stdlib libraries to load on top of DEFAULT_LIBRARIES. The final list is the union of the two, de-duplicated while preserving order. Pass an empty array (the default) to load only the defaults.

  • signature_paths (Array<String, Pathname>, nil) (defaults to: nil)

    explicit list of sig/-style directories. When nil (the default), the canonical project layout <root>/sig is used if it exists, otherwise no signature path is loaded.

  • cache_store (Rigor::Cache::Store, nil) (defaults to: nil)

    persistent cache threaded into the underlying RbsLoader so constant lookups (and, in later v0.0.9 slices, other reflection artefacts) consult the cache. Pass nil (the default) to skip caching for this environment.

  • root: (String) (defaults to: Dir.pwd)
  • libraries: (Array[String]) (defaults to: [])
  • signature_paths: (Array[String | _ToPath], nil) (defaults to: nil)
  • cache_store: (Object, nil) (defaults to: nil)
  • plugin_registry: (Object, nil) (defaults to: nil)
  • dependency_source_index: (Object, nil) (defaults to: nil)
  • rbs_extended_reporter: (Object, nil) (defaults to: nil)
  • boundary_cross_reporter: (Object, nil) (defaults to: nil)
  • source_rbs_synthesis_reporter: (Object, nil) (defaults to: nil)
  • bundler_bundle_path: (String, nil) (defaults to: nil)
  • bundler_auto_detect: (Boolean) (defaults to: false)
  • synthetic_method_index: (Object, nil) (defaults to: nil)
  • project_patched_methods: (Object, nil) (defaults to: nil)

Returns:



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/rigor/environment.rb', line 215

def for_project(root: Dir.pwd, libraries: [], signature_paths: nil, cache_store: nil,
                plugin_registry: nil, dependency_source_index: nil,
                rbs_extended_reporter: nil, boundary_cross_reporter: nil,
                source_rbs_synthesis_reporter: nil,
                bundler_bundle_path: nil, bundler_auto_detect: false,
                bundler_lockfile: nil,
                rbs_collection_lockfile: nil, rbs_collection_auto_detect: false,
                synthetic_method_index: nil, project_patched_methods: nil,
                source_files: [])
  resolved_paths = signature_paths || default_signature_paths(root)
  # O4 MVP — append per-gem `sig/` directories discovered under the target project's bundler install
  # root. Empty array when neither an explicit path nor auto-detection finds a bundle. Order: user
  # `signature_paths:` win first (semantic precedence inside `RbsLoader.build_env_for`); gem-shipped
  # sigs append last so user overrides stay authoritative.
  #
  # O4 Layer 3 — when a Gemfile.lock is available (explicit `bundler_lockfile:` or auto-detected next
  # to the project root), use the locked gem set to filter the discovered `sig/` directories. Stale
  # gems in the bundle install tree (out-of-band installs, version drift after a `bundle update`) are
  # silently dropped so only gems the project actually declares contribute RBS.
  locked = LockfileResolver.locked_gems(
    lockfile_path: bundler_lockfile,
    project_root: root,
    auto_detect: bundler_auto_detect
  )
  gem_sig_paths = BundleSigDiscovery.discover(
    bundle_path: bundler_bundle_path,
    project_root: root,
    auto_detect: bundler_auto_detect,
    locked_gems: locked.empty? ? nil : locked
  ).map(&:to_s)
  # ADR-82 WD9 — the resolved bundle root, so the missing-gem constant index reads each RBS-less gem's
  # entry file from the TARGET's bundle (not rigor's own — see `MissingGemConstantIndex`). Resolved
  # once here; passed through to the lazy index build.
  bundle_root = BundleSigDiscovery.resolve_bundle_path(
    bundle_path: bundler_bundle_path, project_root: root, auto_detect: bundler_auto_detect
  )&.to_s
  # O4 Layer 3 slice 2 — when `rbs collection install` has been run for the target project, parse the
  # resulting `rbs_collection.lock.yaml` and feed each gem's `<collection_path>/<name>/<version>/`
  # directory into `signature_paths:`. Stdlib-typed entries are skipped (already covered by
  # `DEFAULT_LIBRARIES`), as are gems whose RBS rigor already loads from another source —
  # stdlib-extracted default gems (`cgi`, `logger`, …, shipped by the collection under a `git` source)
  # and the `data/vendored_gem_sigs/` bundle (`redis`, `nokogiri`, `pg`, …). `skip_gem_names:` passes
  # both sets so the collection copy doesn't double-declare against rigor's bundled RBS (the
  # `RBS::DuplicatedDeclarationError` hazard).
  merged_libraries = (DEFAULT_LIBRARIES + libraries.map(&:to_s)).uniq
  skip_gem_names = merged_libraries + RbsLoader.vendored_gem_names
  collection_paths = RbsCollectionDiscovery.discover(
    lockfile_path: rbs_collection_lockfile,
    project_root: root,
    auto_detect: rbs_collection_auto_detect,
    skip_gem_names: skip_gem_names
  ).map(&:to_s)
  # ADR-25 — RBS signature directories contributed by loaded plugins via their manifest
  # `signature_paths:`. Resolved to absolute dirs by `Plugin::Base#signature_paths`; additive, ranked
  # below the user's explicit `signature_paths:` and above the opportunistic bundle / collection
  # discovery. A duplicate-declaration conflict degrades through the same O7 failure-memo path.
  plugin_sig_paths = plugin_registry ? plugin_registry.signature_paths.map(&:to_s) : []
  # ADR-72 — Gemfile.lock-gated bundled RBS overlays. For each locked gem that ships no RBS through any
  # resolution path (`:missing`) and has a Rigor-bundled overlay, load that overlay so its core-class
  # extensions resolve (e.g. `Integer#minutes` on a Rails project) — turning a systematic false
  # `call.undefined-method` into a no-op while a project WITHOUT the gem still sees the genuine
  # diagnostic. Appended last so any RBS the project already supplies wins, and skipped for a gem whose
  # opt-in plugin twin is loaded (no duplicate declaration). The paths ride in
  # `loader_signature_paths`, so the env cache descriptor digests them for free.
  missing_gems, overlay_paths = missing_gems_and_overlay_paths(
    locked: locked, default_libraries: merged_libraries, bundle_sig_paths: gem_sig_paths,
    rbs_collection_paths: collection_paths, plugin_registry: plugin_registry
  )
  loader_signature_paths = resolved_paths + plugin_sig_paths + gem_sig_paths +
                           collection_paths + overlay_paths
  # ADR-32 WD4 + WD5 — invoke each loaded plugin's `source_rbs_synthesizer` once per project source
  # file and collect non-nil `[filename, rbs_source]` pairs. The synthesizer-emitting plugin (currently
  # only `rigor-rbs-inline`) is responsible for its own fail-soft on parse errors per WD6; this loop
  # only filters `nil` returns.
  #
  # When a `cache_store` is supplied, each synthesizer invocation is memoised per `(file path + content
  # SHA, plugin id + version + config_hash)` — WD5's cache key — so a second run with unchanged source
  # skips the rbs-inline parse cost. The empty string is the sentinel for "no contribution" so the
  # Store (which treats `nil` as cache miss) can persist the no-contribution decision too.
  virtual_rbs = collect_virtual_rbs(plugin_registry, source_files, cache_store,
                                    source_rbs_synthesis_reporter)
  loader = RbsLoader.new(
    libraries: merged_libraries,
    signature_paths: loader_signature_paths,
    cache_store: cache_store,
    virtual_rbs: virtual_rbs
  )
  # ADR-20 slice 2c + 2e — seed hkt_registry with the bundled builtins. The Environment's
  # `#hkt_registry` getter then LAZILY merges in the RBS env scan on first call so fast paths that
  # don't consult HKT (e.g. `rigor check --cache-stats --no-stats`) don't pay the eager env-build cost
  # up front. URI collisions let the user-authored overlay win over the bundled builtin
  # (last-write-wins per ADR-20 OQ3 tentative).
  new(
    rbs_loader: loader,
    plugin_registry: plugin_registry,
    dependency_source_index: dependency_source_index,
    rbs_extended_reporter: rbs_extended_reporter,
    boundary_cross_reporter: boundary_cross_reporter,
    source_rbs_synthesis_reporter: source_rbs_synthesis_reporter,
    synthetic_method_index: synthetic_method_index,
    project_patched_methods: project_patched_methods,
    hkt_registry: Builtins::HktBuiltins.registry,
    missing_rbs_gems: missing_gems,
    missing_rbs_bundle_path: bundle_root
  )
end

Instance Method Details

#attach_reporters!(rbs_extended_reporter:, boundary_cross_reporter:) ⇒ nil

Replaces the env's per-run reporter slots. Intended for long-lived integrations (LSP ProjectContext) that share one Environment instance across many Runner.run calls: each call attaches its own fresh reporter pair so per-call diagnostic events stay scoped to that call rather than accumulating across publishes.

Single-threaded use only. Concurrent publishes against one Environment must serialise — the LSP Server debouncer + synchronized writer already enforces this for the editor path. The Ractor pool path builds a per-worker Environment and does not reach this surface.

Parameters:

  • rbs_extended_reporter: (Object, nil)
  • boundary_cross_reporter: (Object, nil)

Returns:

  • (nil)


185
186
187
188
189
# File 'lib/rigor/environment.rb', line 185

def attach_reporters!(rbs_extended_reporter:, boundary_cross_reporter:)
  @reporters.rbs_extended = rbs_extended_reporter
  @reporters.boundary_cross = boundary_cross_reporter
  nil
end

#boundary_cross_reporterObject?

Returns:

  • (Object, nil)


165
166
167
# File 'lib/rigor/environment.rb', line 165

def boundary_cross_reporter
  @reporters.boundary_cross
end

#class_known?(name) ⇒ Boolean

Returns true when the constant name is known to either the static registry or the RBS loader. Useful for callers that only need a presence check without materialising a type carrier.

Parameters:

  • name (String, Symbol)

Returns:

  • (Boolean)


505
506
507
508
509
510
511
512
513
# File 'lib/rigor/environment.rb', line 505

def class_known?(name)
  return true if class_registry.nominal_for_name(name)
  return true if class_known_in_rbs?(name)

  # ADR-36 nested-class emission — a variant subclass the substrate synthesised (e.g. `Shape::Circle`
  # from a `variants do variant Circle, Float end` block) is a real class for resolution purposes even
  # though no RBS / source declares it.
  @synthetic_method_index&.knows_class?(name) || false
end

#class_ordering(lhs, rhs) ⇒ ordering

Compares two class/module names using analyzer-owned class data. Returns :equal, :subclass, :superclass, :disjoint, or :unknown. The static registry handles built-ins cheaply; the RBS loader handles project/stdlib classes without relying on host Ruby constants being loaded.

Parameters:

  • lhs (String, Symbol)
  • rhs (String, Symbol)

Returns:

  • (ordering)


536
537
538
539
540
541
542
543
544
545
546
547
# File 'lib/rigor/environment.rb', line 536

def class_ordering(lhs, rhs)
  lhs = normalize_class_name(lhs)
  rhs = normalize_class_name(rhs)
  return :equal if lhs == rhs

  registry_result = class_registry.class_ordering(lhs, rhs)
  return registry_result unless registry_result == :unknown

  return :unknown unless rbs_loader

  rbs_loader.class_ordering(lhs, rhs)
end

#constant_for_name(name) ⇒ Type::t?

Slice A constant-value lookup. Returns the translated Rigor::Type for an RBS-declared non-class constant (Rigor::Analysis::FactStore::BUCKETS: Array[Symbol], Rigor::Configuration::DEFAULT_PATH: String, ...) or nil when no RBS constant declaration covers name. This is the value-bearing counterpart of #singleton_for_name, which only resolves names that name a class or module. Callers that need to type a Prism::ConstantReadNode/ Prism::ConstantPathNode MUST consult #singleton_for_name first and fall through to this query when the constant is not a class.

Parameters:

  • name (String, Symbol)

Returns:

  • (Type::t, nil)


491
492
493
494
495
496
497
498
499
500
501
# File 'lib/rigor/environment.rb', line 491

def constant_for_name(name)
  return nil if rbs_loader.nil?

  # Pure function of `name` for this Environment's lifetime — the refinement table and the RBS constant
  # table are both fixed — so memoise across the lexical-candidate ladder's heavy name reuse.
  key = name.to_s
  @constant_type_cache.fetch(key) do
    Builtins::PredefinedConstantRefinements.lookup(key) ||
      rbs_loader.constant_type(key)
  end
end

#missing_rbs_gem_owner(root_constant_name) ⇒ String?

ADR-82 WD9 — the gem name owning root_constant_name, when that gem is locked in the project's Gemfile.lock, ships no resolvable RBS, and its entry file declares the constant at top level. Nil for everything else — the caller then keeps the generic provenance cause. The index is built once, lazily, from RubyGems metadata + a Prism parse of each gem's entry file; no gem code runs (see MissingGemConstantIndex). Under the fork pool a worker that never meets an unresolved constant never builds it.

Parameters:

  • root_constant_name (String, Symbol)

Returns:

  • (String, nil)


149
150
151
152
153
154
155
156
# File 'lib/rigor/environment.rb', line 149

def missing_rbs_gem_owner(root_constant_name)
  return nil if @missing_rbs_gems.empty?

  index = @missing_rbs_gem_constants_holder.fetch do
    MissingGemConstantIndex.build(@missing_rbs_gems, bundle_path: @missing_rbs_bundle_path)
  end
  index[root_constant_name.to_s]
end

#nominal_for_name(name) ⇒ Type::Nominal?

Resolves a constant name to a Rigor::Type::Nominal (the instance type carrier). Consults the static class registry first (cheap, hardcoded), then falls back to the RBS loader. Returns nil when the name is unknown to both.

NOTE: This is the construction helper for "an instance of class Foo". For "the class object Foo itself" (the value of the constant), use #singleton_for_name instead.

Parameters:

  • name (String, Symbol)

Returns:



466
467
468
469
470
471
# File 'lib/rigor/environment.rb', line 466

def nominal_for_name(name)
  registered = class_registry.nominal_for_name(name)
  return registered if registered

  class_known_in_rbs?(name) ? Type::Combinator.nominal_of(name.to_s) : nil
end

#rbs_extended_reporterObject?

Backwards-compatible reporter accessors — every existing consumer (rbs_extended, method_dispatcher) calls these. The frozen @reporters container is mutable for slot reassignment via #attach_reporters! below.

Returns:

  • (Object, nil)


161
162
163
# File 'lib/rigor/environment.rb', line 161

def rbs_extended_reporter
  @reporters.rbs_extended
end

#rbs_module?(name) ⇒ Boolean

Returns true when the RBS environment carries the named declaration as a Module (not a Class). Used by the user_class_fallback_receiver tier to detect a module-mixin receiver (e.g. PP::ObjectMixin) so the dispatcher can route unresolved method calls through the Nominal[Object] fallback — every concrete includer of M honours Kernel / Object instance methods through its own ancestor chain.

Parameters:

  • name (String, Symbol)

Returns:

  • (Boolean)


527
528
529
530
531
# File 'lib/rigor/environment.rb', line 527

def rbs_module?(name)
  return false unless rbs_loader

  rbs_loader.rbs_module?(name)
end

#reflectionObject?

ADR-15 Phase 2b — returns the loader's read-only, Ractor.shareable? query surface as a frozen Reflection. Built lazily on first access; subsequent calls return the same instance. Returns nil when the environment carries no RBS loader (test-only Environment.new without rbs_loader:).

Returns:

  • (Object, nil)


519
520
521
# File 'lib/rigor/environment.rb', line 519

def reflection
  @rbs_loader&.reflection
end

#singleton_for_name(name) ⇒ Type::Singleton?

Resolves a constant name to a Rigor::Type::Singleton (the class object carrier). The expression Foo evaluates to the class object, whose RBS type is singleton(Foo) -- this method is the corresponding Rigor construction helper.

The lookup uses the same registry/RBS chain as #nominal_for_name so a class is either known to both queries or to neither.

Parameters:

  • name (String, Symbol)

Returns:



479
480
481
482
483
# File 'lib/rigor/environment.rb', line 479

def singleton_for_name(name)
  return nil unless class_known?(name)

  Type::Combinator.singleton_of(name.to_s)
end

#source_rbs_synthesis_reporterObject?

ADR-32 WD6 — the per-run accumulator for synthesizer failure events. Environment.for_project records [:error, message] returns from a plugin's source_rbs_synthesizer here so the Runner can emit source-rbs-synthesis-failed :info diagnostics after analysis completes. Nil when no plugin contributes a synthesizer.

Returns:

  • (Object, nil)


173
174
175
# File 'lib/rigor/environment.rb', line 173

def source_rbs_synthesis_reporter
  @reporters.source_rbs_synthesis
end