tapioca 0.4.16 → 0.4.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,222 @@
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 generic type with an overridden `name`
55
+ # method that returns the name we constructed above.
56
+ #
57
+ # Also, we try to memoize the generic type based on the name, so that
58
+ # we don't have to keep recreating them all the time.
59
+ @generic_instances[name] ||= create_generic_type(constant, name)
60
+ end
61
+
62
+ sig do
63
+ params(
64
+ constant: T.untyped,
65
+ type_member: T::Types::TypeVariable,
66
+ fixed: T.untyped,
67
+ lower: T.untyped,
68
+ upper: T.untyped
69
+ ).void
70
+ end
71
+ def register_type_member(constant, type_member, fixed, lower, upper)
72
+ register_type_variable(constant, :type_member, type_member, fixed, lower, upper)
73
+ end
74
+
75
+ sig do
76
+ params(
77
+ constant: T.untyped,
78
+ type_template: T::Types::TypeVariable,
79
+ fixed: T.untyped,
80
+ lower: T.untyped,
81
+ upper: T.untyped
82
+ ).void
83
+ end
84
+ def register_type_template(constant, type_template, fixed, lower, upper)
85
+ register_type_variable(constant, :type_template, type_template, fixed, lower, upper)
86
+ end
87
+
88
+ sig { params(constant: Module).returns(T.nilable(T::Hash[Integer, String])) }
89
+ def lookup_type_variables(constant)
90
+ @type_variables[object_id_of(constant)]
91
+ end
92
+
93
+ private
94
+
95
+ sig { params(constant: Module, name: String).returns(Module) }
96
+ def create_generic_type(constant, name)
97
+ generic_type = case constant
98
+ when Class
99
+ # For classes, we want to create a subclass, so that an instance of
100
+ # the generic class `Foo[Bar]` is still a `Foo`. That is:
101
+ # `Foo[Bar].new.is_a?(Foo)` should be true, which isn't the case
102
+ # if we just clone the class. But subclassing works just fine.
103
+ create_safe_subclass(constant)
104
+ else
105
+ # This can only be a module and it is fine to just clone modules
106
+ # since they can't have instances and will not have `is_a?` relationships.
107
+ # Moreover, we never `include`/`extend` any generic modules into the
108
+ # ancestor tree, so this doesn't become a problem with checking the
109
+ # instance of a class being `is_a?` of a module type.
110
+ constant.clone
111
+ end
112
+
113
+ # Let's set the `name` method to return the proper generic name
114
+ generic_type.define_singleton_method(:name) { name }
115
+
116
+ # Return the generic type we created
117
+ generic_type
118
+ end
119
+
120
+ # This method is called from intercepted calls to `type_member` and `type_template`.
121
+ # We get passed all the arguments to those methods, as well as the `T::Types::TypeVariable`
122
+ # instance generated by the Sorbet defined `type_member`/`type_template` call on `T::Generic`.
123
+ #
124
+ # This method creates a `String` with that data and stores it in the
125
+ # `@type_variables` lookup table, keyed by the `constant` and `type_variable`.
126
+ #
127
+ # Finally, the original `type_variable` is returned from this method, so that the caller
128
+ # can return it from the original methods as well.
129
+ sig do
130
+ params(
131
+ constant: T.untyped,
132
+ type_variable_type: T.enum([:type_member, :type_template]),
133
+ type_variable: T::Types::TypeVariable,
134
+ fixed: T.untyped,
135
+ lower: T.untyped,
136
+ upper: T.untyped
137
+ ).void
138
+ end
139
+ # rubocop:disable Metrics/ParameterLists
140
+ def register_type_variable(constant, type_variable_type, type_variable, fixed, lower, upper)
141
+ # rubocop:enable Metrics/ParameterLists
142
+ type_variables = lookup_or_initialize_type_variables(constant)
143
+
144
+ type_variables[object_id_of(type_variable)] = serialize_type_variable(
145
+ type_variable_type,
146
+ type_variable.variance,
147
+ fixed,
148
+ lower,
149
+ upper
150
+ )
151
+ end
152
+
153
+ sig { params(constant: Class).returns(Class) }
154
+ def create_safe_subclass(constant)
155
+ # Lookup the "inherited" class method
156
+ inherited_method = constant.method(:inherited)
157
+ # and the module that defines it
158
+ owner = inherited_method.owner
159
+
160
+ # If no one has overriden the inherited method yet, just subclass
161
+ return Class.new(constant) if Class == owner
162
+
163
+ begin
164
+ # Otherwise, some inherited method could be preventing us
165
+ # from creating subclasses, so let's override it and rescue
166
+ owner.send(:define_method, :inherited) do |s|
167
+ begin
168
+ inherited_method.call(s)
169
+ rescue
170
+ # Ignoring errors
171
+ end
172
+ end
173
+
174
+ # return a subclass
175
+ Class.new(constant)
176
+ ensure
177
+ # Reinstate the original inherited method back.
178
+ owner.send(:define_method, :inherited, inherited_method)
179
+ end
180
+ end
181
+
182
+ sig { params(constant: Module).returns(T::Hash[Integer, String]) }
183
+ def lookup_or_initialize_type_variables(constant)
184
+ @type_variables[object_id_of(constant)] ||= {}
185
+ end
186
+
187
+ sig do
188
+ params(
189
+ type_variable_type: Symbol,
190
+ variance: Symbol,
191
+ fixed: T.untyped,
192
+ lower: T.untyped,
193
+ upper: T.untyped
194
+ ).returns(String)
195
+ end
196
+ def serialize_type_variable(type_variable_type, variance, fixed, lower, upper)
197
+ parts = []
198
+ parts << ":#{variance}" unless variance == :invariant
199
+ parts << "fixed: #{fixed}" if fixed
200
+ parts << "lower: #{lower}" unless lower == T.untyped
201
+ parts << "upper: #{upper}" unless upper == BasicObject
202
+
203
+ parameters = parts.join(", ")
204
+
205
+ serialized = T.let(type_variable_type.to_s, String)
206
+ serialized += "(#{parameters})" unless parameters.empty?
207
+
208
+ serialized
209
+ end
210
+
211
+ sig { params(constant: Module).returns(T.nilable(String)) }
212
+ def name_of(constant)
213
+ Module.instance_method(:name).bind(constant).call
214
+ end
215
+
216
+ sig { params(object: BasicObject).returns(Integer) }
217
+ def object_id_of(object)
218
+ Object.instance_method(:object_id).bind(object).call
219
+ end
220
+ end
221
+ end
222
+ 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
@@ -0,0 +1,16 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module T
5
+ module Types
6
+ class Simple
7
+ module NamePatch
8
+ def name
9
+ @name ||= Module.instance_method(:name).bind(@raw_type).call.freeze
10
+ end
11
+ end
12
+
13
+ prepend NamePatch
14
+ end
15
+ end
16
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Tapioca
5
- VERSION = "0.4.16"
5
+ VERSION = "0.4.21"
6
6
  end
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.16
4
+ version: 0.4.21
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-09 00:00:00.000000000 Z
14
+ date: 2021-04-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: