steep 0.25.0 → 0.31.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -0
  3. data/bin/smoke_runner.rb +3 -4
  4. data/bin/steep-prof +1 -2
  5. data/lib/steep.rb +6 -4
  6. data/lib/steep/annotation_parser.rb +2 -4
  7. data/lib/steep/ast/builtin.rb +11 -21
  8. data/lib/steep/ast/types.rb +5 -3
  9. data/lib/steep/ast/types/any.rb +1 -3
  10. data/lib/steep/ast/types/boolean.rb +1 -3
  11. data/lib/steep/ast/types/bot.rb +1 -3
  12. data/lib/steep/ast/types/class.rb +2 -2
  13. data/lib/steep/ast/types/factory.rb +265 -90
  14. data/lib/steep/ast/types/helper.rb +6 -0
  15. data/lib/steep/ast/types/instance.rb +2 -2
  16. data/lib/steep/ast/types/intersection.rb +20 -13
  17. data/lib/steep/ast/types/literal.rb +1 -3
  18. data/lib/steep/ast/types/logic.rb +63 -0
  19. data/lib/steep/ast/types/name.rb +15 -67
  20. data/lib/steep/ast/types/nil.rb +1 -3
  21. data/lib/steep/ast/types/proc.rb +5 -2
  22. data/lib/steep/ast/types/record.rb +9 -4
  23. data/lib/steep/ast/types/self.rb +1 -1
  24. data/lib/steep/ast/types/top.rb +1 -3
  25. data/lib/steep/ast/types/tuple.rb +5 -3
  26. data/lib/steep/ast/types/union.rb +13 -9
  27. data/lib/steep/ast/types/var.rb +2 -2
  28. data/lib/steep/ast/types/void.rb +1 -3
  29. data/lib/steep/errors.rb +14 -0
  30. data/lib/steep/interface/interface.rb +5 -62
  31. data/lib/steep/interface/method_type.rb +394 -93
  32. data/lib/steep/interface/substitution.rb +48 -6
  33. data/lib/steep/module_helper.rb +25 -0
  34. data/lib/steep/project.rb +25 -0
  35. data/lib/steep/project/completion_provider.rb +57 -58
  36. data/lib/steep/project/file_loader.rb +7 -2
  37. data/lib/steep/project/hover_content.rb +92 -83
  38. data/lib/steep/project/signature_file.rb +33 -0
  39. data/lib/steep/project/{file.rb → source_file.rb} +24 -54
  40. data/lib/steep/project/target.rb +31 -12
  41. data/lib/steep/server/base_worker.rb +5 -3
  42. data/lib/steep/server/code_worker.rb +31 -45
  43. data/lib/steep/server/interaction_worker.rb +42 -38
  44. data/lib/steep/server/master.rb +23 -31
  45. data/lib/steep/server/utils.rb +46 -13
  46. data/lib/steep/server/worker_process.rb +4 -2
  47. data/lib/steep/signature/validator.rb +3 -3
  48. data/lib/steep/source.rb +59 -2
  49. data/lib/steep/subtyping/check.rb +36 -50
  50. data/lib/steep/subtyping/constraints.rb +8 -0
  51. data/lib/steep/type_construction.rb +388 -366
  52. data/lib/steep/type_inference/block_params.rb +5 -0
  53. data/lib/steep/type_inference/constant_env.rb +2 -5
  54. data/lib/steep/type_inference/logic_type_interpreter.rb +219 -0
  55. data/lib/steep/type_inference/type_env.rb +2 -2
  56. data/lib/steep/version.rb +1 -1
  57. data/smoke/alias/a.rb +1 -1
  58. data/smoke/case/a.rb +1 -1
  59. data/smoke/hash/d.rb +1 -1
  60. data/smoke/if/a.rb +1 -1
  61. data/smoke/module/a.rb +1 -1
  62. data/smoke/rescue/a.rb +4 -13
  63. data/smoke/toplevel/Steepfile +5 -0
  64. data/smoke/toplevel/a.rb +4 -0
  65. data/smoke/toplevel/a.rbs +3 -0
  66. data/smoke/type_case/a.rb +0 -7
  67. data/steep.gemspec +3 -3
  68. metadata +17 -13
  69. data/lib/steep/ast/method_type.rb +0 -126
  70. data/lib/steep/ast/namespace.rb +0 -80
  71. data/lib/steep/names.rb +0 -86
@@ -14,6 +14,8 @@ module Steep
14
14
  attr_reader :signature_worker
15
15
  attr_reader :code_workers
16
16
 
17
+ include Utils
18
+
17
19
  def initialize(project:, reader:, writer:, interaction_worker:, signature_worker:, code_workers:, queue: Queue.new)
18
20
  @project = project
19
21
  @reader = reader
@@ -23,22 +25,28 @@ module Steep
23
25
  @signature_worker = signature_worker
24
26
  @code_workers = code_workers
25
27
  @worker_to_paths = {}
28
+ @shutdown_request_id = nil
26
29
  end
27
30
 
28
31
  def start
29
- source_paths = project.targets.flat_map {|target| target.source_files.keys }
32
+ source_paths = project.all_source_files
30
33
  bin_size = (source_paths.size / code_workers.size) + 1
31
34
  source_paths.each_slice(bin_size).with_index do |paths, index|
32
35
  register_code_to_worker(paths, worker: code_workers[index])
33
36
  end
34
37
 
38
+ tags = Steep.logger.formatter.current_tags.dup
39
+ tags << "master"
40
+
35
41
  Thread.new do
42
+ Steep.logger.formatter.push_tags(*tags, "from-worker@interaction")
36
43
  interaction_worker.reader.read do |message|
37
44
  process_message_from_worker(message)
38
45
  end
39
46
  end
40
47
 
41
48
  Thread.new do
49
+ Steep.logger.formatter.push_tags(*tags, "from-worker@signature")
42
50
  signature_worker.reader.read do |message|
43
51
  process_message_from_worker(message)
44
52
  end
@@ -46,6 +54,7 @@ module Steep
46
54
 
47
55
  code_workers.each do |worker|
48
56
  Thread.new do
57
+ Steep.logger.formatter.push_tags(*tags, "from-worker@#{worker.name}")
49
58
  worker.reader.read do |message|
50
59
  process_message_from_worker(message)
51
60
  end
@@ -53,13 +62,21 @@ module Steep
53
62
  end
54
63
 
55
64
  Thread.new do
65
+ Steep.logger.formatter.push_tags(*tags, "from-client")
56
66
  reader.read do |request|
57
67
  process_message_from_client(request)
58
68
  end
59
69
  end
60
70
 
61
71
  while job = queue.pop
62
- writer.write(job)
72
+ if @shutdown_request_id
73
+ if job[:id] == @shutdown_request_id
74
+ writer.write(job)
75
+ break
76
+ end
77
+ else
78
+ writer.write(job)
79
+ end
63
80
  end
64
81
 
65
82
  writer.io.close
@@ -89,7 +106,7 @@ module Steep
89
106
  result: LSP::Interface::InitializeResult.new(
90
107
  capabilities: LSP::Interface::ServerCapabilities.new(
91
108
  text_document_sync: LSP::Interface::TextDocumentSyncOptions.new(
92
- change: LSP::Constant::TextDocumentSyncKind::FULL
109
+ change: LSP::Constant::TextDocumentSyncKind::INCREMENTAL
93
110
  ),
94
111
  hover_provider: true,
95
112
  completion_provider: LSP::Interface::CompletionOptions.new(
@@ -104,36 +121,10 @@ module Steep
104
121
  end
105
122
 
106
123
  when "textDocument/didChange"
124
+ update_source(message)
125
+
107
126
  uri = URI.parse(message[:params][:textDocument][:uri])
108
127
  path = project.relative_path(Pathname(uri.path))
109
- text = message[:params][:contentChanges][0][:text]
110
-
111
- project.targets.each do |target|
112
- case
113
- when target.source_file?(path)
114
- if text.empty? && !path.file?
115
- Steep.logger.info { "Deleting source file: #{path}..." }
116
- target.remove_source(path)
117
- else
118
- Steep.logger.info { "Updating source file: #{path}..." }
119
- target.update_source(path, text)
120
- end
121
- when target.possible_source_file?(path)
122
- Steep.logger.info { "Adding source file: #{path}..." }
123
- target.add_source(path, text)
124
- when target.signature_file?(path)
125
- if text.empty? && !path.file?
126
- Steep.logger.info { "Deleting signature file: #{path}..." }
127
- target.remove_signature(path)
128
- else
129
- Steep.logger.info { "Updating signature file: #{path}..." }
130
- target.update_signature(path, text)
131
- end
132
- when target.possible_signature_file?(path)
133
- Steep.logger.info { "Adding signature file: #{path}..." }
134
- target.add_signature(path, text)
135
- end
136
- end
137
128
 
138
129
  unless registered_path?(path)
139
130
  register_code_to_worker [path], worker: least_busy_worker()
@@ -154,6 +145,7 @@ module Steep
154
145
 
155
146
  when "shutdown"
156
147
  queue << { id: id, result: nil }
148
+ @shutdown_request_id = id
157
149
 
158
150
  when "exit"
159
151
  queue << nil
@@ -7,23 +7,56 @@ module Steep
7
7
  project.relative_path(Pathname(uri.path))
8
8
  end
9
9
 
10
+ def apply_change(change, text)
11
+ range = change[:range]
12
+
13
+ if range
14
+ text = text.dup
15
+
16
+ buf = AST::Buffer.new(name: :_, content: text)
17
+
18
+ start_pos = buf.loc_to_pos(range[:start].yield_self {|pos| [pos[:line]+1, pos[:character]] })
19
+ end_pos = buf.loc_to_pos(range[:end].yield_self {|pos| [pos[:line]+1, pos[:character]] })
20
+
21
+ text[start_pos...end_pos] = change[:text]
22
+ text
23
+ else
24
+ change[:text]
25
+ end
26
+ end
27
+
10
28
  def update_source(request)
11
29
  path = source_path(URI.parse(request[:params][:textDocument][:uri]))
12
- text = request[:params][:contentChanges][0][:text]
13
30
  version = request[:params][:textDocument][:version]
31
+ Steep.logger.info { "Updating source: path=#{path}, version=#{version}..." }
32
+
33
+ changes = request[:params][:contentChanges]
34
+
35
+ source_target, signature_targets = project.targets_for_path(path)
36
+
37
+ if source_target
38
+ changes.each do |change|
39
+ case
40
+ when source_target.source_file?(path)
41
+ Steep.logger.debug { "Updating source in #{source_target.name}: path=#{path}" }
42
+ source_target.update_source(path) {|text| apply_change(change, text) }
43
+ when source_target.possible_source_file?(path)
44
+ Steep.logger.debug { "Adding source to #{source_target.name}: path=#{path}" }
45
+ source_target.add_source(path, change[:text])
46
+ end
47
+ end
48
+ end
14
49
 
15
- Steep.logger.debug "Updateing source: path=#{path}, version=#{version}, size=#{text.bytesize}"
16
-
17
- project.targets.each do |target|
18
- case
19
- when target.source_file?(path)
20
- target.update_source path, text
21
- when target.possible_source_file?(path)
22
- target.add_source path, text
23
- when target.signature_file?(path)
24
- target.update_signature path, text
25
- when target.possible_signature_file?(path)
26
- target.add_signature path, text
50
+ signature_targets.each do |target|
51
+ changes.each do |change|
52
+ case
53
+ when target.signature_file?(path)
54
+ Steep.logger.debug { "Updating signature in #{target.name}: path=#{path}" }
55
+ target.update_signature(path) {|text| apply_change(change, text) }
56
+ when target.possible_signature_file?(path)
57
+ Steep.logger.debug { "Adding signature to #{target.name}: path=#{path}" }
58
+ target.add_signature(path, change[:text])
59
+ end
27
60
  end
28
61
  end
29
62
 
@@ -5,13 +5,15 @@ module Steep
5
5
  attr_reader :writer
6
6
  attr_reader :stderr
7
7
 
8
+ attr_reader :name
8
9
  attr_reader :wait_thread
9
10
 
10
- def initialize(reader:, writer:, stderr:, wait_thread:)
11
+ def initialize(reader:, writer:, stderr:, wait_thread:, name:)
11
12
  @reader = reader
12
13
  @writer = writer
13
14
  @stderr = stderr
14
15
  @wait_thread = wait_thread
16
+ @name = name
15
17
  end
16
18
 
17
19
  def self.spawn_worker(type, name:, steepfile:)
@@ -33,7 +35,7 @@ module Steep
33
35
  writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin)
34
36
  reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdout)
35
37
 
36
- new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread)
38
+ new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name)
37
39
  end
38
40
 
39
41
  def self.spawn_code_workers(steepfile:, count: [Etc.nprocessors-3, 1].max)
@@ -130,19 +130,19 @@ module Steep
130
130
  yield
131
131
  rescue RBS::InvalidTypeApplicationError => exn
132
132
  @errors << Errors::InvalidTypeApplicationError.new(
133
- name: factory.type_name(exn.type_name),
133
+ name: exn.type_name,
134
134
  args: exn.args.map {|ty| factory.type(ty) },
135
135
  params: exn.params,
136
136
  location: exn.location
137
137
  )
138
138
  rescue RBS::NoTypeFoundError, RBS::NoSuperclassFoundError, RBS::NoMixinFoundError => exn
139
139
  @errors << Errors::UnknownTypeNameError.new(
140
- name: factory.type_name(exn.type_name),
140
+ name: exn.type_name,
141
141
  location: exn.location
142
142
  )
143
143
  rescue RBS::InvalidOverloadMethodError => exn
144
144
  @errors << Errors::InvalidMethodOverloadError.new(
145
- class_name: factory.type_name(exn.type_name),
145
+ class_name: exn.type_name,
146
146
  method_name: exn.method_name,
147
147
  location: exn.members[0].location
148
148
  )
@@ -38,7 +38,7 @@ module Steep
38
38
  end
39
39
 
40
40
  def self.parser
41
- ::Parser::Ruby25.new(Builder.new).tap do |parser|
41
+ ::Parser::Ruby27.new(Builder.new).tap do |parser|
42
42
  parser.diagnostics.all_errors_are_fatal = true
43
43
  parser.diagnostics.ignore_warnings = true
44
44
  end
@@ -60,7 +60,7 @@ module Steep
60
60
  _, comments, _ = yield_self do
61
61
  buffer = ::Parser::Source::Buffer.new(path.to_s)
62
62
  buffer.source = source_code
63
- parser = ::Parser::Ruby25.new
63
+ parser = ::Parser::Ruby27.new
64
64
 
65
65
  parser.tokenize(buffer)
66
66
  end
@@ -276,6 +276,18 @@ module Steep
276
276
  end
277
277
  end
278
278
 
279
+ def self.map_child_nodes(node)
280
+ children = node.children.map do |child|
281
+ if child.is_a?(::AST::Node)
282
+ yield child
283
+ else
284
+ child
285
+ end
286
+ end
287
+
288
+ node.updated(nil, children)
289
+ end
290
+
279
291
  def annotations(block:, factory:, current_module:)
280
292
  AST::Annotation::Collection.new(
281
293
  annotations: mapping[block.__id__] || [],
@@ -316,5 +328,50 @@ module Steep
316
328
  end
317
329
  end
318
330
  end
331
+
332
+ def self.delete_defs(node, allow_list)
333
+ case node.type
334
+ when :def
335
+ if allow_list.include?(node)
336
+ node
337
+ else
338
+ node.updated(:nil, [])
339
+ end
340
+ when :defs
341
+ if allow_list.include?(node)
342
+ node
343
+ else
344
+ delete_defs(node.children[0], allow_list)
345
+ end
346
+ else
347
+ map_child_nodes(node) do |child|
348
+ delete_defs(child, allow_list)
349
+ end
350
+ end
351
+ end
352
+
353
+ def without_unrelated_defs(line:, column:)
354
+ nodes = find_nodes(line: line, column: column) || []
355
+ defs = Set[].compare_by_identity.merge(nodes.select {|node| node.type == :def || node.type == :defs })
356
+
357
+ node_ = Source.delete_defs(node, defs)
358
+
359
+ Source.new(path: path, node: node_, mapping: mapping)
360
+ end
361
+
362
+ def compact_siblings(node)
363
+ case node
364
+ when :def
365
+ node.updated(:nil, [])
366
+ when :defs
367
+ node.children[0]
368
+ when :class
369
+ node.updated(:class, [node.children[0], node.children[1], nil])
370
+ when :module
371
+ node.updated(:module, [node.children[0], nil])
372
+ else
373
+ node
374
+ end
375
+ end
319
376
  end
320
377
  end
@@ -10,8 +10,7 @@ module Steep
10
10
  end
11
11
 
12
12
  def instance_super_types(type_name, args:)
13
- type_name_1 = factory.type_name_1(type_name)
14
- ancestors = factory.definition_builder.one_instance_ancestors(type_name_1)
13
+ ancestors = factory.definition_builder.one_instance_ancestors(type_name)
15
14
 
16
15
  subst = unless args.empty?
17
16
  args_ = args.map {|type| factory.type_1(type) }
@@ -19,7 +18,7 @@ module Steep
19
18
  end
20
19
 
21
20
  ancestors.each_ancestor.map do |ancestor|
22
- name = factory.type_name(ancestor.name)
21
+ name = ancestor.name
23
22
 
24
23
  case ancestor
25
24
  when RBS::Definition::Ancestor::Instance
@@ -42,9 +41,8 @@ module Steep
42
41
  )
43
42
  end
44
43
  when RBS::Definition::Ancestor::Singleton
45
- AST::Types::Name::Class.new(
44
+ AST::Types::Name::Singleton.new(
46
45
  name: name,
47
- constructor: nil,
48
46
  location: nil
49
47
  )
50
48
  end
@@ -52,11 +50,10 @@ module Steep
52
50
  end
53
51
 
54
52
  def singleton_super_types(type_name)
55
- type_name_1 = factory.type_name_1(type_name)
56
- ancestors = factory.definition_builder.one_singleton_ancestors(type_name_1)
53
+ ancestors = factory.definition_builder.one_singleton_ancestors(type_name)
57
54
 
58
55
  ancestors.each_ancestor.map do |ancestor|
59
- name = factory.type_name(ancestor.name)
56
+ name = ancestor.name
60
57
 
61
58
  case ancestor
62
59
  when RBS::Definition::Ancestor::Instance
@@ -78,9 +75,8 @@ module Steep
78
75
  )
79
76
  end
80
77
  when RBS::Definition::Ancestor::Singleton
81
- AST::Types::Name::Class.new(
78
+ AST::Types::Name::Singleton.new(
82
79
  name: name,
83
- constructor: nil,
84
80
  location: nil
85
81
  )
86
82
  end
@@ -180,6 +176,14 @@ module Steep
180
176
  constraints: constraints
181
177
  )
182
178
 
179
+ when relation.super_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.super_type.name)
180
+ constraints.add(relation.super_type.name, sub_type: relation.sub_type)
181
+ success(constraints: constraints)
182
+
183
+ when relation.sub_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.sub_type.name)
184
+ constraints.add(relation.sub_type.name, super_type: relation.super_type)
185
+ success(constraints: constraints)
186
+
183
187
  when relation.sub_type.is_a?(AST::Types::Union)
184
188
  results = relation.sub_type.types.map do |sub_type|
185
189
  check(Relation.new(sub_type: sub_type, super_type: relation.super_type),
@@ -232,14 +236,6 @@ module Steep
232
236
  results.find(&:failure?)
233
237
  end
234
238
 
235
- when relation.super_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.super_type.name)
236
- constraints.add(relation.super_type.name, sub_type: relation.sub_type)
237
- success(constraints: constraints)
238
-
239
- when relation.sub_type.is_a?(AST::Types::Var) && constraints.unknown?(relation.sub_type.name)
240
- constraints.add(relation.sub_type.name, super_type: relation.super_type)
241
- success(constraints: constraints)
242
-
243
239
  when relation.super_type.is_a?(AST::Types::Var) || relation.sub_type.is_a?(AST::Types::Var)
244
240
  failure(error: Result::Failure::UnknownPairError.new(relation: relation),
245
241
  trace: trace)
@@ -266,7 +262,7 @@ module Steep
266
262
  possible_sub_types = case relation.sub_type
267
263
  when AST::Types::Name::Instance
268
264
  instance_super_types(relation.sub_type.name, args: relation.sub_type.args)
269
- when AST::Types::Name::Class
265
+ when AST::Types::Name::Singleton
270
266
  singleton_super_types(relation.sub_type.name)
271
267
  else
272
268
  []
@@ -302,10 +298,9 @@ module Steep
302
298
  when relation.sub_type.is_a?(AST::Types::Tuple) && relation.super_type.is_a?(AST::Types::Tuple)
303
299
  if relation.sub_type.types.size >= relation.super_type.types.size
304
300
  pairs = relation.sub_type.types.take(relation.super_type.types.size).zip(relation.super_type.types)
305
- results = pairs.flat_map do |t1, t2|
301
+ results = pairs.map do |t1, t2|
306
302
  relation = Relation.new(sub_type: t1, super_type: t2)
307
- [check(relation, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints),
308
- check(relation.flip, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints)]
303
+ check(relation, self_type: self_type, assumption: assumption, trace: trace, constraints: constraints)
309
304
  end
310
305
 
311
306
  if results.all?(&:success?)
@@ -395,12 +390,12 @@ module Steep
395
390
  end
396
391
 
397
392
  def definition_for_type(type)
398
- type_name = factory.type_name_1(type.name)
393
+ type_name = type.name
399
394
 
400
395
  case type
401
396
  when AST::Types::Name::Instance
402
397
  factory.definition_builder.build_instance(type_name)
403
- when AST::Types::Name::Class
398
+ when AST::Types::Name::Singleton
404
399
  factory.definition_builder.build_singleton(type_name)
405
400
  when AST::Types::Name::Interface
406
401
  factory.definition_builder.build_interface(type_name)
@@ -485,11 +480,7 @@ module Steep
485
480
  if sub_type.name == super_type.name && sub_type.args.size == super_type.args.size
486
481
  sub_type.args.zip(super_type.args)
487
482
  end
488
- when sub_type.is_a?(AST::Types::Name::Class) && super_type.is_a?(AST::Types::Name::Class)
489
- if sub_type.name == super_type.name
490
- []
491
- end
492
- when sub_type.is_a?(AST::Types::Name::Module) && super_type.is_a?(AST::Types::Name::Module)
483
+ when sub_type.is_a?(AST::Types::Name::Singleton) && super_type.is_a?(AST::Types::Name::Singleton)
493
484
  if sub_type.name == super_type.name
494
485
  []
495
486
  end
@@ -536,29 +527,24 @@ module Steep
536
527
 
537
528
  def check_method(name, sub_method, super_method, self_type:, assumption:, trace:, constraints:)
538
529
  trace.method name, sub_method, super_method do
539
- case
540
- when sub_method.overload? && super_method.overload?
541
- super_method.types.map do |super_type|
542
- sub_method.types.map do |sub_type|
543
- check_generic_method_type name,
544
- sub_type,
545
- super_type,
546
- self_type: self_type,
547
- assumption: assumption,
548
- trace: trace,
549
- constraints: constraints
550
- end.yield_self do |results|
551
- results.find(&:success?) || results[0]
552
- end
530
+ super_method.method_types.map do |super_type|
531
+ sub_method.method_types.map do |sub_type|
532
+ check_generic_method_type name,
533
+ sub_type,
534
+ super_type,
535
+ self_type: self_type,
536
+ assumption: assumption,
537
+ trace: trace,
538
+ constraints: constraints
553
539
  end.yield_self do |results|
554
- if results.all?(&:success?) || sub_method.incompatible?
555
- success constraints: constraints
556
- else
557
- results.select(&:failure?).last
558
- end
540
+ results.find(&:success?) || results[0]
541
+ end
542
+ end.yield_self do |results|
543
+ if results.all?(&:success?)
544
+ success constraints: constraints
545
+ else
546
+ results.select(&:failure?).last
559
547
  end
560
- else
561
- raise "aaaaaaaaaaaaaa"
562
548
  end
563
549
  end
564
550
  end