class RBS::Prototype::Runtime - rbs: Ruby Standard Library Documentation

# class RBS::Prototype::Runtime

### Attributes

env[R]
merge[R]
outline[RW]
owners_included[R]
patterns[R]
todo[R]

### Public Class Methods

new(patterns:, env:, merge:, todo: false, owners_included: []) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 71
    def initialize(patterns:, env:, merge:, todo: false, owners_included: [])
      @patterns = patterns
      @decls = nil
      @modules = {}
      @env = env
      @merge = merge
      @owners_included = owners_included.map do |name|
        Object.const_get(name)
      end
      @outline = false
      @todo = todo
    end

### Public Instance Methods

block_from_ast_of(method) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 651
    def block_from_ast_of(method)
      begin
        ast = RubyVM::AbstractSyntaxTree.of(method)
      rescue ArgumentError
        return # When the method is defined in eval
      rescue RuntimeError => error
        raise unless error.message.include?("prism")
        return # When the method was compiled by prism
      end

      if ast && ast.type == :SCOPE
        block_from_body(ast)
      end
    end

builder() click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 101
    def builder
      @builder ||= DefinitionBuilder.new(env: env)
    end

decls() click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 109
    def decls
      unless @decls
        @decls = []
        @modules = ObjectSpace.each_object(Module)
          .map { |mod| [const_name(mod), mod] }
          .select { |name, _| name }
          .to_h
        @modules.select { |name, mod| target?(mod) }.sort_by { |name, _| name }.each do |_, mod|
          case mod
          when Class
            generate_class mod
          when Module
            generate_module mod
          end
        end
      end

      @decls or raise
    end

ensure_outer_module_declarations(mod) click to toggle source

Generate/find outer module declarations This is broken down into another method to comply with ‘DRY` This generates/finds declarations in nested form & returns the last array of declarations

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 580
    def ensure_outer_module_declarations(mod)
      # @type var outer_module_names: Array[String]
      *outer_module_names, _ = const_name!(mod).split(/::/) #=> parent = [A, B], mod = C
      # Copy the entries in ivar @decls, not .dup
      destination = @decls || [] #: Array[AST::Declarations::Class::member]

      outer_module_names&.each_with_index do |outer_module_name, i|
        current_name = outer_module_names.take(i+1).join('::')
        outer_module = @modules[current_name]
        outer_decl = destination.detect do |decl|
          case outer_module
          when Class
            decl.is_a?(AST::Declarations::Class) && decl.name.name == outer_module_name.to_sym
          when Module
            decl.is_a?(AST::Declarations::Module) && decl.name.name == outer_module_name.to_sym
          end
        end #: AST::Declarations::Class | AST::Declarations::Module | nil

        # Insert AST::Declarations if declarations are not added previously
        unless outer_decl
          outer_module or raise

          if outer_module.is_a?(Class)
            outer_decl = AST::Declarations::Class.new(
              name: to_type_name(outer_module_name),
              type_params: type_params(outer_module),
              super_class: generate_super_class(outer_module),
              members: [],
              annotations: [],
              location: nil,
              comment: nil
            )
          else
            outer_decl = AST::Declarations::Module.new(
              name: to_type_name(outer_module_name),
              type_params: type_params(outer_module),
              self_types: [],
              members: [],
              annotations: [],
              location: nil,
              comment: nil
            )
          end

          destination << outer_decl
        end

        destination = outer_decl.members
      end

      # Return the array of declarations checked out at the end
      destination
    end

generate_class(mod) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 485
    def generate_class(mod)
      type_name_absolute = to_type_name(const_name!(mod), full_name: true).absolute!
      type_name = to_type_name(const_name!(mod))
      outer_decls = ensure_outer_module_declarations(mod)

      # Check if a declaration exists for the actual module
      decl = outer_decls.detect do |decl|
        decl.is_a?(AST::Declarations::Class) && decl.name.name == only_name(mod).to_sym
      end #: AST::Declarations::Class?

      unless decl
        if StructGenerator.generatable?(mod)
          decl = StructGenerator.new(mod).build_decl
        elsif DataGenerator.generatable?(mod)
          decl = DataGenerator.new(mod).build_decl
        else
          decl = AST::Declarations::Class.new(
            name: to_type_name(only_name(mod)),
            type_params: type_params(mod),
            super_class: generate_super_class(mod),
            members: [],
            annotations: [],
            location: nil,
            comment: nil
          )
        end

        outer_decls << decl
      end

      generate_mixin(mod, decl, type_name, type_name_absolute)

      unless mod < Struct || (RUBY_VERSION >= '3.2' && mod < Data)
        generate_methods(mod, type_name, decl.members) unless outline
      end

      generate_constants mod, decl.members
    end

generate_constants(mod, decls) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 423
    def generate_constants(mod, decls)
      module_name = const_name!(mod)
      Reflection.constants_of(mod, false).sort.each do |name|
        next if todo_object&.skip_constant?(module_name: module_name, name: name)

        begin
          value = mod.const_get(name)
        rescue StandardError, LoadError => e
          RBS.logger.warn("Skipping constant #{name} of #{mod} since #{e}")
          next
        end

        next if Reflection.object_class(value).equal?(Class)
        next if Reflection.object_class(value).equal?(Module)

        unless Reflection.object_class(value).name
          RBS.logger.warn("Skipping constant #{name} #{value} of #{mod} as an instance of anonymous class")
          next
        end

        type = case value
               when true, false
                 Types::Bases::Bool.new(location: nil)
               when nil
                 Types::Optional.new(
                   type: Types::Bases::Any.new(location: nil),
                   location: nil
                 )
               when ARGF
                 Types::ClassInstance.new(name: TypeName.parse("::RBS::Unnamed::ARGFClass"), args: [], location: nil)
               when ENV
                 Types::ClassInstance.new(name: TypeName.parse("::RBS::Unnamed::ENVClass"), args: [], location: nil)
               else
                 value_type_name = to_type_name(const_name!(Reflection.object_class(value)), full_name: true).absolute!
                 args = type_args(value_type_name)
                 Types::ClassInstance.new(name: value_type_name, args: args, location: nil)
               end

        decls << AST::Declarations::Constant.new(
          name: to_type_name(name.to_s),
          type: type,
          location: nil,
          comment: nil
        )
      end
    end

generate_methods(mod, module_name, members) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 299
    def generate_methods(mod, module_name, members)
      module_name_absolute = to_type_name(const_name!(mod), full_name: true).absolute!
      mod.singleton_methods.select {|name| target_method?(mod, singleton: name) }.sort.each do |name|
        method = mod.singleton_class.instance_method(name)
        next if todo_object&.skip_singleton_method?(module_name: module_name_absolute, method: method, accessibility: :public)

        if can_alias?(mod.singleton_class, method)
          members << AST::Members::Alias.new(
            new_name: method.name,
            old_name: method.original_name,
            kind: :singleton,
            location: nil,
            comment: nil,
            annotations: [],
          )
        else
          merge_rbs(module_name, members, singleton: name) do
            RBS.logger.info "missing #{module_name}.#{name} #{method.source_location}"

            members << AST::Members::MethodDefinition.new(
              name: method.name,
              overloads: [
                AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
              ],
              kind: :singleton,
              location: nil,
              comment: nil,
              annotations: [],
              overloading: false,
              visibility: nil
            )
          end
        end
      end

      public_instance_methods = mod.public_instance_methods.select {|name| target_method?(mod, instance: name) }
      unless public_instance_methods.empty?
        public_instance_methods.sort.each do |name|
          method = mod.instance_method(name)
          next if todo_object&.skip_instance_method?(module_name: module_name_absolute, method: method, accessibility: :public)

          if can_alias?(mod, method)
            members << AST::Members::Alias.new(
              new_name: method.name,
              old_name: method.original_name,
              kind: :instance,
              location: nil,
              comment: nil,
              annotations: [],
            )
          else
            merge_rbs(module_name, members, instance: name) do
              RBS.logger.info "missing #{module_name}##{name} #{method.source_location}"

              members << AST::Members::MethodDefinition.new(
                name: method.name,
                overloads: [
                  AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
                ],
                kind: :instance,
                location: nil,
                comment: nil,
                annotations: [],
                overloading: false,
                visibility: nil
              )
            end
          end
        end
      end

      private_instance_methods = mod.private_instance_methods.select {|name| target_method?(mod, instance: name) }
      unless private_instance_methods.empty?
        added = false
        members << AST::Members::Private.new(location: nil)

        private_instance_methods.sort.each do |name|
          method = mod.instance_method(name)
          next if todo_object&.skip_instance_method?(module_name: module_name_absolute, method: method, accessibility: :private)

          added = true
          if can_alias?(mod, method)
            members << AST::Members::Alias.new(
              new_name: method.name,
              old_name: method.original_name,
              kind: :instance,
              location: nil,
              comment: nil,
              annotations: [],
            )
          else
            merge_rbs(module_name, members, instance: name) do
              RBS.logger.info "missing #{module_name}##{name} #{method.source_location}"

              members << AST::Members::MethodDefinition.new(
                name: method.name,
                overloads: [
                  AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
                ],
                kind: :instance,
                location: nil,
                comment: nil,
                annotations: [],
                overloading: false,
                visibility: nil
              )
            end
          end
        end

        members.pop unless added
      end
    end

generate_mixin(mod, decl, type_name, type_name_absolute) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 562
    def generate_mixin(mod, decl, type_name, type_name_absolute)
      each_mixined_module(type_name, mod) do |module_name, module_full_name, mixin_class|
        next if todo_object&.skip_mixin?(type_name: type_name_absolute, module_name: module_full_name, mixin_class: mixin_class)

        args = type_args(module_full_name)
        decl.members << mixin_class.new(
          name: module_name,
          args: args,
          location: nil,
          comment: nil,
          annotations: []
        )
      end
    end

generate_module(mod) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 524
    def generate_module(mod)
      name = const_name(mod)

      unless name
        RBS.logger.warn("Skipping anonymous module #{mod}")
        return
      end

      type_name_absolute = to_type_name(name, full_name: true).absolute!
      type_name = to_type_name(name)
      outer_decls = ensure_outer_module_declarations(mod)

      # Check if a declaration exists for the actual class
      decl = outer_decls.detect do |decl|
        decl.is_a?(AST::Declarations::Module) && decl.name.name == only_name(mod).to_sym
      end #: AST::Declarations::Module?

      unless decl
        decl = AST::Declarations::Module.new(
          name: to_type_name(only_name(mod)),
          type_params: type_params(mod),
          self_types: [],
          members: [],
          annotations: [],
          location: nil,
          comment: nil
        )

        outer_decls << decl
      end

      generate_mixin(mod, decl, type_name, type_name_absolute)

      generate_methods(mod, type_name, decl.members) unless outline

      generate_constants mod, decl.members
    end

generate_super_class(mod) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 470
    def generate_super_class(mod)
      superclass = mod.superclass

      if superclass.nil? || superclass == ::Object
        nil
      elsif const_name(superclass).nil?
        RBS.logger.warn("Skipping anonymous superclass #{superclass} of #{mod}")
        nil
      else
        super_name = to_type_name(const_name!(superclass), full_name: true).absolute!
        super_args = type_args(super_name)
        AST::Declarations::Class::Super.new(name: super_name, args: super_args, location: nil)
      end
    end

merge_rbs(module_name, members, instance: nil, singleton: nil) { || ... } click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 240
    def merge_rbs(module_name, members, instance: nil, singleton: nil)
      if merge
        if env.class_decls[module_name.absolute!]
          # @type var kind: AST::Members::MethodDefinition::kind
          case
          when instance
            method = builder.build_instance(module_name.absolute!).methods[instance]
            method_name = instance
            kind = :instance
          when singleton
            method = builder.build_singleton(module_name.absolute!).methods[singleton]
            method_name = singleton
            kind = :singleton
          end

          if method
            members << AST::Members::MethodDefinition.new(
              name: method_name,
              overloads: method.method_types.map {|type|
                AST::Members::MethodDefinition::Overload.new(
                  annotations: [],
                  method_type: type.update.tap do |ty|
                    def ty.to_s
                      location or raise
                      location.source
                    end
                  end
                )
              },
              kind: kind,
              location: nil,
              comment: method.comments[0],
              annotations: method.annotations,
              overloading: false,
              visibility: nil
            )
            return
          end
        end

        yield
      else
        yield
      end
    end

method_type(method) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 171
    def method_type(method)
      untyped = Types::Bases::Any.new(location: nil)

      required_positionals = [] #: Array[Types::Function::Param]
      optional_positionals = [] #: Array[Types::Function::Param]
      rest = nil #: Types::Function::Param?
      trailing_positionals = [] #: Array[Types::Function::Param]
      required_keywords = {} #: Hash[Symbol, Types::Function::Param]
      optional_keywords = {} #: Hash[Symbol, Types::Function::Param]
      rest_keywords = nil #: Types::Function::Param?

      requireds = required_positionals

      block = nil #: Types::Block?

      method.parameters.each do |(kind, name)|
        case kind
        when :req
          requireds << Types::Function::Param.new(name: name, type: untyped)
        when :opt
          requireds = trailing_positionals
          optional_positionals << Types::Function::Param.new(name: name, type: untyped)
        when :rest
          requireds = trailing_positionals
          name = nil if name == :* # For `def f(...) end` syntax
          rest = Types::Function::Param.new(name: name, type: untyped)
        when :keyreq
          name or raise
          required_keywords[name] = Types::Function::Param.new(name: nil, type: untyped)
        when :key
          name or raise
          optional_keywords[name] = Types::Function::Param.new(name: nil, type: untyped)
        when :keyrest
          rest_keywords = Types::Function::Param.new(name: nil, type: untyped)
        when :block
          block = Types::Block.new(
            type: Types::Function.empty(untyped).update(rest_positionals: Types::Function::Param.new(name: nil, type: untyped)),
            required: true,
            self_type: nil
          )
        end
      end

      block ||= block_from_ast_of(method)

      return_type = if method.name == :initialize
                      Types::Bases::Void.new(location: nil)
                    else
                      untyped
                    end
      method_type = Types::Function.new(
        required_positionals: required_positionals,
        optional_positionals: optional_positionals,
        rest_positionals: rest,
        trailing_positionals: trailing_positionals,
        required_keywords: required_keywords,
        optional_keywords: optional_keywords,
        rest_keywords: rest_keywords,
        return_type: return_type,
      )

      MethodType.new(
        location: nil,
        type_params: [],
        type: method_type,
        block: block
      )
    end

parse(file) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 105
    def parse(file)
      require file
    end

target?(const) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 84
    def target?(const)
      name = const_name(const)
      return false unless name

      patterns.any? do |pattern|
        if pattern.end_with?("*")
          (name || "").start_with?(pattern.chop)
        else
          name == pattern
        end
      end
    end

target_method?(mod, instance: nil, singleton: nil) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 286
    def target_method?(mod, instance: nil, singleton: nil)
      case
      when instance
        method = mod.instance_method(instance)
        method.owner == mod || owners_included.any? {|m| method.owner == m }
      when singleton
        method = mod.singleton_class.instance_method(singleton)
        method.owner == mod.singleton_class || owners_included.any? {|m| method.owner == m.singleton_class }
      else
        raise
      end
    end

todo_object() click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 97
    def todo_object
      @todo_object ||= Todo.new(builder: builder) if todo
    end

type_args(type_name) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 634
    def type_args(type_name)
      if class_decl = env.class_decls.fetch(type_name.absolute!, nil)
        class_decl.type_params.size.times.map { Types::Bases::Any.new(location: nil) }
      else
        []
      end
    end

type_params(mod) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 642
    def type_params(mod)
      type_name = to_type_name(const_name!(mod), full_name: true)
      if class_decl = env.class_decls[type_name.absolute!]
        class_decl.type_params
      else
        []
      end
    end

### Private Instance Methods

can_alias?(mod, method) click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 413
            def can_alias?(mod, method)
      return false if method.name == method.original_name

      begin
        mod.instance_method(method.original_name) && true
      rescue NameError
        false
      end
    end

each_mixined_module(type_name, mod) { |module_name, module_full_name, is_prepend ? Prepend : Include| ... } click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 129
            def each_mixined_module(type_name, mod)
      each_mixined_module_one(type_name, mod) do |module_name, module_full_name, is_prepend|
        yield module_name, module_full_name, is_prepend ? AST::Members::Prepend : AST::Members::Include
      end
      each_mixined_module_one(type_name, mod.singleton_class) do |module_name, module_full_name, _|
        yield module_name, module_full_name, AST::Members::Extend
      end
    end

each_mixined_module_one(type_name, mod) { |module_name, module_full_name, include?(mix)| ... } click to toggle source

    # File rbs-3.8.0/lib/rbs/prototype/runtime.rb, line 138
            def each_mixined_module_one(type_name, mod)
      supers = Set[] #: Set[Module]
      prepends = mod.ancestors.take_while { |m| !mod.equal?(m) }.to_set

      mod.included_modules.each do |mix|
        supers.merge(mix.included_modules)
      end

      if mod.is_a?(Class)
        if superclass = mod.superclass
          superclass.included_modules.each do |mix|
            supers << mix
            supers.merge(mix.included_modules)
          end
        end
      end

      mod.included_modules.uniq.each do |mix|
        if !supers.include?(mix) || prepends.include?(mix)
          unless const_name(mix)
            RBS.logger.warn("Skipping anonymous module #{mix} included in #{mod}")
          else
            module_name = module_full_name = to_type_name(const_name!(mix), full_name: true)
            if module_full_name.namespace == type_name.namespace
              module_name = TypeName.new(name: module_full_name.name, namespace: Namespace.empty)
            end

            yield module_name, module_full_name, prepends.include?(mix)
          end
        end
      end
    end