rigortype 0.1.8 → 0.1.10

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +186 -513
  3. data/lib/rigor/analysis/check_rules.rb +20 -0
  4. data/lib/rigor/analysis/runner.rb +67 -9
  5. data/lib/rigor/analysis/worker_session.rb +13 -4
  6. data/lib/rigor/cache/rbs_descriptor.rb +21 -2
  7. data/lib/rigor/cache/rbs_environment.rb +2 -1
  8. data/lib/rigor/cli/annotate_command.rb +274 -0
  9. data/lib/rigor/cli/baseline_command.rb +36 -16
  10. data/lib/rigor/cli/coverage_command.rb +126 -0
  11. data/lib/rigor/cli/coverage_renderer.rb +162 -0
  12. data/lib/rigor/cli/coverage_report.rb +75 -0
  13. data/lib/rigor/cli/mcp_command.rb +70 -0
  14. data/lib/rigor/cli/prism_colorizer.rb +111 -0
  15. data/lib/rigor/cli.rb +134 -6
  16. data/lib/rigor/environment/rbs_loader.rb +46 -5
  17. data/lib/rigor/environment/reporters.rb +3 -2
  18. data/lib/rigor/environment.rb +168 -5
  19. data/lib/rigor/inference/builtins/method_catalog.rb +17 -1
  20. data/lib/rigor/inference/builtins/time_catalog.rb +10 -1
  21. data/lib/rigor/inference/def_return_typer.rb +98 -0
  22. data/lib/rigor/inference/expression_typer.rb +308 -18
  23. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +109 -0
  24. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +178 -10
  25. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +53 -1
  26. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +62 -15
  27. data/lib/rigor/inference/method_dispatcher/math_folding.rb +149 -0
  28. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +20 -1
  29. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +81 -0
  30. data/lib/rigor/inference/method_dispatcher/set_folding.rb +81 -0
  31. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +431 -9
  32. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +126 -0
  33. data/lib/rigor/inference/method_dispatcher/time_folding.rb +56 -0
  34. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +67 -0
  35. data/lib/rigor/inference/method_dispatcher.rb +148 -1
  36. data/lib/rigor/inference/method_parameter_binder.rb +67 -10
  37. data/lib/rigor/inference/narrowing.rb +29 -10
  38. data/lib/rigor/inference/precision_scanner.rb +131 -0
  39. data/lib/rigor/inference/statement_evaluator.rb +29 -3
  40. data/lib/rigor/mcp/loop.rb +43 -0
  41. data/lib/rigor/mcp/server.rb +263 -0
  42. data/lib/rigor/mcp.rb +16 -0
  43. data/lib/rigor/plugin/base.rb +67 -5
  44. data/lib/rigor/plugin/loader.rb +22 -1
  45. data/lib/rigor/plugin/manifest.rb +101 -10
  46. data/lib/rigor/plugin/protocol_contract.rb +185 -0
  47. data/lib/rigor/plugin/registry.rb +87 -0
  48. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +51 -0
  49. data/lib/rigor/sig_gen/generator.rb +150 -75
  50. data/lib/rigor/triage/catalogue.rb +2 -2
  51. data/lib/rigor/type/combinator.rb +57 -0
  52. data/lib/rigor/type/constant.rb +29 -2
  53. data/lib/rigor/version.rb +1 -1
  54. data/sig/rigor/analysis/baseline.rbs +39 -0
  55. data/sig/rigor/environment.rbs +3 -2
  56. data/sig/rigor/type.rbs +4 -0
  57. data/sig/rigor.rbs +2 -0
  58. metadata +42 -1
@@ -122,13 +122,30 @@ module Rigor
122
122
  return match if match
123
123
  return overloads.find { |mt| overload_has_block?(mt) } if block_required
124
124
 
125
- overloads.first
125
+ # No block at the call site: prefer an overload that does
126
+ # not REQUIRE a block over `overloads.first`. Methods like
127
+ # `Array#filter` / `Enumerable#map` declare the block-
128
+ # bearing overload first (`() { ... } -> Array[Elem]`) and
129
+ # the bare-call overload second (`() -> Enumerator[...]`).
130
+ # Without this, a no-block `[1, 2].filter` would adopt the
131
+ # block overload's `Array[Elem]` return when the call
132
+ # actually yields an `Enumerator`.
133
+ overloads.find { |mt| !overload_requires_block?(mt) } || overloads.first
126
134
  end
127
135
 
128
136
  def overload_has_block?(method_type)
129
137
  method_type.respond_to?(:block) && method_type.block
130
138
  end
131
139
 
140
+ # True when the overload declares a block that the caller
141
+ # MUST supply (`{ ... }` in RBS). An optional block
142
+ # (`?{ ... }`) does NOT count — that overload is a valid
143
+ # match for a block-less call.
144
+ def overload_requires_block?(method_type)
145
+ block = overload_has_block?(method_type)
146
+ !!block && block.required
147
+ end
148
+
132
149
  class << self
133
150
  private
134
151
 
@@ -163,6 +180,7 @@ module Rigor
163
180
 
164
181
  overloads.find do |method_type|
165
182
  next false if block_required && !OverloadSelector.overload_has_block?(method_type)
183
+ next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
166
184
  next false if strict && !strictly_typed_params?(method_type, arg_types.size)
167
185
 
168
186
  matches?(
@@ -199,6 +217,7 @@ module Rigor
199
217
  def find_matching_overload_via_aliases(overloads, arg_types:, block_required:)
200
218
  overloads.find do |method_type|
201
219
  next false if block_required && !OverloadSelector.overload_has_block?(method_type)
220
+ next false if !block_required && OverloadSelector.overload_requires_block?(method_type)
202
221
 
203
222
  fun = method_type.type
204
223
  next false unless arity_compatible?(fun, arg_types.size)
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # Folds `Regexp` class-method calls on statically known arguments.
9
+ #
10
+ # === Supported methods
11
+ #
12
+ # * `escape(str)` / `quote(str)` — escapes regexp meta-characters.
13
+ # Returns `Constant[String]`.
14
+ # * `new(str)` / `new(str, opts)` — constructs a Regexp at fold time
15
+ # when the pattern argument is a `Constant[String]`. The optional
16
+ # second argument may be a `Constant[Integer]` (flag bits), a
17
+ # `Constant[true/false]` (IGNORECASE shorthand), or absent.
18
+ # Returns `Constant[Regexp]`.
19
+ #
20
+ # === Non-constant / unsupported cases
21
+ #
22
+ # Returns `nil` (deferring to the next dispatcher tier) when:
23
+ # - the receiver is not `Singleton[Regexp]`,
24
+ # - the required pattern argument is not a `Constant[String]`,
25
+ # - the method is not in the supported set.
26
+ module RegexpFolding
27
+ REGEXP_ESCAPE_METHODS = Set[:escape, :quote].freeze
28
+ private_constant :REGEXP_ESCAPE_METHODS
29
+
30
+ module_function
31
+
32
+ # @return [Rigor::Type, nil] folded result, or nil to defer.
33
+ def try_dispatch(receiver:, method_name:, args:)
34
+ return nil unless dispatch_target?(receiver)
35
+ return fold_escape(args) if REGEXP_ESCAPE_METHODS.include?(method_name)
36
+ return fold_new(args) if method_name == :new
37
+
38
+ nil
39
+ end
40
+
41
+ def dispatch_target?(receiver)
42
+ receiver.is_a?(Type::Singleton) && receiver.class_name == "Regexp"
43
+ end
44
+
45
+ # `Regexp.escape(str)` / `.quote(str)` — one String arg.
46
+ def fold_escape(args)
47
+ return nil unless args.size == 1
48
+
49
+ arg = args.first
50
+ return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(String)
51
+
52
+ Type::Combinator.constant_of(Regexp.escape(arg.value))
53
+ end
54
+
55
+ # `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — constructs
56
+ # the pattern at inference time. Delegates to Ruby's real
57
+ # `Regexp.new` so all option forms (Integer flags, `true`/`false`,
58
+ # option strings) are handled without case-analysis; non-constant or
59
+ # invalid arguments decline through to the RBS tier.
60
+ def fold_new(args)
61
+ return nil if args.empty? || args.size > 2
62
+
63
+ pattern_arg = args.first
64
+ return nil unless pattern_arg.is_a?(Type::Constant) &&
65
+ pattern_arg.value.is_a?(String)
66
+
67
+ opts = args.size == 2 ? constant_value_or_nil(args[1]) : 0
68
+ return nil if args.size == 2 && opts.nil?
69
+
70
+ Type::Combinator.constant_of(Regexp.new(pattern_arg.value, opts))
71
+ rescue StandardError
72
+ nil
73
+ end
74
+
75
+ def constant_value_or_nil(type)
76
+ type.is_a?(Type::Constant) ? type.value : nil
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../type"
4
+
5
+ module Rigor
6
+ module Inference
7
+ module MethodDispatcher
8
+ # Folds `Set` constructor calls into `Constant[Set]` when all
9
+ # arguments are statically known.
10
+ #
11
+ # Ruby 3.2+ implements Set in C (`set.c`). The constructor is
12
+ # deterministic — it reads only its arguments and writes nothing
13
+ # to global state.
14
+ #
15
+ # === Supported methods
16
+ #
17
+ # * `Set[]` — variadic constructor; each argument must be a
18
+ # `Constant[T]`. Returns `Constant[Set]`.
19
+ # * `Set.new` — zero-argument form; returns `Constant[Set.new]`.
20
+ # * `Set.new(tuple)` — the argument must be a `Tuple` whose
21
+ # every element is a `Constant[T]`. Returns `Constant[Set]`.
22
+ #
23
+ # === Non-constant / unsupported cases
24
+ #
25
+ # Returns `nil` (deferring to the next dispatcher tier) when:
26
+ # - the receiver is not `Singleton[Set]`,
27
+ # - the method is not `[]` or `new`,
28
+ # - any argument is non-constant or structurally opaque.
29
+ module SetFolding
30
+ module_function
31
+
32
+ # @return [Rigor::Type, nil] folded result, or nil to defer.
33
+ def try_dispatch(receiver:, method_name:, args:)
34
+ return nil unless dispatch_target?(receiver)
35
+
36
+ case method_name
37
+ when :[] then fold_bracket(args)
38
+ when :new then fold_new(args)
39
+ end
40
+ end
41
+
42
+ def dispatch_target?(receiver)
43
+ receiver.is_a?(Type::Singleton) && receiver.class_name == "Set"
44
+ end
45
+
46
+ # `Set["a", "b", "c"]` — all positional args must be Constant.
47
+ def fold_bracket(args)
48
+ values = args.map do |a|
49
+ return nil unless a.is_a?(Type::Constant)
50
+
51
+ a.value
52
+ end
53
+
54
+ Type::Combinator.constant_of(::Set.new(values))
55
+ rescue StandardError
56
+ nil
57
+ end
58
+
59
+ # `Set.new` / `Set.new(tuple_of_constants)`.
60
+ def fold_new(args)
61
+ return Type::Combinator.constant_of(::Set.new) if args.empty?
62
+ return nil if args.size > 1
63
+
64
+ arg = args.first
65
+ values = case arg
66
+ when Type::Tuple
67
+ return nil unless arg.elements.all?(Type::Constant)
68
+
69
+ arg.elements.map(&:value)
70
+ else
71
+ return nil
72
+ end
73
+
74
+ Type::Combinator.constant_of(::Set.new(values))
75
+ rescue StandardError
76
+ nil
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end