rigortype 0.1.16 → 0.1.17
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/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +209 -0
- data/lib/rigor/analysis/check_rules.rb +149 -70
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner.rb +434 -37
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +147 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +30 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_instance_definitions.rb +3 -16
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +99 -1
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +2 -7
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +125 -43
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +13 -3
- data/lib/rigor/environment/rbs_loader.rb +76 -3
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +140 -20
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +99 -59
- data/lib/rigor/inference/narrowing.rb +202 -5
- data/lib/rigor/inference/scope_indexer.rb +134 -7
- data/lib/rigor/inference/statement_evaluator.rb +105 -26
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/base.rb +20 -4
- data/lib/rigor/plugin/registry.rb +39 -1
- data/lib/rigor/rbs_extended/conformance_checker.rb +208 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope.rb +123 -9
- data/lib/rigor/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +17 -0
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +5 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/inference.rbs +22 -0
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +5 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- metadata +22 -1
|
@@ -501,6 +501,27 @@ module Rigor
|
|
|
501
501
|
# registry rather than crashing.
|
|
502
502
|
end
|
|
503
503
|
|
|
504
|
+
# Like {#each_class_decl_annotation}, but also yields the
|
|
505
|
+
# owning class / module's RBS name as the first block
|
|
506
|
+
# argument: `(class_name, annotation_string, location)`. Used
|
|
507
|
+
# by {Rigor::RbsExtended::ConformanceChecker} to resolve a
|
|
508
|
+
# `rigor:v1:conforms-to` directive back to the class it
|
|
509
|
+
# annotates. Same fail-soft policy as the un-named variant.
|
|
510
|
+
def each_class_decl_annotation_with_name
|
|
511
|
+
return enum_for(:each_class_decl_annotation_with_name) unless block_given?
|
|
512
|
+
return if env.nil?
|
|
513
|
+
|
|
514
|
+
env.class_decls.each do |rbs_name, entry|
|
|
515
|
+
entry.each_decl do |decl|
|
|
516
|
+
next unless decl.respond_to?(:annotations)
|
|
517
|
+
|
|
518
|
+
decl.annotations.each { |a| yield rbs_name.to_s, a.string, a.location }
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
rescue ::RBS::BaseError, ::Ractor::IsolationError
|
|
522
|
+
# fail-soft: see #each_class_decl_annotation.
|
|
523
|
+
end
|
|
524
|
+
|
|
504
525
|
# Returns a frozen `Hash<String, String>` mapping each loaded
|
|
505
526
|
# class / module name (top-level prefixed) to the file path of
|
|
506
527
|
# its FIRST declaration's RBS source. Used by
|
|
@@ -571,6 +592,44 @@ module Rigor
|
|
|
571
592
|
definition.methods[method_name.to_sym]
|
|
572
593
|
end
|
|
573
594
|
|
|
595
|
+
# @return [Array<Symbol>, nil] every instance-method name on
|
|
596
|
+
# `class_name` — own, inherited, and included — as resolved
|
|
597
|
+
# by `RBS::DefinitionBuilder`. Returns `nil` (NOT `[]`) when
|
|
598
|
+
# the class definition cannot be built so callers can tell
|
|
599
|
+
# "no methods" apart from "unknown class". Used by the
|
|
600
|
+
# `rigor:v1:conforms-to` presence check
|
|
601
|
+
# ({Rigor::RbsExtended::ConformanceChecker}).
|
|
602
|
+
def instance_method_names(class_name)
|
|
603
|
+
definition = instance_definition(class_name)
|
|
604
|
+
return nil unless definition
|
|
605
|
+
|
|
606
|
+
definition.methods.keys
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
# @return [RBS::Definition, nil] the built definition for the RBS
|
|
610
|
+
# interface `interface_name` (`_RewindableStream`), whose `.methods`
|
|
611
|
+
# are the required members (including interface-ancestor members).
|
|
612
|
+
# Returns `nil` when the name does not resolve to a loaded interface
|
|
613
|
+
# (a typo, or the defining library / sig set is not on the load
|
|
614
|
+
# path). Fail-soft on RBS build errors.
|
|
615
|
+
def interface_definition(interface_name)
|
|
616
|
+
rbs_name = parse_type_name(interface_name)
|
|
617
|
+
return nil unless rbs_name
|
|
618
|
+
return nil if env.nil?
|
|
619
|
+
return nil unless env.interface_decls.key?(rbs_name)
|
|
620
|
+
|
|
621
|
+
builder.build_interface(rbs_name)
|
|
622
|
+
rescue ::RBS::BaseError
|
|
623
|
+
nil
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# @return [Array<Symbol>, nil] every method name required by the RBS
|
|
627
|
+
# interface `interface_name`, or nil when it does not resolve. Thin
|
|
628
|
+
# accessor over {#interface_definition} for the presence check.
|
|
629
|
+
def interface_method_names(interface_name)
|
|
630
|
+
interface_definition(interface_name)&.methods&.keys
|
|
631
|
+
end
|
|
632
|
+
|
|
574
633
|
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
575
634
|
# object) definition for `class_name`. The methods on this
|
|
576
635
|
# definition are the *class methods* of `class_name`, including
|
|
@@ -983,14 +1042,28 @@ module Rigor
|
|
|
983
1042
|
nil
|
|
984
1043
|
end
|
|
985
1044
|
|
|
1045
|
+
# Memoised on `@state` (the per-loader store also holding `:env` /
|
|
1046
|
+
# `:builder`): `RBS::TypeName.parse` is a pure, deterministic
|
|
1047
|
+
# function of the normalised string, and the `RBS::TypeName` it
|
|
1048
|
+
# returns is a frozen value object safe to share across callers
|
|
1049
|
+
# (every consumer only reads it — `env.class_decls.key?` /
|
|
1050
|
+
# `builder.build_*`). The same handful of class names are parsed
|
|
1051
|
+
# on nearly every call-site dispatch, so this was a top allocation
|
|
1052
|
+
# site; caching the immutable result (nil included) removes it.
|
|
986
1053
|
def parse_type_name(name)
|
|
987
1054
|
s = name.to_s
|
|
988
1055
|
return nil if s.empty?
|
|
989
1056
|
|
|
990
1057
|
s = "::#{s}" unless s.start_with?("::")
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1058
|
+
cache = (@state[:type_name_cache] ||= {})
|
|
1059
|
+
return cache[s] if cache.key?(s)
|
|
1060
|
+
|
|
1061
|
+
cache[s] =
|
|
1062
|
+
begin
|
|
1063
|
+
RBS::TypeName.parse(s)
|
|
1064
|
+
rescue ::RBS::BaseError
|
|
1065
|
+
nil
|
|
1066
|
+
end
|
|
994
1067
|
end
|
|
995
1068
|
|
|
996
1069
|
def compute_class_known(name)
|
|
@@ -174,8 +174,7 @@ module Rigor
|
|
|
174
174
|
|
|
175
175
|
def bind_trailing_positionals(params_node, bindings, cursor)
|
|
176
176
|
params_node.posts.each do |param|
|
|
177
|
-
|
|
178
|
-
bindings[name] = positional_type_at(cursor) if name
|
|
177
|
+
bind_required_param(param, cursor, bindings)
|
|
179
178
|
cursor += 1
|
|
180
179
|
end
|
|
181
180
|
cursor
|
|
@@ -13,11 +13,8 @@ module Rigor
|
|
|
13
13
|
# `ary_push_internal`, …) that the classifier does not yet
|
|
14
14
|
# recognise. The blocklist captures the methods we have
|
|
15
15
|
# specifically observed flowing as `:leaf` despite mutating.
|
|
16
|
-
ARRAY_CATALOG = MethodCatalog.
|
|
17
|
-
|
|
18
|
-
"../../../../data/builtins/ruby_core/array.yml",
|
|
19
|
-
__dir__
|
|
20
|
-
),
|
|
16
|
+
ARRAY_CATALOG = MethodCatalog.for_topic(
|
|
17
|
+
"array",
|
|
21
18
|
mutating_selectors: {
|
|
22
19
|
"Array" => Set[
|
|
23
20
|
# Mutators classified `:leaf` by the C-body heuristic
|
|
@@ -13,11 +13,8 @@ module Rigor
|
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
14
|
# The data is consumed by future include-aware lookup —
|
|
15
15
|
# see `docs/CURRENT_WORK.md` for the planned slice.
|
|
16
|
-
COMPARABLE_CATALOG = MethodCatalog.
|
|
17
|
-
|
|
18
|
-
"../../../../data/builtins/ruby_core/comparable.yml",
|
|
19
|
-
__dir__
|
|
20
|
-
),
|
|
16
|
+
COMPARABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
|
+
"comparable",
|
|
21
18
|
mutating_selectors: {
|
|
22
19
|
"Comparable" => Set[]
|
|
23
20
|
}
|
|
@@ -21,11 +21,8 @@ module Rigor
|
|
|
21
21
|
# so a hypothetical future `Constant<Complex>` carrier cannot
|
|
22
22
|
# fold an aliasing copy through the catalog (mirrors
|
|
23
23
|
# `range_catalog.rb`, `time_catalog.rb`, `date_catalog.rb`).
|
|
24
|
-
COMPLEX_CATALOG = MethodCatalog.
|
|
25
|
-
|
|
26
|
-
"../../../../data/builtins/ruby_core/complex.yml",
|
|
27
|
-
__dir__
|
|
28
|
-
),
|
|
24
|
+
COMPLEX_CATALOG = MethodCatalog.for_topic(
|
|
25
|
+
"complex",
|
|
29
26
|
mutating_selectors: {
|
|
30
27
|
"Complex" => Set[
|
|
31
28
|
# Defence in depth: `Complex` does not currently expose
|
|
@@ -54,11 +54,8 @@ module Rigor
|
|
|
54
54
|
# blocklist entry; the entries below are defense-in-depth
|
|
55
55
|
# against indirect mutators the regex might miss in a future
|
|
56
56
|
# CRuby bump.
|
|
57
|
-
DATE_CATALOG = MethodCatalog.
|
|
58
|
-
|
|
59
|
-
"../../../../data/builtins/ruby_core/date.yml",
|
|
60
|
-
__dir__
|
|
61
|
-
),
|
|
57
|
+
DATE_CATALOG = MethodCatalog.for_topic(
|
|
58
|
+
"date",
|
|
62
59
|
mutating_selectors: {
|
|
63
60
|
"Date" => Set[
|
|
64
61
|
# `d_lite_initialize_copy` is already classed
|
|
@@ -22,11 +22,8 @@ module Rigor
|
|
|
22
22
|
# against the analyzer process's registry — what UTF-8's
|
|
23
23
|
# alias list is in the analyzer is not necessarily what it
|
|
24
24
|
# is in the analysed program.
|
|
25
|
-
ENCODING_CATALOG = MethodCatalog.
|
|
26
|
-
|
|
27
|
-
"../../../../data/builtins/ruby_core/encoding.yml",
|
|
28
|
-
__dir__
|
|
29
|
-
),
|
|
25
|
+
ENCODING_CATALOG = MethodCatalog.for_topic(
|
|
26
|
+
"encoding",
|
|
30
27
|
mutating_selectors: {
|
|
31
28
|
"Encoding" => Set[
|
|
32
29
|
# Defence-in-depth: mirrors range_catalog.rb /
|
|
@@ -13,11 +13,8 @@ module Rigor
|
|
|
13
13
|
# (which dispatches on the receiver's concrete class).
|
|
14
14
|
# The data is consumed by future include-aware lookup —
|
|
15
15
|
# see `docs/CURRENT_WORK.md` for the planned slice.
|
|
16
|
-
ENUMERABLE_CATALOG = MethodCatalog.
|
|
17
|
-
|
|
18
|
-
"../../../../data/builtins/ruby_core/enumerable.yml",
|
|
19
|
-
__dir__
|
|
20
|
-
),
|
|
16
|
+
ENUMERABLE_CATALOG = MethodCatalog.for_topic(
|
|
17
|
+
"enumerable",
|
|
21
18
|
mutating_selectors: {
|
|
22
19
|
"Enumerable" => Set[]
|
|
23
20
|
}
|
|
@@ -26,11 +26,8 @@ module Rigor
|
|
|
26
26
|
# already declines) or blocklisted because the static classifier
|
|
27
27
|
# missed an indirect side effect. The remaining `:leaf` method
|
|
28
28
|
# that DOES fold is `#cause`, a pure accessor.
|
|
29
|
-
EXCEPTION_CATALOG = MethodCatalog.
|
|
30
|
-
|
|
31
|
-
"../../../../data/builtins/ruby_core/exception.yml",
|
|
32
|
-
__dir__
|
|
33
|
-
),
|
|
29
|
+
EXCEPTION_CATALOG = MethodCatalog.for_topic(
|
|
30
|
+
"exception",
|
|
34
31
|
mutating_selectors: {
|
|
35
32
|
"Exception" => Set[
|
|
36
33
|
# `exc_initialize` writes `mesg` / `backtrace` ivars on
|
|
@@ -15,11 +15,8 @@ module Rigor
|
|
|
15
15
|
# captures every false-positive `:leaf` we have spotted in the
|
|
16
16
|
# generated YAML — bias toward conservatism so a missed fold is
|
|
17
17
|
# acceptable but a folded mutator/yielder is not.
|
|
18
|
-
HASH_CATALOG = MethodCatalog.
|
|
19
|
-
|
|
20
|
-
"../../../../data/builtins/ruby_core/hash.yml",
|
|
21
|
-
__dir__
|
|
22
|
-
),
|
|
18
|
+
HASH_CATALOG = MethodCatalog.for_topic(
|
|
19
|
+
"hash",
|
|
23
20
|
mutating_selectors: {
|
|
24
21
|
"Hash" => Set[
|
|
25
22
|
# Block-dependent iteration — yields via `rb_hash_foreach`
|
|
@@ -27,6 +27,21 @@ module Rigor
|
|
|
27
27
|
FOLDABLE_PURITIES = Set["leaf", "trivial", "leaf_when_numeric"].freeze
|
|
28
28
|
EMPTY_CATALOG = { "classes" => {} }.freeze
|
|
29
29
|
|
|
30
|
+
# Shared root for the offline-generated catalogues. Resolving it
|
|
31
|
+
# here keeps the repo-relative `../../../../` hop in one place
|
|
32
|
+
# instead of copying it into every per-topic loader.
|
|
33
|
+
DATA_ROOT = File.expand_path("../../../../data/builtins/ruby_core", __dir__)
|
|
34
|
+
private_constant :DATA_ROOT
|
|
35
|
+
|
|
36
|
+
# Build a catalog for a named topic, resolving its YAML path
|
|
37
|
+
# under {DATA_ROOT}. Equivalent to `new(path: …)` for the common
|
|
38
|
+
# case where the file is `<topic>.yml`; prefer this over passing
|
|
39
|
+
# an explicit `File.expand_path` so the data-root hop stays
|
|
40
|
+
# centralised.
|
|
41
|
+
def self.for_topic(topic, mutating_selectors: {})
|
|
42
|
+
new(path: File.join(DATA_ROOT, "#{topic}.yml"), mutating_selectors: mutating_selectors)
|
|
43
|
+
end
|
|
44
|
+
|
|
30
45
|
def initialize(path:, mutating_selectors: {})
|
|
31
46
|
@path = path
|
|
32
47
|
@mutating_selectors = mutating_selectors.transform_values(&:freeze).freeze
|
|
@@ -1,104 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require_relative "method_catalog"
|
|
4
4
|
|
|
5
5
|
module Rigor
|
|
6
6
|
module Inference
|
|
7
7
|
module Builtins
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# produced offline by `tool/extract_numeric_catalog.rb` from the
|
|
11
|
-
# CRuby reference checkout under `references/ruby` plus the RBS
|
|
12
|
-
# core signatures under `references/rbs`.
|
|
8
|
+
# `Numeric` family catalog (Integer, Float, Rational, Complex,
|
|
9
|
+
# Numeric). Singleton — load once, consult during dispatch.
|
|
13
10
|
#
|
|
14
|
-
# The
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
11
|
+
# The catalog is produced offline by `tool/extract_builtin_catalog.rb`
|
|
12
|
+
# from the CRuby reference checkout under `references/ruby` plus the
|
|
13
|
+
# RBS core signatures under `references/rbs`. The loader is the
|
|
14
|
+
# runtime bridge: callers ask "is `Integer#+` safe to invoke during
|
|
15
|
+
# constant folding?" and the answer comes straight from the offline
|
|
16
|
+
# classification (`leaf` / `trivial` / `leaf_when_numeric` are
|
|
17
|
+
# foldable; everything else is not).
|
|
18
18
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# only lets it through when every argument is itself a
|
|
31
|
-
# `Constant<Numeric>` or `IntegerRange` — exactly the gate
|
|
32
|
-
# the catalog tag is named for.
|
|
33
|
-
FOLDABLE_PURITIES = Set["leaf", "trivial", "leaf_when_numeric"].freeze
|
|
34
|
-
|
|
35
|
-
EMPTY_CATALOG = { "classes" => {} }.freeze
|
|
36
|
-
private_constant :EMPTY_CATALOG
|
|
37
|
-
|
|
38
|
-
# Path resolved relative to this file. The catalog ships under
|
|
39
|
-
# `data/builtins/ruby_core/numeric.yml` at the gem root.
|
|
40
|
-
CATALOG_PATH = File.expand_path(
|
|
41
|
-
"../../../../data/builtins/ruby_core/numeric.yml",
|
|
42
|
-
__dir__
|
|
43
|
-
)
|
|
44
|
-
private_constant :CATALOG_PATH
|
|
45
|
-
|
|
46
|
-
class << self
|
|
47
|
-
# @param class_name [String] e.g. "Integer", "Float"
|
|
48
|
-
# @param selector [Symbol, String]
|
|
49
|
-
# @param kind [Symbol] :instance (default) or :singleton
|
|
50
|
-
# @return [Boolean]
|
|
51
|
-
def safe_for_folding?(class_name, selector, kind: :instance)
|
|
52
|
-
entry = method_entry(class_name, selector, kind: kind)
|
|
53
|
-
return false unless entry
|
|
54
|
-
|
|
55
|
-
FOLDABLE_PURITIES.include?(entry["purity"])
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# @return [Hash, nil] catalog entry for the given method, or
|
|
59
|
-
# nil when the method is not registered.
|
|
60
|
-
def method_entry(class_name, selector, kind: :instance)
|
|
61
|
-
klass = catalog.dig("classes", class_name.to_s)
|
|
62
|
-
return nil unless klass
|
|
63
|
-
|
|
64
|
-
bucket_key = kind == :singleton ? "singleton_methods" : "instance_methods"
|
|
65
|
-
klass.dig(bucket_key, selector.to_s)
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Used by tests to drop the cached catalog so a different
|
|
69
|
-
# path or content can be exercised. Production code MUST
|
|
70
|
-
# NOT call this during normal operation.
|
|
71
|
-
#
|
|
72
|
-
# ADR-15 Phase 4b.x — reset re-loads eagerly so the
|
|
73
|
-
# singleton-class `@catalog` ivar stays populated, and
|
|
74
|
-
# the loaded Hash is deep-shared via `Ractor.make_shareable`
|
|
75
|
-
# so a worker Ractor reading the ivar via `catalog.dig(...)`
|
|
76
|
-
# does not trip `Ractor::IsolationError`. Plain `.freeze`
|
|
77
|
-
# is insufficient: YAML parses to a nested Hash/Array/String
|
|
78
|
-
# graph and only the outer Hash would be frozen.
|
|
79
|
-
def reset!
|
|
80
|
-
@catalog = Ractor.make_shareable(load_catalog)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
private
|
|
84
|
-
|
|
85
|
-
attr_reader :catalog
|
|
86
|
-
|
|
87
|
-
def load_catalog
|
|
88
|
-
return EMPTY_CATALOG unless File.exist?(CATALOG_PATH)
|
|
89
|
-
|
|
90
|
-
data = YAML.safe_load_file(CATALOG_PATH, permitted_classes: [Symbol])
|
|
91
|
-
data.is_a?(Hash) ? data : EMPTY_CATALOG
|
|
92
|
-
rescue Psych::SyntaxError
|
|
93
|
-
EMPTY_CATALOG
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# ADR-15 Phase 4b.x — eager-load on the main Ractor at
|
|
98
|
-
# module-load time so worker Ractors only READ the
|
|
99
|
-
# populated singleton-class `@catalog` ivar.
|
|
100
|
-
reset!
|
|
101
|
-
end
|
|
19
|
+
# No mutation blocklist is needed. The numeric classes expose no
|
|
20
|
+
# foldable bang or indirect-mutator method that the static C
|
|
21
|
+
# classifier mis-attributes (every `:leaf` numeric method is a pure
|
|
22
|
+
# value computation), so the generic `MethodCatalog` loader — shared
|
|
23
|
+
# with the eighteen other per-class catalogs — covers it directly.
|
|
24
|
+
# This previously hand-rolled its own `safe_for_folding?` /
|
|
25
|
+
# `method_entry` / `load_catalog` copy of `MethodCatalog`; folding
|
|
26
|
+
# it onto the shared loader also picks up alias resolution (e.g.
|
|
27
|
+
# `Integer#magnitude` → `abs`, `Integer#inspect` → `to_s`), which the
|
|
28
|
+
# old bespoke loader silently dropped.
|
|
29
|
+
NUMERIC_CATALOG = MethodCatalog.for_topic("numeric")
|
|
102
30
|
end
|
|
103
31
|
end
|
|
104
32
|
end
|
|
@@ -16,11 +16,8 @@ module Rigor
|
|
|
16
16
|
# helper that triggered the false positive (see
|
|
17
17
|
# `string_catalog.rb`, `array_catalog.rb`, `time_catalog.rb`
|
|
18
18
|
# for the canonical shape).
|
|
19
|
-
PATHNAME_CATALOG = MethodCatalog.
|
|
20
|
-
|
|
21
|
-
"../../../../data/builtins/ruby_core/pathname.yml",
|
|
22
|
-
__dir__
|
|
23
|
-
),
|
|
19
|
+
PATHNAME_CATALOG = MethodCatalog.for_topic(
|
|
20
|
+
"pathname",
|
|
24
21
|
mutating_selectors: {
|
|
25
22
|
"Pathname" => Set[
|
|
26
23
|
# initialize_copy is blocklisted by convention so a
|
|
@@ -31,11 +31,8 @@ module Rigor
|
|
|
31
31
|
# `#source_location`, `#name`, `#owner`, `#receiver`) remain
|
|
32
32
|
# foldable; the RBS tier still resolves return types for
|
|
33
33
|
# the blocklisted methods so callers do not lose precision.
|
|
34
|
-
PROC_CATALOG = MethodCatalog.
|
|
35
|
-
|
|
36
|
-
"../../../../data/builtins/ruby_core/proc.yml",
|
|
37
|
-
__dir__
|
|
38
|
-
),
|
|
34
|
+
PROC_CATALOG = MethodCatalog.for_topic(
|
|
35
|
+
"proc",
|
|
39
36
|
mutating_selectors: {
|
|
40
37
|
"Proc" => Set[
|
|
41
38
|
# `#call` / `#[]` / `#===` / `#yield` invoke the proc
|
|
@@ -21,11 +21,8 @@ module Rigor
|
|
|
21
21
|
# functionally pure they would produce a misleading constant
|
|
22
22
|
# at fold time. The whole class is conservative-by-default
|
|
23
23
|
# at the catalog tier; precision flows through the RBS layer.
|
|
24
|
-
RANDOM_CATALOG = MethodCatalog.
|
|
25
|
-
|
|
26
|
-
"../../../../data/builtins/ruby_core/random.yml",
|
|
27
|
-
__dir__
|
|
28
|
-
),
|
|
24
|
+
RANDOM_CATALOG = MethodCatalog.for_topic(
|
|
25
|
+
"random",
|
|
29
26
|
mutating_selectors: {
|
|
30
27
|
"Random" => Set[
|
|
31
28
|
# `rand_random` -> `random_real` / `random_ulong_limited`
|
|
@@ -15,11 +15,8 @@ module Rigor
|
|
|
15
15
|
# routes through a helper the block/yield regex does not
|
|
16
16
|
# recognise, so the classifier mis-flags them as `:leaf`
|
|
17
17
|
# despite yielding to a block.
|
|
18
|
-
RANGE_CATALOG = MethodCatalog.
|
|
19
|
-
|
|
20
|
-
"../../../../data/builtins/ruby_core/range.yml",
|
|
21
|
-
__dir__
|
|
22
|
-
),
|
|
18
|
+
RANGE_CATALOG = MethodCatalog.for_topic(
|
|
19
|
+
"range",
|
|
23
20
|
mutating_selectors: {
|
|
24
21
|
"Range" => Set[
|
|
25
22
|
# `range_initialize` / `range_initialize_copy` write
|
|
@@ -22,11 +22,8 @@ module Rigor
|
|
|
22
22
|
# hypothetical future `Constant<Rational>` carrier cannot
|
|
23
23
|
# fold an aliasing copy through the catalog and surface a
|
|
24
24
|
# shared mutable handle.
|
|
25
|
-
RATIONAL_CATALOG = MethodCatalog.
|
|
26
|
-
|
|
27
|
-
"../../../../data/builtins/ruby_core/rational.yml",
|
|
28
|
-
__dir__
|
|
29
|
-
),
|
|
25
|
+
RATIONAL_CATALOG = MethodCatalog.for_topic(
|
|
26
|
+
"rational",
|
|
30
27
|
mutating_selectors: {
|
|
31
28
|
"Rational" => Set[
|
|
32
29
|
:initialize_copy
|
|
@@ -38,11 +38,8 @@ module Rigor
|
|
|
38
38
|
# signatures already widen the answer enough to keep the
|
|
39
39
|
# behaviour sound; revisit if the dispatcher ever grows a
|
|
40
40
|
# singleton-aware catalog path.
|
|
41
|
-
REGEXP_CATALOG = MethodCatalog.
|
|
42
|
-
|
|
43
|
-
"../../../../data/builtins/ruby_core/re.yml",
|
|
44
|
-
__dir__
|
|
45
|
-
),
|
|
41
|
+
REGEXP_CATALOG = MethodCatalog.for_topic(
|
|
42
|
+
"re",
|
|
46
43
|
mutating_selectors: {
|
|
47
44
|
"Regexp" => Set[
|
|
48
45
|
# Defensive: aliasing-copy semantics already covered
|
|
@@ -19,11 +19,8 @@ module Rigor
|
|
|
19
19
|
# (`set_iter`, `RETURN_SIZED_ENUMERATOR`) and its identity-
|
|
20
20
|
# mode and reset paths drive into helpers the regex classifier
|
|
21
21
|
# does not yet recognise as block-yielding or mutating.
|
|
22
|
-
SET_CATALOG = MethodCatalog.
|
|
23
|
-
|
|
24
|
-
"../../../../data/builtins/ruby_core/set.yml",
|
|
25
|
-
__dir__
|
|
26
|
-
),
|
|
22
|
+
SET_CATALOG = MethodCatalog.for_topic(
|
|
23
|
+
"set",
|
|
27
24
|
mutating_selectors: {
|
|
28
25
|
"Set" => Set[
|
|
29
26
|
# Indirect mutators classified `:leaf` because the C
|
|
@@ -15,11 +15,8 @@ module Rigor
|
|
|
15
15
|
# mutation primitives). Adding to the blocklist is the
|
|
16
16
|
# corrective surface for false positives until the
|
|
17
17
|
# classifier learns the helper functions.
|
|
18
|
-
STRING_CATALOG = MethodCatalog.
|
|
19
|
-
|
|
20
|
-
"../../../../data/builtins/ruby_core/string.yml",
|
|
21
|
-
__dir__
|
|
22
|
-
),
|
|
18
|
+
STRING_CATALOG = MethodCatalog.for_topic(
|
|
19
|
+
"string",
|
|
23
20
|
mutating_selectors: {
|
|
24
21
|
"String" => Set[
|
|
25
22
|
:replace, :initialize, :initialize_copy, :clear, :<<, :concat, :insert,
|
|
@@ -23,11 +23,8 @@ module Rigor
|
|
|
23
23
|
# member but the answer depends on the subclass's member
|
|
24
24
|
# definition, which the catalog does not see, so we blocklist
|
|
25
25
|
# it defensively.
|
|
26
|
-
STRUCT_CATALOG = MethodCatalog.
|
|
27
|
-
|
|
28
|
-
"../../../../data/builtins/ruby_core/struct.yml",
|
|
29
|
-
__dir__
|
|
30
|
-
),
|
|
26
|
+
STRUCT_CATALOG = MethodCatalog.for_topic(
|
|
27
|
+
"struct",
|
|
31
28
|
mutating_selectors: {
|
|
32
29
|
"Struct" => Set[
|
|
33
30
|
# Defensive: aliasing-copy semantics on a hypothetical
|
|
@@ -29,11 +29,8 @@ module Rigor
|
|
|
29
29
|
# The blocklist captures the false-positive `:leaf` entries
|
|
30
30
|
# whose helper functions the regex classifier did not
|
|
31
31
|
# recognise as mutators.
|
|
32
|
-
TIME_CATALOG = MethodCatalog.
|
|
33
|
-
|
|
34
|
-
"../../../../data/builtins/ruby_core/time.yml",
|
|
35
|
-
__dir__
|
|
36
|
-
),
|
|
32
|
+
TIME_CATALOG = MethodCatalog.for_topic(
|
|
33
|
+
"time",
|
|
37
34
|
mutating_selectors: {
|
|
38
35
|
"Time" => Set[
|
|
39
36
|
# `time_init_copy` writes the `timew` and `vtm` slots on
|