Class: RBS::Prototype::Runtime
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Classes:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Instance Chain:
|
|
Inherits: | Object |
Defined in: | lib/rbs/prototype/runtime.rb, lib/rbs/prototype/runtime/helpers.rb, lib/rbs/prototype/runtime/reflection.rb, lib/rbs/prototype/runtime/value_object_generator.rb |
Class Method Summary
Instance Attribute Summary
Instance Method Summary
- #block_from_ast_of(method)
- #builder
- #decls
-
#ensure_outer_module_declarations(mod)
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. - #generate_class(mod)
- #generate_constants(mod, decls)
- #generate_methods(mod, module_name, members)
- #generate_mixin(mod, decl, type_name, type_name_absolute)
- #generate_module(mod)
- #generate_super_class(mod)
- #merge_rbs(module_name, members, instance: nil, singleton: nil)
- #method_type(method)
- #parse(file)
- #target?(const) ⇒ Boolean
- #target_method?(mod, instance: nil, singleton: nil) ⇒ Boolean
- #todo_object
- #type_args(type_name)
- #type_params(mod)
- #can_alias?(mod, method) ⇒ Boolean private
- #each_mixined_module(type_name, mod) private
- #each_mixined_module_one(type_name, mod) private
Helpers
- Included
#const_name, #const_name!, | |
#only_name | Returns the exact name & not compactly declared name. |
#to_type_name, #untyped |
Helpers
- Included
#any_node?, | |
#args_from_node | NOTE: args_node may be a nil by a bug bugs.ruby-lang.org/issues/17495. |
#block_from_body, #each_child, #each_node, #keyword_hash?, #symbol_literal_node?, #untyped |
Constructor Details
.new(patterns:, env:, merge:, todo: false, owners_included: []) ⇒ Runtime
# File 'lib/rbs/prototype/runtime.rb', line 70
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
Instance Attribute Details
#env (readonly)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 64
attr_reader :env
#merge (readonly)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 65
attr_reader :merge
#outline (rw)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 68
attr_accessor :outline
#owners_included (readonly)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 67
attr_reader :owners_included
#patterns (readonly)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 63
attr_reader :patterns
#todo (readonly)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 66
attr_reader :todo
Instance Method Details
#block_from_ast_of(method)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 650
def block_from_ast_of(method) return nil if RUBY_VERSION < '3.1' begin ast = RubyVM::AbstractSyntaxTree.of(method) rescue ArgumentError return # When the method is defined in eval rescue RuntimeError => error raise unless error. .include?("prism") return # When the method was compiled by prism end if ast && ast.type == :SCOPE block_from_body(ast) end end
#builder
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 100
def builder @builder ||= DefinitionBuilder.new(env: env) end
#can_alias?(mod, method) ⇒ Boolean
(private)
# File 'lib/rbs/prototype/runtime.rb', line 412
private 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
#decls
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 108
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
#each_mixined_module(type_name, mod) (private)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 128
private 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) (private)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 137
private 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
#ensure_outer_module_declarations(mod)
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 'lib/rbs/prototype/runtime.rb', line 579
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 484
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 422
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("::RBS::Unnamed::ARGFClass"), args: [], location: nil) when ENV Types::ClassInstance.new(name: TypeName("::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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 298
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 561
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 523
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 469
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 239
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 170
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)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 104
def parse(file) require file end
#target?(const) ⇒ Boolean
# File 'lib/rbs/prototype/runtime.rb', line 83
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) ⇒ Boolean
# File 'lib/rbs/prototype/runtime.rb', line 285
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
[ GitHub ]#type_args(type_name)
[ GitHub ]#type_params(mod)
[ GitHub ]# File 'lib/rbs/prototype/runtime.rb', line 641
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