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.
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