Skip to content

Commit

Permalink
Merge pull request #1590 from ksss/fix-runtime-struct
Browse files Browse the repository at this point in the history
[prototype runtime] Fix error if direct inherited
  • Loading branch information
soutaro authored Nov 1, 2023
2 parents f5373b3 + 4a5d06c commit ff0f38c
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 36 deletions.
4 changes: 2 additions & 2 deletions lib/rbs/prototype/runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
89 changes: 64 additions & 25 deletions lib/rbs/prototype/runtime/value_object_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ def build_member_accessors(ast_members_class)
end

class StructGenerator < ValueObjectBase
def self.generatable?(target)
return false unless target < Struct
# Avoid direct inherited class like `class Option < Struct`
return false unless target.respond_to?(:members)

true
end

private

CAN_CALL_KEYWORD_INIT_P = Struct.new(:tmp).respond_to?(:keyword_init?)

def build_super_class
Expand All @@ -104,31 +114,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
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(
Expand All @@ -144,6 +144,34 @@ 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
Expand Down Expand Up @@ -179,6 +207,17 @@ def build_s_keyword_init_p
end

class DataGenerator < ValueObjectBase
def self.generatable?(target)
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

def build_super_class
AST::Declarations::Class::Super.new(name: TypeName("::Data"), args: [], location: nil)
end
Expand Down
70 changes: 61 additions & 9 deletions sig/prototype/runtime.rbs
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down
24 changes: 24 additions & 0 deletions test/rbs/runtime_prototype_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down Expand Up @@ -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
Expand All @@ -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|
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit ff0f38c

Please sign in to comment.