Class: Rigor::Environment::RbsLoader
- Inherits:
-
Object
- Object
- Rigor::Environment::RbsLoader
- Defined in:
- lib/rigor/environment/rbs_loader.rb
Overview
Loads RBS class declarations and method definitions from disk and exposes them to the inference engine in a small, stable surface.
Slice 4 phase 1 only enabled the RBS core signatures shipped with the ‘rbs` gem (`Object`, `Integer`, `String`, `Array`, …). Phase 2a adds opt-in stdlib library loading (`pathname`, `json`, `tempfile`, …) and arbitrary-directory signature loading (typically the project’s local ‘sig/` tree). Both are off by default on `RbsLoader.default` so the core-only fast path stays cheap; project-aware loading is opted into through for_project or by constructing a custom loader.
The default instance is shared across the process: building the core RBS environment costs hundreds of milliseconds and the data is read-only. The shared instance is frozen, but holds a mutable state hash for lazy memoization of the heavy ‘RBS::Environment` and `RBS::DefinitionBuilder` – the user-visible API stays purely functional.
See docs/internal-spec/inference-engine.md for the binding contract. rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#cache_store ⇒ Object
readonly
Returns the value of attribute cache_store.
-
#libraries ⇒ Object
readonly
Returns the value of attribute libraries.
-
#signature_paths ⇒ Object
readonly
Returns the value of attribute signature_paths.
Class Method Summary collapse
-
.build_env_for(libraries:, signature_paths:) ⇒ Object
Builds an ‘RBS::Environment` from explicit `libraries` and `signature_paths`.
- .default ⇒ Object
-
.reset_default! ⇒ Object
Used by tests to discard the cached default loader; production code MUST NOT call this.
- .vendored_gem_sig_paths ⇒ Object
Instance Method Summary collapse
-
#class_decl_paths ⇒ Object
Returns a frozen ‘Hash<String, String>` mapping each loaded class / module name (top-level prefixed) to the file path of its FIRST declaration’s RBS source.
-
#class_known?(name) ⇒ Boolean
Returns true when an RBS class or module declaration with the given name is loaded.
- #class_ordering(lhs, rhs) ⇒ Object
-
#class_type_param_names(class_name) ⇒ Object
Slice 4 phase 2d.
-
#constant_names ⇒ Array<String>
Every RBS-declared constant name (top-level prefixed, e.g., ‘“::Math::PI”`) currently loaded into the environment.
-
#constant_type(name) ⇒ Object
Slice A constant-value lookup.
-
#each_class_decl_annotation ⇒ Object
ADR-20 slice 2e — iterates over every ‘%a…` annotation attached to a class- or module-level declaration in the loaded RBS environment, yielding `(annotation_string, source_location)` pairs.
-
#each_constant_decl ⇒ Object
Yields ‘(name, entry)` for every RBS constant declaration currently loaded into the environment.
-
#each_known_class_name ⇒ Object
Yields every known class / module / alias name (top-level prefixed) currently loaded into the environment.
-
#initialize(libraries: [], signature_paths: [], cache_store: nil) ⇒ RbsLoader
constructor
A new instance of RbsLoader.
-
#instance_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class definition through Cache::RbsInstanceDefinitions.fetch so subsequent runs (and other loaders sharing the same Store) skip the `RBS::DefinitionBuilder.build_instance` step.
- #instance_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
-
#prewarm ⇒ Object
ADR-15 Phase 4b.x — eagerly drives every cached producer so a subsequent worker Ractor can serve all of its RBS queries from the Marshal blob on disk without ever calling ‘RBS::EnvironmentLoader.new`.
-
#rbs_module?(name) ⇒ Boolean
Returns true when the named RBS declaration is a Module (‘RBS::AST::Declarations::Module`) rather than a Class.
-
#reflection ⇒ Object
ADR-15 Phase 2b — return the loader’s read-only query surface as a frozen, ‘Ractor.shareable?` Reflection value object.
-
#singleton_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class singleton definition through Cache::RbsSingletonDefinitions.fetch; the same caching discipline as #instance_definition.
-
#singleton_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
The class method on ‘class_name`.
-
#uncached_instance_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer (Cache::RbsInstanceDefinitions).
-
#uncached_singleton_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer.
Constructor Details
#initialize(libraries: [], signature_paths: [], cache_store: nil) ⇒ RbsLoader
Returns a new instance of RbsLoader.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/rigor/environment/rbs_loader.rb', line 117 def initialize(libraries: [], signature_paths: [], cache_store: nil) @libraries = libraries.map(&:to_s).freeze @signature_paths = signature_paths.map { |p| Pathname(p) }.freeze @cache_store = cache_store # Per-loader memoization bucket. Held as a single # mutable Hash so the loader instance itself can be # `.freeze`d (per ADR-15 reflection-facade contract) # without losing the lazy-memo behaviour. Slot names # currently consulted: `:env`, `:env_loaded`, # `:env_build_warned`, `:builder`, `:reflection`, # `:instance_definitions_table`, # `:singleton_definitions_table`. Constructed via # `Hash.new` (NOT a `{ ... }` literal) so Rigor's # `HashShape` narrowing doesn't infer a fixed key set # from the initial state and fold post-initial slot # reads (e.g. `@state[:env_loaded]`) to a constant # `nil`. @state = Hash.new # rubocop:disable Style/EmptyLiteral @instance_definition_cache = {} @singleton_definition_cache = {} @class_known_cache = {} @hierarchy = RbsHierarchy.new(self) end |
Instance Attribute Details
#cache_store ⇒ Object (readonly)
Returns the value of attribute cache_store.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def cache_store @cache_store end |
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def libraries @libraries end |
#signature_paths ⇒ Object (readonly)
Returns the value of attribute signature_paths.
98 99 100 |
# File 'lib/rigor/environment/rbs_loader.rb', line 98 def signature_paths @signature_paths end |
Class Method Details
.build_env_for(libraries:, signature_paths:) ⇒ Object
Builds an ‘RBS::Environment` from explicit `libraries` and `signature_paths`. Stateless surface so the v0.0.9 Cache::RbsEnvironment producer can build an env on cache miss without holding a loader instance, and the instance-side #build_env delegates here so the implementation stays single-rooted.
Vendored gem stubs (‘data/vendored_gem_sigs/<gem>/`) are loaded on top of `signature_paths` so the per-gem RBS bundled with Rigor itself is in scope for every analysis run. The gem stubs are intentionally read-only and appended LAST so user-supplied `signature_paths` win on name conflicts.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/rigor/environment/rbs_loader.rb', line 59 def build_env_for(libraries:, signature_paths:) rbs_loader = RBS::EnvironmentLoader.new libraries.each do |library| next unless rbs_loader.has_library?(library: library, version: nil) rbs_loader.add(library: library, version: nil) end signature_paths.each do |path| path = Pathname(path) unless path.is_a?(Pathname) rbs_loader.add(path: path) if path.directory? end vendored_gem_sig_paths.each do |path| rbs_loader.add(path: path) if path.directory? end RBS::Environment.from_loader(rbs_loader).resolve_type_names end |
.default ⇒ Object
34 35 36 |
# File 'lib/rigor/environment/rbs_loader.rb', line 34 def default @default ||= new.freeze end |
.reset_default! ⇒ Object
Used by tests to discard the cached default loader; production code MUST NOT call this. The shared loader holds a several-MB RBS::Environment, so dropping it during a normal run wastes the cost of rebuilding it.
42 43 44 |
# File 'lib/rigor/environment/rbs_loader.rb', line 42 def reset_default! @default = nil end |
.vendored_gem_sig_paths ⇒ Object
89 90 91 92 93 94 95 |
# File 'lib/rigor/environment/rbs_loader.rb', line 89 def vendored_gem_sig_paths return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT) Dir.children(VENDORED_GEM_SIGS_ROOT).map do |gem_dir| Pathname(File.join(VENDORED_GEM_SIGS_ROOT, gem_dir)) end end |
Instance Method Details
#class_decl_paths ⇒ Object
Returns a frozen ‘Hash<String, String>` mapping each loaded class / module name (top-level prefixed) to the file path of its FIRST declaration’s RBS source. Used by Analysis::RunStats to attribute the type universe between “project sig/” (paths under the configured ‘signature_paths`) and “bundled” (everything else — RBS core, stdlib libraries, gem-bundled RBS). Each value is a frozen `String` so the whole result is `Ractor.shareable?` — the Phase 4b worker pool ships a snapshot back to the coordinator on the first `:prepare` message.
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/rigor/environment/rbs_loader.rb', line 243 def class_decl_paths return {}.freeze if env.nil? result = {} env.class_decls.each do |rbs_name, entry| decl = entry.primary_decl next if decl.nil? location = decl.location next if location.nil? buffer = location.buffer name = buffer.respond_to?(:name) ? buffer.name : nil next if name.nil? result[rbs_name.to_s.dup.freeze] = name.to_s.dup.freeze end result.freeze rescue ::RBS::BaseError {}.freeze end |
#class_known?(name) ⇒ Boolean
Returns true when an RBS class or module declaration with the given name is loaded. Accepts unprefixed or top-level-prefixed names (“Integer” or “::Integer”). Memoized per-name (positive and negative results both cache).
When ‘cache_store` is set, the loader fetches the entire set of known class / module / alias names once (per process) through Cache::RbsKnownClassNames.fetch and answers `class_known?` from the in-memory Set. Cold runs pay a single env walk and persist the result; warm runs (and a separate loader sharing the same Store) skip the env walk entirely.
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/rigor/environment/rbs_loader.rb', line 152 def class_known?(name) key = name.to_s return @class_known_cache[key] if @class_known_cache.key?(key) @class_known_cache[key] = if cache_store cached_class_known(name) else compute_class_known(name) end end |
#class_ordering(lhs, rhs) ⇒ Object
372 373 374 |
# File 'lib/rigor/environment/rbs_loader.rb', line 372 def class_ordering(lhs, rhs) @hierarchy.class_ordering(lhs, rhs) end |
#class_type_param_names(class_name) ⇒ Object
Slice 4 phase 2d. Returns the class’s declared type-parameter names as Symbols (e.g., ‘[:Elem]` for `Array`, `[:K, :V]` for `Hash`). Used by the dispatcher to build the substitution map from receiver `type_args` into the method’s return type. The instance definition is the canonical source because singleton methods (e.g., ‘Array.new`) parameterize over the same `Elem` as instance methods.
Returns an empty array for non-generic classes and for unknown names (the loader stays fail-soft). NOTE: in the ‘rbs` gem, `RBS::Definition#type_params` returns `Array<Symbol>` directly, not the AST `TypeParam` object (those live on the AST level).
When ‘cache_store` is set, the loader fetches the entire type-parameter-name table once (per process) through Cache::RbsClassTypeParamNames.fetch and answers point lookups from it. Cold runs build the table once and persist it; warm runs (and a separate loader sharing the same Store) skip the env walk entirely.
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/rigor/environment/rbs_loader.rb', line 360 def class_type_param_names(class_name) if cache_store key = class_name.to_s.delete_prefix("::") return type_param_names_table.fetch(key, []).dup end definition = instance_definition(class_name) return [] unless definition definition.type_params.dup end |
#constant_names ⇒ Array<String>
Returns every RBS-declared constant name (top-level prefixed, e.g., ‘“::Math::PI”`) currently loaded into the environment. Used by the cache producer that materialises the constant-type table; ordinary callers should keep using #constant_type for point lookups.
381 382 383 384 385 386 387 |
# File 'lib/rigor/environment/rbs_loader.rb', line 381 def constant_names return [] if env.nil? env.constant_decls.keys.map(&:to_s) rescue ::RBS::BaseError [] end |
#constant_type(name) ⇒ Object
Slice A constant-value lookup. Returns the translated ‘Rigor::Type` for a non-class constant declaration (`BUCKETS: Array`, `DEFAULT_PATH: String`, …) or `nil` when no constant entry exists for `name` in the loaded RBS environment. Callers MUST treat the return value as authoritative when present and as “unknown” when nil; the loader does NOT consult the class declarations here — class objects are still resolved through #class_known? and `Environment#singleton_for_name`.
When ‘cache_store` is set, the loader fetches the entire translated constant table once (per process) through Cache::RbsConstantTable.fetch and answers point lookups from it. Cold runs pay the translation cost up-front and write the result to disk; warm runs skip the translation entirely and pay only a `Marshal.load` of the table.
421 422 423 424 425 426 427 428 429 430 431 432 |
# File 'lib/rigor/environment/rbs_loader.rb', line 421 def constant_type(name) rbs_name = parse_type_name(name) return nil unless rbs_name if cache_store constant_type_table[rbs_name.to_s] else translate_constant_decl(rbs_name) end rescue ::RBS::BaseError nil end |
#each_class_decl_annotation ⇒ Object
ADR-20 slice 2e — iterates over every ‘%a…` annotation attached to a class- or module-level declaration in the loaded RBS environment, yielding `(annotation_string, source_location)` pairs. Used by Inference::HktRegistry.scan_rbs_loader to find `rigor:v1:hkt_register` / `rigor:v1:hkt_define` directives in user-authored overlays and merge them into the per-`Environment` HKT registry. Yields nothing when the env failed to build (fail-soft, same shape as #each_known_class_name).
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/rigor/environment/rbs_loader.rb', line 213 def each_class_decl_annotation return enum_for(:each_class_decl_annotation) unless block_given? return if env.nil? env.class_decls.each_value do |entry| entry.each_decl do |decl| next unless decl.respond_to?(:annotations) decl.annotations.each { |a| yield a.string, a.location } end end rescue ::RBS::BaseError, ::Ractor::IsolationError # fail-soft: matches each_known_class_name's policy. # Ractor::IsolationError surfaces when the scan is # invoked from a non-main Ractor pool worker before # ADR-15's full deep-freeze migration completes — the # worker falls back to the base (builtins-only) # registry rather than crashing. end |
#each_constant_decl ⇒ Object
Yields ‘(name, entry)` for every RBS constant declaration currently loaded into the environment. The cache producer uses this to materialise the constant-type table without going back through #constant_type (which would recurse back into the cache when `cache_store` is set).
394 395 396 397 398 399 400 401 402 403 |
# File 'lib/rigor/environment/rbs_loader.rb', line 394 def each_constant_decl return enum_for(:each_constant_decl) unless block_given? return if env.nil? env.constant_decls.each do |rbs_name, entry| yield rbs_name.to_s, entry end rescue ::RBS::BaseError # fail-soft: a broken RBS environment yields no entries. end |
#each_known_class_name ⇒ Object
Yields every known class / module / alias name (top-level prefixed) currently loaded into the environment. The cache producer that materialises the known-name set uses this so it never recurses back through #class_known?.
189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/rigor/environment/rbs_loader.rb', line 189 def each_known_class_name return enum_for(:each_known_class_name) unless block_given? return if env.nil? env.class_decls.each_key { |rbs_name| yield rbs_name.to_s } env.class_alias_decls.each_key { |rbs_name| yield rbs_name.to_s } rescue ::RBS::BaseError # fail-soft: a broken RBS environment yields no names. # Analyzer-internal errors (NameError, NoMethodError, # LoadError) are NOT swallowed — those are bugs and # must surface so they don't hide silently the way the # v0.0.9 cache `Cache::Descriptor` regression did. end |
#instance_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class definition through Cache::RbsInstanceDefinitions.fetch so subsequent runs (and other loaders sharing the same Store) skip the `RBS::DefinitionBuilder.build_instance` step. In-memory `@instance_definition_cache` keeps the per-process short-circuit on top.
276 277 278 279 280 281 282 283 284 285 |
# File 'lib/rigor/environment/rbs_loader.rb', line 276 def instance_definition(class_name) key = class_name.to_s return @instance_definition_cache[key] if @instance_definition_cache.key?(key) @instance_definition_cache[key] = if cache_store cached_instance_definition(class_name) else build_instance_definition(class_name) end end |
#instance_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
296 297 298 299 300 301 |
# File 'lib/rigor/environment/rbs_loader.rb', line 296 def instance_method(class_name:, method_name:) definition = instance_definition(class_name) return nil unless definition definition.methods[method_name.to_sym] end |
#prewarm ⇒ Object
ADR-15 Phase 4b.x — eagerly drives every cached producer so a subsequent worker Ractor can serve all of its RBS queries from the Marshal blob on disk without ever calling ‘RBS::EnvironmentLoader.new`. The loader path that calls `EnvironmentLoader.new` transitively reads a chain of non-`Ractor.shareable?` module constants (`RBS::EnvironmentLoader::DEFAULT_CORE_ROOT`, `RBS::Repository::DEFAULT_STDLIB_ROOT`, `Gem::Requirement::DefaultRequirement`, …) and trips `Ractor::IsolationError`. Pre-warming the cache on the main Ractor and letting workers consult ONLY the Marshal-loaded blob sidesteps the whole chain.
No-op when ‘cache_store` is nil — without a Store the worker has no choice but to build env via the loader, so the caller MUST ensure pool mode runs with caching enabled. Returns `self` so the call chains cleanly from the `Runner` pre-spawn hook.
453 454 455 456 457 458 459 460 461 462 463 464 |
# File 'lib/rigor/environment/rbs_loader.rb', line 453 def prewarm return self if cache_store.nil? env known_class_names_set constant_type_table type_param_names_table ancestor_names_table instance_definitions_table singleton_definitions_table self end |
#rbs_module?(name) ⇒ Boolean
Returns true when the named RBS declaration is a Module (‘RBS::AST::Declarations::Module`) rather than a Class. The `user_class_fallback_receiver` tier consults this to route `Nominal.some_kernel_method` (where M is a module mixin like `PP::ObjectMixin`) through the `Nominal` fallback, because every concrete includer of M sees Kernel / Object instance methods as part of its own ancestor chain.
Returns false for classes, for unknown names, and when the RBS environment failed to build (fail-soft).
173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/rigor/environment/rbs_loader.rb', line 173 def rbs_module?(name) return false if env.nil? rbs_name = parse_type_name(name) return false if rbs_name.nil? entry = env.class_decls[rbs_name] entry.is_a?(::RBS::Environment::ModuleEntry) rescue ::RBS::BaseError false end |
#reflection ⇒ Object
ADR-15 Phase 2b — return the loader’s read-only query surface as a frozen, ‘Ractor.shareable?` Rigor::Environment::Reflection value object. Built lazily on first access; the loader memoises so repeated calls return the same instance.
The Reflection consumes the loader’s already-warmed cache producers (or, when no ‘cache_store` is set, eagerly walks the env). Once constructed, the Reflection carries the derived tables independently and never re-consults the loader — making it safe to share across Ractors while the loader stays per- process / per-Ractor for write-path operations.
479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/rigor/environment/rbs_loader.rb', line 479 def reflection @state[:reflection] ||= begin require_relative "reflection" Environment::Reflection.new( known_class_names: known_class_names_set, instance_definitions: instance_definitions_table, singleton_definitions: singleton_definitions_table, type_param_names: type_param_names_table, constant_types: constant_type_table, ancestor_names: ancestor_names_table ) end end |
#singleton_definition(class_name) ⇒ RBS::Definition?
When ‘cache_store` is set, the loader fetches the per-class singleton definition through Cache::RbsSingletonDefinitions.fetch; the same caching discipline as #instance_definition.
313 314 315 316 317 318 319 320 321 322 |
# File 'lib/rigor/environment/rbs_loader.rb', line 313 def singleton_definition(class_name) key = class_name.to_s return @singleton_definition_cache[key] if @singleton_definition_cache.key?(key) @singleton_definition_cache[key] = if cache_store cached_singleton_definition(class_name) else build_singleton_definition(class_name) end end |
#singleton_method(class_name:, method_name:) ⇒ RBS::Definition::Method?
Returns the class method on ‘class_name`. For example, `singleton_method(class_name: “Integer”, method_name: :sqrt)` returns the definition for `Integer.sqrt`, while `singleton_method(class_name: “Foo”, method_name: :new)` returns Class#new for any class type.
334 335 336 337 338 339 |
# File 'lib/rigor/environment/rbs_loader.rb', line 334 def singleton_method(class_name:, method_name:) definition = singleton_definition(class_name) return nil unless definition definition.methods[method_name.to_sym] end |
#uncached_instance_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer (Cache::RbsInstanceDefinitions). Avoids the ‘private_method_called` round-trip a `loader.send(…)` callsite would require.
291 292 293 |
# File 'lib/rigor/environment/rbs_loader.rb', line 291 def uncached_instance_definition(class_name) build_instance_definition(class_name) end |
#uncached_singleton_definition(class_name) ⇒ Object
Public uncached accessor used by the cache producer.
325 326 327 |
# File 'lib/rigor/environment/rbs_loader.rb', line 325 def uncached_singleton_definition(class_name) build_singleton_definition(class_name) end |