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
Constant Summary collapse
- SYNTHETIC_NAMESPACE_BUFFER =
Buffer name stamped on the ‘module` declarations synthesized by synthesize_missing_namespaces. Re-read off the built env by #synthesized_namespaces so the analysis layer can surface an `:info` diagnostic naming the project’s malformed-RBS namespaces — robust across the marshalled env cache, since the sentinel rides along on each synthetic declaration’s location.
"(rigor: synthesized namespaces)"- SYNTHETIC_STUB_BUFFER =
Buffer name stamped on the stub ‘class` / `module` declarations synthesized by stub_missing_referenced_types for types the project’s RBS references but no loaded signature declares. #synthesized_stub_types reads them back off the built env (so the answer survives the marshalled env cache), and #synthesized_type_names folds them together with the namespace stubs into the set MethodDispatcher resolves to ‘Dynamic` (no false `call.undefined-method`).
"(rigor: synthesized stub types)"- MAX_STUB_PASSES =
ADR-5 robustness, second tier. A project ‘signature_paths:` RBS that references a type no loaded signature declares —`def x: () -> DRb::DRbServer` when the `drb` RBS is not available, or a stale reference to its own removed `Textbringer::EditorError` — makes `RBS::DefinitionBuilder#build_instance` raise `NoTypeFoundError`, and (per RBS’s all-or-nothing per-class build) that single unresolved reference takes down EVERY method on the class, not just the one signature. Observed on shugo/textbringer: one ‘DRb::DRbServer` reference left the whole `Textbringer::Commands` module — including its 186-call-site `define_command` DSL — resolving as `Dynamic`.
We synthesize an empty stub for each such referenced-but- undeclared type so the rest of the class builds. A leaf type is stubbed as ‘class`, its enclosing namespaces as `module`. Stubbed types carry no methods, so a call against a value of a stubbed type would otherwise mis-fire `call.undefined-method`; MethodDispatcher consults #synthesized_type_names and resolves such calls to `Dynamic` instead (the same no-false-positive contract as the dependency-source tier).
Detection re-uses RBS’s own builder (correct by construction): build every PROJECT class and read the missing name out of the raised error. Bounded to ‘signature_paths` classes (stdlib / vendored RBS is well-formed) and to MAX_STUB_PASSES iterations — a fresh stub can expose a deeper reference the first build error hid, but empty stubs reference nothing, so the fixpoint converges quickly.
5
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.
-
#virtual_rbs ⇒ Object
readonly
Returns the value of attribute virtual_rbs.
Class Method Summary collapse
-
.add_virtual_rbs(env, virtual_rbs) ⇒ Object
ADR-32 WD4 — merge synthesised-from-source RBS strings into the freshly-built environment.
-
.append_stub_declarations(base_env, missing) ⇒ Object
Adds empty stub declarations for the missing referenced types (and any enclosing namespace they need) to the pre-resolve env, tagged with SYNTHETIC_STUB_BUFFER.
-
.build_env_for(libraries:, signature_paths:, virtual_rbs: []) ⇒ Object
Builds an ‘RBS::Environment` from explicit `libraries` and `signature_paths`.
-
.collect_missing_namespaces(env) ⇒ Object
Returns the ‘::`-stripped names of every enclosing namespace that some declaration references but no declaration defines, shallowest-first so the synthesized source declares `Foo` before `Foo::Bar`.
- .default ⇒ Object
-
.project_entry?(entry, project_files) ⇒ Boolean
True when a ‘class_decls` entry was declared in one of the project’s own signature files (by declaration location), so the sweep skips the bundled stdlib / vendored universe.
-
.project_sig_files(signature_paths) ⇒ Object
The absolute paths of every ‘.rbs` file under the project’s ‘signature_paths:` (NOT vendored / stdlib RBS — those are well-formed, so attempting to build them would only waste time).
-
.reset_default! ⇒ Object
Used by tests to discard the cached default loader; production code MUST NOT call this.
- .stub_missing_referenced_types(base_env, resolved, project_files) ⇒ Object
-
.synthesize_missing_namespaces(env) ⇒ Object
Robustness (ADR-5): a project whose RBS declares qualified names (‘class Foo::Bar`) without ever declaring the enclosing namespace (`module Foo`) is invalid by upstream RBS rules — `RBS::DefinitionBuilder#build_instance` raises `NoTypeFoundError: Could not find ::Foo`, which the loader’s fail-soft rescue turns into a silent dispatch miss (every method on every such class degrades to ‘Dynamic`).
-
.unresolved_referenced_types(env, project_files) ⇒ Object
Builds every project class (instance + singleton side) and returns the ‘::`-stripped names of the types whose absence raised `NoTypeFoundError`.
-
.vendored_gem_names ⇒ Object
Gem names whose RBS ships under ‘data/vendored_gem_sigs/<gem>/`.
- .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, virtual_rbs: []) ⇒ 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`.
-
#synthesized_namespaces ⇒ Object
The enclosing namespaces RbsLoader.synthesize_missing_namespaces had to invent because the project’s ‘signature_paths:` RBS declared qualified names (`class Foo::Bar`) without ever declaring `Foo`.
-
#synthesized_stub_types ⇒ Object
The referenced-but-undeclared types RbsLoader.stub_missing_referenced_types stubbed so the project classes that mention them could build (e.g. an unavailable ‘DRb::DRbServer`, or a stale `Textbringer::EditorError`).
-
#synthesized_type_names ⇒ Object
Every type name Rigor invented to make an otherwise-inert / unbuildable project signature set resolve — both the namespace stubs and the referenced-type stubs.
-
#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, virtual_rbs: []) ⇒ RbsLoader
Returns a new instance of RbsLoader.
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'lib/rigor/environment/rbs_loader.rb', line 353 def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: []) @libraries = libraries.map(&:to_s).freeze @signature_paths = signature_paths.map { |p| Pathname(p) }.freeze @cache_store = cache_store @virtual_rbs = virtual_rbs.map { |name, content| [name.to_s.dup.freeze, content.to_s.dup.freeze].freeze }.freeze # 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.
327 328 329 |
# File 'lib/rigor/environment/rbs_loader.rb', line 327 def cache_store @cache_store end |
#libraries ⇒ Object (readonly)
Returns the value of attribute libraries.
327 328 329 |
# File 'lib/rigor/environment/rbs_loader.rb', line 327 def libraries @libraries end |
#signature_paths ⇒ Object (readonly)
Returns the value of attribute signature_paths.
327 328 329 |
# File 'lib/rigor/environment/rbs_loader.rb', line 327 def signature_paths @signature_paths end |
#virtual_rbs ⇒ Object (readonly)
Returns the value of attribute virtual_rbs.
327 328 329 |
# File 'lib/rigor/environment/rbs_loader.rb', line 327 def virtual_rbs @virtual_rbs end |
Class Method Details
.add_virtual_rbs(env, virtual_rbs) ⇒ Object
ADR-32 WD4 — merge synthesised-from-source RBS strings into the freshly-built environment. Each entry is a ‘[virtual_filename, rbs_source]` pair. `virtual_filename` is purely for diagnostic provenance (RBS parse errors cite it) — it is not a real file path. Per WD6 the synthesizer-emit path is responsible for catching its own parse errors and returning `nil` rather than garbage; this method assumes its input is parseable and only rescues `RBS::ParsingError` as a fail-soft.
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/rigor/environment/rbs_loader.rb', line 270 def add_virtual_rbs(env, virtual_rbs) return if virtual_rbs.nil? || virtual_rbs.empty? virtual_rbs.each do |filename, content| next if content.nil? || content.empty? buffer = ::RBS::Buffer.new(name: filename.to_s, content: content.to_s) _, directives, decls = ::RBS::Parser.parse_signature(buffer) source = ::RBS::Source::RBS.new(buffer, directives || [], decls || []) env.add_source(source) rescue ::RBS::BaseError # WD6 fail-soft: a single broken virtual RBS contribution # does not pull the whole env down. The plugin layer # records a `source-rbs-synthesis-failed` info diagnostic # in slice 2; here we just skip the entry. end end |
.append_stub_declarations(base_env, missing) ⇒ Object
Adds empty stub declarations for the missing referenced types (and any enclosing namespace they need) to the pre-resolve env, tagged with SYNTHETIC_STUB_BUFFER. A name that is a prefix of another name is declared ‘module` (it is a namespace); a leaf is declared `class` (referenced types appear in instance position far more often than as mixins).
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/rigor/environment/rbs_loader.rb', line 244 def append_stub_declarations(base_env, missing) names = missing.to_set missing.each do |name| parts = name.split("::") (1...parts.length).each { |i| names << parts[0, i].join("::") } end source = names.sort_by { |n| n.count(":") }.map do |name| keyword = names.any? { |other| other != name && other.start_with?("#{name}::") } ? "module" : "class" "#{keyword} #{name}\nend\n" end.join buffer = ::RBS::Buffer.new(name: SYNTHETIC_STUB_BUFFER, content: source) _, directives, decls = ::RBS::Parser.parse_signature(buffer) base_env.add_source(::RBS::Source::RBS.new(buffer, directives || [], decls || [])) rescue ::RBS::BaseError nil end |
.build_env_for(libraries:, signature_paths:, virtual_rbs: []) ⇒ 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.
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/rigor/environment/rbs_loader.rb', line 77 def build_env_for(libraries:, signature_paths:, virtual_rbs: []) 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 env = RBS::Environment.from_loader(rbs_loader) add_virtual_rbs(env, virtual_rbs) synthesize_missing_namespaces(env) resolved = env.resolve_type_names stub_missing_referenced_types(env, resolved, project_sig_files(signature_paths)) end |
.collect_missing_namespaces(env) ⇒ Object
Returns the ‘::`-stripped names of every enclosing namespace that some declaration references but no declaration defines, shallowest-first so the synthesized source declares `Foo` before `Foo::Bar`.
176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/rigor/environment/rbs_loader.rb', line 176 def collect_missing_namespaces(env) declared = env.class_decls.keys.to_set missing = {} env.class_decls.each_key do |type_name| path = type_name.namespace.path path.each_index do |i| prefix = path[0..i] full = ::RBS::TypeName.parse("::#{prefix.join('::')}") missing[prefix.join("::")] = prefix.length unless declared.include?(full) end end missing.sort_by { |_name, depth| depth }.map(&:first) end |
.default ⇒ Object
52 53 54 |
# File 'lib/rigor/environment/rbs_loader.rb', line 52 def default @default ||= new.freeze end |
.project_entry?(entry, project_files) ⇒ Boolean
True when a ‘class_decls` entry was declared in one of the project’s own signature files (by declaration location), so the sweep skips the bundled stdlib / vendored universe.
229 230 231 232 233 234 235 236 |
# File 'lib/rigor/environment/rbs_loader.rb', line 229 def project_entry?(entry, project_files) decl = entry.respond_to?(:primary_decl) ? entry.primary_decl : nil location = decl&.location buffer_name = location&.buffer&.name return false unless buffer_name project_files.include?(File.(buffer_name.to_s)) end |
.project_sig_files(signature_paths) ⇒ Object
The absolute paths of every ‘.rbs` file under the project’s ‘signature_paths:` (NOT vendored / stdlib RBS — those are well-formed, so attempting to build them would only waste time). Used to scope the referenced-type build sweep.
194 195 196 197 198 199 200 201 |
# File 'lib/rigor/environment/rbs_loader.rb', line 194 def project_sig_files(signature_paths) signature_paths.flat_map do |path| path = Pathname(path) unless path.is_a?(Pathname) next [] unless path.directory? Dir.glob(path.join("**", "*.rbs")).map { |p| File.(p) } end.to_set 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.
60 61 62 |
# File 'lib/rigor/environment/rbs_loader.rb', line 60 def reset_default! @default = nil end |
.stub_missing_referenced_types(base_env, resolved, project_files) ⇒ Object
130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/rigor/environment/rbs_loader.rb', line 130 def stub_missing_referenced_types(base_env, resolved, project_files) return resolved if project_files.empty? MAX_STUB_PASSES.times do missing = unresolved_referenced_types(resolved, project_files) break if missing.empty? append_stub_declarations(base_env, missing) resolved = base_env.resolve_type_names end resolved end |
.synthesize_missing_namespaces(env) ⇒ Object
Robustness (ADR-5): a project whose RBS declares qualified names (‘class Foo::Bar`) without ever declaring the enclosing namespace (`module Foo`) is invalid by upstream RBS rules —`RBS::DefinitionBuilder#build_instance` raises `NoTypeFoundError: Could not find ::Foo`, which the loader’s fail-soft rescue turns into a silent dispatch miss (every method on every such class degrades to ‘Dynamic`). This is a common authoring mistake (e.g. shugo/textbringer ships a `sig/` that `rbs validate` itself rejects). Rather than let an otherwise-usable signature set contribute nothing, synthesize an empty `module` declaration for each undeclared enclosing namespace so the definitions build. We only ever add names that are absent — a genuinely-declared namespace (module or class, here or in a loaded gem) is left untouched.
157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/rigor/environment/rbs_loader.rb', line 157 def synthesize_missing_namespaces(env) missing = collect_missing_namespaces(env) return if missing.empty? source = missing.map { |name| "module #{name}\nend\n" }.join buffer = ::RBS::Buffer.new(name: SYNTHETIC_NAMESPACE_BUFFER, content: source) _, directives, decls = ::RBS::Parser.parse_signature(buffer) env.add_source(::RBS::Source::RBS.new(buffer, directives || [], decls || [])) rescue ::RBS::BaseError # Fail-soft: synthesis is an opportunistic uplift, never a # hard requirement. A parse failure here just leaves the env # as it was (dispatch misses on the affected classes). nil end |
.unresolved_referenced_types(env, project_files) ⇒ Object
Builds every project class (instance + singleton side) and returns the ‘::`-stripped names of the types whose absence raised `NoTypeFoundError`. Only the FIRST missing reference per class surfaces per build, which is why the caller loops.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/rigor/environment/rbs_loader.rb', line 207 def unresolved_referenced_types(env, project_files) builder = ::RBS::DefinitionBuilder.new(env: env) missing = [] env.class_decls.each do |type_name, entry| next unless project_entry?(entry, project_files) %i[build_instance build_singleton].each do |build| builder.public_send(build, type_name) rescue ::RBS::NoTypeFoundError => e name = e.[/Could not find (\S+)/, 1] missing << name.sub(/\A::/, "") if name rescue ::RBS::BaseError # Other build failures (duplicate decl, mixin cycle, ...) # are not ours to repair here — leave them fail-soft. end end missing.uniq end |
.vendored_gem_names ⇒ Object
Gem names whose RBS ships under ‘data/vendored_gem_sigs/<gem>/`. The directory walk is the source of truth (the `README.md` sibling is not a gem and is excluded). Callers building the RBS env use this set to drop the matching `rbs collection install` directory before it double-declares against the vendored copy — the same hazard `DEFAULT_LIBRARIES` creates for stdlib-extracted gems. See `RbsCollectionDiscovery`’s ‘skip_gem_names:`.
318 319 320 321 322 323 324 |
# File 'lib/rigor/environment/rbs_loader.rb', line 318 def vendored_gem_names return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT) Dir.children(VENDORED_GEM_SIGS_ROOT).reject do |child| File.file?(File.join(VENDORED_GEM_SIGS_ROOT, child)) end end |
.vendored_gem_sig_paths ⇒ Object
301 302 303 304 305 306 307 |
# File 'lib/rigor/environment/rbs_loader.rb', line 301 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.
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
# File 'lib/rigor/environment/rbs_loader.rb', line 514 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.
423 424 425 426 427 428 429 430 431 432 |
# File 'lib/rigor/environment/rbs_loader.rb', line 423 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
643 644 645 |
# File 'lib/rigor/environment/rbs_loader.rb', line 643 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.
631 632 633 634 635 636 637 638 639 640 641 |
# File 'lib/rigor/environment/rbs_loader.rb', line 631 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.
652 653 654 655 656 657 658 |
# File 'lib/rigor/environment/rbs_loader.rb', line 652 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.
692 693 694 695 696 697 698 699 700 701 702 703 |
# File 'lib/rigor/environment/rbs_loader.rb', line 692 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).
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/rigor/environment/rbs_loader.rb', line 484 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).
665 666 667 668 669 670 671 672 673 674 |
# File 'lib/rigor/environment/rbs_loader.rb', line 665 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?.
460 461 462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/rigor/environment/rbs_loader.rb', line 460 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.
547 548 549 550 551 552 553 554 555 556 |
# File 'lib/rigor/environment/rbs_loader.rb', line 547 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?
567 568 569 570 571 572 |
# File 'lib/rigor/environment/rbs_loader.rb', line 567 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.
724 725 726 727 728 729 730 731 732 733 734 735 |
# File 'lib/rigor/environment/rbs_loader.rb', line 724 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).
444 445 446 447 448 449 450 451 452 453 454 |
# File 'lib/rigor/environment/rbs_loader.rb', line 444 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.
750 751 752 753 754 755 756 757 758 759 760 761 762 |
# File 'lib/rigor/environment/rbs_loader.rb', line 750 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.
584 585 586 587 588 589 590 591 592 593 |
# File 'lib/rigor/environment/rbs_loader.rb', line 584 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.
605 606 607 608 609 610 |
# File 'lib/rigor/environment/rbs_loader.rb', line 605 def singleton_method(class_name:, method_name:) definition = singleton_definition(class_name) return nil unless definition definition.methods[method_name.to_sym] end |
#synthesized_namespaces ⇒ Object
The enclosing namespaces synthesize_missing_namespaces had to invent because the project’s ‘signature_paths:` RBS declared qualified names (`class Foo::Bar`) without ever declaring `Foo`. Recovered by scanning the built env for class/module entries whose every declaration originated from the synthetic buffer, so the answer survives the marshalled-env cache (where no build-time collector would). Returns `::`-stripped names, shallowest-first. Empty for a well-formed sig set (the common case) and whenever the env failed to build.
387 388 389 |
# File 'lib/rigor/environment/rbs_loader.rb', line 387 def synthesized_namespaces names_synthesized_in(SYNTHETIC_NAMESPACE_BUFFER) end |
#synthesized_stub_types ⇒ Object
The referenced-but-undeclared types stub_missing_referenced_types stubbed so the project classes that mention them could build (e.g. an unavailable ‘DRb::DRbServer`, or a stale `Textbringer::EditorError`). Recovered off the built env like #synthesized_namespaces, so it survives the marshalled-env cache.
397 398 399 |
# File 'lib/rigor/environment/rbs_loader.rb', line 397 def synthesized_stub_types names_synthesized_in(SYNTHETIC_STUB_BUFFER) end |
#synthesized_type_names ⇒ Object
Every type name Rigor invented to make an otherwise-inert / unbuildable project signature set resolve — both the namespace stubs and the referenced-type stubs. MethodDispatcher resolves a call whose receiver is one of these (and that no real signature answered) to ‘Dynamic`, so the empty stub never mis-fires `call.undefined-method`. Memoised; empty (and cheap) for the common well-formed sig set.
408 409 410 |
# File 'lib/rigor/environment/rbs_loader.rb', line 408 def synthesized_type_names @state[:synthesized_type_names] ||= (synthesized_namespaces + synthesized_stub_types).to_set 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.
562 563 564 |
# File 'lib/rigor/environment/rbs_loader.rb', line 562 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.
596 597 598 |
# File 'lib/rigor/environment/rbs_loader.rb', line 596 def uncached_singleton_definition(class_name) build_singleton_definition(class_name) end |