sorbet 0.4.4250 → 0.4.4253
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/bin/srb +117 -0
- data/bin/srb-rbi +232 -0
- data/lib/constant_cache.rb +193 -0
- data/lib/create-config.rb +32 -0
- data/lib/fetch-rbis.rb +127 -0
- data/lib/find-gem-rbis.rb +52 -0
- data/lib/gem-generator-tracepoint.rb +54 -0
- data/lib/gem-generator-tracepoint/tracepoint_serializer.rb +256 -0
- data/lib/gem-generator-tracepoint/tracer.rb +180 -0
- data/lib/gem_loader.rb +579 -0
- data/lib/gem_loader.rbi +15 -0
- data/lib/hidden-definition-finder.rb +441 -0
- data/lib/real_stdlib.rb +79 -0
- data/lib/require_everything.rb +128 -0
- data/lib/serialize.rb +360 -0
- data/lib/status.rb +21 -0
- data/lib/step_interface.rb +11 -0
- data/lib/suggest-typed.rb +41 -0
- data/lib/t.rb +58 -0
- data/lib/todo-rbi.rb +48 -0
- metadata +101 -11
data/lib/gem_loader.rbi
ADDED
@@ -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
|