steep 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +95 -0
- data/Rakefile +23 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/smoke_runner.rb +106 -0
- data/exe/steep +18 -0
- data/lib/steep.rb +33 -0
- data/lib/steep/annotation.rb +223 -0
- data/lib/steep/cli.rb +79 -0
- data/lib/steep/drivers/check.rb +141 -0
- data/lib/steep/errors.rb +207 -0
- data/lib/steep/interface.rb +280 -0
- data/lib/steep/parser.y +311 -0
- data/lib/steep/signature/class.rb +358 -0
- data/lib/steep/signature/errors.rb +78 -0
- data/lib/steep/signature/extension.rb +51 -0
- data/lib/steep/signature/interface.rb +48 -0
- data/lib/steep/source.rb +98 -0
- data/lib/steep/type_assignability.rb +362 -0
- data/lib/steep/type_construction.rb +993 -0
- data/lib/steep/type_name.rb +37 -0
- data/lib/steep/types.rb +4 -0
- data/lib/steep/types/any.rb +31 -0
- data/lib/steep/types/class.rb +27 -0
- data/lib/steep/types/instance.rb +27 -0
- data/lib/steep/types/merge.rb +32 -0
- data/lib/steep/types/name.rb +57 -0
- data/lib/steep/types/union.rb +42 -0
- data/lib/steep/types/var.rb +38 -0
- data/lib/steep/typing.rb +70 -0
- data/lib/steep/version.rb +3 -0
- data/manual/annotations.md +144 -0
- data/sig/signature.rbi +54 -0
- data/sig/types.rbi +43 -0
- data/smoke/and/a.rb +9 -0
- data/smoke/array/a.rb +22 -0
- data/smoke/block/a.rb +12 -0
- data/smoke/block/a.rbi +4 -0
- data/smoke/case/a.rb +20 -0
- data/smoke/class/a.rb +31 -0
- data/smoke/class/a.rbi +9 -0
- data/smoke/class/b.rb +7 -0
- data/smoke/class/c.rb +10 -0
- data/smoke/const/a.rb +30 -0
- data/smoke/dstr/a.rb +6 -0
- data/smoke/extension/a.rb +11 -0
- data/smoke/extension/a.rbi +8 -0
- data/smoke/extension/b.rb +12 -0
- data/smoke/extension/c.rb +9 -0
- data/smoke/hello/hello.rb +13 -0
- data/smoke/hello/hello.rbi +7 -0
- data/smoke/if/a.rb +20 -0
- data/smoke/implements/a.rb +14 -0
- data/smoke/implements/a.rbi +6 -0
- data/smoke/literal/a.rb +16 -0
- data/smoke/map/a.rb +5 -0
- data/smoke/method/a.rb +26 -0
- data/smoke/method/a.rbi +0 -0
- data/smoke/module/a.rb +21 -0
- data/smoke/module/a.rbi +7 -0
- data/smoke/module/b.rb +8 -0
- data/smoke/self/a.rb +23 -0
- data/smoke/self/a.rbi +4 -0
- data/smoke/super/a.rb +34 -0
- data/smoke/super/a.rbi +10 -0
- data/smoke/yield/a.rb +18 -0
- data/stdlib/builtin.rbi +89 -0
- data/steep.gemspec +32 -0
- metadata +214 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 91398a985c422d844373a28dadd59f8e55b73e8f
|
4
|
+
data.tar.gz: bdf3272c32938f7b5e762cfffce099b26630115b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1345b14455a625814b4beafcab5ad3327f47b2dd0be200a5ecbc73157c83ab8074b6069a80acffb8fe563987c042f133820c33c07d1a7baa4039d29e35438735
|
7
|
+
data.tar.gz: 455b3c6502ef94579bb859f12cbaca314285f426cf1a0b3cf5a9cad0c1533664738f8674b20ebde46ef8b8fcaa443ace1ece38f57922e5427465771afbf98f77
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Steep - Gradual Typing for Ruby
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'steep'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install steep
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Run `steep check` command in project dir.
|
22
|
+
|
23
|
+
```
|
24
|
+
$ steep check
|
25
|
+
$ steep check lib
|
26
|
+
```
|
27
|
+
|
28
|
+
It loads signatures from the global registry and `sig` dir in current dir.
|
29
|
+
If you want to load signuatres from dir different from `sig`, pass `-I` parameter.
|
30
|
+
|
31
|
+
```
|
32
|
+
$ steep check -I signauture -I sig .
|
33
|
+
```
|
34
|
+
|
35
|
+
Note that when `-I` option is given, `steep` does not load signatures from `sig` dir.
|
36
|
+
|
37
|
+
## Type Annotations
|
38
|
+
|
39
|
+
You have to write type annotations in your Ruby program.
|
40
|
+
|
41
|
+
```rb
|
42
|
+
# @import ActiveRecord.Try
|
43
|
+
|
44
|
+
class Foo
|
45
|
+
# @class Foo<A> extends Object
|
46
|
+
|
47
|
+
# @attribute results: (readonly) Array<A>
|
48
|
+
attr_reader :results
|
49
|
+
|
50
|
+
# @type initialize: () -> _
|
51
|
+
def initialize()
|
52
|
+
@results = []
|
53
|
+
end
|
54
|
+
|
55
|
+
# @type plus: (Addable<X, A>, X) -> A
|
56
|
+
def plus(x, y)
|
57
|
+
(x + y).try do |a|
|
58
|
+
results << a
|
59
|
+
a
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Signature
|
66
|
+
|
67
|
+
Steep does not allow types to be constructed from Ruby programs.
|
68
|
+
You have to write down signatures by yourself.
|
69
|
+
|
70
|
+
### Signature Scaffolding
|
71
|
+
|
72
|
+
Steep allows generate a *scaffold* from Ruby programs.
|
73
|
+
|
74
|
+
```
|
75
|
+
$ steep scaffold lib/**/*.rb
|
76
|
+
```
|
77
|
+
|
78
|
+
The generated scaffold includes:
|
79
|
+
|
80
|
+
* Signature definition for each class/module defined in the given program
|
81
|
+
* Method definition stub for each method
|
82
|
+
|
83
|
+
The scaffold may be a good starting point for writing signatures.
|
84
|
+
|
85
|
+
|
86
|
+
## Development
|
87
|
+
|
88
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
89
|
+
|
90
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
91
|
+
|
92
|
+
## Contributing
|
93
|
+
|
94
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/steep.
|
95
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rake/testtask"
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << "test"
|
6
|
+
t.libs << "lib"
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :test
|
11
|
+
task :build => :racc
|
12
|
+
task :test => :racc
|
13
|
+
task :install => :racc
|
14
|
+
|
15
|
+
rule /\.rb/ => ".y" do |t|
|
16
|
+
sh "racc", "-v", "-o", "#{t.name}", "#{t.source}"
|
17
|
+
end
|
18
|
+
|
19
|
+
task :racc => "lib/steep/parser.rb"
|
20
|
+
|
21
|
+
task :smoke do
|
22
|
+
sh "bundle", "exec", "bin/smoke_runner.rb", *Dir.glob("smoke/*")
|
23
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "steep"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/bin/smoke_runner.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
$LOAD_PATH << Pathname(__dir__) + "../lib"
|
6
|
+
|
7
|
+
require "steep"
|
8
|
+
require "rainbow"
|
9
|
+
require "optparse"
|
10
|
+
|
11
|
+
verbose = false
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.on("-v", "--verbose") do verbose = true end
|
15
|
+
end.parse!(ARGV)
|
16
|
+
|
17
|
+
Expectation = Struct.new(:line, :message)
|
18
|
+
|
19
|
+
failed = false
|
20
|
+
|
21
|
+
ARGV.each do |arg|
|
22
|
+
dir = Pathname(arg)
|
23
|
+
puts "🏇 Running smoke test in #{dir}..."
|
24
|
+
|
25
|
+
rb_files = []
|
26
|
+
expectations = []
|
27
|
+
|
28
|
+
dir.children.each do |file|
|
29
|
+
if file.extname == ".rb"
|
30
|
+
buffer = ::Parser::Source::Buffer.new(file.to_s)
|
31
|
+
buffer.source = file.read
|
32
|
+
parser = ::Parser::CurrentRuby.new
|
33
|
+
|
34
|
+
_, comments, _ = parser.tokenize(buffer)
|
35
|
+
comments.each do |comment|
|
36
|
+
src = comment.text.gsub(/\A#\s*/, '')
|
37
|
+
|
38
|
+
if src =~ /!expects(@(\+\d+))?/
|
39
|
+
offset = $2&.to_i || 1
|
40
|
+
message = src.gsub!(/\A!expects(@\+\d+)? +/, '')
|
41
|
+
line = comment.location.line
|
42
|
+
|
43
|
+
expectations << Expectation.new(line+offset, message)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
rb_files << file
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
stderr = StringIO.new
|
52
|
+
stdout = StringIO.new
|
53
|
+
|
54
|
+
builtin = Pathname(__dir__) + "../stdlib"
|
55
|
+
begin
|
56
|
+
driver = Steep::Drivers::Check.new(source_paths: rb_files,
|
57
|
+
signature_dirs: [builtin, dir],
|
58
|
+
stdout: stdout,
|
59
|
+
stderr: stderr)
|
60
|
+
|
61
|
+
driver.run
|
62
|
+
rescue => exn
|
63
|
+
puts "ERROR: #{exn.inspect}"
|
64
|
+
exn.backtrace.each do |loc|
|
65
|
+
puts " #{loc}"
|
66
|
+
end
|
67
|
+
|
68
|
+
failed = true
|
69
|
+
end
|
70
|
+
|
71
|
+
if verbose
|
72
|
+
stdout.string.each_line do |line|
|
73
|
+
puts "stdout> #{line.chomp}"
|
74
|
+
end
|
75
|
+
|
76
|
+
stderr.string.each_line do |line|
|
77
|
+
puts "stderr> #{line.chomp}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
lines = stdout.string.each_line.to_a.map(&:chomp)
|
82
|
+
|
83
|
+
expectations.each do |expectation|
|
84
|
+
deleted = lines.reject! do |string|
|
85
|
+
string =~ /:#{expectation.line}:\d+: #{Regexp.quote expectation.message}\Z/
|
86
|
+
end
|
87
|
+
|
88
|
+
unless deleted
|
89
|
+
puts Rainbow(" 💀 Expected error not found: #{expectation.line}:#{expectation.message}").red
|
90
|
+
failed = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
unless lines.empty?
|
95
|
+
lines.each do |line|
|
96
|
+
puts Rainbow(" 🤦♀️ Unexpected error found: #{line}").red
|
97
|
+
end
|
98
|
+
failed = true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if failed
|
103
|
+
exit(1)
|
104
|
+
else
|
105
|
+
puts Rainbow("All smoke test pass 😆").blue
|
106
|
+
end
|
data/exe/steep
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
$LOAD_PATH << Pathname(__dir__) + "../lib"
|
6
|
+
|
7
|
+
require 'steep'
|
8
|
+
require 'steep/cli'
|
9
|
+
|
10
|
+
begin
|
11
|
+
Steep::CLI.new(argv: ARGV.dup, stdout: STDOUT, stderr: STDERR, stdin: STDIN).run
|
12
|
+
rescue => exn
|
13
|
+
STDERR.puts exn.inspect
|
14
|
+
exn.backtrace.each do |t|
|
15
|
+
STDERR.puts " #{t}"
|
16
|
+
end
|
17
|
+
exit 2
|
18
|
+
end
|
data/lib/steep.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "steep/version"
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "parser/current"
|
5
|
+
require "ast_utils"
|
6
|
+
|
7
|
+
require "steep/types"
|
8
|
+
require "steep/type_name"
|
9
|
+
require "steep/interface"
|
10
|
+
require "steep/signature/class"
|
11
|
+
require "steep/signature/interface"
|
12
|
+
require "steep/signature/errors"
|
13
|
+
require "steep/signature/extension"
|
14
|
+
require "steep/types/any"
|
15
|
+
require "steep/types/name"
|
16
|
+
require "steep/types/var"
|
17
|
+
require "steep/types/union"
|
18
|
+
require "steep/types/class"
|
19
|
+
require "steep/types/instance"
|
20
|
+
require "steep/types/merge"
|
21
|
+
require "steep/type_assignability"
|
22
|
+
require "steep/parser"
|
23
|
+
require "steep/annotation"
|
24
|
+
require "steep/source"
|
25
|
+
require "steep/typing"
|
26
|
+
require "steep/errors"
|
27
|
+
require "steep/type_construction"
|
28
|
+
|
29
|
+
require "steep/drivers/check"
|
30
|
+
|
31
|
+
module Steep
|
32
|
+
# Your code goes here...
|
33
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Steep
|
2
|
+
module Annotation
|
3
|
+
class Base; end
|
4
|
+
|
5
|
+
class VarType < Base
|
6
|
+
attr_reader :var
|
7
|
+
attr_reader :type
|
8
|
+
|
9
|
+
def initialize(var:, type:)
|
10
|
+
@var = var
|
11
|
+
@type = type
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
other.is_a?(VarType) &&
|
16
|
+
other.var == var &&
|
17
|
+
other.type == type
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class MethodType < Base
|
22
|
+
attr_reader :method
|
23
|
+
attr_reader :type
|
24
|
+
|
25
|
+
def initialize(method:, type:)
|
26
|
+
@method = method
|
27
|
+
@type = type
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
other.is_a?(MethodType) &&
|
32
|
+
other.method == method &&
|
33
|
+
other.type == type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ReturnType < Base
|
38
|
+
attr_reader :type
|
39
|
+
|
40
|
+
def initialize(type:)
|
41
|
+
@type = type
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
other.is_a?(ReturnType) && other.type == type
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class BlockType < Base
|
50
|
+
attr_reader :type
|
51
|
+
|
52
|
+
def initialize(type:)
|
53
|
+
@type = type
|
54
|
+
end
|
55
|
+
|
56
|
+
def ==(other)
|
57
|
+
other.is_a?(BlockType) && other.type == type
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SelfType < Base
|
62
|
+
attr_reader :type
|
63
|
+
|
64
|
+
def initialize(type:)
|
65
|
+
@type = type
|
66
|
+
end
|
67
|
+
|
68
|
+
def ==(other)
|
69
|
+
other.is_a?(SelfType) && other.type == type
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class InstanceType < Base
|
74
|
+
attr_reader :type
|
75
|
+
|
76
|
+
def initialize(type:)
|
77
|
+
@type = type
|
78
|
+
end
|
79
|
+
|
80
|
+
def ==(other)
|
81
|
+
other.is_a?(InstanceType) && other.type == type
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class ModuleType < Base
|
86
|
+
attr_reader :type
|
87
|
+
|
88
|
+
def initialize(type:)
|
89
|
+
@type = type
|
90
|
+
end
|
91
|
+
|
92
|
+
def ==(other)
|
93
|
+
other.is_a?(ModuleType) && other.type == type
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Implements < Base
|
98
|
+
attr_reader :module_name
|
99
|
+
|
100
|
+
def initialize(module_name:)
|
101
|
+
@module_name = module_name
|
102
|
+
end
|
103
|
+
|
104
|
+
def ==(other)
|
105
|
+
other.is_a?(Implements) && other.module_name == module_name
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class NameType < Base
|
110
|
+
attr_reader :name
|
111
|
+
attr_reader :type
|
112
|
+
|
113
|
+
def initialize(name:, type:)
|
114
|
+
@name = name
|
115
|
+
@type = type
|
116
|
+
end
|
117
|
+
|
118
|
+
def ==(other)
|
119
|
+
other.is_a?(self.class) && other.name == name && other.type == type
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class ConstType < NameType
|
124
|
+
end
|
125
|
+
|
126
|
+
class IvarType < NameType
|
127
|
+
end
|
128
|
+
|
129
|
+
class Dynamic < Base
|
130
|
+
attr_reader :name
|
131
|
+
|
132
|
+
def initialize(name:)
|
133
|
+
@name = name
|
134
|
+
end
|
135
|
+
|
136
|
+
def ==(other)
|
137
|
+
other.is_a?(Dynamic) && other.name == name
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Collection
|
142
|
+
attr_reader :var_types
|
143
|
+
attr_reader :method_types
|
144
|
+
attr_reader :annotations
|
145
|
+
attr_reader :block_type
|
146
|
+
attr_reader :return_type
|
147
|
+
attr_reader :self_type
|
148
|
+
attr_reader :const_types
|
149
|
+
attr_reader :instance_type
|
150
|
+
attr_reader :module_type
|
151
|
+
attr_reader :implement_module
|
152
|
+
attr_reader :ivar_types
|
153
|
+
attr_reader :dynamics
|
154
|
+
|
155
|
+
def initialize(annotations:)
|
156
|
+
@var_types = {}
|
157
|
+
@method_types = {}
|
158
|
+
@const_types = {}
|
159
|
+
@ivar_types = {}
|
160
|
+
@dynamics = Set.new
|
161
|
+
|
162
|
+
annotations.each do |annotation|
|
163
|
+
case annotation
|
164
|
+
when VarType
|
165
|
+
var_types[annotation.var] = annotation
|
166
|
+
when MethodType
|
167
|
+
method_types[annotation.method] = annotation
|
168
|
+
when BlockType
|
169
|
+
@block_type = annotation.type
|
170
|
+
when ReturnType
|
171
|
+
@return_type = annotation.type
|
172
|
+
when SelfType
|
173
|
+
@self_type = annotation.type
|
174
|
+
when ConstType
|
175
|
+
@const_types[annotation.name] = annotation.type
|
176
|
+
when InstanceType
|
177
|
+
@instance_type = annotation.type
|
178
|
+
when ModuleType
|
179
|
+
@module_type = annotation.type
|
180
|
+
when Implements
|
181
|
+
@implement_module = annotation.module_name
|
182
|
+
when IvarType
|
183
|
+
ivar_types[annotation.name] = annotation.type
|
184
|
+
when Dynamic
|
185
|
+
dynamics << annotation.name
|
186
|
+
else
|
187
|
+
raise "Unexpected annotation: #{annotation.inspect}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
@annotations = annotations
|
192
|
+
end
|
193
|
+
|
194
|
+
def lookup_var_type(name)
|
195
|
+
var_types[name]&.type
|
196
|
+
end
|
197
|
+
|
198
|
+
def lookup_method_type(name)
|
199
|
+
method_types[name]&.type
|
200
|
+
end
|
201
|
+
|
202
|
+
def lookup_const_type(node)
|
203
|
+
const_types[node]
|
204
|
+
end
|
205
|
+
|
206
|
+
def +(other)
|
207
|
+
self.class.new(annotations: annotations.reject {|a| a.is_a?(BlockType) } + other.annotations)
|
208
|
+
end
|
209
|
+
|
210
|
+
def any?(&block)
|
211
|
+
annotations.any?(&block)
|
212
|
+
end
|
213
|
+
|
214
|
+
def size
|
215
|
+
annotations.size
|
216
|
+
end
|
217
|
+
|
218
|
+
def include?(obj)
|
219
|
+
annotations.include?(obj)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|