steep 0.1.0.pre2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +1 -1
  3. data/README.md +146 -33
  4. data/bin/smoke_runner.rb +43 -10
  5. data/lib/steep/ast/annotation/collection.rb +93 -0
  6. data/lib/steep/ast/annotation.rb +131 -0
  7. data/lib/steep/ast/buffer.rb +47 -0
  8. data/lib/steep/ast/location.rb +82 -0
  9. data/lib/steep/ast/method_type.rb +116 -0
  10. data/lib/steep/ast/signature/class.rb +33 -0
  11. data/lib/steep/ast/signature/const.rb +17 -0
  12. data/lib/steep/ast/signature/env.rb +123 -0
  13. data/lib/steep/ast/signature/extension.rb +21 -0
  14. data/lib/steep/ast/signature/gvar.rb +17 -0
  15. data/lib/steep/ast/signature/interface.rb +31 -0
  16. data/lib/steep/ast/signature/members.rb +71 -0
  17. data/lib/steep/ast/signature/module.rb +21 -0
  18. data/lib/steep/ast/type_params.rb +13 -0
  19. data/lib/steep/ast/types/any.rb +39 -0
  20. data/lib/steep/ast/types/bot.rb +39 -0
  21. data/lib/steep/ast/types/class.rb +35 -0
  22. data/lib/steep/ast/types/helper.rb +21 -0
  23. data/lib/steep/ast/types/instance.rb +39 -0
  24. data/lib/steep/ast/types/intersection.rb +74 -0
  25. data/lib/steep/ast/types/name.rb +124 -0
  26. data/lib/steep/ast/types/self.rb +39 -0
  27. data/lib/steep/ast/types/top.rb +39 -0
  28. data/lib/steep/ast/types/union.rb +74 -0
  29. data/lib/steep/ast/types/var.rb +57 -0
  30. data/lib/steep/ast/types/void.rb +35 -0
  31. data/lib/steep/cli.rb +28 -1
  32. data/lib/steep/drivers/annotations.rb +32 -0
  33. data/lib/steep/drivers/check.rb +53 -77
  34. data/lib/steep/drivers/scaffold.rb +303 -0
  35. data/lib/steep/drivers/utils/each_signature.rb +66 -0
  36. data/lib/steep/drivers/utils/validator.rb +115 -0
  37. data/lib/steep/drivers/validate.rb +39 -0
  38. data/lib/steep/errors.rb +291 -19
  39. data/lib/steep/interface/abstract.rb +44 -0
  40. data/lib/steep/interface/builder.rb +470 -0
  41. data/lib/steep/interface/instantiated.rb +126 -0
  42. data/lib/steep/interface/ivar_chain.rb +26 -0
  43. data/lib/steep/interface/method.rb +60 -0
  44. data/lib/steep/{interface.rb → interface/method_type.rb} +111 -100
  45. data/lib/steep/interface/substitution.rb +65 -0
  46. data/lib/steep/module_name.rb +116 -0
  47. data/lib/steep/parser.rb +1314 -814
  48. data/lib/steep/parser.y +536 -175
  49. data/lib/steep/source.rb +220 -25
  50. data/lib/steep/subtyping/check.rb +673 -0
  51. data/lib/steep/subtyping/constraints.rb +275 -0
  52. data/lib/steep/subtyping/relation.rb +41 -0
  53. data/lib/steep/subtyping/result.rb +126 -0
  54. data/lib/steep/subtyping/trace.rb +48 -0
  55. data/lib/steep/subtyping/variable_occurrence.rb +49 -0
  56. data/lib/steep/subtyping/variable_variance.rb +69 -0
  57. data/lib/steep/type_construction.rb +1630 -524
  58. data/lib/steep/type_inference/block_params.rb +100 -0
  59. data/lib/steep/type_inference/constant_env.rb +55 -0
  60. data/lib/steep/type_inference/send_args.rb +222 -0
  61. data/lib/steep/type_inference/type_env.rb +226 -0
  62. data/lib/steep/type_name.rb +27 -7
  63. data/lib/steep/typing.rb +4 -0
  64. data/lib/steep/version.rb +1 -1
  65. data/lib/steep.rb +71 -16
  66. data/smoke/and/a.rb +4 -2
  67. data/smoke/array/a.rb +4 -5
  68. data/smoke/array/b.rb +4 -4
  69. data/smoke/block/a.rb +2 -2
  70. data/smoke/block/a.rbi +2 -0
  71. data/smoke/block/b.rb +15 -0
  72. data/smoke/case/a.rb +3 -3
  73. data/smoke/class/a.rb +3 -3
  74. data/smoke/class/b.rb +0 -2
  75. data/smoke/class/d.rb +2 -2
  76. data/smoke/class/e.rb +1 -1
  77. data/smoke/class/f.rb +2 -2
  78. data/smoke/class/g.rb +8 -0
  79. data/smoke/const/a.rb +3 -3
  80. data/smoke/dstr/a.rb +1 -1
  81. data/smoke/ensure/a.rb +22 -0
  82. data/smoke/enumerator/a.rb +6 -6
  83. data/smoke/enumerator/b.rb +22 -0
  84. data/smoke/extension/a.rb +2 -2
  85. data/smoke/extension/b.rb +3 -3
  86. data/smoke/extension/c.rb +1 -1
  87. data/smoke/hello/hello.rb +2 -2
  88. data/smoke/if/a.rb +4 -2
  89. data/smoke/kwbegin/a.rb +8 -0
  90. data/smoke/literal/a.rb +5 -5
  91. data/smoke/method/a.rb +5 -5
  92. data/smoke/method/a.rbi +4 -0
  93. data/smoke/method/b.rb +29 -0
  94. data/smoke/module/a.rb +3 -3
  95. data/smoke/module/a.rbi +9 -0
  96. data/smoke/module/b.rb +2 -2
  97. data/smoke/module/c.rb +1 -1
  98. data/smoke/module/d.rb +5 -0
  99. data/smoke/module/e.rb +13 -0
  100. data/smoke/module/f.rb +13 -0
  101. data/smoke/rescue/a.rb +62 -0
  102. data/smoke/super/a.rb +2 -2
  103. data/smoke/type_case/a.rb +35 -0
  104. data/smoke/yield/a.rb +2 -2
  105. data/stdlib/builtin.rbi +463 -24
  106. data/steep.gemspec +3 -2
  107. metadata +91 -29
  108. data/lib/steep/annotation.rb +0 -223
  109. data/lib/steep/signature/class.rb +0 -450
  110. data/lib/steep/signature/extension.rb +0 -51
  111. data/lib/steep/signature/interface.rb +0 -49
  112. data/lib/steep/types/any.rb +0 -31
  113. data/lib/steep/types/class.rb +0 -27
  114. data/lib/steep/types/instance.rb +0 -27
  115. data/lib/steep/types/merge.rb +0 -32
  116. data/lib/steep/types/name.rb +0 -57
  117. data/lib/steep/types/union.rb +0 -42
  118. data/lib/steep/types/var.rb +0 -38
  119. data/lib/steep/types.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 74822cb390656501c900a6a8c1cb9fbe60c4dc4c
4
- data.tar.gz: 1bd94c67e5339288af5052ecdbfaa553ae151aee
2
+ SHA256:
3
+ metadata.gz: e44524f5daae1f0506108ead6a30bc8abff67ab100d2639e21dd75e3384dab7e
4
+ data.tar.gz: 1a5595ef390995476f5d6dd88fb3c1684bdca27a87ec26d048b071e4a5ccf9d0
5
5
  SHA512:
6
- metadata.gz: 8102354b9e6ca837f22439b9b721e6e7ec67bc4b2e49de5db26a59d7167d5399ba04183b2e88d49afa3bf20cb88e5ed94ce6363a22caa0e7b5ec88ffd9963dfd
7
- data.tar.gz: ea2304c8ee9850fc540c0b656daf4b100c97de914a4a124e332ef53117b0aa137c6f05a62402227a4601d51a74b1b5298c64980a6e3be9af300f480e41045e0a
6
+ metadata.gz: 97c834ae154ac5bc617f4f65be66820531524888237ace9bc316c9cbecb1e2433f8a40b947c9c4d1ae97da7701b55622fe2a7407464fb9db90666e6fd790897b
7
+ data.tar.gz: fe8b9e5ab3367df5b28281a0179b58af6b96fd497ee84293b69533808d2b52ffa7ebc801021456fd0fc528c21d2cd07eb63f9b6ca49899052b9cf746eb184cdd
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.4.0
4
+ - 2.5.0
5
5
  before_install: gem install bundler -v 1.13.7
6
6
  script: bundle exec rake test smoke
data/README.md CHANGED
@@ -4,73 +4,181 @@
4
4
 
5
5
  Install via RubyGems.
6
6
 
7
- $ gem install steep --pre
8
-
9
- Note that Steep is not released yet (pre-released). Add `--pre` for `gem install`.
7
+ $ gem install steep
10
8
 
11
9
  ### Requirements
12
10
 
13
- Steep requires Ruby 2.4.
11
+ Steep requires Ruby 2.5.
14
12
 
15
13
  ## Usage
16
14
 
17
15
  Steep does not infer types from Ruby programs, but requires declaring types and writing annotations.
18
16
  You have to go on the following three steps.
19
17
 
20
- ### 1. Declare Signatures
18
+ ### 1. Declare Types
21
19
 
22
- Declare signatures in `.rbi` files.
20
+ Declare types in `.rbi` files in `sig` directory.
23
21
 
24
22
  ```
25
- interface _Foo {
26
- def do_something: (String) -> any
27
- }
23
+ class Person
24
+ @name: String
25
+ @contacts: Array<Email | Phone>
26
+
27
+ def initialize: (name: String) -> any
28
+ def name: -> String
29
+ def contacts: -> Array<Email | Phone>
30
+ def guess_country: -> (String | nil)
31
+ end
28
32
 
29
- module Fooable : _Foo {
30
- def foo: (Array<String>) { (String) -> String } -> any
31
- }
33
+ class Email
34
+ @address: String
32
35
 
33
- class SuperFoo {
34
- include Fooable
36
+ def initialize: (address: String) -> any
37
+ def address: -> String
38
+ end
35
39
 
36
- def name: -> String
37
- def do_something: (String) -> any
38
- def bar: (?Symbol, size: Integer) -> Symbol
39
- }
40
+ class Phone
41
+ @country: String
42
+ @number: String
43
+
44
+ def initialize: (country: String, number: String) -> any
45
+ def country: -> String
46
+ def number: -> String
47
+
48
+ def self.countries: -> Hash<String, String>
49
+ end
40
50
  ```
41
51
 
42
- ### 2. Annotate Ruby Code
52
+ * You can use simple *generics*, like `Hash<String, String>`.
53
+ * You can use *union types*, like `Email | Phone`.
54
+ * You have to declare not only public methods but also private methods and instance variables.
55
+ * You can declare *singleton methods*, like `self.countries`.
56
+ * There is `nil` type to represent *nullable* types.
43
57
 
44
- Write annotations to your Ruby code.
58
+ ### 2. Write Ruby Code
59
+
60
+ Write Ruby code with annotations.
45
61
 
46
62
  ```rb
47
- class Foo
48
- # @implements SuperFoo
49
- # @type const Helper: FooHelper
63
+ class Person
64
+ # `@dynamic` annotation is to tell steep that
65
+ # the `name` and `contacts` methods are defined without def syntax.
66
+ # (Steep can skip checking if the methods are implemented.)
50
67
 
51
- # @dynamic name
68
+ # @dynamic name, contacts
52
69
  attr_reader :name
70
+ attr_reader :contacts
53
71
 
54
- def do_something(string)
55
- # ...
72
+ def initialize(name:)
73
+ @name = name
74
+ @contacts = []
56
75
  end
57
76
 
58
- def bar(symbol = :default, size:)
59
- Helper.run_bar(symbol, size: size)
77
+ def guess_country()
78
+ contacts.map do |contact|
79
+ # With case expression, simple type-case is implemented.
80
+ # `contact` has type of `Phone | Email` but in the `when` clause, contact has type of `Phone`.
81
+ case contact
82
+ when Phone
83
+ contact.country
84
+ end
85
+ end.compact.first
86
+ end
87
+ end
88
+
89
+ class Email
90
+ # @dynamic address
91
+ attr_reader :address
92
+
93
+ def initialize(address:)
94
+ @address = address
95
+ end
96
+
97
+ def ==(other)
98
+ # `other` has type of `any`, which means type checking is skipped.
99
+ # No type errors can be detected in this method.
100
+ other.is_a?(self.class) && other.address == address
101
+ end
102
+
103
+ def hash
104
+ self.class.hash ^ address.hash
105
+ end
106
+ end
107
+
108
+ class Phone
109
+ # @dynamic country, number
110
+
111
+ def initialize(country:, number:)
112
+ @country = country
113
+ @number = number
114
+ end
115
+
116
+ def ==(other)
117
+ # You cannot use `case` for type case because `other` has type of `any`, not a union type.
118
+ # You have to explicitly declare the type of `other` in `if` expression.
119
+
120
+ if other.is_a?(Phone)
121
+ # @type var other: Phone
122
+ other.country == country && other.number == number
123
+ end
124
+ end
125
+
126
+ def hash
127
+ self.class.hash ^ country.hash ^ number.hash
60
128
  end
61
129
  end
62
130
  ```
63
131
 
64
132
  ### 3. Type Check
65
133
 
66
- Run `steep check` command to type check.
134
+ Run `steep check` command to type check. 💡
67
135
 
68
136
  ```
69
- $ steep check lib/foo.rb
70
- foo.rb:41:18: NoMethodError: type=FooHelper, method=run_bar
71
- foo.rb:42:24: NoMethodError: type=String, method==~
137
+ $ steep check lib
138
+ lib/phone.rb:46:0: MethodDefinitionMissing: module=::Phone, method=self.countries (class Phone)
139
+ ```
140
+
141
+ You now find `Phone.countries` method is not implemented yet. 🙃
142
+
143
+ ## Scaffolding
144
+
145
+ You can use `steep scaffold` command to generate a signature declaration.
146
+
147
+ ```
148
+ $ steep scaffold lib/*.rb
149
+ class Person
150
+ @name: any
151
+ @contacts: Array<any>
152
+ def initialize: (name: any) -> Array<any>
153
+ def guess_country: () -> any
154
+ end
155
+
156
+ class Email
157
+ @address: any
158
+ def initialize: (address: any) -> any
159
+ def ==: (any) -> any
160
+ def hash: () -> any
161
+ end
162
+
163
+ class Phone
164
+ @country: any
165
+ @number: any
166
+ def initialize: (country: any, number: any) -> any
167
+ def ==: (any) -> void
168
+ def hash: () -> any
169
+ end
72
170
  ```
73
171
 
172
+ It prints all methods, classes, instance variables, and constants.
173
+ It can be a good starting point to writing signatures.
174
+
175
+ Because it just prints all `def`s, you may find some odd points:
176
+
177
+ * The type of `initialize` in `Person` looks strange.
178
+ * There are no `attr_reader` methods extracted.
179
+
180
+ Generally, these are by our design.
181
+
74
182
  ## Commandline
75
183
 
76
184
  `steep check` is the command to run type checking.
@@ -85,7 +193,7 @@ If you don't specify `-I` option, it assumes `sig` directory.
85
193
 
86
194
  ### Detecting Fallback
87
195
 
88
- When Steep finds a node which cannot be typed, it assumes the type of the node is *any*.
196
+ When Steep finds an expression which cannot be typed, it assumes the type of the node is *any*.
89
197
  *any* type does not raise any type error so that fallback to *any* may hide some type errors.
90
198
 
91
199
  Using `--fallback-any-is-error` option prints the fallbacks.
@@ -100,6 +208,11 @@ Use `--dump-all-types` for that.
100
208
 
101
209
  $ steep check --dump-all-types test.rb
102
210
 
211
+ ### Verbose option
212
+
213
+ Try `-v` option to report more information about type checking.
214
+
215
+
103
216
  ## Examples
104
217
 
105
218
  You can find examples in `smoke` directory.
data/bin/smoke_runner.rb CHANGED
@@ -14,7 +14,11 @@ OptionParser.new do |opts|
14
14
  opts.on("-v", "--verbose") do verbose = true end
15
15
  end.parse!(ARGV)
16
16
 
17
- Expectation = Struct.new(:line, :message)
17
+ Expectation = Struct.new(:line, :message, :path, :starts) do
18
+ attr_accessor :prefix_test
19
+ end
20
+
21
+ allowed_paths = []
18
22
 
19
23
  failed = false
20
24
 
@@ -35,12 +39,24 @@ ARGV.each do |arg|
35
39
  comments.each do |comment|
36
40
  src = comment.text.gsub(/\A#\s*/, '')
37
41
 
42
+ if src =~ /!expects\*(@(\+\d+))?/
43
+ offset = $2&.to_i || 1
44
+ message = src.gsub!(/\A!expects\*(@\+\d+)? +/, '')
45
+ line = comment.location.line
46
+
47
+ expectations << Expectation.new(line+offset, message, file).tap {|e| e.prefix_test = true }
48
+ end
49
+
38
50
  if src =~ /!expects(@(\+\d+))?/
39
51
  offset = $2&.to_i || 1
40
52
  message = src.gsub!(/\A!expects(@\+\d+)? +/, '')
41
53
  line = comment.location.line
42
54
 
43
- expectations << Expectation.new(line+offset, message)
55
+ expectations << Expectation.new(line+offset, message, file)
56
+ end
57
+
58
+ if src =~ /ALLOW FAILURE/
59
+ allowed_paths << file
44
60
  end
45
61
  end
46
62
 
@@ -58,6 +74,7 @@ ARGV.each do |arg|
58
74
  stdout: stdout,
59
75
  stderr: stderr)
60
76
 
77
+ Rainbow.enabled = false
61
78
  driver.run
62
79
  rescue => exn
63
80
  puts "ERROR: #{exn.inspect}"
@@ -66,6 +83,8 @@ ARGV.each do |arg|
66
83
  end
67
84
 
68
85
  failed = true
86
+ ensure
87
+ Rainbow.enabled = true
69
88
  end
70
89
 
71
90
  if verbose
@@ -82,22 +101,36 @@ ARGV.each do |arg|
82
101
 
83
102
  expectations.each do |expectation|
84
103
  deleted = lines.reject! do |string|
85
- string =~ /:#{expectation.line}:\d+: #{Regexp.quote expectation.message}\Z/
104
+ if expectation.prefix_test
105
+ string =~ /\A#{Regexp.escape(expectation.path.to_s)}:#{expectation.line}:\d+: #{Regexp.quote expectation.message}/
106
+ else
107
+ string =~ /\A#{Regexp.escape(expectation.path.to_s)}:#{expectation.line}:\d+: #{Regexp.quote expectation.message} \(/
108
+ end
86
109
  end
87
110
 
88
111
  unless deleted
89
- puts Rainbow(" 💀 Expected error not found: #{expectation.line}:#{expectation.message}").red
90
- failed = true
112
+ allowed = allowed_paths.any? {|path| path == expectation.path }
113
+ message = Rainbow(" 💀 Expected error not found: #{expectation.path}:#{expectation.line}:#{expectation.message}")
114
+ if allowed
115
+ puts message.yellow
116
+ else
117
+ puts message.red
118
+ failed = true
119
+ end
91
120
  end
92
121
  end
93
122
 
94
123
  unless lines.empty?
95
124
  lines.each do |line|
96
- if line =~ /:\d+:\d+:/
97
- puts Rainbow(" 🤦‍♀️ Unexpected error found: #{line}").red
98
- failed = true
99
- else
100
- puts Rainbow(" 🤦 Unexpected error found, but ignored: #{line}").yellow
125
+ if line =~ /\A([^:]+):\d+:\d+:/
126
+ message = Rainbow(" 🤦‍♀️ Unexpected error found: #{line}")
127
+
128
+ if allowed_paths.include?(Pathname($1))
129
+ puts message.yellow
130
+ else
131
+ puts message.red
132
+ failed = true
133
+ end
101
134
  end
102
135
  end
103
136
  end
@@ -0,0 +1,93 @@
1
+ module Steep
2
+ module AST
3
+ module Annotation
4
+ class Collection
5
+ attr_reader :var_types
6
+ attr_reader :method_types
7
+ attr_reader :annotations
8
+ attr_reader :block_type
9
+ attr_reader :return_type
10
+ attr_reader :self_type
11
+ attr_reader :const_types
12
+ attr_reader :instance_type
13
+ attr_reader :module_type
14
+ attr_reader :implement_module
15
+ attr_reader :ivar_types
16
+ attr_reader :dynamics
17
+ attr_reader :break_type
18
+
19
+ def initialize(annotations:)
20
+ @var_types = {}
21
+ @method_types = {}
22
+ @const_types = {}
23
+ @ivar_types = {}
24
+ @dynamics = {}
25
+ @break_type = nil
26
+
27
+ annotations.each do |annotation|
28
+ case annotation
29
+ when VarType
30
+ var_types[annotation.name] = annotation
31
+ when MethodType
32
+ method_types[annotation.name] = annotation
33
+ when BlockType
34
+ @block_type = annotation.type
35
+ when ReturnType
36
+ @return_type = annotation.type
37
+ when SelfType
38
+ @self_type = annotation.type
39
+ when ConstType
40
+ @const_types[annotation.name] = annotation.type
41
+ when InstanceType
42
+ @instance_type = annotation.type
43
+ when ModuleType
44
+ @module_type = annotation.type
45
+ when Implements
46
+ @implement_module = annotation
47
+ when IvarType
48
+ ivar_types[annotation.name] = annotation.type
49
+ when Dynamic
50
+ annotation.names.each do |name|
51
+ dynamics[name.name] = name
52
+ end
53
+ when BreakType
54
+ @break_type = annotation.type
55
+ else
56
+ raise "Unexpected annotation: #{annotation.inspect}"
57
+ end
58
+ end
59
+
60
+ @annotations = annotations
61
+ end
62
+
63
+ def lookup_var_type(name)
64
+ var_types[name]&.type
65
+ end
66
+
67
+ def lookup_method_type(name)
68
+ method_types[name]&.type
69
+ end
70
+
71
+ def lookup_const_type(node)
72
+ const_types[node]
73
+ end
74
+
75
+ def +(other)
76
+ self.class.new(annotations: annotations.reject {|a| a.is_a?(BlockType) } + other.annotations)
77
+ end
78
+
79
+ def any?(&block)
80
+ annotations.any?(&block)
81
+ end
82
+
83
+ def size
84
+ annotations.size
85
+ end
86
+
87
+ def include?(obj)
88
+ annotations.include?(obj)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,131 @@
1
+ module Steep
2
+ module AST
3
+ module Annotation
4
+ class Named
5
+ attr_reader :name
6
+ attr_reader :type
7
+ attr_reader :location
8
+
9
+ def initialize(name:, type:, location: nil)
10
+ @name = name
11
+ @type = type
12
+ @location = location
13
+ end
14
+
15
+ def ==(other)
16
+ other.is_a?(self.class) &&
17
+ other.name == name &&
18
+ other.type == type &&
19
+ (!other.location || !location || other.location == location)
20
+ end
21
+ end
22
+
23
+ class Typed
24
+ attr_reader :type
25
+ attr_reader :annotation
26
+ attr_reader :location
27
+
28
+ def initialize(type:, location: nil)
29
+ @type = type
30
+ @location = location
31
+ end
32
+
33
+ def ==(other)
34
+ other.is_a?(self.class) &&
35
+ other.type == type &&
36
+ (!other.location || !location || other.location == location)
37
+ end
38
+ end
39
+
40
+ class ReturnType < Typed; end
41
+ class BlockType < Typed; end
42
+ class SelfType < Typed; end
43
+ class InstanceType < Typed; end
44
+ class ModuleType < Typed; end
45
+ class BreakType < Typed; end
46
+
47
+ class MethodType < Named; end
48
+ class VarType < Named; end
49
+ class ConstType < Named; end
50
+ class IvarType < Named; end
51
+
52
+ class Implements
53
+ class Module
54
+ attr_reader :name
55
+ attr_reader :args
56
+
57
+ def initialize(name:, args:)
58
+ @name = name
59
+ @args = args
60
+ end
61
+
62
+ def ==(other)
63
+ other.is_a?(Module) && other.name == name && other.args == args
64
+ end
65
+
66
+ alias eql? ==
67
+
68
+ def hash
69
+ self.class.hash ^ name.hash ^ args.hash
70
+ end
71
+ end
72
+
73
+ attr_reader :location
74
+ attr_reader :name
75
+
76
+ def initialize(name:, location:)
77
+ @location = location
78
+ @name = name
79
+ end
80
+
81
+ def ==(other)
82
+ other.is_a?(Implements) &&
83
+ other.name == name &&
84
+ other.location == location
85
+ end
86
+ end
87
+
88
+ class Dynamic
89
+ class Name
90
+ attr_reader :kind
91
+ attr_reader :name
92
+ attr_reader :location
93
+
94
+ def initialize(name:, kind:, location: nil)
95
+ @name = name
96
+ @kind = kind
97
+ @location = location
98
+ end
99
+
100
+ def instance_method?
101
+ kind == :instance || kind == :module_instance
102
+ end
103
+
104
+ def module_method?
105
+ kind == :module || kind == :module_instance
106
+ end
107
+
108
+ def ==(other)
109
+ other.is_a?(Name) &&
110
+ other.name == name &&
111
+ other.kind == kind
112
+ end
113
+ end
114
+
115
+ attr_reader :location
116
+ attr_reader :names
117
+
118
+ def initialize(names:, location: nil)
119
+ @location = location
120
+ @names = names
121
+ end
122
+
123
+ def ==(other)
124
+ other.is_a?(Dynamic) &&
125
+ other.names == names &&
126
+ (!other.location || location || other.location == location)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,47 @@
1
+ module Steep
2
+ module AST
3
+ class Buffer
4
+ attr_reader :name
5
+ attr_reader :content
6
+ attr_reader :lines
7
+ attr_reader :ranges
8
+
9
+ def initialize(name:, content:)
10
+ @name = name
11
+ @content = content
12
+
13
+ @lines = content.lines
14
+
15
+ @ranges = []
16
+ offset = 0
17
+ lines.each do |line|
18
+ size = line.bytesize
19
+ range = offset .. (offset+size)
20
+ ranges << range
21
+ offset += size
22
+ end
23
+ end
24
+
25
+ def pos_to_loc(pos)
26
+ index = ranges.bsearch_index do |range|
27
+ pos < range.end
28
+ end
29
+
30
+ if index
31
+ [index + 1, pos - ranges[index].begin]
32
+ else
33
+ [1, pos]
34
+ end
35
+ end
36
+
37
+ def loc_to_pos(loc)
38
+ line, column = loc
39
+ ranges[line - 1].begin + column
40
+ end
41
+
42
+ def source(range)
43
+ content[range]
44
+ end
45
+ end
46
+ end
47
+ end