diff --git a/lib/rbs/definition.rb b/lib/rbs/definition.rb index 7a474a083..fdee50d05 100644 --- a/lib/rbs/definition.rb +++ b/lib/rbs/definition.rb @@ -315,6 +315,13 @@ def interface_type? self_type.is_a?(Types::Interface) end + def delegates? + !ancestors.params.empty? && + ancestors.ancestors.any? do |ancestor| + ancestor.name.name == :Delegator + end + end + def type_params type_params_decl.each.map(&:name) end diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 0eaf5e6b3..0e271a561 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -163,6 +163,18 @@ def build_instance(type_name, no_self_types: false) end end + if super_class&.name&.name == :Delegator && + args != super_class.args + delegate_class = super_class.args[0] + + build_instance(delegate_class.name).yield_self do |defn| + merge_definition(src: defn, + dest: definition, + subst: Substitution.build(defn.type_params, delegate_class.args), + keep_super: true) + end + end + if self_types = one_ancestors.self_types unless no_self_types self_types.each do |ans| diff --git a/stdlib/delegate/0/core.rbs b/stdlib/delegate/0/core.rbs new file mode 100644 index 000000000..087cbe7f2 --- /dev/null +++ b/stdlib/delegate/0/core.rbs @@ -0,0 +1,131 @@ +# This library provides three different ways to delegate method calls to an +# object. The easiest to use is SimpleDelegator. Pass an object to the +# constructor and all methods supported by the object will be delegated. This +# object can be changed later. +# +# Going a step further, the top level DelegateClass method allows you to easily +# setup delegation through class inheritance. This is considerably more +# flexible and thus probably the most common use for this library. +# +# Finally, if you need full control over the delegation scheme, you can inherit +# from the abstract class Delegator and customize as needed. (If you find +# yourself needing this control, have a look at Forwardable which is also in the +# standard library. It may suit your needs better.) +# +# SimpleDelegator's implementation serves as a nice example of the use of +# Delegator: +# +# require 'delegate' +# +# class SimpleDelegator < Delegator +# def __getobj__ +# @delegate_sd_obj # return object we are delegating to, required +# end +# +# def __setobj__(obj) +# @delegate_sd_obj = obj # change delegation object, +# # a feature we're providing +# end +# end +# +# ## Notes +# +# Be advised, RDoc will not detect delegated methods. +class Delegator[A] < BasicObject + def self.const_missing: (Symbol | String n) -> untyped + + def self.delegating_block: (String | Symbol mid) -> untyped + + def self.public_api: () -> Array[Symbol] + + public + + # Delegates ! to the _*getobj*_ + # + def !: () -> bool + + # Returns true if two objects are not considered of equal value. + # + def !=: (untyped obj) -> bool + + # Returns true if two objects are considered of equal value. + # + def ==: (untyped obj) -> bool + + # This method must be overridden by subclasses and should return the object + # method calls are being delegated to. + # + def __getobj__: () -> A + + # This method must be overridden by subclasses and change the object delegate to + # *obj*. + # + def __setobj__: (A obj) -> A + + # Returns true if two objects are considered of equal value. + # + def eql?: (untyped obj) -> bool + + # :method: freeze Freeze both the object returned by _*getobj*_ and self. + # + def freeze: () -> void + + # Serialization support for the object returned by _*getobj*_. + # + def marshal_dump: () -> Array[untyped] + + # Reinitializes delegation from a serialized object. + # + def marshal_load: (untyped data) -> A + + def method_missing: (untyped m, *untyped args) { (*untyped) -> untyped } -> untyped + + # Returns the methods available to this delegate object as the union of this + # object's and _*getobj*_ methods. + # + def methods: (?boolish all) -> Array[Symbol] + + # Returns the methods available to this delegate object as the union of this + # object's and _*getobj*_ protected methods. + # + def protected_methods: (?boolish all) -> Array[Symbol] + + # Returns the methods available to this delegate object as the union of this + # object's and _*getobj*_ public methods. + # + def public_methods: (?boolish all) -> Array[Symbol] + + private + + # Pass in the *obj* to delegate method calls to. All methods supported by *obj* + # will be delegated to. + # + def initialize: (A obj) -> void + + def initialize_clone: (A obj, ?freeze: bool) -> void + + def initialize_dup: (A obj) -> void + + # Checks for a method provided by this the delegate object by forwarding the + # call through _*getobj*_. + # + def respond_to_missing?: (Symbol m, bool include_private) -> bool + + # Handle BasicObject instances + # + def target_respond_to?: (A target, Symbol m, bool include_private) -> bool + + VERSION: String +end + +class SimpleDelegator[A] < Delegator[A] + @delegate_sd_obj: A + + def __getobj__: () -> A + + def __setobj__: (A obj) -> A +end + +module Kernel + def self?.DelegateClass: [A] (Class superclass) ?{ () -> singleton(Delegator) } -> singleton(Delegator) +end \ No newline at end of file diff --git a/test/stdlib/Delegate_test.rb b/test/stdlib/Delegate_test.rb new file mode 100644 index 000000000..ad972410f --- /dev/null +++ b/test/stdlib/Delegate_test.rb @@ -0,0 +1,69 @@ +require_relative "test_helper" +require "delegate" + +class DelegatorInstanceTest < Test::Unit::TestCase + include TypeAssertions + + library "delegate" + testing "::Delegator[::String]" + + def test_get_obj + assert_send_type "() -> String", + string_delegator, :__getobj__ + end + + def test_delegate_method + assert_send_type "(String) -> String", + string_delegator, :<<, " world" + end + + def test_Delegate_class + assert_send_type '(singleton(Integer)) -> singleton(Delegator)', + Kernel, :DelegateClass, Integer + end + + private + + def string_delegator + Class.new(Delegator) do + def initialize + super("hello") + end + + def __getobj__ + @obj + end + + def __setobj__(obj) + @obj = obj + end + end.new + end +end + +class SimpleDelegatorInstanceTest < Test::Unit::TestCase + include TypeAssertions + + library "delegate" + testing "::SimpleDelegator[::String]" + + def test_get_obj + assert_send_type "() -> String", + string_delegator, :__getobj__ + end + + def test_delegate_method + assert_send_type "(String) -> String", + string_delegator, :<<, " world" + end + + private + + def string_delegator + Class.new(SimpleDelegator) do + def initialize + super("hello") + end + end.new + end +end diff --git a/test/stdlib/test_helper.rb b/test/stdlib/test_helper.rb index 4ff6fc4fd..98cccee33 100644 --- a/test/stdlib/test_helper.rb +++ b/test/stdlib/test_helper.rb @@ -327,6 +327,16 @@ def class_class def method_types(method) type, definition = target + if definition.delegates? + delegate_class = type.args[0] + builder.build_instance(delegate_class.name).yield_self do |defn| + builder.merge_definition(src: defn, + dest: definition, + subst: RBS::Substitution.build(defn.type_params, delegate_class.args), + keep_super: true) + end + end + case when definition.instance_type? subst = RBS::Substitution.build(definition.type_params, type.args)