Skip to content

Instantly share code, notes, and snippets.

@tk0miya
Created June 20, 2023 18:06
Show Gist options
  • Save tk0miya/88d6439089dbee321e992f15831913c7 to your computer and use it in GitHub Desktop.
Save tk0miya/88d6439089dbee321e992f15831913c7 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
require 'pathname'
require 'rbs'
require 'rbs/prototype/rb'
module RBS
module Prototype
class RB
alias process_orig process
attr_reader :delegates
def process(node, decls:, comments:, context:)
case node.type
when :FCALL, :VCALL
# Inside method definition cannot reach here.
args = node.children[1]&.children || []
case node.children[0]
when :delegate
@delegates ||= {}
@delegates[context.namespace] ||= []
@delegates[context.namespace] << args
else
process_orig(node, decls:, comments:, context:)
end
else
process_orig(node, decls:, comments:, context:)
end
end
end
end
end
class Delegate
attr_reader :methods, :options
def initialize(args)
*methods, options, _ = args
@methods = methods.map { |m| instantiate(m) }
@options = instantiate(options)
end
def to
options[:to]
end
def prefixed?
options.fetch(:prefix, false)
end
def instantiate(node) # rubocop:disable Metrics/MethodLength
case node
when RubyVM::AbstractSyntaxTree::Node
case node.type
when :LIT
node.children.first
when :HASH
children = node.children.first.children.compact.map { |c| instantiate(c) }
Hash[*children]
when :TRUE
true
when :FALSE
false
else
p node.type
raise
end
else
p node
raise
end
end
end
def lookup_method(rbs_builder, type_name, method_name)
rbs_builder.build_instance(type_name)&.methods&.fetch(method_name, nil)
end
def return_types(method)
return [] unless method
method.defs.map { |d| d.type.type.return_type.name }.uniq
end
pathname = Pathname('/app/app/models/xxx.rb')
parser = RBS::Prototype::RB.new
parser.parse(pathname.read)
loader = RBS::EnvironmentLoader.new
loader.add(path: Pathname('sig'))
rbs_builder = RBS::DefinitionBuilder.new env: RBS::Environment.from_loader(loader).resolve_type_names
parser.delegates.each do |namespace, definitions|
puts "class #{namespace.path.join('::')}"
definitions.each do |definition|
delegate = Delegate.new(definition)
rtypes = return_types(lookup_method(rbs_builder, namespace.to_type_name, delegate.to))
delegate.methods.each do |method|
defs = rtypes.flat_map { |rtype| lookup_method(rbs_builder, rtype, method)&.defs || [] }.map { |d| d.type.to_s }
defs << '() -> untyped' if defs.empty?
if delegate.prefixed?
puts " def #{delegate.to}_#{method}: #{defs.join(' | ')}"
else
puts " def #{method}: #{defs.join(' | ')}"
end
end
end
puts 'end'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment