tapioca 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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