class Sorbet::Private::HiddenMethodFinder
Constants
- DENYLIST
These methods are defined in C++ and we want our C++ definition to win instead of a shim.
- DIFF_RBI
- ERRORS_RBI
- HEADER
- HIDDEN_RBI
- PATH
- RBI_CONSTANTS
- RBI_CONSTANTS_ERR
- SOURCE_CONSTANTS
- SOURCE_CONSTANTS_ERR
- TMP_PATH
- TMP_RBI
Public Class Methods
main()
click to toggle source
# File lib/hidden-definition-finder.rb, line 37 def self.main self.new.main end
output_file()
click to toggle source
# File lib/hidden-definition-finder.rb, line 447 def self.output_file PATH end
Public Instance Methods
all_modules_and_aliases()
click to toggle source
# File lib/hidden-definition-finder.rb, line 73 def all_modules_and_aliases puts "Naming all Modules" [constant_cache.all_module_names.sort, constant_cache.all_module_aliases] end
capture_stderr() { || ... }
click to toggle source
# File lib/hidden-definition-finder.rb, line 399 def capture_stderr real_stderr = $stderr $stderr = StringIO.new yield $stderr.string ensure $stderr = real_stderr end
constant_cache()
click to toggle source
# File lib/hidden-definition-finder.rb, line 67 def constant_cache @cache ||= Sorbet::Private::ConstantLookupCache.new @cache end
gen_source_rbi(classes, aliases)
click to toggle source
# File lib/hidden-definition-finder.rb, line 82 def gen_source_rbi(classes, aliases) puts "Generating #{TMP_RBI} with #{classes.count} modules and #{aliases.count} aliases" serializer = Sorbet::Private::Serialize.new(constant_cache) buffer = [] buffer << Sorbet::Private::Serialize.header # should we do something with these errors? capture_stderr do classes.each do |class_name| buffer << serializer.class_or_module(class_name) end aliases.each do |base, other_names| other_names.each do |other_name| buffer << serializer.alias(base, other_name) end end end File.write(TMP_RBI, buffer.join("\n")) end
looks_like_stub_name(name)
click to toggle source
# File lib/hidden-definition-finder.rb, line 312 def looks_like_stub_name(name) name.include?('$') end
main()
click to toggle source
# File lib/hidden-definition-finder.rb, line 41 def main mk_dir require_everything classes, aliases = all_modules_and_aliases gen_source_rbi(classes, aliases) rm_rbis write_constants source, rbi = read_constants write_diff(source, rbi) split_rbi rm_dir end
mk_dir()
click to toggle source
# File lib/hidden-definition-finder.rb, line 54 def mk_dir FileUtils.mkdir_p(PATH) unless Dir.exist?(PATH) end
read_constants()
click to toggle source
# File lib/hidden-definition-finder.rb, line 155 def read_constants puts "Reading #{SOURCE_CONSTANTS}" source = JSON.parse(File.read(SOURCE_CONSTANTS)) puts "Reading #{RBI_CONSTANTS}" rbi = JSON.parse(File.read(RBI_CONSTANTS)) [source, rbi] end
real_name(mod)
click to toggle source
# File lib/hidden-definition-finder.rb, line 78 def real_name(mod) constant_cache.name_by_class(mod) end
require_everything()
click to toggle source
# File lib/hidden-definition-finder.rb, line 62 def require_everything puts "Requiring all of your code" Sorbet::Private::RequireEverything.require_everything end
rm_dir()
click to toggle source
# File lib/hidden-definition-finder.rb, line 58 def rm_dir FileUtils.rm_r(TMP_PATH) end
serialize_alias(source_entry, rbi_entry, my_klass, source_symbols, rbi_symbols)
click to toggle source
# File lib/hidden-definition-finder.rb, line 293 def serialize_alias(source_entry, rbi_entry, my_klass, source_symbols, rbi_symbols) return if rbi_entry["kind"] != "STATIC_FIELD" return if source_entry == rbi_entry if source_entry is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule' if !is_stub return end end return if !rbi_entry["aliasTo"] fqn = rbi_symbols[rbi_entry["id"]] other_fqn = rbi_symbols[rbi_entry["aliasTo"]] return if looks_like_stub_name(fqn) ret = String.new ret << "#{fqn} = #{other_fqn}\n" return ret end
serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
click to toggle source
# File lib/hidden-definition-finder.rb, line 213 def serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name) return if rbi_entry["kind"] != "CLASS_OR_MODULE" name = rbi_entry["name"]["name"] if name.start_with?('<Class:') name = name.sub('<Class:', '').sub('>', '') my_klass_is_singleton = true else my_klass_is_singleton = false end begin my_klass = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess rescue LoadError, NameError, ArgumentError => e return "# #{e.message.gsub("\n", "\n# ")}" end return if !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) && !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Module) # We specifically don't typecheck anything in T:: since it is hardcoded # into sorbet. We don't include anything in Sorbet::Private:: because # it's private. return if ['T', 'Sorbet::Private'].include?(real_name(my_klass)) source_type = nil if !source_entry if source_by_name[name] source_type = source_by_name[name]["kind"] end else source_type = source_entry["kind"] end if source_type && source_type != "CLASS_OR_MODULE" return "# The source says #{real_name(my_klass)} is a #{source_type} but reflection says it is a #{rbi_entry['kind']}" end if !source_entry source_children = [] source_mixins = [] is_stub = true else source_children = source_entry.fetch("children", []) source_mixins = source_entry.fetch("mixins", []) is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule' end rbi_children = rbi_entry.fetch("children", []) rbi_mixins = rbi_entry.fetch("mixins", []) methods = serialize_methods(source_children, rbi_children, my_klass, my_klass_is_singleton) includes = serialize_includes(source_mixins, rbi_mixins, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols) values = serialize_values(source_children, rbi_children, my_klass, source_symbols) ret = [] if !without_errors(methods).empty? || !without_errors(includes).empty? || !without_errors(values).empty? || is_stub fqn = real_name(my_klass) if fqn klass_str = String.new klass_str << (Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) ? "class #{fqn}\n" : "module #{fqn}\n") klass_str << includes.join("\n") klass_str << "\n" unless klass_str.end_with?("\n") klass_str << methods.join("\n") klass_str << "\n" unless klass_str.end_with?("\n") klass_str << values.join("\n") klass_str << "\n" unless klass_str.end_with?("\n") klass_str << "end\n" ret << klass_str end end children = serialize_constants(source_children, rbi_children, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols) if children != "" ret << children end ret.empty? ? nil : ret.join("\n") end
serialize_constants(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
click to toggle source
# File lib/hidden-definition-finder.rb, line 196 def serialize_constants(source, rbi, klass, is_singleton, source_symbols, rbi_symbols) source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h ret = [] rbi.each do |rbi_entry| # skip duplicated constant fields next if rbi_entry["name"]["kind"] == "UNIQUE" and rbi_entry["name"]["unique"] == "MANGLE_RENAME" source_entry = source_by_name[rbi_entry["name"]["name"]] ret << serialize_alias(source_entry, rbi_entry, klass, source_symbols, rbi_symbols) ret << serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name) end ret.compact.join("\n") end
symbols_id_to_name(entry, prefix)
click to toggle source
# File lib/hidden-definition-finder.rb, line 176 def symbols_id_to_name(entry, prefix) ret = {} symbols_id_to_name_real(entry, prefix, ret) ret end
write_constants()
click to toggle source
# File lib/hidden-definition-finder.rb, line 102 def write_constants puts "Printing your code's symbol table into #{SOURCE_CONSTANTS}" io = IO.popen( [ File.realpath("#{__dir__}/../bin/srb"), 'tc', '--print=symbol-table-full-json', '--stdout-hup-hack', '--silence-dev-message', '--no-error-count', '-e', # this is additive with any files / dirs '""', ], err: SOURCE_CONSTANTS_ERR ) File.write(SOURCE_CONSTANTS, io.read) io.close raise "Your source can't be read by Sorbet.\nYou can try `find . -type f | xargs -L 1 -t bundle exec srb tc --no-config --isolate-error-code 1000` and hopefully the last file it is processing before it dies is the culprit.\nIf not, maybe the errors in this file will help: #{SOURCE_CONSTANTS_ERR}" if File.read(SOURCE_CONSTANTS).empty? puts "Printing #{TMP_RBI}'s symbol table into #{RBI_CONSTANTS}" io = IO.popen( [ {'SRB_SKIP_GEM_RBIS' => 'true'}, File.realpath("#{__dir__}/../bin/srb"), 'tc', # Make sure we don't load a sorbet/config in your cwd '--no-config', '--print=symbol-table-full-json', # The hidden-definition serializer is not smart enough to put T::Enum # constants it discovers inside an `enums do` block. We probably want # to come up with a better long term solution here. '--suppress-error-code=3506', # Method redefined with mismatched argument is ok since sometime # people monkeypatch over method '--suppress-error-code=4010', # Redefining constant is needed because we serialize things both as # aliases and in-class constants. '--suppress-error-code=4012', # Invalid nesting is ok because we don't generate all the intermediate # namespaces for aliases '--suppress-error-code=4015', '--stdout-hup-hack', '--silence-dev-message', '--no-error-count', TMP_RBI, ], err: RBI_CONSTANTS_ERR ) File.write(RBI_CONSTANTS, io.read) io.close raise "#{TMP_RBI} had unexpected errors. Check this file for a clue: #{RBI_CONSTANTS_ERR}" unless $?.success? end
write_diff(source, rbi)
click to toggle source
# File lib/hidden-definition-finder.rb, line 163 def write_diff(source, rbi) puts "Building rbi id to symbol map" rbi_symbols = symbols_id_to_name(rbi, '') puts "Building source id to symbol map" source_symbols = symbols_id_to_name(source, '') puts "Writing #{DIFF_RBI}" diff = serialize_constants( source.fetch("children", []), rbi.fetch("children", []), Object, false, source_symbols, rbi_symbols) File.write(DIFF_RBI, diff) end
Private Instance Methods
categorize(line)
click to toggle source
# File lib/hidden-definition-finder.rb, line 440 def categorize(line) if line.start_with?('#') return :errors end return :hidden end
rm_rbis()
click to toggle source
# File lib/hidden-definition-finder.rb, line 408 def rm_rbis File.delete(HIDDEN_RBI) if File.exist?(HIDDEN_RBI) File.delete(ERRORS_RBI) if File.exist?(ERRORS_RBI) end
serialize_includes(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
click to toggle source
# File lib/hidden-definition-finder.rb, line 386 def serialize_includes(source, rbi, klass, is_singleton, source_symbols, rbi_symbols) ret = [] source_mixins = source.map {|id| source_symbols[id]} rbi_mixins = rbi.map {|id| rbi_symbols[id]} rbi_mixins.each do |rbi_mixin| if !source_mixins.include?(rbi_mixin) keyword = is_singleton ? "extend" : "include" ret << " #{keyword} ::#{rbi_mixin}" end end ret end
serialize_methods(source, rbi, klass, is_singleton)
click to toggle source
# File lib/hidden-definition-finder.rb, line 352 def serialize_methods(source, rbi, klass, is_singleton) source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h ret = [] maker = Sorbet::Private::Serialize.new(constant_cache) rbi.each do |rbi_entry| next if rbi_entry["kind"] != "METHOD" name = rbi_entry["name"]["name"] next if source_by_name[name] next if DENYLIST.include?([klass.object_id, name]) next if name.start_with?('<') && name.end_with?('>') begin if is_singleton method = klass.singleton_method(name) else method = klass.instance_method(name) end rescue => e ret << "# #{e.message.gsub("\n", "\n# ")}" next end errors = capture_stderr do ret << maker.serialize_method(method, is_singleton, with_sig: false) end errors.split("\n").each do |line| ret << "# #{line}" end end ret end
serialize_values(source, rbi, klass, source_symbols)
click to toggle source
# File lib/hidden-definition-finder.rb, line 316 def serialize_values(source, rbi, klass, source_symbols) source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h ret = [] rbi.each do |rbi_entry| name = rbi_entry["name"]["name"] source_entry = source_by_name[name] if source_entry is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule' next unless is_stub end next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{Sorbet::Private::RealStdlib.real_name(klass)}::#{name}") begin my_value = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess rescue StandardError, LoadError => e ret << "# #{e.message.gsub("\n", "\n# ")}" next end next if Sorbet::Private::RealStdlib.real_is_a?(my_value, Class) || Sorbet::Private::RealStdlib.real_is_a?(my_value, Module) if defined?(T::Types::TypeMember) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeMember) ret << (my_value.variance == :invariant ? " #{name} = type_member" : " #{name} = type_member(#{my_value.variance.inspect})") elsif defined?(T::Types::TypeTemplate) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeTemplate) ret << (my_value.variance == :invariant ? " #{name} = type_template" : " #{name} = type_template(#{my_value.variance.inspect})") else ret << " #{name} = ::T.let(nil, ::T.untyped)" end end ret end
split_rbi()
click to toggle source
# File lib/hidden-definition-finder.rb, line 413 def split_rbi puts "Generating split RBIs into #{PATH}" output = { hidden: String.new, errors: String.new, } valid = File.read(DIFF_RBI) cur_output = T.let(nil, T.untyped) valid.split("\n").each do |line| category = categorize(line) if category == :errors # Don't ever switch to errors output permanantly output[category] << line + "\n" next end if !category.nil? cur_output = output[category] end cur_output << line + "\n" end File.write(HIDDEN_RBI, HEADER + "\n" + output[:hidden]) File.write(ERRORS_RBI, HEADER + "\n" + output[:errors]) end
symbols_id_to_name_real(entry, prefix, ret)
click to toggle source
# File lib/hidden-definition-finder.rb, line 182 def symbols_id_to_name_real(entry, prefix, ret) name = entry["name"]["name"] if prefix == '' || prefix == "<root>" fqn = name.to_s else fqn = "#{prefix}::#{name}" end ret[entry["id"]] = fqn entry.fetch("children", []).each do |child| symbols_id_to_name_real(child, fqn, ret) end end
without_errors(lines)
click to toggle source
# File lib/hidden-definition-finder.rb, line 289 def without_errors(lines) lines.reject {|line| line.start_with?("#")} end