rbs 2.8.1 → 3.0.0.dev.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/comments.yml +1 -1
  3. data/.github/workflows/ruby.yml +9 -6
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +16 -16
  6. data/ext/rbs_extension/constants.c +2 -0
  7. data/ext/rbs_extension/constants.h +1 -0
  8. data/ext/rbs_extension/parser.c +23 -13
  9. data/ext/rbs_extension/ruby_objs.c +15 -3
  10. data/ext/rbs_extension/ruby_objs.h +2 -1
  11. data/lib/rbs/ast/members.rb +49 -15
  12. data/lib/rbs/cli.rb +6 -1
  13. data/lib/rbs/collection/config/lockfile.rb +115 -0
  14. data/lib/rbs/collection/config/lockfile_generator.rb +89 -48
  15. data/lib/rbs/collection/config.rb +11 -39
  16. data/lib/rbs/collection/installer.rb +9 -13
  17. data/lib/rbs/collection/sources/base.rb +2 -2
  18. data/lib/rbs/collection/sources/git.rb +135 -62
  19. data/lib/rbs/collection/sources/rubygems.rb +10 -12
  20. data/lib/rbs/collection/sources/stdlib.rb +10 -13
  21. data/lib/rbs/collection/sources.rb +7 -1
  22. data/lib/rbs/collection.rb +1 -0
  23. data/lib/rbs/definition.rb +1 -1
  24. data/lib/rbs/definition_builder/method_builder.rb +3 -3
  25. data/lib/rbs/definition_builder.rb +449 -572
  26. data/lib/rbs/environment.rb +5 -3
  27. data/lib/rbs/environment_loader.rb +11 -10
  28. data/lib/rbs/locator.rb +2 -2
  29. data/lib/rbs/prototype/helpers.rb +29 -13
  30. data/lib/rbs/prototype/node_usage.rb +99 -0
  31. data/lib/rbs/prototype/rb.rb +3 -2
  32. data/lib/rbs/prototype/rbi.rb +6 -4
  33. data/lib/rbs/prototype/runtime.rb +25 -12
  34. data/lib/rbs/substitution.rb +19 -0
  35. data/lib/rbs/types.rb +1 -5
  36. data/lib/rbs/validator.rb +2 -1
  37. data/lib/rbs/version.rb +1 -1
  38. data/lib/rbs/writer.rb +26 -17
  39. data/lib/rbs.rb +1 -0
  40. data/lib/rdoc_plugin/parser.rb +1 -1
  41. data/schema/members.json +15 -10
  42. data/sig/collection/config/lockfile.rbs +80 -0
  43. data/sig/collection/config/lockfile_generator.rbs +55 -0
  44. data/sig/collection/config.rbs +5 -48
  45. data/sig/collection/installer.rbs +1 -1
  46. data/sig/collection/sources.rbs +66 -29
  47. data/sig/definition_builder.rbs +94 -81
  48. data/sig/environment_loader.rbs +1 -1
  49. data/sig/errors.rbs +21 -0
  50. data/sig/members.rbs +31 -7
  51. data/sig/prototype/node_usage.rbs +20 -0
  52. data/sig/shims/bundler.rbs +13 -0
  53. data/sig/shims/rubygems.rbs +9 -0
  54. data/sig/shims.rbs +0 -22
  55. data/sig/substitution.rbs +6 -0
  56. data/sig/writer.rbs +2 -0
  57. data/stdlib/yaml/0/yaml.rbs +1 -1
  58. metadata +11 -4
@@ -365,11 +365,13 @@ module RBS
365
365
  AST::Members::MethodDefinition.new(
366
366
  name: member.name,
367
367
  kind: member.kind,
368
- types: member.types.map do |type|
369
- resolve_method_type(resolver, type, context: context)
368
+ overloads: member.overloads.map do |overload|
369
+ overload.update(
370
+ method_type: resolve_method_type(resolver, overload.method_type, context: context)
371
+ )
370
372
  end,
371
373
  comment: member.comment,
372
- overload: member.overload?,
374
+ overloading: member.overloading?,
373
375
  annotations: member.annotations,
374
376
  location: member.location,
375
377
  visibility: member.visibility
@@ -60,25 +60,26 @@ module RBS
60
60
 
61
61
  def resolve_dependencies(library:, version:)
62
62
  [Collection::Sources::Rubygems.instance, Collection::Sources::Stdlib.instance].each do |source|
63
- # @type var gem: { 'name' => String, 'version' => String? }
64
- gem = { 'name' => library, 'version' => version }
65
- next unless source.has?(gem)
63
+ next unless source.has?(library, version)
66
64
 
67
- gem['version'] ||= source.versions(gem).last
68
- source.dependencies_of(gem)&.each do |dep|
65
+ unless version
66
+ version = source.versions(library).last or raise
67
+ end
68
+
69
+ source.dependencies_of(library, version)&.each do |dep|
69
70
  add(library: dep['name'], version: nil)
70
71
  end
71
72
  return
72
73
  end
73
74
  end
74
75
 
75
- def add_collection(collection_config)
76
- collection_config.check_rbs_availability!
76
+ def add_collection(lockfile)
77
+ lockfile.check_rbs_availability!
77
78
 
78
- repository.add(collection_config.repo_path)
79
+ repository.add(lockfile.fullpath)
79
80
 
80
- collection_config.gems.each do |gem|
81
- add(library: gem['name'], version: gem['version'], resolve_dependencies: false)
81
+ lockfile.gems.each_value do |gem|
82
+ add(library: gem[:name], version: gem[:version], resolve_dependencies: false)
82
83
  end
83
84
  end
84
85
 
data/lib/rbs/locator.rb CHANGED
@@ -113,8 +113,8 @@ module RBS
113
113
 
114
114
  case member
115
115
  when AST::Members::MethodDefinition
116
- member.types.each do |method_type|
117
- find_in_method_type(pos, array: array, method_type: method_type) and return true
116
+ member.overloads.each do |overload|
117
+ find_in_method_type(pos, array: array, method_type: overload.method_type) and return true
118
118
  end
119
119
  when AST::Members::InstanceVariable, AST::Members::ClassInstanceVariable, AST::Members::ClassVariable
120
120
  find_in_type(pos, array: array, type: member.type) and return true
@@ -7,27 +7,43 @@ module RBS
7
7
 
8
8
  def block_from_body(node)
9
9
  _, args_node, body_node = node.children
10
+ _pre_num, _pre_init, _opt, _first_post, _post_num, _post_init, _rest, _kw, _kwrest, block_var = args_from_node(args_node)
10
11
 
11
- _pre_num, _pre_init, _opt, _first_post, _post_num, _post_init, _rest, _kw, _kwrest, block = args_from_node(args_node)
12
+ # @type var body_node: node?
13
+ if body_node
14
+ yields = any_node?(body_node) {|n| n.type == :YIELD }
15
+ end
16
+
17
+ if yields || block_var
18
+ required = true
19
+
20
+ if body_node
21
+ if any_node?(body_node) {|n| n.type == :FCALL && n.children[0] == :block_given? && !n.children[1] }
22
+ required = false
23
+ end
24
+ end
25
+
26
+ if _rest == :* && block_var == :&
27
+ # ... is given
28
+ required = false
29
+ end
12
30
 
13
- method_block = nil
31
+ if block_var
32
+ if body_node
33
+ usage = NodeUsage.new(body_node)
34
+ if usage.each_conditional_node.any? {|n| n.type == :LVAR && n.children[0] == block_var }
35
+ required = false
36
+ end
37
+ end
38
+ end
14
39
 
15
- if block
16
40
  method_block = Types::Block.new(
17
- required: false,
41
+ required: required,
18
42
  type: Types::Function.empty(untyped),
19
43
  self_type: nil
20
44
  )
21
- end
22
-
23
- if body_node
24
- if (yields = any_node?(body_node) {|n| n.type == :YIELD })
25
- method_block = Types::Block.new(
26
- required: true,
27
- type: Types::Function.empty(untyped),
28
- self_type: nil
29
- )
30
45
 
46
+ if yields
31
47
  yields.each do |yield_node|
32
48
  array_content = yield_node.children[0]&.children&.compact || []
33
49
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS
4
+ module Prototype
5
+ class NodeUsage
6
+ include Helpers
7
+
8
+ attr_reader :conditional_nodes
9
+
10
+ def initialize(node)
11
+ @node = node
12
+ @conditional_nodes = Set[].compare_by_identity
13
+
14
+ calculate(node, conditional: false)
15
+ end
16
+
17
+ def each_conditional_node(&block)
18
+ if block
19
+ conditional_nodes.each(&block)
20
+ else
21
+ conditional_nodes.each
22
+ end
23
+ end
24
+
25
+ def calculate(node, conditional:)
26
+ if conditional
27
+ conditional_nodes << node
28
+ end
29
+
30
+ case node.type
31
+ when :IF, :UNLESS
32
+ cond_node, true_node, false_node = node.children
33
+ calculate(cond_node, conditional: true)
34
+ calculate(true_node, conditional: conditional) if true_node
35
+ calculate(false_node, conditional: conditional) if false_node
36
+ when :AND, :OR
37
+ left, right = node.children
38
+ calculate(left, conditional: true)
39
+ calculate(right, conditional: conditional)
40
+ when :QCALL
41
+ receiver, _, args = node.children
42
+ calculate(receiver, conditional: true)
43
+ calculate(args, conditional: false) if args
44
+ when :WHILE
45
+ cond, body = node.children
46
+ calculate(cond, conditional: true)
47
+ calculate(body, conditional: false) if body
48
+ when :OP_ASGN_OR, :OP_ASGN_AND
49
+ var, _, asgn = node.children
50
+ calculate(var, conditional: true)
51
+ calculate(asgn, conditional: conditional)
52
+ when :LASGN, :IASGN, :GASGN
53
+ _, lhs = node.children
54
+ calculate(lhs, conditional: conditional) if lhs
55
+ when :MASGN
56
+ lhs, _ = node.children
57
+ calculate(lhs, conditional: conditional)
58
+ when :CDECL
59
+ if node.children.size == 2
60
+ _, lhs = node.children
61
+ calculate(lhs, conditional: conditional)
62
+ else
63
+ const, _, lhs = node.children
64
+ calculate(const, conditional: false)
65
+ calculate(lhs, conditional: conditional)
66
+ end
67
+ when :SCOPE
68
+ _, _, body = node.children
69
+ calculate(body, conditional: conditional)
70
+ when :CASE2
71
+ _, *branches = node.children
72
+ branches.each do |branch|
73
+ if branch.type == :WHEN
74
+ list, body = branch.children
75
+ list.children.each do |child|
76
+ if child
77
+ calculate(child, conditional: true)
78
+ end
79
+ end
80
+ calculate(body, conditional: conditional)
81
+ else
82
+ calculate(branch, conditional: conditional)
83
+ end
84
+ end
85
+ when :BLOCK
86
+ *nodes, last = node.children
87
+ nodes.each do |no|
88
+ calculate(no, conditional: false)
89
+ end
90
+ calculate(last, conditional: conditional) if last
91
+ else
92
+ each_child(node) do |child|
93
+ calculate(child, conditional: false)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -186,10 +186,11 @@ module RBS
186
186
  name: def_name,
187
187
  location: nil,
188
188
  annotations: [],
189
- types: types,
189
+ overloads: types.map {|type| AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: type )},
190
190
  kind: kind,
191
191
  comment: comments[node.first_lineno - 1],
192
- overload: false
192
+ overloading: false,
193
+ visibility: nil
193
194
  )
194
195
 
195
196
  decls.push member unless decls.include?(member)
@@ -185,10 +185,11 @@ module RBS
185
185
  name: node.children[1],
186
186
  location: nil,
187
187
  annotations: [],
188
- types: types,
188
+ overloads: types.map {|type| AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: type) },
189
189
  kind: :singleton,
190
190
  comment: comment,
191
- overload: false
191
+ overloading: false,
192
+ visibility: nil
192
193
  )
193
194
  end
194
195
 
@@ -205,10 +206,11 @@ module RBS
205
206
  name: node.children[0],
206
207
  location: nil,
207
208
  annotations: [],
208
- types: types,
209
+ overloads: types.map {|type| AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: type) },
209
210
  kind: :instance,
210
211
  comment: comment,
211
- overload: false
212
+ overloading: false,
213
+ visibility: nil
212
214
  )
213
215
  end
214
216
 
@@ -187,18 +187,22 @@ module RBS
187
187
  if method
188
188
  members << AST::Members::MethodDefinition.new(
189
189
  name: method_name,
190
- types: method.method_types.map {|type|
191
- type.update.tap do |ty|
192
- def ty.to_s
193
- location.source
190
+ overloads: method.method_types.map {|type|
191
+ AST::Members::MethodDefinition::Overload.new(
192
+ annotations: [],
193
+ method_type: type.update.tap do |ty|
194
+ def ty.to_s
195
+ location.source
196
+ end
194
197
  end
195
- end
198
+ )
196
199
  },
197
200
  kind: kind,
198
201
  location: nil,
199
202
  comment: method.comments[0],
200
203
  annotations: method.annotations,
201
- overload: false
204
+ overloading: false,
205
+ visibility: nil
202
206
  )
203
207
  return
204
208
  end
@@ -231,12 +235,15 @@ module RBS
231
235
 
232
236
  members << AST::Members::MethodDefinition.new(
233
237
  name: method.name,
234
- types: [method_type(method)],
238
+ overloads: [
239
+ AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
240
+ ],
235
241
  kind: :singleton,
236
242
  location: nil,
237
243
  comment: nil,
238
244
  annotations: [],
239
- overload: false
245
+ overloading: false,
246
+ visibility: nil
240
247
  )
241
248
  end
242
249
  else
@@ -264,12 +271,15 @@ module RBS
264
271
 
265
272
  members << AST::Members::MethodDefinition.new(
266
273
  name: method.name,
267
- types: [method_type(method)],
274
+ overloads: [
275
+ AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
276
+ ],
268
277
  kind: :instance,
269
278
  location: nil,
270
279
  comment: nil,
271
280
  annotations: [],
272
- overload: false
281
+ overloading: false,
282
+ visibility: nil
273
283
  )
274
284
  end
275
285
  else
@@ -298,12 +308,15 @@ module RBS
298
308
 
299
309
  members << AST::Members::MethodDefinition.new(
300
310
  name: method.name,
301
- types: [method_type(method)],
311
+ overloads: [
312
+ AST::Members::MethodDefinition::Overload.new(annotations: [], method_type: method_type(method))
313
+ ],
302
314
  kind: :instance,
303
315
  location: nil,
304
316
  comment: nil,
305
317
  annotations: [],
306
- overload: false
318
+ overloading: false,
319
+ visibility: nil
307
320
  )
308
321
  end
309
322
  else
@@ -50,6 +50,8 @@ module RBS
50
50
  end
51
51
  end
52
52
 
53
+ alias [] apply
54
+
53
55
  def without(*vars)
54
56
  Substitution.new.tap do |subst|
55
57
  subst.mapping.merge!(mapping)
@@ -60,5 +62,22 @@ module RBS
60
62
  subst.instance_type = self.instance_type
61
63
  end
62
64
  end
65
+
66
+ def +(other)
67
+ return self if other.empty?
68
+ return other if self.empty?
69
+
70
+ Substitution.new.tap do |subst|
71
+ subst.mapping.merge!(mapping)
72
+
73
+ other.mapping.each do |var, type|
74
+ if mapping.key?(var)
75
+ subst.add(from: var, to: self[type])
76
+ else
77
+ subst.add(from: var, to: type)
78
+ end
79
+ end
80
+ end
81
+ end
63
82
  end
64
83
  end
data/lib/rbs/types.rb CHANGED
@@ -757,11 +757,7 @@ module RBS
757
757
 
758
758
  def to_s
759
759
  if name
760
- if Parser::KEYWORDS.include?(name.to_s)
761
- "#{type} `#{name}`"
762
- else
763
- "#{type} #{name}"
764
- end
760
+ "#{type} #{name}"
765
761
  else
766
762
  "#{type}"
767
763
  end
data/lib/rbs/validator.rb CHANGED
@@ -98,7 +98,8 @@ module RBS
98
98
  end
99
99
 
100
100
  def validate_method_definition(method_def, type_name:)
101
- method_def.types.each do |method_type|
101
+ method_def.overloads.each do |overload|
102
+ method_type = overload.method_type
102
103
  unless method_type.type_params.empty?
103
104
  loc = method_type.location&.aref(:type_params)
104
105
 
data/lib/rbs/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RBS
4
- VERSION = "2.8.1"
4
+ VERSION = "3.0.0.dev.1"
5
5
  end
data/lib/rbs/writer.rb CHANGED
@@ -39,21 +39,27 @@ module RBS
39
39
  end
40
40
  end
41
41
 
42
+ def format_annotation(annotation)
43
+ string = annotation.string
44
+ case
45
+ when string !~ /\}/
46
+ "%a{#{string}}"
47
+ when string !~ /\)/
48
+ "%a(#{string})"
49
+ when string !~ /\]/
50
+ "%a[#{string}]"
51
+ when string !~ /\>/
52
+ "%a<#{string}>"
53
+ when string !~ /\|/
54
+ "%a|#{string}|"
55
+ else
56
+ raise
57
+ end
58
+ end
59
+
42
60
  def write_annotation(annotations)
43
61
  annotations.each do |annotation|
44
- string = annotation.string
45
- case
46
- when string !~ /\}/
47
- puts "%a{#{string}}"
48
- when string !~ /\)/
49
- puts "%a(#{string})"
50
- when string !~ /\]/
51
- puts "%a[#{string}]"
52
- when string !~ /\>/
53
- puts "%a<#{string}>"
54
- when string !~ /\|/
55
- puts "%a|#{string}|"
56
- end
62
+ puts format_annotation(annotation)
57
63
  end
58
64
  end
59
65
 
@@ -289,17 +295,20 @@ module RBS
289
295
 
290
296
  string << prefix
291
297
 
292
- member.types.each.with_index do |type, index|
298
+ member.overloads.each.with_index do |overload, index|
293
299
  if index > 0
294
300
  string << padding
295
301
  string << "|"
296
302
  end
297
303
 
298
- string << " #{type}\n"
304
+ overload.annotations.each do |annotation|
305
+ string << " #{format_annotation(annotation)}"
306
+ end
307
+ string << " #{overload.method_type}\n"
299
308
  end
300
309
 
301
- if member.overload
302
- if member.types.size > 0
310
+ if member.overloading?
311
+ if member.overloads.size > 0
303
312
  string << padding
304
313
  string << "|"
305
314
  end
data/lib/rbs.rb CHANGED
@@ -39,6 +39,7 @@ require "rbs/prototype/helpers"
39
39
  require "rbs/prototype/rbi"
40
40
  require "rbs/prototype/rb"
41
41
  require "rbs/prototype/runtime"
42
+ require "rbs/prototype/node_usage"
42
43
  require "rbs/type_name_resolver"
43
44
  require "rbs/environment_walker"
44
45
  require "rbs/vendorer"
@@ -74,7 +74,7 @@ module RBS
74
74
  method = RDoc::AnyMethod.new(nil, decl.name.to_s)
75
75
  method.singleton = decl.singleton?
76
76
  method.visibility = decl.visibility
77
- method.call_seq = decl.types.map { |type| "#{decl.name.to_s}#{type.to_s}" }.join("\n")
77
+ method.call_seq = decl.overloads.map {|overload| "#{decl.name.to_s}#{overload.method_type.to_s}" }.join("\n")
78
78
  if loc = decl.location
79
79
  method.start_collecting_tokens
80
80
  method.add_token({ line_no: 1, char_no: 1, kind: :on_comment, text: "# File #{@top_level.relative_name}, line(s) #{loc.start_line}:#{loc.end_line}\n" })
data/schema/members.json CHANGED
@@ -14,10 +14,21 @@
14
14
  "kind": {
15
15
  "enum": ["instance", "singleton", "singleton_instance"]
16
16
  },
17
- "types": {
17
+ "overloads": {
18
18
  "type": "array",
19
19
  "items": {
20
- "$ref": "methodType.json"
20
+ "type": "object",
21
+ "properties": {
22
+ "annotations": {
23
+ "type": "array",
24
+ "items": {
25
+ "$ref": "annotation.json"
26
+ }
27
+ },
28
+ "method_type": {
29
+ "$ref": "methodType.json"
30
+ }
31
+ }
21
32
  }
22
33
  },
23
34
  "comment": {
@@ -29,23 +40,17 @@
29
40
  "$ref": "annotation.json"
30
41
  }
31
42
  },
32
- "attributes": {
33
- "type": "array",
34
- "items": {
35
- "enum": ["incompatible"]
36
- }
37
- },
38
43
  "location": {
39
44
  "$ref": "location.json"
40
45
  },
41
- "overload": {
46
+ "overloading": {
42
47
  "type": "boolean"
43
48
  },
44
49
  "visibility": {
45
50
  "enum": ["public", "private", null]
46
51
  }
47
52
  },
48
- "required": ["member", "name", "kind", "types", "comment", "annotations", "location", "visibility"]
53
+ "required": ["member", "name", "kind", "overloads", "comment", "annotations", "location", "visibility", "overloading"]
49
54
  },
50
55
  "variable": {
51
56
  "title": "Declaration for instance variables and class variables",
@@ -0,0 +1,80 @@
1
+ module RBS
2
+ module Collection
3
+ class Config
4
+ # Lockfile represents the `rbs_collection.lock.yaml`, that contains configurations and *resolved* gems with their sources
5
+ #
6
+ class Lockfile
7
+ # Data structure stored in `rbs_collection.lock.yaml`
8
+ #
9
+ type lockfile_data = {
10
+ "sources" => Array[Sources::Git::source_entry]?, # null if empty
11
+ "path" => String,
12
+ "gems" => Array[library_data]?, # null if empty
13
+ "gemfile_lock_path" => String? # gemfile_lock_path is optional because older versions doesn't have it
14
+ }
15
+
16
+ type library_data = {
17
+ 'name' => String,
18
+ 'version' => String,
19
+ 'source' => Sources::source_entry
20
+ }
21
+
22
+ # In-memory data structure that represents a library
23
+ #
24
+ type library = {
25
+ name: String,
26
+ version: String,
27
+ source: Sources::t
28
+ }
29
+
30
+ attr_reader lockfile_path: Pathname
31
+
32
+ # Path of the directory where lockfile is saved in
33
+ #
34
+ # `lockfile_path.parent`
35
+ #
36
+ attr_reader lockfile_dir: Pathname
37
+
38
+ # Relative to lockfile_dir
39
+ #
40
+ attr_reader path: Pathname
41
+
42
+ # Relative to lockfile_dir
43
+ #
44
+ attr_reader gemfile_lock_path: Pathname?
45
+
46
+ attr_reader sources: Hash[String, Sources::Git]
47
+
48
+ attr_reader gems: Hash[String, library]
49
+
50
+ def initialize: (lockfile_path: Pathname, path: Pathname, gemfile_lock_path: Pathname?) -> void
51
+
52
+ # `lockfile_dir` + `path`
53
+ #
54
+ def fullpath: () -> Pathname
55
+
56
+ # `lockfile_dir` + `gemfile_lock_path`
57
+ #
58
+ %a{pure} def gemfile_lock_fullpath: () -> Pathname?
59
+
60
+ def to_lockfile: () -> lockfile_data
61
+
62
+ def each_source: () { (Sources::t) -> void } -> void
63
+ | () -> Enumerator[Sources::t, void]
64
+
65
+ def self.from_lockfile: (lockfile_path: Pathname, data: lockfile_data) -> Lockfile
66
+
67
+ # Validates if directories are set up correctly
68
+ #
69
+ # * Ensures if `path` is a directory
70
+ # * Ensures if `git` sources are set up correctly
71
+ #
72
+ def check_rbs_availability!: () -> void
73
+
74
+ private
75
+
76
+ def library_data: (library) -> library_data
77
+ end
78
+ end
79
+ end
80
+ end