lib/ivar/manifest.rb
# frozen_string_literal: true require_relative "declaration" require_relative "explicit_declaration" require_relative "explicit_keyword_declaration" require_relative "explicit_positional_declaration" module Ivar # Represents a manifest of instance variable declarations for a class/module class Manifest # @return [Class, Module] The class or module this manifest is associated with attr_reader :owner # Initialize a new manifest # @param owner [Class, Module] The class or module this manifest is associated with def initialize(owner) @owner = owner @declarations_by_name = {} end # @return [Hash<Symbol, Declaration>] The declarations hash keyed by variable name attr_reader :declarations_by_name # @return [Array<Declaration>] The declarations in this manifest def declarations @declarations_by_name.values end # Add an explicit declaration to the manifest # @param declaration [ExplicitDeclaration] The declaration to add # @return [ExplicitDeclaration] The added declaration def add_explicit_declaration(declaration) name = declaration.name @declarations_by_name[name] = declaration declaration.on_declare(@owner) declaration end # Get all ancestor manifests in reverse order (from highest to lowest in the hierarchy) # Only includes ancestors that have existing manifests # @return [Array<Manifest>] Array of ancestor manifests def ancestor_manifests return [] unless @owner.respond_to?(:ancestors) @owner .ancestors.reject { |ancestor| ancestor == @owner } .filter_map { |ancestor| Ivar.get_manifest(ancestor, create: false) } .reverse end def explicitly_declared_ivars all_declarations.grep(ExplicitDeclaration).map(&:name) end # Get all declarations, including those from ancestor manifests # @return [Array<Declaration>] All declarations def all_declarations ancestor_manifests .flat_map(&:declarations) .+(declarations) # use hash stores to preserve order and deduplicate by name .each_with_object({}) { |decl, acc| acc[decl.name] = decl } .values end # Check if a variable is declared in this manifest or ancestor manifests # @param name [Symbol, String] The variable name # @return [Boolean] Whether the variable is declared def declared?(name) name = name.to_sym # Check in this manifest first return true if @declarations_by_name.key?(name) # Then check in ancestor manifests ancestor_manifests.any? do |ancestor_manifest| ancestor_manifest.declarations_by_name.key?(name) end end # Get a declaration by name # @param name [Symbol, String] The variable name # @return [Declaration, nil] The declaration, or nil if not found def get_declaration(name) name = name.to_sym # Check in this manifest first return @declarations_by_name[name] if @declarations_by_name.key?(name) # Then check in ancestor manifests, starting from the closest ancestor ancestor_manifests.each do |ancestor_manifest| if ancestor_manifest.declarations_by_name.key?(name) return ancestor_manifest.declarations_by_name[name] end end nil end # Get all explicit declarations # @return [Array<ExplicitDeclaration>] All explicit declarations def explicit_declarations declarations.select { |decl| decl.is_a?(ExplicitDeclaration) } end # Process before_init callbacks for all declarations # @param instance [Object] The object being initialized # @param args [Array] Positional arguments # @param kwargs [Hash] Keyword arguments # @return [Array, Hash] The modified args and kwargs def process_before_init(instance, args, kwargs) # Get all declarations from parent to child, with child declarations taking precedence declarations_to_process = all_declarations # Process all initializations in a single pass # The before_init method will handle keyword arguments with proper precedence declarations_to_process.each do |declaration| declaration.before_init(instance, args, kwargs) end [args, kwargs] end end end