sorbet 0.4.4250 → 0.4.4253

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module Sorbet::Private::RealStdlib
5
+ def self.real_is_a?(o, klass)
6
+ @real_is_a ||= Object.instance_method(:is_a?)
7
+ @real_is_a.bind(o).call(klass)
8
+ end
9
+
10
+ def self.real_constants(mod)
11
+ @real_constants ||= Module.instance_method(:constants)
12
+ @real_constants.bind(mod).call(false)
13
+ end
14
+
15
+ def self.real_object_id(o)
16
+ @real_object_id ||= Object.instance_method(:object_id)
17
+ @real_object_id.bind(o).call
18
+ end
19
+
20
+ def self.real_name(o)
21
+ @real_name ||= Module.instance_method(:name)
22
+ @real_name.bind(o).call
23
+ end
24
+
25
+ def self.real_ancestors(mod)
26
+ @real_ancestors ||= Module.instance_method(:ancestors)
27
+ @real_ancestors.bind(mod).call
28
+ end
29
+
30
+ def self.real_instance_methods(mod, arg)
31
+ @real_instance_methods ||= Module.instance_method(:instance_methods)
32
+ @real_instance_methods.bind(mod).call(arg)
33
+ end
34
+
35
+ def self.real_singleton_methods(mod, arg)
36
+ @real_singleton_methods ||= Module.instance_method(:singleton_methods)
37
+ @real_singleton_methods.bind(mod).call(arg)
38
+ end
39
+
40
+ def self.real_private_instance_methods(mod, arg)
41
+ @real_private_instance_methods ||= Module.instance_method(:private_instance_methods)
42
+ @real_private_instance_methods.bind(mod).call(arg)
43
+ end
44
+
45
+ def self.real_singleton_class(obj)
46
+ @real_singleton_class ||= Object.instance_method(:singleton_class)
47
+ @real_singleton_class.bind(obj).call
48
+ end
49
+
50
+ def self.real_spaceship(obj, arg)
51
+ @real_spaceship ||= Object.instance_method(:<=>)
52
+ @real_spaceship.bind(obj).call(arg)
53
+ end
54
+
55
+ def self.real_hash(o)
56
+ @real_hash ||= Object.instance_method(:hash)
57
+ @real_hash.bind(o).call
58
+ end
59
+
60
+ def self.real_superclass(o)
61
+ @real_superclass ||= Class.instance_method(:superclass)
62
+ @real_superclass.bind(o).call
63
+ end
64
+
65
+ def self.real_eqeq(obj, other)
66
+ @real_eqeq ||= Object.instance_method(:==)
67
+ @real_eqeq.bind(obj).call(other)
68
+ end
69
+
70
+ def self.real_autoload?(o, klass)
71
+ @real_autoload ||= Object.instance_method(:autoload?)
72
+ @real_autoload.bind(o).call(klass)
73
+ end
74
+
75
+ def self.real_const_get(obj, const, arg)
76
+ @real_const_get ||= Object.singleton_class.instance_method(:const_get)
77
+ @real_const_get.bind(obj).call(const, arg)
78
+ end
79
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'pathname'
5
+
6
+ require_relative './gem_loader'
7
+ require_relative './status'
8
+
9
+ class ExitCalledError < RuntimeError
10
+ end
11
+
12
+ class Sorbet::Private::RequireEverything
13
+ # Goes through the most common ways to require all your userland code
14
+ def self.require_everything
15
+ return if @already_ran
16
+ @already_ran = true
17
+ patch_kernel
18
+ load_rails
19
+ load_bundler # this comes second since some rails projects fail `Bundler.require' before rails is loaded
20
+ require_all_files
21
+ end
22
+
23
+ def self.load_rails
24
+ return unless rails?
25
+ require './config/application'
26
+ rails = Object.const_get(:Rails)
27
+ rails.application.require_environment!
28
+ rails.application.eager_load!
29
+ true
30
+ end
31
+
32
+ def self.load_bundler
33
+ return unless File.exist?('Gemfile')
34
+ begin
35
+ require 'bundler'
36
+ rescue LoadError
37
+ return
38
+ end
39
+ Sorbet::Private::GemLoader.require_all_gems
40
+ end
41
+
42
+ def self.require_all_files
43
+ excluded_paths = Set.new
44
+ excluded_paths += excluded_rails_files if rails?
45
+
46
+ abs_paths = Dir.glob("#{Dir.pwd}/**/*.rb")
47
+ errors = []
48
+ abs_paths.each_with_index do |abs_path, i|
49
+ # Executable files are likely not meant to be required.
50
+ # Some things we're trying to prevent against:
51
+ # - misbehaving require-time side effects (removing files, reading from stdin, etc.)
52
+ # - extra long runtime (making network requests, running a benchmark)
53
+ # While this isn't a perfect heuristic for these things, it's pretty good.
54
+ next if File.executable?(abs_path)
55
+ next if excluded_paths.include?(abs_path)
56
+ next if /^# +typed: +ignore$/.match(File.read(abs_path).scrub)
57
+
58
+ begin
59
+ my_require(abs_path, i+1, abs_paths.size)
60
+ rescue LoadError, NoMethodError, SyntaxError
61
+ next
62
+ rescue
63
+ errors << abs_path
64
+ next
65
+ end
66
+ end
67
+ # one more chance for order dependent things
68
+ errors.each_with_index do |abs_path, i|
69
+ begin
70
+ my_require(abs_path, i+1, errors.size)
71
+ rescue
72
+ end
73
+ end
74
+
75
+ Sorbet::Private::Status.done
76
+ end
77
+
78
+ def self.my_require(abs_path, numerator, denominator)
79
+ rel_path = Pathname.new(abs_path).relative_path_from(Pathname.new(Dir.pwd)).to_s
80
+ Sorbet::Private::Status.say("[#{numerator}/#{denominator}] require_relative './#{rel_path}'")
81
+ require_relative abs_path
82
+ end
83
+
84
+ def self.patch_kernel
85
+ Kernel.send(:define_method, :exit) do |*|
86
+ puts 'Kernel#exit was called while requiring ruby source files'
87
+ raise ExitCalledError.new
88
+ end
89
+
90
+ Kernel.send(:define_method, :at_exit) do |&block|
91
+ if File.split($0).last == 'rake'
92
+ # Let `rake test` work
93
+ super
94
+ return proc {}
95
+ end
96
+ # puts "Ignoring at_exit: #{block}"
97
+ proc {}
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def self.excluded_rails_files
104
+ excluded_paths = Set.new
105
+
106
+ # Exclude files that have already been loaded by rails
107
+ rails = Object.const_get(:Rails)
108
+ load_paths = rails.application.send(:_all_load_paths)
109
+ load_paths.each do |path|
110
+ excluded_paths += Dir.glob("#{path}/**/*.rb")
111
+ end
112
+
113
+ # Exclude initializers, as they have already been run by rails and
114
+ # can contain side-effects like monkey-patching that should
115
+ # only be run once.
116
+ excluded_paths += Dir.glob("#{Dir.pwd}/config/initializers/**/*.rb")
117
+ end
118
+
119
+ def self.rails?
120
+ return false unless File.exist?('config/application.rb')
121
+ begin
122
+ require 'rails'
123
+ rescue LoadError
124
+ return false
125
+ end
126
+ true
127
+ end
128
+ end
data/lib/serialize.rb ADDED
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ require 'bigdecimal'
5
+
6
+ class Sorbet::Private::Serialize
7
+ BLACKLIST_CONSTANTS = [
8
+ ['DidYouMean', :NameErrorCheckers], # https://github.com/yuki24/did_you_mean/commit/b72fdbe194401f1be21f8ad7b6e3f784a0ad197d
9
+ ['Net', :OpenSSL], # https://github.com/yuki24/did_you_mean/commit/b72fdbe194401f1be21f8ad7b6e3f784a0ad197d
10
+ ]
11
+
12
+ SPECIAL_METHOD_NAMES = %w[! ~ +@ ** -@ * / % + - << >> & | ^ < <= => > >= == === != =~ !~ <=> [] []= `]
13
+
14
+ def initialize(constant_cache)
15
+ @constant_cache = constant_cache
16
+ end
17
+
18
+ private def get_constants(mod, inherited=nil)
19
+ @real_constants ||= Module.instance_method(:constants)
20
+ if inherited.nil?
21
+ @real_constants.bind(mod).call
22
+ else
23
+ @real_constants.bind(mod).call(inherited)
24
+ end
25
+ end
26
+
27
+ def self.header(typed="true", subcommand="update")
28
+ buffer = []
29
+ buffer << "# This file is autogenerated. Do not edit it by hand. Regenerate it with:"
30
+ buffer << "# srb rbi #{subcommand}"
31
+ if typed
32
+ buffer << ""
33
+ buffer << "# typed: #{typed}"
34
+ end
35
+ buffer << ""
36
+ buffer.join("\n")
37
+ end
38
+
39
+ def class_or_module(class_name)
40
+ if !valid_class_name(class_name)
41
+ return " # Skipping serializing #{class_name} because it is an invalid name\n"
42
+ end
43
+
44
+ klass = @constant_cache.class_by_name(class_name)
45
+ is_nil = nil.equal?(klass)
46
+ raise "#{class_name} is not a Class or Module. Maybe it was miscategorized?" if is_nil
47
+
48
+ ret = String.new
49
+
50
+ superclass = Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? Sorbet::Private::RealStdlib.real_superclass(klass) : nil
51
+ if superclass
52
+ superclass_str = Sorbet::Private::RealStdlib.real_eqeq(superclass, Object) ? '' : @constant_cache.name_by_class(superclass)
53
+ else
54
+ superclass_str = ''
55
+ end
56
+ superclass_str = !superclass_str || superclass_str.empty? ? '' : " < #{superclass_str}"
57
+ ret << (Sorbet::Private::RealStdlib.real_is_a?(klass, Class) ? "class #{class_name}#{superclass_str}\n" : "module #{class_name}\n")
58
+ ret << " extend ::T::Sig\n"
59
+
60
+ # We don't use .included_modules since that also has all the aweful things
61
+ # that are mixed into Object. This way we at least have a delimiter before
62
+ # the awefulness starts (the superclass).
63
+ Sorbet::Private::RealStdlib.real_ancestors(klass).each do |ancestor|
64
+ next if Sorbet::Private::RealStdlib.real_eqeq(ancestor, klass)
65
+ break if Sorbet::Private::RealStdlib.real_eqeq(ancestor, superclass)
66
+ ancestor_name = @constant_cache.name_by_class(ancestor)
67
+ next unless ancestor_name
68
+ next if ancestor_name == class_name
69
+ if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class)
70
+ ret << " # Skipping `include #{ancestor_name}` because it is a Class\n"
71
+ next
72
+ end
73
+ if !valid_class_name(ancestor_name)
74
+ ret << " # Skipping `include #{ancestor_name}` because it is an invalid name\n"
75
+ next
76
+ end
77
+ ret << " include ::#{ancestor_name}\n"
78
+ end
79
+ Sorbet::Private::RealStdlib.real_singleton_class(klass).ancestors.each do |ancestor|
80
+ next if ancestor == Sorbet::Private::RealStdlib.real_singleton_class(klass)
81
+ break if superclass && ancestor == Sorbet::Private::RealStdlib.real_singleton_class(superclass)
82
+ break if ancestor == Module
83
+ break if ancestor == Object
84
+ ancestor_name = @constant_cache.name_by_class(ancestor)
85
+ next unless ancestor_name
86
+ if Sorbet::Private::RealStdlib.real_is_a?(ancestor, Class)
87
+ ret << " # Skipping `extend #{ancestor_name}` because it is a Class\n"
88
+ next
89
+ end
90
+ if !valid_class_name(ancestor_name)
91
+ ret << " # Skipping `extend #{ancestor_name}` because it is an invalid name\n"
92
+ next
93
+ end
94
+ ret << " extend ::#{ancestor_name}\n"
95
+ end
96
+
97
+ constants = []
98
+ # Declare all the type_members and type_templates
99
+ constants += get_constants(klass).uniq.map do |const_sym|
100
+ # We have to not pass `false` because `klass.constants` intentionally is
101
+ # pulling in all the ancestor constants
102
+ next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}")
103
+ begin
104
+ value = klass.const_get(const_sym)
105
+ rescue LoadError, NameError, RuntimeError, ArgumentError
106
+ ret << "# Failed to load #{class_name}::#{const_sym}\n"
107
+ next
108
+ end
109
+ # next if !Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeVariable)
110
+ next if Sorbet::Private::RealStdlib.real_is_a?(value, Module)
111
+ next if !comparable?(value)
112
+ [const_sym, value]
113
+ end
114
+ constants += get_constants(klass, false).uniq.map do |const_sym|
115
+ next if BLACKLIST_CONSTANTS.include?([class_name, const_sym])
116
+ next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{class_name}::#{const_sym}")
117
+ begin
118
+ value = klass.const_get(const_sym, false)
119
+ rescue LoadError, NameError, RuntimeError, ArgumentError
120
+ ret << "# Failed to load #{class_name}::#{const_sym}\n"
121
+ next
122
+ end
123
+ next if Sorbet::Private::RealStdlib.real_is_a?(value, Module)
124
+ next if !comparable?(value)
125
+ [const_sym, value]
126
+ end
127
+ constants_sorted = constants.compact.sort_by do |const_sym, _value|
128
+ const_sym
129
+ end
130
+ constants_uniq = constants_sorted.uniq do |const_sym, _value|
131
+ const_sym.hash
132
+ end
133
+ constants_serialized = constants_uniq.map do |const_sym, value|
134
+ constant(const_sym, value)
135
+ end
136
+ ret << constants_serialized.join("\n")
137
+ ret << "\n\n" if !constants_serialized.empty?
138
+
139
+ methods = []
140
+ instance_methods = Sorbet::Private::RealStdlib.real_instance_methods(klass, false)
141
+ begin
142
+ initialize = klass.instance_method(:initialize)
143
+ rescue
144
+ initialize = nil
145
+ end
146
+ if initialize && initialize.owner == klass
147
+ # This method never apears in the reflection list...
148
+ instance_methods += [:initialize]
149
+ end
150
+ Sorbet::Private::RealStdlib.real_ancestors(klass).reject {|ancestor| @constant_cache.name_by_class(ancestor)}.each do |ancestor|
151
+ instance_methods += ancestor.instance_methods(false)
152
+ end
153
+
154
+ # uniq here is required because we populate additional methos from anonymous superclasses and there
155
+ # might be duplicates
156
+ methods += instance_methods.sort.uniq.map do |method_sym|
157
+ begin
158
+ method = klass.instance_method(method_sym)
159
+ rescue => e
160
+ ret << "# #{e}\n"
161
+ next
162
+ end
163
+ next if blacklisted_method(method)
164
+ next if ancestor_has_method(method, klass)
165
+ serialize_method(method)
166
+ end
167
+ # uniq is not required here, but added to be on the safe side
168
+ methods += Sorbet::Private::RealStdlib.real_singleton_methods(klass, false).sort.uniq.map do |method_sym|
169
+ begin
170
+ method = klass.singleton_method(method_sym)
171
+ rescue => e
172
+ ret << "# #{e}\n"
173
+ next
174
+ end
175
+ next if blacklisted_method(method)
176
+ next if ancestor_has_method(method, Sorbet::Private::RealStdlib.real_singleton_class(klass))
177
+ serialize_method(method, true)
178
+ end
179
+ ret << methods.join("\n")
180
+ ret << "end\n"
181
+
182
+ ret
183
+ end
184
+
185
+ def alias(base, other_name)
186
+ ret = String.new
187
+ ret << "#{other_name} = #{base}"
188
+ ret
189
+ end
190
+
191
+ def comparable?(value)
192
+ return false if Sorbet::Private::RealStdlib.real_is_a?(value, BigDecimal) && value.nan?
193
+ return false if Sorbet::Private::RealStdlib.real_is_a?(value, Float) && value.nan?
194
+ return false if Sorbet::Private::RealStdlib.real_is_a?(value, Complex)
195
+ true
196
+ end
197
+
198
+ def blacklisted_method(method)
199
+ method.name =~ /__validator__[0-9]{8}/ || method.name =~ /.*:.*/
200
+ end
201
+
202
+ def valid_method_name(name)
203
+ return true if SPECIAL_METHOD_NAMES.include?(name)
204
+ return false if name =~ /^\d/
205
+ name =~ /^[[:word:]]+[?!=]?$/
206
+ end
207
+
208
+ def ancestor_has_method(method, klass)
209
+ return false if !Sorbet::Private::RealStdlib.real_is_a?(klass, Class)
210
+ first_ancestor = klass.ancestors.find do |ancestor|
211
+ next if ancestor == klass
212
+ begin
213
+ ancestor.instance_method(method.name)
214
+ rescue NameError
215
+ nil
216
+ end
217
+ end
218
+ return false unless first_ancestor
219
+ first_ancestor.instance_method(method.name).parameters == method.parameters
220
+ end
221
+
222
+ def constant(const, value)
223
+ #if Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeTemplate)
224
+ #" #{const} = type_template"
225
+ #elsif Sorbet::Private::RealStdlib.real_is_a?(value, T::Types::TypeMember)
226
+ #" #{const} = type_member"
227
+ #else
228
+ #" #{const} = ::T.let(nil, ::T.untyped)"
229
+ #end
230
+ if KEYWORDS.include?(const.to_sym)
231
+ return "# Illegal constant name: #{const}"
232
+ end
233
+ " #{const} = ::T.let(nil, ::T.untyped)"
234
+ end
235
+
236
+ def serialize_method(method, static=false, with_sig: true)
237
+ name = method.name.to_s
238
+ ret = String.new
239
+ if !valid_method_name(name)
240
+ ret << "# Illegal method name: #{name}"
241
+ return
242
+ end
243
+ parameters = from_method(method)
244
+ ret << serialize_sig(parameters) if with_sig
245
+ args = parameters.map do |(kind, param_name)|
246
+ to_sig(kind, param_name)
247
+ end.compact.join(', ')
248
+ ret << " def #{static ? 'self.' : ''}#{name}(#{args}); end\n"
249
+ ret
250
+ end
251
+
252
+ def valid_class_name(name)
253
+ name.split("::").each do |piece|
254
+ return false if piece[0].upcase != piece[0]
255
+ end
256
+ return false if [
257
+ 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ClassOverride',
258
+ 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ModuleOverride',
259
+ 'Sorbet::Private::GemGeneratorTracepoint::Tracer::ObjectOverride',
260
+ ].include?(name)
261
+ true
262
+ end
263
+
264
+ def serialize_sig(parameters)
265
+ ret = String.new
266
+ if !parameters.empty?
267
+ ret << " sig do\n"
268
+ ret << " params(\n"
269
+ parameters.each do |(_kind, name)|
270
+ ret << " #{name}: ::T.untyped,\n"
271
+ end
272
+ ret << " )\n"
273
+ ret << " .returns(::T.untyped)\n"
274
+ ret << " end\n"
275
+ else
276
+ ret << " sig {returns(::T.untyped)}\n"
277
+ end
278
+ ret
279
+ end
280
+
281
+ # from https://docs.ruby-lang.org/en/2.4.0/keywords_rdoc.html
282
+ KEYWORDS = [
283
+ :__ENCODING__,
284
+ :__LINE__,
285
+ :__FILE__,
286
+ :BEGIN,
287
+ :END,
288
+ :alias,
289
+ :and,
290
+ :begin,
291
+ :break,
292
+ :case,
293
+ :class,
294
+ :def,
295
+ :defined?,
296
+ :do,
297
+ :else,
298
+ :elsif,
299
+ :end,
300
+ :ensure,
301
+ :false,
302
+ :for,
303
+ :if,
304
+ :in,
305
+ :module,
306
+ :next,
307
+ :nil,
308
+ :not,
309
+ :or,
310
+ :redo,
311
+ :rescue,
312
+ :retry,
313
+ :return,
314
+ :self,
315
+ :super,
316
+ :then,
317
+ :true,
318
+ :undef,
319
+ :unless,
320
+ :until,
321
+ :when,
322
+ :while,
323
+ :yield,
324
+ ]
325
+
326
+ def from_method(method)
327
+ uniq = 0
328
+ method.parameters.map.with_index do |(kind, name), index|
329
+ if !name
330
+ arg_name = method.name.to_s[0...-1]
331
+ if (!KEYWORDS.include?(arg_name.to_sym)) && method.name.to_s.end_with?('=') && arg_name =~ /\A[a-z_][a-z0-9A-Z_]*\Z/ && index == 0
332
+ name = arg_name
333
+ else
334
+ name = '_' + (uniq == 0 ? '' : uniq.to_s)
335
+ uniq += 1
336
+ end
337
+ end
338
+ [kind, name]
339
+ end
340
+ end
341
+
342
+ def to_sig(kind, name)
343
+ case kind
344
+ when :req
345
+ name.to_s
346
+ when :opt
347
+ "#{name}=T.unsafe(nil)"
348
+ when :rest
349
+ "*#{name}"
350
+ when :keyreq
351
+ "#{name}:"
352
+ when :key
353
+ "#{name}: T.unsafe(nil)"
354
+ when :keyrest
355
+ "**#{name}"
356
+ when :block
357
+ "&#{name}"
358
+ end
359
+ end
360
+ end