steep 0.1.0.pre

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/README.md +95 -0
  6. data/Rakefile +23 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/bin/smoke_runner.rb +106 -0
  10. data/exe/steep +18 -0
  11. data/lib/steep.rb +33 -0
  12. data/lib/steep/annotation.rb +223 -0
  13. data/lib/steep/cli.rb +79 -0
  14. data/lib/steep/drivers/check.rb +141 -0
  15. data/lib/steep/errors.rb +207 -0
  16. data/lib/steep/interface.rb +280 -0
  17. data/lib/steep/parser.y +311 -0
  18. data/lib/steep/signature/class.rb +358 -0
  19. data/lib/steep/signature/errors.rb +78 -0
  20. data/lib/steep/signature/extension.rb +51 -0
  21. data/lib/steep/signature/interface.rb +48 -0
  22. data/lib/steep/source.rb +98 -0
  23. data/lib/steep/type_assignability.rb +362 -0
  24. data/lib/steep/type_construction.rb +993 -0
  25. data/lib/steep/type_name.rb +37 -0
  26. data/lib/steep/types.rb +4 -0
  27. data/lib/steep/types/any.rb +31 -0
  28. data/lib/steep/types/class.rb +27 -0
  29. data/lib/steep/types/instance.rb +27 -0
  30. data/lib/steep/types/merge.rb +32 -0
  31. data/lib/steep/types/name.rb +57 -0
  32. data/lib/steep/types/union.rb +42 -0
  33. data/lib/steep/types/var.rb +38 -0
  34. data/lib/steep/typing.rb +70 -0
  35. data/lib/steep/version.rb +3 -0
  36. data/manual/annotations.md +144 -0
  37. data/sig/signature.rbi +54 -0
  38. data/sig/types.rbi +43 -0
  39. data/smoke/and/a.rb +9 -0
  40. data/smoke/array/a.rb +22 -0
  41. data/smoke/block/a.rb +12 -0
  42. data/smoke/block/a.rbi +4 -0
  43. data/smoke/case/a.rb +20 -0
  44. data/smoke/class/a.rb +31 -0
  45. data/smoke/class/a.rbi +9 -0
  46. data/smoke/class/b.rb +7 -0
  47. data/smoke/class/c.rb +10 -0
  48. data/smoke/const/a.rb +30 -0
  49. data/smoke/dstr/a.rb +6 -0
  50. data/smoke/extension/a.rb +11 -0
  51. data/smoke/extension/a.rbi +8 -0
  52. data/smoke/extension/b.rb +12 -0
  53. data/smoke/extension/c.rb +9 -0
  54. data/smoke/hello/hello.rb +13 -0
  55. data/smoke/hello/hello.rbi +7 -0
  56. data/smoke/if/a.rb +20 -0
  57. data/smoke/implements/a.rb +14 -0
  58. data/smoke/implements/a.rbi +6 -0
  59. data/smoke/literal/a.rb +16 -0
  60. data/smoke/map/a.rb +5 -0
  61. data/smoke/method/a.rb +26 -0
  62. data/smoke/method/a.rbi +0 -0
  63. data/smoke/module/a.rb +21 -0
  64. data/smoke/module/a.rbi +7 -0
  65. data/smoke/module/b.rb +8 -0
  66. data/smoke/self/a.rb +23 -0
  67. data/smoke/self/a.rbi +4 -0
  68. data/smoke/super/a.rb +34 -0
  69. data/smoke/super/a.rbi +10 -0
  70. data/smoke/yield/a.rb +18 -0
  71. data/stdlib/builtin.rbi +89 -0
  72. data/steep.gemspec +32 -0
  73. metadata +214 -0
@@ -0,0 +1,78 @@
1
+ module Steep
2
+ module Signature
3
+ module Errors
4
+ class Base
5
+ # @implements Steep__Signature__Error
6
+
7
+ # @dynamic signature
8
+ attr_reader :signature
9
+
10
+ def initialize(signature:)
11
+ @signature = signature
12
+ end
13
+ end
14
+
15
+ class UnknownTypeName < Base
16
+ # @implements Steep__Signature__Errors__UnknownTypeName
17
+
18
+ # @dynamic type
19
+ attr_reader :type
20
+
21
+ def initialize(signature:, type:)
22
+ super(signature: signature)
23
+ @type = type
24
+ end
25
+ end
26
+
27
+ class IncompatibleOverride < Base
28
+ # @implements Steep__Signature__Errors__IncompatibleOverride
29
+
30
+ # @dynamic method_name
31
+ attr_reader :method_name
32
+ # @dynamic this_method
33
+ attr_reader :this_method
34
+ # @dynamic super_method
35
+ attr_reader :super_method
36
+
37
+ def initialize(signature:, method_name:, this_method:, super_method:)
38
+ super(signature: signature)
39
+ @method_name = method_name
40
+ @this_method = this_method
41
+ @super_method = super_method
42
+ end
43
+ end
44
+
45
+ class InvalidTypeApplication < Base
46
+ attr_reader :type_name
47
+ attr_reader :type_args
48
+
49
+ def initialize(signature:, type_name:, type_args:)
50
+ super(signature: signature)
51
+ @type_name = type_name
52
+ @type_args = type_args
53
+ end
54
+ end
55
+
56
+ class InvalidSelfType < Base
57
+ attr_reader :member
58
+
59
+ def initialize(signature:, member:)
60
+ super(signature: signature)
61
+ @member = member
62
+ end
63
+ end
64
+
65
+ class UnexpectedTypeNameKind < Base
66
+ attr_reader :type
67
+ attr_reader :expected_kind
68
+
69
+ def initialize(signature:, type:, expected_kind:)
70
+ super(signature: signature)
71
+ @type = type
72
+ @expected_kind = expected_kind
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
@@ -0,0 +1,51 @@
1
+ module Steep
2
+ module Signature
3
+ class Extension
4
+ # @implements Steep__Signature__Extension
5
+
6
+ def initialize(module_name:, extension_name:, members:)
7
+ @module_name = module_name
8
+ @extension_name = extension_name
9
+ @members = members
10
+ end
11
+
12
+ def module_name; @module_name; end
13
+ def extension_name; @extension_name; end
14
+ def members; @members; end
15
+
16
+ def ==(other)
17
+ # @type var other_: Steep__Signature__Extension
18
+ other_ = other
19
+ other.is_a?(self.class) &&
20
+ other_.module_name == module_name &&
21
+ other_.extension_name == extension_name &&
22
+ other_.members == members
23
+ end
24
+
25
+ def name
26
+ :"#{module_name} (#{extension_name})"
27
+ end
28
+
29
+ include WithMembers
30
+ prepend WithMethods
31
+
32
+ def validate(assignability)
33
+ each_type do |type|
34
+ assignability.validate_type_presence self, type
35
+ end
36
+ end
37
+
38
+ def instance_methods(assignability:, klass:, instance:, params:)
39
+ {}
40
+ end
41
+
42
+ def module_methods(assignability:, klass:, instance:, params:)
43
+ {}
44
+ end
45
+
46
+ def type_application_hash(args)
47
+ {}
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ module Steep
2
+ module Signature
3
+ class Interface
4
+ # @implements Steep__Signature__Interface
5
+ # @type const Hash: Hash.class
6
+ # @type const Interface: Steep__Signature__Interface.class
7
+ # @type const Steep::Interface: Steep__Interface.class
8
+ # @type const Steep::Interface::Method: Steep__Method.class
9
+
10
+ # @dynamic name
11
+ attr_reader :name
12
+ # @dynamic params
13
+ attr_reader :params
14
+ # @dynamic methods
15
+ attr_reader :methods
16
+
17
+ def initialize(name:, params:, methods:)
18
+ @name = name
19
+ @params = params
20
+ @methods = methods
21
+ end
22
+
23
+ def ==(other)
24
+ # @type var other_: Steep__Signature__Interface
25
+ other_ = other
26
+ other_.is_a?(Interface) && other_.name == name && other_.params == params && other_.methods == methods
27
+ end
28
+
29
+ def to_interface(klass:, instance:, params:)
30
+ raise "Invalid type application: expected: #{self.params.size}, actual: #{params.size}" if params.size != self.params.size
31
+
32
+ # @type var map: Hash<Symbol, Steep__Type>
33
+ map = Hash[self.params.zip(params)]
34
+ Steep::Interface.new(name: name,
35
+ methods: methods.transform_values {|method|
36
+ types = method.map {|method_type|
37
+ method_type.substitute(klass: klass, instance: instance, params: map)
38
+ }
39
+ Steep::Interface::Method.new(types: types, super_method: nil)
40
+ })
41
+ end
42
+
43
+ def validate(assignability)
44
+
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,98 @@
1
+ module Steep
2
+ class Source
3
+ class LocatedAnnotation
4
+ attr_reader :line
5
+ attr_reader :annotation
6
+ attr_reader :source
7
+
8
+ def initialize(line:, source:, annotation:)
9
+ @line = line
10
+ @source = source
11
+ @annotation = annotation
12
+ end
13
+
14
+ def ==(other)
15
+ other.is_a?(LocatedAnnotation) &&
16
+ other.line == line &&
17
+ other.annotation == annotation
18
+ end
19
+ end
20
+
21
+ attr_reader :path
22
+ attr_reader :node
23
+ attr_reader :mapping
24
+
25
+ def initialize(path:, node:, mapping:)
26
+ @path = path
27
+ @node = node
28
+ @mapping = mapping
29
+ end
30
+
31
+ def self.parse(source_code, path:, labeling: ASTUtils::Labeling.new)
32
+ node = labeling.translate(::Parser::CurrentRuby.parse(source_code, path.to_s), {})
33
+
34
+ annotations = []
35
+
36
+ buffer = ::Parser::Source::Buffer.new(path.to_s)
37
+ buffer.source = source_code
38
+ parser = ::Parser::CurrentRuby.new
39
+
40
+ _, comments, _ = parser.tokenize(buffer)
41
+ comments.each do |comment|
42
+ src = comment.text.gsub(/\A#\s*/, '')
43
+ annotation = Steep::Parser.parse_annotation_opt(src)
44
+ if annotation
45
+ annotations << LocatedAnnotation.new(line: comment.location.line, source: src, annotation: annotation)
46
+ end
47
+ end
48
+
49
+ mapping = {}
50
+ construct_mapping(node: node, annotations: annotations, mapping: mapping)
51
+
52
+ annotations.each do |annot|
53
+ mapping[node.__id__] = [] unless mapping.key?(node.__id__)
54
+ mapping[node.__id__] << annot.annotation
55
+ end
56
+
57
+ new(path: path, node: node, mapping: mapping)
58
+ end
59
+
60
+ def self.construct_mapping(node:, annotations:, mapping:)
61
+ each_child_node(node) do |child|
62
+ construct_mapping(node: child, annotations: annotations, mapping: mapping)
63
+ end
64
+
65
+ case node.type
66
+ when :def, :block, :module, :class
67
+ start_line = node.loc.line
68
+ end_line = node.loc.last_line
69
+
70
+ consumed = []
71
+
72
+ annotations.each do |annot|
73
+ if start_line <= annot.line && annot.line < end_line
74
+ consumed << annot
75
+ mapping[node.__id__] = [] unless mapping.key?(node.__id__)
76
+ mapping[node.__id__] << annot.annotation
77
+ end
78
+ end
79
+
80
+ consumed.each do |annot|
81
+ annotations.delete annot
82
+ end
83
+ end
84
+ end
85
+
86
+ def self.each_child_node(node)
87
+ node.children.each do |child|
88
+ if child.is_a?(AST::Node)
89
+ yield child
90
+ end
91
+ end
92
+ end
93
+
94
+ def annotations(block:)
95
+ Annotation::Collection.new(annotations: mapping[block.__id__] || [])
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,362 @@
1
+ module Steep
2
+ class TypeAssignability
3
+ attr_reader :signatures
4
+ attr_reader :errors
5
+
6
+ def initialize()
7
+ @signatures = {}
8
+ @klasses = []
9
+ @instances = []
10
+ @errors = []
11
+
12
+ if block_given?
13
+ yield self
14
+ validate
15
+ end
16
+ end
17
+
18
+ def with(klass: nil, instance: nil, &block)
19
+ @klasses.push(klass) if klass
20
+ @instances.push(instance) if instance
21
+ yield
22
+ ensure
23
+ @klasses.pop if klass
24
+ @instances.pop if instance
25
+ end
26
+
27
+ def klass
28
+ @klasses.last
29
+ end
30
+
31
+ def instance
32
+ @instances.last
33
+ end
34
+
35
+ def add_signature(signature)
36
+ raise "Signature Duplicated: #{signature.name}" if signatures.key?(signature.name)
37
+ signatures[signature.name] = signature
38
+ end
39
+
40
+ def test(src:, dest:, known_pairs: [])
41
+ case
42
+ when src.is_a?(Types::Any) || dest.is_a?(Types::Any)
43
+ true
44
+ when src == dest
45
+ true
46
+ when src.is_a?(Types::Union)
47
+ src.types.all? do |type|
48
+ test(src: type, dest: dest, known_pairs: known_pairs)
49
+ end
50
+ when dest.is_a?(Types::Union)
51
+ dest.types.any? do |type|
52
+ test(src: src, dest: type, known_pairs: known_pairs)
53
+ end
54
+ when src.is_a?(Types::Var) || dest.is_a?(Types::Var)
55
+ known_pairs.include?([src, dest])
56
+ when src.is_a?(Types::Name) && dest.is_a?(Types::Name)
57
+ test_interface(resolve_interface(src.name, src.params), resolve_interface(dest.name, dest.params), known_pairs)
58
+ else
59
+ raise "Unexpected type: src=#{src.inspect}, dest=#{dest.inspect}, known_pairs=#{known_pairs.inspect}"
60
+ end
61
+ end
62
+
63
+ def test_application(params:, argument:, index:)
64
+ param_type = params.flat_unnamed_params[index]&.last
65
+ if param_type
66
+ unless test(src: argument, dest: param_type)
67
+ yield param_type
68
+ end
69
+ end
70
+ end
71
+
72
+ def test_interface(src, dest, known_pairs)
73
+ if src.name == dest.name
74
+ return true
75
+ end
76
+
77
+ if known_pairs.include?([src, dest])
78
+ return true
79
+ end
80
+
81
+ pairs = known_pairs + [[src, dest]]
82
+
83
+ dest.methods.all? do |name, dest_methods|
84
+ if src.methods.key?(name)
85
+ src_methods = src.methods[name]
86
+
87
+ dest_methods.types.all? do |dest_method|
88
+ src_methods.types.any? do |src_method|
89
+ test_method(src_method, dest_method, pairs)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def test_method(src, dest, known_pairs)
97
+ test_params(src.params, dest.params, known_pairs) &&
98
+ test_block(src.block, dest.block, known_pairs) &&
99
+ test(src: src.return_type, dest: dest.return_type, known_pairs: known_pairs)
100
+ end
101
+
102
+ def test_params(src, dest, known_pairs)
103
+ assigning_pairs = []
104
+
105
+ src_flat = src.flat_unnamed_params
106
+ dest_flat = dest.flat_unnamed_params
107
+
108
+ case
109
+ when dest.rest
110
+ return false unless src.rest
111
+
112
+ while src_flat.size > 0
113
+ src_type = src_flat.shift
114
+ dest_type = dest_flat.shift
115
+
116
+ if dest_type
117
+ assigning_pairs << [src_type.last, dest_type.last]
118
+ else
119
+ assigning_pairs << [src_type.last, dest.rest]
120
+ end
121
+ end
122
+
123
+ if src.rest
124
+ assigning_pairs << [src.rest, dest.rest]
125
+ end
126
+ when src.rest
127
+ while src_flat.size > 0
128
+ src_type = src_flat.shift
129
+ dest_type = dest_flat.shift
130
+
131
+ if dest_type
132
+ assigning_pairs << [src_type.last, dest_type.last]
133
+ else
134
+ break
135
+ end
136
+ end
137
+
138
+ if src.rest && !dest_flat.empty?
139
+ dest_flat.each do |dest_type|
140
+ assigning_pairs << [src.rest, dest_type.last]
141
+ end
142
+ end
143
+ when src.required.size + src.optional.size >= dest.required.size + dest.optional.size && !src.rest
144
+ while src_flat.size > 0
145
+ src_type = src_flat.shift
146
+ dest_type = dest_flat.shift
147
+
148
+ if dest_type
149
+ assigning_pairs << [src_type.last, dest_type.last]
150
+ else
151
+ break
152
+ end
153
+ end
154
+ else
155
+ return false
156
+ end
157
+
158
+ src_flat_kws = src.flat_keywords
159
+ dest_flat_kws = dest.flat_keywords
160
+
161
+ dest_flat_kws.each do |name, _|
162
+ if src_flat_kws.key?(name)
163
+ assigning_pairs << [src_flat_kws[name], dest_flat_kws[name]]
164
+ else
165
+ if src.rest_keywords
166
+ assigning_pairs << [src.rest_keywords, dest_flat_kws[name]]
167
+ else
168
+ return false
169
+ end
170
+ end
171
+ end
172
+
173
+ src.required_keywords.each do |name, _|
174
+ unless dest.required_keywords.key?(name)
175
+ return false
176
+ end
177
+ end
178
+
179
+ if src.rest_keywords && dest.rest_keywords
180
+ assigning_pairs << [src.rest_keywords, dest.rest_keywords]
181
+ end
182
+
183
+ assigning_pairs.all? do |pair|
184
+ src_type = pair.first
185
+ dest_type = pair.last
186
+
187
+ test(src: dest_type, dest: src_type, known_pairs: known_pairs)
188
+ end
189
+ end
190
+
191
+ def test_block(src, dest, known_pairs)
192
+ return true if !src && !dest
193
+ return false if !src || !dest
194
+
195
+ raise "Keyword args for block is not yet supported" unless src.params&.flat_keywords&.empty?
196
+ raise "Keyword args for block is not yet supported" unless dest.params&.flat_keywords&.empty?
197
+
198
+ ss = src.params.flat_unnamed_params
199
+ ds = dest.params.flat_unnamed_params
200
+
201
+ max = ss.size > ds.size ? ss.size : ds.size
202
+
203
+ for i in 0...max
204
+ s = ss[i]&.last || src.params.rest
205
+ d = ds[i]&.last || dest.params.rest
206
+
207
+ if s && d
208
+ test(src: s, dest: d, known_pairs: known_pairs) or return false
209
+ end
210
+ end
211
+
212
+ if src.params.rest && dest.params.rest
213
+ test(src: src.params.rest, dest: dest.params.rest, known_pairs: known_pairs) or return false
214
+ end
215
+
216
+ test(src: dest.return_type, dest: src.return_type, known_pairs: known_pairs)
217
+ end
218
+
219
+ def resolve_interface(name, params, klass: nil, instance: nil)
220
+ klass ||= Types::Name.module(name: name.name, params: params)
221
+ instance ||= Types::Name.instance(name: name.name, params: params)
222
+
223
+ case name
224
+ when TypeName::Interface
225
+ signatures[name.name].to_interface(klass: klass, instance: instance, params: params)
226
+ when TypeName::Instance
227
+ methods = signatures[name.name].instance_methods(assignability: self, klass: klass, instance: instance, params: params)
228
+ Interface.new(name: name, methods: methods)
229
+ when TypeName::Module
230
+ methods = signatures[name.name].module_methods(assignability: self, klass: klass, instance: instance, params: params)
231
+ Interface.new(name: name, methods: methods)
232
+ else
233
+ raise "Unexpected type name: #{name.inspect}"
234
+ end
235
+ end
236
+
237
+ def lookup_included_signature(type)
238
+ raise "#{self.class}#lookup_included_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
239
+ raise "#{self.class}#lookup_included_signature expects module instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)
240
+
241
+ signatures[type.name.name]
242
+ end
243
+
244
+ def lookup_super_class_signature(type)
245
+ raise "#{self.class}#lookup_super_class_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
246
+ raise "#{self.class}#lookup_super_class_signature expects module instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)
247
+
248
+ signature = signatures[type.name.name]
249
+
250
+ raise "#{self.class}#lookup_super_class_signature expects class: #{type.name.inspect}" unless signature.is_a?(Signature::Class)
251
+
252
+ signature
253
+ end
254
+
255
+ def lookup_class_signature(type)
256
+ raise "#{self.class}#lookup_class_signature expects type name: #{type.inspect}" unless type.is_a?(Types::Name)
257
+ raise "#{self.class}#lookup_class_signature expects instance name: #{type.name.inspect}" unless type.name.is_a?(TypeName::Instance)
258
+
259
+ signature = signatures[type.name.name]
260
+
261
+ raise "#{self.class}#lookup_super_class_signature expects class: #{signature.inspect}" unless signature.is_a?(Signature::Class)
262
+
263
+ signature
264
+ end
265
+
266
+ def lookup_extensions(module_name)
267
+ signatures.values.select do |signature|
268
+ case signature
269
+ when Signature::Extension
270
+ signature.module_name == module_name
271
+ end
272
+ end
273
+ end
274
+
275
+ def method_type(type, name)
276
+ case type
277
+ when Types::Any
278
+ return type
279
+ when Types::Merge
280
+ methods = type.types.map {|t|
281
+ resolve_interface(t.name, t.params, klass: Types::Var.new(name: :some_klass), instance: Types::Var.new(name: :some_instance))
282
+ }.each.with_object({}) {|interface, methods|
283
+ methods.merge! interface.methods
284
+ }
285
+ method = methods[name]
286
+ when Types::Name
287
+ interface = resolve_interface(type.name, type.params)
288
+ method = interface.methods[name]
289
+ else
290
+ raise "Unexpected type: #{type}"
291
+ end
292
+
293
+ if method
294
+ yield(method) || Types::Any.new
295
+ else
296
+ yield(nil) || Types::Any.new
297
+ end
298
+ end
299
+
300
+ def validate
301
+ signatures.each do |name, signature|
302
+ signature.validate(self)
303
+ end
304
+ end
305
+
306
+ def validate_type_presence(signature, type)
307
+ if type.is_a?(Types::Name)
308
+ unless signatures[type.name.name]
309
+ errors << Signature::Errors::UnknownTypeName.new(signature: signature, type: type)
310
+ end
311
+ end
312
+ end
313
+
314
+ def validate_method_compatibility(signature, method_name, method)
315
+ if method.super_method
316
+ test = method.types.all? {|method_type|
317
+ method.super_method.types.any? {|super_type|
318
+ test_method(method_type, super_type, [])
319
+ }
320
+ }
321
+
322
+ unless test
323
+ errors << Signature::Errors::IncompatibleOverride.new(signature: signature,
324
+ method_name: method_name,
325
+ this_method: method.types,
326
+ super_method: method.super_method.types)
327
+ end
328
+ end
329
+ end
330
+
331
+ def compact(types)
332
+ types = types.reject {|type| type.is_a?(Types::Any) }
333
+
334
+ if types.empty?
335
+ [Types::Any.new]
336
+ else
337
+ compact0(types)
338
+ end
339
+ end
340
+
341
+ def compact0(types)
342
+ if types.size == 1
343
+ types
344
+ else
345
+ type, *types_ = types
346
+ compacted = compact0(types_)
347
+ compacted.flat_map do |type_|
348
+ case
349
+ when type == type_
350
+ [type]
351
+ when test(src: type_, dest: type)
352
+ [type]
353
+ when test(src: type, dest: type_)
354
+ [type_]
355
+ else
356
+ [type, type_]
357
+ end
358
+ end.uniq
359
+ end
360
+ end
361
+ end
362
+ end