From e42c9293a92d1bcec55c0d7fe3532b588355beb0 Mon Sep 17 00:00:00 2001 From: ksss Date: Wed, 1 Nov 2023 10:20:05 +0900 Subject: [PATCH 1/2] Fix error if direct inherited --- lib/rbs/prototype/runtime.rb | 4 +- .../runtime/value_object_generator.rb | 81 +++++++++++++------ test/rbs/runtime_prototype_test.rb | 24 ++++++ test/test_helper.rb | 4 + 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/lib/rbs/prototype/runtime.rb b/lib/rbs/prototype/runtime.rb index cec0448a5..e9c482538 100644 --- a/lib/rbs/prototype/runtime.rb +++ b/lib/rbs/prototype/runtime.rb @@ -493,9 +493,9 @@ def generate_class(mod) end #: AST::Declarations::Class? unless decl - if mod < Struct + if StructGenerator.generatable?(mod) decl = StructGenerator.new(mod).build_decl - elsif RUBY_VERSION >= '3.2' && mod < Data + elsif DataGenerator.generatable?(mod) decl = DataGenerator.new(mod).build_decl else decl = AST::Declarations::Class.new( diff --git a/lib/rbs/prototype/runtime/value_object_generator.rb b/lib/rbs/prototype/runtime/value_object_generator.rb index b4bef63e3..cc4338734 100644 --- a/lib/rbs/prototype/runtime/value_object_generator.rb +++ b/lib/rbs/prototype/runtime/value_object_generator.rb @@ -85,6 +85,12 @@ def build_member_accessors(ast_members_class) end class StructGenerator < ValueObjectBase + def self.generatable?(target) + target < Struct && target.respond_to?(:members) + end + + private + CAN_CALL_KEYWORD_INIT_P = Struct.new(:tmp).respond_to?(:keyword_init?) def build_super_class @@ -104,31 +110,21 @@ def build_s_new [:new, :[]].map do |name| new_overloads = [] - if CAN_CALL_KEYWORD_INIT_P ? (@target_class.keyword_init? == false || @target_class.keyword_init? == nil) : true - new_overloads << AST::Members::MethodDefinition::Overload.new( - annotations: [], - method_type: MethodType.new( - type: Types::Function.empty(Types::Bases::Instance.new(location: nil)).update( - optional_positionals: @target_class.members.map { |m| Types::Function::Param.new(name: m, type: untyped) }, - ), - type_params: [], - block: nil, - location: nil, - ) - ) - end - if CAN_CALL_KEYWORD_INIT_P ? (@target_class.keyword_init? == true || @target_class.keyword_init? == nil) : true - new_overloads << AST::Members::MethodDefinition::Overload.new( - annotations: [], - method_type: MethodType.new( - type: Types::Function.empty(Types::Bases::Instance.new(location: nil)).update( - optional_keywords: @target_class.members.to_h { |m| [m, Types::Function::Param.new(name: nil, type: untyped)] }, - ), - type_params: [], - block: nil, - location: nil, - ) - ) + if CAN_CALL_KEYWORD_INIT_P && @target_class.respond_to?(:keyword_init?) + case @target_class.keyword_init? + when false + new_overloads << build_overload_for_positional_arguments + when true + new_overloads << build_overload_for_keyword_arguments + when nil + new_overloads << build_overload_for_positional_arguments + new_overloads << build_overload_for_keyword_arguments + else + raise + end + else + new_overloads << build_overload_for_positional_arguments + new_overloads << build_overload_for_keyword_arguments end AST::Members::MethodDefinition.new( @@ -144,9 +140,38 @@ def build_s_new end end + def build_overload_for_positional_arguments + AST::Members::MethodDefinition::Overload.new( + annotations: [], + method_type: MethodType.new( + type: Types::Function.empty(Types::Bases::Instance.new(location: nil)).update( + optional_positionals: @target_class.members.map { |m| Types::Function::Param.new(name: m, type: untyped) }, + ), + type_params: [], + block: nil, + location: nil, + ) + ) + end + + def build_overload_for_keyword_arguments + AST::Members::MethodDefinition::Overload.new( + annotations: [], + method_type: MethodType.new( + type: Types::Function.empty(Types::Bases::Instance.new(location: nil)).update( + optional_keywords: @target_class.members.to_h { |m| [m, Types::Function::Param.new(name: nil, type: untyped)] }, + ), + type_params: [], + block: nil, + location: nil, + ) + ) + end + # def self.keyword_init?: () -> bool? def build_s_keyword_init_p return [] unless CAN_CALL_KEYWORD_INIT_P + return [] unless @target_class.respond_to?(:keyword_init?) return_type = @target_class.keyword_init?.nil? \ ? Types::Bases::Nil.new(location: nil) @@ -179,6 +204,12 @@ def build_s_keyword_init_p end class DataGenerator < ValueObjectBase + def self.generatable?(target) + RUBY_VERSION >= '3.2' && target < Data && target.respond_to?(:members) + end + + private + def build_super_class AST::Declarations::Class::Super.new(name: TypeName("::Data"), args: [], location: nil) end diff --git a/test/rbs/runtime_prototype_test.rb b/test/rbs/runtime_prototype_test.rb index a1a5211d7..2a24b35f0 100644 --- a/test/rbs/runtime_prototype_test.rb +++ b/test/rbs/runtime_prototype_test.rb @@ -700,6 +700,8 @@ class StructInheritWithNil < Struct.new(:foo, :bar, keyword_init: nil) end StructKeywordInitTrue = Struct.new(:foo, :bar, keyword_init: true) StructKeywordInitFalse = Struct.new(:foo, :bar, keyword_init: false) + class StructDirectInherited < Struct + end def test_struct SignatureManager.new do |manager| @@ -846,6 +848,16 @@ def members: () -> [ :foo, :bar ] end RBS end + + p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::StructDirectInherited"], env: env, merge: false) + assert_write p.decls, <<~RBS + module RBS + class RuntimePrototypeTest < ::Test::Unit::TestCase + class StructDirectInherited < ::Struct[untyped] + end + end + end + RBS end end end @@ -854,6 +866,8 @@ def members: () -> [ :foo, :bar ] class DataInherit < Data.define(:foo, :bar) end DataConst = Data.define(:foo, :bar) + class DataDirectInherit < Data + end def test_data SignatureManager.new do |manager| @@ -903,6 +917,16 @@ def members: () -> [ :foo, :bar ] end end RBS + + p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::DataDirectInherit"], env: env, merge: false) + assert_write p.decls, <<~RBS + module RBS + class RuntimePrototypeTest < ::Test::Unit::TestCase + class DataDirectInherit < ::Data + end + end + end + RBS end end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 08b3a9e23..4849b07e8 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -146,6 +146,10 @@ module Enumerable[A] class Hash[unchecked out K, unchecked out V] include Enumerable[[K, V]] end + +class Struct[Elem] + include Enumerable[Elem?] +end SIG def add_file(path, content) From 4a5d06ce4dc2f2dbe49953e8b04e627daf334804 Mon Sep 17 00:00:00 2001 From: ksss Date: Wed, 1 Nov 2023 11:27:02 +0900 Subject: [PATCH 2/2] Fix typing --- .../runtime/value_object_generator.rb | 16 +++-- sig/prototype/runtime.rbs | 70 ++++++++++++++++--- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/lib/rbs/prototype/runtime/value_object_generator.rb b/lib/rbs/prototype/runtime/value_object_generator.rb index cc4338734..975f772a7 100644 --- a/lib/rbs/prototype/runtime/value_object_generator.rb +++ b/lib/rbs/prototype/runtime/value_object_generator.rb @@ -86,7 +86,11 @@ def build_member_accessors(ast_members_class) class StructGenerator < ValueObjectBase def self.generatable?(target) - target < Struct && target.respond_to?(:members) + return false unless target < Struct + # Avoid direct inherited class like `class Option < Struct` + return false unless target.respond_to?(:members) + + true end private @@ -110,7 +114,7 @@ def build_s_new [:new, :[]].map do |name| new_overloads = [] - if CAN_CALL_KEYWORD_INIT_P && @target_class.respond_to?(:keyword_init?) + if CAN_CALL_KEYWORD_INIT_P case @target_class.keyword_init? when false new_overloads << build_overload_for_positional_arguments @@ -171,7 +175,6 @@ def build_overload_for_keyword_arguments # def self.keyword_init?: () -> bool? def build_s_keyword_init_p return [] unless CAN_CALL_KEYWORD_INIT_P - return [] unless @target_class.respond_to?(:keyword_init?) return_type = @target_class.keyword_init?.nil? \ ? Types::Bases::Nil.new(location: nil) @@ -205,7 +208,12 @@ def build_s_keyword_init_p class DataGenerator < ValueObjectBase def self.generatable?(target) - RUBY_VERSION >= '3.2' && target < Data && target.respond_to?(:members) + return false unless RUBY_VERSION >= '3.2' + return false unless target < Data + # Avoid direct inherited class like `class Option < Data` + return false unless target.respond_to?(:members) + + true end private diff --git a/sig/prototype/runtime.rbs b/sig/prototype/runtime.rbs index 82d73f5e8..12b0476a5 100644 --- a/sig/prototype/runtime.rbs +++ b/sig/prototype/runtime.rbs @@ -1,6 +1,67 @@ module RBS module Prototype class Runtime + module Helpers + private + + def const_name: (Module const) -> String? + + def const_name!: (Module const) -> String + + # Returns the exact name & not compactly declared name + def only_name: (Module mod) -> String + + def to_type_name: (String name, ?full_name: bool) -> TypeName + + def untyped: () -> untyped + end + + class ValueObjectBase + include Helpers + + def build_decl: () -> AST::Declarations::Class + + private + + def build_member_accessors: (untyped ast_members_class) -> untyped + + def build_s_members: () -> Array[AST::Members::MethodDefinition] + + def initialize: (Class target_class) -> void + end + + class StructGenerator < ValueObjectBase + def self.generatable?: (Class target) -> bool + + private + + def add_decl_members: (AST::Declarations::Class decl) -> void + + def build_overload_for_keyword_arguments: () -> AST::Members::MethodDefinition::Overload + + def build_overload_for_positional_arguments: () -> AST::Members::MethodDefinition::Overload + + def build_s_keyword_init_p: () -> Array[AST::Members::MethodDefinition] + + def build_s_new: () -> Array[AST::Members::MethodDefinition] + + def build_super_class: () -> AST::Declarations::Class::Super + + CAN_CALL_KEYWORD_INIT_P: bool + end + + class DataGenerator < ValueObjectBase + def self.generatable?: (Class target) -> bool + + private + + def add_decl_members: (AST::Declarations::Class decl) -> void + + def build_s_new: () -> Array[AST::Members::MethodDefinition] + + def build_super_class: () -> AST::Declarations::Class::Super + end + class Todo @builder: DefinitionBuilder @@ -58,8 +119,6 @@ module RBS def decls: () -> Array[AST::Declarations::t] - def to_type_name: (String name, ?full_name: bool) -> TypeName - def each_mixined_module: (TypeName type_name, Module mod) { (TypeName, TypeName, mixin_class) -> void } -> void def each_mixined_module_one: (TypeName type_name, Module mod) { (TypeName, TypeName, bool) -> void } -> void @@ -87,13 +146,6 @@ module RBS # This generates/finds declarations in nested form & returns the last array of declarations def ensure_outer_module_declarations: (Module mod) -> Array[AST::Declarations::Class::member] - # Returns the exact name & not compactly declared name - def only_name: (Module mod) -> String - - def const_name: (Module const) -> String? - - def const_name!: (Module const) -> String - def object_class: (untyped) -> untyped def type_args: (TypeName type_name) -> Array[Types::t]