rigortype 0.4.7 → 0.4.8
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 +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- data/lib/rigor/inference/memo_audit.rb +0 -55
|
@@ -57,12 +57,13 @@ module Rigor
|
|
|
57
57
|
last: :tuple_last,
|
|
58
58
|
size: :tuple_size,
|
|
59
59
|
length: :tuple_size,
|
|
60
|
-
count: :
|
|
60
|
+
count: :tuple_count,
|
|
61
61
|
empty?: :tuple_empty?,
|
|
62
62
|
any?: :tuple_any?,
|
|
63
63
|
all?: :tuple_all?,
|
|
64
64
|
none?: :tuple_none?,
|
|
65
65
|
include?: :tuple_include?,
|
|
66
|
+
member?: :tuple_include?,
|
|
66
67
|
sum: :tuple_sum,
|
|
67
68
|
min: :tuple_min,
|
|
68
69
|
max: :tuple_max,
|
|
@@ -70,13 +71,23 @@ module Rigor
|
|
|
70
71
|
sort: :tuple_sort,
|
|
71
72
|
reverse: :tuple_reverse,
|
|
72
73
|
to_a: :tuple_to_a,
|
|
74
|
+
entries: :tuple_to_a,
|
|
75
|
+
to_ary: :tuple_to_a,
|
|
73
76
|
to_h: :tuple_to_h,
|
|
77
|
+
to_set: :tuple_to_set,
|
|
74
78
|
zip: :tuple_zip,
|
|
75
79
|
:[] => :tuple_index,
|
|
76
80
|
slice: :tuple_index,
|
|
77
81
|
fetch: :tuple_index,
|
|
78
82
|
dig: :tuple_dig,
|
|
79
83
|
values_at: :tuple_values_at,
|
|
84
|
+
fetch_values: :tuple_fetch_values,
|
|
85
|
+
assoc: :tuple_assoc,
|
|
86
|
+
rassoc: :tuple_rassoc,
|
|
87
|
+
tally: :tuple_tally,
|
|
88
|
+
transpose: :tuple_transpose,
|
|
89
|
+
product: :tuple_product,
|
|
90
|
+
pack: :tuple_pack,
|
|
80
91
|
# `concat` / `<<` are deliberately ABSENT from this table: both mutate the receiver in place
|
|
81
92
|
# (`ARRAY_MUTATORS` in `MutationWidening`), and a fold tier has no way to write the folded
|
|
82
93
|
# result back into the caller's binding — it can only return a value for the call expression.
|
|
@@ -216,6 +227,7 @@ module Rigor
|
|
|
216
227
|
# `CONST = [...].freeze; CONST.any? { … }`; fixing it at the fold (not the rule) unblocks that
|
|
217
228
|
# preservation.
|
|
218
229
|
return nil unless context.block_type.nil?
|
|
230
|
+
return constant_set_first(context) if method_name == :first && receiver.is_a?(Type::Constant)
|
|
219
231
|
|
|
220
232
|
handler = RECEIVER_HANDLERS[receiver.class]
|
|
221
233
|
return nil unless handler
|
|
@@ -335,6 +347,30 @@ module Rigor
|
|
|
335
347
|
Type::Combinator.nominal_of("Set", type_args: [Type::Combinator.untyped])
|
|
336
348
|
end
|
|
337
349
|
|
|
350
|
+
# Issue #2657 — `Set#first` (no argument) on a constant-folded Set held in a LOCAL that provably nobody else
|
|
351
|
+
# can reach. RBS alone answers `E?` over a constant receiver whose `E` is unbound, so `Set["r"].first` read
|
|
352
|
+
# `Dynamic[top]?`. The answer is the literal's first member (insertion order), `nil` for the empty set.
|
|
353
|
+
#
|
|
354
|
+
# What is proved: the receiver is a bare local read whose binding carries the `:synthesised` construction
|
|
355
|
+
# fact, which {Inference::ConstructionEscape} withholds from a local that is referenced inside any block or
|
|
356
|
+
# lambda, passed as an argument, held in a container or an alias, receives a call outside its reader /
|
|
357
|
+
# iterator / modelled-mutator lists (`method`, `instance_eval`, `send`, a project-defined name), or lives in a
|
|
358
|
+
# body that calls `binding` / `eval`; and the modelled mutators (`clear`, `delete`, `<<`, ...) widen the
|
|
359
|
+
# carrier off `Constant[Set]` at the call itself.
|
|
360
|
+
# What is NOT covered, and keeps the RBS answer: a receiver that is not a local (an ivar, a global, a
|
|
361
|
+
# constant, a call result, a literal), and whatever the escape scan itself does not see.
|
|
362
|
+
def constant_set_first(context)
|
|
363
|
+
constant = context.receiver
|
|
364
|
+
return nil unless context.args.nil? || context.args.empty?
|
|
365
|
+
return nil unless Inference::SetMutation.constant?(constant)
|
|
366
|
+
|
|
367
|
+
node = context.call_node&.receiver
|
|
368
|
+
return nil unless node.is_a?(Prism::LocalVariableReadNode)
|
|
369
|
+
return nil unless context.scope&.construction_fact?(node.name)
|
|
370
|
+
|
|
371
|
+
Type::Combinator.constant_of(constant.value.first)
|
|
372
|
+
end
|
|
373
|
+
|
|
338
374
|
# `Integer#to_s` / `Integer#inspect` (#993) on a bare `Nominal[Integer]` receiver, ahead of the
|
|
339
375
|
# existing `:*` binary fold. `#inspect` takes no argument and matches `#to_s()` byte for byte, so
|
|
340
376
|
# it routes through the same no-arg branch.
|
|
@@ -843,6 +879,25 @@ module Rigor
|
|
|
843
879
|
Type::Combinator.constant_of(tuple.elements.size)
|
|
844
880
|
end
|
|
845
881
|
|
|
882
|
+
# `tuple.count` / `tuple.count(needle)` — 0-arg mirrors `#size`; 1-arg counts elements equal to
|
|
883
|
+
# `needle` (`==`) when the needle and every element are constant values or shapes (or folds to `0`
|
|
884
|
+
# on `[]`).
|
|
885
|
+
def tuple_count(tuple, _method_name, args)
|
|
886
|
+
return Type::Combinator.constant_of(tuple.elements.size) if args.empty?
|
|
887
|
+
return nil unless args.size == 1
|
|
888
|
+
|
|
889
|
+
needle = unwrap_constant_type(args.first)
|
|
890
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
891
|
+
return Type::Combinator.constant_of(0) if tuple.elements.empty?
|
|
892
|
+
|
|
893
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
894
|
+
return nil if values.nil?
|
|
895
|
+
|
|
896
|
+
Type::Combinator.constant_of(values.count { |v| v == needle })
|
|
897
|
+
rescue StandardError
|
|
898
|
+
nil
|
|
899
|
+
end
|
|
900
|
+
|
|
846
901
|
# `tuple.empty?` — folds to a precise bool from the tuple's known arity.
|
|
847
902
|
# rubocop:disable Style/ReturnNilInPredicateMethodDefinition
|
|
848
903
|
def tuple_empty?(tuple, _method_name, args)
|
|
@@ -884,27 +939,29 @@ module Rigor
|
|
|
884
939
|
Type::Combinator.constant_of(decision)
|
|
885
940
|
end
|
|
886
941
|
|
|
887
|
-
# `tuple.include?(needle)` — folds to a precise bool when the needle
|
|
888
|
-
#
|
|
889
|
-
#
|
|
890
|
-
#
|
|
942
|
+
# `tuple.include?(needle)` — folds to a precise bool when the needle and elements are constant
|
|
943
|
+
# scalars or constant nested Tuple / HashShape values. If any element matches the needle's value
|
|
944
|
+
# the answer is `Constant[true]`; if every element is constant and structurally distinct from the
|
|
945
|
+
# needle the answer is `Constant[false]`.
|
|
891
946
|
def tuple_include?(tuple, _method_name, args)
|
|
892
947
|
return nil unless args.size == 1
|
|
893
948
|
|
|
894
|
-
needle = args.first
|
|
895
|
-
return nil
|
|
949
|
+
needle = unwrap_constant_type(args.first)
|
|
950
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
896
951
|
return Type::Combinator.constant_of(false) if tuple.elements.empty?
|
|
897
952
|
|
|
898
|
-
return Type::Combinator.constant_of(true) if any_element_matches?(tuple.elements, needle
|
|
899
|
-
return Type::Combinator.constant_of(false)
|
|
953
|
+
return Type::Combinator.constant_of(true) if any_element_matches?(tuple.elements, needle)
|
|
954
|
+
return Type::Combinator.constant_of(false) unless unwrap_constant_elements(tuple.elements).nil?
|
|
900
955
|
|
|
901
956
|
nil
|
|
902
957
|
end
|
|
903
958
|
# rubocop:enable Style/ReturnNilInPredicateMethodDefinition
|
|
904
959
|
|
|
905
|
-
# `tuple.sum` — when every element is a numeric Constant
|
|
906
|
-
#
|
|
960
|
+
# `tuple.sum` / `tuple.sum(init)` — when every element is a numeric Constant (and optional `init`
|
|
961
|
+
# is a numeric Constant), fold to `Constant[sum]`; when every element and `init` are String
|
|
962
|
+
# Constants, fold to `Constant[sum]` within `TUPLE_JOIN_BYTE_LIMIT`.
|
|
907
963
|
def tuple_sum(tuple, _method_name, args)
|
|
964
|
+
return tuple_sum_with_init(tuple, args.first) if args.size == 1
|
|
908
965
|
return nil unless args.empty?
|
|
909
966
|
return Type::Combinator.constant_of(0) if tuple.elements.empty?
|
|
910
967
|
|
|
@@ -912,16 +969,45 @@ module Rigor
|
|
|
912
969
|
return nil if values.nil?
|
|
913
970
|
|
|
914
971
|
Type::Combinator.constant_of(values.sum)
|
|
972
|
+
rescue StandardError
|
|
973
|
+
nil
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
def tuple_sum_with_init(tuple, init)
|
|
977
|
+
return nil unless init.is_a?(Type::Constant)
|
|
978
|
+
return tuple_sum_with_string_init(tuple, init.value) if init.value.is_a?(String)
|
|
979
|
+
return nil unless init.value.is_a?(Numeric)
|
|
980
|
+
return init if tuple.elements.empty?
|
|
981
|
+
|
|
982
|
+
values = constant_numeric_values(tuple.elements)
|
|
983
|
+
return nil if values.nil?
|
|
984
|
+
|
|
985
|
+
Type::Combinator.constant_of(values.sum(init.value))
|
|
986
|
+
rescue StandardError
|
|
987
|
+
nil
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
def tuple_sum_with_string_init(tuple, init_str)
|
|
991
|
+
values = constant_values(tuple.elements)
|
|
992
|
+
return nil if values.nil? || !values.all?(String)
|
|
993
|
+
return nil if values.sum(&:bytesize) + init_str.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
994
|
+
|
|
995
|
+
result = values.sum(init_str)
|
|
996
|
+
return nil if result.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
997
|
+
|
|
998
|
+
Type::Combinator.constant_of(result)
|
|
999
|
+
rescue StandardError
|
|
1000
|
+
nil
|
|
915
1001
|
end
|
|
916
1002
|
|
|
917
1003
|
# `tuple.join(sep = "")` — fold to the joined `Constant[String]` when every element is a
|
|
918
|
-
#
|
|
919
|
-
#
|
|
1004
|
+
# constant scalar or nested constant Tuple and the separator is absent or a `Constant[String]`.
|
|
1005
|
+
# Capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
920
1006
|
def tuple_join(tuple, _method_name, args)
|
|
921
1007
|
sep = tuple_join_separator(args)
|
|
922
1008
|
return nil if sep.nil?
|
|
923
1009
|
|
|
924
|
-
values =
|
|
1010
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
925
1011
|
return nil if values.nil?
|
|
926
1012
|
|
|
927
1013
|
result = values.join(sep)
|
|
@@ -945,14 +1031,13 @@ module Rigor
|
|
|
945
1031
|
end
|
|
946
1032
|
|
|
947
1033
|
# `tuple.inspect` / `tuple.to_s` (`Array#to_s` is an exact alias of `Array#inspect`) — folds when
|
|
948
|
-
# every element is a
|
|
949
|
-
# so the analyzer's Ruby (4.0.x's `Array#inspect` format)
|
|
950
|
-
# Capped at `TUPLE_JOIN_BYTE_LIMIT` like `#join
|
|
951
|
-
# similarly unbounded.
|
|
1034
|
+
# every element is a constant scalar or nested constant Tuple / HashShape, by reconstructing the
|
|
1035
|
+
# Ruby Array and calling the REAL `inspect` so the analyzer's Ruby (4.0.x's `Array#inspect` format)
|
|
1036
|
+
# is matched rather than re-derived. Capped at `TUPLE_JOIN_BYTE_LIMIT` like `#join`.
|
|
952
1037
|
def tuple_inspect(tuple, _method_name, args)
|
|
953
1038
|
return nil unless args.empty?
|
|
954
1039
|
|
|
955
|
-
values =
|
|
1040
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
956
1041
|
return nil if values.nil?
|
|
957
1042
|
|
|
958
1043
|
result = values.inspect
|
|
@@ -997,7 +1082,7 @@ module Rigor
|
|
|
997
1082
|
Type::Combinator.tuple_of(*(tuple.elements * count))
|
|
998
1083
|
end
|
|
999
1084
|
|
|
1000
|
-
# `tuple.min` / `tuple.max` — fold when every element is a
|
|
1085
|
+
# `tuple.min` / `tuple.max` — fold when every element is a constant whose values share a
|
|
1001
1086
|
# Ruby-comparable domain. Empty tuples fold to `Constant[nil]`. The 1-arg `min(n)` / `max(n)`
|
|
1002
1087
|
# form folds to a `Tuple` of the n edge-most values in Ruby's order (`min(n)` ascending, `max(n)`
|
|
1003
1088
|
# descending) — the n-arg sibling of `first(n)`.
|
|
@@ -1013,11 +1098,11 @@ module Rigor
|
|
|
1013
1098
|
return tuple_minmax_n(tuple, args.first, edge) unless args.empty?
|
|
1014
1099
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
1015
1100
|
|
|
1016
|
-
values =
|
|
1101
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1017
1102
|
return nil if values.nil?
|
|
1018
1103
|
|
|
1019
1104
|
result = values.public_send(edge)
|
|
1020
|
-
|
|
1105
|
+
lift_constant_value(result)
|
|
1021
1106
|
rescue StandardError
|
|
1022
1107
|
nil
|
|
1023
1108
|
end
|
|
@@ -1030,19 +1115,18 @@ module Rigor
|
|
|
1030
1115
|
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
1031
1116
|
return nil if arg.value.negative?
|
|
1032
1117
|
|
|
1033
|
-
values =
|
|
1118
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1034
1119
|
return nil if values.nil?
|
|
1035
1120
|
|
|
1036
1121
|
picked = values.public_send(edge, arg.value)
|
|
1037
|
-
Type::Combinator.tuple_of(*picked.map { |v|
|
|
1122
|
+
Type::Combinator.tuple_of(*picked.map { |v| lift_constant_value(v) })
|
|
1038
1123
|
rescue StandardError
|
|
1039
1124
|
nil
|
|
1040
1125
|
end
|
|
1041
1126
|
|
|
1042
|
-
# `tuple.minmax` — the `[min, max]` pair as a 2-slot `Tuple
|
|
1043
|
-
#
|
|
1044
|
-
#
|
|
1045
|
-
# mixed-class values decline.
|
|
1127
|
+
# `tuple.minmax` — the `[min, max]` pair as a 2-slot `Tuple`, mirroring the `Range#minmax` fold.
|
|
1128
|
+
# Every element must be constant and the values must Ruby-compare; an empty tuple folds to
|
|
1129
|
+
# `Tuple[nil, nil]` (Ruby's `[].minmax`), incomparable mixed-class values decline.
|
|
1046
1130
|
def tuple_minmax_pair(tuple, _method_name, args)
|
|
1047
1131
|
return nil unless args.empty?
|
|
1048
1132
|
|
|
@@ -1051,30 +1135,30 @@ module Rigor
|
|
|
1051
1135
|
return Type::Combinator.tuple_of(nil_const, nil_const)
|
|
1052
1136
|
end
|
|
1053
1137
|
|
|
1054
|
-
values =
|
|
1138
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1055
1139
|
return nil if values.nil?
|
|
1056
1140
|
|
|
1057
1141
|
low, high = values.minmax
|
|
1058
1142
|
Type::Combinator.tuple_of(
|
|
1059
|
-
|
|
1060
|
-
|
|
1143
|
+
lift_constant_value(low),
|
|
1144
|
+
lift_constant_value(high)
|
|
1061
1145
|
)
|
|
1062
1146
|
rescue StandardError
|
|
1063
1147
|
nil
|
|
1064
1148
|
end
|
|
1065
1149
|
|
|
1066
|
-
# `tuple.sort` — every element must be
|
|
1150
|
+
# `tuple.sort` — every element must be constant and the values must Ruby-compare. The result
|
|
1067
1151
|
# is a Tuple with the same elements in sorted order. Comparison failures (mixed-class
|
|
1068
1152
|
# incomparable values) decline.
|
|
1069
1153
|
def tuple_sort(tuple, _method_name, args)
|
|
1070
1154
|
return nil unless args.empty?
|
|
1071
1155
|
return tuple if tuple.elements.size <= 1
|
|
1072
1156
|
|
|
1073
|
-
values =
|
|
1157
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1074
1158
|
return nil if values.nil?
|
|
1075
1159
|
|
|
1076
1160
|
sorted = values.sort
|
|
1077
|
-
Type::Combinator.tuple_of(*sorted.map { |v|
|
|
1161
|
+
Type::Combinator.tuple_of(*sorted.map { |v| lift_constant_value(v) })
|
|
1078
1162
|
rescue StandardError
|
|
1079
1163
|
nil
|
|
1080
1164
|
end
|
|
@@ -1101,7 +1185,7 @@ module Rigor
|
|
|
1101
1185
|
# runtime semantics). Declines when any `other_k` is not a Tuple, since the arity is then unknown
|
|
1102
1186
|
# and the result would be `Array[Array[…]]` — RBS already gives that answer.
|
|
1103
1187
|
def tuple_zip(tuple, _method_name, args)
|
|
1104
|
-
return nil if args.
|
|
1188
|
+
return nil if args.size > MAX_ZIP_ARITY
|
|
1105
1189
|
return nil unless args.all?(Type::Tuple)
|
|
1106
1190
|
|
|
1107
1191
|
zipped = tuple.elements.each_with_index.map do |elem, i|
|
|
@@ -1139,23 +1223,180 @@ module Rigor
|
|
|
1139
1223
|
[key.value, value]
|
|
1140
1224
|
end
|
|
1141
1225
|
|
|
1142
|
-
# `tuple.
|
|
1143
|
-
# `
|
|
1144
|
-
|
|
1226
|
+
# `tuple.to_set` — folds to `Constant[Set]` when every element is a `Constant`, matching
|
|
1227
|
+
# `Set[...]` / `Set.new([...])`. Non-constant elements decline so RBS keeps `Set[Elem]`.
|
|
1228
|
+
def tuple_to_set(tuple, _method_name, args)
|
|
1229
|
+
return nil unless args.empty?
|
|
1230
|
+
|
|
1231
|
+
values = constant_values(tuple.elements)
|
|
1232
|
+
return nil if values.nil?
|
|
1233
|
+
|
|
1234
|
+
Type::Combinator.constant_of(::Set.new(values))
|
|
1235
|
+
rescue StandardError
|
|
1236
|
+
nil
|
|
1237
|
+
end
|
|
1238
|
+
|
|
1239
|
+
# `tuple.values_at(i1, i2, ...)` — returns a Tuple of per-index / per-range elements. Each
|
|
1240
|
+
# argument must be a `Constant[Integer]` or a `Constant[Range]` with integer/endless/beginless
|
|
1241
|
+
# endpoints. Out-of-range positive indices fill with `Constant[nil]`, mirroring Ruby's runtime
|
|
1242
|
+
# behaviour. Declines when any argument is non-static, raises `RangeError`, or exceeds
|
|
1243
|
+
# `MAX_SET_OPERATION_SIZE`.
|
|
1145
1244
|
def tuple_values_at(tuple, _method_name, args)
|
|
1146
|
-
return nil if args.empty?
|
|
1245
|
+
return nil if args.empty? || args.size > MAX_SET_OPERATION_SIZE
|
|
1246
|
+
|
|
1247
|
+
raw_args = args.map do |arg|
|
|
1248
|
+
return nil unless arg.is_a?(Type::Constant) && values_at_selector?(arg.value)
|
|
1249
|
+
|
|
1250
|
+
arg.value
|
|
1251
|
+
end
|
|
1252
|
+
picked = tuple.elements.values_at(*raw_args)
|
|
1253
|
+
return nil if picked.size > MAX_SET_OPERATION_SIZE
|
|
1254
|
+
|
|
1255
|
+
Type::Combinator.tuple_of(*picked.map { |elem| elem || Type::Combinator.constant_of(nil) })
|
|
1256
|
+
rescue StandardError
|
|
1257
|
+
nil
|
|
1258
|
+
end
|
|
1259
|
+
|
|
1260
|
+
def values_at_selector?(value)
|
|
1261
|
+
return true if value.is_a?(Integer)
|
|
1262
|
+
return false unless value.is_a?(Range) && integer_range?(value)
|
|
1147
1263
|
|
|
1264
|
+
!(value.end.is_a?(Integer) && value.end > MAX_SET_OPERATION_SIZE)
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
# `tuple.fetch_values(i1, i2, ...)` — returns a Tuple of per-index elements when every argument
|
|
1268
|
+
# is an in-bounds `Constant[Integer]`; an out-of-range or non-static index declines so RBS handles
|
|
1269
|
+
# the possible `IndexError`.
|
|
1270
|
+
def tuple_fetch_values(tuple, _method_name, args)
|
|
1148
1271
|
values = args.map do |arg|
|
|
1149
|
-
return nil unless arg.is_a?(Type::Constant)
|
|
1150
|
-
return nil unless arg.value.is_a?(Integer)
|
|
1272
|
+
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
1151
1273
|
|
|
1152
1274
|
idx = normalise_index(arg.value, tuple.elements.size)
|
|
1153
|
-
|
|
1275
|
+
return nil unless idx
|
|
1276
|
+
|
|
1277
|
+
tuple.elements[idx]
|
|
1154
1278
|
end
|
|
1155
1279
|
|
|
1156
1280
|
Type::Combinator.tuple_of(*values)
|
|
1157
1281
|
end
|
|
1158
1282
|
|
|
1283
|
+
# `tuple.assoc(needle)` / `tuple.rassoc(needle)` — scans inner Tuples in declaration order for a
|
|
1284
|
+
# constant match at slot 0 (`assoc`) or slot 1 (`rassoc`). Known non-array scalars/hashes/ranges
|
|
1285
|
+
# are skipped, while an earlier non-constant slot or opaque element declines.
|
|
1286
|
+
def tuple_assoc(tuple, _method_name, args)
|
|
1287
|
+
tuple_slot_lookup(tuple, args, slot_index: 0)
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
def tuple_rassoc(tuple, _method_name, args)
|
|
1291
|
+
tuple_slot_lookup(tuple, args, slot_index: 1)
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1294
|
+
def tuple_slot_lookup(tuple, args, slot_index:)
|
|
1295
|
+
return nil unless args.size == 1
|
|
1296
|
+
|
|
1297
|
+
needle = unwrap_constant_type(args.first)
|
|
1298
|
+
return nil if needle.equal?(NOT_CONSTANT)
|
|
1299
|
+
|
|
1300
|
+
tuple.elements.each do |elem|
|
|
1301
|
+
next if non_array_shape?(elem)
|
|
1302
|
+
return nil unless elem.is_a?(Type::Tuple)
|
|
1303
|
+
|
|
1304
|
+
match = tuple_slot_match(elem, needle, slot_index)
|
|
1305
|
+
return nil if match == :decline
|
|
1306
|
+
return elem if match
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
Type::Combinator.constant_of(nil)
|
|
1310
|
+
rescue StandardError
|
|
1311
|
+
nil
|
|
1312
|
+
end
|
|
1313
|
+
|
|
1314
|
+
def non_array_shape?(elem)
|
|
1315
|
+
elem.is_a?(Type::Constant) || elem.is_a?(Type::HashShape) ||
|
|
1316
|
+
elem.is_a?(Type::IntegerRange) || elem.is_a?(Type::FloatRange)
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
def tuple_slot_match(elem, needle, slot_index)
|
|
1320
|
+
return false if elem.elements.size <= slot_index
|
|
1321
|
+
|
|
1322
|
+
slot = unwrap_constant_type(elem.elements[slot_index])
|
|
1323
|
+
return :decline if slot.equal?(NOT_CONSTANT)
|
|
1324
|
+
|
|
1325
|
+
slot == needle
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
# `tuple.tally` — returns a closed `HashShape` of `value => Constant[count]` when every element
|
|
1329
|
+
# is a `Constant` whose value is a valid static HashShape key.
|
|
1330
|
+
def tuple_tally(tuple, _method_name, args)
|
|
1331
|
+
return nil unless args.empty?
|
|
1332
|
+
|
|
1333
|
+
values = constant_values(tuple.elements)
|
|
1334
|
+
return nil if values.nil?
|
|
1335
|
+
return nil unless values.all? { |v| static_shape_key?(v) }
|
|
1336
|
+
|
|
1337
|
+
counts = values.tally.transform_values { |n| Type::Combinator.constant_of(n) }
|
|
1338
|
+
Type::Combinator.hash_shape_of(counts)
|
|
1339
|
+
end
|
|
1340
|
+
|
|
1341
|
+
# `tuple.transpose` — transposes a Tuple of equal-length Tuples into a Tuple of Tuples.
|
|
1342
|
+
def tuple_transpose(tuple, _method_name, args)
|
|
1343
|
+
return nil unless args.empty?
|
|
1344
|
+
return tuple if tuple.elements.empty?
|
|
1345
|
+
return nil unless tuple.elements.all?(Type::Tuple)
|
|
1346
|
+
|
|
1347
|
+
width = tuple.elements.first.elements.size
|
|
1348
|
+
return nil unless tuple.elements.all? { |row| row.elements.size == width }
|
|
1349
|
+
|
|
1350
|
+
transposed = tuple.elements.map(&:elements).transpose
|
|
1351
|
+
Type::Combinator.tuple_of(*transposed.map { |col| Type::Combinator.tuple_of(*col) })
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
# `tuple.product(other_1, ...)` — Cartesian product of Tuples, bounded by
|
|
1355
|
+
# `MAX_SET_OPERATION_ARITY` and `MAX_SET_OPERATION_SIZE`.
|
|
1356
|
+
def tuple_product(tuple, _method_name, args)
|
|
1357
|
+
return nil if args.size > MAX_SET_OPERATION_ARITY
|
|
1358
|
+
return nil unless args.all?(Type::Tuple)
|
|
1359
|
+
|
|
1360
|
+
total = args.reduce(tuple.elements.size) { |acc, other| acc * other.elements.size }
|
|
1361
|
+
return nil if total > MAX_SET_OPERATION_SIZE
|
|
1362
|
+
return Type::Combinator.tuple_of if total.zero?
|
|
1363
|
+
|
|
1364
|
+
pairs = tuple.elements.product(*args.map(&:elements))
|
|
1365
|
+
Type::Combinator.tuple_of(*pairs.map { |row| Type::Combinator.tuple_of(*row) })
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
# `tuple.pack(template)` — folds to `Constant[String]` when all elements and template are
|
|
1369
|
+
# `Constant` (excluding non-deterministic pointer directives `p`/`P` and numeric counts or offsets
|
|
1370
|
+
# whose sum exceeds `TUPLE_JOIN_BYTE_LIMIT`), capped at `TUPLE_JOIN_BYTE_LIMIT`.
|
|
1371
|
+
def tuple_pack(tuple, _method_name, args)
|
|
1372
|
+
return nil unless args.size == 1
|
|
1373
|
+
|
|
1374
|
+
template = args.first
|
|
1375
|
+
return nil unless template.is_a?(Type::Constant) && template.value.is_a?(String)
|
|
1376
|
+
return nil if template.value.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
1377
|
+
return nil if template.value.match?(/[pP]/)
|
|
1378
|
+
return nil if pack_count_exceeds_limit?(template.value)
|
|
1379
|
+
|
|
1380
|
+
values = constant_values(tuple.elements)
|
|
1381
|
+
return nil if values.nil?
|
|
1382
|
+
|
|
1383
|
+
result = values.pack(template.value)
|
|
1384
|
+
return nil if result.bytesize > TUPLE_JOIN_BYTE_LIMIT
|
|
1385
|
+
|
|
1386
|
+
Type::Combinator.constant_of(result)
|
|
1387
|
+
rescue StandardError
|
|
1388
|
+
nil
|
|
1389
|
+
end
|
|
1390
|
+
|
|
1391
|
+
def pack_count_exceeds_limit?(template)
|
|
1392
|
+
total = 0
|
|
1393
|
+
template.scan(/\d+/) do |digits|
|
|
1394
|
+
total += digits.to_i
|
|
1395
|
+
return true if total > TUPLE_JOIN_BYTE_LIMIT
|
|
1396
|
+
end
|
|
1397
|
+
false
|
|
1398
|
+
end
|
|
1399
|
+
|
|
1159
1400
|
# `tuple + other` — concatenates two Tuples. Both sides must be `Type::Tuple`. Returns a new
|
|
1160
1401
|
# Tuple whose elements are those of the receiver followed by those of the argument.
|
|
1161
1402
|
# `at(n)` — the strict single-Integer accessor. Deliberately NOT an alias of {#tuple_index}: `[]`
|
|
@@ -1178,15 +1419,33 @@ module Rigor
|
|
|
1178
1419
|
# Every set operation is evaluated by running Ruby's own operator over the unwrapped values, which
|
|
1179
1420
|
# is the only way to reproduce its `eql?` / `hash` semantics exactly — `[1] & [1.0]` is empty even
|
|
1180
1421
|
# though `1 == 1.0`, so an equality-based reimplementation would fold the wrong answer.
|
|
1181
|
-
def tuple_intersection(tuple,
|
|
1422
|
+
def tuple_intersection(tuple, method_name, args)
|
|
1423
|
+
return tuple if method_name == :intersection && args.empty?
|
|
1424
|
+
|
|
1182
1425
|
set_operation(tuple, args) { |values, others| others.reduce(values, :&) }
|
|
1183
1426
|
end
|
|
1184
1427
|
|
|
1185
|
-
def tuple_union(tuple,
|
|
1428
|
+
def tuple_union(tuple, method_name, args)
|
|
1429
|
+
return tuple_zero_arg_union(tuple) if method_name == :union && args.empty?
|
|
1430
|
+
|
|
1186
1431
|
set_operation(tuple, args) { |values, others| others.reduce(values, :|) }
|
|
1187
1432
|
end
|
|
1188
1433
|
|
|
1189
|
-
def
|
|
1434
|
+
def tuple_zero_arg_union(tuple)
|
|
1435
|
+
return tuple if tuple.elements.size <= 1
|
|
1436
|
+
|
|
1437
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1438
|
+
return nil if values.nil?
|
|
1439
|
+
|
|
1440
|
+
result = values.union
|
|
1441
|
+
return nil if result.size > MAX_SET_OPERATION_SIZE
|
|
1442
|
+
|
|
1443
|
+
Type::Combinator.tuple_of(*result.map { |value| lift_constant_value(value) })
|
|
1444
|
+
end
|
|
1445
|
+
|
|
1446
|
+
def tuple_difference(tuple, method_name, args)
|
|
1447
|
+
return tuple if method_name == :difference && args.empty?
|
|
1448
|
+
|
|
1190
1449
|
set_operation(tuple, args) { |values, others| others.reduce(values, :-) }
|
|
1191
1450
|
end
|
|
1192
1451
|
|
|
@@ -1203,23 +1462,23 @@ module Rigor
|
|
|
1203
1462
|
|
|
1204
1463
|
# Shared body for the set operations: unwrap the receiver and every Tuple argument, hand the raw
|
|
1205
1464
|
# values to `block`, and lift the result back. Declines unless every element on both sides is a
|
|
1206
|
-
#
|
|
1207
|
-
#
|
|
1465
|
+
# constant scalar or constant nested Tuple / HashShape (an unknown element makes membership
|
|
1466
|
+
# undecidable) and every argument is a `Tuple`.
|
|
1208
1467
|
def set_operation(tuple, args)
|
|
1209
1468
|
return nil if args.empty? || args.size > MAX_SET_OPERATION_ARITY
|
|
1210
1469
|
|
|
1211
|
-
values =
|
|
1470
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1212
1471
|
return nil if values.nil?
|
|
1213
1472
|
|
|
1214
1473
|
others = args.map do |arg|
|
|
1215
1474
|
return nil unless arg.is_a?(Type::Tuple)
|
|
1216
1475
|
|
|
1217
|
-
|
|
1476
|
+
unwrap_constant_elements(arg.elements) || (return nil)
|
|
1218
1477
|
end
|
|
1219
1478
|
result = yield(values, others)
|
|
1220
1479
|
return nil if result.size > MAX_SET_OPERATION_SIZE
|
|
1221
1480
|
|
|
1222
|
-
Type::Combinator.tuple_of(*result.map { |value|
|
|
1481
|
+
Type::Combinator.tuple_of(*result.map { |value| lift_constant_value(value) })
|
|
1223
1482
|
end
|
|
1224
1483
|
|
|
1225
1484
|
# An argument list longer than this, or a result wider than this, declines to the RBS tier rather
|
|
@@ -1278,16 +1537,19 @@ module Rigor
|
|
|
1278
1537
|
end
|
|
1279
1538
|
|
|
1280
1539
|
# `uniq` (no block) → `Tuple` of the first occurrence of each distinct value. Folds only when
|
|
1281
|
-
# every element is a
|
|
1540
|
+
# every element is a constant scalar or constant nested Tuple / HashShape so `eql?` identity is
|
|
1541
|
+
# decidable; the block form defers.
|
|
1282
1542
|
def tuple_uniq(tuple, _method_name, args)
|
|
1283
1543
|
return nil unless args.empty?
|
|
1284
|
-
return nil unless tuple.elements.all?(Type::Constant)
|
|
1285
1544
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1545
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1546
|
+
return nil if values.nil?
|
|
1547
|
+
|
|
1548
|
+
seen = {}
|
|
1549
|
+
kept = tuple.elements.zip(values).each_with_object([]) do |(element, value), acc|
|
|
1550
|
+
next if seen.key?(value)
|
|
1289
1551
|
|
|
1290
|
-
seen
|
|
1552
|
+
seen[value] = true
|
|
1291
1553
|
acc << element
|
|
1292
1554
|
end
|
|
1293
1555
|
Type::Combinator.tuple_of(*kept)
|
|
@@ -1295,9 +1557,9 @@ module Rigor
|
|
|
1295
1557
|
|
|
1296
1558
|
# `index(obj)` / `find_index(obj)` → `Constant[Integer]` of the first element equal to `obj`,
|
|
1297
1559
|
# `Constant[nil]` when none match. Folds only for the argument form (the block form defers) when
|
|
1298
|
-
# every element AND the argument are
|
|
1560
|
+
# every element AND the argument are constant (decidable equality).
|
|
1299
1561
|
def tuple_find_index(tuple, _method_name, args)
|
|
1300
|
-
constant_index(tuple, args) { |
|
|
1562
|
+
constant_index(tuple, args) { |values, needle| values.index { |v| v == needle } }
|
|
1301
1563
|
end
|
|
1302
1564
|
|
|
1303
1565
|
# `tuple.flatten` / `tuple.flatten(depth)` — recursively flattens nested Tuple elements into a
|
|
@@ -1341,17 +1603,19 @@ module Rigor
|
|
|
1341
1603
|
|
|
1342
1604
|
# `rindex(obj)` → the LAST matching index, same decidability gate.
|
|
1343
1605
|
def tuple_rindex(tuple, _method_name, args)
|
|
1344
|
-
constant_index(tuple, args) { |
|
|
1606
|
+
constant_index(tuple, args) { |values, needle| values.rindex { |v| v == needle } }
|
|
1345
1607
|
end
|
|
1346
1608
|
|
|
1347
1609
|
def constant_index(tuple, args)
|
|
1348
1610
|
return nil unless args.size == 1
|
|
1349
1611
|
|
|
1350
|
-
needle = args.first
|
|
1351
|
-
|
|
1352
|
-
return nil
|
|
1612
|
+
needle = unwrap_constant_type(args.first)
|
|
1613
|
+
values = unwrap_constant_elements(tuple.elements)
|
|
1614
|
+
return nil if needle.equal?(NOT_CONSTANT) || values.nil?
|
|
1353
1615
|
|
|
1354
|
-
Type::Combinator.constant_of(yield(
|
|
1616
|
+
Type::Combinator.constant_of(yield(values, needle))
|
|
1617
|
+
rescue StandardError
|
|
1618
|
+
nil
|
|
1355
1619
|
end
|
|
1356
1620
|
|
|
1357
1621
|
# `tuple.take(n)` — returns the first n elements as a new Tuple. The argument must be a
|
|
@@ -1422,7 +1686,63 @@ module Rigor
|
|
|
1422
1686
|
end
|
|
1423
1687
|
|
|
1424
1688
|
def any_element_matches?(elements, value)
|
|
1425
|
-
elements.any?
|
|
1689
|
+
elements.any? do |e|
|
|
1690
|
+
unwrapped = unwrap_constant_type(e)
|
|
1691
|
+
!unwrapped.equal?(NOT_CONSTANT) && unwrapped == value
|
|
1692
|
+
end
|
|
1693
|
+
end
|
|
1694
|
+
|
|
1695
|
+
NOT_CONSTANT = Object.new.freeze
|
|
1696
|
+
private_constant :NOT_CONSTANT
|
|
1697
|
+
|
|
1698
|
+
# Unwraps a `Type::Constant`, a `Type::Tuple` of recursively constant elements, or a closed
|
|
1699
|
+
# required-only `Type::HashShape` of recursively constant values into its plain Ruby value, or
|
|
1700
|
+
# `NOT_CONSTANT` when any part is non-constant.
|
|
1701
|
+
def unwrap_constant_type(type)
|
|
1702
|
+
case type
|
|
1703
|
+
when Type::Constant
|
|
1704
|
+
type.value
|
|
1705
|
+
when Type::Tuple
|
|
1706
|
+
values = unwrap_constant_elements(type.elements)
|
|
1707
|
+
values.nil? ? NOT_CONSTANT : values
|
|
1708
|
+
when Type::HashShape
|
|
1709
|
+
unwrap_constant_hash_shape(type)
|
|
1710
|
+
else
|
|
1711
|
+
NOT_CONSTANT
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
|
|
1715
|
+
def unwrap_constant_hash_shape(shape)
|
|
1716
|
+
return NOT_CONSTANT unless shape.closed? && shape.optional_keys.empty?
|
|
1717
|
+
|
|
1718
|
+
pairs = {}
|
|
1719
|
+
shape.pairs.each do |k, v|
|
|
1720
|
+
unwrapped = unwrap_constant_type(v)
|
|
1721
|
+
return NOT_CONSTANT if unwrapped.equal?(NOT_CONSTANT)
|
|
1722
|
+
|
|
1723
|
+
pairs[k] = unwrapped
|
|
1724
|
+
end
|
|
1725
|
+
pairs
|
|
1726
|
+
end
|
|
1727
|
+
|
|
1728
|
+
def unwrap_constant_elements(elements)
|
|
1729
|
+
elements.map do |elem|
|
|
1730
|
+
val = unwrap_constant_type(elem)
|
|
1731
|
+
return nil if val.equal?(NOT_CONSTANT)
|
|
1732
|
+
|
|
1733
|
+
val
|
|
1734
|
+
end
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1737
|
+
def lift_constant_value(value)
|
|
1738
|
+
case value
|
|
1739
|
+
when Array
|
|
1740
|
+
Type::Combinator.tuple_of(*value.map { |v| lift_constant_value(v) })
|
|
1741
|
+
when Hash
|
|
1742
|
+
Type::Combinator.hash_shape_of(value.transform_values { |v| lift_constant_value(v) })
|
|
1743
|
+
else
|
|
1744
|
+
Type::Combinator.constant_of(value)
|
|
1745
|
+
end
|
|
1426
1746
|
end
|
|
1427
1747
|
|
|
1428
1748
|
# Per-element Constant value extraction. Returns nil when any element is non-Constant, so the
|