rigortype 0.0.5 → 0.0.7
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/data/builtins/ruby_core/pathname.yml +1067 -0
- data/lib/rigor/analysis/check_rules.rb +38 -41
- data/lib/rigor/builtins/imported_refinements.rb +93 -3
- data/lib/rigor/inference/builtins/pathname_catalog.rb +35 -0
- data/lib/rigor/inference/expression_typer.rb +310 -25
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +322 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +325 -8
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +45 -4
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +4 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +409 -0
- data/lib/rigor/inference/method_dispatcher.rb +88 -18
- data/lib/rigor/inference/method_parameter_binder.rb +3 -5
- data/lib/rigor/inference/narrowing.rb +38 -6
- data/lib/rigor/inference/statement_evaluator.rb +5 -7
- data/lib/rigor/reflection.rb +203 -0
- data/lib/rigor/type/combinator.rb +244 -1
- data/lib/rigor/type/constant.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/sig/rigor/reflection.rbs +17 -0
- data/sig/rigor/type.rbs +5 -0
- metadata +6 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "../reflection"
|
|
5
6
|
require_relative "../type"
|
|
6
7
|
require_relative "../environment"
|
|
7
8
|
require_relative "../rbs_extended"
|
|
@@ -285,6 +286,8 @@ module Rigor
|
|
|
285
286
|
complement_integer_range(current_type, refinement_type)
|
|
286
287
|
when Type::Intersection
|
|
287
288
|
complement_intersection(current_type, refinement_type)
|
|
289
|
+
when Type::Refined
|
|
290
|
+
complement_refined(current_type, refinement_type)
|
|
288
291
|
else
|
|
289
292
|
current_type
|
|
290
293
|
end
|
|
@@ -526,6 +529,38 @@ module Rigor
|
|
|
526
529
|
Type::Combinator.union(*per_member)
|
|
527
530
|
end
|
|
528
531
|
|
|
532
|
+
# v0.0.7 — complement of a `Refined[base, predicate]`
|
|
533
|
+
# refinement within `current_type`. Walks the current
|
|
534
|
+
# type's union members; parts that are disjoint from
|
|
535
|
+
# the refinement's base survive unchanged; parts equal
|
|
536
|
+
# to the refinement itself drop entirely (they were
|
|
537
|
+
# exactly the negated subset); parts that overlap the
|
|
538
|
+
# base contribute `Difference[part, refined]` so
|
|
539
|
+
# downstream narrowing knows the refinement subset is
|
|
540
|
+
# excluded.
|
|
541
|
+
#
|
|
542
|
+
# The result is sound but imprecise: without a
|
|
543
|
+
# complementary carrier (e.g. `mixed-case-string` for
|
|
544
|
+
# `~lowercase-string`) we cannot enumerate the surviving
|
|
545
|
+
# values. Difference is the carrier-of-last-resort, and
|
|
546
|
+
# the existing `Type::Difference` consumers already
|
|
547
|
+
# treat it correctly.
|
|
548
|
+
def complement_refined(current_type, refined)
|
|
549
|
+
base = refined.base
|
|
550
|
+
parts = current_type.is_a?(Type::Union) ? current_type.members : [current_type]
|
|
551
|
+
|
|
552
|
+
survivors = parts.map do |part|
|
|
553
|
+
next nil if part == refined
|
|
554
|
+
next part if base_disjoint?(base, part)
|
|
555
|
+
|
|
556
|
+
Type::Combinator.difference(part, refined)
|
|
557
|
+
end.compact
|
|
558
|
+
|
|
559
|
+
return current_type if survivors.empty?
|
|
560
|
+
|
|
561
|
+
Type::Combinator.union(*survivors)
|
|
562
|
+
end
|
|
563
|
+
|
|
529
564
|
def falsey_value?(value)
|
|
530
565
|
value.nil? || value == false
|
|
531
566
|
end
|
|
@@ -1248,18 +1283,15 @@ module Rigor
|
|
|
1248
1283
|
end
|
|
1249
1284
|
|
|
1250
1285
|
def resolve_rbs_extended_method(node, scope)
|
|
1251
|
-
loader = scope.environment.rbs_loader
|
|
1252
|
-
return nil if loader.nil?
|
|
1253
|
-
|
|
1254
1286
|
receiver_type = scope.type_of(node.receiver)
|
|
1255
1287
|
class_name = rbs_extended_class_name(receiver_type)
|
|
1256
1288
|
return nil if class_name.nil?
|
|
1257
|
-
return nil unless
|
|
1289
|
+
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
1258
1290
|
|
|
1259
1291
|
if receiver_type.is_a?(Type::Singleton)
|
|
1260
|
-
|
|
1292
|
+
Rigor::Reflection.singleton_method_definition(class_name, node.name, scope: scope)
|
|
1261
1293
|
else
|
|
1262
|
-
|
|
1294
|
+
Rigor::Reflection.instance_method_definition(class_name, node.name, scope: scope)
|
|
1263
1295
|
end
|
|
1264
1296
|
rescue StandardError
|
|
1265
1297
|
nil
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
+
require_relative "../reflection"
|
|
5
6
|
require_relative "../type"
|
|
6
7
|
require_relative "../analysis/fact_store"
|
|
7
8
|
require_relative "../source/node_walker"
|
|
@@ -838,7 +839,7 @@ module Rigor
|
|
|
838
839
|
end
|
|
839
840
|
end
|
|
840
841
|
|
|
841
|
-
def resolve_call_method(call_node, current_scope) # rubocop:disable Metrics/
|
|
842
|
+
def resolve_call_method(call_node, current_scope) # rubocop:disable Metrics/PerceivedComplexity
|
|
842
843
|
receiver_node = call_node.receiver
|
|
843
844
|
receiver_type =
|
|
844
845
|
if receiver_node
|
|
@@ -848,17 +849,14 @@ module Rigor
|
|
|
848
849
|
end
|
|
849
850
|
return nil if receiver_type.nil?
|
|
850
851
|
|
|
851
|
-
loader = current_scope.environment.rbs_loader
|
|
852
|
-
return nil if loader.nil?
|
|
853
|
-
|
|
854
852
|
class_name = assertion_class_name(receiver_type)
|
|
855
853
|
return nil if class_name.nil?
|
|
856
|
-
return nil unless
|
|
854
|
+
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: current_scope)
|
|
857
855
|
|
|
858
856
|
if receiver_type.is_a?(Type::Singleton)
|
|
859
|
-
|
|
857
|
+
Rigor::Reflection.singleton_method_definition(class_name, call_node.name, scope: current_scope)
|
|
860
858
|
else
|
|
861
|
-
|
|
859
|
+
Rigor::Reflection.instance_method_definition(class_name, call_node.name, scope: current_scope)
|
|
862
860
|
end
|
|
863
861
|
rescue StandardError
|
|
864
862
|
nil
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
# Read-side facade over Rigor's three reflection sources:
|
|
7
|
+
#
|
|
8
|
+
# 1. **`Rigor::Environment::ClassRegistry`** — Ruby `Class` /
|
|
9
|
+
# `Module` objects (Integer, Float, Set, Pathname, …) registered
|
|
10
|
+
# at boot. Static; never changes during a `rigor check` run.
|
|
11
|
+
# 2. **`Rigor::Environment::RbsLoader`** — RBS-side declarations
|
|
12
|
+
# (instance / singleton methods, class hierarchy, constants).
|
|
13
|
+
# Loaded on demand from the project's `sig/` directory + the
|
|
14
|
+
# bundled stdlib RBS.
|
|
15
|
+
# 3. **`Rigor::Scope` discovered facts** — source-side discoveries
|
|
16
|
+
# produced by `Rigor::Inference::ScopeIndexer` (user-defined
|
|
17
|
+
# classes / modules, in-source constants, discovered method
|
|
18
|
+
# nodes, class ivar / cvar declarations).
|
|
19
|
+
#
|
|
20
|
+
# This module is the **stable read shape** that v0.1.0's plugin
|
|
21
|
+
# API will be designed against. ADR-2 (`docs/adr/2-extension-api.md`)
|
|
22
|
+
# calls out a unified reflection layer as a prerequisite for the
|
|
23
|
+
# extension protocols, and `docs/design/20260505-v0.1.0-readiness.md`
|
|
24
|
+
# nominates this module as the highest-leverage cold-start slice.
|
|
25
|
+
#
|
|
26
|
+
# The facade is **read-only and additive**. Existing call sites
|
|
27
|
+
# that read directly from `Rigor::Scope` or
|
|
28
|
+
# `Rigor::Environment::RbsLoader` continue to work unchanged;
|
|
29
|
+
# they migrate to the facade at their own pace. The facade
|
|
30
|
+
# performs no caching beyond what the underlying sources already
|
|
31
|
+
# provide.
|
|
32
|
+
#
|
|
33
|
+
# ## Public surface (v0.0.7 first pass)
|
|
34
|
+
#
|
|
35
|
+
# - {.class_known?} — does any source recognise this class /
|
|
36
|
+
# module name?
|
|
37
|
+
# - {.class_ordering} — `:equal` / `:subclass` / `:superclass` /
|
|
38
|
+
# `:disjoint` / `:unknown` ordering between two class names.
|
|
39
|
+
# - {.nominal_for_name} — `Rigor::Type::Nominal` for the class
|
|
40
|
+
# name, joining registry + RBS lookups.
|
|
41
|
+
# - {.singleton_for_name} — `Rigor::Type::Singleton` for the
|
|
42
|
+
# class name's class object.
|
|
43
|
+
# - {.constant_type_for} — type of a constant (joins in-source
|
|
44
|
+
# constants and RBS-side constants).
|
|
45
|
+
# - {.instance_method_definition} / {.singleton_method_definition}
|
|
46
|
+
# — RBS-side `RBS::Definition::Method` for the method, or
|
|
47
|
+
# `nil` when the method is not declared in RBS. Source-side
|
|
48
|
+
# discovered methods are exposed through {.discovered_method?}
|
|
49
|
+
# below until the unified `MethodDefinition` carrier ships.
|
|
50
|
+
# - {.discovered_class?} / {.discovered_method?} — has the
|
|
51
|
+
# ScopeIndexer pass recorded the class / method as user-
|
|
52
|
+
# defined in the analyzed sources?
|
|
53
|
+
#
|
|
54
|
+
# The provenance side of the API (which source family contributed
|
|
55
|
+
# each fact) is explicitly out of scope for the v0.0.7 first
|
|
56
|
+
# pass. v0.1.0's plugin API adds it as a separate concern.
|
|
57
|
+
module Reflection
|
|
58
|
+
module_function
|
|
59
|
+
|
|
60
|
+
# @param class_name [String, Symbol]
|
|
61
|
+
# @param scope [Rigor::Scope]
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def class_known?(class_name, scope: Scope.empty)
|
|
64
|
+
return true if scope.discovered_classes.key?(class_name.to_s)
|
|
65
|
+
|
|
66
|
+
scope.environment.class_known?(class_name)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# RBS-only variant of {.class_known?}. Use when the caller
|
|
70
|
+
# needs to know specifically whether RBS has a definition
|
|
71
|
+
# for the class, independent of any source-discovered
|
|
72
|
+
# `class Foo; end` declarations. The diagnostic-rule code
|
|
73
|
+
# paths that walk RBS method tables to decide whether to
|
|
74
|
+
# flag a missing method use this variant; otherwise the
|
|
75
|
+
# source-discovered class would suppress the rule even
|
|
76
|
+
# when no RBS sig actually proves the method exists.
|
|
77
|
+
#
|
|
78
|
+
# The kwarg accepts either `scope:` or `environment:`. The
|
|
79
|
+
# latter is for call sites that don't carry a `Scope`
|
|
80
|
+
# (most are bottom-half dispatcher code paths called with
|
|
81
|
+
# only an environment).
|
|
82
|
+
def rbs_class_known?(class_name, scope: nil, environment: nil)
|
|
83
|
+
loader = rbs_loader_for(scope, environment)
|
|
84
|
+
return false if loader.nil?
|
|
85
|
+
|
|
86
|
+
loader.class_known?(class_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @return [Symbol] one of `:equal`, `:subclass`, `:superclass`,
|
|
90
|
+
# `:disjoint`, `:unknown`.
|
|
91
|
+
def class_ordering(lhs, rhs, scope: Scope.empty)
|
|
92
|
+
scope.environment.class_ordering(lhs, rhs)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns the `Rigor::Type::Nominal` for the class name, or
|
|
96
|
+
# nil when no source knows the class.
|
|
97
|
+
def nominal_for_name(class_name, scope: Scope.empty)
|
|
98
|
+
scope.environment.nominal_for_name(class_name)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns the `Rigor::Type::Singleton` for the class name's
|
|
102
|
+
# class object, or nil when no source knows the class.
|
|
103
|
+
def singleton_for_name(class_name, scope: Scope.empty)
|
|
104
|
+
scope.environment.singleton_for_name(class_name)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns the type of the named constant. Joins in-source
|
|
108
|
+
# constants (recorded by `ScopeIndexer`) and RBS-side
|
|
109
|
+
# constants. In-source wins on collision because the user's
|
|
110
|
+
# source is the authoritative declaration.
|
|
111
|
+
def constant_type_for(constant_name, scope: Scope.empty)
|
|
112
|
+
key = constant_name.to_s
|
|
113
|
+
in_source = scope.in_source_constants[key]
|
|
114
|
+
return in_source if in_source
|
|
115
|
+
|
|
116
|
+
scope.environment.constant_for_name(constant_name)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns the RBS `RBS::Definition::Method` for the instance
|
|
120
|
+
# method, or nil when the class or method is not in RBS. The
|
|
121
|
+
# source-side discovered-method facts are reachable through
|
|
122
|
+
# {.discovered_method?}; a future slice will unify the two
|
|
123
|
+
# under a `MethodDefinition` carrier.
|
|
124
|
+
def instance_method_definition(class_name, method_name, scope: nil, environment: nil)
|
|
125
|
+
loader = rbs_loader_for(scope, environment)
|
|
126
|
+
return nil if loader.nil?
|
|
127
|
+
|
|
128
|
+
loader.instance_method(class_name: class_name.to_s, method_name: method_name.to_sym)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Returns the RBS `RBS::Definition::Method` for the singleton
|
|
132
|
+
# (class-side) method, or nil.
|
|
133
|
+
def singleton_method_definition(class_name, method_name, scope: nil, environment: nil)
|
|
134
|
+
loader = rbs_loader_for(scope, environment)
|
|
135
|
+
return nil if loader.nil?
|
|
136
|
+
|
|
137
|
+
loader.singleton_method(class_name: class_name.to_s, method_name: method_name.to_sym)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns the full RBS instance-side class definition
|
|
141
|
+
# (`RBS::Definition`), used by callers that walk the method
|
|
142
|
+
# table or member list. Returns nil when the class is not in
|
|
143
|
+
# RBS or when the loader cannot build a definition (e.g.
|
|
144
|
+
# constant aliases, malformed signatures).
|
|
145
|
+
def instance_definition(class_name, scope: nil, environment: nil)
|
|
146
|
+
loader = rbs_loader_for(scope, environment)
|
|
147
|
+
return nil if loader.nil?
|
|
148
|
+
|
|
149
|
+
loader.instance_definition(class_name.to_s)
|
|
150
|
+
rescue StandardError
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Returns the full RBS singleton-side class definition.
|
|
155
|
+
def singleton_definition(class_name, scope: nil, environment: nil)
|
|
156
|
+
loader = rbs_loader_for(scope, environment)
|
|
157
|
+
return nil if loader.nil?
|
|
158
|
+
|
|
159
|
+
loader.singleton_definition(class_name.to_s)
|
|
160
|
+
rescue StandardError
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Returns the RBS-declared type parameter names for the
|
|
165
|
+
# class (e.g. `[:A]` for `Array[A]`), or `[]` when the class
|
|
166
|
+
# is non-generic / not in RBS. Used by the dispatcher when
|
|
167
|
+
# binding generic method types to a concrete receiver.
|
|
168
|
+
def class_type_param_names(class_name, scope: nil, environment: nil)
|
|
169
|
+
loader = rbs_loader_for(scope, environment)
|
|
170
|
+
return [] if loader.nil?
|
|
171
|
+
|
|
172
|
+
loader.class_type_param_names(class_name.to_s)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Internal helper — resolves the RBS loader from either the
|
|
176
|
+
# `scope:` or the `environment:` kwarg, defaulting to the
|
|
177
|
+
# empty scope's environment when neither is given. Public
|
|
178
|
+
# methods document both spellings; the helper centralises
|
|
179
|
+
# the dispatch.
|
|
180
|
+
def rbs_loader_for(scope, environment)
|
|
181
|
+
return environment.rbs_loader if environment
|
|
182
|
+
return scope.environment.rbs_loader if scope
|
|
183
|
+
|
|
184
|
+
Scope.empty.environment.rbs_loader
|
|
185
|
+
end
|
|
186
|
+
private_class_method :rbs_loader_for
|
|
187
|
+
|
|
188
|
+
# @return [Boolean] true when the analyzed source contains a
|
|
189
|
+
# class / module declaration for the given name. Does NOT
|
|
190
|
+
# consult the RBS loader (use {.class_known?} for the union).
|
|
191
|
+
def discovered_class?(class_name, scope: Scope.empty)
|
|
192
|
+
scope.discovered_classes.key?(class_name.to_s)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# @param kind [:instance, :singleton]
|
|
196
|
+
# @return [Boolean] true when the ScopeIndexer recorded a
|
|
197
|
+
# `def` for the given method on the given class with the
|
|
198
|
+
# matching kind.
|
|
199
|
+
def discovered_method?(class_name, method_name, kind: :instance, scope: Scope.empty)
|
|
200
|
+
scope.discovered_method?(class_name, method_name, kind)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -219,9 +219,252 @@ module Rigor
|
|
|
219
219
|
collapse_union(normalized_union_members(types))
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
# `key_of[T]` type function — projects the type-level
|
|
223
|
+
# union of `T`'s known keys. Recognised shapes:
|
|
224
|
+
#
|
|
225
|
+
# - `Type::HashShape{a: A, b: B}` → `Constant<:a> | Constant<:b>`.
|
|
226
|
+
# - `Type::Tuple[A, B, C]` → `Constant<0> | Constant<1> | Constant<2>`.
|
|
227
|
+
# - `Type::Nominal["Hash", [K, V]]` → `K` (untyped if absent).
|
|
228
|
+
# - `Type::Nominal["Array", [E]]` → `non-negative-int`.
|
|
229
|
+
# - `Type::Constant` whose value is a Hash / Array / Range —
|
|
230
|
+
# project through the literal's per-element keys.
|
|
231
|
+
#
|
|
232
|
+
# Other inputs (`Top`, `Dynamic`, untyped Nominals, `Union`,
|
|
233
|
+
# `Refined`, `Difference`, `Intersection`) project to `top`
|
|
234
|
+
# so the type function always returns a value — callers may
|
|
235
|
+
# narrow further when they know more.
|
|
236
|
+
def key_of(type)
|
|
237
|
+
case type
|
|
238
|
+
when HashShape then hash_shape_keys(type)
|
|
239
|
+
when Tuple then tuple_indices(type)
|
|
240
|
+
when Nominal then nominal_keys(type)
|
|
241
|
+
when Constant then constant_keys(type.value)
|
|
242
|
+
else top
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# `value_of[T]` type function — projects the type-level
|
|
247
|
+
# union of `T`'s known values. Mirror of `key_of`:
|
|
248
|
+
#
|
|
249
|
+
# - `Type::HashShape{a: A, b: B}` → `A | B`.
|
|
250
|
+
# - `Type::Tuple[A, B, C]` → `A | B | C`.
|
|
251
|
+
# - `Type::Nominal["Hash", [K, V]]` → `V` (untyped if absent).
|
|
252
|
+
# - `Type::Nominal["Array", [E]]` → `E` (untyped if absent).
|
|
253
|
+
# - `Type::Constant` whose value is a Hash / Array / Range —
|
|
254
|
+
# union of `Constant<…>` for each element.
|
|
255
|
+
def value_of(type)
|
|
256
|
+
case type
|
|
257
|
+
when HashShape then hash_shape_values(type)
|
|
258
|
+
when Tuple then tuple_values(type)
|
|
259
|
+
when Nominal then nominal_values(type)
|
|
260
|
+
when Constant then constant_values(type.value)
|
|
261
|
+
else top
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# `int_mask[1, 2, 4]` type function — every Integer
|
|
266
|
+
# representable by a bitwise OR over the listed flags,
|
|
267
|
+
# including 0. The closure of `[1, 2, 4]` is
|
|
268
|
+
# `{0, 1, 2, 3, 4, 5, 6, 7}`. Returns a `Union[Constant…]`
|
|
269
|
+
# for small closures and a covering `IntegerRange` once
|
|
270
|
+
# the cardinality exceeds `INT_MASK_UNION_LIMIT`. Returns
|
|
271
|
+
# `nil` when the input is malformed (non-integer flag,
|
|
272
|
+
# negative flag, or too many flags to compute the closure
|
|
273
|
+
# cheaply).
|
|
274
|
+
INT_MASK_FLAG_LIMIT = 6
|
|
275
|
+
INT_MASK_UNION_LIMIT = 16
|
|
276
|
+
private_constant :INT_MASK_FLAG_LIMIT, :INT_MASK_UNION_LIMIT
|
|
277
|
+
|
|
278
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
279
|
+
def int_mask(flags)
|
|
280
|
+
return nil unless flags.is_a?(Array) && flags.all?(Integer)
|
|
281
|
+
return nil if flags.any?(&:negative?)
|
|
282
|
+
return nil if flags.size > INT_MASK_FLAG_LIMIT
|
|
283
|
+
|
|
284
|
+
values = compute_int_mask_closure(flags)
|
|
285
|
+
return nil if values.nil?
|
|
286
|
+
|
|
287
|
+
if values.size <= INT_MASK_UNION_LIMIT
|
|
288
|
+
union(*values.map { |v| constant_of(v) })
|
|
289
|
+
else
|
|
290
|
+
integer_range(values.min, values.max)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
294
|
+
|
|
295
|
+
# `int_mask_of[T]` — derives the int_mask closure from
|
|
296
|
+
# a finite integer-literal type:
|
|
297
|
+
# `Constant<n>` (single flag), `Union[Constant…]` (every
|
|
298
|
+
# member must be a `Constant<Integer>`). Returns nil for
|
|
299
|
+
# incompatible inputs (Top, Dynamic, IntegerRange, mixed
|
|
300
|
+
# member shapes).
|
|
301
|
+
def int_mask_of(type)
|
|
302
|
+
flags = extract_constant_int_set(type)
|
|
303
|
+
return nil if flags.nil?
|
|
304
|
+
|
|
305
|
+
int_mask(flags)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# `T[K]` indexed-access type operator — extracts the type
|
|
309
|
+
# at index / key `K` from a structured `T`:
|
|
310
|
+
#
|
|
311
|
+
# - `Tuple[A, B, C][Constant<i>]` → `A` / `B` / `C` (out-of-
|
|
312
|
+
# range indices return `Top` for safety).
|
|
313
|
+
# - `HashShape{a: A, b: B}[Constant<:a>]` → `A`.
|
|
314
|
+
# - `Nominal[Hash, [K, V]][_]` → `V` (untyped if absent).
|
|
315
|
+
# - `Nominal[Array, [E]][_]` → `E` (untyped if absent).
|
|
316
|
+
#
|
|
317
|
+
# Other shapes (`Top`, `Dynamic`, untyped Nominals,
|
|
318
|
+
# `Union`, `Refined`, `Difference`, `Intersection`)
|
|
319
|
+
# project to `Top`. The key argument is itself a
|
|
320
|
+
# `Type::t`; only `Type::Constant` keys produce a precise
|
|
321
|
+
# answer.
|
|
322
|
+
def indexed_access(type, key)
|
|
323
|
+
case type
|
|
324
|
+
when Tuple then tuple_indexed_access(type, key)
|
|
325
|
+
when HashShape then hash_shape_indexed_access(type, key)
|
|
326
|
+
when Nominal then nominal_indexed_access(type)
|
|
327
|
+
when Constant then constant_indexed_access(type.value, key)
|
|
328
|
+
else top
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
class << self # rubocop:disable Metrics/ClassLength
|
|
223
333
|
private
|
|
224
334
|
|
|
335
|
+
def hash_shape_keys(shape)
|
|
336
|
+
return Bot.instance if shape.pairs.empty?
|
|
337
|
+
|
|
338
|
+
union(*shape.pairs.keys.map { |k| constant_of(k) })
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def hash_shape_values(shape)
|
|
342
|
+
return Bot.instance if shape.pairs.empty?
|
|
343
|
+
|
|
344
|
+
union(*shape.pairs.values)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def tuple_indices(tuple)
|
|
348
|
+
return Bot.instance if tuple.elements.empty?
|
|
349
|
+
|
|
350
|
+
union(*tuple.elements.each_index.map { |i| constant_of(i) })
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def tuple_values(tuple)
|
|
354
|
+
return Bot.instance if tuple.elements.empty?
|
|
355
|
+
|
|
356
|
+
union(*tuple.elements)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def nominal_keys(nominal)
|
|
360
|
+
case nominal.class_name
|
|
361
|
+
when "Hash"
|
|
362
|
+
nominal.type_args.first || untyped
|
|
363
|
+
when "Array"
|
|
364
|
+
non_negative_int
|
|
365
|
+
else
|
|
366
|
+
top
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def nominal_values(nominal)
|
|
371
|
+
case nominal.class_name
|
|
372
|
+
when "Hash"
|
|
373
|
+
nominal.type_args[1] || untyped
|
|
374
|
+
when "Array"
|
|
375
|
+
nominal.type_args.first || untyped
|
|
376
|
+
else
|
|
377
|
+
top
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# `Type::Constant` only carries scalar literals (Integer
|
|
382
|
+
# / Float / String / Symbol / Range / Rational / Complex
|
|
383
|
+
# / true / false / nil); Array and Hash literals become
|
|
384
|
+
# Tuple / HashShape carriers earlier in the typer. Range
|
|
385
|
+
# is the only scalar with meaningful key/value
|
|
386
|
+
# projections.
|
|
387
|
+
def compute_int_mask_closure(flags)
|
|
388
|
+
unique = flags.uniq
|
|
389
|
+
return [0] if unique.empty?
|
|
390
|
+
|
|
391
|
+
# Closure under bitwise OR over a set of non-negative
|
|
392
|
+
# integers is `0..(max_or_value)` only when the flags
|
|
393
|
+
# are bit-disjoint; otherwise it's a strict subset.
|
|
394
|
+
# Enumerate every subset's OR.
|
|
395
|
+
closure = Set.new([0])
|
|
396
|
+
unique.each do |flag|
|
|
397
|
+
closure |= closure.map { |c| c | flag }
|
|
398
|
+
end
|
|
399
|
+
closure.to_a.sort
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def extract_constant_int_set(type)
|
|
403
|
+
case type
|
|
404
|
+
when Constant
|
|
405
|
+
type.value.is_a?(Integer) ? [type.value] : nil
|
|
406
|
+
when Union
|
|
407
|
+
type.members.all?(Constant) ? type.members.map(&:value).grep(Integer) : nil
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def tuple_indexed_access(tuple, key)
|
|
412
|
+
return top unless key.is_a?(Constant) && key.value.is_a?(Integer)
|
|
413
|
+
|
|
414
|
+
index = key.value
|
|
415
|
+
return top if index.negative? || index >= tuple.elements.size
|
|
416
|
+
|
|
417
|
+
tuple.elements[index]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def hash_shape_indexed_access(shape, key)
|
|
421
|
+
return top unless key.is_a?(Constant)
|
|
422
|
+
|
|
423
|
+
shape.pairs[key.value] || top
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def nominal_indexed_access(nominal)
|
|
427
|
+
case nominal.class_name
|
|
428
|
+
when "Hash" then nominal.type_args[1] || untyped
|
|
429
|
+
when "Array" then nominal.type_args.first || untyped
|
|
430
|
+
else top
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def constant_indexed_access(value, key)
|
|
435
|
+
return top unless key.is_a?(Constant)
|
|
436
|
+
|
|
437
|
+
if value.is_a?(Range) && key.value.is_a?(Integer)
|
|
438
|
+
element = value.to_a[key.value]
|
|
439
|
+
return constant_of(element) unless element.nil?
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
top
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def constant_keys(value)
|
|
446
|
+
return non_negative_int if value.is_a?(Range) && value.begin.is_a?(Integer)
|
|
447
|
+
|
|
448
|
+
top
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def constant_values(value)
|
|
452
|
+
return range_value_of(value) if value.is_a?(Range)
|
|
453
|
+
|
|
454
|
+
top
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def range_value_of(range)
|
|
458
|
+
beg = range.begin
|
|
459
|
+
en = range.end
|
|
460
|
+
return top unless beg.is_a?(Integer) && en.is_a?(Integer)
|
|
461
|
+
|
|
462
|
+
upper = range.exclude_end? ? en - 1 : en
|
|
463
|
+
return Bot.instance if upper < beg
|
|
464
|
+
|
|
465
|
+
integer_range(beg, upper)
|
|
466
|
+
end
|
|
467
|
+
|
|
225
468
|
def normalized_union_members(types)
|
|
226
469
|
flattened = []
|
|
227
470
|
types.each { |t| flatten_into(flattened, t) }
|
data/lib/rigor/type/constant.rb
CHANGED
data/lib/rigor/version.rb
CHANGED
data/lib/rigor.rb
CHANGED
|
@@ -18,6 +18,7 @@ require_relative "rigor/inference/method_parameter_binder"
|
|
|
18
18
|
require_relative "rigor/inference/closure_escape_analyzer"
|
|
19
19
|
require_relative "rigor/inference/statement_evaluator"
|
|
20
20
|
require_relative "rigor/scope"
|
|
21
|
+
require_relative "rigor/reflection"
|
|
21
22
|
require_relative "rigor/source"
|
|
22
23
|
require_relative "rigor/inference/scope_indexer"
|
|
23
24
|
require_relative "rigor/inference/coverage_scanner"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Rigor
|
|
2
|
+
module Reflection
|
|
3
|
+
def self?.class_known?: (String | Symbol class_name, ?scope: Scope) -> bool
|
|
4
|
+
def self?.rbs_class_known?: (String | Symbol class_name, ?scope: Scope) -> bool
|
|
5
|
+
def self?.class_ordering: (String | Symbol lhs, String | Symbol rhs, ?scope: Scope) -> Symbol
|
|
6
|
+
def self?.nominal_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Nominal?
|
|
7
|
+
def self?.singleton_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Singleton?
|
|
8
|
+
def self?.constant_type_for: (String | Symbol constant_name, ?scope: Scope) -> Type::t?
|
|
9
|
+
def self?.instance_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
10
|
+
def self?.singleton_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
11
|
+
def self?.instance_definition: (String | Symbol class_name, ?scope: Scope) -> untyped
|
|
12
|
+
def self?.singleton_definition: (String | Symbol class_name, ?scope: Scope) -> untyped
|
|
13
|
+
def self?.class_type_param_names: (String | Symbol class_name, ?scope: Scope) -> Array[Symbol]
|
|
14
|
+
def self?.discovered_class?: (String | Symbol class_name, ?scope: Scope) -> bool
|
|
15
|
+
def self?.discovered_method?: (String | Symbol class_name, String | Symbol method_name, ?kind: Symbol, ?scope: Scope) -> bool
|
|
16
|
+
end
|
|
17
|
+
end
|
data/sig/rigor/type.rbs
CHANGED
|
@@ -256,6 +256,11 @@ module Rigor
|
|
|
256
256
|
def self?.tuple_of: (*Type::t elements) -> Tuple
|
|
257
257
|
def self?.hash_shape_of: (?Hash[untyped, Type::t]? pairs, **untyped options) -> HashShape
|
|
258
258
|
def self?.union: (*Type::t types) -> Type::t
|
|
259
|
+
def self?.key_of: (Type::t type) -> Type::t
|
|
260
|
+
def self?.value_of: (Type::t type) -> Type::t
|
|
261
|
+
def self?.int_mask: (Array[Integer] flags) -> Type::t?
|
|
262
|
+
def self?.int_mask_of: (Type::t type) -> Type::t?
|
|
263
|
+
def self?.indexed_access: (Type::t type, Type::t key) -> Type::t
|
|
259
264
|
end
|
|
260
265
|
end
|
|
261
266
|
end
|
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.
|
|
4
|
+
version: 0.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rigor contributors
|
|
@@ -169,6 +169,7 @@ files:
|
|
|
169
169
|
- data/builtins/ruby_core/hash.yml
|
|
170
170
|
- data/builtins/ruby_core/io.yml
|
|
171
171
|
- data/builtins/ruby_core/numeric.yml
|
|
172
|
+
- data/builtins/ruby_core/pathname.yml
|
|
172
173
|
- data/builtins/ruby_core/range.yml
|
|
173
174
|
- data/builtins/ruby_core/rational.yml
|
|
174
175
|
- data/builtins/ruby_core/set.yml
|
|
@@ -205,6 +206,7 @@ files:
|
|
|
205
206
|
- lib/rigor/inference/builtins/hash_catalog.rb
|
|
206
207
|
- lib/rigor/inference/builtins/method_catalog.rb
|
|
207
208
|
- lib/rigor/inference/builtins/numeric_catalog.rb
|
|
209
|
+
- lib/rigor/inference/builtins/pathname_catalog.rb
|
|
208
210
|
- lib/rigor/inference/builtins/range_catalog.rb
|
|
209
211
|
- lib/rigor/inference/builtins/rational_catalog.rb
|
|
210
212
|
- lib/rigor/inference/builtins/set_catalog.rb
|
|
@@ -216,6 +218,7 @@ files:
|
|
|
216
218
|
- lib/rigor/inference/fallback.rb
|
|
217
219
|
- lib/rigor/inference/fallback_tracer.rb
|
|
218
220
|
- lib/rigor/inference/method_dispatcher.rb
|
|
221
|
+
- lib/rigor/inference/method_dispatcher/block_folding.rb
|
|
219
222
|
- lib/rigor/inference/method_dispatcher/constant_folding.rb
|
|
220
223
|
- lib/rigor/inference/method_dispatcher/file_folding.rb
|
|
221
224
|
- lib/rigor/inference/method_dispatcher/iterator_dispatch.rb
|
|
@@ -230,6 +233,7 @@ files:
|
|
|
230
233
|
- lib/rigor/inference/scope_indexer.rb
|
|
231
234
|
- lib/rigor/inference/statement_evaluator.rb
|
|
232
235
|
- lib/rigor/rbs_extended.rb
|
|
236
|
+
- lib/rigor/reflection.rb
|
|
233
237
|
- lib/rigor/scope.rb
|
|
234
238
|
- lib/rigor/source.rb
|
|
235
239
|
- lib/rigor/source/node_locator.rb
|
|
@@ -259,6 +263,7 @@ files:
|
|
|
259
263
|
- sig/rigor/environment.rbs
|
|
260
264
|
- sig/rigor/inference.rbs
|
|
261
265
|
- sig/rigor/rbs_extended.rbs
|
|
266
|
+
- sig/rigor/reflection.rbs
|
|
262
267
|
- sig/rigor/scope.rbs
|
|
263
268
|
- sig/rigor/source.rbs
|
|
264
269
|
- sig/rigor/testing.rbs
|