steep 0.1.0.pre
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 +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
|