rigortype 0.0.2 → 0.0.4

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -7
  3. data/data/builtins/ruby_core/array.yml +1470 -0
  4. data/data/builtins/ruby_core/file.yml +501 -0
  5. data/data/builtins/ruby_core/hash.yml +936 -0
  6. data/data/builtins/ruby_core/io.yml +1594 -0
  7. data/data/builtins/ruby_core/numeric.yml +1809 -0
  8. data/data/builtins/ruby_core/range.yml +389 -0
  9. data/data/builtins/ruby_core/set.yml +594 -0
  10. data/data/builtins/ruby_core/string.yml +1850 -0
  11. data/data/builtins/ruby_core/time.yml +750 -0
  12. data/lib/rigor/analysis/check_rules.rb +97 -4
  13. data/lib/rigor/analysis/runner.rb +4 -0
  14. data/lib/rigor/builtins/imported_refinements.rb +251 -0
  15. data/lib/rigor/configuration.rb +6 -1
  16. data/lib/rigor/inference/acceptance.rb +324 -6
  17. data/lib/rigor/inference/builtins/array_catalog.rb +46 -0
  18. data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
  19. data/lib/rigor/inference/builtins/method_catalog.rb +90 -0
  20. data/lib/rigor/inference/builtins/numeric_catalog.rb +93 -0
  21. data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
  22. data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
  23. data/lib/rigor/inference/builtins/string_catalog.rb +39 -0
  24. data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
  25. data/lib/rigor/inference/expression_typer.rb +48 -1
  26. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +670 -16
  27. data/lib/rigor/inference/method_dispatcher/file_folding.rb +144 -0
  28. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +215 -0
  29. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
  30. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -0
  31. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +240 -4
  32. data/lib/rigor/inference/method_dispatcher.rb +28 -21
  33. data/lib/rigor/inference/method_parameter_binder.rb +29 -4
  34. data/lib/rigor/inference/narrowing.rb +376 -4
  35. data/lib/rigor/inference/scope_indexer.rb +10 -2
  36. data/lib/rigor/inference/statement_evaluator.rb +213 -2
  37. data/lib/rigor/rbs_extended.rb +230 -15
  38. data/lib/rigor/scope.rb +14 -0
  39. data/lib/rigor/type/combinator.rb +159 -1
  40. data/lib/rigor/type/difference.rb +155 -0
  41. data/lib/rigor/type/integer_range.rb +137 -0
  42. data/lib/rigor/type/intersection.rb +135 -0
  43. data/lib/rigor/type/refined.rb +174 -0
  44. data/lib/rigor/type.rb +4 -0
  45. data/lib/rigor/version.rb +1 -1
  46. data/sig/rigor/rbs_extended.rbs +14 -0
  47. data/sig/rigor/scope.rbs +1 -0
  48. data/sig/rigor/type.rbs +91 -1
  49. metadata +25 -1
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../trinary"
4
+
5
+ module Rigor
6
+ module Type
7
+ # `Refined[base, predicate_id]` — predicate-subset half of
8
+ # the OQ3 refinement-carrier strategy
9
+ # ([ADR-3](docs/adr/3-type-representation.md), Working
10
+ # Decision Option C). Sibling of `Type::Difference`, which
11
+ # carries the point-removal half.
12
+ #
13
+ # lowercase-string = Refined[Nominal[String], :lowercase]
14
+ # uppercase-string = Refined[Nominal[String], :uppercase]
15
+ # numeric-string = Refined[Nominal[String], :numeric]
16
+ #
17
+ # The carrier wraps a base type and a `predicate_id` Symbol
18
+ # drawn from {PREDICATES}. The recogniser is invoked at
19
+ # constant-fold and acceptance time over a `Constant<base>`
20
+ # value; for non-Constant receivers the carrier is a marker
21
+ # the catalog tier consults to project `String#downcase` /
22
+ # `String#upcase` (etc.) into the matching refinement.
23
+ #
24
+ # Display routes through {CANONICAL_NAMES}: registered
25
+ # `(base_class_name, predicate_id)` pairs print in their
26
+ # kebab-case spelling (`lowercase-string`); unregistered
27
+ # combinations fall back to the `base & predicate?` operator
28
+ # form per
29
+ # [`type-operators.md`](docs/type-specification/type-operators.md).
30
+ #
31
+ # Construction MUST go through `Type::Combinator.refined` /
32
+ # the per-name factories (`Combinator.lowercase_string`,
33
+ # `Combinator.uppercase_string`, `Combinator.numeric_string`).
34
+ # Direct `.new` is an internal escape hatch for tests and
35
+ # combinator's own implementation.
36
+ class Refined
37
+ attr_reader :base, :predicate_id
38
+
39
+ def initialize(base, predicate_id)
40
+ raise ArgumentError, "predicate_id must be a Symbol" unless predicate_id.is_a?(Symbol)
41
+
42
+ @base = base
43
+ @predicate_id = predicate_id
44
+ freeze
45
+ end
46
+
47
+ def describe(verbosity = :short)
48
+ named = canonical_name
49
+ return named if named
50
+
51
+ "#{base.describe(verbosity)} & #{predicate_id}?"
52
+ end
53
+
54
+ # Erases to the base nominal: every refinement MUST erase
55
+ # to its base per [`rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
56
+ def erase_to_rbs
57
+ base.erase_to_rbs
58
+ end
59
+
60
+ def top
61
+ Trinary.no
62
+ end
63
+
64
+ def bot
65
+ Trinary.no
66
+ end
67
+
68
+ def dynamic
69
+ base.respond_to?(:dynamic) ? base.dynamic : Trinary.no
70
+ end
71
+
72
+ def accepts(other, mode: :gradual)
73
+ Inference::Acceptance.accepts(self, other, mode: mode)
74
+ end
75
+
76
+ def ==(other)
77
+ other.is_a?(Refined) && base == other.base && predicate_id == other.predicate_id
78
+ end
79
+ alias eql? ==
80
+
81
+ def hash
82
+ [Refined, base, predicate_id].hash
83
+ end
84
+
85
+ def inspect
86
+ "#<Rigor::Type::Refined #{describe(:short)}>"
87
+ end
88
+
89
+ # Recognises a Ruby value against this carrier's
90
+ # predicate. The trinary return is intentional: `true` /
91
+ # `false` when the predicate registry decides, `nil`
92
+ # when the predicate is unknown to the registry, so
93
+ # callers (today {Inference::Acceptance}) can fall
94
+ # through to gradual-mode `:maybe`.
95
+ # rubocop:disable Style/ReturnNilInPredicateMethodDefinition
96
+ def matches?(value)
97
+ recogniser = PREDICATES[predicate_id]
98
+ return nil if recogniser.nil?
99
+
100
+ !!recogniser.call(value)
101
+ end
102
+ # rubocop:enable Style/ReturnNilInPredicateMethodDefinition
103
+
104
+ # `predicate_id => recogniser` table. The recogniser is
105
+ # called with a Ruby value (typically the inner `value`
106
+ # of a `Constant`) and returns truthy when the value
107
+ # satisfies the predicate. The recogniser MUST be total
108
+ # (return false rather than raise) over arbitrary input,
109
+ # so callers can pass any `Constant#value` without a
110
+ # type-prefilter.
111
+ #
112
+ # Plugin-contributed predicates land here once ADR-2 is
113
+ # in flight; today the table is closed over the v0.0.4
114
+ # built-in catalogue.
115
+ #
116
+ # Recogniser policy:
117
+ #
118
+ # - `:numeric` is deliberately conservative — only decimal
119
+ # integer and plain-decimal-fraction strings are
120
+ # recognised, mirroring `imported-built-in-types.md`'s
121
+ # "Rigor's numeric-string predicate" wording. Looser
122
+ # forms (scientific, hex, rational) MAY join later
123
+ # without breaking the registry contract.
124
+ # - `:decimal_int` is "what `Integer(s, 10)` would parse
125
+ # without remainder" — one or more decimal digits,
126
+ # optional leading sign, no whitespace, no fractional
127
+ # tail.
128
+ # - `:octal_int` and `:hex_int` REQUIRE their conventional
129
+ # prefix (`0o` / `0O` / leading `0` for octal; `0x` /
130
+ # `0X` for hex) so the predicate is disjoint from
131
+ # `:decimal_int`. A bare `"755"` is decimal-int-string,
132
+ # not octal-int-string. This matches the typical user
133
+ # intent — a refinement marks a string that "looks like
134
+ # octal", not "happens to be base-8 valid".
135
+ NUMERIC_STRING_PATTERN = /\A-?\d+(?:\.\d+)?\z/
136
+ DECIMAL_INT_STRING_PATTERN = /\A-?\d+\z/
137
+ OCTAL_INT_STRING_PATTERN = /\A-?(?:0[oO][0-7]+|0[0-7]+)\z/
138
+ HEX_INT_STRING_PATTERN = /\A-?0[xX][0-9a-fA-F]+\z/
139
+ private_constant :NUMERIC_STRING_PATTERN, :DECIMAL_INT_STRING_PATTERN,
140
+ :OCTAL_INT_STRING_PATTERN, :HEX_INT_STRING_PATTERN
141
+
142
+ PREDICATES = {
143
+ lowercase: ->(v) { v.is_a?(String) && v == v.downcase },
144
+ uppercase: ->(v) { v.is_a?(String) && v == v.upcase },
145
+ numeric: ->(v) { v.is_a?(String) && NUMERIC_STRING_PATTERN.match?(v) },
146
+ decimal_int: ->(v) { v.is_a?(String) && DECIMAL_INT_STRING_PATTERN.match?(v) },
147
+ octal_int: ->(v) { v.is_a?(String) && OCTAL_INT_STRING_PATTERN.match?(v) },
148
+ hex_int: ->(v) { v.is_a?(String) && HEX_INT_STRING_PATTERN.match?(v) }
149
+ }.freeze
150
+
151
+ # Maps `[base_class_name, predicate_id]` pairs to their
152
+ # kebab-case canonical name. Registered shapes print
153
+ # through `describe`; unregistered combinations fall back
154
+ # to the operator form.
155
+ CANONICAL_NAMES = {
156
+ ["String", :lowercase] => "lowercase-string",
157
+ ["String", :uppercase] => "uppercase-string",
158
+ ["String", :numeric] => "numeric-string",
159
+ ["String", :decimal_int] => "decimal-int-string",
160
+ ["String", :octal_int] => "octal-int-string",
161
+ ["String", :hex_int] => "hex-int-string"
162
+ }.freeze
163
+ private_constant :CANONICAL_NAMES
164
+
165
+ private
166
+
167
+ def canonical_name
168
+ return nil unless base.is_a?(Nominal)
169
+
170
+ CANONICAL_NAMES[[base.class_name, predicate_id]]
171
+ end
172
+ end
173
+ end
174
+ end
data/lib/rigor/type.rb CHANGED
@@ -16,8 +16,12 @@ require_relative "type/dynamic"
16
16
  require_relative "type/nominal"
17
17
  require_relative "type/singleton"
18
18
  require_relative "type/constant"
19
+ require_relative "type/integer_range"
19
20
  require_relative "type/tuple"
20
21
  require_relative "type/hash_shape"
21
22
  require_relative "type/union"
23
+ require_relative "type/difference"
24
+ require_relative "type/refined"
25
+ require_relative "type/intersection"
22
26
  require_relative "type/accepts_result"
23
27
  require_relative "type/combinator"
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.4"
5
5
  end
@@ -39,5 +39,19 @@ module Rigor
39
39
 
40
40
  def self?.read_assert_effects: (untyped method_def) -> Array[AssertEffect]
41
41
  def self?.parse_assert_annotation: (String string) -> AssertEffect?
42
+
43
+ def self?.read_return_type_override: (untyped method_def) -> Type::t?
44
+ def self?.parse_return_type_override: (String string) -> Type::t?
45
+
46
+ class ParamOverride
47
+ attr_reader param_name: Symbol
48
+ attr_reader type: Type::t
49
+
50
+ def self.new: (param_name: Symbol, type: Type::t) -> ParamOverride
51
+ end
52
+
53
+ def self?.read_param_type_overrides: (untyped method_def) -> Array[ParamOverride]
54
+ def self?.param_type_override_map: (untyped method_def) -> Hash[Symbol, Type::t]
55
+ def self?.parse_param_annotation: (String string) -> ParamOverride?
42
56
  end
43
57
  end
data/sig/rigor/scope.rbs CHANGED
@@ -38,6 +38,7 @@ module Rigor
38
38
  def discovered_method?: (String | Symbol class_name, String | Symbol method_name, Symbol kind) -> bool
39
39
  def with_discovered_def_nodes: (Hash[String, Hash[Symbol, untyped]] table) -> Scope
40
40
  def user_def_for: (String | Symbol class_name, String | Symbol method_name) -> untyped?
41
+ def top_level_def_for: (String | Symbol method_name) -> untyped?
41
42
  def with_fact: (Analysis::FactStore::Fact fact) -> Scope
42
43
  def with_self_type: (Type::t? type) -> Scope
43
44
  def with_declared_types: (Hash[untyped, Type::t] table) -> Scope
data/sig/rigor/type.rbs CHANGED
@@ -1,6 +1,6 @@
1
1
  module Rigor
2
2
  module Type
3
- type t = Top | Bot | Dynamic | Constant | Nominal | Singleton | Union | Tuple | HashShape
3
+ type t = Top | Bot | Dynamic | Constant | IntegerRange | Nominal | Singleton | Union | Difference | Tuple | HashShape
4
4
 
5
5
  type accepts_mode = :strict | :gradual | :loose
6
6
 
@@ -58,6 +58,30 @@ module Rigor
58
58
  def inspect: () -> String
59
59
  end
60
60
 
61
+ class IntegerRange
62
+ NEG_INFINITY: Symbol
63
+ POS_INFINITY: Symbol
64
+ attr_reader min: (Integer | Symbol)
65
+ attr_reader max: (Integer | Symbol)
66
+ def initialize: ((Integer | Symbol) min, (Integer | Symbol) max) -> void
67
+ def universal?: () -> bool
68
+ def finite?: () -> bool
69
+ def cardinality: () -> (Integer | Float)
70
+ def covers?: (untyped value) -> bool
71
+ def lower: () -> Numeric
72
+ def upper: () -> Numeric
73
+ def describe: (?Symbol verbosity) -> String
74
+ def generic_description: () -> String
75
+ def erase_to_rbs: () -> String
76
+ def top: () -> Top
77
+ def bot: () -> Bot
78
+ def dynamic: () -> Dynamic
79
+ def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
80
+ def ==: (untyped other) -> bool
81
+ def hash: () -> Integer
82
+ def inspect: () -> String
83
+ end
84
+
61
85
  class Nominal
62
86
  attr_reader class_name: String
63
87
  attr_reader type_args: Array[Type::t]
@@ -101,6 +125,51 @@ module Rigor
101
125
  def inspect: () -> String
102
126
  end
103
127
 
128
+ class Difference
129
+ attr_reader base: Type::t
130
+ attr_reader removed: Type::t
131
+ def initialize: (Type::t base, Type::t removed) -> void
132
+ def describe: (?Symbol verbosity) -> String
133
+ def erase_to_rbs: () -> String
134
+ def top: () -> Top
135
+ def bot: () -> Bot
136
+ def dynamic: () -> Dynamic
137
+ def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
138
+ def ==: (untyped other) -> bool
139
+ def hash: () -> Integer
140
+ def inspect: () -> String
141
+ end
142
+
143
+ class Refined
144
+ attr_reader base: Type::t
145
+ attr_reader predicate_id: Symbol
146
+ def initialize: (Type::t base, Symbol predicate_id) -> void
147
+ def describe: (?Symbol verbosity) -> String
148
+ def erase_to_rbs: () -> String
149
+ def top: () -> Top
150
+ def bot: () -> Bot
151
+ def dynamic: () -> Dynamic
152
+ def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
153
+ def matches?: (untyped value) -> bool?
154
+ def ==: (untyped other) -> bool
155
+ def hash: () -> Integer
156
+ def inspect: () -> String
157
+ end
158
+
159
+ class Intersection
160
+ attr_reader members: Array[Type::t]
161
+ def initialize: (Array[Type::t] members) -> void
162
+ def describe: (?Symbol verbosity) -> String
163
+ def erase_to_rbs: () -> String
164
+ def top: () -> Top
165
+ def bot: () -> Bot
166
+ def dynamic: () -> Dynamic
167
+ def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
168
+ def ==: (untyped other) -> bool
169
+ def hash: () -> Integer
170
+ def inspect: () -> String
171
+ end
172
+
104
173
  class Tuple
105
174
  attr_reader elements: Array[Type::t]
106
175
  def initialize: (Array[Type::t] elements) -> void
@@ -163,6 +232,27 @@ module Rigor
163
232
  def self?.nominal_of: (Module | String class_name_or_object, ?type_args: Array[Type::t]) -> Nominal
164
233
  def self?.singleton_of: (Module | String class_name_or_object) -> Singleton
165
234
  def self?.constant_of: (untyped value) -> Constant
235
+ def self?.difference: (Type::t base, Type::t removed) -> Difference
236
+ def self?.non_empty_string: () -> Difference
237
+ def self?.non_zero_int: () -> Difference
238
+ def self?.non_empty_array: (?Type::t element) -> Difference
239
+ def self?.non_empty_hash: (?Type::t key, ?Type::t value) -> Difference
240
+ def self?.refined: (Type::t base, Symbol predicate_id) -> Refined
241
+ def self?.lowercase_string: () -> Refined
242
+ def self?.uppercase_string: () -> Refined
243
+ def self?.numeric_string: () -> Refined
244
+ def self?.decimal_int_string: () -> Refined
245
+ def self?.octal_int_string: () -> Refined
246
+ def self?.hex_int_string: () -> Refined
247
+ def self?.intersection: (*Type::t members) -> Type::t
248
+ def self?.non_empty_lowercase_string: () -> Type::t
249
+ def self?.non_empty_uppercase_string: () -> Type::t
250
+ def self?.integer_range: ((Integer | Symbol) min, (Integer | Symbol) max) -> IntegerRange
251
+ def self?.positive_int: () -> IntegerRange
252
+ def self?.non_negative_int: () -> IntegerRange
253
+ def self?.negative_int: () -> IntegerRange
254
+ def self?.non_positive_int: () -> IntegerRange
255
+ def self?.universal_int: () -> IntegerRange
166
256
  def self?.tuple_of: (*Type::t elements) -> Tuple
167
257
  def self?.hash_shape_of: (?Hash[untyped, Type::t]? pairs, **untyped options) -> HashShape
168
258
  def self?.union: (*Type::t types) -> Type::t
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -160,6 +160,15 @@ extra_rdoc_files: []
160
160
  files:
161
161
  - LICENSE
162
162
  - README.md
163
+ - data/builtins/ruby_core/array.yml
164
+ - data/builtins/ruby_core/file.yml
165
+ - data/builtins/ruby_core/hash.yml
166
+ - data/builtins/ruby_core/io.yml
167
+ - data/builtins/ruby_core/numeric.yml
168
+ - data/builtins/ruby_core/range.yml
169
+ - data/builtins/ruby_core/set.yml
170
+ - data/builtins/ruby_core/string.yml
171
+ - data/builtins/ruby_core/time.yml
163
172
  - exe/rigor
164
173
  - lib/rigor.rb
165
174
  - lib/rigor/analysis/check_rules.rb
@@ -169,6 +178,7 @@ files:
169
178
  - lib/rigor/analysis/runner.rb
170
179
  - lib/rigor/ast.rb
171
180
  - lib/rigor/ast/type_node.rb
181
+ - lib/rigor/builtins/imported_refinements.rb
172
182
  - lib/rigor/cli.rb
173
183
  - lib/rigor/cli/type_of_command.rb
174
184
  - lib/rigor/cli/type_of_renderer.rb
@@ -182,6 +192,14 @@ files:
182
192
  - lib/rigor/environment/rbs_loader.rb
183
193
  - lib/rigor/inference/acceptance.rb
184
194
  - lib/rigor/inference/block_parameter_binder.rb
195
+ - lib/rigor/inference/builtins/array_catalog.rb
196
+ - lib/rigor/inference/builtins/hash_catalog.rb
197
+ - lib/rigor/inference/builtins/method_catalog.rb
198
+ - lib/rigor/inference/builtins/numeric_catalog.rb
199
+ - lib/rigor/inference/builtins/range_catalog.rb
200
+ - lib/rigor/inference/builtins/set_catalog.rb
201
+ - lib/rigor/inference/builtins/string_catalog.rb
202
+ - lib/rigor/inference/builtins/time_catalog.rb
185
203
  - lib/rigor/inference/closure_escape_analyzer.rb
186
204
  - lib/rigor/inference/coverage_scanner.rb
187
205
  - lib/rigor/inference/expression_typer.rb
@@ -189,6 +207,8 @@ files:
189
207
  - lib/rigor/inference/fallback_tracer.rb
190
208
  - lib/rigor/inference/method_dispatcher.rb
191
209
  - lib/rigor/inference/method_dispatcher/constant_folding.rb
210
+ - lib/rigor/inference/method_dispatcher/file_folding.rb
211
+ - lib/rigor/inference/method_dispatcher/iterator_dispatch.rb
192
212
  - lib/rigor/inference/method_dispatcher/overload_selector.rb
193
213
  - lib/rigor/inference/method_dispatcher/rbs_dispatch.rb
194
214
  - lib/rigor/inference/method_dispatcher/shape_dispatch.rb
@@ -210,9 +230,13 @@ files:
210
230
  - lib/rigor/type/bot.rb
211
231
  - lib/rigor/type/combinator.rb
212
232
  - lib/rigor/type/constant.rb
233
+ - lib/rigor/type/difference.rb
213
234
  - lib/rigor/type/dynamic.rb
214
235
  - lib/rigor/type/hash_shape.rb
236
+ - lib/rigor/type/integer_range.rb
237
+ - lib/rigor/type/intersection.rb
215
238
  - lib/rigor/type/nominal.rb
239
+ - lib/rigor/type/refined.rb
216
240
  - lib/rigor/type/singleton.rb
217
241
  - lib/rigor/type/top.rb
218
242
  - lib/rigor/type/tuple.rb