Skip to content

Commit c2ff234

Browse files
committed
Extract into its own file
1 parent e43c4a7 commit c2ff234

3 files changed

Lines changed: 60 additions & 56 deletions

File tree

lib/type_toolkit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require_relative "type_toolkit/ext/method"
77
require_relative "type_toolkit/ext/module"
88
require_relative "type_toolkit/ext/nil_assertions"
9+
require_relative "type_toolkit/ext/sorbet-runtime/nil_assertions"
910

1011
module TypeToolkit
1112
end

lib/type_toolkit/ext/nil_assertions.rb

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -25,62 +25,6 @@ def not_nil!
2525
end
2626
end
2727

28-
# FIXME: this is load-order dependent, and will break if it's loaded after the real
29-
# `T::Private::Types::Void::VOID` module, which is frozen:
30-
# https://github.com/sorbet/sorbet/blob/f0cb505/gems/sorbet-runtime/lib/types/private/types/void.rb#L17-L19
31-
if Bundler.locked_gems.specs.any? { |s| s.name == "sorbet-runtime" }
32-
module TypeToolkit
33-
module SorbetRuntimeCompatibility
34-
module VoidPatch
35-
# An override of `not_nil!` intended to reduce the discrepancy between test and production environments
36-
# when Sorbet Runtime is used in tests but not production.
37-
#
38-
# When Sorbet Runtime is active `void`-returning methods have their return value replaced with the
39-
# `T::Private::Types::Void::VOID` module. If Sorbet Runtime is on in tests but not production,
40-
# this introduces a dangerous difference in behaviour for methods that return `nil`:
41-
#
42-
# * In test code, it'll be replaced with `T::Private::Types::Void::VOID`.
43-
# If `not_nil!` is called on it (and this override didn't exist), it'll just return `self`.
44-
#
45-
# * In production code, that `nil` value will left-as-is, and calling `not_nil!` on it will raise an error.
46-
#: -> bot
47-
def not_nil!
48-
# Do not rely on this message content! Its content is subject to change.
49-
raise TypeToolkit::UnexpectedNilError, "Called `not_nil!` on a void value (T::Private::Types::Void::VOID)"
50-
end
51-
end
52-
end
53-
end
54-
55-
module T
56-
module Types
57-
class Base; end
58-
end
59-
60-
module Private
61-
module Types
62-
class Void < T::Types::Base
63-
if defined?(::T::Private::Types::Void::VOID)
64-
# sorbet-runtime was already loaded, and the `VOID` module is already frozen, so we can't change it.
65-
# Instead, replace it with our own copy.
66-
::T::Private::Types::Void.const_set(:VOID, ::Module.new do
67-
extend ::TypeToolkit::SorbetRuntimeCompatibility::VoidPatch
68-
69-
freeze # Freeze it like the original
70-
end)
71-
else
72-
# sorbet-runtime hasn't been loaded yet, so we're defining the `VOID` module for the first time.
73-
module VOID
74-
extend ::TypeToolkit::SorbetRuntimeCompatibility::VoidPatch
75-
# Leave it unfrozen so sorbet-runtime can freeze it later when it defines it.
76-
end
77-
end
78-
end
79-
end
80-
end
81-
end
82-
end
83-
8428
module TypeToolkit
8529
# An error raised when calling `#not_nil!` on a `nil` value.
8630
#
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# typed: ignore
2+
# frozen_string_literal: true
3+
4+
# This file is `typed: ignore` so we don't have to implement the abstract methods from `T::Types::Base`,
5+
# which `sorbet-runtime` will implement eventually anyway.
6+
7+
require "bundler"
8+
return unless Bundler.locked_gems.specs.any? { |s| s.name == "sorbet-runtime" }
9+
10+
module TypeToolkit
11+
module SorbetRuntimeCompatibility
12+
module VoidPatch
13+
# An override of `not_nil!` intended to reduce the discrepancy between test and production environments
14+
# when Sorbet Runtime is used in tests but not production.
15+
#
16+
# When Sorbet Runtime is active `void`-returning methods have their return value replaced with the
17+
# `T::Private::Types::Void::VOID` module. If Sorbet Runtime is on in tests but not production,
18+
# this introduces a dangerous difference in behaviour for methods that return `nil`:
19+
#
20+
# * In test code, it'll be replaced with `T::Private::Types::Void::VOID`.
21+
# If `not_nil!` is called on it (and this override didn't exist), it'll just return `self`.
22+
#
23+
# * In production code, that `nil` value will left-as-is, and calling `not_nil!` on it will raise an error.
24+
#: -> bot
25+
def not_nil!
26+
# Do not rely on this message content! Its content is subject to change.
27+
raise TypeToolkit::UnexpectedNilError, "Called `not_nil!` on a void value (T::Private::Types::Void::VOID)"
28+
end
29+
end
30+
end
31+
end
32+
33+
module T
34+
module Types
35+
class Base; end
36+
end
37+
38+
module Private
39+
module Types
40+
class Void < T::Types::Base
41+
if defined?(::T::Private::Types::Void::VOID)
42+
# sorbet-runtime was already loaded, and the `VOID` module is already frozen, so we can't change it.
43+
# Instead, replace it with our own copy.
44+
::T::Private::Types::Void.const_set(:VOID, ::Module.new do
45+
extend ::TypeToolkit::SorbetRuntimeCompatibility::VoidPatch
46+
47+
freeze # Freeze it like the original
48+
end)
49+
else
50+
# sorbet-runtime hasn't been loaded yet, so we're defining the `VOID` module for the first time.
51+
module VOID
52+
extend ::TypeToolkit::SorbetRuntimeCompatibility::VoidPatch
53+
# Leave it unfrozen so sorbet-runtime can freeze it later when it defines it.
54+
end
55+
end
56+
end
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)