steep 0.1.0.pre2 → 0.1.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.
- 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
|