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,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,441 @@
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-json',
109
+ '--stdout-hup-hack',
110
+ '--silence-dev-message',
111
+ '--no-error-count',
112
+ ],
113
+ err: SOURCE_CONSTANTS_ERR
114
+ )
115
+ File.write(SOURCE_CONSTANTS, io.read)
116
+ io.close
117
+ raise "Your source can't be read by Sorbet.\nYou can try `srb tc --print=symbol-table-json -vvvv --max-threads 1` 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?
118
+
119
+ puts "Printing #{TMP_RBI}'s symbol table into #{RBI_CONSTANTS}"
120
+ # Change dir to deal with you having a sorbet/config in your cwd
121
+ Dir.chdir(TMP_PATH) do
122
+ io = IO.popen(
123
+ [
124
+ File.realpath("#{__dir__}/../bin/srb"),
125
+ 'tc',
126
+ '--print=symbol-table-json',
127
+ # Method redefined with mismatched argument is ok since sometime
128
+ # people monkeypatch over method
129
+ '--error-black-list=4010',
130
+ # Redefining constant is needed because we serialize things both as
131
+ # aliases and in-class constants.
132
+ '--error-black-list=4012',
133
+ # Invalid nesting is ok because we don't generate all the intermediate
134
+ # namespaces for aliases
135
+ '--error-black-list=4015',
136
+ '--stdout-hup-hack',
137
+ '--silence-dev-message',
138
+ '--no-error-count',
139
+ TMP_RBI,
140
+ ],
141
+ err: RBI_CONSTANTS_ERR
142
+ )
143
+ end
144
+ File.write(RBI_CONSTANTS, io.read)
145
+ io.close
146
+ raise "#{TMP_RBI} had unexpected errors. Check this file for a clue: #{RBI_CONSTANTS_ERR}" unless $?.success?
147
+ end
148
+
149
+ def read_constants
150
+ puts "Reading #{SOURCE_CONSTANTS}"
151
+ source = JSON.parse(File.read(SOURCE_CONSTANTS))
152
+ puts "Reading #{RBI_CONSTANTS}"
153
+ rbi = JSON.parse(File.read(RBI_CONSTANTS))
154
+ [source, rbi]
155
+ end
156
+
157
+ def write_diff(source, rbi)
158
+ puts "Building rbi id to symbol map"
159
+ rbi_symbols = symbols_id_to_name(rbi, '')
160
+ puts "Building source id to symbol map"
161
+ source_symbols = symbols_id_to_name(source, '')
162
+ puts "Writing #{DIFF_RBI}"
163
+ diff = serialize_constants(
164
+ source.fetch("children", []),
165
+ rbi.fetch("children", []),
166
+ Object, false, source_symbols, rbi_symbols)
167
+ File.write(DIFF_RBI, diff)
168
+ end
169
+
170
+ def symbols_id_to_name(entry, prefix)
171
+ ret = {}
172
+ symbols_id_to_name_real(entry, prefix, ret)
173
+ ret
174
+ end
175
+
176
+ private def symbols_id_to_name_real(entry, prefix, ret)
177
+ name = entry["name"]["name"]
178
+ if prefix == '' || prefix == "<root>"
179
+ fqn = name.to_s
180
+ else
181
+ fqn = "#{prefix}::#{name}"
182
+ end
183
+
184
+ ret[entry["id"]] = fqn
185
+ entry.fetch("children", []).each do |child|
186
+ symbols_id_to_name_real(child, fqn, ret)
187
+ end
188
+ end
189
+
190
+ def serialize_constants(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
191
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
192
+ ret = []
193
+
194
+ rbi.each do |rbi_entry|
195
+ source_entry = source_by_name[rbi_entry["name"]["name"]]
196
+
197
+ ret << serialize_alias(source_entry, rbi_entry, klass, source_symbols, rbi_symbols)
198
+ ret << serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
199
+ end
200
+
201
+ ret.compact.join("\n")
202
+ end
203
+
204
+ def serialize_class(source_entry, rbi_entry, klass, source_symbols, rbi_symbols, source_by_name)
205
+ return if rbi_entry["kind"] != "CLASS"
206
+
207
+ name = rbi_entry["name"]["name"]
208
+ if name.start_with?('<Class:')
209
+ name = name.sub('<Class:', '').sub('>', '')
210
+ my_klass_is_singleton = true
211
+ else
212
+ my_klass_is_singleton = false
213
+ end
214
+ begin
215
+ my_klass = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
216
+ rescue LoadError, NameError, ArgumentError => e
217
+ return "# #{e.message.gsub("\n", "\n# ")}"
218
+ end
219
+
220
+ return if !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) && !Sorbet::Private::RealStdlib.real_is_a?(my_klass, Module)
221
+
222
+ # We specifically don't typecheck anything in T:: since it is hardcoded
223
+ # into sorbet
224
+ return if real_name(my_klass) == 'T'
225
+
226
+ source_type = nil
227
+ if !source_entry
228
+ if source_by_name[name]
229
+ source_type = source_by_name[name]["kind"]
230
+ end
231
+ else
232
+ source_type = source_entry["kind"]
233
+ end
234
+ if source_type && source_type != "CLASS"
235
+ return "# The source says #{real_name(my_klass)} is a #{source_type} but reflection says it is a #{rbi_entry['kind']}"
236
+ end
237
+
238
+ if !source_entry
239
+ source_children = []
240
+ source_mixins = []
241
+ is_stub = true
242
+ else
243
+ source_children = source_entry.fetch("children", [])
244
+ source_mixins = source_entry.fetch("mixins", [])
245
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
246
+ end
247
+ rbi_children = rbi_entry.fetch("children", [])
248
+ rbi_mixins = rbi_entry.fetch("mixins", [])
249
+
250
+ methods = serialize_methods(source_children, rbi_children, my_klass, my_klass_is_singleton)
251
+ includes = serialize_includes(source_mixins, rbi_mixins, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
252
+ values = serialize_values(source_children, rbi_children, my_klass, source_symbols)
253
+
254
+ ret = []
255
+ if !without_errors(methods).empty? || !without_errors(includes).empty? || !without_errors(values).empty? || is_stub
256
+ fqn = real_name(my_klass)
257
+ if fqn
258
+ klass_str = String.new
259
+ klass_str << (Sorbet::Private::RealStdlib.real_is_a?(my_klass, Class) ? "class #{fqn}\n" : "module #{fqn}\n")
260
+ klass_str << includes.join("\n")
261
+ klass_str << "\n" unless klass_str.end_with?("\n")
262
+ klass_str << methods.join("\n")
263
+ klass_str << "\n" unless klass_str.end_with?("\n")
264
+ klass_str << values.join("\n")
265
+ klass_str << "\n" unless klass_str.end_with?("\n")
266
+ klass_str << "end\n"
267
+ ret << klass_str
268
+ end
269
+ end
270
+
271
+ children = serialize_constants(source_children, rbi_children, my_klass, my_klass_is_singleton, source_symbols, rbi_symbols)
272
+ if children != ""
273
+ ret << children
274
+ end
275
+
276
+ ret.empty? ? nil : ret.join("\n")
277
+ end
278
+
279
+ private def without_errors(lines)
280
+ lines.reject {|line| line.start_with?("#")}
281
+ end
282
+
283
+ def serialize_alias(source_entry, rbi_entry, my_klass, source_symbols, rbi_symbols)
284
+ return if rbi_entry["kind"] != "STATIC_FIELD"
285
+ return if source_entry == rbi_entry
286
+ if source_entry
287
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
288
+ if !is_stub
289
+ return
290
+ end
291
+ end
292
+ return if !rbi_entry["aliasTo"]
293
+
294
+ fqn = rbi_symbols[rbi_entry["id"]]
295
+ other_fqn = rbi_symbols[rbi_entry["aliasTo"]]
296
+ return if looks_like_stub_name(fqn)
297
+ ret = String.new
298
+ ret << "#{fqn} = #{other_fqn}\n"
299
+ return ret
300
+ end
301
+
302
+ def looks_like_stub_name(name)
303
+ name.include?('$')
304
+ end
305
+
306
+ private def serialize_values(source, rbi, klass, source_symbols)
307
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
308
+ ret = []
309
+ rbi.each do |rbi_entry|
310
+ name = rbi_entry["name"]["name"]
311
+ source_entry = source_by_name[name]
312
+ if source_entry
313
+ is_stub = source_entry['superClass'] && source_symbols[source_entry['superClass']] == 'Sorbet::Private::Static::StubModule'
314
+ next unless is_stub
315
+ end
316
+ next if Sorbet::Private::ConstantLookupCache::DEPRECATED_CONSTANTS.include?("#{Sorbet::Private::RealStdlib.real_name(klass)}::#{name}")
317
+ begin
318
+ my_value = klass.const_get(name, false) # rubocop:disable PrisonGuard/NoDynamicConstAccess
319
+ rescue StandardError, LoadError => e
320
+ ret << "# #{e.message.gsub("\n", "\n# ")}"
321
+ next
322
+ end
323
+ next if Sorbet::Private::RealStdlib.real_is_a?(my_value, Class) || Sorbet::Private::RealStdlib.real_is_a?(my_value, Module)
324
+ ret << " #{name} = ::T.let(nil, ::T.untyped)"
325
+ end
326
+ ret
327
+ end
328
+
329
+ # These methods are defined in C++ and we want our C++ definition to
330
+ # win instead of a shim.
331
+ BLACKLIST = Set.new([
332
+ [Class.object_id, "new"],
333
+ [BasicObject.object_id, "initialize"],
334
+ ]).freeze
335
+
336
+ private def serialize_methods(source, rbi, klass, is_singleton)
337
+ source_by_name = source.map {|v| [v["name"]["name"], v]}.to_h
338
+ ret = []
339
+ maker = Sorbet::Private::Serialize.new(constant_cache)
340
+ rbi.each do |rbi_entry|
341
+ next if rbi_entry["kind"] != "METHOD"
342
+ name = rbi_entry["name"]["name"]
343
+ next if source_by_name[name]
344
+
345
+ next if BLACKLIST.include?([klass.object_id, name])
346
+ next if name.start_with?('<') && name.end_with?('>')
347
+
348
+ begin
349
+ if is_singleton
350
+ method = klass.singleton_method(name)
351
+ else
352
+ method = klass.instance_method(name)
353
+ end
354
+ rescue => e
355
+ ret << "# #{e.message.gsub("\n", "\n# ")}"
356
+ next
357
+ end
358
+
359
+ super_method = method.super_method
360
+ # next if super_method && T::AbstractUtils.abstract_method?(method) == T::AbstractUtils.abstract_method?(super_method)
361
+
362
+ errors = capture_stderr do
363
+ ret << maker.serialize_method(method, is_singleton, with_sig: false)
364
+ end
365
+ errors.split("\n").each do |line|
366
+ ret << "# #{line}"
367
+ end
368
+ end
369
+
370
+ ret
371
+ end
372
+
373
+ private def serialize_includes(source, rbi, klass, is_singleton, source_symbols, rbi_symbols)
374
+ ret = []
375
+ source_mixins = source.map {|id| source_symbols[id]}
376
+ rbi_mixins = rbi.map {|id| rbi_symbols[id]}
377
+ rbi_mixins.each do |rbi_mixin|
378
+ if !source_mixins.include?(rbi_mixin)
379
+ keyword = is_singleton ? "extend" : "include"
380
+ ret << " #{keyword} ::#{rbi_mixin}"
381
+ end
382
+ end
383
+ ret
384
+ end
385
+
386
+ def capture_stderr
387
+ real_stderr = $stderr
388
+ $stderr = StringIO.new
389
+ yield
390
+ $stderr.string
391
+ ensure
392
+ $stderr = real_stderr
393
+ end
394
+
395
+ private def rm_rbis
396
+ File.delete(HIDDEN_RBI) if File.exist?(HIDDEN_RBI)
397
+ File.delete(ERRORS_RBI) if File.exist?(ERRORS_RBI)
398
+ end
399
+
400
+ private def split_rbi
401
+ puts "Generating split RBIs into #{PATH}"
402
+ output = {
403
+ hidden: String.new,
404
+ errors: String.new,
405
+ }
406
+
407
+ valid = File.read(DIFF_RBI)
408
+ cur_output = T.let(nil, T.untyped)
409
+
410
+ valid.split("\n").each do |line|
411
+ category = categorize(line)
412
+ if category == :errors
413
+ # Don't ever switch to errors output permanantly
414
+ output[category] << line + "\n"
415
+ next
416
+ end
417
+ if !category.nil?
418
+ cur_output = output[category]
419
+ end
420
+ cur_output << line + "\n"
421
+ end
422
+
423
+ File.write(HIDDEN_RBI, HEADER + "\n" + output[:hidden])
424
+ File.write(ERRORS_RBI, HEADER + "\n" + output[:errors])
425
+ end
426
+
427
+ private def categorize(line)
428
+ if line.start_with?('#')
429
+ return :errors
430
+ end
431
+ return :hidden
432
+ end
433
+
434
+ def self.output_file
435
+ PATH
436
+ end
437
+ end
438
+
439
+ if $PROGRAM_NAME == __FILE__
440
+ Sorbet::Private::HiddenMethodFinder.main
441
+ end