Skip to content
Open
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
1 change: 1 addition & 0 deletions lib/tapioca/internal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require "tapioca/sorbet_ext/name_patch"
require "tapioca/sorbet_ext/generic_name_patch"
require "tapioca/sorbet_ext/proc_bind_patch"
require "tapioca/sorbet_ext/void_patch"
require "tapioca/runtime/generic_type_registry"

# The rewriter needs to be loaded very early so RBS comments within Tapioca itself are rewritten
Expand Down
29 changes: 29 additions & 0 deletions lib/tapioca/sorbet_ext/void_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# typed: true
# frozen_string_literal: true

# If Signature has `effective_return_type`, then `return_type` always returns the correct type.
# Ref: https://github.com/sorbet/sorbet/pull/10121
return if T::Private::Methods::Signature.method_defined?(:effective_return_type)

module T
module Private
module Methods
module DeclBuilderPatch
def void
super.tap do
@_real_returns_is_void = true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw, I think we could move this strategy into sorbet-runtime itself, and avoid having to patch it here. I think that adding one more true/false instance variable to the Signature data structure would not cause an increase in memory usage because of the way that Ruby buckets objects with N instance variables.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, something like this I assume: sorbet/sorbet#10121

I think I'll still merge this and then, later on, make the patching conditional on the existence of Signature#real_return_type method.

end
end

def finalize!
super.tap do
#: self as untyped
decl.returns = T::Private::Types::Void::Private::INSTANCE if @_real_returns_is_void
end
end
end

DeclBuilder.prepend(DeclBuilderPatch)
end
end
end
10 changes: 9 additions & 1 deletion sorbet/rbi/shims/sorbet.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ module T::Private
def self.sealed_module?(mod); end
end

class Types::NotTyped < T::Types::Base; end
module Types
class NotTyped < T::Types::Base; end

module Void::Private
INSTANCE = T.let(T.unsafe(nil), T::Private::Types::Void)
end
end

module Methods
ARG_NOT_PROVIDED = T.let(T.unsafe(nil), Object)
Expand Down Expand Up @@ -53,6 +59,8 @@ module T::Private
def finalized; end
def finalized=(finalized); end
end

class Signature; end
end

module DeclState
Expand Down
26 changes: 26 additions & 0 deletions spec/tapioca/gem/pipeline_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4627,6 +4627,32 @@ def foo; end
assert_equal(output, compiled)
end

it "compiles methods with .void.checked(:tests) properly" do
add_ruby_file("bar.rb", <<~RUBY)
class Bar
extend T::Sig

sig { params(x: Integer).void.checked(:tests) }
def initialize(x); end

sig { void.checked(:tests) }
def foo; end
end
RUBY

output = template(<<~RBI)
class Bar
sig { params(x: ::Integer).void }
def initialize(x); end

sig { void }
def foo; end
end
RBI

assert_equal(output, compile)
end

it "compiles constants with nil values" do
add_ruby_file("foo.rb", <<~RUBY)
class Foo
Expand Down
Loading