tapioca 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +491 -73
  4. data/lib/tapioca/cli.rb +40 -3
  5. data/lib/tapioca/commands/annotations.rb +154 -0
  6. data/lib/tapioca/commands/dsl.rb +20 -1
  7. data/lib/tapioca/commands/gem.rb +17 -57
  8. data/lib/tapioca/commands/init.rb +1 -0
  9. data/lib/tapioca/commands/todo.rb +4 -2
  10. data/lib/tapioca/commands.rb +1 -0
  11. data/lib/tapioca/dsl/compiler.rb +2 -2
  12. data/lib/tapioca/dsl/compilers/aasm.rb +1 -1
  13. data/lib/tapioca/dsl/compilers/action_controller_helpers.rb +1 -1
  14. data/lib/tapioca/dsl/compilers/action_mailer.rb +1 -1
  15. data/lib/tapioca/dsl/compilers/active_job.rb +1 -1
  16. data/lib/tapioca/dsl/compilers/active_model_attributes.rb +1 -1
  17. data/lib/tapioca/dsl/compilers/active_model_secure_password.rb +1 -1
  18. data/lib/tapioca/dsl/compilers/active_record_associations.rb +1 -1
  19. data/lib/tapioca/dsl/compilers/active_record_columns.rb +1 -1
  20. data/lib/tapioca/dsl/compilers/active_record_enum.rb +1 -1
  21. data/lib/tapioca/dsl/compilers/active_record_fixtures.rb +3 -1
  22. data/lib/tapioca/dsl/compilers/active_record_relations.rb +8 -8
  23. data/lib/tapioca/dsl/compilers/active_record_scope.rb +1 -1
  24. data/lib/tapioca/dsl/compilers/active_record_typed_store.rb +1 -1
  25. data/lib/tapioca/dsl/compilers/active_resource.rb +1 -1
  26. data/lib/tapioca/dsl/compilers/active_storage.rb +6 -2
  27. data/lib/tapioca/dsl/compilers/active_support_concern.rb +1 -1
  28. data/lib/tapioca/dsl/compilers/active_support_current_attributes.rb +1 -1
  29. data/lib/tapioca/dsl/compilers/config.rb +2 -2
  30. data/lib/tapioca/dsl/compilers/frozen_record.rb +1 -1
  31. data/lib/tapioca/dsl/compilers/identity_cache.rb +1 -1
  32. data/lib/tapioca/dsl/compilers/mixed_in_class_attributes.rb +1 -1
  33. data/lib/tapioca/dsl/compilers/protobuf.rb +27 -3
  34. data/lib/tapioca/dsl/compilers/rails_generators.rb +1 -1
  35. data/lib/tapioca/dsl/compilers/sidekiq_worker.rb +1 -1
  36. data/lib/tapioca/dsl/compilers/smart_properties.rb +1 -1
  37. data/lib/tapioca/dsl/compilers/state_machines.rb +1 -1
  38. data/lib/tapioca/dsl/compilers/url_helpers.rb +5 -2
  39. data/lib/tapioca/dsl/helpers/param_helper.rb +4 -1
  40. data/lib/tapioca/dsl/pipeline.rb +32 -1
  41. data/lib/tapioca/dsl.rb +6 -0
  42. data/lib/tapioca/executor.rb +4 -46
  43. data/lib/tapioca/gem/listeners/methods.rb +26 -1
  44. data/lib/tapioca/gem/listeners/sorbet_props.rb +1 -1
  45. data/lib/tapioca/gem/listeners/sorbet_required_ancestors.rb +1 -0
  46. data/lib/tapioca/gem/listeners/sorbet_signatures.rb +1 -1
  47. data/lib/tapioca/gem/pipeline.rb +5 -1
  48. data/lib/tapioca/gemfile.rb +50 -3
  49. data/lib/tapioca/helpers/config_helper.rb +13 -0
  50. data/lib/tapioca/helpers/rbi_helper.rb +114 -7
  51. data/lib/tapioca/helpers/shims_helper.rb +36 -8
  52. data/lib/tapioca/helpers/signatures_helper.rb +17 -0
  53. data/lib/tapioca/helpers/sorbet_helper.rb +5 -11
  54. data/lib/tapioca/helpers/test/content.rb +1 -0
  55. data/lib/tapioca/helpers/test/dsl_compiler.rb +1 -0
  56. data/lib/tapioca/helpers/test/template.rb +1 -0
  57. data/lib/tapioca/helpers/type_variable_helper.rb +43 -0
  58. data/lib/tapioca/internal.rb +4 -1
  59. data/lib/tapioca/rbi_ext/model.rb +14 -2
  60. data/lib/tapioca/repo_index.rb +41 -0
  61. data/lib/tapioca/runtime/generic_type_registry.rb +4 -2
  62. data/lib/tapioca/runtime/loader.rb +3 -0
  63. data/lib/tapioca/runtime/reflection.rb +17 -13
  64. data/lib/tapioca/sorbet_ext/generic_name_patch.rb +38 -21
  65. data/lib/tapioca/static/symbol_table_parser.rb +2 -0
  66. data/lib/tapioca/version.rb +1 -1
  67. data/lib/tapioca.rb +5 -0
  68. metadata +26 -21
@@ -4,14 +4,121 @@
4
4
  module Tapioca
5
5
  module RBIHelper
6
6
  extend T::Sig
7
+ extend T::Helpers
7
8
 
8
- sig { params(sig_string: String).returns(String) }
9
- def sanitize_signature_types(sig_string)
10
- sig_string
11
- .gsub(".returns(<VOID>)", ".void")
12
- .gsub("<VOID>", "void")
13
- .gsub("<NOT-TYPED>", "T.untyped")
14
- .gsub(".params()", "")
9
+ requires_ancestor { Thor::Shell }
10
+ requires_ancestor { SorbetHelper }
11
+
12
+ sig do
13
+ params(
14
+ command: String,
15
+ gem_dir: String,
16
+ dsl_dir: String,
17
+ auto_strictness: T::Boolean,
18
+ gems: T::Array[Gemfile::GemSpec],
19
+ compilers: T::Enumerable[Class]
20
+ ).void
21
+ end
22
+ def validate_rbi_files(command:, gem_dir:, dsl_dir:, auto_strictness:, gems: [], compilers: [])
23
+ error_url_base = Spoom::Sorbet::Errors::DEFAULT_ERROR_URL_BASE
24
+
25
+ say("Checking generated RBI files... ")
26
+ res = sorbet(
27
+ "--no-config",
28
+ "--error-url-base=#{error_url_base}",
29
+ "--stop-after namer",
30
+ dsl_dir,
31
+ gem_dir
32
+ )
33
+ say(" Done", :green)
34
+
35
+ errors = Spoom::Sorbet::Errors::Parser.parse_string(res.err)
36
+
37
+ if errors.empty?
38
+ say(" No errors found\n\n", [:green, :bold])
39
+ return
40
+ end
41
+
42
+ parse_errors = errors.select { |error| error.code < 4000 }
43
+
44
+ if parse_errors.any?
45
+ say_error(<<~ERR, :red)
46
+
47
+ ##### INTERNAL ERROR #####
48
+
49
+ There are parse errors in the generated RBI files.
50
+
51
+ This seems related to a bug in Tapioca.
52
+ Please open an issue at https://github.com/Shopify/tapioca/issues/new with the following information:
53
+
54
+ Tapioca v#{Tapioca::VERSION}
55
+
56
+ Command:
57
+ #{command}
58
+
59
+ ERR
60
+
61
+ say_error(<<~ERR, :red) if gems.any?
62
+ Gems:
63
+ #{gems.map { |gem| " #{gem.name} (#{gem.version})" }.join("\n")}
64
+
65
+ ERR
66
+
67
+ say_error(<<~ERR, :red) if compilers.any?
68
+ Compilers:
69
+ #{compilers.map { |compiler| " #{compiler.name}" }.join("\n")}
70
+
71
+ ERR
72
+
73
+ say_error(<<~ERR, :red)
74
+ Errors:
75
+ #{parse_errors.map { |error| " #{error}" }.join("\n")}
76
+
77
+ ##########################
78
+
79
+ ERR
80
+ end
81
+
82
+ if auto_strictness
83
+ redef_errors = errors.select { |error| error.code == 4010 }
84
+ update_gem_rbis_strictnesses(redef_errors, gem_dir)
85
+ end
86
+
87
+ Kernel.exit(1) if parse_errors.any?
88
+ end
89
+
90
+ private
91
+
92
+ sig { params(errors: T::Array[Spoom::Sorbet::Errors::Error], gem_dir: String).void }
93
+ def update_gem_rbis_strictnesses(errors, gem_dir)
94
+ files = []
95
+
96
+ errors.each do |error|
97
+ # Collect the file with error
98
+ files << error.file
99
+ error.more.each do |line|
100
+ # Also collect the conflicting definition file paths
101
+ next unless line.include?("Previous definition")
102
+
103
+ files << line.split(":").first&.strip
104
+ end
105
+ end
106
+
107
+ files
108
+ .uniq
109
+ .sort
110
+ .select { |file| file.start_with?(gem_dir) }
111
+ .each do |file|
112
+ Spoom::Sorbet::Sigils.change_sigil_in_file(file, "false")
113
+ say("\n Changed strictness of #{file} to `typed: false` (conflicting with DSL files)", [:yellow, :bold])
114
+ end
115
+
116
+ say("\n")
117
+ end
118
+
119
+ sig { params(path: String).returns(String) }
120
+ def gem_name_from_rbi_path(path)
121
+ T.must(File.basename(path, ".rbi").split("@").first)
15
122
  end
16
123
  end
17
124
  end
@@ -8,20 +8,25 @@ module Tapioca
8
8
 
9
9
  requires_ancestor { Thor::Shell }
10
10
 
11
+ SORBET_PAYLOAD_URL = "https://github.com/sorbet/sorbet/tree/master/rbi"
12
+
13
+ sig { params(index: RBI::Index, dir: String).void }
14
+ def index_payload(index, dir)
15
+ return unless Dir.exist?(dir)
16
+
17
+ say("Loading Sorbet payload... ")
18
+ files = Dir.glob("#{dir}/**/*.rbi").sort
19
+ parse_and_index_files(index, files)
20
+ say(" Done", :green)
21
+ end
22
+
11
23
  sig { params(index: RBI::Index, kind: String, dir: String).void }
12
24
  def index_rbis(index, kind, dir)
13
25
  return unless Dir.exist?(dir) && !Dir.empty?(dir)
14
26
 
15
27
  say("Loading #{kind} RBIs from #{dir}... ")
16
28
  files = Dir.glob("#{dir}/**/*.rbi").sort
17
-
18
- trees = files.map do |file|
19
- RBI::Parser.parse_file(file)
20
- rescue RBI::ParseError => e
21
- say_error("\nWarning: #{e} (#{e.location})", :yellow)
22
- end.compact
23
-
24
- index.visit_all(trees)
29
+ parse_and_index_files(index, files)
25
30
  say(" Done", :green)
26
31
  end
27
32
 
@@ -32,14 +37,37 @@ module Tapioca
32
37
  index.keys.each do |key|
33
38
  nodes = index[key]
34
39
  next unless shims_have_duplicates?(nodes, shim_rbi_dir)
40
+
35
41
  duplicates[key] = nodes
36
42
  end
37
43
  say(" Done", :green)
38
44
  duplicates
39
45
  end
40
46
 
47
+ sig { params(loc: RBI::Loc, path_prefix: T.nilable(String)).returns(String) }
48
+ def location_to_payload_url(loc, path_prefix:)
49
+ return loc.to_s unless path_prefix
50
+
51
+ url = loc.file || ""
52
+ return loc.to_s unless url.start_with?(path_prefix)
53
+
54
+ url = url.sub(path_prefix, SORBET_PAYLOAD_URL)
55
+ url = "#{url}#L#{loc.begin_line}"
56
+ url
57
+ end
58
+
41
59
  private
42
60
 
61
+ sig { params(index: RBI::Index, files: T::Array[String]).void }
62
+ def parse_and_index_files(index, files)
63
+ trees = files.map do |file|
64
+ RBI::Parser.parse_file(file)
65
+ rescue RBI::ParseError => e
66
+ say_error("\nWarning: #{e} (#{e.location})", :yellow)
67
+ end.compact
68
+ index.visit_all(trees)
69
+ end
70
+
43
71
  sig { params(nodes: T::Array[RBI::Node], shim_rbi_dir: String).returns(T::Boolean) }
44
72
  def shims_have_duplicates?(nodes, shim_rbi_dir)
45
73
  return false if nodes.size == 1
@@ -0,0 +1,17 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module SignaturesHelper
6
+ extend T::Sig
7
+
8
+ sig { params(sig_string: String).returns(String) }
9
+ def sanitize_signature_types(sig_string)
10
+ sig_string
11
+ .gsub(".returns(<VOID>)", ".void")
12
+ .gsub("<VOID>", "void")
13
+ .gsub("<NOT-TYPED>", "T.untyped")
14
+ .gsub(".params()", "")
15
+ end
16
+ end
17
+ end
@@ -21,20 +21,14 @@ module Tapioca
21
21
  SORBET_EXE_PATH_ENV_VAR = "TAPIOCA_SORBET_EXE"
22
22
 
23
23
  FEATURE_REQUIREMENTS = T.let({
24
- # First tag that includes https://github.com/sorbet/sorbet/pull/4706
25
- to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"),
24
+ to_ary_nil_support: ::Gem::Requirement.new(">= 0.5.9220"), # https://github.com/sorbet/sorbet/pull/4706
25
+ print_payload_sources: ::Gem::Requirement.new(">= 0.5.9818"), # https://github.com/sorbet/sorbet/pull/5504
26
+ type_variable_block_syntax: ::Gem::Requirement.new(">= 0.5.9892"), # https://github.com/sorbet/sorbet/pull/5639
26
27
  }.freeze, T::Hash[Symbol, ::Gem::Requirement])
27
28
 
28
- class CmdResult < T::Struct
29
- const :out, String
30
- const :err, String
31
- const :status, T::Boolean
32
- end
33
-
34
- sig { params(sorbet_args: String).returns(CmdResult) }
29
+ sig { params(sorbet_args: String).returns(Spoom::ExecResult) }
35
30
  def sorbet(*sorbet_args)
36
- out, err, status = Open3.capture3([sorbet_path, *sorbet_args].join(" "))
37
- CmdResult.new(out: out, err: err, status: status.success? || false)
31
+ Spoom::Sorbet.srb(sorbet_args.join(" "), sorbet_bin: sorbet_path, capture_err: true)
38
32
  end
39
33
 
40
34
  sig { returns(String) }
@@ -41,6 +41,7 @@ module Tapioca
41
41
  def add_content_file(name, content)
42
42
  file_name = tmp_path("lib/#{name}")
43
43
  raise ArgumentError, "a file named '#{name}' was already added; cannot overwrite." if File.exist?(file_name)
44
+
44
45
  FileUtils.mkdir_p(File.dirname(file_name))
45
46
  File.write(file_name, content)
46
47
  file_name
@@ -46,6 +46,7 @@ module Tapioca
46
46
  sig { returns(CompilerContext) }
47
47
  def context
48
48
  raise "Please call `use_dsl_compiler` before" unless @context
49
+
49
50
  @context
50
51
  end
51
52
 
@@ -36,6 +36,7 @@ module Tapioca
36
36
  def indented(str, indent)
37
37
  str.lines.map! do |line|
38
38
  next line if line.chomp.empty?
39
+
39
40
  (" " * indent) + line
40
41
  end.join
41
42
  end
@@ -0,0 +1,43 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module TypeVariableHelper
6
+ extend T::Sig
7
+ extend SorbetHelper
8
+
9
+ sig do
10
+ params(
11
+ type: String,
12
+ variance: Symbol,
13
+ fixed: T.nilable(String),
14
+ upper: T.nilable(String),
15
+ lower: T.nilable(String)
16
+ ).returns(String)
17
+ end
18
+ def self.serialize_type_variable(type, variance, fixed, upper, lower)
19
+ variance = nil if variance == :invariant
20
+
21
+ bounds = []
22
+ bounds << "fixed: #{fixed}" if fixed
23
+ bounds << "lower: #{lower}" if lower
24
+ bounds << "upper: #{upper}" if upper
25
+
26
+ parameters = []
27
+ block = []
28
+
29
+ parameters << ":#{variance}" if variance
30
+
31
+ if sorbet_supports?(:type_variable_block_syntax)
32
+ block = bounds
33
+ else
34
+ parameters.concat(bounds)
35
+ end
36
+
37
+ serialized = type.dup
38
+ serialized << "(#{parameters.join(", ")})" unless parameters.empty?
39
+ serialized << " { { #{block.join(", ")} } }" unless block.empty?
40
+ serialized
41
+ end
42
+ end
43
+ end
@@ -6,14 +6,17 @@ require "tapioca/runtime/reflection"
6
6
  require "tapioca/runtime/trackers"
7
7
  require "tapioca/runtime/dynamic_mixin_compiler"
8
8
  require "tapioca/runtime/loader"
9
+ require "tapioca/helpers/sorbet_helper"
10
+ require "tapioca/helpers/type_variable_helper"
9
11
  require "tapioca/sorbet_ext/generic_name_patch"
10
12
  require "tapioca/sorbet_ext/fixed_hash_patch"
11
13
  require "tapioca/runtime/generic_type_registry"
12
14
  require "tapioca/helpers/cli_helper"
13
15
  require "tapioca/helpers/config_helper"
16
+ require "tapioca/helpers/signatures_helper"
14
17
  require "tapioca/helpers/rbi_helper"
15
18
  require "tapioca/helpers/shims_helper"
16
- require "tapioca/helpers/sorbet_helper"
19
+ require "tapioca/repo_index"
17
20
  require "tapioca/commands"
18
21
  require "tapioca/cli"
19
22
  require "tapioca/gemfile"
@@ -61,8 +61,18 @@ module RBI
61
61
  create_node(RBI::MixesInClassMethods.new(name))
62
62
  end
63
63
 
64
- sig { params(name: String, value: String).void }
65
- def create_type_member(name, value: "type_member")
64
+ sig do
65
+ params(
66
+ name: String,
67
+ type: String,
68
+ variance: Symbol,
69
+ fixed: T.nilable(String),
70
+ upper: T.nilable(String),
71
+ lower: T.nilable(String)
72
+ ).void
73
+ end
74
+ def create_type_variable(name, type:, variance: :invariant, fixed: nil, upper: nil, lower: nil)
75
+ value = Tapioca::TypeVariableHelper.serialize_type_variable(type, variance, fixed, upper, lower)
66
76
  create_node(RBI::TypeMember.new(name, value))
67
77
  end
68
78
 
@@ -98,6 +108,7 @@ module RBI
98
108
  sig { params(name: String).returns(T::Boolean) }
99
109
  def valid_method_name?(name)
100
110
  return true if SPECIAL_METHOD_NAMES.include?(name)
111
+
101
112
  !!name.match(/^[a-zA-Z_][[:word:]]*[?!=]?$/)
102
113
  end
103
114
 
@@ -110,6 +121,7 @@ module RBI
110
121
  def create_node(node)
111
122
  cached = nodes_cache[node.to_s]
112
123
  return cached if cached
124
+
113
125
  nodes_cache[node.to_s] = node
114
126
  self << node
115
127
  node
@@ -0,0 +1,41 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ class RepoIndex
6
+ extend T::Sig
7
+ extend T::Generic
8
+
9
+ sig { params(json: String).returns(RepoIndex) }
10
+ def self.from_json(json)
11
+ RepoIndex.from_hash(JSON.parse(json))
12
+ end
13
+
14
+ sig { params(hash: T::Hash[String, T::Hash[T.untyped, T.untyped]]).returns(RepoIndex) }
15
+ def self.from_hash(hash)
16
+ hash.each_with_object(RepoIndex.new) do |(name, _), index|
17
+ index << name
18
+ end
19
+ end
20
+
21
+ sig { void }
22
+ def initialize
23
+ @entries = T.let(Set.new, T::Set[String])
24
+ end
25
+
26
+ sig { params(gem_name: String).void }
27
+ def <<(gem_name)
28
+ @entries.add(gem_name)
29
+ end
30
+
31
+ sig { returns(T::Enumerable[String]) }
32
+ def gems
33
+ @entries.sort
34
+ end
35
+
36
+ sig { params(gem_name: String).returns(T::Boolean) }
37
+ def has_gem?(gem_name)
38
+ @entries.include?(gem_name)
39
+ end
40
+ end
41
+ end
@@ -111,8 +111,10 @@ module Tapioca
111
111
  constant.clone
112
112
  end
113
113
 
114
- # Let's set the `name` method to return the proper generic name
115
- generic_type.define_singleton_method(:name) { name }
114
+ # Let's set the `name` and `to_s` methods to return the proper generic name
115
+ name_proc = -> { name }
116
+ generic_type.define_singleton_method(:name, name_proc)
117
+ generic_type.define_singleton_method(:to_s, name_proc)
116
118
 
117
119
  # We need to define a `<=` method on the cloned constant, so that Sorbet
118
120
  # can do covariance/contravariance checks on the type variables.
@@ -41,6 +41,7 @@ module Tapioca
41
41
  sig { params(file: T.nilable(String)).void }
42
42
  def require_helper(file)
43
43
  return unless file
44
+
44
45
  file = File.absolute_path(file)
45
46
  return unless File.exist?(file)
46
47
 
@@ -51,6 +52,8 @@ module Tapioca
51
52
  def rails_engines
52
53
  return [] unless Object.const_defined?("Rails::Engine")
53
54
 
55
+ safe_require("active_support/core_ext/class/subclasses")
56
+
54
57
  # We can use `Class#descendants` here, since we know Rails is loaded
55
58
  Object.const_get("Rails::Engine").descendants.reject(&:abstract_railtie?)
56
59
  end
@@ -35,58 +35,58 @@ module Tapioca
35
35
 
36
36
  sig { params(object: BasicObject).returns(Class).checked(:never) }
37
37
  def class_of(object)
38
- CLASS_METHOD.bind(object).call
38
+ CLASS_METHOD.bind_call(object)
39
39
  end
40
40
 
41
41
  sig { params(constant: Module).returns(T::Array[Symbol]) }
42
42
  def constants_of(constant)
43
- CONSTANTS_METHOD.bind(constant).call(false)
43
+ CONSTANTS_METHOD.bind_call(constant, false)
44
44
  end
45
45
 
46
46
  sig { params(constant: Module).returns(T.nilable(String)) }
47
47
  def name_of(constant)
48
- name = NAME_METHOD.bind(constant).call
48
+ name = NAME_METHOD.bind_call(constant)
49
49
  name&.start_with?("#<") ? nil : name
50
50
  end
51
51
 
52
52
  sig { params(constant: Module).returns(Class) }
53
53
  def singleton_class_of(constant)
54
- SINGLETON_CLASS_METHOD.bind(constant).call
54
+ SINGLETON_CLASS_METHOD.bind_call(constant)
55
55
  end
56
56
 
57
57
  sig { params(constant: Module).returns(T::Array[Module]) }
58
58
  def ancestors_of(constant)
59
- ANCESTORS_METHOD.bind(constant).call
59
+ ANCESTORS_METHOD.bind_call(constant)
60
60
  end
61
61
 
62
62
  sig { params(constant: Class).returns(T.nilable(Class)) }
63
63
  def superclass_of(constant)
64
- SUPERCLASS_METHOD.bind(constant).call
64
+ SUPERCLASS_METHOD.bind_call(constant)
65
65
  end
66
66
 
67
67
  sig { params(object: BasicObject).returns(Integer).checked(:never) }
68
68
  def object_id_of(object)
69
- OBJECT_ID_METHOD.bind(object).call
69
+ OBJECT_ID_METHOD.bind_call(object)
70
70
  end
71
71
 
72
72
  sig { params(object: BasicObject, other: BasicObject).returns(T::Boolean).checked(:never) }
73
73
  def are_equal?(object, other)
74
- EQUAL_METHOD.bind(object).call(other)
74
+ EQUAL_METHOD.bind_call(object, other)
75
75
  end
76
76
 
77
77
  sig { params(constant: Module).returns(T::Array[Symbol]) }
78
78
  def public_instance_methods_of(constant)
79
- PUBLIC_INSTANCE_METHODS_METHOD.bind(constant).call
79
+ PUBLIC_INSTANCE_METHODS_METHOD.bind_call(constant)
80
80
  end
81
81
 
82
82
  sig { params(constant: Module).returns(T::Array[Symbol]) }
83
83
  def protected_instance_methods_of(constant)
84
- PROTECTED_INSTANCE_METHODS_METHOD.bind(constant).call
84
+ PROTECTED_INSTANCE_METHODS_METHOD.bind_call(constant)
85
85
  end
86
86
 
87
87
  sig { params(constant: Module).returns(T::Array[Symbol]) }
88
88
  def private_instance_methods_of(constant)
89
- PRIVATE_INSTANCE_METHODS_METHOD.bind(constant).call
89
+ PRIVATE_INSTANCE_METHODS_METHOD.bind_call(constant)
90
90
  end
91
91
 
92
92
  sig { params(constant: Module).returns(T::Array[Module]) }
@@ -124,7 +124,7 @@ module Tapioca
124
124
 
125
125
  sig { params(constant: Module, method: Symbol).returns(Method) }
126
126
  def method_of(constant, method)
127
- METHOD_METHOD.bind(constant).call(method)
127
+ METHOD_METHOD.bind_call(constant, method)
128
128
  end
129
129
 
130
130
  # Returns an array with all classes that are < than the supplied class.
@@ -140,7 +140,11 @@ module Tapioca
140
140
  #
141
141
  # class D < C; end
142
142
  # descendants_of(C) # => [B, A, D]
143
- sig { type_parameters(:U).params(klass: T.type_parameter(:U)).returns(T::Array[T.type_parameter(:U)]) }
143
+ sig do
144
+ type_parameters(:U)
145
+ .params(klass: T.all(Class, T.type_parameter(:U)))
146
+ .returns(T::Array[T.type_parameter(:U)])
147
+ end
144
148
  def descendants_of(klass)
145
149
  result = ObjectSpace.each_object(klass.singleton_class).reject do |k|
146
150
  T.cast(k, Module).singleton_class? || T.unsafe(k) == klass
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "tapioca/sorbet_ext/name_patch"
5
+ require "tapioca/helpers/sorbet_helper"
5
6
 
6
7
  module T
7
8
  module Generic
@@ -20,31 +21,47 @@ module T
20
21
  Tapioca::Runtime::GenericTypeRegistry.register_type(constant, types)
21
22
  end
22
23
 
23
- def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
24
+ def type_member(variance = :invariant, fixed: nil, lower: nil, upper: nil, &blk)
24
25
  # `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
25
26
  # We use that when registering the type member and then later return it from this method.
27
+ hash = if blk
28
+ blk.call
29
+ else
30
+ {
31
+ fixed: fixed,
32
+ lower: lower,
33
+ upper: upper,
34
+ }
35
+ end
36
+
26
37
  Tapioca::TypeVariableModule.new(
27
38
  T.cast(self, Module),
28
39
  Tapioca::TypeVariableModule::Type::Member,
29
40
  variance,
30
- fixed,
31
- lower,
32
- upper
41
+ **hash,
33
42
  ).tap do |type_variable|
34
43
  Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
35
44
  end
36
45
  end
37
46
 
38
- def type_template(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
47
+ def type_template(variance = :invariant, fixed: nil, lower: nil, upper: nil, &blk)
39
48
  # `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
40
49
  # We use that when registering the type template and then later return it from this method.
50
+ hash = if blk
51
+ blk.call
52
+ else
53
+ {
54
+ fixed: fixed,
55
+ lower: lower,
56
+ upper: upper,
57
+ }
58
+ end
59
+
41
60
  Tapioca::TypeVariableModule.new(
42
61
  T.cast(self, Module),
43
62
  Tapioca::TypeVariableModule::Type::Template,
44
63
  variance,
45
- fixed,
46
- lower,
47
- upper
64
+ **hash,
48
65
  ).tap do |type_variable|
49
66
  Tapioca::Runtime::GenericTypeRegistry.register_type_variable(self, type_variable)
50
67
  end
@@ -122,7 +139,7 @@ module Tapioca
122
139
  sig do
123
140
  params(context: Module, type: Type, variance: Symbol, fixed: T.untyped, lower: T.untyped, upper: T.untyped).void
124
141
  end
125
- def initialize(context, type, variance, fixed, lower, upper) # rubocop:disable Metrics/ParameterLists
142
+ def initialize(context, type, variance, fixed: nil, lower: nil, upper: nil)
126
143
  @context = context
127
144
  @type = type
128
145
  @variance = variance
@@ -137,7 +154,7 @@ module Tapioca
137
154
  constant_name = super
138
155
 
139
156
  # This is a hack to work around modules under anonymous modules not having
140
- # names in 2.6 and 2.7: https://bugs.ruby-lang.org/issues/14895
157
+ # names in 2.7: https://bugs.ruby-lang.org/issues/14895
141
158
  #
142
159
  # This happens when a type variable is declared under `class << self`, for
143
160
  # example.
@@ -153,17 +170,17 @@ module Tapioca
153
170
 
154
171
  sig { returns(String) }
155
172
  def serialize
156
- parts = []
157
- parts << ":#{@variance}" unless @variance == :invariant
158
- parts << "fixed: #{@fixed}" if @fixed
159
- parts << "lower: #{@lower}" unless @lower == T.untyped
160
- parts << "upper: #{@upper}" unless @upper == BasicObject
161
-
162
- parameters = parts.join(", ")
163
-
164
- serialized = @type.serialize.dup
165
- serialized << "(#{parameters})" unless parameters.empty?
166
- serialized
173
+ fixed = @fixed.to_s if @fixed
174
+ upper = @upper.to_s if @upper
175
+ lower = @lower.to_s if @lower
176
+
177
+ TypeVariableHelper.serialize_type_variable(
178
+ @type.serialize,
179
+ @variance,
180
+ fixed,
181
+ upper,
182
+ lower
183
+ )
167
184
  end
168
185
 
169
186
  sig { returns(Tapioca::TypeVariable) }
@@ -16,6 +16,8 @@ module Tapioca
16
16
  parser = SymbolTableParser.new
17
17
  parser.parse_object(obj)
18
18
  parser.symbols
19
+ rescue JSON::ParserError
20
+ Set.new
19
21
  end
20
22
 
21
23
  sig { returns(T::Set[String]) }
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.7.0"
5
+ VERSION = "0.8.0"
6
6
  end