Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop generating new singleton methods for abstract classes #1524

Merged
merged 2 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions lib/tapioca/gem/listeners/methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def on_scope(event)

compile_method(node, symbol, constant, initialize_method_for(constant))
compile_directly_owned_methods(node, symbol, constant)
compile_directly_owned_methods(node, symbol, singleton_class_of(constant))
compile_directly_owned_methods(node, symbol, singleton_class_of(constant), attached_class: constant)
end

sig do
Expand All @@ -29,14 +29,22 @@ def on_scope(event)
module_name: String,
mod: Module,
for_visibility: T::Array[Symbol],
attached_class: T.nilable(Module),
).void
end
def compile_directly_owned_methods(tree, module_name, mod, for_visibility = [:public, :protected, :private])
def compile_directly_owned_methods(
tree,
module_name,
mod,
for_visibility = [:public, :protected, :private],
attached_class: nil
)
method_names_by_visibility(mod)
.delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
.each do |visibility, method_list|
method_list.sort!.map do |name|
next if name == :initialize
next if method_new_in_abstract_class?(attached_class, name)

vis = case visibility
when :protected
Expand Down Expand Up @@ -180,6 +188,19 @@ def struct_method?(constant, method_name)
.include?(method_name.gsub(/=$/, "").to_sym)
end

sig do
params(
attached_class: T.nilable(Module),
method_name: Symbol,
).returns(T.nilable(T::Boolean))
end
def method_new_in_abstract_class?(attached_class, method_name)
attached_class &&
method_name == :new &&
!!abstract_type_of(attached_class) &&
Class === attached_class.singleton_class
end

sig { params(constant: Module).returns(T.nilable(UnboundMethod)) }
def initialize_method_for(constant)
constant.instance_method(:initialize)
Expand Down
7 changes: 3 additions & 4 deletions lib/tapioca/gem/listeners/sorbet_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ def on_scope(event)
constant = event.constant
node = event.node

abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type) ||
T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
abstract_type = abstract_type_of(constant)

node << RBI::Helper.new(abstract_type.to_s) if abstract_type
node << RBI::Helper.new("final") if T::Private::Final.final_module?(constant)
node << RBI::Helper.new("sealed") if T::Private::Sealed.sealed_module?(constant)
node << RBI::Helper.new("final") if final_module?(constant)
node << RBI::Helper.new("sealed") if sealed_module?(constant)
end

sig { override.params(event: NodeAdded).returns(T::Boolean) }
Expand Down
16 changes: 16 additions & 0 deletions lib/tapioca/runtime/reflection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ def file_candidates_for(constant)
end.to_set
end

sig { params(constant: Module).returns(T.untyped) }
def abstract_type_of(constant)
T::Private::Abstract::Data.get(constant, :abstract_type) ||
T::Private::Abstract::Data.get(singleton_class_of(constant), :abstract_type)
end

sig { params(constant: Module).returns(T::Boolean) }
def final_module?(constant)
T::Private::Final.final_module?(constant)
end

sig { params(constant: Module).returns(T::Boolean) }
def sealed_module?(constant)
T::Private::Sealed.sealed_module?(constant)
end

private

sig { params(constant: Module).returns(T::Array[UnboundMethod]) }
Expand Down
87 changes: 50 additions & 37 deletions spec/tapioca/gem/pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,6 @@ class Bar

sig { abstract.void }
def foo; end

class << self
<% if ruby_version(">= 3.1") %>
def new(*args, **_arg1, &blk); end
<% else %>
def new(*args, &blk); end
<% end %>
end
end
RBI

Expand All @@ -185,12 +177,6 @@ class Bar
class << self
sig { abstract.void }
def foo; end

<% if ruby_version(">= 3.1") %>
def new(*args, **_arg1, &blk); end
<% else %>
def new(*args, &blk); end
<% end %>
end
end
RBI
Expand Down Expand Up @@ -221,12 +207,6 @@ class Bar
class << self
sig { abstract.void }
def foo; end

<% if ruby_version(">= 3.1") %>
def new(*args, **_arg1, &blk); end
<% else %>
def new(*args, &blk); end
<% end %>
end
end
RBI
Expand Down Expand Up @@ -263,6 +243,56 @@ def foo; end
assert_equal(output, compile)
end

it "correctly compiles new method definitions in classes and modules" do
add_ruby_file("abstract.rb", <<~RUBY)
module Foo
extend T::Helpers

abstract!
end

class Bar
extend T::Helpers

abstract!
end

class Baz
def self.new(a, b)
end
end

module Quux
def self.new(a, b)
end
end
RUBY

output = template(<<~RBI)
class Bar
abstract!
end

class Baz
class << self
def new(a, b); end
end
end

module Foo
abstract!
end

module Quux
class << self
def new(a, b); end
end
end
RBI

assert_equal(output, compile)
end

it "compiles complex type aliases" do
add_ruby_file("bar.rb", <<~RUBY)
module Bar
Expand Down Expand Up @@ -2888,14 +2918,6 @@ class Baz

sig { abstract.void }
def do_it; end

class << self
<% if ruby_version(">= 3.1") %>
def new(*args, **_arg1, &blk); end
<% else %>
def new(*args, &blk); end
<% end %>
end
end

class Buzz
Expand Down Expand Up @@ -4108,15 +4130,6 @@ def foo; end
# source://the-dep//lib/foo.rb#11
class Baz
abstract!

class << self
# source://sorbet-runtime/#{sorbet_runtime_version}/lib/types/private/abstract/declare.rb#37
<% if ruby_version(">= 3.1") %>
def new(*args, **_arg1, &blk); end
<% else %>
def new(*args, &blk); end
<% end %>
end
end

# source://the-dep//lib/foo.rb#1
Expand Down