lib/tapioca/compilers/symbol_table/symbol_generator.rb in tapioca-0.4.17 vs lib/tapioca/compilers/symbol_table/symbol_generator.rb in tapioca-0.4.18

- old
+ new

@@ -72,13 +72,19 @@ return unless constant compile(symbol, constant) end - sig { params(symbol: String, inherit: T::Boolean).returns(BasicObject).checked(:never) } - def resolve_constant(symbol, inherit: false) - Object.const_get(symbol, inherit) + sig do + params( + symbol: String, + inherit: T::Boolean, + namespace: Module + ).returns(BasicObject).checked(:never) + end + def resolve_constant(symbol, inherit: false, namespace: Object) + namespace.const_get(symbol, inherit) rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError nil end sig do @@ -140,13 +146,15 @@ .checked(:never) end def compile_object(name, value) return if symbol_ignored?(name) klass = class_of(value) - return if name_of(klass)&.start_with?("T::Types::", "T::Private::") + klass_name = name_of(klass) - type_name = public_module?(klass) && name_of(klass) || "T.untyped" + return if klass_name&.start_with?("T::Types::", "T::Private::") + + type_name = public_module?(klass) && klass_name || "T.untyped" indented("#{name} = T.let(T.unsafe(nil), #{type_name})") end sig { params(name: String, constant: Module).returns(T.nilable(String)) } def compile_module(name, constant) @@ -179,34 +187,37 @@ return if symbol_ignored?(name) && methods.nil? [ compile_module_helpers(constant), + compile_type_variables(constant), compile_mixins(constant), compile_mixes_in_class_methods(constant), compile_props(constant), compile_enums(constant), methods, - ].select { |b| b != "" }.join("\n\n") + ].select { |b| b && !b.empty? }.join("\n\n") end end - sig { params(constant: Module).returns(String) } + sig { params(constant: Module).returns(T.nilable(String)) } def compile_module_helpers(constant) abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type) helpers = [] helpers << indented("#{abstract_type}!") if abstract_type helpers << indented("final!") if T::Private::Final.final_module?(constant) helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant) + return if helpers.empty? + helpers.join("\n") end - sig { params(constant: Module).returns(String) } + sig { params(constant: Module).returns(T.nilable(String)) } def compile_props(constant) - return "" unless T::Props::ClassMethods === constant + return unless T::Props::ClassMethods === constant constant.props.map do |name, prop| method = "prop" method = "const" if prop.fetch(:immutable, false) type = prop.fetch(:type_object, "T.untyped").to_s.gsub(".returns(<VOID>)", ".void") @@ -217,15 +228,15 @@ indented("#{method} :#{name}, #{type}") end end.join("\n") end - sig { params(constant: Module).returns(String) } + sig { params(constant: Module).returns(T.nilable(String)) } def compile_enums(constant) - return "" unless T::Enum > constant + return unless T::Enum > constant - enums = T.cast(constant, T::Enum).values.map do |enum_type| + enums = T.unsafe(constant).values.map do |enum_type| enum_type.instance_variable_get(:@const_name).to_s end content = [ indented('enums do'), @@ -248,15 +259,75 @@ next unless subconstant compile(symbol, subconstant) end.compact - return "" if output.empty? + return if output.empty? "\n" + output.join("\n\n") end + sig { params(constant: Module).returns(T.nilable(String)) } + def compile_type_variables(constant) + type_variables = compile_type_variable_declarations(constant) + singleton_class_type_variables = compile_type_variable_declarations(singleton_class_of(constant)) + + return if !type_variables && !singleton_class_type_variables + + type_variables += "\n" if type_variables + singleton_class_type_variables += "\n" if singleton_class_type_variables + + [ + type_variables, + singleton_class_type_variables, + ].compact.join("\n").rstrip + end + + sig { params(constant: Module).returns(T.nilable(String)) } + def compile_type_variable_declarations(constant) + with_indentation_for_constant(constant) do + # Try to find the type variables defined on this constant, bail if we can't + type_variables = GenericTypeRegistry.lookup_type_variables(constant) + return unless type_variables + + # Create a map of subconstants (via their object ids) to their names. + # We need this later when we want to lookup the name of the registered type + # variable via the value of the type variable constant. + subconstant_to_name_lookup = constants_of(constant).map do |constant_name| + [ + object_id_of(resolve_constant(constant_name.to_s, namespace: constant)), + constant_name, + ] + end.to_h + + # Map each type variable to its string representation. + # + # Each entry of `type_variables` maps an object_id to a String, + # and the order they are inserted into the hash is the order they should be + # defined in the source code. + # + # By looping over these entries and then getting the actual constant name + # from the `subconstant_to_name_lookup` we defined above, gives us all the + # information we need to serialize type variable definitions. + type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable| + constant_name = subconstant_to_name_lookup[type_variable_id] + # Here, we know that constant_value will be an instance of + # T::Types::CustomTypeVariable, which knows how to serialize + # itself to a type_member/type_template + indented("#{constant_name} = #{serialized_type_variable}") + end.compact + + return if type_variable_declarations.empty? + + [ + indented("extend T::Generic"), + "", + *type_variable_declarations, + ].compact.join("\n") + end + end + sig { params(constant: Class).returns(String) } def compile_superclass(constant) superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment while (superclass = superclass_of(constant)) @@ -429,33 +500,23 @@ ) instance_methods = compile_directly_owned_methods(name, constant) singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant)) - return if symbol_ignored?(name) && instance_methods.empty? && singleton_methods.empty? + return if symbol_ignored?(name) && !instance_methods && !singleton_methods [ - initialize_method || "", + initialize_method, instance_methods, singleton_methods, - ].select { |b| b.strip != "" }.join("\n\n") + ].select { |b| b && !b.strip.empty? }.join("\n\n") end - sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) } + sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(T.nilable(String)) } def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private]) - indent_step = 0 - preamble = nil - postamble = nil - - if mod.singleton_class? - indent_step = 1 - preamble = indented("class << self") - postamble = indented("end") - end - - methods = with_indentation(indent_step) do - method_names_by_visibility(mod) + with_indentation_for_constant(mod) do + methods = method_names_by_visibility(mod) .delete_if { |visibility, _method_list| !for_visibility.include?(visibility) } .flat_map do |visibility, method_list| compiled = method_list.sort!.map do |name| next if name == :initialize compile_method(module_name, mod, mod.instance_method(name)) @@ -468,20 +529,15 @@ end compiled end .compact - .join("\n") - end - return "" if methods.strip == "" + return if methods.empty? - [ - preamble, - methods, - postamble, - ].compact.join("\n") + methods.join("\n") + end end sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) } def method_names_by_visibility(mod) { @@ -657,10 +713,37 @@ yield ensure @indent -= 2 * step end + sig do + params( + constant: Module, + blk: T.proc + .returns(T.nilable(String)) + ) + .returns(T.nilable(String)) + end + def with_indentation_for_constant(constant, &blk) + step = if constant.singleton_class? + 1 + else + 0 + end + + result = with_indentation(step, &blk) + + return result unless result + return result unless constant.singleton_class? + + [ + indented("class << self"), + result, + indented("end"), + ].compact.join("\n") + end + sig { params(str: String).returns(String) } def indented(str) " " * @indent + str end @@ -857,15 +940,15 @@ T::Private::Methods.signature_for_method(method) rescue LoadError, StandardError nil end - sig { params(constant: Module).returns(String) } + sig { params(constant: T::Types::Base).returns(String) } def type_of(constant) constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class") end - sig { params(object: Object).returns(T::Boolean).checked(:never) } + sig { params(object: BasicObject).returns(Integer).checked(:never) } def object_id_of(object) Object.instance_method(:object_id).bind(object).call end sig { params(constant: Module, other: BasicObject).returns(T::Boolean).checked(:never) }