class Sorbet::Private::Serialize
def self.header(typed="true", subcommand="update")
def self.header(typed="true", subcommand="update") buffer = [] buffer << "# This file is autogenerated. Do not edit it by hand. Regenerate it with:" buffer << "# srb rbi #{subcommand}" if typed buffer << "" buffer << "# typed: #{typed}" end buffer << "" buffer.join("\n") end
def alias(base, other_name)
def alias(base, other_name) ret = String.new ret << "#{other_name} = #{base}" ret end
def ancestor_has_method(method, klass)
def ancestor_has_method(method, klass) return false if !Sorbet::Private::RealStdlib.real_is_a?(klass, Class) first_ancestor = klass.ancestors.find do |ancestor| next if ancestor == klass begin ancestor.instance_method(method.name) rescue NameError nil end end return false unless first_ancestor first_ancestor.instance_method(method.name).parameters == method.parameters end
def blacklisted_method(method)
def blacklisted_method(method) method.name =~ /__validator__[0-9]{8}/ || method.name =~ /.*:.*/ end
def class_or_module(class_name)
def class_or_module(class_name) if !valid_class_name(class_name) return " # Skipping serializing #{class_name} because it is an invalid name\n" end klass = @constant_cache.class_by_name(class_name) is_nil = nil.equal?(klass) raise "#{class_name} is not a Class or Module. Maybe it was miscategorized?" if is_nil ret = String.new superclass = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? Sorbet::Private::RealStdlib.real_superclass(klass) : nil if superclass superclass_str = Sorbet::Private::RealStdlib.real_eqeq(superclass, Object) ? '' : @constant_cache.name_by_class(superclass) else superclass_str = '' end superclass_str = !superclass_str || superclass_str.empty? ? '' : " < #{superclass_str}" ret << (Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? "class #{class_name}#{superclass_str}\n" : "module #{class_name}\n") ret << " extend ::T::Sig\n" # We don't use .included_modules since that also has all the aweful things # that are mixed into Object. This way we at least have a delimiter before # the awefulness starts (the superclass). Sorbet::Private::RealStdlib.real_ancestors(klass).each do |ancestor| next if Sorbet::Private::RealStdlib.real_eqeq(ancestor, klass) break if Sorbet::Private::RealStdlib.real_eqeq(ancestor, superclass) ancestor_name = @constant_cache.name_by_class(ancestor) next unless ancestor_name next if ancestor_name == class_name if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class) ret << " # Skipping `include #{ancestor_name}` because it is a Class\n" next end if !valid_class_name(ancestor_name) ret << " # Skipping `include #{ancestor_name}` because it is an invalid name\n" next end ret << " include ::#{ancestor_name}\n" end Sorbet::Private::RealStdlib.real_singleton_class(klass).ancestors.each do |ancestor| next if ancestor == Sorbet::Private::RealStdlib.real_singleton_class(klass) break if superclass && ancestor == Sorbet::Private::RealStdlib.real_singleton_class(superclass) break if ancestor == Module break if ancestor == Object ancestor_name = @constant_cache.name_by_class(ancestor) next unless ancestor_name if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class) ret << " # Skipping `extend #{ancestor_name}` because it is a Class\n" next end if !valid_class_name(ancestor_name) ret << " # Skipping `extend #{ancestor_name}` because it is an invalid name\n" next end ret << " extend ::#{ancestor_name}\n" end constants = [] # Declare all the type_members and type_templates constants += get_constants(klass).uniq.map do |const_sym| # We have to not pass `false` because `klass.constants` intentionally is # pulling in all the ancestor constants next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}") begin value = klass.const_get(const_sym) rescue LoadError, NameError, RuntimeError, ArgumentError ret << "# Failed to load #{class_name}::#{const_sym}\n" next end # next if !Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeVariable) next if Sorbet::Private::RealStdlib.real_is_a?(value, Module) next if !comparable?(value) [const_sym, value] end constants += get_constants(klass, false).uniq.map do |const_sym| next if BLACKLIST_CONSTANTS.include?([class_name, const_sym]) next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}") begin value = klass.const_get(const_sym, false) rescue LoadError, NameError, RuntimeError, ArgumentError ret << "# Failed to load #{class_name}::#{const_sym}\n" next end next if Sorbet::Private::RealStdlib.real_is_a?(value, Module) next if !comparable?(value) [const_sym, value] end constants_sorted = constants.compact.sort_by do |const_sym, _value| const_sym end constants_uniq = constants_sorted.uniq do |const_sym, _value| const_sym.hash end constants_serialized = constants_uniq.map do |const_sym, value| constant(const_sym, value) end ret << constants_serialized.join("\n") ret << "\n\n" if !constants_serialized.empty? methods = [] instance_methods = Sorbet::Private::RealStdlib.real_instance_methods(klass, false) begin initialize = klass.instance_method(:initialize) rescue initialize = nil end if initialize && initialize.owner == klass # This method never apears in the reflection list... instance_methods += [:initialize] end Sorbet::Private::RealStdlib.real_ancestors(klass).reject {|ancestor| @constant_cache.name_by_class(ancestor)}.each do |ancestor| instance_methods += ancestor.instance_methods(false) end # uniq here is required because we populate additional methos from anonymous superclasses and there # might be duplicates methods += instance_methods.sort.uniq.map do |method_sym| begin method = klass.instance_method(method_sym) rescue => e ret << "# #{e}\n" next end next if blacklisted_method(method) next if ancestor_has_method(method, klass) serialize_method(method) end # uniq is not required here, but added to be on the safe side methods += Sorbet::Private::RealStdlib.real_singleton_methods(klass, false).sort.uniq.map do |method_sym| begin method = klass.singleton_method(method_sym) rescue => e ret << "# #{e}\n" next end next if blacklisted_method(method) next if ancestor_has_method(method, Sorbet::Private::RealStdlib.real_singleton_class(klass)) serialize_method(method, true) end ret << methods.join("\n") ret << "end\n" ret end
def comparable?(value)
def comparable?(value) return false if Sorbet::Private::RealStdlib.real_is_a?(value, BigDecimal) && value.nan? return false if Sorbet::Private::RealStdlib.real_is_a?(value, Float) && value.nan? return false if Sorbet::Private::RealStdlib.real_is_a?(value, Complex) true end
def constant(const, value)
def constant(const, value) #if Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeTemplate) #" #{const} = type_template" #elsif Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeMember) #" #{const} = type_member" #else #" #{const} = ::T.let(nil, ::T.untyped)" #end if KEYWORDS.include?(const.to_sym) return "# Illegal constant name: #{const}" end " #{const} = ::T.let(nil, ::T.untyped)" end
def from_method(method)
def from_method(method) uniq = 0 method.parameters.map.with_index do |(kind, name), index| if !name arg_name = method.name.to_s[0...-1] if (!KEYWORDS.include?(arg_name.to_sym)) && method.name.to_s.end_with?('=') && arg_name =~ /\A[a-z_][a-z0-9A-Z_]*\Z/ && index == 0 name = arg_name else name = '_' + (uniq == 0 ? '' : uniq.to_s) uniq += 1 end end [kind, name] end end
def get_constants(mod, inherited=nil)
def get_constants(mod, inherited=nil) constants ||= Module.instance_method(:constants) erited.nil? l_constants.bind(mod).call l_constants.bind(mod).call(inherited)
def initialize(constant_cache)
def initialize(constant_cache) @constant_cache = constant_cache end
def serialize_method(method, static=false, with_sig: true)
def serialize_method(method, static=false, with_sig: true) name = method.name.to_s ret = String.new if !valid_method_name(name) ret << "# Illegal method name: #{name}" return end parameters = from_method(method) # a hack for appeasing Sorbet in the presence of the Enumerable interface if name == 'each' && !parameters.any? {|(kind, _)| kind == :block} parameters.push([:block, "blk"]) end ret << serialize_sig(parameters) if with_sig args = parameters.map do |(kind, param_name)| to_sig(kind, param_name) end.compact.join(', ') ret << " def #{static ? 'self.' : ''}#{name}(#{args}); end\n" ret end
def serialize_sig(parameters)
def serialize_sig(parameters) ret = String.new if !parameters.empty? ret << " sig do\n" ret << " params(\n" parameters.each do |(_kind, name)| ret << " #{name}: ::T.untyped,\n" end ret << " )\n" ret << " .returns(::T.untyped)\n" ret << " end\n" else ret << " sig {returns(::T.untyped)}\n" end ret end
def to_sig(kind, name)
def to_sig(kind, name) case kind when :req name.to_s when :opt "#{name}=T.unsafe(nil)" when :rest "*#{name}" when :keyreq "#{name}:" when :key "#{name}: T.unsafe(nil)" when :keyrest "**#{name}" when :block "&#{name}" end end
def valid_class_name(name)
def valid_class_name(name) name.split("::").each do |piece| return false if piece[0].upcase != piece[0] end return false if [ 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ClassOverride', 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ModuleOverride', 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ObjectOverride', ].include?(name) true end
def valid_method_name(name)
def valid_method_name(name) return true if SPECIAL_METHOD_NAMES.include?(name) return false if name =~ /^\d/ name =~ /^[[:word:]]+[?!=]?$/ end