rigortype 0.1.18 → 0.2.0
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 +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +756 -132
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/diagnostic.rb +8 -0
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
- data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
- data/lib/rigor/analysis/runner.rb +75 -27
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +31 -25
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +122 -16
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +118 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +4 -5
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +8 -4
- data/lib/rigor/cli/triage_renderer.rb +15 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +12 -3
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +100 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +74 -5
- data/lib/rigor/environment.rb +17 -7
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +1072 -71
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +112 -49
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +147 -11
- data/lib/rigor/inference/narrowing.rb +284 -53
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +821 -76
- data/lib/rigor/inference/statement_evaluator.rb +1179 -102
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +245 -87
- data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +6 -8
- data/lib/rigor/plugin/manifest.rb +49 -90
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +18 -18
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +16 -2
- data/lib/rigor/scope.rb +185 -16
- data/lib/rigor/sig_gen/generator.rb +8 -0
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +45 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +18 -1
- data/sig/rigor/type.rbs +37 -1
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +25 -2
- data/lib/rigor/plugin/macro/external_file.rb +0 -143
|
@@ -7,18 +7,15 @@ module Rigor
|
|
|
7
7
|
# piece of a Rigor-side type expression that the reducer
|
|
8
8
|
# ({HktReducer}) walks against a concrete argument list.
|
|
9
9
|
#
|
|
10
|
-
# Slice 2a ships a programmatic constructor surface
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# `body` String stays opaque and `body_tree` is the
|
|
17
|
-
# evaluable form.
|
|
10
|
+
# Slice 2a ships a programmatic constructor surface; plugin
|
|
11
|
+
# and Rigor-bundled overlay authors may build a body tree
|
|
12
|
+
# by hand using these node types. The string-grammar parser
|
|
13
|
+
# (`HktBodyParser`, Slice 2b, shipped) reads `Definition#body`
|
|
14
|
+
# (populated by Slice 1's `HktDirectives.parse_define`) into
|
|
15
|
+
# this node tree; `body_tree` is the evaluable form.
|
|
18
16
|
#
|
|
19
|
-
# The
|
|
20
|
-
#
|
|
21
|
-
# near-term adopters:
|
|
17
|
+
# The nine node types cover JSON.parse, dry-monads, and the
|
|
18
|
+
# ADR-20 § D3 conditional / membership forms (shipped):
|
|
22
19
|
#
|
|
23
20
|
# - {TypeLeaf} — wraps a fully-built `Rigor::Type`
|
|
24
21
|
# (use for atoms like `nil`, `Constant<true>`,
|
|
@@ -11,29 +11,27 @@ module Rigor
|
|
|
11
11
|
# `%a{rigor:v1:hkt_define}` payloads) into the `HktBody`
|
|
12
12
|
# node tree the Slice 2a reducer evaluates against.
|
|
13
13
|
#
|
|
14
|
-
# The
|
|
15
|
-
# union-of-atoms
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# without conditionals). The conditional / indexed-access
|
|
20
|
-
# forms (`E <: T ? A : B`, `E in [k1, k2]`) drafted in D3
|
|
21
|
-
# remain a follow-up slice — bodies that contain `?`
|
|
22
|
-
# raise `ParseError` and the calling directive parser
|
|
23
|
-
# drops the body_tree (the body String remains stored and
|
|
24
|
-
# the reducer falls back to `app.bound`).
|
|
14
|
+
# The grammar implements the full ADR-20 § D3 subset:
|
|
15
|
+
# union-of-atoms/parameterised-forms for `JSON.parse`'s
|
|
16
|
+
# `json::value` recursive sum, plus the conditional and
|
|
17
|
+
# membership forms shipped in subsequent slices. Indexed-access
|
|
18
|
+
# forms remain deferred (no concrete demand yet).
|
|
25
19
|
#
|
|
26
|
-
# ## Grammar
|
|
20
|
+
# ## Grammar
|
|
27
21
|
#
|
|
28
22
|
# body := union
|
|
29
23
|
# union := type_expr ("|" type_expr)*
|
|
30
24
|
# type_expr := atom | nominal_app | app_ref | param
|
|
25
|
+
# | conditional
|
|
31
26
|
# atom := "nil" | "true" | "false" | "bool" | "untyped"
|
|
32
27
|
# param := UCNAME (when UCNAME ∈ params)
|
|
33
28
|
# nominal_app := class_name ("[" type_expr ("," type_expr)* "]")?
|
|
34
29
|
# class_name := "::"? UCNAME ("::" UCNAME)*
|
|
35
30
|
# app_ref := "App" "[" uri "," type_expr ("," type_expr)* "]"
|
|
36
31
|
# uri := IDENT ("::" IDENT)+
|
|
32
|
+
# conditional := "(" test "?" union ":" union ")"
|
|
33
|
+
# test := type_expr ("<:" | "==") type_expr
|
|
34
|
+
# | type_expr "in" "[" type_expr ("," type_expr)* "]"
|
|
37
35
|
# UCNAME := /[A-Z]\w*/
|
|
38
36
|
# IDENT := /[a-z_]\w*/
|
|
39
37
|
#
|
|
@@ -10,12 +10,12 @@ module Rigor
|
|
|
10
10
|
# `%a{rigor:v1:hkt_define: ...}` annotations in shipped
|
|
11
11
|
# `.rbs` files.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
13
|
+
# The registry stores registration metadata (arity, variance,
|
|
14
|
+
# bound) and the definition body as both a raw String and an
|
|
15
|
+
# evaluable `HktBody` node tree. `HktReducer` (Slice 2a) and
|
|
16
|
+
# `HktBodyParser` (Slice 2b) are both shipped and reduce
|
|
17
|
+
# `Type::App` instances against the definition. The carrier
|
|
18
|
+
# never needs to read from the registry
|
|
19
19
|
# because Slice 1's `Type::App` carries its `bound` directly;
|
|
20
20
|
# the registry exists at this slice solely so the parser
|
|
21
21
|
# round-trip and downstream slices have a stable target API.
|
|
@@ -65,16 +65,15 @@ module Rigor
|
|
|
65
65
|
# definition.
|
|
66
66
|
#
|
|
67
67
|
# `body` is the raw String payload from the `%a{...}`
|
|
68
|
-
# annotation (Slice 1's parser populates it)
|
|
69
|
-
#
|
|
68
|
+
# annotation (Slice 1's parser populates it); parsed into
|
|
69
|
+
# `body_tree` by `HktBodyParser` (Slice 2b, shipped).
|
|
70
70
|
#
|
|
71
|
-
# `body_tree` is the
|
|
71
|
+
# `body_tree` is the evaluable form: a
|
|
72
72
|
# `Rigor::Inference::HktBody::*` node tree the Slice 2a
|
|
73
73
|
# reducer walks against the application's concrete
|
|
74
74
|
# arguments. Plugin and Rigor-bundled overlay authors
|
|
75
75
|
# construct it programmatically through
|
|
76
|
-
# {
|
|
77
|
-
# it from `body` once it ships. The reducer treats a
|
|
76
|
+
# {.definition_with_body_tree}. The reducer treats a
|
|
78
77
|
# `nil` `body_tree` as "definition not yet evaluable"
|
|
79
78
|
# and returns the registered bound.
|
|
80
79
|
Definition = Data.define(:uri, :params, :body, :body_tree, :source_path, :source_line) do
|
|
@@ -17,7 +17,7 @@ module Rigor
|
|
|
17
17
|
# `MyApp.get(...)` call);
|
|
18
18
|
# - the underlying class `X` equals or inherits from the
|
|
19
19
|
# entry's `receiver_constraint`;
|
|
20
|
-
# - the call's method name is in the entry's `
|
|
20
|
+
# - the call's method name is in the entry's `method_names`.
|
|
21
21
|
#
|
|
22
22
|
# On a match the helper returns the **instance** type of
|
|
23
23
|
# the receiver class (`Nominal[X]`) — the narrowed
|
|
@@ -55,7 +55,7 @@ module Rigor
|
|
|
55
55
|
# replaces the per-call plugins × block_as_methods linear scan.
|
|
56
56
|
# Entries arrive in (plugin registration, declaration) order, so
|
|
57
57
|
# the first ancestry match below is the same entry the previous
|
|
58
|
-
# walk returned; the
|
|
58
|
+
# walk returned; the method-name membership the old `matches?` checked
|
|
59
59
|
# is guaranteed by the table key.
|
|
60
60
|
entries = registry.contribution_index.block_entries_for(call_node.name)
|
|
61
61
|
entries.each do |entry|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# `Array#to_h { |x| [k, v] }` (and the no-block-pair tuple form's
|
|
9
|
+
# block sibling) return-type fold.
|
|
10
|
+
#
|
|
11
|
+
# `Enumerable#to_h` with a block maps every element to a
|
|
12
|
+
# `[key, value]` pair and collects them into a Hash. When the
|
|
13
|
+
# block's inferred return type is a recognizable 2-element
|
|
14
|
+
# `Tuple` (`[K, V]`), this tier projects the pair into a
|
|
15
|
+
# `Hash[K, V]` nominal whose key/value parameters are the
|
|
16
|
+
# widened pair types. Without this fold the call hits the RBS
|
|
17
|
+
# generic and the block's `[K, V]` return is dropped, typing as
|
|
18
|
+
# `Hash[Dynamic[top], Dynamic[top]]`.
|
|
19
|
+
#
|
|
20
|
+
# Value-pinned constants in the pair (`Constant[2]`) are widened
|
|
21
|
+
# to their nominal (`Integer`) for the Hash parameters: the built
|
|
22
|
+
# hash holds many keys, so pinning the parameter to a single
|
|
23
|
+
# element's literal would be unsound for the aggregate. The same
|
|
24
|
+
# widening the loop-body fixpoint applies (`Combinator#
|
|
25
|
+
# widen_value_pinned`) is reused.
|
|
26
|
+
#
|
|
27
|
+
# Declines (returns `nil`, leaving today's RBS answer) when:
|
|
28
|
+
#
|
|
29
|
+
# - there is no block at the call site,
|
|
30
|
+
# - the block return type is not a 2-element `Tuple`, or
|
|
31
|
+
# - the receiver is not an Array-shaped carrier (`Tuple` or
|
|
32
|
+
# `Array` nominal). Hash receivers keep their existing
|
|
33
|
+
# `ShapeDispatch#hash_to_h` identity fold.
|
|
34
|
+
module ArrayToHFolding
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
def try_dispatch(context)
|
|
38
|
+
return nil unless context.method_name == :to_h
|
|
39
|
+
|
|
40
|
+
block_type = context.block_type
|
|
41
|
+
return nil unless block_type.is_a?(Type::Tuple)
|
|
42
|
+
return nil unless block_type.elements.size == 2
|
|
43
|
+
return nil unless array_shaped?(context.receiver)
|
|
44
|
+
|
|
45
|
+
key = Type::Combinator.widen_value_pinned(block_type.elements[0])
|
|
46
|
+
value = Type::Combinator.widen_value_pinned(block_type.elements[1])
|
|
47
|
+
Type::Combinator.nominal_of("Hash", type_args: [key, value])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def array_shaped?(receiver)
|
|
51
|
+
case receiver
|
|
52
|
+
when Type::Tuple then true
|
|
53
|
+
when Type::Nominal then receiver.class_name == "Array"
|
|
54
|
+
else false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -42,10 +42,7 @@ module Rigor
|
|
|
42
42
|
#
|
|
43
43
|
# This is the single place the call-context field list is
|
|
44
44
|
# enumerated — the whole point of the value object is to absorb
|
|
45
|
-
# the wide keyword list the tiers used to each redeclare.
|
|
46
|
-
# ParameterLists disable here retires the per-tier disables (the
|
|
47
|
-
# `RbsDispatch` quartet-plus signatures) rather than adding to
|
|
48
|
-
# them.
|
|
45
|
+
# the wide keyword list the tiers used to each redeclare.
|
|
49
46
|
def self.build(receiver:, method_name:, args:, # rubocop:disable Metrics/ParameterLists
|
|
50
47
|
block_type: nil, environment: nil, call_node: nil,
|
|
51
48
|
scope: nil, self_type_override: nil, public_only: false)
|
|
@@ -40,7 +40,7 @@ module Rigor
|
|
|
40
40
|
# receiver/argument combination.
|
|
41
41
|
#
|
|
42
42
|
# Anything else returns `nil`, signalling "no rule matched" so the
|
|
43
|
-
# caller (`
|
|
43
|
+
# caller (`MethodDispatcher`) falls back to `Dynamic[Top]` and records a
|
|
44
44
|
# fail-soft event. Slice 4 (RBS-backed) layers another dispatch tier
|
|
45
45
|
# behind this rule book, but the constant-folding semantics defined
|
|
46
46
|
# here MUST NOT regress: any value reachable by literal arithmetic at
|
|
@@ -51,7 +51,7 @@ module Rigor
|
|
|
51
51
|
NUMERIC_BINARY = Set[
|
|
52
52
|
:+, :-, :*, :/, :%, :**, :&, :|, :^, :<<, :>>,
|
|
53
53
|
:<, :<=, :>, :>=, :==, :!=, :<=>,
|
|
54
|
-
:gcd, :lcm, :fdiv
|
|
54
|
+
:gcd, :lcm, :fdiv, :quo, :ceildiv, :[]
|
|
55
55
|
].freeze
|
|
56
56
|
STRING_BINARY = Set[
|
|
57
57
|
:+, :*, :==, :!=, :<, :<=, :>, :>=, :<=>,
|
|
@@ -60,12 +60,31 @@ module Rigor
|
|
|
60
60
|
:match?, :index, :rindex, :center, :ljust, :rjust,
|
|
61
61
|
# 1-arg pure transforms/queries whose output never exceeds the
|
|
62
62
|
# input: `delete`/`squeeze` shrink the string, `count` → Integer.
|
|
63
|
-
:delete, :count, :squeeze
|
|
63
|
+
:delete, :count, :squeeze,
|
|
64
|
+
# ASCII / Unicode-case-fold comparison — deterministic, no
|
|
65
|
+
# locale read: `casecmp` → -1/0/1, `casecmp?` → bool/nil.
|
|
66
|
+
:casecmp, :casecmp?
|
|
64
67
|
].freeze
|
|
65
68
|
SYMBOL_BINARY = Set[:==, :!=, :<=>, :<, :<=, :>, :>=].freeze
|
|
66
69
|
BOOL_BINARY = Set[:&, :|, :^, :==, :!=, :===].freeze
|
|
67
70
|
NIL_BINARY = Set[:==, :!=].freeze
|
|
68
|
-
|
|
71
|
+
# Rational arithmetic / ordering are exact and pure. Division
|
|
72
|
+
# (`/`) and `**` may return a `Float`/`Complex` for some operands,
|
|
73
|
+
# all of which are foldable `Constant` value classes. `==` / `!=`
|
|
74
|
+
# are deliberately EXCLUDED: `Rational#==` (`nurat_eqeq_p`) routes
|
|
75
|
+
# through `rb_funcall(:==)` on the operands — user-redefinable —
|
|
76
|
+
# so the catalog classifies it `:dispatch` and the equality stays
|
|
77
|
+
# the RBS `bool`. (The set would otherwise bypass that gate.)
|
|
78
|
+
RATIONAL_BINARY = Set[
|
|
79
|
+
:+, :-, :*, :/, :**, :<=>, :<, :<=, :>, :>=,
|
|
80
|
+
:div, :modulo, :%, :remainder, :fdiv, :quo
|
|
81
|
+
].freeze
|
|
82
|
+
# Complex arithmetic. `ops_for` gains a `Complex` branch so these
|
|
83
|
+
# reach the binary fold path (Complex was previously unary-only).
|
|
84
|
+
# `/` and `**` stay foldable (Complex result). `==` / `!=` are
|
|
85
|
+
# excluded for the same reason as Rational (`nucomp_eqeq_p`
|
|
86
|
+
# delegates to operand `==`); ordering is undefined for Complex.
|
|
87
|
+
COMPLEX_BINARY = Set[:+, :-, :*, :/, :**].freeze
|
|
69
88
|
|
|
70
89
|
# v0.0.3 C — pure unary catalogue. Each method must:
|
|
71
90
|
# - take zero arguments,
|
|
@@ -83,20 +102,30 @@ module Rigor
|
|
|
83
102
|
# user-defined `def is_odd(n) = n.odd?` so
|
|
84
103
|
# `Parity.new.is_odd(3)` types as `Constant[true]`
|
|
85
104
|
# rather than the RBS-widened `bool`.
|
|
105
|
+
# NOTE: `:hash` is deliberately NOT in any of these sets.
|
|
106
|
+
# `Object#hash` (and the `String`/`Symbol`/`Integer`/`Float`
|
|
107
|
+
# overrides) is salted with a per-process SipHash seed, so
|
|
108
|
+
# `"abc".hash` returns a different Integer in every Ruby
|
|
109
|
+
# process. Folding it to a `Constant` would bake one process's
|
|
110
|
+
# value into the type (and the on-disk cache), making the
|
|
111
|
+
# result non-deterministic across runs — a violation of the
|
|
112
|
+
# purity contract this catalogue rests on. A literal's `.hash`
|
|
113
|
+
# therefore stays the RBS-widened `Integer`. The deterministic
|
|
114
|
+
# siblings `:inspect` / `:to_s` remain folded.
|
|
86
115
|
INTEGER_UNARY = Set[
|
|
87
116
|
:odd?, :even?, :zero?, :positive?, :negative?,
|
|
88
117
|
:succ, :pred, :next, :abs, :magnitude,
|
|
89
118
|
:bit_length, :to_s, :to_i, :to_int, :to_f,
|
|
90
119
|
:floor, :ceil, :round, :truncate, :chr,
|
|
91
|
-
:inspect,
|
|
120
|
+
:inspect, :-@, :+@, :~, :to_r, :to_c
|
|
92
121
|
].freeze
|
|
93
122
|
FLOAT_UNARY = Set[
|
|
94
123
|
:zero?, :positive?, :negative?,
|
|
95
124
|
:nan?, :finite?, :infinite?,
|
|
96
125
|
:abs, :magnitude, :floor, :ceil, :round, :truncate,
|
|
97
126
|
:next_float, :prev_float,
|
|
98
|
-
:to_s, :to_i, :to_int, :to_f,
|
|
99
|
-
:inspect,
|
|
127
|
+
:to_s, :to_i, :to_int, :to_f, :to_r, :rationalize,
|
|
128
|
+
:inspect, :-@, :+@
|
|
100
129
|
].freeze
|
|
101
130
|
STRING_UNARY = Set[
|
|
102
131
|
:upcase, :downcase, :capitalize, :swapcase,
|
|
@@ -104,20 +133,29 @@ module Rigor
|
|
|
104
133
|
:empty?, :strip, :lstrip, :rstrip, :chomp, :chop, :squeeze,
|
|
105
134
|
:to_s, :to_str, :to_sym, :intern,
|
|
106
135
|
:to_i, :to_f, :ord, :chr, :hex, :oct, :succ, :next,
|
|
107
|
-
:
|
|
136
|
+
:sum, :inspect
|
|
108
137
|
].freeze
|
|
109
138
|
SYMBOL_UNARY = Set[
|
|
110
139
|
:to_s, :to_sym, :to_proc, :length, :size,
|
|
111
140
|
:empty?, :upcase, :downcase, :capitalize,
|
|
112
|
-
:swapcase, :
|
|
141
|
+
:swapcase, :succ, :next, :inspect
|
|
113
142
|
].freeze
|
|
114
|
-
BOOL_UNARY = Set[:!, :to_s, :inspect,
|
|
115
|
-
NIL_UNARY = Set[:nil?, :!, :to_s, :to_a, :to_h, :inspect
|
|
143
|
+
BOOL_UNARY = Set[:!, :to_s, :inspect, :&, :|, :^].freeze
|
|
144
|
+
NIL_UNARY = Set[:nil?, :!, :to_s, :to_a, :to_h, :inspect].freeze
|
|
116
145
|
RATIONAL_UNARY = Set[
|
|
117
146
|
:zero?, :integer?, :real, :abs2,
|
|
118
|
-
:conj, :conjugate, :nonzero
|
|
147
|
+
:conj, :conjugate, :nonzero?,
|
|
148
|
+
:numerator, :denominator, :abs, :magnitude,
|
|
149
|
+
:to_f, :to_i, :to_int, :to_r, :rationalize,
|
|
150
|
+
:floor, :ceil, :round, :truncate,
|
|
151
|
+
:-@, :+@
|
|
152
|
+
].freeze
|
|
153
|
+
COMPLEX_UNARY = Set[
|
|
154
|
+
:zero?, :nonzero?,
|
|
155
|
+
:abs, :magnitude, :abs2, :arg, :angle, :phase,
|
|
156
|
+
:conjugate, :conj, :real, :imaginary, :imag,
|
|
157
|
+
:to_c, :-@, :+@
|
|
119
158
|
].freeze
|
|
120
|
-
COMPLEX_UNARY = Set[:zero?, :nonzero?].freeze
|
|
121
159
|
|
|
122
160
|
STRING_FOLD_BYTE_LIMIT = 4096
|
|
123
161
|
|
|
@@ -226,13 +264,54 @@ module Rigor
|
|
|
226
264
|
case type
|
|
227
265
|
when Type::Constant then [type.value]
|
|
228
266
|
when Type::Union
|
|
229
|
-
return
|
|
230
|
-
|
|
231
|
-
|
|
267
|
+
return type.members.map(&:value) if type.members.all?(Type::Constant)
|
|
268
|
+
|
|
269
|
+
# A union that mixes `Constant<Integer>` and `IntegerRange`
|
|
270
|
+
# members (e.g. an accumulator's running fixpoint assumption
|
|
271
|
+
# `1 | int<1, 6>`) folds as the bounding interval. The
|
|
272
|
+
# range-arithmetic path (`try_fold_binary_range`) then keeps
|
|
273
|
+
# the result an `IntegerRange` instead of bailing to Dynamic.
|
|
274
|
+
union_integer_bounds(type)
|
|
232
275
|
when Type::IntegerRange then type
|
|
233
276
|
end
|
|
234
277
|
end
|
|
235
278
|
|
|
279
|
+
# Returns the bounding `IntegerRange` over a union whose members
|
|
280
|
+
# are each an Integer `Constant` or an `IntegerRange`; `nil`
|
|
281
|
+
# otherwise (a Float constant or any non-numeric member declines,
|
|
282
|
+
# so precision is never silently lost).
|
|
283
|
+
def union_integer_bounds(union)
|
|
284
|
+
lowers = []
|
|
285
|
+
uppers = []
|
|
286
|
+
union.members.each do |member|
|
|
287
|
+
case member
|
|
288
|
+
when Type::Constant
|
|
289
|
+
return nil unless member.value.is_a?(Integer)
|
|
290
|
+
|
|
291
|
+
lowers << member.value
|
|
292
|
+
uppers << member.value
|
|
293
|
+
when Type::IntegerRange
|
|
294
|
+
lowers << member.lower
|
|
295
|
+
uppers << member.upper
|
|
296
|
+
else
|
|
297
|
+
return nil
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
# `IntegerRange#lower`/`#upper` surface an unbounded edge as
|
|
301
|
+
# `±Float::INFINITY`; `integer_range` wants the `±∞` *sentinel*,
|
|
302
|
+
# so map the extremum back.
|
|
303
|
+
Type::Combinator.integer_range(infinity_to_sentinel(lowers.min),
|
|
304
|
+
infinity_to_sentinel(uppers.max))
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def infinity_to_sentinel(bound)
|
|
308
|
+
case bound
|
|
309
|
+
when -Float::INFINITY then Type::IntegerRange::NEG_INFINITY
|
|
310
|
+
when Float::INFINITY then Type::IntegerRange::POS_INFINITY
|
|
311
|
+
else bound
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
236
315
|
def try_fold_unary(set, method_name)
|
|
237
316
|
case set
|
|
238
317
|
when Array then try_fold_unary_set(set, method_name)
|
|
@@ -345,9 +424,15 @@ module Rigor
|
|
|
345
424
|
# Only fires on a single-receiver Range with finite integer
|
|
346
425
|
# endpoints; mixed unions fall through so the existing
|
|
347
426
|
# union-of-Constants path keeps the rest of the arms.
|
|
348
|
-
RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length, :entries, :minmax
|
|
427
|
+
RANGE_FOLD_METHODS = Set[:to_a, :first, :last, :min, :max, :count, :size, :length, :entries, :minmax,
|
|
428
|
+
:sum].freeze
|
|
429
|
+
# 1-arg head/tail projections on a `Constant<Range>`. `first(n)` /
|
|
430
|
+
# `take(n)` return the first `n` elements, `last(n)` the final `n` —
|
|
431
|
+
# each lifts to a per-position `Tuple[Constant[Integer]…]`. The
|
|
432
|
+
# no-arg `first` / `last` stay on the unary path (single Integer).
|
|
433
|
+
RANGE_FOLD_BINARY_METHODS = Set[:first, :last, :take].freeze
|
|
349
434
|
RANGE_TO_A_LIMIT = 16
|
|
350
|
-
private_constant :RANGE_FOLD_METHODS, :RANGE_TO_A_LIMIT
|
|
435
|
+
private_constant :RANGE_FOLD_METHODS, :RANGE_FOLD_BINARY_METHODS, :RANGE_TO_A_LIMIT
|
|
351
436
|
|
|
352
437
|
def try_fold_range_constant_unary(receiver_values, method_name)
|
|
353
438
|
return nil unless RANGE_FOLD_METHODS.include?(method_name)
|
|
@@ -367,6 +452,11 @@ module Rigor
|
|
|
367
452
|
when :last, :max then range_endpoint_constant(range, :last)
|
|
368
453
|
when :count, :size, :length then Type::Combinator.constant_of(range.to_a.size)
|
|
369
454
|
when :minmax then range_minmax_tuple(range)
|
|
455
|
+
# `range.sum` is closed-form (Gauss) for an integer range, so a
|
|
456
|
+
# huge range still costs O(1) and yields a single Integer — no
|
|
457
|
+
# materialisation, no cap needed. Endless ranges are already
|
|
458
|
+
# excluded by the Integer-endpoint guard in the caller.
|
|
459
|
+
when :sum then Type::Combinator.constant_of(range.sum)
|
|
370
460
|
end
|
|
371
461
|
end
|
|
372
462
|
|
|
@@ -400,10 +490,46 @@ module Rigor
|
|
|
400
490
|
)
|
|
401
491
|
end
|
|
402
492
|
|
|
493
|
+
# `(1..10).first(3)` / `.take(3)` / `.last(3)` — the 1-arg head /
|
|
494
|
+
# tail forms. `first`/`last` already fold no-arg through the unary
|
|
495
|
+
# path; this is the n-arg sibling, mirroring the Tuple carrier's
|
|
496
|
+
# `first(n)`/`take(n)` handlers. Lifts to `Tuple[Constant…]`.
|
|
497
|
+
def try_fold_range_constant_binary(receiver_values, method_name, arg_values)
|
|
498
|
+
return nil unless RANGE_FOLD_BINARY_METHODS.include?(method_name)
|
|
499
|
+
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
500
|
+
|
|
501
|
+
range = receiver_values.first
|
|
502
|
+
return nil unless range.is_a?(Range)
|
|
503
|
+
return nil unless range.begin.is_a?(Integer) && range.end.is_a?(Integer)
|
|
504
|
+
|
|
505
|
+
range_take_tuple(range, method_name, arg_values.first)
|
|
506
|
+
rescue StandardError
|
|
507
|
+
nil
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def range_take_tuple(range, method_name, count)
|
|
511
|
+
return nil unless count.is_a?(Integer) && !count.negative?
|
|
512
|
+
# `first(n)`/`last(n)`/`take(n)` materialise at most `min(n, size)`
|
|
513
|
+
# elements; cap that count so a huge `n` (or range) never blows up
|
|
514
|
+
# the Constant. `Range#size` is O(1) for integer endpoints.
|
|
515
|
+
return nil if [count, range.size].min > RANGE_TO_A_LIMIT
|
|
516
|
+
|
|
517
|
+
values = method_name == :last ? range.last(count) : range.first(count)
|
|
518
|
+
return Type::Combinator.tuple_of if values.empty?
|
|
519
|
+
|
|
520
|
+
Type::Combinator.tuple_of(*values.map { |v| Type::Combinator.constant_of(v) })
|
|
521
|
+
end
|
|
522
|
+
|
|
403
523
|
def try_fold_binary_set(receiver_values, method_name, arg_values)
|
|
524
|
+
range_lift = try_fold_range_constant_binary(receiver_values, method_name, arg_values)
|
|
525
|
+
return range_lift if range_lift
|
|
526
|
+
|
|
404
527
|
string_lift = try_fold_string_array_binary(receiver_values, method_name, arg_values)
|
|
405
528
|
return string_lift if string_lift
|
|
406
529
|
|
|
530
|
+
integer_lift = try_fold_integer_array_binary(receiver_values, method_name, arg_values)
|
|
531
|
+
return integer_lift if integer_lift
|
|
532
|
+
|
|
407
533
|
pathname_lift = try_fold_pathname_binary(receiver_values, method_name, arg_values)
|
|
408
534
|
return pathname_lift if pathname_lift
|
|
409
535
|
|
|
@@ -415,15 +541,20 @@ module Rigor
|
|
|
415
541
|
end
|
|
416
542
|
build_constant_type(results, source: receiver_values + arg_values)
|
|
417
543
|
end
|
|
418
|
-
# v0.0.7 — `Constant<String>#chars` / `bytes` / `
|
|
419
|
-
# `split` (no-arg) return a Ruby Array of foldable
|
|
544
|
+
# v0.0.7 — `Constant<String>#chars` / `bytes` / `codepoints` /
|
|
545
|
+
# `lines` / `split` (no-arg) return a Ruby Array of foldable
|
|
420
546
|
# scalars; `foldable_constant_value?` rejects Array
|
|
421
547
|
# results, so the standard unary path declines. Lift the
|
|
422
548
|
# Array to a per-position `Tuple[Constant…]` directly,
|
|
423
549
|
# capped at `STRING_ARRAY_LIFT_LIMIT` to keep the result
|
|
424
|
-
# bounded for long strings.
|
|
425
|
-
|
|
426
|
-
|
|
550
|
+
# bounded for long strings. (`codepoints` yields per-character
|
|
551
|
+
# Integer codepoints, the sibling of the byte-valued `bytes`.)
|
|
552
|
+
STRING_ARRAY_UNARY_METHODS = Set[:chars, :bytes, :codepoints, :lines, :split].freeze
|
|
553
|
+
# `partition` / `rpartition` always return a fixed 3-element
|
|
554
|
+
# `[head, separator, tail]` Array whose members are substrings of
|
|
555
|
+
# the receiver (bounded by the input), so they lift to a precise
|
|
556
|
+
# 3-slot `Tuple[Constant…]`.
|
|
557
|
+
STRING_ARRAY_BINARY_METHODS = Set[:split, :scan, :partition, :rpartition].freeze
|
|
427
558
|
STRING_ARRAY_LIFT_LIMIT = 32
|
|
428
559
|
private_constant :STRING_ARRAY_UNARY_METHODS,
|
|
429
560
|
:STRING_ARRAY_BINARY_METHODS,
|
|
@@ -453,6 +584,14 @@ module Rigor
|
|
|
453
584
|
INTEGER_ARRAY_UNARY_METHODS = Set[:digits].freeze
|
|
454
585
|
private_constant :INTEGER_ARRAY_UNARY_METHODS
|
|
455
586
|
|
|
587
|
+
# 1-arg Integer methods that return an Array of foldable
|
|
588
|
+
# Integers: `digits(base)` (base-n place values; raises on a
|
|
589
|
+
# negative receiver or base < 2 → declines) and `gcdlcm(other)`
|
|
590
|
+
# (the fixed `[gcd, lcm]` pair). Both are pure arithmetic; the
|
|
591
|
+
# result lifts to a `Tuple[Constant[Integer]…]`.
|
|
592
|
+
INTEGER_ARRAY_BINARY_METHODS = Set[:digits, :gcdlcm].freeze
|
|
593
|
+
private_constant :INTEGER_ARRAY_BINARY_METHODS
|
|
594
|
+
|
|
456
595
|
# v0.0.7 — `Constant<Pathname>` delegates to a curated set
|
|
457
596
|
# of pure path-manipulation methods. Pathname is immutable
|
|
458
597
|
# in Ruby (per its docstring) and the catalog classifies
|
|
@@ -572,6 +711,25 @@ module Rigor
|
|
|
572
711
|
nil
|
|
573
712
|
end
|
|
574
713
|
|
|
714
|
+
# `Constant<Integer>#digits(base)` / `#gcdlcm(other)` — the
|
|
715
|
+
# 1-arg Array-returning Integer methods. `digits(base)` declines
|
|
716
|
+
# on a negative receiver (the unary path's guard); other domain
|
|
717
|
+
# errors (base < 2) raise and are rescued. `gcdlcm` is total over
|
|
718
|
+
# Integer args.
|
|
719
|
+
def try_fold_integer_array_binary(receiver_values, method_name, arg_values)
|
|
720
|
+
return nil unless INTEGER_ARRAY_BINARY_METHODS.include?(method_name)
|
|
721
|
+
return nil unless receiver_values.size == 1 && arg_values.size == 1
|
|
722
|
+
|
|
723
|
+
receiver = receiver_values.first
|
|
724
|
+
arg = arg_values.first
|
|
725
|
+
return nil unless receiver.is_a?(Integer) && arg.is_a?(Integer)
|
|
726
|
+
return nil if method_name == :digits && receiver.negative?
|
|
727
|
+
|
|
728
|
+
lift_array_result(receiver.public_send(method_name, arg))
|
|
729
|
+
rescue StandardError
|
|
730
|
+
nil
|
|
731
|
+
end
|
|
732
|
+
|
|
575
733
|
# `Constant<Complex>#rect` / `#rectangular` — lifts `[real, imaginary]`
|
|
576
734
|
# to `Tuple[Constant[re], Constant[im]]`. Both components are always
|
|
577
735
|
# numeric (Integer or Float for literal complexes), so they satisfy
|
|
@@ -1265,17 +1423,16 @@ module Rigor
|
|
|
1265
1423
|
end
|
|
1266
1424
|
end
|
|
1267
1425
|
|
|
1268
|
-
# `String#reverse` / `#swapcase` etc. produce a
|
|
1269
|
-
#
|
|
1270
|
-
#
|
|
1271
|
-
#
|
|
1272
|
-
#
|
|
1273
|
-
#
|
|
1274
|
-
#
|
|
1275
|
-
#
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
false
|
|
1426
|
+
# `String#reverse` / `#swapcase` / `#succ` etc. produce a string
|
|
1427
|
+
# at least as large as the receiver. The binary `:+` / `:*` paths
|
|
1428
|
+
# have their own `string_blow_up?` output guard; this is the unary
|
|
1429
|
+
# analogue — decline to fold a unary String op whose receiver is
|
|
1430
|
+
# already at or beyond `STRING_FOLD_BYTE_LIMIT`, since the folded
|
|
1431
|
+
# output would be just as large and constant-materialising it buys
|
|
1432
|
+
# no precision worth the bytes. Non-String receivers never blow up
|
|
1433
|
+
# through a unary op, so they pass.
|
|
1434
|
+
def string_unary_blow_up?(receiver_value, _method_name)
|
|
1435
|
+
receiver_value.is_a?(String) && receiver_value.bytesize >= STRING_FOLD_BYTE_LIMIT
|
|
1279
1436
|
end
|
|
1280
1437
|
|
|
1281
1438
|
# Scalar / String / Symbol values fold; everything
|
|
@@ -1294,7 +1451,24 @@ module Rigor
|
|
|
1294
1451
|
private_constant :FOLDABLE_CONSTANT_CLASSES
|
|
1295
1452
|
|
|
1296
1453
|
def foldable_constant_value?(value)
|
|
1297
|
-
FOLDABLE_CONSTANT_CLASSES.any? { |klass| value.is_a?(klass) }
|
|
1454
|
+
return false unless FOLDABLE_CONSTANT_CLASSES.any? { |klass| value.is_a?(klass) }
|
|
1455
|
+
|
|
1456
|
+
# A NaN result (`0.0 / 0.0`, `Float::NAN`-propagating arithmetic,
|
|
1457
|
+
# or a NaN-bearing Complex) is non-reflexive under `==`, so a
|
|
1458
|
+
# `Constant[NaN]` would break the `==` / `eql?` / `hash` contract
|
|
1459
|
+
# `build_constant_type` relies on for union dedup. Decline the
|
|
1460
|
+
# fold and let the RBS tier answer with the widened class.
|
|
1461
|
+
return false if value.is_a?(Float) && value.nan?
|
|
1462
|
+
return false if value.is_a?(Complex) && complex_nan?(value)
|
|
1463
|
+
|
|
1464
|
+
true
|
|
1465
|
+
end
|
|
1466
|
+
|
|
1467
|
+
# True when either component of a Complex is NaN.
|
|
1468
|
+
def complex_nan?(value)
|
|
1469
|
+
real = value.real
|
|
1470
|
+
imag = value.imaginary
|
|
1471
|
+
(real.is_a?(Float) && real.nan?) || (imag.is_a?(Float) && imag.nan?)
|
|
1298
1472
|
end
|
|
1299
1473
|
|
|
1300
1474
|
def safe?(receiver_value, method_name, arg_value)
|
|
@@ -1315,6 +1489,7 @@ module Rigor
|
|
|
1315
1489
|
when true, false then BOOL_BINARY
|
|
1316
1490
|
when nil then NIL_BINARY
|
|
1317
1491
|
when Rational then RATIONAL_BINARY
|
|
1492
|
+
when Complex then COMPLEX_BINARY
|
|
1318
1493
|
else Set.new
|
|
1319
1494
|
end
|
|
1320
1495
|
end
|