tapioca 0.4.17 → 0.4.18
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.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/exe/tapioca +17 -2
- data/lib/tapioca.rb +1 -27
- data/lib/tapioca/cli.rb +1 -108
- data/lib/tapioca/cli/main.rb +136 -0
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +4 -4
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +1 -1
- data/lib/tapioca/compilers/dsl/base.rb +1 -1
- data/lib/tapioca/compilers/dsl/url_helpers.rb +3 -3
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +122 -39
- data/lib/tapioca/config.rb +1 -1
- data/lib/tapioca/config_builder.rb +7 -12
- data/lib/tapioca/generator.rb +87 -33
- data/lib/tapioca/generic_type_registry.rb +170 -0
- data/lib/tapioca/internal.rb +21 -0
- data/lib/tapioca/sorbet_ext/generic_name_patch.rb +66 -0
- data/lib/tapioca/sorbet_ext/name_patch.rb +16 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3709ab7f54589bdf97715ffc2cc7c3a98ad703a6e318a264db70429078cdf26
|
4
|
+
data.tar.gz: 3e38366a54f9b3286de28a2b6d557762b2cf564848b8184a76aadc75504a41fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ef5e31a85a71607340ad54e2fff59b1a5bd0d3f81b1481bd4830ca5e73e5ea9ed1504d38808d8bf49a9686276685167a35103769fdb30d66b61362c4d9c072b
|
7
|
+
data.tar.gz: e0b1daab32f024cc5f3f1fc019b5c1dcd040ce0657e59b81d736702e3cb380a9e7265397f108f4787163e50d9b31e793b960cd33f875cf45a061cee17fa1857e
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
> :warning: **Note**: This software is currently under active development. The API and interface should be considered unstable until a v1.0.0 release.
|
2
|
+
|
1
3
|
# Tapioca
|
2
4
|
|
3
5
|

|
@@ -14,7 +16,7 @@ For gems that have a normal default `require` and load all of their constants th
|
|
14
16
|
|
15
17
|
For example, suppose you are using the class `BetterHtml::Parser` exported from the `better_html` gem. Just doing a `require "better_html"` (which is the default require) does not load that type:
|
16
18
|
|
17
|
-
```
|
19
|
+
```shell
|
18
20
|
$ bundle exec pry
|
19
21
|
[1] pry(main)> require 'better_html'
|
20
22
|
=> true
|
@@ -110,7 +112,7 @@ This will generate DSL RBIs for specified constants (or for all handled constant
|
|
110
112
|
- `--prerequire [file]`: A file to be required before `Bundler.require` is called.
|
111
113
|
- `--postrequire [file]`: A file to be required after `Bundler.require` is called.
|
112
114
|
- `--out [directory]`: The output directory for generated RBI files, default to `sorbet/rbi/gems`.
|
113
|
-
- `--generate-command [command]`: The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
|
115
|
+
- `--generate-command [command]`: **[DEPRECATED]** The command to run to regenerate RBI files (used in header comment of the RBI files), defaults to the current command.
|
114
116
|
- `--typed-overrides [gem:level]`: Overrides typed sigils for generated gem RBIs for gem `gem` to level `level` (`level` can be one of `ignore`, `false`, `true`, `strict`, or `strong`, see [the Sorbet docs](https://sorbet.org/docs/static#file-level-granularity-strictness-levels) for more details).
|
115
117
|
|
116
118
|
## Contributing
|
data/exe/tapioca
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
require 'sorbet-runtime'
|
5
5
|
|
6
|
-
|
6
|
+
begin
|
7
|
+
T::Configuration.default_checked_level = :never
|
8
|
+
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
9
|
+
T::Configuration.inline_type_error_handler = ->(*) {}
|
10
|
+
# Suppresses errors caused by incorrect parameter ordering
|
11
|
+
T::Configuration.sig_validation_error_handler = ->(*) {}
|
12
|
+
rescue
|
13
|
+
# Need this rescue so that if another gem has
|
14
|
+
# already set the checked level by the time we
|
15
|
+
# get to it, we don't fail outright.
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
require_relative "../lib/tapioca/internal"
|
20
|
+
|
21
|
+
Tapioca::Cli::Main.start(ARGV)
|
data/lib/tapioca.rb
CHANGED
@@ -17,31 +17,5 @@ module Tapioca
|
|
17
17
|
class Error < StandardError; end
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
T::Configuration.default_checked_level = :never
|
22
|
-
# Suppresses errors caused by T.cast, T.let, T.must, etc.
|
23
|
-
T::Configuration.inline_type_error_handler = ->(*) {}
|
24
|
-
# Suppresses errors caused by incorrect parameter ordering
|
25
|
-
T::Configuration.sig_validation_error_handler = ->(*) {}
|
26
|
-
rescue
|
27
|
-
# Need this rescue so that if another gem has
|
28
|
-
# already set the checked level by the time we
|
29
|
-
# get to it, we don't fail outright.
|
30
|
-
nil
|
31
|
-
end
|
32
|
-
|
33
|
-
require "tapioca/loader"
|
34
|
-
require "tapioca/constant_locator"
|
35
|
-
require "tapioca/config"
|
36
|
-
require "tapioca/config_builder"
|
37
|
-
require "tapioca/generator"
|
38
|
-
require "tapioca/cli"
|
39
|
-
require "tapioca/gemfile"
|
40
|
-
require "tapioca/compilers/sorbet"
|
41
|
-
require "tapioca/compilers/requires_compiler"
|
42
|
-
require "tapioca/compilers/symbol_table_compiler"
|
43
|
-
require "tapioca/compilers/symbol_table/symbol_generator"
|
44
|
-
require "tapioca/compilers/symbol_table/symbol_loader"
|
45
|
-
require "tapioca/compilers/todos_compiler"
|
46
|
-
require "tapioca/compilers/dsl_compiler"
|
20
|
+
require "tapioca/compilers/dsl/base"
|
47
21
|
require "tapioca/version"
|
data/lib/tapioca/cli.rb
CHANGED
@@ -4,112 +4,5 @@
|
|
4
4
|
require 'thor'
|
5
5
|
|
6
6
|
module Tapioca
|
7
|
-
|
8
|
-
include(Thor::Actions)
|
9
|
-
|
10
|
-
class_option :prerequire,
|
11
|
-
aliases: ["--pre", "-b"],
|
12
|
-
banner: "file",
|
13
|
-
desc: "A file to be required before Bundler.require is called"
|
14
|
-
class_option :postrequire,
|
15
|
-
aliases: ["--post", "-a"],
|
16
|
-
banner: "file",
|
17
|
-
desc: "A file to be required after Bundler.require is called"
|
18
|
-
class_option :outdir,
|
19
|
-
aliases: ["--out", "-o"],
|
20
|
-
banner: "directory",
|
21
|
-
desc: "The output directory for generated RBI files"
|
22
|
-
class_option :generate_command,
|
23
|
-
aliases: ["--cmd", "-c"],
|
24
|
-
banner: "command",
|
25
|
-
desc: "The command to run to regenerate RBI files"
|
26
|
-
class_option :exclude,
|
27
|
-
aliases: ["-x"],
|
28
|
-
type: :array,
|
29
|
-
banner: "gem [gem ...]",
|
30
|
-
desc: "Excludes the given gem(s) from RBI generation"
|
31
|
-
class_option :typed_overrides,
|
32
|
-
aliases: ["--typed", "-t"],
|
33
|
-
type: :hash,
|
34
|
-
banner: "gem:level [gem:level ...]",
|
35
|
-
desc: "Overrides for typed sigils for generated gem RBIs"
|
36
|
-
|
37
|
-
map T.unsafe(%w[--version -v] => :__print_version)
|
38
|
-
|
39
|
-
desc "init", "initializes folder structure"
|
40
|
-
def init
|
41
|
-
create_file(Config::SORBET_CONFIG, skip: true) do
|
42
|
-
<<~CONTENT
|
43
|
-
--dir
|
44
|
-
.
|
45
|
-
CONTENT
|
46
|
-
end
|
47
|
-
create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
|
48
|
-
<<~CONTENT
|
49
|
-
# typed: false
|
50
|
-
# frozen_string_literal: true
|
51
|
-
|
52
|
-
# Add your extra requires here
|
53
|
-
CONTENT
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
desc "require", "generate the list of files to be required by tapioca"
|
58
|
-
def require
|
59
|
-
Tapioca.silence_warnings do
|
60
|
-
generator.build_requires
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
desc "todo", "generate the list of unresolved constants"
|
65
|
-
def todo
|
66
|
-
Tapioca.silence_warnings do
|
67
|
-
generator.build_todos
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
desc "dsl [constant...]", "generate RBIs for dynamic methods"
|
72
|
-
option :generators,
|
73
|
-
type: :array,
|
74
|
-
aliases: ["--gen", "-g"],
|
75
|
-
banner: "generator [generator ...]",
|
76
|
-
desc: "Only run supplied DSL generators"
|
77
|
-
def dsl(*constants)
|
78
|
-
Tapioca.silence_warnings do
|
79
|
-
generator.build_dsl(constants)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
desc "generate [gem...]", "generate RBIs from gems"
|
84
|
-
def generate(*gems)
|
85
|
-
Tapioca.silence_warnings do
|
86
|
-
generator.build_gem_rbis(gems)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
desc "sync", "sync RBIs to Gemfile"
|
91
|
-
def sync
|
92
|
-
Tapioca.silence_warnings do
|
93
|
-
generator.sync_rbis_with_gemfile
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
desc "--version, -v", "show version"
|
98
|
-
def __print_version
|
99
|
-
puts "Tapioca v#{Tapioca::VERSION}"
|
100
|
-
end
|
101
|
-
|
102
|
-
no_commands do
|
103
|
-
def self.exit_on_failure?
|
104
|
-
true
|
105
|
-
end
|
106
|
-
|
107
|
-
def generator
|
108
|
-
current_command = T.must(current_command_chain.first)
|
109
|
-
@generator ||= Generator.new(
|
110
|
-
ConfigBuilder.from_options(current_command, options)
|
111
|
-
)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
7
|
+
module Cli; end
|
115
8
|
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
module Cli
|
6
|
+
class Main < Thor
|
7
|
+
include(Thor::Actions)
|
8
|
+
|
9
|
+
class_option :prerequire,
|
10
|
+
aliases: ["--pre", "-b"],
|
11
|
+
banner: "file",
|
12
|
+
desc: "A file to be required before Bundler.require is called"
|
13
|
+
class_option :postrequire,
|
14
|
+
aliases: ["--post", "-a"],
|
15
|
+
banner: "file",
|
16
|
+
desc: "A file to be required after Bundler.require is called"
|
17
|
+
class_option :outdir,
|
18
|
+
aliases: ["--out", "-o"],
|
19
|
+
banner: "directory",
|
20
|
+
desc: "The output directory for generated RBI files"
|
21
|
+
class_option :generate_command,
|
22
|
+
aliases: ["--cmd", "-c"],
|
23
|
+
banner: "command",
|
24
|
+
desc: "The command to run to regenerate RBI files"
|
25
|
+
class_option :exclude,
|
26
|
+
aliases: ["-x"],
|
27
|
+
type: :array,
|
28
|
+
banner: "gem [gem ...]",
|
29
|
+
desc: "Excludes the given gem(s) from RBI generation"
|
30
|
+
class_option :typed_overrides,
|
31
|
+
aliases: ["--typed", "-t"],
|
32
|
+
type: :hash,
|
33
|
+
banner: "gem:level [gem:level ...]",
|
34
|
+
desc: "Overrides for typed sigils for generated gem RBIs"
|
35
|
+
|
36
|
+
map T.unsafe(%w[--version -v] => :__print_version)
|
37
|
+
|
38
|
+
desc "init", "initializes folder structure"
|
39
|
+
def init
|
40
|
+
create_config
|
41
|
+
create_post_require
|
42
|
+
generate_binstub
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "require", "generate the list of files to be required by tapioca"
|
46
|
+
def require
|
47
|
+
Tapioca.silence_warnings do
|
48
|
+
generator.build_requires
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "todo", "generate the list of unresolved constants"
|
53
|
+
def todo
|
54
|
+
Tapioca.silence_warnings do
|
55
|
+
generator.build_todos
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
desc "dsl [constant...]", "generate RBIs for dynamic methods"
|
60
|
+
option :generators,
|
61
|
+
type: :array,
|
62
|
+
aliases: ["--gen", "-g"],
|
63
|
+
banner: "generator [generator ...]",
|
64
|
+
desc: "Only run supplied DSL generators"
|
65
|
+
option :verify,
|
66
|
+
type: :boolean,
|
67
|
+
default: false,
|
68
|
+
desc: "Verifies RBIs are up-to-date"
|
69
|
+
def dsl(*constants)
|
70
|
+
Tapioca.silence_warnings do
|
71
|
+
generator.build_dsl(constants, should_verify: options[:verify])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
desc "generate [gem...]", "generate RBIs from gems"
|
76
|
+
def generate(*gems)
|
77
|
+
Tapioca.silence_warnings do
|
78
|
+
generator.build_gem_rbis(gems)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "sync", "sync RBIs to Gemfile"
|
83
|
+
def sync
|
84
|
+
Tapioca.silence_warnings do
|
85
|
+
generator.sync_rbis_with_gemfile
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "--version, -v", "show version"
|
90
|
+
def __print_version
|
91
|
+
puts "Tapioca v#{Tapioca::VERSION}"
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def create_config
|
97
|
+
create_file(Config::SORBET_CONFIG, skip: true) do
|
98
|
+
<<~CONTENT
|
99
|
+
--dir
|
100
|
+
.
|
101
|
+
CONTENT
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_post_require
|
106
|
+
create_file(Config::DEFAULT_POSTREQUIRE, skip: true) do
|
107
|
+
<<~CONTENT
|
108
|
+
# typed: false
|
109
|
+
# frozen_string_literal: true
|
110
|
+
|
111
|
+
# Add your extra requires here
|
112
|
+
CONTENT
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def generate_binstub
|
117
|
+
installer = Bundler::Installer.new(Bundler.root, Bundler.definition)
|
118
|
+
spec = Bundler.definition.specs.find { |s| s.name == "tapioca" }
|
119
|
+
installer.generate_bundler_executable_stubs(spec, { force: true })
|
120
|
+
end
|
121
|
+
|
122
|
+
no_commands do
|
123
|
+
def self.exit_on_failure?
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
def generator
|
128
|
+
current_command = T.must(current_command_chain.first)
|
129
|
+
@generator ||= Generator.new(
|
130
|
+
ConfigBuilder.from_options(current_command, options)
|
131
|
+
)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -371,7 +371,6 @@ module Tapioca
|
|
371
371
|
return unless signature
|
372
372
|
|
373
373
|
return_type = signature.return_type
|
374
|
-
return if T::Types::Simple === return_type && T::Generic === return_type.raw_type
|
375
374
|
return if return_type == T::Private::Types::Void || return_type == T::Private::Types::NotTyped
|
376
375
|
|
377
376
|
return_type.to_s
|
@@ -382,10 +381,11 @@ module Tapioca
|
|
382
381
|
signature = T::Private::Methods.signature_for_method(column_type.method(method))
|
383
382
|
return unless signature
|
384
383
|
|
385
|
-
|
386
|
-
|
384
|
+
# Arg types is an array [name, type] entries, so we desctructure the type of
|
385
|
+
# first argument to get the first argument type
|
386
|
+
_, first_argument_type = signature.arg_types.first
|
387
387
|
|
388
|
-
|
388
|
+
first_argument_type.to_s
|
389
389
|
end
|
390
390
|
|
391
391
|
sig { params(type: String).returns(String) }
|
@@ -123,7 +123,7 @@ module Tapioca
|
|
123
123
|
# Compile a Ruby method return type into a Parlour type
|
124
124
|
sig do
|
125
125
|
params(method_def: T.any(Method, UnboundMethod))
|
126
|
-
.returns(String)
|
126
|
+
.returns(T.nilable(String))
|
127
127
|
end
|
128
128
|
def compile_method_return_type_to_parlour(method_def)
|
129
129
|
signature = T::Private::Methods.signature_for_method(method_def)
|
@@ -89,7 +89,7 @@ module Tapioca
|
|
89
89
|
class UrlHelpers < Base
|
90
90
|
extend T::Sig
|
91
91
|
|
92
|
-
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant:
|
92
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
|
93
93
|
def decorate(root, constant)
|
94
94
|
case constant
|
95
95
|
when GeneratedPathHelpersModule.singleton_class, GeneratedUrlHelpersModule.singleton_class
|
@@ -127,7 +127,7 @@ module Tapioca
|
|
127
127
|
|
128
128
|
private
|
129
129
|
|
130
|
-
sig { params(root: Parlour::RbiGenerator::Namespace, constant:
|
130
|
+
sig { params(root: Parlour::RbiGenerator::Namespace, constant: Module).void }
|
131
131
|
def generate_module_for(root, constant)
|
132
132
|
root.create_module(T.must(constant.name)) do |mod|
|
133
133
|
mod.create_include("::ActionDispatch::Routing::UrlFor")
|
@@ -143,7 +143,7 @@ module Tapioca
|
|
143
143
|
end
|
144
144
|
end
|
145
145
|
|
146
|
-
sig { params(mod: Parlour::RbiGenerator::Namespace, constant:
|
146
|
+
sig { params(mod: Parlour::RbiGenerator::Namespace, constant: Module, helper_module: Module).void }
|
147
147
|
def create_mixins_for(mod, constant, helper_module)
|
148
148
|
include_helper = constant.ancestors.include?(helper_module) || NON_DISCOVERABLE_INCLUDERS.include?(constant)
|
149
149
|
extend_helper = constant.singleton_class.ancestors.include?(helper_module)
|
@@ -74,9 +74,15 @@ module Tapioca
|
|
74
74
|
compile(symbol, constant)
|
75
75
|
end
|
76
76
|
|
77
|
-
sig
|
78
|
-
|
79
|
-
|
77
|
+
sig do
|
78
|
+
params(
|
79
|
+
symbol: String,
|
80
|
+
inherit: T::Boolean,
|
81
|
+
namespace: Module
|
82
|
+
).returns(BasicObject).checked(:never)
|
83
|
+
end
|
84
|
+
def resolve_constant(symbol, inherit: false, namespace: Object)
|
85
|
+
namespace.const_get(symbol, inherit)
|
80
86
|
rescue NameError, LoadError, RuntimeError, ArgumentError, TypeError
|
81
87
|
nil
|
82
88
|
end
|
@@ -142,9 +148,11 @@ module Tapioca
|
|
142
148
|
def compile_object(name, value)
|
143
149
|
return if symbol_ignored?(name)
|
144
150
|
klass = class_of(value)
|
145
|
-
|
151
|
+
klass_name = name_of(klass)
|
152
|
+
|
153
|
+
return if klass_name&.start_with?("T::Types::", "T::Private::")
|
146
154
|
|
147
|
-
type_name = public_module?(klass) &&
|
155
|
+
type_name = public_module?(klass) && klass_name || "T.untyped"
|
148
156
|
indented("#{name} = T.let(T.unsafe(nil), #{type_name})")
|
149
157
|
end
|
150
158
|
|
@@ -181,16 +189,17 @@ module Tapioca
|
|
181
189
|
|
182
190
|
[
|
183
191
|
compile_module_helpers(constant),
|
192
|
+
compile_type_variables(constant),
|
184
193
|
compile_mixins(constant),
|
185
194
|
compile_mixes_in_class_methods(constant),
|
186
195
|
compile_props(constant),
|
187
196
|
compile_enums(constant),
|
188
197
|
methods,
|
189
|
-
].select { |b| b
|
198
|
+
].select { |b| b && !b.empty? }.join("\n\n")
|
190
199
|
end
|
191
200
|
end
|
192
201
|
|
193
|
-
sig { params(constant: Module).returns(String) }
|
202
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
194
203
|
def compile_module_helpers(constant)
|
195
204
|
abstract_type = T::Private::Abstract::Data.get(constant, :abstract_type)
|
196
205
|
|
@@ -199,12 +208,14 @@ module Tapioca
|
|
199
208
|
helpers << indented("final!") if T::Private::Final.final_module?(constant)
|
200
209
|
helpers << indented("sealed!") if T::Private::Sealed.sealed_module?(constant)
|
201
210
|
|
211
|
+
return if helpers.empty?
|
212
|
+
|
202
213
|
helpers.join("\n")
|
203
214
|
end
|
204
215
|
|
205
|
-
sig { params(constant: Module).returns(String) }
|
216
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
206
217
|
def compile_props(constant)
|
207
|
-
return
|
218
|
+
return unless T::Props::ClassMethods === constant
|
208
219
|
|
209
220
|
constant.props.map do |name, prop|
|
210
221
|
method = "prop"
|
@@ -219,11 +230,11 @@ module Tapioca
|
|
219
230
|
end.join("\n")
|
220
231
|
end
|
221
232
|
|
222
|
-
sig { params(constant: Module).returns(String) }
|
233
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
223
234
|
def compile_enums(constant)
|
224
|
-
return
|
235
|
+
return unless T::Enum > constant
|
225
236
|
|
226
|
-
enums = T.
|
237
|
+
enums = T.unsafe(constant).values.map do |enum_type|
|
227
238
|
enum_type.instance_variable_get(:@const_name).to_s
|
228
239
|
end
|
229
240
|
|
@@ -250,11 +261,71 @@ module Tapioca
|
|
250
261
|
compile(symbol, subconstant)
|
251
262
|
end.compact
|
252
263
|
|
253
|
-
return
|
264
|
+
return if output.empty?
|
254
265
|
|
255
266
|
"\n" + output.join("\n\n")
|
256
267
|
end
|
257
268
|
|
269
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
270
|
+
def compile_type_variables(constant)
|
271
|
+
type_variables = compile_type_variable_declarations(constant)
|
272
|
+
singleton_class_type_variables = compile_type_variable_declarations(singleton_class_of(constant))
|
273
|
+
|
274
|
+
return if !type_variables && !singleton_class_type_variables
|
275
|
+
|
276
|
+
type_variables += "\n" if type_variables
|
277
|
+
singleton_class_type_variables += "\n" if singleton_class_type_variables
|
278
|
+
|
279
|
+
[
|
280
|
+
type_variables,
|
281
|
+
singleton_class_type_variables,
|
282
|
+
].compact.join("\n").rstrip
|
283
|
+
end
|
284
|
+
|
285
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
286
|
+
def compile_type_variable_declarations(constant)
|
287
|
+
with_indentation_for_constant(constant) do
|
288
|
+
# Try to find the type variables defined on this constant, bail if we can't
|
289
|
+
type_variables = GenericTypeRegistry.lookup_type_variables(constant)
|
290
|
+
return unless type_variables
|
291
|
+
|
292
|
+
# Create a map of subconstants (via their object ids) to their names.
|
293
|
+
# We need this later when we want to lookup the name of the registered type
|
294
|
+
# variable via the value of the type variable constant.
|
295
|
+
subconstant_to_name_lookup = constants_of(constant).map do |constant_name|
|
296
|
+
[
|
297
|
+
object_id_of(resolve_constant(constant_name.to_s, namespace: constant)),
|
298
|
+
constant_name,
|
299
|
+
]
|
300
|
+
end.to_h
|
301
|
+
|
302
|
+
# Map each type variable to its string representation.
|
303
|
+
#
|
304
|
+
# Each entry of `type_variables` maps an object_id to a String,
|
305
|
+
# and the order they are inserted into the hash is the order they should be
|
306
|
+
# defined in the source code.
|
307
|
+
#
|
308
|
+
# By looping over these entries and then getting the actual constant name
|
309
|
+
# from the `subconstant_to_name_lookup` we defined above, gives us all the
|
310
|
+
# information we need to serialize type variable definitions.
|
311
|
+
type_variable_declarations = type_variables.map do |type_variable_id, serialized_type_variable|
|
312
|
+
constant_name = subconstant_to_name_lookup[type_variable_id]
|
313
|
+
# Here, we know that constant_value will be an instance of
|
314
|
+
# T::Types::CustomTypeVariable, which knows how to serialize
|
315
|
+
# itself to a type_member/type_template
|
316
|
+
indented("#{constant_name} = #{serialized_type_variable}")
|
317
|
+
end.compact
|
318
|
+
|
319
|
+
return if type_variable_declarations.empty?
|
320
|
+
|
321
|
+
[
|
322
|
+
indented("extend T::Generic"),
|
323
|
+
"",
|
324
|
+
*type_variable_declarations,
|
325
|
+
].compact.join("\n")
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
258
329
|
sig { params(constant: Class).returns(String) }
|
259
330
|
def compile_superclass(constant)
|
260
331
|
superclass = T.let(nil, T.nilable(Class)) # rubocop:disable Lint/UselessAssignment
|
@@ -431,29 +502,19 @@ module Tapioca
|
|
431
502
|
instance_methods = compile_directly_owned_methods(name, constant)
|
432
503
|
singleton_methods = compile_directly_owned_methods(name, singleton_class_of(constant))
|
433
504
|
|
434
|
-
return if symbol_ignored?(name) && instance_methods
|
505
|
+
return if symbol_ignored?(name) && !instance_methods && !singleton_methods
|
435
506
|
|
436
507
|
[
|
437
|
-
initialize_method
|
508
|
+
initialize_method,
|
438
509
|
instance_methods,
|
439
510
|
singleton_methods,
|
440
|
-
].select { |b| b.strip
|
511
|
+
].select { |b| b && !b.strip.empty? }.join("\n\n")
|
441
512
|
end
|
442
513
|
|
443
|
-
sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(String) }
|
514
|
+
sig { params(module_name: String, mod: Module, for_visibility: T::Array[Symbol]).returns(T.nilable(String)) }
|
444
515
|
def compile_directly_owned_methods(module_name, mod, for_visibility = [:public, :protected, :private])
|
445
|
-
|
446
|
-
|
447
|
-
postamble = nil
|
448
|
-
|
449
|
-
if mod.singleton_class?
|
450
|
-
indent_step = 1
|
451
|
-
preamble = indented("class << self")
|
452
|
-
postamble = indented("end")
|
453
|
-
end
|
454
|
-
|
455
|
-
methods = with_indentation(indent_step) do
|
456
|
-
method_names_by_visibility(mod)
|
516
|
+
with_indentation_for_constant(mod) do
|
517
|
+
methods = method_names_by_visibility(mod)
|
457
518
|
.delete_if { |visibility, _method_list| !for_visibility.include?(visibility) }
|
458
519
|
.flat_map do |visibility, method_list|
|
459
520
|
compiled = method_list.sort!.map do |name|
|
@@ -470,16 +531,11 @@ module Tapioca
|
|
470
531
|
compiled
|
471
532
|
end
|
472
533
|
.compact
|
473
|
-
.join("\n")
|
474
|
-
end
|
475
534
|
|
476
|
-
|
535
|
+
return if methods.empty?
|
477
536
|
|
478
|
-
|
479
|
-
|
480
|
-
methods,
|
481
|
-
postamble,
|
482
|
-
].compact.join("\n")
|
537
|
+
methods.join("\n")
|
538
|
+
end
|
483
539
|
end
|
484
540
|
|
485
541
|
sig { params(mod: Module).returns(T::Hash[Symbol, T::Array[Symbol]]) }
|
@@ -659,6 +715,33 @@ module Tapioca
|
|
659
715
|
@indent -= 2 * step
|
660
716
|
end
|
661
717
|
|
718
|
+
sig do
|
719
|
+
params(
|
720
|
+
constant: Module,
|
721
|
+
blk: T.proc
|
722
|
+
.returns(T.nilable(String))
|
723
|
+
)
|
724
|
+
.returns(T.nilable(String))
|
725
|
+
end
|
726
|
+
def with_indentation_for_constant(constant, &blk)
|
727
|
+
step = if constant.singleton_class?
|
728
|
+
1
|
729
|
+
else
|
730
|
+
0
|
731
|
+
end
|
732
|
+
|
733
|
+
result = with_indentation(step, &blk)
|
734
|
+
|
735
|
+
return result unless result
|
736
|
+
return result unless constant.singleton_class?
|
737
|
+
|
738
|
+
[
|
739
|
+
indented("class << self"),
|
740
|
+
result,
|
741
|
+
indented("end"),
|
742
|
+
].compact.join("\n")
|
743
|
+
end
|
744
|
+
|
662
745
|
sig { params(str: String).returns(String) }
|
663
746
|
def indented(str)
|
664
747
|
" " * @indent + str
|
@@ -859,12 +942,12 @@ module Tapioca
|
|
859
942
|
nil
|
860
943
|
end
|
861
944
|
|
862
|
-
sig { params(constant:
|
945
|
+
sig { params(constant: T::Types::Base).returns(String) }
|
863
946
|
def type_of(constant)
|
864
947
|
constant.to_s.gsub(/\bAttachedClass\b/, "T.attached_class")
|
865
948
|
end
|
866
949
|
|
867
|
-
sig { params(object:
|
950
|
+
sig { params(object: BasicObject).returns(Integer).checked(:never) }
|
868
951
|
def object_id_of(object)
|
869
952
|
Object.instance_method(:object_id).bind(object).call
|
870
953
|
end
|
data/lib/tapioca/config.rb
CHANGED
@@ -8,7 +8,6 @@ module Tapioca
|
|
8
8
|
const(:outdir, String)
|
9
9
|
const(:prerequire, T.nilable(String))
|
10
10
|
const(:postrequire, String)
|
11
|
-
const(:generate_command, String)
|
12
11
|
const(:exclude, T::Array[String])
|
13
12
|
const(:typed_overrides, T::Hash[String, String])
|
14
13
|
const(:todos_path, String)
|
@@ -27,6 +26,7 @@ module Tapioca
|
|
27
26
|
TAPIOCA_PATH = T.let("#{SORBET_PATH}/tapioca", String)
|
28
27
|
TAPIOCA_CONFIG = T.let("#{TAPIOCA_PATH}/config.yml", String)
|
29
28
|
|
29
|
+
DEFAULT_COMMAND = T.let("bin/tapioca", String)
|
30
30
|
DEFAULT_POSTREQUIRE = T.let("#{TAPIOCA_PATH}/require.rb", String)
|
31
31
|
DEFAULT_RBIDIR = T.let("#{SORBET_PATH}/rbi", String)
|
32
32
|
DEFAULT_DSLDIR = T.let("#{DEFAULT_RBIDIR}/dsl", String)
|
@@ -10,9 +10,13 @@ module Tapioca
|
|
10
10
|
|
11
11
|
sig { params(command: Symbol, options: T::Hash[String, T.untyped]).returns(Config) }
|
12
12
|
def from_options(command, options)
|
13
|
-
|
14
|
-
|
15
|
-
)
|
13
|
+
merged_options = merge_options(default_options(command), config_options, options)
|
14
|
+
|
15
|
+
puts(<<~MSG) if merged_options.include?("generate_command")
|
16
|
+
DEPRECATION: The `-c` and `--cmd` flags will be removed in a future release.
|
17
|
+
MSG
|
18
|
+
|
19
|
+
Config.from_hash(merged_options)
|
16
20
|
end
|
17
21
|
|
18
22
|
private
|
@@ -40,14 +44,6 @@ module Tapioca
|
|
40
44
|
DEFAULT_OPTIONS.merge("outdir" => default_outdir)
|
41
45
|
end
|
42
46
|
|
43
|
-
sig { returns(String) }
|
44
|
-
def default_command
|
45
|
-
command = File.basename($PROGRAM_NAME)
|
46
|
-
args = ARGV.join(" ")
|
47
|
-
|
48
|
-
"#{command} #{args}".strip
|
49
|
-
end
|
50
|
-
|
51
47
|
sig { params(options: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
|
52
48
|
def merge_options(*options)
|
53
49
|
options.each_with_object({}) do |option, result|
|
@@ -65,7 +61,6 @@ module Tapioca
|
|
65
61
|
DEFAULT_OPTIONS = T.let({
|
66
62
|
"postrequire" => Config::DEFAULT_POSTREQUIRE,
|
67
63
|
"outdir" => nil,
|
68
|
-
"generate_command" => default_command,
|
69
64
|
"exclude" => [],
|
70
65
|
"typed_overrides" => Config::DEFAULT_OVERRIDES,
|
71
66
|
"todos_path" => Config::DEFAULT_TODOSPATH,
|
data/lib/tapioca/generator.rb
CHANGED
@@ -63,7 +63,7 @@ module Tapioca
|
|
63
63
|
|
64
64
|
content = String.new
|
65
65
|
content << rbi_header(
|
66
|
-
|
66
|
+
"#{Config::DEFAULT_COMMAND} require",
|
67
67
|
reason: "explicit gem requires",
|
68
68
|
strictness: "false"
|
69
69
|
)
|
@@ -76,8 +76,8 @@ module Tapioca
|
|
76
76
|
say("Done", :green)
|
77
77
|
|
78
78
|
say("All requires from this application have been written to #{name}.", [:green, :bold])
|
79
|
-
cmd = set_color("
|
80
|
-
say("Please review changes and commit them, then run
|
79
|
+
cmd = set_color("#{Config::DEFAULT_COMMAND} sync", :yellow, :bold)
|
80
|
+
say("Please review changes and commit them, then run `#{cmd}`.", [:green, :bold])
|
81
81
|
end
|
82
82
|
|
83
83
|
sig { void }
|
@@ -99,7 +99,7 @@ module Tapioca
|
|
99
99
|
|
100
100
|
content = String.new
|
101
101
|
content << rbi_header(
|
102
|
-
|
102
|
+
"#{Config::DEFAULT_COMMAND} todo",
|
103
103
|
reason: "unresolved constants",
|
104
104
|
strictness: "false"
|
105
105
|
)
|
@@ -116,16 +116,26 @@ module Tapioca
|
|
116
116
|
say("Please review changes and commit them.", [:green, :bold])
|
117
117
|
end
|
118
118
|
|
119
|
-
sig
|
120
|
-
|
119
|
+
sig do
|
120
|
+
params(
|
121
|
+
requested_constants: T::Array[String],
|
122
|
+
should_verify: T::Boolean,
|
123
|
+
).void
|
124
|
+
end
|
125
|
+
def build_dsl(requested_constants, should_verify: false)
|
121
126
|
load_application(eager_load: requested_constants.empty?)
|
122
127
|
load_dsl_generators
|
123
128
|
|
124
|
-
|
125
|
-
|
126
|
-
|
129
|
+
if should_verify
|
130
|
+
say("Checking for out-of-date RBIs...")
|
131
|
+
else
|
132
|
+
say("Compiling DSL RBI files...")
|
133
|
+
end
|
127
134
|
say("")
|
128
135
|
|
136
|
+
outpath = should_verify ? Dir.mktmpdir : config.outpath
|
137
|
+
rbi_files_to_purge = existing_rbi_filenames(requested_constants)
|
138
|
+
|
129
139
|
compiler = Compilers::DslCompiler.new(
|
130
140
|
requested_constants: constantize(requested_constants),
|
131
141
|
requested_generators: config.generators,
|
@@ -135,24 +145,21 @@ module Tapioca
|
|
135
145
|
)
|
136
146
|
|
137
147
|
compiler.run do |constant, contents|
|
138
|
-
filename = compile_dsl_rbi(constant, contents)
|
148
|
+
filename = compile_dsl_rbi(constant, contents, outpath: Pathname.new(outpath))
|
139
149
|
rbi_files_to_purge.delete(filename) if filename
|
140
150
|
end
|
151
|
+
say("")
|
141
152
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
rbi_files_to_purge.sort.each do |filename|
|
147
|
-
remove(filename)
|
148
|
-
end
|
149
|
-
end
|
153
|
+
if should_verify
|
154
|
+
perform_dsl_verification(outpath)
|
155
|
+
else
|
156
|
+
purge_stale_dsl_rbi_files(rbi_files_to_purge)
|
150
157
|
|
151
|
-
|
152
|
-
say("Done", :green)
|
158
|
+
say("Done", :green)
|
153
159
|
|
154
|
-
|
155
|
-
|
160
|
+
say("All operations performed in working directory.", [:green, :bold])
|
161
|
+
say("Please review changes and commit them.", [:green, :bold])
|
162
|
+
end
|
156
163
|
end
|
157
164
|
|
158
165
|
sig { void }
|
@@ -218,7 +225,7 @@ module Tapioca
|
|
218
225
|
say_error("If you populated ", :yellow)
|
219
226
|
say_error("#{file} ", :bold, :blue)
|
220
227
|
say_error("with ", :yellow)
|
221
|
-
say_error("
|
228
|
+
say_error("`#{Config::DEFAULT_COMMAND} require`", :bold, :blue)
|
222
229
|
say_error("you should probably review it and remove the faulty line.", :yellow)
|
223
230
|
end
|
224
231
|
|
@@ -286,10 +293,10 @@ module Tapioca
|
|
286
293
|
constant_map.values
|
287
294
|
end
|
288
295
|
|
289
|
-
sig { params(requested_constants: T::Array[String]).returns(T::Set[Pathname]) }
|
290
|
-
def existing_rbi_filenames(requested_constants)
|
296
|
+
sig { params(requested_constants: T::Array[String], path: Pathname).returns(T::Set[Pathname]) }
|
297
|
+
def existing_rbi_filenames(requested_constants, path: config.outpath)
|
291
298
|
filenames = if requested_constants.empty?
|
292
|
-
Pathname.glob(
|
299
|
+
Pathname.glob(path / "**/*.rbi")
|
293
300
|
else
|
294
301
|
requested_constants.map do |constant_name|
|
295
302
|
dsl_rbi_filename(constant_name)
|
@@ -477,7 +484,7 @@ module Tapioca
|
|
477
484
|
rbi_body_content = compiler.compile(gem)
|
478
485
|
content = String.new
|
479
486
|
content << rbi_header(
|
480
|
-
|
487
|
+
"#{Config::DEFAULT_COMMAND} sync",
|
481
488
|
reason: "types exported from the `#{gem.name}` gem",
|
482
489
|
strictness: strictness
|
483
490
|
)
|
@@ -494,23 +501,22 @@ module Tapioca
|
|
494
501
|
end
|
495
502
|
File.write(filename.to_s, content)
|
496
503
|
|
497
|
-
Pathname.glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
504
|
+
T.unsafe(Pathname).glob((config.outpath / "#{gem.name}@*.rbi").to_s) do |file|
|
498
505
|
remove(file) unless file.basename.to_s == gem.rbi_file_name
|
499
506
|
end
|
500
507
|
end
|
501
508
|
|
502
|
-
sig { params(constant: Module, contents: String).returns(T.nilable(Pathname)) }
|
503
|
-
def compile_dsl_rbi(constant, contents)
|
509
|
+
sig { params(constant: Module, contents: String, outpath: Pathname).returns(T.nilable(Pathname)) }
|
510
|
+
def compile_dsl_rbi(constant, contents, outpath: config.outpath)
|
504
511
|
return if contents.nil?
|
505
512
|
|
506
|
-
command = format(config.generate_command, constant.name)
|
507
513
|
constant_name = Module.instance_method(:name).bind(constant).call
|
508
514
|
rbi_name = constant_name.underscore + ".rbi"
|
509
|
-
filename =
|
515
|
+
filename = outpath / rbi_name
|
510
516
|
|
511
517
|
out = String.new
|
512
518
|
out << rbi_header(
|
513
|
-
|
519
|
+
"#{Config::DEFAULT_COMMAND} dsl #{constant_name}",
|
514
520
|
reason: "dynamic methods in `#{constant.name}`"
|
515
521
|
)
|
516
522
|
out << contents
|
@@ -522,5 +528,53 @@ module Tapioca
|
|
522
528
|
|
523
529
|
filename
|
524
530
|
end
|
531
|
+
|
532
|
+
sig { params(tmp_dir: Pathname).returns(T.nilable(String)) }
|
533
|
+
def verify_dsl_rbi(tmp_dir:)
|
534
|
+
existing_rbis = existing_rbi_filenames([]).sort
|
535
|
+
new_rbis = existing_rbi_filenames([], path: tmp_dir).grep_v(/gem|shim/).sort
|
536
|
+
|
537
|
+
return "New file(s) introduced." if existing_rbis.length != new_rbis.length
|
538
|
+
|
539
|
+
desynced_files = []
|
540
|
+
|
541
|
+
(0..existing_rbis.length - 1).each do |i|
|
542
|
+
desynced_files << new_rbis[i] unless FileUtils.identical?(existing_rbis[i], new_rbis[i])
|
543
|
+
end
|
544
|
+
|
545
|
+
unless desynced_files.empty?
|
546
|
+
filenames = desynced_files.map { |f| f.to_s.sub!(tmp_dir.to_s, "sorbet/rbi/dsl") }.join("\n - ")
|
547
|
+
|
548
|
+
return "File(s) updated:\n - #{filenames}"
|
549
|
+
end
|
550
|
+
|
551
|
+
nil
|
552
|
+
end
|
553
|
+
|
554
|
+
sig { params(dir: String).void }
|
555
|
+
def perform_dsl_verification(dir)
|
556
|
+
if (error = verify_dsl_rbi(tmp_dir: Pathname.new(dir)))
|
557
|
+
say("RBI files are out-of-date, please run `#{Config::DEFAULT_COMMAND} dsl` to update.")
|
558
|
+
say("Reason: ", [:red])
|
559
|
+
say(error)
|
560
|
+
exit(1)
|
561
|
+
else
|
562
|
+
say("Nothing to do, all RBIs are up-to-date.")
|
563
|
+
end
|
564
|
+
ensure
|
565
|
+
FileUtils.remove_entry(dir)
|
566
|
+
end
|
567
|
+
|
568
|
+
sig { params(files: T::Set[Pathname]).void }
|
569
|
+
def purge_stale_dsl_rbi_files(files)
|
570
|
+
if files.any?
|
571
|
+
say("Removing stale RBI files...")
|
572
|
+
|
573
|
+
files.sort.each do |filename|
|
574
|
+
remove(filename)
|
575
|
+
end
|
576
|
+
say("")
|
577
|
+
end
|
578
|
+
end
|
525
579
|
end
|
526
580
|
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Tapioca
|
5
|
+
# This class is responsible for storing and looking up information related to generic types.
|
6
|
+
#
|
7
|
+
# The class stores 2 different kinds of data, in two separate lookup tables:
|
8
|
+
# 1. a lookup of generic type instances by name: `@generic_instances`
|
9
|
+
# 2. a lookup of type variable serializer by constant and type variable
|
10
|
+
# instance: `@type_variables`
|
11
|
+
#
|
12
|
+
# By storing the above data, we can cheaply query each constant against this registry
|
13
|
+
# to see if it declares any generic type variables. This becomes a simple lookup in the
|
14
|
+
# `@type_variables` hash table with the given constant.
|
15
|
+
#
|
16
|
+
# If there is no entry, then we can cheaply know that we can skip generic type
|
17
|
+
# information generation for this type.
|
18
|
+
#
|
19
|
+
# On the other hand, if we get a result, then the result will be a hash of type
|
20
|
+
# variable to type variable serializers. This allows us to associate type variables
|
21
|
+
# to the constant names that represent them, easily.
|
22
|
+
module GenericTypeRegistry
|
23
|
+
@generic_instances = T.let(
|
24
|
+
{},
|
25
|
+
T::Hash[String, Module]
|
26
|
+
)
|
27
|
+
|
28
|
+
@type_variables = T.let(
|
29
|
+
{},
|
30
|
+
T::Hash[Integer, T::Hash[Integer, String]]
|
31
|
+
)
|
32
|
+
|
33
|
+
class << self
|
34
|
+
extend T::Sig
|
35
|
+
|
36
|
+
# This method is responsible for building the name of the instantiated concrete type
|
37
|
+
# and cloning the given constant so that we can return a type that is the same
|
38
|
+
# as the current type but is a different instance and has a different name method.
|
39
|
+
#
|
40
|
+
# We cache those cloned instances by their name in `@generic_instances`, so that
|
41
|
+
# we don't keep instantiating a new type every single time it is referenced.
|
42
|
+
# For example, `[Foo[Integer], Foo[Integer], Foo[Integer], Foo[String]]` will only
|
43
|
+
# result in 2 clones (1 for `Foo[Integer]` and another for `Foo[String]`) and
|
44
|
+
# 2 hash lookups (for the other two `Foo[Integer]`s).
|
45
|
+
#
|
46
|
+
# This method returns the created or cached clone of the constant.
|
47
|
+
sig { params(constant: T.untyped, types: T.untyped).returns(Module) }
|
48
|
+
def register_type(constant, types)
|
49
|
+
# Build the name of the instantiated generic type,
|
50
|
+
# something like `"Foo[X, Y, Z]"`
|
51
|
+
type_list = types.map { |type| T::Utils.coerce(type).name }.join(", ")
|
52
|
+
name = "#{name_of(constant)}[#{type_list}]"
|
53
|
+
|
54
|
+
# Create a clone of the constant with an overridden `name`
|
55
|
+
# method that returns the name we constructed above.
|
56
|
+
#
|
57
|
+
# Also, we try to memoize the clone based on the name, so that
|
58
|
+
# we don't have to keep recreating clones all the time.
|
59
|
+
@generic_instances[name] ||= constant.clone.tap do |clone|
|
60
|
+
clone.define_singleton_method(:name) { name }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
sig do
|
65
|
+
params(
|
66
|
+
constant: T.untyped,
|
67
|
+
type_member: T::Types::TypeVariable,
|
68
|
+
fixed: T.untyped,
|
69
|
+
lower: T.untyped,
|
70
|
+
upper: T.untyped
|
71
|
+
).void
|
72
|
+
end
|
73
|
+
def register_type_member(constant, type_member, fixed, lower, upper)
|
74
|
+
register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
|
75
|
+
end
|
76
|
+
|
77
|
+
sig do
|
78
|
+
params(
|
79
|
+
constant: T.untyped,
|
80
|
+
type_template: T::Types::TypeVariable,
|
81
|
+
fixed: T.untyped,
|
82
|
+
lower: T.untyped,
|
83
|
+
upper: T.untyped
|
84
|
+
).void
|
85
|
+
end
|
86
|
+
def register_type_template(constant, type_template, fixed, lower, upper)
|
87
|
+
register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
|
91
|
+
def lookup_type_variables(constant)
|
92
|
+
@type_variables[object_id_of(constant)]
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# This method is called from intercepted calls to `type_member` and `type_template`.
|
98
|
+
# We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
|
99
|
+
# instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
|
100
|
+
#
|
101
|
+
# This method creates a `String` with that data and stores it in the
|
102
|
+
# `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
|
103
|
+
#
|
104
|
+
# Finally, the original `type_variable` is returned from this method, so that the caller
|
105
|
+
# can return it from the original methods as well.
|
106
|
+
sig do
|
107
|
+
params(
|
108
|
+
constant: T.untyped,
|
109
|
+
type_variable_type: T.enum([:type_member, :type_template]),
|
110
|
+
type_variable: T::Types::TypeVariable,
|
111
|
+
fixed: T.untyped,
|
112
|
+
lower: T.untyped,
|
113
|
+
upper: T.untyped
|
114
|
+
).void
|
115
|
+
end
|
116
|
+
# rubocop:disable Metrics/ParameterLists
|
117
|
+
def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
|
118
|
+
# rubocop:enable Metrics/ParameterLists
|
119
|
+
type_variables = lookup_or_initialize_type_variables(constant)
|
120
|
+
|
121
|
+
type_variables[object_id_of(type_variable)] = serialize_type_variable(
|
122
|
+
type_variable_type,
|
123
|
+
type_variable.variance,
|
124
|
+
fixed,
|
125
|
+
lower,
|
126
|
+
upper
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { params(constant: Module).returns(T::Hash[Integer, String]) }
|
131
|
+
def lookup_or_initialize_type_variables(constant)
|
132
|
+
@type_variables[object_id_of(constant)] ||= {}
|
133
|
+
end
|
134
|
+
|
135
|
+
sig do
|
136
|
+
params(
|
137
|
+
type_variable_type: Symbol,
|
138
|
+
variance: Symbol,
|
139
|
+
fixed: T.untyped,
|
140
|
+
lower: T.untyped,
|
141
|
+
upper: T.untyped
|
142
|
+
).returns(String)
|
143
|
+
end
|
144
|
+
def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
|
145
|
+
parts = []
|
146
|
+
parts << ":#{variance}" unless variance == :invariant
|
147
|
+
parts << "fixed: #{fixed}" if fixed
|
148
|
+
parts << "lower: #{lower}" unless lower == T.untyped
|
149
|
+
parts << "upper: #{upper}" unless upper == BasicObject
|
150
|
+
|
151
|
+
parameters = parts.join(", ")
|
152
|
+
|
153
|
+
serialized = T.let(type_variable_type.to_s, String)
|
154
|
+
serialized += "(#{parameters})" unless parameters.empty?
|
155
|
+
|
156
|
+
serialized
|
157
|
+
end
|
158
|
+
|
159
|
+
sig { params(constant: Module).returns(T.nilable(String)) }
|
160
|
+
def name_of(constant)
|
161
|
+
Module.instance_method(:name).bind(constant).call
|
162
|
+
end
|
163
|
+
|
164
|
+
sig { params(object: BasicObject).returns(Integer) }
|
165
|
+
def object_id_of(object)
|
166
|
+
Object.instance_method(:object_id).bind(object).call
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "tapioca"
|
5
|
+
require "tapioca/loader"
|
6
|
+
require "tapioca/constant_locator"
|
7
|
+
require "tapioca/generic_type_registry"
|
8
|
+
require "tapioca/sorbet_ext/generic_name_patch"
|
9
|
+
require "tapioca/config"
|
10
|
+
require "tapioca/config_builder"
|
11
|
+
require "tapioca/generator"
|
12
|
+
require "tapioca/cli"
|
13
|
+
require "tapioca/cli/main"
|
14
|
+
require "tapioca/gemfile"
|
15
|
+
require "tapioca/compilers/sorbet"
|
16
|
+
require "tapioca/compilers/requires_compiler"
|
17
|
+
require "tapioca/compilers/symbol_table_compiler"
|
18
|
+
require "tapioca/compilers/symbol_table/symbol_generator"
|
19
|
+
require "tapioca/compilers/symbol_table/symbol_loader"
|
20
|
+
require "tapioca/compilers/todos_compiler"
|
21
|
+
require "tapioca/compilers/dsl_compiler"
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "tapioca/sorbet_ext/name_patch"
|
5
|
+
|
6
|
+
module T
|
7
|
+
module Generic
|
8
|
+
# This module intercepts calls to generic type instantiations and type variable definitions.
|
9
|
+
# Tapioca stores the data from those calls in a `GenericTypeRegistry` which can then be used
|
10
|
+
# to look up the original call details when we are trying to do code generation.
|
11
|
+
#
|
12
|
+
# We are interested in the data of the `[]`, `type_member` and `type_template` calls which
|
13
|
+
# are all needed to generate good generic information at runtime.
|
14
|
+
module TypeStoragePatch
|
15
|
+
def [](*types)
|
16
|
+
# `T::Generic#[]` just returns `self`, so let's call and store it.
|
17
|
+
constant = super
|
18
|
+
# `register_type` method builds and returns an instantiated clone of the generic type
|
19
|
+
# so, we just return that from this method as well.
|
20
|
+
Tapioca::GenericTypeRegistry.register_type(constant, types)
|
21
|
+
end
|
22
|
+
|
23
|
+
def type_member(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
|
24
|
+
# `T::Generic#type_member` just instantiates a `T::Type::TypeMember` instance and returns it.
|
25
|
+
# We use that when registering the type member and then later return it from this method.
|
26
|
+
type_member = super
|
27
|
+
Tapioca::GenericTypeRegistry.register_type_member(self, type_member, fixed, lower, upper)
|
28
|
+
type_member
|
29
|
+
end
|
30
|
+
|
31
|
+
def type_template(variance = :invariant, fixed: nil, lower: T.untyped, upper: BasicObject)
|
32
|
+
# `T::Generic#type_template` just instantiates a `T::Type::TypeTemplate` instance and returns it.
|
33
|
+
# We use that when registering the type template and then later return it from this method.
|
34
|
+
type_template = super
|
35
|
+
Tapioca::GenericTypeRegistry.register_type_template(self, type_template, fixed, lower, upper)
|
36
|
+
type_template
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
prepend TypeStoragePatch
|
41
|
+
end
|
42
|
+
|
43
|
+
module Types
|
44
|
+
class Simple
|
45
|
+
# This module intercepts calls to the `name` method for
|
46
|
+
# simple types, so that it can ask the name to the type if
|
47
|
+
# the type is generic, since, by this point, we've created
|
48
|
+
# a clone of that type with the `name` method returning the
|
49
|
+
# appropriate name for that specific concrete type.
|
50
|
+
module GenericNamePatch
|
51
|
+
def name
|
52
|
+
if T::Generic === @raw_type
|
53
|
+
# for types that are generic, use the name
|
54
|
+
# returned by the "name" method of this instance
|
55
|
+
@name ||= T.unsafe(@raw_type).name.freeze
|
56
|
+
else
|
57
|
+
# otherwise, fallback to the normal name lookup
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
prepend GenericNamePatch
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/tapioca/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapioca
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ufuk Kayserilioglu
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: exe
|
13
13
|
cert_chain: []
|
14
|
-
date: 2021-03-
|
14
|
+
date: 2021-03-22 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: bundler
|
@@ -125,6 +125,7 @@ files:
|
|
125
125
|
- exe/tapioca
|
126
126
|
- lib/tapioca.rb
|
127
127
|
- lib/tapioca/cli.rb
|
128
|
+
- lib/tapioca/cli/main.rb
|
128
129
|
- lib/tapioca/compilers/dsl/action_controller_helpers.rb
|
129
130
|
- lib/tapioca/compilers/dsl/action_mailer.rb
|
130
131
|
- lib/tapioca/compilers/dsl/active_record_associations.rb
|
@@ -155,7 +156,11 @@ files:
|
|
155
156
|
- lib/tapioca/core_ext/class.rb
|
156
157
|
- lib/tapioca/gemfile.rb
|
157
158
|
- lib/tapioca/generator.rb
|
159
|
+
- lib/tapioca/generic_type_registry.rb
|
160
|
+
- lib/tapioca/internal.rb
|
158
161
|
- lib/tapioca/loader.rb
|
162
|
+
- lib/tapioca/sorbet_ext/generic_name_patch.rb
|
163
|
+
- lib/tapioca/sorbet_ext/name_patch.rb
|
159
164
|
- lib/tapioca/version.rb
|
160
165
|
homepage: https://github.com/Shopify/tapioca
|
161
166
|
licenses:
|