steep 0.1.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/README.md +95 -0
  6. data/Rakefile +23 -0
  7. data/bin/console +14 -0
  8. data/bin/setup +8 -0
  9. data/bin/smoke_runner.rb +106 -0
  10. data/exe/steep +18 -0
  11. data/lib/steep.rb +33 -0
  12. data/lib/steep/annotation.rb +223 -0
  13. data/lib/steep/cli.rb +79 -0
  14. data/lib/steep/drivers/check.rb +141 -0
  15. data/lib/steep/errors.rb +207 -0
  16. data/lib/steep/interface.rb +280 -0
  17. data/lib/steep/parser.y +311 -0
  18. data/lib/steep/signature/class.rb +358 -0
  19. data/lib/steep/signature/errors.rb +78 -0
  20. data/lib/steep/signature/extension.rb +51 -0
  21. data/lib/steep/signature/interface.rb +48 -0
  22. data/lib/steep/source.rb +98 -0
  23. data/lib/steep/type_assignability.rb +362 -0
  24. data/lib/steep/type_construction.rb +993 -0
  25. data/lib/steep/type_name.rb +37 -0
  26. data/lib/steep/types.rb +4 -0
  27. data/lib/steep/types/any.rb +31 -0
  28. data/lib/steep/types/class.rb +27 -0
  29. data/lib/steep/types/instance.rb +27 -0
  30. data/lib/steep/types/merge.rb +32 -0
  31. data/lib/steep/types/name.rb +57 -0
  32. data/lib/steep/types/union.rb +42 -0
  33. data/lib/steep/types/var.rb +38 -0
  34. data/lib/steep/typing.rb +70 -0
  35. data/lib/steep/version.rb +3 -0
  36. data/manual/annotations.md +144 -0
  37. data/sig/signature.rbi +54 -0
  38. data/sig/types.rbi +43 -0
  39. data/smoke/and/a.rb +9 -0
  40. data/smoke/array/a.rb +22 -0
  41. data/smoke/block/a.rb +12 -0
  42. data/smoke/block/a.rbi +4 -0
  43. data/smoke/case/a.rb +20 -0
  44. data/smoke/class/a.rb +31 -0
  45. data/smoke/class/a.rbi +9 -0
  46. data/smoke/class/b.rb +7 -0
  47. data/smoke/class/c.rb +10 -0
  48. data/smoke/const/a.rb +30 -0
  49. data/smoke/dstr/a.rb +6 -0
  50. data/smoke/extension/a.rb +11 -0
  51. data/smoke/extension/a.rbi +8 -0
  52. data/smoke/extension/b.rb +12 -0
  53. data/smoke/extension/c.rb +9 -0
  54. data/smoke/hello/hello.rb +13 -0
  55. data/smoke/hello/hello.rbi +7 -0
  56. data/smoke/if/a.rb +20 -0
  57. data/smoke/implements/a.rb +14 -0
  58. data/smoke/implements/a.rbi +6 -0
  59. data/smoke/literal/a.rb +16 -0
  60. data/smoke/map/a.rb +5 -0
  61. data/smoke/method/a.rb +26 -0
  62. data/smoke/method/a.rbi +0 -0
  63. data/smoke/module/a.rb +21 -0
  64. data/smoke/module/a.rbi +7 -0
  65. data/smoke/module/b.rb +8 -0
  66. data/smoke/self/a.rb +23 -0
  67. data/smoke/self/a.rbi +4 -0
  68. data/smoke/super/a.rb +34 -0
  69. data/smoke/super/a.rbi +10 -0
  70. data/smoke/yield/a.rb +18 -0
  71. data/stdlib/builtin.rbi +89 -0
  72. data/steep.gemspec +32 -0
  73. 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
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /lib/steep/parser.output
11
+ /lib/steep/parser.rb
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.13.7
6
+ script: bundle exec rake test smoke
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in steep.gemspec
4
+ gemspec
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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