sorbet 0.5.5841

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.
@@ -0,0 +1,15 @@
1
+ # typed: true
2
+
3
+ # Write this as an RBI because the gem_loader.rb file itself cannot be typed.
4
+ # By nature, it references constants in other gems in an attempt to load them.
5
+ # They will not load in this project. It's marked as `typed: ignore`, and this
6
+ # file exists so that we can call into gem_loader from typed code.
7
+
8
+ class Sorbet::Private::GemLoader
9
+ sig {params(gem: String).void}
10
+ def self.require_gem(gem)
11
+ end
12
+
13
+ sig {void}
14
+ def self.require_all_gems; end
15
+ end
@@ -0,0 +1,456 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # typed: true
4
+
5
+ class Sorbet; end
6
+ module Sorbet::Private; end
7
+
8
+ require_relative './t'
9
+ require_relative './step_interface'
10
+ require_relative './serialize'
11
+ require_relative './constant_cache'
12
+ require_relative './require_everything'
13
+ require_relative './real_stdlib'
14
+
15
+ require 'fileutils'
16
+ require 'json'
17
+ require 'set'
18
+ require 'tmpdir'
19
+
20
+ class Sorbet::Private::HiddenMethodFinder
21
+ PATH = "sorbet/rbi/hidden-definitions/"
22
+ TMP_PATH = Dir.mktmpdir + "/"
23
+ TMP_RBI = TMP_PATH + "reflection.rbi"
24
+ DIFF_RBI = TMP_PATH + "hidden.rbi.tmp"
25
+ RBI_CONSTANTS = TMP_PATH + "reflection.json"
26
+ RBI_CONSTANTS_ERR = RBI_CONSTANTS + ".err"
27
+ SOURCE_CONSTANTS = TMP_PATH + "from-source.json"
28
+ SOURCE_CONSTANTS_ERR = SOURCE_CONSTANTS + ".err"
29
+
30
+ HIDDEN_RBI = PATH + "hidden.rbi"
31
+ ERRORS_RBI = PATH + "errors.txt"
32
+
33
+ HEADER = Sorbet::Private::Serialize.header('autogenerated', 'hidden-definitions')
34
+
35
+ include Sorbet::Private::StepInterface
36
+
37
+ def self.main
38
+ self.new.main
39
+ end
40
+
41
+ def main
42
+ mk_dir
43
+ require_everything
44
+ classes, aliases = all_modules_and_aliases
45
+ gen_source_rbi(classes, aliases)
46
+ rm_rbis
47
+ write_constants
48
+ source, rbi = read_constants
49
+ write_diff(source, rbi)
50
+ split_rbi
51
+ rm_dir
52
+ end
53
+
54
+ def mk_dir
55
+ FileUtils.mkdir_p(PATH) unless Dir.exist?(PATH)
56
+ end
57
+
58
+ def rm_dir
59
+ FileUtils.rm_r(TMP_PATH)
60
+ end
61
+
62
+ def require_everything
63
+ puts "Requiring all of your code"
64
+ Sorbet::Private::RequireEverything.require_everything
65
+ end
66
+
67
+ def constant_cache
68
+ @cache ||= Sorbet::Private::ConstantLookupCache.new
69
+ @cache
70
+ end
71
+
72
+
73
+ def all_modules_and_aliases
74
+ puts "Naming all Modules"
75
+ [constant_cache.all_module_names.sort, constant_cache.all_module_aliases]
76
+ end
77
+
78
+ def real_name(mod)
79
+ constant_cache.name_by_class(mod)
80
+ end
81
+
82
+ def gen_source_rbi(classes, aliases)
83
+ puts "Generating #{TMP_RBI} with #{classes.count} modules and #{aliases.count} aliases"
84
+ serializer = Sorbet::Private::Serialize.new(constant_cache)
85
+ buffer = []
86
+ buffer << Sorbet::Private::Serialize.header
87
+
88
+ # should we do something with these errors?
89
+ capture_stderr do
90
+ classes.each do |class_name|
91
+ buffer << serializer.class_or_module(class_name)
92
+ end
93
+ aliases.each do |base, other_names|
94
+ other_names.each do |other_name|
95
+ buffer << serializer.alias(base, other_name)
96
+ end
97
+ end
98
+ end
99
+ File.write(TMP_RBI, buffer.join("\n"))
100
+ end
101
+
102
+ def write_constants
103
+ puts "Printing your code's symbol table into #{SOURCE_CONSTANTS}"
104
+ io = IO.popen(
105
+ [
106
+ File.realpath("#{__dir__}/../bin/srb"),
107
+ 'tc',
108
+ '--print=symbol-table-full-json',
109
+ '--stdout-hup-hack',
110
+ '--silence-dev-message',
111
+ '--no-error-count',
112
+ '-e', # this is additive with any files / dirs
113
+ '""',
114
+ ],
115
+ err: SOURCE_CONSTANTS_ERR
116
+ )
117
+ File.write(SOURCE_CONSTANTS, io.read)
118
+ io.close
119
+ raise "Your source can't be read by Sorbet.\nYou can try `find . -type f | xargs -L 1 -t bundle exec srb tc --no-config --error-white-list 1000` and hopefully the last file it is processing before it dies is the culprit.\nIf not, maybe the errors in this file will help: #{SOURCE_CONSTANTS_ERR}" if File.read(SOURCE_CONSTANTS).empty?
120
+
121
+ puts "Printing #{TMP_RBI}'s symbol table into #{RBI_CONSTANTS}"
122
+ io = IO.popen(
123
+ [
124
+ File.realpath("#{__dir__}/../bin/srb"),
125
+ 'tc',
126
+ # Make sure we don't load a sorbet/config in your cwd
127
+ '--no-config',
128
+ '--print=symbol-table-full-json',
129
+ # The hidden-definition serializer is not smart enough to put T::Enum
130
+ # constants it discovers inside an `enums do` block. We probably want
131
+ # to come up with a better long term solution here.
132
+ '--error-black-list=3506',
133
+ # Method redefined with mismatched argument is ok since sometime
134
+ # people monkeypatch over method
135
+ '--error-black-list=4010',
136
+ # Redefining constant is needed because we serialize things both as
137
+ # aliases and in-class constants.
138
+ '--error-black-list=4012',
139
+ # Invalid nesting is ok because we don't generate all the intermediate
140
+ # namespaces for aliases
141
+ '--error-black-list=4015',
142
+ '--stdout-hup-hack',
143
+ '--silence-dev-message',
144
+ '--no-error-count',
145
+ TMP_RBI,
146
+ ],
147
+ err: RBI_CONSTANTS_ERR
148
+ )
149
+ File.write(RBI_CONSTANTS, io.read)
150
+ io.close
151
+ raise "#{TMP_RBI} had unexpected errors. Check this file for a clue: #{RBI_CONSTANTS_ERR}" unless $?.success?
152
+ end
153
+
154
+ def read_constants
155
+ puts "Reading #{SOURCE_CONSTANTS}"
156
+ source = JSON.parse(File.read(SOURCE_CONSTANTS))
157
+ puts "Reading #{RBI_CONSTANTS}"
158
+ rbi = JSON.parse(File.read(RBI_CONSTANTS))
159
+ [source, rbi]
160
+ end
161
+
162
+ def write_diff(source, rbi)
163
+ puts "Building rbi id to symbol map"
164
+ rbi_symbols = symbols_id_to_name(rbi, '')
165
+ puts "Building source id to symbol map"
166
+ source_symbols = symbols_id_to_name(source, '')
167
+ puts "Writing #{DIFF_RBI}"
168
+ diff = serialize_constants(
169
+ source.fetch("children", []),
170
+ rbi.fetch("children", []),
171
+ Object, false, source_symbols, rbi_symbols)
172
+ File.write(DIFF_RBI, diff)
173
+ end
174
+
175
+ def symbols_id_to_name(entry, prefix)
176
+ ret = {}
177
+ symbols_id_to_name_real(entry, prefix, ret)
178
+ ret
179
+ end
180
+
181
+ private def symbols_id_to_name_real(entry, prefix, ret)
182
+ name = entry["name"]["name"]
183
+ if prefix == '' || prefix == "<root>"
184
+ fqn = name.to_s
185
+ else
186
+ fqn = "#{prefix}::#{name}"
187
+ end
188
+
189
+ ret[entry["id"]] = fqn
190
+ entry.fetch("children", []).each do |child|
191
+ symbols_id_to_name_real(child, fqn, ret)
192
+ end
193
+ end
194
+
195
+ def serialize_constants(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
196
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
197
+ ret = []
198
+
199
+ rbi.each do |rbi_entry|
200
+ # skip duplicated constant fields
201
+ next if rbi_entry["name"]["kind"] == "UNIQUE" and rbi_entry["name"]["unique"] == "MANGLE_RENAME"
202
+
203
+ source_entry = source_by_name[rbi_entry["name"]["name"]]
204
+
205
+ ret << serialize_alias(source_entry, rbi_entry, klass, source_symbols, rbi_symbols)
206
+ ret << serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
207
+ end
208
+
209
+ ret.compact.join("\n")
210
+ end
211
+
212
+ def serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
213
+ return if rbi_entry["kind"] != "CLASS_OR_MODULE"
214
+
215
+ name = rbi_entry["name"]["name"]
216
+ if name.start_with?('<Class:')
217
+ name = name.sub('<Class:', '').sub('>', '')
218
+ my_klass_is_singleton = true
219
+ else
220
+ my_klass_is_singleton = false
221
+ end
222
+ begin
223
+ my_klass = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
224
+ rescue LoadError, NameError, ArgumentError => e
225
+ return "# #{e.message.gsub("\n", "\n# ")}"
226
+ end
227
+
228
+ return if !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) && !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Module)
229
+
230
+ # We specifically don't typecheck anything in T:: since it is hardcoded
231
+ # into sorbet. We don't include anything in Sorbet::Private:: because
232
+ # it's private.
233
+ return if ['T', 'Sorbet::Private'].include?(real_name(my_klass))
234
+
235
+ source_type = nil
236
+ if !source_entry
237
+ if source_by_name[name]
238
+ source_type = source_by_name[name]["kind"]
239
+ end
240
+ else
241
+ source_type = source_entry["kind"]
242
+ end
243
+ if source_type && source_type != "CLASS_OR_MODULE"
244
+ return "# The source says #{real_name(my_klass)} is a #{source_type} but reflection says it is a #{rbi_entry['kind']}"
245
+ end
246
+
247
+ if !source_entry
248
+ source_children = []
249
+ source_mixins = []
250
+ is_stub = true
251
+ else
252
+ source_children = source_entry.fetch("children", [])
253
+ source_mixins = source_entry.fetch("mixins", [])
254
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
255
+ end
256
+ rbi_children = rbi_entry.fetch("children", [])
257
+ rbi_mixins = rbi_entry.fetch("mixins", [])
258
+
259
+ methods = serialize_methods(source_children, rbi_children, my_klass, my_klass_is_singleton)
260
+ includes = serialize_includes(source_mixins, rbi_mixins, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
261
+ values = serialize_values(source_children, rbi_children, my_klass, source_symbols)
262
+
263
+ ret = []
264
+ if !without_errors(methods).empty? || !without_errors(includes).empty? || !without_errors(values).empty? || is_stub
265
+ fqn = real_name(my_klass)
266
+ if fqn
267
+ klass_str = String.new
268
+ klass_str << (Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) ? "class #{fqn}\n" : "module #{fqn}\n")
269
+ klass_str << includes.join("\n")
270
+ klass_str << "\n" unless klass_str.end_with?("\n")
271
+ klass_str << methods.join("\n")
272
+ klass_str << "\n" unless klass_str.end_with?("\n")
273
+ klass_str << values.join("\n")
274
+ klass_str << "\n" unless klass_str.end_with?("\n")
275
+ klass_str << "end\n"
276
+ ret << klass_str
277
+ end
278
+ end
279
+
280
+ children = serialize_constants(source_children, rbi_children, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
281
+ if children != ""
282
+ ret << children
283
+ end
284
+
285
+ ret.empty? ? nil : ret.join("\n")
286
+ end
287
+
288
+ private def without_errors(lines)
289
+ lines.reject {|line| line.start_with?("#")}
290
+ end
291
+
292
+ def serialize_alias(source_entry, rbi_entry, my_klass, source_symbols, rbi_symbols)
293
+ return if rbi_entry["kind"] != "STATIC_FIELD"
294
+ return if source_entry == rbi_entry
295
+ if source_entry
296
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
297
+ if !is_stub
298
+ return
299
+ end
300
+ end
301
+ return if !rbi_entry["aliasTo"]
302
+
303
+ fqn = rbi_symbols[rbi_entry["id"]]
304
+ other_fqn = rbi_symbols[rbi_entry["aliasTo"]]
305
+ return if looks_like_stub_name(fqn)
306
+ ret = String.new
307
+ ret << "#{fqn} = #{other_fqn}\n"
308
+ return ret
309
+ end
310
+
311
+ def looks_like_stub_name(name)
312
+ name.include?('$')
313
+ end
314
+
315
+ private def serialize_values(source, rbi, klass, source_symbols)
316
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
317
+ ret = []
318
+ rbi.each do |rbi_entry|
319
+ name = rbi_entry["name"]["name"]
320
+ source_entry = source_by_name[name]
321
+ if source_entry
322
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
323
+ next unless is_stub
324
+ end
325
+ next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{Sorbet::Private::RealStdlib.real_name(klass)}::#{name}")
326
+ begin
327
+ my_value = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
328
+ rescue StandardError, LoadError => e
329
+ ret << "# #{e.message.gsub("\n", "\n# ")}"
330
+ next
331
+ end
332
+ next if Sorbet::Private::RealStdlib.real_is_a?(my_value, Class) || Sorbet::Private::RealStdlib.real_is_a?(my_value, Module)
333
+ if defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeMember)
334
+ ret << (my_value.variance == :invariant ? " #{name} = type_member" : " #{name} = type_member(#{my_value.variance.inspect})")
335
+ elsif defined?(T::Types) && Sorbet::Private::RealStdlib.real_is_a?(my_value, T::Types::TypeTemplate)
336
+ ret << (my_value.variance == :invariant ? " #{name} = type_template" : " #{name} = type_template(#{my_value.variance.inspect})")
337
+ else
338
+ ret << " #{name} = ::T.let(nil, ::T.untyped)"
339
+ end
340
+ end
341
+ ret
342
+ end
343
+
344
+ # These methods are defined in C++ and we want our C++ definition to
345
+ # win instead of a shim.
346
+ BLACKLIST = Set.new([
347
+ [Class.object_id, "new"],
348
+ [BasicObject.object_id, "initialize"],
349
+ ]).freeze
350
+
351
+ private def serialize_methods(source, rbi, klass, is_singleton)
352
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
353
+ ret = []
354
+ maker = Sorbet::Private::Serialize.new(constant_cache)
355
+ rbi.each do |rbi_entry|
356
+ next if rbi_entry["kind"] != "METHOD"
357
+ name = rbi_entry["name"]["name"]
358
+ next if source_by_name[name]
359
+
360
+ next if BLACKLIST.include?([klass.object_id, name])
361
+ next if name.start_with?('<') && name.end_with?('>')
362
+
363
+ begin
364
+ if is_singleton
365
+ method = klass.singleton_method(name)
366
+ else
367
+ method = klass.instance_method(name)
368
+ end
369
+ rescue => e
370
+ ret << "# #{e.message.gsub("\n", "\n# ")}"
371
+ next
372
+ end
373
+
374
+ super_method = method.super_method
375
+ # next if super_method && T::AbstractUtils.abstract_method?(method) == T::AbstractUtils.abstract_method?(super_method)
376
+
377
+ errors = capture_stderr do
378
+ ret << maker.serialize_method(method, is_singleton, with_sig: false)
379
+ end
380
+ errors.split("\n").each do |line|
381
+ ret << "# #{line}"
382
+ end
383
+ end
384
+
385
+ ret
386
+ end
387
+
388
+ private def serialize_includes(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
389
+ ret = []
390
+ source_mixins = source.map {|id| source_symbols[id]}
391
+ rbi_mixins = rbi.map {|id| rbi_symbols[id]}
392
+ rbi_mixins.each do |rbi_mixin|
393
+ if !source_mixins.include?(rbi_mixin)
394
+ keyword = is_singleton ? "extend" : "include"
395
+ ret << " #{keyword} ::#{rbi_mixin}"
396
+ end
397
+ end
398
+ ret
399
+ end
400
+
401
+ def capture_stderr
402
+ real_stderr = $stderr
403
+ $stderr = StringIO.new
404
+ yield
405
+ $stderr.string
406
+ ensure
407
+ $stderr = real_stderr
408
+ end
409
+
410
+ private def rm_rbis
411
+ File.delete(HIDDEN_RBI) if File.exist?(HIDDEN_RBI)
412
+ File.delete(ERRORS_RBI) if File.exist?(ERRORS_RBI)
413
+ end
414
+
415
+ private def split_rbi
416
+ puts "Generating split RBIs into #{PATH}"
417
+ output = {
418
+ hidden: String.new,
419
+ errors: String.new,
420
+ }
421
+
422
+ valid = File.read(DIFF_RBI)
423
+ cur_output = T.let(nil, T.untyped)
424
+
425
+ valid.split("\n").each do |line|
426
+ category = categorize(line)
427
+ if category == :errors
428
+ # Don't ever switch to errors output permanantly
429
+ output[category] << line + "\n"
430
+ next
431
+ end
432
+ if !category.nil?
433
+ cur_output = output[category]
434
+ end
435
+ cur_output << line + "\n"
436
+ end
437
+
438
+ File.write(HIDDEN_RBI, HEADER + "\n" + output[:hidden])
439
+ File.write(ERRORS_RBI, HEADER + "\n" + output[:errors])
440
+ end
441
+
442
+ private def categorize(line)
443
+ if line.start_with?('#')
444
+ return :errors
445
+ end
446
+ return :hidden
447
+ end
448
+
449
+ def self.output_file
450
+ PATH
451
+ end
452
+ end
453
+
454
+ if $PROGRAM_NAME == __FILE__
455
+ Sorbet::Private::HiddenMethodFinder.main
456
+ end