orthoses 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -1
  3. data/Gemfile.lock +25 -22
  4. data/README.md +8 -0
  5. data/examples/rack-test/Gemfile +8 -0
  6. data/examples/rack-test/Gemfile.lock +30 -0
  7. data/examples/rack-test/Rakefile +7 -0
  8. data/examples/rack-test/generate.rb +31 -0
  9. data/lib/orthoses/call_tracer/capturable.rb +2 -2
  10. data/lib/orthoses/call_tracer/lazy.rb +12 -0
  11. data/lib/orthoses/content/duplication_checker.rb +1 -1
  12. data/lib/orthoses/content/environment.rb +2 -1
  13. data/lib/orthoses/content/header_builder.rb +4 -5
  14. data/lib/orthoses/content.rb +21 -7
  15. data/lib/orthoses/sort.rb +18 -0
  16. data/lib/orthoses/trace/attribute.rb +85 -0
  17. data/lib/orthoses/trace/method.rb +202 -0
  18. data/lib/orthoses/trace/targetable.rb +17 -0
  19. data/lib/orthoses/trace.rb +21 -0
  20. data/lib/orthoses/utils/type_list.rb +73 -0
  21. data/lib/orthoses/utils.rb +23 -32
  22. data/lib/orthoses/version.rb +1 -1
  23. data/lib/orthoses.rb +4 -1
  24. data/orthoses.gemspec +1 -1
  25. data/sig/orthoses/call_tracer/lazy.rbs +2 -1
  26. data/sig/orthoses/call_tracer.rbs +1 -1
  27. data/sig/orthoses/content/header_builder.rbs +1 -0
  28. data/sig/orthoses/content.rbs +2 -0
  29. data/sig/orthoses/lazy_trace_point.rbs +1 -1
  30. data/sig/orthoses/sort.rbs +6 -0
  31. data/sig/orthoses/trace/attribute.rbs +8 -0
  32. data/sig/orthoses/trace/method/info.rbs +4 -0
  33. data/sig/orthoses/trace/method.rbs +11 -0
  34. data/sig/orthoses/trace/targetable.rbs +5 -0
  35. data/sig/orthoses/trace.rbs +6 -0
  36. data/sig/orthoses/utils/type_list.rbs +10 -0
  37. data/sig/orthoses/utils.rbs +1 -3
  38. data/sig/orthoses.rbs +1 -1
  39. metadata +22 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ee37ddf25be5bbed113698301a2aeafb3b50e7af1da412b9a1fc6afad18d8c2
4
- data.tar.gz: '075957101080441e02f4129303e766ecdfe1e7f30616177bc41a2cbf41f521f8'
3
+ metadata.gz: 0bde1745c71d6d30b7b45ce7f956a1c02f0d44353932c3743211aa1c05f58d39
4
+ data.tar.gz: bbe5b95a7665d39faab82b5f53e18cffd22962df1913f1c52698ea51c6045e2c
5
5
  SHA512:
6
- metadata.gz: 98c1cff14563ecf14713890e56bddb053d6f128aacad7ff23b6f96cc8b2c338cbea511cc668ac18079ec921cb0183b145f6b26a3c04403a98e8aa0bf734942f7
7
- data.tar.gz: 5dd573a63a6998e3a1fac1dd94f292dd80742185277124f6449ded149be662b74995815bad629157fc553cb8bef15bb31b123fdbcdccb1af3dff4d2645bc0da2
6
+ metadata.gz: 496d255f99751ac854e5b05ecac69b1e0dc7dbb5fca53bf99d6d622e2dbb154af643de7bf8de8190dca15ff1933fac35c17735da9285532a8a3246d7002836cd
7
+ data.tar.gz: 56c77cf9074eeb75547c1be3868e9843b0d5337400d2248592879f76fd6cc14f99660d8dbbf593c1d66bac1152e1dafbaa4820ea45a49f5f47945027ce45d53a
data/Gemfile CHANGED
@@ -8,4 +8,8 @@ gemspec
8
8
  gem "rbs"
9
9
  gem "rake", "~> 13.0"
10
10
  gem "rgot", "~> 1.1"
11
- gem "steep"
11
+ gem "activesupport"
12
+
13
+ group :steep do
14
+ gem "steep", "1.4.0.dev.2"
15
+ end
data/Gemfile.lock CHANGED
@@ -1,43 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- orthoses (1.4.0)
5
- rbs (~> 2.0)
4
+ orthoses (1.6.0)
5
+ rbs (~> 3.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activesupport (7.0.4)
10
+ activesupport (7.0.4.3)
11
11
  concurrent-ruby (~> 1.0, >= 1.0.2)
12
12
  i18n (>= 1.6, < 2)
13
13
  minitest (>= 5.1)
14
14
  tzinfo (~> 2.0)
15
15
  ast (2.4.2)
16
- concurrent-ruby (1.1.10)
17
- csv (3.2.5)
16
+ concurrent-ruby (1.2.2)
17
+ csv (3.2.6)
18
18
  ffi (1.15.5)
19
- fileutils (1.6.0)
19
+ fileutils (1.7.0)
20
20
  i18n (1.12.0)
21
21
  concurrent-ruby (~> 1.0)
22
- json (2.6.2)
23
- language_server-protocol (3.17.0.1)
24
- listen (3.7.1)
22
+ json (2.6.3)
23
+ language_server-protocol (3.17.0.3)
24
+ listen (3.8.0)
25
25
  rb-fsevent (~> 0.10, >= 0.10.3)
26
26
  rb-inotify (~> 0.9, >= 0.9.10)
27
- logger (1.5.1)
28
- minitest (5.16.3)
27
+ logger (1.5.3)
28
+ minitest (5.18.0)
29
29
  parallel (1.22.1)
30
- parser (3.1.2.1)
30
+ parser (3.2.1.1)
31
31
  ast (~> 2.4.1)
32
+ pathname (0.2.1)
32
33
  rainbow (3.1.1)
33
34
  rake (13.0.6)
34
35
  rb-fsevent (0.11.2)
35
36
  rb-inotify (0.10.1)
36
37
  ffi (~> 1.0)
37
- rbs (2.7.0)
38
- rgot (1.1.0)
39
- securerandom (0.2.0)
40
- steep (1.2.1)
38
+ rbs (3.0.4)
39
+ rgot (1.3.0)
40
+ securerandom (0.2.2)
41
+ steep (1.4.0.dev.2)
41
42
  activesupport (>= 5.1)
42
43
  csv (>= 3.0.9)
43
44
  fileutils (>= 1.1.0)
@@ -47,27 +48,29 @@ GEM
47
48
  logger (>= 1.3.0)
48
49
  parallel (>= 1.0.0)
49
50
  parser (>= 3.1)
51
+ pathname (>= 0.2.1)
50
52
  rainbow (>= 2.2.2, < 4.0)
51
- rbs (>= 2.7.0)
53
+ rbs (>= 2.8.0)
52
54
  securerandom (>= 0.1)
53
55
  strscan (>= 1.0.0)
54
56
  terminal-table (>= 2, < 4)
55
- strscan (3.0.4)
57
+ strscan (3.0.6)
56
58
  terminal-table (3.0.2)
57
59
  unicode-display_width (>= 1.1.1, < 3)
58
- tzinfo (2.0.5)
60
+ tzinfo (2.0.6)
59
61
  concurrent-ruby (~> 1.0)
60
- unicode-display_width (2.3.0)
62
+ unicode-display_width (2.4.2)
61
63
 
62
64
  PLATFORMS
63
65
  ruby
64
66
 
65
67
  DEPENDENCIES
68
+ activesupport
66
69
  orthoses!
67
70
  rake (~> 13.0)
68
71
  rbs
69
72
  rgot (~> 1.1)
70
- steep
73
+ steep (= 1.4.0.dev.2)
71
74
 
72
75
  BUNDLED WITH
73
- 2.3.9
76
+ 2.4.8
data/README.md CHANGED
@@ -169,6 +169,14 @@ Run `rbs prototype runtime` command process with `patterns` option string.
169
169
 
170
170
  Force load const defined by `autoload`.
171
171
 
172
+ ### Orthoses::Sort
173
+
174
+ Sort signatures by class/module.
175
+
176
+ ## Orthoses::Trace
177
+
178
+ Trace the argument and return value objects when the method is actually called and determine the type.
179
+
172
180
  ## Development
173
181
 
174
182
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rack"
4
+ gem "rake"
5
+ gem "cgi"
6
+ gem "minitest"
7
+ gem "minitest-global_expectations"
8
+ gem "orthoses", path: "../../"
@@ -0,0 +1,30 @@
1
+ PATH
2
+ remote: ../..
3
+ specs:
4
+ orthoses (1.4.0)
5
+ rbs (~> 2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ cgi (0.3.6)
11
+ minitest (5.16.3)
12
+ minitest-global_expectations (1.0.1)
13
+ minitest (> 5)
14
+ rack (3.0.2)
15
+ rake (13.0.6)
16
+ rbs (2.8.1)
17
+
18
+ PLATFORMS
19
+ arm64-darwin-21
20
+
21
+ DEPENDENCIES
22
+ cgi
23
+ minitest
24
+ minitest-global_expectations
25
+ orthoses!
26
+ rack
27
+ rake
28
+
29
+ BUNDLED WITH
30
+ 2.3.16
@@ -0,0 +1,7 @@
1
+ file :src do
2
+ sh "git clone --depth 1 https://github.com/rack/rack-test.git src"
3
+ end
4
+
5
+ task :generate => :src do
6
+ load "generate.rb"
7
+ end
@@ -0,0 +1,31 @@
1
+ require 'pathname'
2
+ require 'orthoses'
3
+ require 'fileutils'
4
+
5
+ include FileUtils
6
+
7
+ out = Pathname('out')
8
+ out.rmtree rescue nil
9
+ Orthoses.logger.level = :warn
10
+ Orthoses::Builder.new do
11
+ use Orthoses::CreateFileByName,
12
+ base_dir: out.to_s
13
+ # use Orthoses::Sort
14
+ use Orthoses::Filter do |name, content|
15
+ name.start_with?("Rack::Test")
16
+ end
17
+ use Orthoses::Trace,
18
+ patterns: ['Rack::Test*']
19
+ run ->(){
20
+ cd "src" do
21
+ load "spec/all.rb"
22
+ Minitest.run
23
+ # avoid to run on at_exit
24
+ module ::Minitest
25
+ def self.run args = []
26
+ 0
27
+ end
28
+ end
29
+ end
30
+ }
31
+ end.call
@@ -25,13 +25,13 @@ module Orthoses
25
25
  else
26
26
  hash[name] = tp.binding.local_variable_get(name).then do |var|
27
27
  case var
28
- when Module, Thread::Backtrace::Location
28
+ when Module, Thread::Backtrace::Location, IO, Encoding
29
29
  var
30
30
  else
31
31
  var.dup
32
32
  end
33
33
  rescue => err
34
- warn("#dup fail (#{err.class}) #{err.message}")
34
+ warn("#{var.class}#dup failed with #{err.class}:#{err.message}")
35
35
  var
36
36
  end
37
37
  end
@@ -2,6 +2,18 @@
2
2
 
3
3
  module Orthoses
4
4
  class CallTracer
5
+ # CallTracer::Lazy is possible to perform a trace
6
+ # equivalent to CallTracer before method is defined.
7
+ # scope = CallTracerLazy.new
8
+ # scope.trace("ActiveRecord::Base#scope") do
9
+ # require 'active_record/all'
10
+ # @loader.call
11
+ # end
12
+ # scope.captures.each do |capture|
13
+ # capture.argument[:name]
14
+ # capture.argument[:body]
15
+ # capture.argument[:block]
16
+ # end
5
17
  class Lazy
6
18
  include Capturable
7
19
 
@@ -11,7 +11,7 @@ module Orthoses
11
11
  return unless @decl.respond_to?(:members)
12
12
  uniq_map = {}
13
13
  @decl.members.each do |member|
14
- if member.instance_of?(RBS::AST::Members::MethodDefinition) && member.overload
14
+ if member.instance_of?(RBS::AST::Members::MethodDefinition) && member.overloading?
15
15
  # avoid to duplicate and keep order
16
16
  uniq_map[Object.new] = member
17
17
  else
@@ -7,7 +7,8 @@ module Orthoses
7
7
  paths.each do |path|
8
8
  Orthoses.logger.debug("Load #{path}")
9
9
  buffer = RBS::Buffer.new(name: path.to_s, content: File.read(path.to_s, encoding: "UTF-8"))
10
- RBS::Parser.parse_signature(buffer).each do |decl|
10
+ _, _, decls = RBS::Parser.parse_signature(buffer)
11
+ decls.each do |decl|
11
12
  env << decl
12
13
  end
13
14
  end
@@ -3,7 +3,7 @@ module Orthoses
3
3
  class HeaderBuilder
4
4
  def initialize(env:)
5
5
  @env = env
6
- @resolver = RBS::TypeNameResolver.from_env(env)
6
+ @resolver = RBS::Resolver::TypeNameResolver.new(env)
7
7
  end
8
8
 
9
9
  def build(entry:, name_hint: nil)
@@ -66,11 +66,10 @@ module Orthoses
66
66
  "interface #{name_and_params(resolved_name.relative!, entry.decl.type_params)}"
67
67
  end
68
68
 
69
+ include RBS::Environment::ContextUtil
70
+
69
71
  def build_context(entry:)
70
- context = entry.outer.length.times.map do |i|
71
- entry.outer[0, i + 1].map(&:name).inject(:+).to_namespace.absolute!
72
- end
73
- context.push(RBS::Namespace.root)
72
+ calculate_context(entry.outer + [entry.decl])
74
73
  end
75
74
 
76
75
  def name_and_params(name, params)
@@ -60,12 +60,17 @@ module Orthoses
60
60
  end
61
61
 
62
62
  def uniq!
63
- out = ArrayIO.new
64
- writer = RBS::Writer.new(out: out)
65
- to_decl.members.each do |member|
66
- writer.write_member(member)
67
- end
68
- @body.replace(out.to_a)
63
+ lines = decl_to_lines(to_decl)
64
+ @body.replace(lines)
65
+ end
66
+
67
+ def sort!
68
+ require 'rbs/sorter'
69
+ sorter = ::RBS::Sorter.new(nil, stdout: nil)
70
+ decl = to_decl
71
+ sorter.sort_decl!(decl)
72
+ lines = decl_to_lines(decl)
73
+ @body.replace(lines)
69
74
  end
70
75
 
71
76
  def original_rbs
@@ -93,6 +98,15 @@ module Orthoses
93
98
  end
94
99
  end
95
100
 
101
+ def decl_to_lines(decl)
102
+ out = ArrayIO.new
103
+ writer = RBS::Writer.new(out: out)
104
+ decl.members.each do |member|
105
+ writer.write_member(member)
106
+ end
107
+ out.to_a
108
+ end
109
+
96
110
  def uniqed_body_string
97
111
  out = StringIO.new
98
112
  writer = RBS::Writer.new(out: out)
@@ -105,7 +119,7 @@ module Orthoses
105
119
  name: "orthoses/content.rb",
106
120
  content: original_rbs
107
121
  )
108
- parsed_decls = RBS::Parser.parse_signature(buffer)
122
+ _, _, parsed_decls = RBS::Parser.parse_signature(buffer)
109
123
  unless parsed_decls.length == 1
110
124
  raise "expect decls.length == 1, but got #{parsed_decls.length}"
111
125
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ # use Orthoses::Sort
5
+ class Sort
6
+ def initialize(loader)
7
+ @loader = loader
8
+ end
9
+
10
+ def call
11
+ @loader.call.tap do |store|
12
+ store.each do |name, content|
13
+ content.sort!
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ class Trace
5
+ class Attribute
6
+ include Targetable
7
+
8
+ def initialize(loader, patterns:)
9
+ @loader = loader
10
+ @patterns = patterns
11
+
12
+ @captured_dict = Hash.new { |h, k| h[k] = Hash.new { |hh, kk| hh[kk] = [] } }
13
+ end
14
+
15
+ def call
16
+ ::Module.prepend(Orthoses::Attribute::Hook)
17
+ store = nil
18
+ build_trace_hook.enable(target: Orthoses::Attribute::Hook.instance_method(:attr)) do
19
+ build_trace_hook.enable(target: Orthoses::Attribute::Hook.instance_method(:attr_accessor)) do
20
+ build_trace_hook.enable(target: Orthoses::Attribute::Hook.instance_method(:attr_reader)) do
21
+ build_trace_hook.enable(target: Orthoses::Attribute::Hook.instance_method(:attr_writer)) do
22
+ store = @loader.call
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ @captured_dict.each do |mod_name, captures|
29
+ captures.each do |(kind, prefix, name), types|
30
+ injected = Utils::TypeList.new(types).inject
31
+ store[mod_name] << "#{kind} #{prefix}#{name}: #{injected}"
32
+ end
33
+ end
34
+
35
+ store
36
+ end
37
+
38
+ private
39
+
40
+ def build_trace_hook
41
+ TracePoint.new(:call) do |tp1|
42
+ mod_name = Orthoses::Utils.module_name(tp1.self)
43
+ if m = tp1.self.to_s.match(/#<Class:([\w:]+)>/)
44
+ mod_name = m[1]
45
+ end
46
+ next unless mod_name
47
+ next unless target?(mod_name)
48
+ is_singleton = tp1.self.singleton_class?
49
+ prefix = is_singleton ? "self." : ""
50
+ kind = tp1.method_id
51
+
52
+ tp1.binding.local_variable_get(:names).each do |name|
53
+ attr_module = Module.new{
54
+ if [:attr_accessor, :attr_reader, :attr].include?(kind)
55
+ define_method(name) do
56
+ super()
57
+ end
58
+ end
59
+ if [:attr_accessor, :attr_writer, :attr].include?(kind)
60
+ define_method("#{name}=") do |object|
61
+ super(object)
62
+ end
63
+ end
64
+ }
65
+
66
+ tp1.self.prepend attr_module
67
+
68
+ if [:attr_accessor, :attr_reader, :attr].include?(kind)
69
+ TracePoint.new(:return) do |tp2|
70
+ type = Utils.object_to_rbs(tp2.return_value)
71
+ @captured_dict[mod_name][[kind, prefix, name]] << type
72
+ end.enable(target: attr_module.instance_method(name))
73
+ end
74
+ if [:attr_accessor, :attr_writer, :attr].include?(kind)
75
+ TracePoint.new(:call) do |tp2|
76
+ type = Utils.object_to_rbs(tp2.binding.local_variable_get(:object))
77
+ @captured_dict[mod_name][[kind, prefix, name]] << type
78
+ end.enable(target: attr_module.instance_method("#{name}="))
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ class Trace
5
+ class Method
6
+ # internal
7
+ Info = Struct.new(:key, :op_name_types, :raised, keyword_init: true)
8
+ include Targetable
9
+
10
+ def initialize(loader, patterns:)
11
+ @loader = loader
12
+ @patterns = patterns
13
+
14
+ @stack = []
15
+ @args_return_map = Hash.new { |h, k| h[k] = [] }
16
+ @alias_map = {}
17
+ end
18
+
19
+ def call
20
+ build_trace_point.enable do
21
+ @loader.call
22
+ end.tap do |store|
23
+ build_members.each do |(mod_name, member)|
24
+ out = StringIO.new
25
+ writer = ::RBS::Writer.new(out: out)
26
+ writer.write_member(member)
27
+ store[mod_name] << out.string
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def build_trace_point
35
+ TracePoint.new(:call, :return, :raise) do |tp|
36
+ case tp.event
37
+ when :call
38
+ if tp.defined_class.singleton_class?
39
+ # e.g. `Minitest::Spec::DSL#to_s`` may return `nil` with `#to_s`
40
+ m = tp.defined_class.to_s&.match(/#<Class:([\w:]+)>/) or next
41
+ mod_name = m[1] or next
42
+ kind = :singleton
43
+ else
44
+ mod_name = Utils.module_name(tp.defined_class) or next
45
+ kind = :instance
46
+ end
47
+
48
+ next unless target?(mod_name)
49
+
50
+ visibility = tp.self.private_methods.include?(tp.method_id) ? :private : nil
51
+ key = [mod_name, kind, visibility, tp.method_id]
52
+ op_name_types = tp.parameters.map do |op, name|
53
+ case name
54
+ when nil, :*, :**, :&
55
+ [op, name, 'untyped']
56
+ else
57
+ type = Utils.object_to_rbs(tp.binding.local_variable_get(name))
58
+ [op, name, type]
59
+ end
60
+ end
61
+
62
+ @alias_map[[mod_name, kind, tp.method_id]] = tp.callee_id if tp.method_id != tp.callee_id
63
+ @stack.push(Info.new(key: key, op_name_types: op_name_types, raised: false))
64
+ when :raise
65
+ @stack.last&.raised = true
66
+ when :return
67
+ info = @stack.pop
68
+ if info && !info.raised
69
+ @args_return_map[info.key] << [info.op_name_types, Utils.object_to_rbs(tp.return_value)]
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def build_members
76
+ build_method_definitions.concat(build_aliases)
77
+ end
78
+
79
+ def build_method_definitions
80
+ untyped = ::RBS::Types::Bases::Any.new(location: nil)
81
+
82
+ @args_return_map.map do |(mod_name, kind, visibility, method_id), type_samples|
83
+ type_samples.uniq!
84
+ method_types = type_samples.map do |(op_name_types, return_type)|
85
+ required_positionals = []
86
+ optional_positionals = []
87
+ rest = nil
88
+ trailing_positionals = []
89
+ required_keywords = {}
90
+ optional_keywords = {}
91
+ rest_keywords = nil
92
+
93
+ requireds = required_positionals
94
+
95
+ block = nil
96
+
97
+ op_name_types.each do |op, name, string_type|
98
+ type = ::RBS::Parser.parse_type(string_type)
99
+
100
+ case op
101
+ when :req
102
+ requireds << ::RBS::Types::Function::Param.new(name: name, type: type)
103
+ when :opt
104
+ requireds = trailing_positionals
105
+ optional_positionals << ::RBS::Types::Function::Param.new(name: name, type: type)
106
+ when :rest
107
+ requireds = trailing_positionals
108
+ name = nil if name == :* # For `def f(...) end` syntax
109
+ rest = ::RBS::Types::Function::Param.new(name: name, type: type)
110
+ when :keyreq
111
+ required_keywords[name] = ::RBS::Types::Function::Param.new(name: nil, type: type)
112
+ when :key
113
+ optional_keywords[name] = ::RBS::Types::Function::Param.new(name: nil, type: type)
114
+ when :keyrest
115
+ rest_keywords = ::RBS::Types::Function::Param.new(name: nil, type: type)
116
+ when :block
117
+ block = ::RBS::Types::Block.new(
118
+ type: ::RBS::Types::Function.empty(untyped).update(rest_positionals: ::RBS::Types::Function::Param.new(name: nil, type: untyped)),
119
+ required: false,
120
+ self_type: nil
121
+ )
122
+ end
123
+ end
124
+
125
+ return_type = if method_id == :initialize
126
+ ::RBS::Types::Bases::Void.new(location: nil)
127
+ else
128
+ ::RBS::Parser.parse_type(return_type)
129
+ end
130
+
131
+ ::RBS::MethodType.new(
132
+ location: nil,
133
+ type_params: [],
134
+ type: ::RBS::Types::Function.new(
135
+ required_positionals: required_positionals,
136
+ optional_positionals: optional_positionals,
137
+ rest_positionals: rest,
138
+ trailing_positionals: trailing_positionals,
139
+ required_keywords: required_keywords,
140
+ optional_keywords: optional_keywords,
141
+ rest_keywords: rest_keywords,
142
+ return_type: return_type,
143
+ ),
144
+ block: block
145
+ )
146
+ end
147
+
148
+ # from:
149
+ # def foo: () -> true
150
+ # | () -> false
151
+ # | (Integer) -> Integer
152
+ # to:
153
+ # def foo: () -> bool
154
+ # | (Integer) -> Integer
155
+ functions_without_return_type_map = method_types.group_by do |method_type|
156
+ method_type.type.with_return_type(untyped)
157
+ end
158
+ types = functions_without_return_type_map.map do |func, method_types_by_arg|
159
+ injected = Utils::TypeList.new(method_types_by_arg.map(&:type).map(&:return_type)).inject
160
+ method_type = method_types_by_arg.first
161
+ method_type.update(type: method_type.type.with_return_type(injected))
162
+ end
163
+
164
+ [
165
+ mod_name,
166
+ RBS::AST::Members::MethodDefinition.new(
167
+ annotations: [],
168
+ comment: nil,
169
+ kind: kind,
170
+ location: nil,
171
+ name: method_id,
172
+ overloading: false,
173
+ overloads: types.map { |type|
174
+ RBS::AST::Members::MethodDefinition::Overload.new(
175
+ annotations: [],
176
+ method_type: type
177
+ )
178
+ },
179
+ visibility: visibility
180
+ )
181
+ ]
182
+ end
183
+ end
184
+
185
+ def build_aliases
186
+ @alias_map.map do |(mod_name, kind, method_id), callee_id|
187
+ [
188
+ mod_name,
189
+ RBS::AST::Members::Alias.new(
190
+ new_name: callee_id,
191
+ old_name: method_id,
192
+ kind: kind,
193
+ annotations: [],
194
+ location: nil,
195
+ comment: nil,
196
+ )
197
+ ]
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ class Trace
5
+ module Targetable
6
+ def target?(name)
7
+ @patterns.any? do |pattern|
8
+ if pattern.end_with?("*")
9
+ (name || "").start_with?(pattern.chop)
10
+ else
11
+ name == pattern
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Orthoses
2
+ # Collect argument and return types during code running
3
+ # use Orthoses::Trace,
4
+ # patterns: ['Foo::Bar*']
5
+ class Trace
6
+ autoload :Attribute, 'orthoses/trace/attribute'
7
+ autoload :Method, 'orthoses/trace/method'
8
+ autoload :Targetable, 'orthoses/trace/targetable'
9
+
10
+ def initialize(loader, patterns:)
11
+ @loader = loader
12
+ @patterns = patterns
13
+ end
14
+
15
+ def call
16
+ @loader = Trace::Attribute.new(@loader, patterns: @patterns)
17
+ @loader = Trace::Method.new(@loader, patterns: @patterns)
18
+ @loader.call
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Orthoses
4
+ module Utils
5
+ # internal
6
+ class TypeList
7
+ UNTYPED = ::RBS::Types::Bases::Any.new(location: nil)
8
+ NIL_TYPE = ::RBS::Types::Bases::Nil.new(location: nil)
9
+ TRUE_TYPE = ::RBS::Types::Literal.new(literal: true, location: nil)
10
+ FALSE_TYPE = ::RBS::Types::Literal.new(literal: false, location: nil)
11
+
12
+ def initialize(types)
13
+ @types = types.map do |type|
14
+ case type
15
+ when String
16
+ ::RBS::Parser.parse_type(type)
17
+ else
18
+ type
19
+ end
20
+ end
21
+ end
22
+
23
+ def inject
24
+ uniqed = @types.uniq
25
+
26
+ return UNTYPED if uniqed.find { |t| t == UNTYPED }
27
+
28
+ has_true = uniqed.delete(TRUE_TYPE)
29
+ has_false = uniqed.delete(FALSE_TYPE)
30
+ if has_true || has_false
31
+ uniqed << ::RBS::Types::Bases::Bool.new(location: nil)
32
+ end
33
+
34
+ return UNTYPED if uniqed.empty?
35
+ return uniqed.first if uniqed.length == 1
36
+
37
+ args_or_other = uniqed.group_by do |type|
38
+ type.respond_to?(:args) && !type.args.empty?
39
+ end
40
+ if args_or_other[true]
41
+ # { Array => [Array[Intger], Array[String]], Hash => [Hash[untyped, untyped]], ... }
42
+ type_by_name = args_or_other[true].group_by do |type|
43
+ type.name
44
+ end
45
+ type_by_name.each do |name, types|
46
+ if with_untyped_type = types.find { |type| type.args.include?(UNTYPED) }
47
+ types.replace([with_untyped_type])
48
+ end
49
+ end
50
+ args_injected = type_by_name.values.flatten
51
+ args_injected.concat(args_or_other[false]) if args_or_other[false]
52
+ uniqed = args_injected
53
+ end
54
+
55
+ after_optional = uniqed.delete(NIL_TYPE)
56
+ injected = uniqed.inject do |r, type|
57
+ case r
58
+ when ::RBS::Types::Union
59
+ r.tap { r.types << type }
60
+ else
61
+ ::RBS::Types::Union.new(types: [r, type], location: nil)
62
+ end
63
+ end
64
+
65
+ if after_optional
66
+ return ::RBS::Types::Optional.new(type: injected, location: nil)
67
+ end
68
+
69
+ injected
70
+ end
71
+ end
72
+ end
73
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Orthoses
4
4
  module Utils
5
+ autoload :TypeList, "orthoses/utils/type_list"
5
6
  autoload :Underscore, "orthoses/utils/underscore"
6
7
 
7
8
  def self.unautoload!
@@ -49,28 +50,6 @@ module Orthoses
49
50
  RBS::Namespace.parse(name).to_type_name
50
51
  end
51
52
 
52
- class << self
53
- # Generated scripts are not always located in the root.
54
- # By default, it follows the upper level directory from the current directory
55
- # to find the rbs_collection.yaml file and set it to `rbs_collection_pathname`.
56
- # It can be reconfigured if necessary.
57
- attr_accessor :rbs_collection_pathname
58
- end
59
- # set default
60
- self.rbs_collection_pathname = ->() {
61
- begin
62
- start = here = Dir.pwd
63
- until ok = RBS::Collection::Config::PATH.exist?
64
- Dir.chdir("..")
65
- return nil if Dir.pwd == here
66
- here = Dir.pwd
67
- end
68
- Pathname(here) + RBS::Collection::Config::PATH
69
- ensure
70
- Dir.chdir(start)
71
- end
72
- }.call
73
-
74
53
  def self.rbs_environment(library: nil, collection: true, cache: true)
75
54
  @env_cache ||= {}
76
55
  if cache && hit = @env_cache[[library, collection]]
@@ -79,9 +58,15 @@ module Orthoses
79
58
 
80
59
  loader = RBS::EnvironmentLoader.new
81
60
 
82
- if collection && rbs_collection_pathname
83
- config = RBS::Collection::Config.lockfile_of(rbs_collection_pathname) or raise
84
- loader.add_collection(config)
61
+ if collection
62
+ config_path = RBS::Collection::Config.find_config_path || RBS::Collection::Config::PATH || raise("needs rbs_collection.yaml")
63
+ lock_path = RBS::Collection::Config.to_lockfile_path(config_path)
64
+ raise "needs rbs_collection.yaml file" unless lock_path.file?
65
+ lock = RBS::Collection::Config::Lockfile.from_lockfile(
66
+ lockfile_path: lock_path,
67
+ data: YAML.load_file(lock_path.to_s)
68
+ )
69
+ loader.add_collection(lock)
85
70
  end
86
71
 
87
72
  case library
@@ -101,7 +86,7 @@ module Orthoses
101
86
  end
102
87
  end
103
88
 
104
- def self.object_to_rbs(object, strict:)
89
+ def self.object_to_rbs(object, strict: false)
105
90
  case object
106
91
  when Class, Module
107
92
  if name = module_name(object)
@@ -115,8 +100,14 @@ module Orthoses
115
100
  else
116
101
  module_name(object.class) || 'untyped'
117
102
  end
118
- when true, false, nil
119
- object.inspect
103
+ when true, false
104
+ if strict
105
+ object.inspect
106
+ else
107
+ "bool"
108
+ end
109
+ when nil
110
+ "nil"
120
111
  when Set
121
112
  if object.empty?
122
113
  "Set[untyped]"
@@ -136,7 +127,7 @@ module Orthoses
136
127
  if strict
137
128
  "[#{ary.join(', ')}]"
138
129
  else
139
- "Array[#{ary.uniq.join(' | ')}]"
130
+ "Array[#{TypeList.new(ary).inject}]"
140
131
  end
141
132
  end
142
133
  when Hash
@@ -146,9 +137,9 @@ module Orthoses
146
137
  if strict && object.keys.all? { |key| key.is_a?(Symbol) && /\A\w+\z/.match?(key) }
147
138
  "{ #{object.map { |k, v| "#{k}: #{object_to_rbs(v, strict: strict)}" }.join(', ')} }"
148
139
  else
149
- keys = object.keys.map { |k| object_to_rbs(k, strict: strict) }.uniq
150
- values = object.values.map { |v| object_to_rbs(v, strict: strict) }.uniq
151
- "Hash[#{keys.join(' | ')}, #{values.join(' | ')}]"
140
+ keys = object.keys.map { |k| object_to_rbs(k, strict: strict) }
141
+ values = object.values.map { |v| object_to_rbs(v, strict: strict) }
142
+ "Hash[#{TypeList.new(keys).inject}, #{TypeList.new(values).inject}]"
152
143
  end
153
144
  end
154
145
  when Range
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Orthoses
4
- VERSION = "1.4.0"
4
+ VERSION = "1.6.0"
5
5
  end
data/lib/orthoses.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'rbs'
4
4
  require 'pathname'
5
5
 
6
+ require 'orthoses/utils'
7
+
6
8
  module Orthoses
7
9
  autoload :Attribute, 'orthoses/attribute'
8
10
  autoload :Builder, 'orthoses/builder'
@@ -21,10 +23,11 @@ module Orthoses
21
23
  autoload :PP, 'orthoses/pp'
22
24
  autoload :RBSPrototypeRB, 'orthoses/rbs_prototype_rb'
23
25
  autoload :RBSPrototypeRuntime, 'orthoses/rbs_prototype_runtime'
26
+ autoload :Sort, 'orthoses/sort'
24
27
  autoload :Store, 'orthoses/store'
25
28
  autoload :Tap, 'orthoses/tap'
29
+ autoload :Trace, 'orthoses/trace'
26
30
  autoload :Autoload, 'orthoses/autoload'
27
- autoload :Utils, 'orthoses/utils'
28
31
  autoload :VERSION, 'orthoses/version'
29
32
  autoload :Walk, 'orthoses/walk'
30
33
  autoload :Writer, 'orthoses/writer'
data/orthoses.gemspec CHANGED
@@ -32,5 +32,5 @@ Gem::Specification.new do |spec|
32
32
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ["lib"]
34
34
 
35
- spec.add_dependency "rbs", "~> 2.0"
35
+ spec.add_dependency "rbs", "~> 3.0"
36
36
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class Orthoses::CallTracer::Lazy
4
4
  def initialize: () -> void
5
- def trace: (untyped name) ?{ () -> untyped } -> untyped
5
+ def trace: (String name) ?{ () -> untyped } -> untyped
6
+ attr_reader captures: Array[untyped]
6
7
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  class Orthoses::CallTracer
4
4
  def initialize: () -> void
5
- def trace: [T] (Method | UnboundMethod) ?{ () -> T } -> T
5
+ def trace: (Method | UnboundMethod) ?{ () -> untyped } -> untyped
6
6
  attr_accessor captures: Array[Capture]
7
7
  end
@@ -10,4 +10,5 @@ class Orthoses::Content::HeaderBuilder
10
10
  private def build_context: (entry: untyped) -> untyped
11
11
  private def name_and_params: (untyped name, untyped params) -> ::String
12
12
  private def name_and_args: (untyped name, untyped args) -> (::String | nil)
13
+ include RBS::Environment::ContextUtil
13
14
  end
@@ -10,7 +10,9 @@ class Orthoses::Content
10
10
  def to_rbs: () -> String
11
11
  def to_decl: () -> untyped
12
12
  def uniq!: () -> untyped
13
+ def sort!: () -> untyped
13
14
  def original_rbs: () -> untyped
15
+ private def decl_to_lines: (untyped decl) -> untyped
14
16
  private def uniqed_body_string: () -> String
15
17
  private def uniqed_body_decl: () -> RBS::AST::Declarations::t
16
18
  private def auto_header: () -> (nil | untyped)
@@ -3,7 +3,7 @@
3
3
  class Orthoses::LazyTracePoint < ::TracePoint
4
4
  def method_added: (untyped id) -> untyped
5
5
  def singleton_method_added: (untyped id) -> untyped
6
- def initialize: (*untyped events) ?{ () -> untyped } -> void
6
+ def initialize: (*untyped events) { () -> untyped } -> void
7
7
  def enable: (?target: untyped?, ?target_line: untyped?, ?target_thread: untyped?) ?{ () -> untyped } -> untyped
8
8
  private def trace_instance_method: () ?{ () -> untyped } -> untyped
9
9
  private def trace_singleton_method: () ?{ () -> untyped } -> untyped
@@ -0,0 +1,6 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Sort
4
+ def initialize: (untyped loader) -> void
5
+ def call: () -> untyped
6
+ end
@@ -0,0 +1,8 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Trace::Attribute
4
+ def initialize: (untyped loader, patterns: untyped) -> void
5
+ def call: () -> untyped
6
+ private def build_trace_hook: () -> untyped
7
+ include Orthoses::Trace::Targetable
8
+ end
@@ -0,0 +1,4 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Trace::Method::Info < ::Struct[untyped]
4
+ end
@@ -0,0 +1,11 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Trace::Method
4
+ def initialize: (untyped loader, patterns: untyped) -> void
5
+ def call: () -> untyped
6
+ private def build_trace_point: () -> untyped
7
+ private def build_members: () -> untyped
8
+ private def build_method_definitions: () -> untyped
9
+ private def build_aliases: () -> untyped
10
+ include Orthoses::Trace::Targetable
11
+ end
@@ -0,0 +1,5 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ module Orthoses::Trace::Targetable
4
+ def target?: (untyped name) -> untyped
5
+ end
@@ -0,0 +1,6 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Trace
4
+ def initialize: (untyped loader, patterns: untyped) -> void
5
+ def call: () -> untyped
6
+ end
@@ -0,0 +1,10 @@
1
+ # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
+
3
+ class Orthoses::Utils::TypeList
4
+ def initialize: (untyped types) -> void
5
+ def inject: () -> untyped
6
+ FALSE_TYPE: RBS::Types::Literal
7
+ NIL_TYPE: RBS::Types::Bases::Nil
8
+ TRUE_TYPE: RBS::Types::Literal
9
+ UNTYPED: RBS::Types::Bases::Any
10
+ end
@@ -13,7 +13,7 @@ module Orthoses::Utils
13
13
 
14
14
  def self.rbs_environment: (?library: String | Array[String] | nil, ?collection: boolish, ?cache: boolish) -> RBS::Environment
15
15
 
16
- def self.object_to_rbs: (untyped object, strict: bool) -> String
16
+ def self.object_to_rbs: (untyped object, ?strict: bool) -> String
17
17
 
18
18
  def self.module_name: (Module mod) -> String?
19
19
 
@@ -24,6 +24,4 @@ module Orthoses::Utils
24
24
  def self.new_store: () -> Orthoses::store
25
25
 
26
26
  UNBOUND_NAME_METHOD: UnboundMethod
27
-
28
- attr_accessor self.rbs_collection_pathname: Pathname
29
27
  end
data/sig/orthoses.rbs CHANGED
@@ -1,7 +1,7 @@
1
1
  # THIS IS GENERATED CODE from `$ rake generate_self_sig`
2
2
 
3
3
  module Orthoses
4
- VERSION: "1.4.0"
4
+ VERSION: "1.6.0"
5
5
 
6
6
  attr_accessor self.logger: ::Logger
7
7
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: orthoses
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ksss
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-22 00:00:00.000000000 Z
11
+ date: 2023-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '2.0'
19
+ version: '3.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '2.0'
26
+ version: '3.0'
27
27
  description: Build RBS by Rack base architecture
28
28
  email:
29
29
  - co000ri@gmail.com
@@ -37,6 +37,10 @@ files:
37
37
  - LICENSE.txt
38
38
  - README.md
39
39
  - examples/minitest/Rakefile
40
+ - examples/rack-test/Gemfile
41
+ - examples/rack-test/Gemfile.lock
42
+ - examples/rack-test/Rakefile
43
+ - examples/rack-test/generate.rb
40
44
  - examples/simple_middleware.rb
41
45
  - examples/simple_middleware.rbs
42
46
  - lib/orthoses.rb
@@ -66,9 +70,15 @@ files:
66
70
  - lib/orthoses/pp.rb
67
71
  - lib/orthoses/rbs_prototype_rb.rb
68
72
  - lib/orthoses/rbs_prototype_runtime.rb
73
+ - lib/orthoses/sort.rb
69
74
  - lib/orthoses/store.rb
70
75
  - lib/orthoses/tap.rb
76
+ - lib/orthoses/trace.rb
77
+ - lib/orthoses/trace/attribute.rb
78
+ - lib/orthoses/trace/method.rb
79
+ - lib/orthoses/trace/targetable.rb
71
80
  - lib/orthoses/utils.rb
81
+ - lib/orthoses/utils/type_list.rb
72
82
  - lib/orthoses/utils/underscore.rb
73
83
  - lib/orthoses/version.rb
74
84
  - lib/orthoses/walk.rb
@@ -112,9 +122,16 @@ files:
112
122
  - sig/orthoses/pp.rbs
113
123
  - sig/orthoses/rbs_prototype_rb.rbs
114
124
  - sig/orthoses/rbs_prototype_runtime.rbs
125
+ - sig/orthoses/sort.rbs
115
126
  - sig/orthoses/store.rbs
116
127
  - sig/orthoses/tap.rbs
128
+ - sig/orthoses/trace.rbs
129
+ - sig/orthoses/trace/attribute.rbs
130
+ - sig/orthoses/trace/method.rbs
131
+ - sig/orthoses/trace/method/info.rbs
132
+ - sig/orthoses/trace/targetable.rbs
117
133
  - sig/orthoses/utils.rbs
134
+ - sig/orthoses/utils/type_list.rbs
118
135
  - sig/orthoses/utils/underscore.rbs
119
136
  - sig/orthoses/walk.rbs
120
137
  - sig/orthoses/writer.rbs
@@ -139,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
156
  - !ruby/object:Gem::Version
140
157
  version: '0'
141
158
  requirements: []
142
- rubygems_version: 3.3.16
159
+ rubygems_version: 3.4.8
143
160
  signing_key:
144
161
  specification_version: 4
145
162
  summary: Framework for Generate RBS