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.
- checksums.yaml +5 -5
- data/.travis.yml +1 -1
- data/README.md +146 -33
- data/bin/smoke_runner.rb +43 -10
- data/lib/steep/ast/annotation/collection.rb +93 -0
- data/lib/steep/ast/annotation.rb +131 -0
- data/lib/steep/ast/buffer.rb +47 -0
- data/lib/steep/ast/location.rb +82 -0
- data/lib/steep/ast/method_type.rb +116 -0
- data/lib/steep/ast/signature/class.rb +33 -0
- data/lib/steep/ast/signature/const.rb +17 -0
- data/lib/steep/ast/signature/env.rb +123 -0
- data/lib/steep/ast/signature/extension.rb +21 -0
- data/lib/steep/ast/signature/gvar.rb +17 -0
- data/lib/steep/ast/signature/interface.rb +31 -0
- data/lib/steep/ast/signature/members.rb +71 -0
- data/lib/steep/ast/signature/module.rb +21 -0
- data/lib/steep/ast/type_params.rb +13 -0
- data/lib/steep/ast/types/any.rb +39 -0
- data/lib/steep/ast/types/bot.rb +39 -0
- data/lib/steep/ast/types/class.rb +35 -0
- data/lib/steep/ast/types/helper.rb +21 -0
- data/lib/steep/ast/types/instance.rb +39 -0
- data/lib/steep/ast/types/intersection.rb +74 -0
- data/lib/steep/ast/types/name.rb +124 -0
- data/lib/steep/ast/types/self.rb +39 -0
- data/lib/steep/ast/types/top.rb +39 -0
- data/lib/steep/ast/types/union.rb +74 -0
- data/lib/steep/ast/types/var.rb +57 -0
- data/lib/steep/ast/types/void.rb +35 -0
- data/lib/steep/cli.rb +28 -1
- data/lib/steep/drivers/annotations.rb +32 -0
- data/lib/steep/drivers/check.rb +53 -77
- data/lib/steep/drivers/scaffold.rb +303 -0
- data/lib/steep/drivers/utils/each_signature.rb +66 -0
- data/lib/steep/drivers/utils/validator.rb +115 -0
- data/lib/steep/drivers/validate.rb +39 -0
- data/lib/steep/errors.rb +291 -19
- data/lib/steep/interface/abstract.rb +44 -0
- data/lib/steep/interface/builder.rb +470 -0
- data/lib/steep/interface/instantiated.rb +126 -0
- data/lib/steep/interface/ivar_chain.rb +26 -0
- data/lib/steep/interface/method.rb +60 -0
- data/lib/steep/{interface.rb → interface/method_type.rb} +111 -100
- data/lib/steep/interface/substitution.rb +65 -0
- data/lib/steep/module_name.rb +116 -0
- data/lib/steep/parser.rb +1314 -814
- data/lib/steep/parser.y +536 -175
- data/lib/steep/source.rb +220 -25
- data/lib/steep/subtyping/check.rb +673 -0
- data/lib/steep/subtyping/constraints.rb +275 -0
- data/lib/steep/subtyping/relation.rb +41 -0
- data/lib/steep/subtyping/result.rb +126 -0
- data/lib/steep/subtyping/trace.rb +48 -0
- data/lib/steep/subtyping/variable_occurrence.rb +49 -0
- data/lib/steep/subtyping/variable_variance.rb +69 -0
- data/lib/steep/type_construction.rb +1630 -524
- data/lib/steep/type_inference/block_params.rb +100 -0
- data/lib/steep/type_inference/constant_env.rb +55 -0
- data/lib/steep/type_inference/send_args.rb +222 -0
- data/lib/steep/type_inference/type_env.rb +226 -0
- data/lib/steep/type_name.rb +27 -7
- data/lib/steep/typing.rb +4 -0
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +71 -16
- data/smoke/and/a.rb +4 -2
- data/smoke/array/a.rb +4 -5
- data/smoke/array/b.rb +4 -4
- data/smoke/block/a.rb +2 -2
- data/smoke/block/a.rbi +2 -0
- data/smoke/block/b.rb +15 -0
- data/smoke/case/a.rb +3 -3
- data/smoke/class/a.rb +3 -3
- data/smoke/class/b.rb +0 -2
- data/smoke/class/d.rb +2 -2
- data/smoke/class/e.rb +1 -1
- data/smoke/class/f.rb +2 -2
- data/smoke/class/g.rb +8 -0
- data/smoke/const/a.rb +3 -3
- data/smoke/dstr/a.rb +1 -1
- data/smoke/ensure/a.rb +22 -0
- data/smoke/enumerator/a.rb +6 -6
- data/smoke/enumerator/b.rb +22 -0
- data/smoke/extension/a.rb +2 -2
- data/smoke/extension/b.rb +3 -3
- data/smoke/extension/c.rb +1 -1
- data/smoke/hello/hello.rb +2 -2
- data/smoke/if/a.rb +4 -2
- data/smoke/kwbegin/a.rb +8 -0
- data/smoke/literal/a.rb +5 -5
- data/smoke/method/a.rb +5 -5
- data/smoke/method/a.rbi +4 -0
- data/smoke/method/b.rb +29 -0
- data/smoke/module/a.rb +3 -3
- data/smoke/module/a.rbi +9 -0
- data/smoke/module/b.rb +2 -2
- data/smoke/module/c.rb +1 -1
- data/smoke/module/d.rb +5 -0
- data/smoke/module/e.rb +13 -0
- data/smoke/module/f.rb +13 -0
- data/smoke/rescue/a.rb +62 -0
- data/smoke/super/a.rb +2 -2
- data/smoke/type_case/a.rb +35 -0
- data/smoke/yield/a.rb +2 -2
- data/stdlib/builtin.rbi +463 -24
- data/steep.gemspec +3 -2
- metadata +91 -29
- data/lib/steep/annotation.rb +0 -223
- data/lib/steep/signature/class.rb +0 -450
- data/lib/steep/signature/extension.rb +0 -51
- data/lib/steep/signature/interface.rb +0 -49
- data/lib/steep/types/any.rb +0 -31
- data/lib/steep/types/class.rb +0 -27
- data/lib/steep/types/instance.rb +0 -27
- data/lib/steep/types/merge.rb +0 -32
- data/lib/steep/types/name.rb +0 -57
- data/lib/steep/types/union.rb +0 -42
- data/lib/steep/types/var.rb +0 -38
- data/lib/steep/types.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e44524f5daae1f0506108ead6a30bc8abff67ab100d2639e21dd75e3384dab7e
|
4
|
+
data.tar.gz: 1a5595ef390995476f5d6dd88fb3c1684bdca27a87ec26d048b071e4a5ccf9d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 97c834ae154ac5bc617f4f65be66820531524888237ace9bc316c9cbecb1e2433f8a40b947c9c4d1ae97da7701b55622fe2a7407464fb9db90666e6fd790897b
|
7
|
+
data.tar.gz: fe8b9e5ab3367df5b28281a0179b58af6b96fd497ee84293b69533808d2b52ffa7ebc801021456fd0fc528c21d2cd07eb63f9b6ca49899052b9cf746eb184cdd
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -4,73 +4,181 @@
|
|
4
4
|
|
5
5
|
Install via RubyGems.
|
6
6
|
|
7
|
-
$ gem install steep
|
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.
|
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
|
18
|
+
### 1. Declare Types
|
21
19
|
|
22
|
-
Declare
|
20
|
+
Declare types in `.rbi` files in `sig` directory.
|
23
21
|
|
24
22
|
```
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
}
|
33
|
+
class Email
|
34
|
+
@address: String
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
def initialize: (address: String) -> any
|
37
|
+
def address: -> String
|
38
|
+
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
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
|
-
|
58
|
+
### 2. Write Ruby Code
|
59
|
+
|
60
|
+
Write Ruby code with annotations.
|
45
61
|
|
46
62
|
```rb
|
47
|
-
class
|
48
|
-
#
|
49
|
-
#
|
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
|
55
|
-
|
72
|
+
def initialize(name:)
|
73
|
+
@name = name
|
74
|
+
@contacts = []
|
56
75
|
end
|
57
76
|
|
58
|
-
def
|
59
|
-
|
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
|
70
|
-
|
71
|
-
|
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
|
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
|
-
|
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
|
-
|
90
|
-
|
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 =~
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|