rigortype 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/README.md +215 -117
- data/data/builtins/ruby_core/comparable.yml +87 -0
- data/data/builtins/ruby_core/complex.yml +505 -0
- data/data/builtins/ruby_core/date.yml +1737 -0
- data/data/builtins/ruby_core/enumerable.yml +557 -0
- data/data/builtins/ruby_core/file.yml +9 -0
- data/data/builtins/ruby_core/hash.yml +936 -0
- data/data/builtins/ruby_core/range.yml +389 -0
- data/data/builtins/ruby_core/rational.yml +365 -0
- data/data/builtins/ruby_core/set.yml +594 -0
- data/data/builtins/ruby_core/string.yml +9 -0
- data/data/builtins/ruby_core/time.yml +752 -0
- data/lib/rigor/analysis/check_rules.rb +11 -3
- data/lib/rigor/builtins/imported_refinements.rb +192 -10
- data/lib/rigor/cli.rb +1 -1
- data/lib/rigor/inference/acceptance.rb +181 -12
- data/lib/rigor/inference/builtins/comparable_catalog.rb +27 -0
- data/lib/rigor/inference/builtins/complex_catalog.rb +41 -0
- data/lib/rigor/inference/builtins/date_catalog.rb +98 -0
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +27 -0
- data/lib/rigor/inference/builtins/hash_catalog.rb +40 -0
- data/lib/rigor/inference/builtins/range_catalog.rb +46 -0
- data/lib/rigor/inference/builtins/rational_catalog.rb +38 -0
- data/lib/rigor/inference/builtins/set_catalog.rb +54 -0
- data/lib/rigor/inference/builtins/time_catalog.rb +64 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +145 -11
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +202 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +95 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +23 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +135 -6
- data/lib/rigor/inference/method_dispatcher.rb +3 -1
- data/lib/rigor/inference/method_parameter_binder.rb +29 -4
- data/lib/rigor/inference/narrowing.rb +211 -0
- data/lib/rigor/inference/scope_indexer.rb +87 -11
- data/lib/rigor/inference/statement_evaluator.rb +6 -0
- data/lib/rigor/rbs_extended.rb +170 -14
- data/lib/rigor/type/combinator.rb +90 -0
- data/lib/rigor/type/integer_range.rb +4 -2
- data/lib/rigor/type/intersection.rb +135 -0
- data/lib/rigor/type/refined.rb +174 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/environment.rbs +4 -6
- data/sig/rigor/inference.rbs +2 -1
- data/sig/rigor/rbs_extended.rbs +11 -0
- data/sig/rigor/type.rbs +75 -35
- metadata +22 -1
|
@@ -11,6 +11,8 @@ require_relative "tuple"
|
|
|
11
11
|
require_relative "hash_shape"
|
|
12
12
|
require_relative "union"
|
|
13
13
|
require_relative "difference"
|
|
14
|
+
require_relative "refined"
|
|
15
|
+
require_relative "intersection"
|
|
14
16
|
|
|
15
17
|
module Rigor
|
|
16
18
|
module Type
|
|
@@ -133,6 +135,65 @@ module Rigor
|
|
|
133
135
|
)
|
|
134
136
|
end
|
|
135
137
|
|
|
138
|
+
# Predicate-subset refinement carrier (ADR-3 OQ3 Option C,
|
|
139
|
+
# second half). Use `lowercase_string` /
|
|
140
|
+
# `uppercase_string` / `numeric_string` for the imported
|
|
141
|
+
# built-in shapes; raw `refined(base, predicate_id)` for
|
|
142
|
+
# ad-hoc refinements introduced by an `RBS::Extended`
|
|
143
|
+
# annotation or a plugin-contributed predicate.
|
|
144
|
+
def refined(base, predicate_id)
|
|
145
|
+
Refined.new(base, predicate_id)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def lowercase_string
|
|
149
|
+
Refined.new(nominal_of("String"), :lowercase)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def uppercase_string
|
|
153
|
+
Refined.new(nominal_of("String"), :uppercase)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def numeric_string
|
|
157
|
+
Refined.new(nominal_of("String"), :numeric)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def decimal_int_string
|
|
161
|
+
Refined.new(nominal_of("String"), :decimal_int)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def octal_int_string
|
|
165
|
+
Refined.new(nominal_of("String"), :octal_int)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def hex_int_string
|
|
169
|
+
Refined.new(nominal_of("String"), :hex_int)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Normalised intersection. Flattens nested Intersections,
|
|
173
|
+
# drops `Top` members, collapses to `Bot` if any member is
|
|
174
|
+
# `Bot`, deduplicates structurally-equal members, sorts the
|
|
175
|
+
# survivors by `describe(:short)`, and collapses 0-/1-member
|
|
176
|
+
# results so a degenerate intersection never reaches the
|
|
177
|
+
# carrier. See ADR-3 OQ3 for the rationale; the lattice
|
|
178
|
+
# algebra is in
|
|
179
|
+
# [`value-lattice.md`](docs/type-specification/value-lattice.md).
|
|
180
|
+
def intersection(*members)
|
|
181
|
+
collapse_intersection(normalised_intersection_members(members))
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# `non-empty-lowercase-string` = non-empty-string ∩
|
|
185
|
+
# lowercase-string. Composes the point-removal half
|
|
186
|
+
# (`Difference[String, ""]`) with the predicate-subset half
|
|
187
|
+
# (`Refined[String, :lowercase]`). Both members erase to
|
|
188
|
+
# `String` so the carrier's RBS erasure is unambiguous.
|
|
189
|
+
def non_empty_lowercase_string
|
|
190
|
+
intersection(non_empty_string, lowercase_string)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def non_empty_uppercase_string
|
|
194
|
+
intersection(non_empty_string, uppercase_string)
|
|
195
|
+
end
|
|
196
|
+
|
|
136
197
|
# Constructs a heterogeneous, fixed-arity Tuple from positional
|
|
137
198
|
# element types. `tuple_of()` produces the empty tuple `Tuple[]`,
|
|
138
199
|
# which is structurally distinct from the raw `Nominal[Array]`.
|
|
@@ -185,6 +246,35 @@ module Rigor
|
|
|
185
246
|
end
|
|
186
247
|
end
|
|
187
248
|
|
|
249
|
+
# Symmetric counterparts to the Union normalisers. The
|
|
250
|
+
# absorbing element is `Bot` (anything intersected with
|
|
251
|
+
# nothing is nothing) and the identity element is `Top`
|
|
252
|
+
# (intersecting with the universal type is a no-op).
|
|
253
|
+
def normalised_intersection_members(types)
|
|
254
|
+
flattened = []
|
|
255
|
+
types.each { |t| flatten_intersection_into(flattened, t) }
|
|
256
|
+
return [bot] if flattened.any?(Bot)
|
|
257
|
+
|
|
258
|
+
flattened.reject! { |t| t.is_a?(Top) }
|
|
259
|
+
unique_members(flattened)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def collapse_intersection(types)
|
|
263
|
+
case types.size
|
|
264
|
+
when 0 then top
|
|
265
|
+
when 1 then types.first
|
|
266
|
+
else Intersection.new(sort_members(types))
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def flatten_intersection_into(acc, type)
|
|
271
|
+
if type.is_a?(Intersection)
|
|
272
|
+
type.members.each { |m| flatten_intersection_into(acc, m) }
|
|
273
|
+
else
|
|
274
|
+
acc << type
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
188
278
|
def resolve_class_name(class_name_or_object)
|
|
189
279
|
name =
|
|
190
280
|
case class_name_or_object
|
|
@@ -65,11 +65,13 @@ module Rigor
|
|
|
65
65
|
# `:neg_infinity`). Use this in arithmetic comparisons; never compare
|
|
66
66
|
# `:neg_infinity` directly with an `Integer`.
|
|
67
67
|
def lower
|
|
68
|
-
|
|
68
|
+
m = min
|
|
69
|
+
m.is_a?(Symbol) ? -Float::INFINITY : m
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def upper
|
|
72
|
-
|
|
73
|
+
m = max
|
|
74
|
+
m.is_a?(Symbol) ? Float::INFINITY : m
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
ALIAS_NAMES = {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../trinary"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Type
|
|
7
|
+
# `Intersection[M1, M2, …]` — value set is the meet of every
|
|
8
|
+
# member's value set. The carrier composes refinements that
|
|
9
|
+
# share a base, in particular the catalogued
|
|
10
|
+
# `non-empty-lowercase-string` (= `Difference[String, ""] &
|
|
11
|
+
# Refined[String, :lowercase]`) and
|
|
12
|
+
# `non-empty-uppercase-string` shapes from
|
|
13
|
+
# [`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md).
|
|
14
|
+
# See [ADR-3](docs/adr/3-type-representation.md) for the
|
|
15
|
+
# OQ3 working decision and the rationale for keeping
|
|
16
|
+
# Intersection a thin wrapper rather than per-shape carriers.
|
|
17
|
+
#
|
|
18
|
+
# Construction MUST go through `Type::Combinator.intersection`
|
|
19
|
+
# (or the per-name factories
|
|
20
|
+
# `Combinator.non_empty_lowercase_string` /
|
|
21
|
+
# `Combinator.non_empty_uppercase_string`). The factory:
|
|
22
|
+
#
|
|
23
|
+
# - flattens nested intersections,
|
|
24
|
+
# - drops `Top` members (Top is the identity of intersection),
|
|
25
|
+
# - collapses to `Bot` if any member is `Bot` (Bot is absorbing),
|
|
26
|
+
# - deduplicates structurally-equal members,
|
|
27
|
+
# - sorts the surviving members by `describe(:short)` so two
|
|
28
|
+
# structurally-equal intersections built in different orders
|
|
29
|
+
# compare equal,
|
|
30
|
+
# - returns `Top` for the empty intersection,
|
|
31
|
+
# - returns the lone member for a 1-element intersection (so
|
|
32
|
+
# the carrier is never inhabited by a degenerate single-member
|
|
33
|
+
# shape).
|
|
34
|
+
#
|
|
35
|
+
# Direct `.new` callers MUST pass an already-normalised member
|
|
36
|
+
# list and are expected to be tests or the combinator itself.
|
|
37
|
+
class Intersection
|
|
38
|
+
attr_reader :members
|
|
39
|
+
|
|
40
|
+
def initialize(members)
|
|
41
|
+
@members = members.dup.freeze
|
|
42
|
+
freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def describe(verbosity = :short)
|
|
46
|
+
named = canonical_name
|
|
47
|
+
return named if named
|
|
48
|
+
|
|
49
|
+
members.map { |m| m.describe(verbosity) }.join(" & ")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# An intersection of refinements over the same base type
|
|
53
|
+
# erases to that base. We use the first member's erasure
|
|
54
|
+
# because the v0.0.4 catalogue (`non-empty-lowercase-string`
|
|
55
|
+
# etc.) is restricted to same-base composition; richer
|
|
56
|
+
# cross-base intersections will need a stricter erasure
|
|
57
|
+
# rule (likely "lowest common ancestor" via the inference
|
|
58
|
+
# engine's class hierarchy).
|
|
59
|
+
def erase_to_rbs
|
|
60
|
+
members.first.erase_to_rbs
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def top
|
|
64
|
+
Trinary.no
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def bot
|
|
68
|
+
Trinary.no
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def dynamic
|
|
72
|
+
Trinary.no
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def accepts(other, mode: :gradual)
|
|
76
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ==(other)
|
|
80
|
+
other.is_a?(Intersection) && members == other.members
|
|
81
|
+
end
|
|
82
|
+
alias eql? ==
|
|
83
|
+
|
|
84
|
+
def hash
|
|
85
|
+
[Intersection, members].hash
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def inspect
|
|
89
|
+
"#<Rigor::Type::Intersection #{describe(:short)}>"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
# Maps a structurally-recognised composite shape to its
|
|
95
|
+
# kebab-case canonical name. The recognised set is kept in
|
|
96
|
+
# sync with the imported-built-in catalogue
|
|
97
|
+
# ([`imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md)).
|
|
98
|
+
#
|
|
99
|
+
# Detection is order-independent — `Combinator.intersection`
|
|
100
|
+
# sorts the canonical member list, but reading the registry
|
|
101
|
+
# the other way around (a user-authored Intersection built
|
|
102
|
+
# in any order) MUST still print in its canonical spelling.
|
|
103
|
+
def canonical_name
|
|
104
|
+
return nil unless members.size == 2
|
|
105
|
+
|
|
106
|
+
bases = members.map { |m| canonical_role(m) }.compact
|
|
107
|
+
return nil unless bases.size == 2
|
|
108
|
+
|
|
109
|
+
roles = bases.sort
|
|
110
|
+
case roles
|
|
111
|
+
when %w[lowercase non_empty_string] then "non-empty-lowercase-string"
|
|
112
|
+
when %w[non_empty_string uppercase] then "non-empty-uppercase-string"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns a stable role tag for the recognised composite
|
|
117
|
+
# members so `canonical_name` can pattern-match on a sorted
|
|
118
|
+
# role pair regardless of construction order. Returns nil
|
|
119
|
+
# when the member is not part of any catalogued composite —
|
|
120
|
+
# any nil contribution disqualifies the canonical-name path
|
|
121
|
+
# and the operator-form fallback kicks in.
|
|
122
|
+
def canonical_role(member)
|
|
123
|
+
case member
|
|
124
|
+
when Difference
|
|
125
|
+
"non_empty_string" if member == Type::Combinator.non_empty_string
|
|
126
|
+
when Refined
|
|
127
|
+
case member
|
|
128
|
+
when Type::Combinator.lowercase_string then "lowercase"
|
|
129
|
+
when Type::Combinator.uppercase_string then "uppercase"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -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
|
@@ -21,5 +21,7 @@ require_relative "type/tuple"
|
|
|
21
21
|
require_relative "type/hash_shape"
|
|
22
22
|
require_relative "type/union"
|
|
23
23
|
require_relative "type/difference"
|
|
24
|
+
require_relative "type/refined"
|
|
25
|
+
require_relative "type/intersection"
|
|
24
26
|
require_relative "type/accepts_result"
|
|
25
27
|
require_relative "type/combinator"
|
data/lib/rigor/version.rb
CHANGED
data/sig/rigor/environment.rbs
CHANGED
|
@@ -38,15 +38,13 @@ module Rigor
|
|
|
38
38
|
|
|
39
39
|
def initialize: (?libraries: Array[String], ?signature_paths: Array[String | _ToPath]) -> void
|
|
40
40
|
def class_known?: (String | Symbol name) -> bool
|
|
41
|
-
def instance_definition: (String | Symbol class_name) -> untyped
|
|
42
|
-
def instance_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped
|
|
43
|
-
def singleton_definition: (String | Symbol class_name) -> untyped
|
|
44
|
-
def singleton_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped
|
|
41
|
+
def instance_definition: (String | Symbol class_name) -> untyped?
|
|
42
|
+
def instance_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped?
|
|
43
|
+
def singleton_definition: (String | Symbol class_name) -> untyped?
|
|
44
|
+
def singleton_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped?
|
|
45
45
|
def class_type_param_names: (String | Symbol class_name) -> Array[Symbol]
|
|
46
46
|
def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
|
|
47
47
|
def constant_type: (String name) -> Type::t?
|
|
48
|
-
def instance_definition: (String | Symbol class_name) -> untyped?
|
|
49
|
-
def singleton_definition: (String | Symbol class_name) -> untyped?
|
|
50
48
|
end
|
|
51
49
|
|
|
52
50
|
class RbsHierarchy
|
data/sig/rigor/inference.rbs
CHANGED
|
@@ -85,6 +85,7 @@ module Rigor
|
|
|
85
85
|
def self?.narrow_not_equal: (Type::t type, untyped literal) -> Type::t
|
|
86
86
|
def self?.narrow_class: (Type::t type, String class_name, ?exact: bool, ?environment: Environment) -> Type::t
|
|
87
87
|
def self?.narrow_not_class: (Type::t type, String class_name, ?exact: bool, ?environment: Environment) -> Type::t
|
|
88
|
+
def self?.narrow_not_refinement: (Type::t current_type, Type::t refinement_type) -> Type::t
|
|
88
89
|
def self?.predicate_scopes: (untyped node, Scope scope) -> [Scope, Scope]
|
|
89
90
|
def self?.case_when_scopes: (untyped subject, Array[untyped] conditions, Scope scope) -> [Scope, Scope]
|
|
90
91
|
def self?.analyse: (untyped node, Scope scope) -> untyped
|
|
@@ -131,7 +132,7 @@ module Rigor
|
|
|
131
132
|
module ScopeIndexer
|
|
132
133
|
def self?.index: (untyped root, default_scope: Scope) -> Hash[untyped, Scope]
|
|
133
134
|
def self?.build_declaration_overrides: (untyped root) -> Hash[untyped, Type::t]
|
|
134
|
-
def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t]
|
|
135
|
+
def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] identity_table, Hash[String, Type::t] discovered) -> void
|
|
135
136
|
def self?.qualified_name_for: (untyped constant_path_node) -> String?
|
|
136
137
|
def self?.render_constant_path: (untyped node) -> String
|
|
137
138
|
def self?.propagate: (untyped node, Hash[untyped, Scope] table, Scope parent_scope) -> void
|
data/sig/rigor/rbs_extended.rbs
CHANGED
|
@@ -42,5 +42,16 @@ module Rigor
|
|
|
42
42
|
|
|
43
43
|
def self?.read_return_type_override: (untyped method_def) -> Type::t?
|
|
44
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?
|
|
45
56
|
end
|
|
46
57
|
end
|