steep 0.17.1 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +5 -1
- data/CHANGELOG.md +27 -0
- data/Gemfile +7 -0
- data/bin/steep-prof +16 -0
- data/lib/steep.rb +1 -0
- data/lib/steep/ast/types/factory.rb +15 -12
- data/lib/steep/ast/types/literal.rb +4 -0
- data/lib/steep/project/completion_provider.rb +26 -5
- data/lib/steep/project/dsl.rb +7 -1
- data/lib/steep/project/file.rb +7 -0
- data/lib/steep/project/file_loader.rb +1 -1
- data/lib/steep/project/hover_content.rb +9 -3
- data/lib/steep/project/options.rb +7 -0
- data/lib/steep/project/target.rb +43 -23
- data/lib/steep/server/interaction_worker.rb +7 -6
- data/lib/steep/server/signature_worker.rb +4 -0
- data/lib/steep/signature/errors.rb +28 -0
- data/lib/steep/signature/validator.rb +57 -27
- data/lib/steep/subtyping/check.rb +205 -34
- data/lib/steep/type_construction.rb +170 -63
- data/lib/steep/type_inference/type_env.rb +2 -2
- data/lib/steep/typing.rb +6 -0
- data/lib/steep/version.rb +1 -1
- data/smoke/class/a.rbs +1 -2
- data/smoke/class/f.rb +3 -1
- data/smoke/extension/a.rbs +3 -2
- data/smoke/extension/e.rbs +2 -3
- data/smoke/interface/a.rbs +6 -6
- data/steep.gemspec +1 -8
- metadata +7 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1008a3723d59bab0d00afb4b385ee2752d8679461416eba84e48cc9ae479b0a4
|
4
|
+
data.tar.gz: 58c470fe9d8c619d9d0767a392ab6557b6dc50b007c6c99ff6ad27a79a88ecd4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f1074ca3634f9df53f361f78a4838912cb7420918f72afdb08e49ab9d64bbe97177780a54648526ff3947b36ab2f0fb2fec3fce73ed97ce0b835359c81ff52d
|
7
|
+
data.tar.gz: b1c5b3fb447832fb648a56b4d827592ba0ddd719e384795786e27201a32305ce36a0d93edde61c94a311708599dd7090359bc1f15ae04ef0fa3717bd897cb580
|
data/.github/workflows/ruby.yml
CHANGED
@@ -14,6 +14,10 @@ jobs:
|
|
14
14
|
container_tag:
|
15
15
|
- 2.6.5-bionic
|
16
16
|
- 2.7.0-bionic
|
17
|
+
task:
|
18
|
+
- test
|
19
|
+
- smoke
|
20
|
+
- build
|
17
21
|
container:
|
18
22
|
image: rubylang/ruby:${{ matrix.container_tag }}
|
19
23
|
steps:
|
@@ -24,4 +28,4 @@ jobs:
|
|
24
28
|
gem install bundler
|
25
29
|
bundle install --jobs 4 --retry 3
|
26
30
|
bin/setup
|
27
|
-
bundle exec rake
|
31
|
+
bundle exec rake ${{matrix.task}}
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,33 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.22.0 (2020-08-03)
|
6
|
+
|
7
|
+
* Improve signature validation ([#175](https://github.com/soutaro/steep/pull/175), [#177](https://github.com/soutaro/steep/pull/177))
|
8
|
+
* Fix boolean literal typing ([#172](https://github.com/soutaro/steep/pull/172))
|
9
|
+
* Make exit code success when Steep has unreported type errors ([#171](https://github.com/soutaro/steep/pull/171))
|
10
|
+
* Allow `./` prefix for signature pattern ([#170](https://github.com/soutaro/steep/pull/170))
|
11
|
+
|
12
|
+
## 0.21.0 (2020-07-20)
|
13
|
+
|
14
|
+
* Fix LSP hover ([#168](https://github.com/soutaro/steep/pull/168))
|
15
|
+
* Nominal subtyping ([#167](https://github.com/soutaro/steep/pull/167))
|
16
|
+
|
17
|
+
## 0.20.0 (2020-07-17)
|
18
|
+
|
19
|
+
* Support singleton class definitions ([#166](https://github.com/soutaro/steep/pull/166))
|
20
|
+
|
21
|
+
## 0.19.0 (2020-07-12)
|
22
|
+
|
23
|
+
* Update RBS. ([#157](https://github.com/soutaro/steep/pull/157))
|
24
|
+
* No `initialize` in completion. ([#164](https://github.com/soutaro/steep/pull/164))
|
25
|
+
* Granular typing option setup. ([#163](https://github.com/soutaro/steep/pull/163))
|
26
|
+
|
27
|
+
## 0.18.0 (2020-07-06)
|
28
|
+
|
29
|
+
* Sort result of `Pathname#glob` ([#154](https://github.com/soutaro/steep/pull/154))
|
30
|
+
* Sort methods in LanguageServer to return non-inherited methods first ([#159](https://github.com/soutaro/steep/pull/159))
|
31
|
+
|
5
32
|
## 0.17.1 (2020-06-15)
|
6
33
|
|
7
34
|
* Allow RBS gem to be 0.4 ([#153](https://github.com/soutaro/steep/pull/153))
|
data/Gemfile
CHANGED
@@ -5,3 +5,10 @@ gemspec
|
|
5
5
|
|
6
6
|
gem "with_steep_types", path: "test/gems/with_steep_types"
|
7
7
|
gem "without_steep_types", path: "test/gems/without_steep_types"
|
8
|
+
|
9
|
+
gem "rake"
|
10
|
+
gem "minitest", "~> 5.0"
|
11
|
+
gem "racc", "~> 1.4"
|
12
|
+
gem "minitest-reporters"
|
13
|
+
gem "minitest-hooks"
|
14
|
+
gem "stackprof"
|
data/bin/steep-prof
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "stackprof"
|
4
|
+
|
5
|
+
mode = (ENV["STEEP_STACKPROF_MODE"] || :wall).to_sym
|
6
|
+
out = ENV["STEEP_STACKPROF_OUT"] || "tmp/stackprof-#{mode}-steep.dump"
|
7
|
+
|
8
|
+
def exit(*)
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
STDERR.puts "Running profiler: mode=#{mode}, out=#{out}"
|
14
|
+
StackProf.run(mode: mode, out: out) do
|
15
|
+
load File.join(__dir__, "../exe/steep")
|
16
|
+
end
|
data/lib/steep.rb
CHANGED
@@ -8,6 +8,10 @@ module Steep
|
|
8
8
|
@definition_builder = builder
|
9
9
|
end
|
10
10
|
|
11
|
+
def type_name_resolver
|
12
|
+
@type_name_resolver ||= RBS::TypeNameResolver.from_env(definition_builder.env)
|
13
|
+
end
|
14
|
+
|
11
15
|
def type(type)
|
12
16
|
case type
|
13
17
|
when RBS::Types::Bases::Any
|
@@ -277,8 +281,7 @@ module Steep
|
|
277
281
|
|
278
282
|
def unfold(type_name)
|
279
283
|
type_name_1(type_name).yield_self do |type_name|
|
280
|
-
|
281
|
-
type(definition_builder.env.absolute_type(decl.type, namespace: type_name.namespace))
|
284
|
+
type(definition_builder.expand_alias(type_name))
|
282
285
|
end
|
283
286
|
end
|
284
287
|
|
@@ -331,7 +334,7 @@ module Steep
|
|
331
334
|
method.method_types.map do |type|
|
332
335
|
method_type(type, self_type: self_type) {|ty| ty.subst(subst) }
|
333
336
|
end,
|
334
|
-
incompatible:
|
337
|
+
incompatible: name == :initialize || name == :new
|
335
338
|
)
|
336
339
|
end
|
337
340
|
end
|
@@ -339,8 +342,7 @@ module Steep
|
|
339
342
|
when Name::Interface
|
340
343
|
Interface::Interface.new(type: self_type, private: private).tap do |interface|
|
341
344
|
type_name = type_name_1(type.name)
|
342
|
-
|
343
|
-
definition = definition_builder.build_interface(type_name, decl)
|
345
|
+
definition = definition_builder.build_interface(type_name)
|
344
346
|
|
345
347
|
subst = Interface::Substitution.build(
|
346
348
|
definition.type_params,
|
@@ -363,7 +365,7 @@ module Steep
|
|
363
365
|
definition = definition_builder.build_singleton(type_name_1(type.name))
|
364
366
|
|
365
367
|
instance_type = Name::Instance.new(name: type.name,
|
366
|
-
args: definition.
|
368
|
+
args: definition.type_params.map {Any.new(location: nil)},
|
367
369
|
location: nil)
|
368
370
|
subst = Interface::Substitution.build(
|
369
371
|
[],
|
@@ -543,12 +545,12 @@ module Steep
|
|
543
545
|
|
544
546
|
def module_name?(type_name)
|
545
547
|
name = type_name_1(type_name)
|
546
|
-
env.
|
548
|
+
entry = env.class_decls[name] and entry.is_a?(RBS::Environment::ModuleEntry)
|
547
549
|
end
|
548
550
|
|
549
551
|
def class_name?(type_name)
|
550
552
|
name = type_name_1(type_name)
|
551
|
-
env.
|
553
|
+
entry = env.class_decls[name] and entry.is_a?(RBS::Environment::ClassEntry)
|
552
554
|
end
|
553
555
|
|
554
556
|
def env
|
@@ -556,13 +558,14 @@ module Steep
|
|
556
558
|
end
|
557
559
|
|
558
560
|
def absolute_type(type, namespace:)
|
559
|
-
|
560
|
-
|
561
|
+
absolute_type = type_1(type).map_type_name do |name|
|
562
|
+
absolute_type_name(name, namespace: namespace) || name.absolute!
|
563
|
+
end
|
564
|
+
type(absolute_type)
|
561
565
|
end
|
562
566
|
|
563
567
|
def absolute_type_name(type_name, namespace:)
|
564
|
-
|
565
|
-
namespace: namespace_1(namespace)) {|name| name.absolute! })
|
568
|
+
type_name_resolver.resolve(type_name, context: namespace_1(namespace).ascend)
|
566
569
|
end
|
567
570
|
end
|
568
571
|
end
|
@@ -10,7 +10,15 @@ module Steep
|
|
10
10
|
|
11
11
|
InstanceVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
|
12
12
|
LocalVariableItem = Struct.new(:identifier, :range, :type, keyword_init: true)
|
13
|
-
MethodNameItem = Struct.new(:identifier, :range, :definition, :
|
13
|
+
MethodNameItem = Struct.new(:identifier, :range, :definition, :def_type, :inherited_method, keyword_init: true) do
|
14
|
+
def method_type
|
15
|
+
def_type.type
|
16
|
+
end
|
17
|
+
|
18
|
+
def comment
|
19
|
+
def_type.comment
|
20
|
+
end
|
21
|
+
end
|
14
22
|
|
15
23
|
attr_reader :source_text
|
16
24
|
attr_reader :path
|
@@ -231,20 +239,22 @@ module Steep
|
|
231
239
|
subtyping.factory.definition_builder.build_singleton(type_name)
|
232
240
|
when AST::Types::Name::Interface
|
233
241
|
type_name = subtyping.factory.type_name_1(type.name)
|
234
|
-
|
235
|
-
subtyping.factory.definition_builder.build_interface(type_name, interface)
|
242
|
+
subtyping.factory.definition_builder.build_interface(type_name)
|
236
243
|
end
|
237
244
|
|
238
245
|
if definition
|
239
246
|
definition.methods.each do |name, method|
|
247
|
+
next if disallowed_method?(name)
|
248
|
+
|
240
249
|
if include_private || method.public?
|
241
250
|
if name.to_s.start_with?(prefix)
|
242
251
|
if word_name?(name.to_s)
|
243
|
-
method.
|
252
|
+
method.defs.each do |def_type|
|
244
253
|
items << MethodNameItem.new(identifier: name,
|
245
254
|
range: range,
|
246
255
|
definition: method,
|
247
|
-
|
256
|
+
def_type: def_type,
|
257
|
+
inherited_method: inherited_method?(method, definition))
|
248
258
|
end
|
249
259
|
end
|
250
260
|
end
|
@@ -293,6 +303,17 @@ module Steep
|
|
293
303
|
|
294
304
|
index
|
295
305
|
end
|
306
|
+
|
307
|
+
def inherited_method?(method, definition)
|
308
|
+
method.implemented_in != definition.type_name
|
309
|
+
end
|
310
|
+
|
311
|
+
def disallowed_method?(name)
|
312
|
+
# initialize isn't invoked by developers when creating
|
313
|
+
# instances of new classes, so don't show it as
|
314
|
+
# an LSP option
|
315
|
+
name == :initialize
|
316
|
+
end
|
296
317
|
end
|
297
318
|
end
|
298
319
|
end
|
data/lib/steep/project/dsl.rb
CHANGED
@@ -10,6 +10,7 @@ module Steep
|
|
10
10
|
attr_reader :no_builtin
|
11
11
|
attr_reader :vendor_dir
|
12
12
|
attr_reader :strictness_level
|
13
|
+
attr_reader :typing_option_hash
|
13
14
|
|
14
15
|
def initialize(name, sources: [], libraries: [], signatures: [], ignored_sources: [])
|
15
16
|
@name = name
|
@@ -19,6 +20,7 @@ module Steep
|
|
19
20
|
@ignored_sources = ignored_sources
|
20
21
|
@vendor_dir = nil
|
21
22
|
@strictness_level = :default
|
23
|
+
@typing_option_hash = {}
|
22
24
|
end
|
23
25
|
|
24
26
|
def initialize_copy(other)
|
@@ -29,6 +31,7 @@ module Steep
|
|
29
31
|
@ignored_sources = other.ignored_sources.dup
|
30
32
|
@vendor_dir = other.vendor_dir
|
31
33
|
@strictness_level = other.strictness_level
|
34
|
+
@typing_option_hash = other.typing_option_hash
|
32
35
|
end
|
33
36
|
|
34
37
|
def check(*args)
|
@@ -43,8 +46,9 @@ module Steep
|
|
43
46
|
libraries.push(*args)
|
44
47
|
end
|
45
48
|
|
46
|
-
def typing_options(level)
|
49
|
+
def typing_options(level = @strictness_level, **hash)
|
47
50
|
@strictness_level = level
|
51
|
+
@typing_option_hash = hash
|
48
52
|
end
|
49
53
|
|
50
54
|
def signature(*args)
|
@@ -128,6 +132,8 @@ module Steep
|
|
128
132
|
options.apply_lenient_typing_options!
|
129
133
|
end
|
130
134
|
|
135
|
+
options.merge!(target.typing_option_hash)
|
136
|
+
|
131
137
|
case target.vendor_dir
|
132
138
|
when Array
|
133
139
|
options.vendored_stdlib_path = target.vendor_dir[0]
|
data/lib/steep/project/file.rb
CHANGED
@@ -95,6 +95,13 @@ module Steep
|
|
95
95
|
source: source,
|
96
96
|
timestamp: Time.now
|
97
97
|
)
|
98
|
+
rescue RBS::NoTypeFoundError,
|
99
|
+
RBS::NoMixinFoundError,
|
100
|
+
RBS::NoSuperclassFoundError,
|
101
|
+
RBS::DuplicatedMethodDefinitionError,
|
102
|
+
RBS::InvalidTypeApplicationError => exn
|
103
|
+
# Skip logging known signature errors (they are handled with load_signatures(validate: true))
|
104
|
+
@status = TypeCheckErrorStatus.new(error: exn)
|
98
105
|
rescue => exn
|
99
106
|
Steep.log_error(exn)
|
100
107
|
@status = TypeCheckErrorStatus.new(error: exn)
|
@@ -4,7 +4,13 @@ module Steep
|
|
4
4
|
TypeContent = Struct.new(:node, :type, :location, keyword_init: true)
|
5
5
|
VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
|
6
6
|
MethodCallContent = Struct.new(:node, :method_name, :type, :definition, :location, keyword_init: true)
|
7
|
-
DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true)
|
7
|
+
DefinitionContent = Struct.new(:node, :method_name, :method_type, :definition, :location, keyword_init: true) do
|
8
|
+
def comment_string
|
9
|
+
if comments = definition&.comments
|
10
|
+
comments.map {|c| c.string.chomp }.uniq.join("\n----\n")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
8
14
|
|
9
15
|
InstanceMethodName = Struct.new(:class_name, :method_name)
|
10
16
|
SingletonMethodName = Struct.new(:class_name, :method_name)
|
@@ -80,7 +86,7 @@ module Steep
|
|
80
86
|
when AST::Types::Name::Instance
|
81
87
|
method_definition = method_definition_for(factory, receiver_type.name, instance_method: method_name)
|
82
88
|
if method_definition&.defined_in
|
83
|
-
owner_name = factory.type_name(method_definition.defined_in
|
89
|
+
owner_name = factory.type_name(method_definition.defined_in)
|
84
90
|
[
|
85
91
|
InstanceMethodName.new(owner_name, method_name),
|
86
92
|
method_definition
|
@@ -89,7 +95,7 @@ module Steep
|
|
89
95
|
when AST::Types::Name::Class
|
90
96
|
method_definition = method_definition_for(factory, receiver_type.name, singleton_method: method_name)
|
91
97
|
if method_definition&.defined_in
|
92
|
-
owner_name = factory.type_name(method_definition.defined_in
|
98
|
+
owner_name = factory.type_name(method_definition.defined_in)
|
93
99
|
[
|
94
100
|
SingletonMethodName.new(owner_name, method_name),
|
95
101
|
method_definition
|
@@ -52,6 +52,13 @@ module Steep
|
|
52
52
|
true
|
53
53
|
end
|
54
54
|
end
|
55
|
+
|
56
|
+
def merge!(hash)
|
57
|
+
self.allow_fallback_any = hash[:allow_fallback_any] if hash.key?(:allow_fallback_any)
|
58
|
+
self.allow_missing_definitions = hash[:allow_missing_definitions] if hash.key?(:allow_missing_definitions)
|
59
|
+
self.allow_unknown_constant_assignment = hash[:allow_unknown_constant_assignment] if hash.key?(:allow_unknown_constant_assignment)
|
60
|
+
self.allow_unknown_method_calls = hash[:allow_unknown_method_calls] if hash.key?(:allow_unknown_method_calls)
|
61
|
+
end
|
55
62
|
end
|
56
63
|
end
|
57
64
|
end
|
data/lib/steep/project/target.rb
CHANGED
@@ -15,6 +15,7 @@ module Steep
|
|
15
15
|
|
16
16
|
SignatureSyntaxErrorStatus = Struct.new(:timestamp, :errors, keyword_init: true)
|
17
17
|
SignatureValidationErrorStatus = Struct.new(:timestamp, :errors, keyword_init: true)
|
18
|
+
SignatureOtherErrorStatus = Struct.new(:timestamp, :error, keyword_init: true)
|
18
19
|
TypeCheckStatus = Struct.new(:environment, :subtyping, :type_check_sources, :timestamp, keyword_init: true)
|
19
20
|
|
20
21
|
def initialize(name:, options:, source_patterns:, ignore_patterns:, signature_patterns:)
|
@@ -78,6 +79,7 @@ module Steep
|
|
78
79
|
def self.test_pattern(patterns, path, ext:)
|
79
80
|
patterns.any? do |pattern|
|
80
81
|
p = pattern.end_with?(File::Separator) ? pattern : pattern + File::Separator
|
82
|
+
p.delete_prefix!('./')
|
81
83
|
(path.to_s.start_with?(p) && path.extname == ext) || File.fnmatch(pattern, path.to_s)
|
82
84
|
end
|
83
85
|
end
|
@@ -95,7 +97,7 @@ module Steep
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def environment
|
98
|
-
@environment ||= RBS::Environment.new().
|
100
|
+
@environment ||= RBS::Environment.new().yield_self do |env|
|
99
101
|
stdlib_root = options.vendored_stdlib_path || RBS::EnvironmentLoader::STDLIB_ROOT
|
100
102
|
gem_vendor_path = options.vendored_gems_path
|
101
103
|
loader = RBS::EnvironmentLoader.new(stdlib_root: stdlib_root, gem_vendor_path: gem_vendor_path)
|
@@ -103,6 +105,8 @@ module Steep
|
|
103
105
|
loader.add(library: lib)
|
104
106
|
end
|
105
107
|
loader.load(env: env)
|
108
|
+
|
109
|
+
env.resolve_type_names
|
106
110
|
end
|
107
111
|
end
|
108
112
|
|
@@ -125,34 +129,50 @@ module Steep
|
|
125
129
|
if status.is_a?(TypeCheckStatus) && updated_files.empty?
|
126
130
|
yield status.environment, status.subtyping, status.timestamp
|
127
131
|
else
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
file.status.
|
133
|
-
|
132
|
+
begin
|
133
|
+
env = environment.dup
|
134
|
+
|
135
|
+
signature_files.each_value do |file|
|
136
|
+
if file.status.is_a?(SignatureFile::DeclarationsStatus)
|
137
|
+
file.status.declarations.each do |decl|
|
138
|
+
env << decl
|
139
|
+
end
|
134
140
|
end
|
135
141
|
end
|
136
|
-
end
|
137
142
|
|
138
|
-
|
139
|
-
factory = AST::Types::Factory.new(builder: definition_builder)
|
140
|
-
check = Subtyping::Check.new(factory: factory)
|
143
|
+
env = env.resolve_type_names
|
141
144
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
+
definition_builder = RBS::DefinitionBuilder.new(env: env)
|
146
|
+
factory = AST::Types::Factory.new(builder: definition_builder)
|
147
|
+
check = Subtyping::Check.new(factory: factory)
|
145
148
|
|
146
|
-
if
|
147
|
-
|
149
|
+
if validate
|
150
|
+
validator = Signature::Validator.new(checker: check)
|
151
|
+
validator.validate()
|
152
|
+
|
153
|
+
if validator.no_error?
|
154
|
+
yield env, check, Time.now
|
155
|
+
else
|
156
|
+
@status = SignatureValidationErrorStatus.new(
|
157
|
+
errors: validator.each_error.to_a,
|
158
|
+
timestamp: Time.now
|
159
|
+
)
|
160
|
+
end
|
148
161
|
else
|
149
|
-
|
150
|
-
errors: validator.each_error.to_a,
|
151
|
-
timestamp: Time.now
|
152
|
-
)
|
162
|
+
yield env, check, Time.now
|
153
163
|
end
|
154
|
-
|
155
|
-
|
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
|
+
)
|
174
|
+
rescue => exn
|
175
|
+
@status = SignatureOtherErrorStatus.new(error: exn, timestamp: Time.now)
|
156
176
|
end
|
157
177
|
end
|
158
178
|
|
@@ -196,7 +216,7 @@ module Steep
|
|
196
216
|
def errors
|
197
217
|
case status
|
198
218
|
when TypeCheckStatus
|
199
|
-
source_files.each_value.flat_map(&:errors)
|
219
|
+
source_files.each_value.flat_map(&:errors).select { |error | options.error_to_report?(error) }
|
200
220
|
else
|
201
221
|
[]
|
202
222
|
end
|