steep 0.21.0 → 0.27.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/Gemfile +7 -0
  4. data/bin/steep-prof +16 -0
  5. data/lib/steep/ast/types.rb +5 -3
  6. data/lib/steep/ast/types/any.rb +1 -3
  7. data/lib/steep/ast/types/boolean.rb +1 -3
  8. data/lib/steep/ast/types/bot.rb +1 -3
  9. data/lib/steep/ast/types/class.rb +2 -2
  10. data/lib/steep/ast/types/factory.rb +58 -16
  11. data/lib/steep/ast/types/helper.rb +6 -0
  12. data/lib/steep/ast/types/instance.rb +2 -2
  13. data/lib/steep/ast/types/intersection.rb +8 -4
  14. data/lib/steep/ast/types/literal.rb +5 -3
  15. data/lib/steep/ast/types/name.rb +13 -9
  16. data/lib/steep/ast/types/nil.rb +1 -3
  17. data/lib/steep/ast/types/proc.rb +5 -2
  18. data/lib/steep/ast/types/record.rb +9 -4
  19. data/lib/steep/ast/types/self.rb +1 -1
  20. data/lib/steep/ast/types/top.rb +1 -3
  21. data/lib/steep/ast/types/tuple.rb +5 -3
  22. data/lib/steep/ast/types/union.rb +11 -3
  23. data/lib/steep/ast/types/var.rb +2 -2
  24. data/lib/steep/ast/types/void.rb +1 -3
  25. data/lib/steep/drivers/check.rb +4 -0
  26. data/lib/steep/interface/method_type.rb +48 -18
  27. data/lib/steep/interface/substitution.rb +32 -2
  28. data/lib/steep/project/target.rb +17 -3
  29. data/lib/steep/server/base_worker.rb +4 -3
  30. data/lib/steep/server/master.rb +3 -1
  31. data/lib/steep/server/signature_worker.rb +3 -0
  32. data/lib/steep/signature/errors.rb +28 -0
  33. data/lib/steep/signature/validator.rb +11 -1
  34. data/lib/steep/source.rb +2 -1
  35. data/lib/steep/subtyping/check.rb +38 -21
  36. data/lib/steep/type_construction.rb +446 -181
  37. data/lib/steep/type_inference/constant_env.rb +1 -1
  38. data/lib/steep/type_inference/context.rb +8 -0
  39. data/lib/steep/type_inference/context_array.rb +4 -3
  40. data/lib/steep/type_inference/logic.rb +31 -0
  41. data/lib/steep/typing.rb +7 -0
  42. data/lib/steep/version.rb +1 -1
  43. data/smoke/hash/d.rb +1 -1
  44. data/steep.gemspec +1 -8
  45. metadata +5 -88
@@ -27,7 +27,7 @@ module Steep
27
27
  end
28
28
 
29
29
  def free_variables
30
- Set.new
30
+ @fvs ||= Set.new([self])
31
31
  end
32
32
 
33
33
  def level
@@ -26,9 +26,7 @@ module Steep
26
26
  "⟙"
27
27
  end
28
28
 
29
- def free_variables
30
- Set.new
31
- end
29
+ include Helper::NoFreeVariables
32
30
 
33
31
  def level
34
32
  [2]
@@ -30,9 +30,11 @@ module Steep
30
30
  "[#{types.join(", ")}]"
31
31
  end
32
32
 
33
- def free_variables
34
- types.each.with_object(Set.new) do |type, set|
35
- set.merge(type.free_variables)
33
+ def free_variables()
34
+ @fvs ||= Set.new.tap do |set|
35
+ types.each do |type|
36
+ set.merge(type.free_variables)
37
+ end
36
38
  end
37
39
  end
38
40
 
@@ -11,6 +11,9 @@ module Steep
11
11
  end
12
12
 
13
13
  def self.build(types:, location: nil)
14
+ return AST::Types::Bot.new if types.empty?
15
+ return types.first if types.size == 1
16
+
14
17
  types.flat_map do |type|
15
18
  if type.is_a?(Union)
16
19
  type.types
@@ -29,7 +32,10 @@ module Steep
29
32
  type
30
33
  end
31
34
  end.compact.uniq.yield_self do |tys|
32
- if tys.length == 1
35
+ case tys.size
36
+ when 0
37
+ AST::Types::Bot.new
38
+ when 1
33
39
  tys.first
34
40
  else
35
41
  new(types: tys.sort_by(&:hash), location: location)
@@ -58,8 +64,10 @@ module Steep
58
64
  end
59
65
 
60
66
  def free_variables
61
- types.each.with_object(Set.new) do |type, set|
62
- set.merge(type.free_variables)
67
+ @fvs ||= Set.new.tap do |set|
68
+ types.each do |type|
69
+ set.merge(type.free_variables)
70
+ end
63
71
  end
64
72
  end
65
73
 
@@ -44,8 +44,8 @@ module Steep
44
44
  end
45
45
  end
46
46
 
47
- def free_variables
48
- Set.new([name])
47
+ def free_variables()
48
+ @fvs ||= Set.new([name])
49
49
  end
50
50
 
51
51
  def level
@@ -26,9 +26,7 @@ module Steep
26
26
  "void"
27
27
  end
28
28
 
29
- def free_variables
30
- Set.new
31
- end
29
+ include Helper::NoFreeVariables
32
30
 
33
31
  def level
34
32
  [0]
@@ -60,6 +60,10 @@ module Steep
60
60
  Steep.log_error source_file.status.error
61
61
  end
62
62
  end
63
+ when Project::Target::SignatureOtherErrorStatus
64
+ Steep.log_error status.error
65
+ else
66
+ Steep.logger.error { "Unexpected status: #{status.class}" }
63
67
  end
64
68
  end
65
69
  end
@@ -211,10 +211,10 @@ module Steep
211
211
  end
212
212
  end
213
213
 
214
- def free_variables
215
- Set.new.tap do |fvs|
214
+ def free_variables()
215
+ @fvs ||= Set.new.tap do |set|
216
216
  each_type do |type|
217
- fvs.merge type.free_variables
217
+ set.merge(type.free_variables)
218
218
  end
219
219
  end
220
220
  end
@@ -224,14 +224,29 @@ module Steep
224
224
  end
225
225
 
226
226
  def subst(s)
227
- self.class.new(
228
- required: required.map {|t| t.subst(s) },
229
- optional: optional.map {|t| t.subst(s) },
230
- rest: rest&.subst(s),
231
- required_keywords: required_keywords.transform_values {|t| t.subst(s) },
232
- optional_keywords: optional_keywords.transform_values {|t| t.subst(s) },
233
- rest_keywords: rest_keywords&.subst(s)
234
- )
227
+ return self if s.empty?
228
+ return self if empty?
229
+ return self if free_variables.disjoint?(s.domain)
230
+
231
+ rs = required.map {|t| t.subst(s) }
232
+ os = optional.map {|t| t.subst(s) }
233
+ r = rest&.subst(s)
234
+ rk = required_keywords.transform_values {|t| t.subst(s) }
235
+ ok = optional_keywords.transform_values {|t| t.subst(s) }
236
+ k = rest_keywords&.subst(s)
237
+
238
+ if rs == required && os == optional && r == rest && rk == required_keywords && ok == optional_keywords && k == rest_keywords
239
+ self
240
+ else
241
+ self.class.new(
242
+ required: required.map {|t| t.subst(s) },
243
+ optional: optional.map {|t| t.subst(s) },
244
+ rest: rest&.subst(s),
245
+ required_keywords: required_keywords.transform_values {|t| t.subst(s) },
246
+ optional_keywords: optional_keywords.transform_values {|t| t.subst(s) },
247
+ rest_keywords: rest_keywords&.subst(s)
248
+ )
249
+ end
235
250
  end
236
251
 
237
252
  def size
@@ -557,14 +572,19 @@ module Steep
557
572
  end
558
573
 
559
574
  def subst(s)
560
- self.class.new(
561
- type: type.subst(s),
562
- optional: optional
563
- )
575
+ ty = type.subst(s)
576
+ if ty == type
577
+ self
578
+ else
579
+ self.class.new(
580
+ type: ty,
581
+ optional: optional
582
+ )
583
+ end
564
584
  end
565
585
 
566
- def free_variables
567
- type.free_variables
586
+ def free_variables()
587
+ @fvs ||= type.free_variables
568
588
  end
569
589
 
570
590
  def to_s
@@ -617,10 +637,20 @@ module Steep
617
637
  end
618
638
 
619
639
  def free_variables
620
- (params.free_variables + (block&.free_variables || Set.new) + return_type.free_variables) - Set.new(type_params)
640
+ @fvs ||= Set.new.tap do |set|
641
+ set.merge(params.free_variables)
642
+ if block
643
+ set.merge(block.free_variables)
644
+ end
645
+ set.merge(return_type.free_variables)
646
+ set.subtract(type_params)
647
+ end
621
648
  end
622
649
 
623
650
  def subst(s)
651
+ return self if s.empty?
652
+ return self if free_variables.disjoint?(s.domain)
653
+
624
654
  s_ = s.except(type_params)
625
655
 
626
656
  self.class.new(
@@ -26,7 +26,32 @@ module Steep
26
26
  end
27
27
 
28
28
  def self.empty
29
- new(dictionary: {}, instance_type: AST::Types::Instance.new, module_type: AST::Types::Class.new, self_type: AST::Types::Self.new)
29
+ new(dictionary: {},
30
+ instance_type: INSTANCE_TYPE,
31
+ module_type: CLASS_TYPE,
32
+ self_type: SELF_TYPE)
33
+ end
34
+
35
+ def empty?
36
+ dictionary.empty? &&
37
+ instance_type.is_a?(AST::Types::Instance) &&
38
+ module_type.is_a?(AST::Types::Class) &&
39
+ self_type.is_a?(AST::Types::Self)
40
+ end
41
+
42
+ INSTANCE_TYPE = AST::Types::Instance.new
43
+ CLASS_TYPE = AST::Types::Class.new
44
+ SELF_TYPE = AST::Types::Self.new
45
+
46
+ def domain
47
+ set = Set.new
48
+
49
+ set.merge(dictionary.keys)
50
+ set << INSTANCE_TYPE unless instance_type.is_a?(AST::Types::Instance)
51
+ set << CLASS_TYPE unless instance_type.is_a?(AST::Types::Class)
52
+ set << SELF_TYPE unless instance_type.is_a?(AST::Types::Self)
53
+
54
+ set
30
55
  end
31
56
 
32
57
  def to_s
@@ -81,6 +106,11 @@ module Steep
81
106
  raise "Duplicated key on merge!: #{key}, #{a}, #{b}"
82
107
  end
83
108
  end
109
+
110
+ @instance_type = instance_type.subst(s)
111
+ @module_type = module_type.subst(s)
112
+ @self_type = self_type.subst(s)
113
+
84
114
  self
85
115
  end
86
116
 
@@ -92,7 +122,7 @@ module Steep
92
122
  end
93
123
 
94
124
  def add!(v, ty)
95
- merge!(Substitution.new(dictionary: { v => ty }, instance_type: nil, module_type: nil, self_type: nil))
125
+ merge!(Substitution.new(dictionary: { v => ty }, instance_type: instance_type, module_type: module_type, self_type: self_type))
96
126
  end
97
127
  end
98
128
  end
@@ -79,6 +79,7 @@ module Steep
79
79
  def self.test_pattern(patterns, path, ext:)
80
80
  patterns.any? do |pattern|
81
81
  p = pattern.end_with?(File::Separator) ? pattern : pattern + File::Separator
82
+ p.delete_prefix!('./')
82
83
  (path.to_s.start_with?(p) && path.extname == ext) || File.fnmatch(pattern, path.to_s)
83
84
  end
84
85
  end
@@ -160,7 +161,18 @@ module Steep
160
161
  else
161
162
  yield env, check, Time.now
162
163
  end
164
+ rescue RBS::DuplicatedDeclarationError => exn
165
+ @status = SignatureValidationErrorStatus.new(
166
+ errors: [
167
+ Signature::Errors::DuplicatedDefinitionError.new(
168
+ name: exn.name,
169
+ location: exn.decls[0].location
170
+ )
171
+ ],
172
+ timestamp: Time.now
173
+ )
163
174
  rescue => exn
175
+ Steep.log_error exn
164
176
  @status = SignatureOtherErrorStatus.new(error: exn, timestamp: Time.now)
165
177
  end
166
178
  end
@@ -183,8 +195,10 @@ module Steep
183
195
  type_check_sources = []
184
196
 
185
197
  target_sources.each do |file|
186
- if file.type_check(check, timestamp)
187
- type_check_sources << file
198
+ Steep.logger.tagged("path=#{file.path}") do
199
+ if file.type_check(check, timestamp)
200
+ type_check_sources << file
201
+ end
188
202
  end
189
203
  end
190
204
 
@@ -205,7 +219,7 @@ module Steep
205
219
  def errors
206
220
  case status
207
221
  when TypeCheckStatus
208
- source_files.each_value.flat_map(&:errors)
222
+ source_files.each_value.flat_map(&:errors).select { |error | options.error_to_report?(error) }
209
223
  else
210
224
  []
211
225
  end
@@ -12,6 +12,7 @@ module Steep
12
12
  @project = project
13
13
  @reader = reader
14
14
  @writer = writer
15
+ @shutdown = false
15
16
  end
16
17
 
17
18
  def handle_request(request)
@@ -28,7 +29,7 @@ module Steep
28
29
  Steep.logger.formatter.push_tags(*tags)
29
30
  Steep.logger.tagged "background" do
30
31
  while job = queue.pop
31
- handle_job(job)
32
+ handle_job(job) unless @shutdown
32
33
  end
33
34
  end
34
35
  end
@@ -38,11 +39,11 @@ module Steep
38
39
  reader.read do |request|
39
40
  case request[:method]
40
41
  when "shutdown"
41
- # nop
42
+ @shutdown = true
42
43
  when "exit"
43
44
  break
44
45
  else
45
- handle_request(request)
46
+ handle_request(request) unless @shutdown
46
47
  end
47
48
  end
48
49
  ensure
@@ -23,6 +23,7 @@ module Steep
23
23
  @signature_worker = signature_worker
24
24
  @code_workers = code_workers
25
25
  @worker_to_paths = {}
26
+ @shutdown = false
26
27
  end
27
28
 
28
29
  def start
@@ -59,7 +60,7 @@ module Steep
59
60
  end
60
61
 
61
62
  while job = queue.pop
62
- writer.write(job)
63
+ writer.write(job) unless @shutdown
63
64
  end
64
65
 
65
66
  writer.io.close
@@ -154,6 +155,7 @@ module Steep
154
155
 
155
156
  when "shutdown"
156
157
  queue << { id: id, result: nil }
158
+ @shutdown = true
157
159
 
158
160
  when "exit"
159
161
  queue << nil
@@ -112,6 +112,9 @@ module Steep
112
112
  target.signature_files.each_key.with_object({}) do |path, hash|
113
113
  hash[path] = []
114
114
  end
115
+ when Project::Target::SignatureOtherErrorStatus
116
+ Steep.log_error status.error
117
+ {}
115
118
  else
116
119
  Steep.logger.info "Unexpected target status: #{status.class}"
117
120
  {}
@@ -19,6 +19,19 @@ module Steep
19
19
  end
20
20
  end
21
21
 
22
+ class DuplicatedDefinitionError < Base
23
+ attr_reader :name
24
+
25
+ def initialize(name:, location:)
26
+ @name = name
27
+ @location = location
28
+ end
29
+
30
+ def puts(io)
31
+ io.puts "#{loc_to_s}\sDuplicatedDefinitionError: name=#{name}"
32
+ end
33
+ end
34
+
22
35
  class UnknownTypeNameError < Base
23
36
  attr_reader :name
24
37
 
@@ -48,6 +61,21 @@ module Steep
48
61
  io.puts "#{loc_to_s}\tInvalidTypeApplicationError: name=#{name}, expected=[#{params.join(", ")}], actual=[#{args.join(", ")}]"
49
62
  end
50
63
  end
64
+
65
+ class InvalidMethodOverloadError < Base
66
+ attr_reader :class_name
67
+ attr_reader :method_name
68
+
69
+ def initialize(class_name:, method_name:, location:)
70
+ @class_name = class_name
71
+ @method_name = method_name
72
+ @location = location
73
+ end
74
+
75
+ def puts(io)
76
+ io.puts "#{loc_to_s}\tInvalidMethodOverloadError: class_name=#{class_name}, method_name=#{method_name}"
77
+ end
78
+ end
51
79
  end
52
80
  end
53
81
  end
@@ -53,6 +53,7 @@ module Steep
53
53
  validate_decl
54
54
  validate_const
55
55
  validate_global
56
+ validate_alias
56
57
  end
57
58
 
58
59
  def validate_type(type)
@@ -99,6 +100,7 @@ module Steep
99
100
  env.constant_decls.each do |name, entry|
100
101
  rescue_validation_errors do
101
102
  Steep.logger.debug "Validating constant `#{name}`..."
103
+ builder.ensure_namespace!(name.namespace, location: entry.decl.location)
102
104
  validate_type entry.decl.type
103
105
  end
104
106
  end
@@ -117,7 +119,9 @@ module Steep
117
119
  env.alias_decls.each do |name, entry|
118
120
  rescue_validation_errors do
119
121
  Steep.logger.debug "Validating alias `#{name}`..."
120
- validate_type(entry.decl.type)
122
+ builder.expand_alias(name).tap do |type|
123
+ validate_type(type)
124
+ end
121
125
  end
122
126
  end
123
127
  end
@@ -136,6 +140,12 @@ module Steep
136
140
  name: factory.type_name(exn.type_name),
137
141
  location: exn.location
138
142
  )
143
+ rescue RBS::InvalidOverloadMethodError => exn
144
+ @errors << Errors::InvalidMethodOverloadError.new(
145
+ class_name: factory.type_name(exn.type_name),
146
+ method_name: exn.method_name,
147
+ location: exn.members[0].location
148
+ )
139
149
  end
140
150
  end
141
151
  end