rigortype 0.4.6 → 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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- 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 +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -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 +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- 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_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- 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 +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- 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 +81 -5
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- 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 +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -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/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +123 -25
- 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 +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- 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/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- 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 +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- 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 +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- 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/plugins.rb +1 -1
- 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/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +81 -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 +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- 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/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- 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/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- 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/effects.rb +1 -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-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- 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-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -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 +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- 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-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- 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 +82 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../typing_state"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Inference
|
|
5
7
|
# The value the statement evaluator last gave a loop node, and the scope it evaluated the loop from, so
|
|
@@ -13,40 +15,90 @@ module Rigor
|
|
|
13
15
|
# evaluation would. It is not compared with `Scope#==`, which ignores several fields (`match_frame`,
|
|
14
16
|
# `declared_refinements`, `struct_fold_safe_locals`, the opaque-block flags, `lexical_nesting`, the origins tables,
|
|
15
17
|
# `discovery`): across the cost spec's 10933 reads every match was an identity match, and `lib/` read no record at
|
|
16
|
-
# all (issue #2262). It lives in a per-thread map
|
|
17
|
-
#
|
|
18
|
+
# all (issue #2262). It lives in a per-thread map keyed by the loop node. The map's keys are weak but its values
|
|
19
|
+
# are strong, and a recorded scope reaches its discovery, its environment and the parse tree that holds the key,
|
|
20
|
+
# so a record never goes on its own (issue #2412): the map is a run-scoped slot ({TypingState.release_run_scoped}),
|
|
21
|
+
# emptied when the run that filled it ends.
|
|
18
22
|
module LoopValueRecord
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
# ADR-129 WD1 — a Table, and a read-through cache of the computation's own value: a record read back answers
|
|
24
|
+
# what evaluating the loop again from the same scope would.
|
|
25
|
+
RECORD = TypingState.table(
|
|
26
|
+
:loop_value_record,
|
|
27
|
+
run_scoped: true, read_through: true, entries: TypingState::WeakKeyMapEntries,
|
|
28
|
+
reason: "keyed by loop node; a read-through cache of the loop's own evaluation"
|
|
29
|
+
)
|
|
30
|
+
private_constant :RECORD
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
# ADR-129 WD3 — a Resource read only as a delta: {#loops_evaluated} feeds a before/after comparison taken within
|
|
33
|
+
# one conditional's evaluation, so with no loop evaluated in between the delta is zero.
|
|
34
|
+
LOOPS_EVALUATED = TypingState.resource(
|
|
35
|
+
:loops_evaluated,
|
|
36
|
+
read_exemption: :delta_internal, run_scoped: true,
|
|
37
|
+
reason: "a count of the loops this thread evaluated, incremented in place and read as a delta"
|
|
38
|
+
)
|
|
39
|
+
private_constant :LOOPS_EVALUATED
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
# Issue #2767 — the ADR-55 fixpoint summaries are an ambient input of a loop's value: an ADR-55 fixpoint
|
|
42
|
+
# evaluates a recursive method's body several times from the same body scope object, each time under a wider
|
|
43
|
+
# assumption, so a value recorded in one iteration would answer an identity-matching read in the next one with
|
|
44
|
+
# the narrower iterate, and the fixpoint would converge on it (`[(while …; break(n.zero? ? 1 : q(n - 1)); end)]`
|
|
45
|
+
# typed `[bot]`). The generation moves whenever a summary's assumption is seeded or changed
|
|
46
|
+
# ({advance_generation}), and a record answers only a read in the generation it was written in. The advance in
|
|
47
|
+
# `fixpoint_step` is the one a witness needs (the spec above); the advances at the seed, the entangled degrade
|
|
48
|
+
# and the cap collapse are precautions, since each leaves no in-flight evaluation of that body scope behind.
|
|
49
|
+
#
|
|
50
|
+
# ADR-129 WD3 — a Resource whose value is fixed while the state id is: every advance is a {TypingState::State#rewrite}.
|
|
51
|
+
GENERATION = TypingState.resource(
|
|
52
|
+
:summary_generation,
|
|
53
|
+
read_exemption: :id_determined, run_scoped: true,
|
|
54
|
+
reason: "a count of the ADR-55 summary assumption changes on this thread; a record keys on it"
|
|
55
|
+
)
|
|
56
|
+
private_constant :GENERATION
|
|
57
|
+
|
|
58
|
+
module_function
|
|
26
59
|
|
|
27
60
|
def record(node, scope, type)
|
|
28
|
-
|
|
61
|
+
TypingState.current.write(LOOPS_EVALUATED, loops_evaluated + 1)
|
|
29
62
|
conditional(node, scope, type)
|
|
30
63
|
end
|
|
31
64
|
|
|
65
|
+
# The current ADR-55 summary generation (#2767), which a record made now carries and a read must match.
|
|
66
|
+
def generation = TypingState.current[GENERATION] || 0
|
|
67
|
+
|
|
68
|
+
# A summary assumption was seeded or changed: no record written before answers a read from now on (#2767).
|
|
69
|
+
def advance_generation
|
|
70
|
+
state = TypingState.current
|
|
71
|
+
state.write(GENERATION, (state[GENERATION] || 0) + 1)
|
|
72
|
+
state.rewrite(GENERATION)
|
|
73
|
+
end
|
|
74
|
+
|
|
32
75
|
# How many loops this thread has evaluated, so a conditional can tell whether its evaluation held one.
|
|
33
|
-
def loops_evaluated =
|
|
76
|
+
def loops_evaluated = TypingState.current[LOOPS_EVALUATED] || 0
|
|
34
77
|
|
|
35
78
|
# The same record for a conditional that evaluated a loop in a branch: `id(c ? (while …) : 0)` types the
|
|
36
79
|
# conditional again from the scope it was evaluated from, and each typing evaluated the loop again from a
|
|
37
80
|
# branch scope of its own, which multiplied the passes of every loop nested in it (issue #2262).
|
|
38
81
|
def conditional(node, scope, type)
|
|
39
|
-
|
|
82
|
+
record = [scope, type, generation]
|
|
83
|
+
state = TypingState.current
|
|
84
|
+
state.log_write(RECORD, node, record)
|
|
85
|
+
state.weak_key_map(RECORD)[node] = record
|
|
40
86
|
type
|
|
41
87
|
end
|
|
42
88
|
|
|
43
|
-
#
|
|
89
|
+
# Runs the evaluation a {fetch} that answered nil falls back to: the read-through Table's miss (ADR-129 WD1),
|
|
90
|
+
# which touches when it writes another Table while a capture is open.
|
|
91
|
+
def miss(&) = TypingState.current.memo_miss(RECORD, &)
|
|
92
|
+
|
|
93
|
+
# The recorded value of `node` evaluated from `scope` itself in the current summary generation, or nil.
|
|
44
94
|
def fetch(node, scope)
|
|
45
|
-
|
|
95
|
+
state = TypingState.current
|
|
96
|
+
slot = state[RECORD]&.[](node)
|
|
97
|
+
state.log_read(RECORD, node, slot || TypingState::ABSENT)
|
|
46
98
|
return nil if slot.nil?
|
|
47
99
|
|
|
48
|
-
recorded, type = slot
|
|
49
|
-
recorded.equal?(scope) ? type : nil
|
|
100
|
+
recorded, type, recorded_generation = slot
|
|
101
|
+
recorded.equal?(scope) && recorded_generation == generation ? type : nil
|
|
50
102
|
end
|
|
51
103
|
end
|
|
52
104
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "../../typing_state"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
8
9
|
module Inference
|
|
@@ -15,6 +16,22 @@ module Rigor
|
|
|
15
16
|
# `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
|
|
16
17
|
# too, asked only while `$_` is narrowed.
|
|
17
18
|
class Frame
|
|
19
|
+
# ADR-129 G4 — the frame's extensional caches. A scan is keyed on its node (and reading kind) and on the scope's
|
|
20
|
+
# local and instance-variable tables by identity, the inputs a lookup argument's answer reads; the rest of what
|
|
21
|
+
# it reads through the scope (constant resolution over the lexical nesting, discovery and environment) is fixed
|
|
22
|
+
# for the body the frame runs. The other three read the body and the parameters alone.
|
|
23
|
+
TypingState.memo_field(self, :@scans, :@kind_scans, :@self_call_fallback,
|
|
24
|
+
reason: "keyed on the node and the scope's locals and ivars tables by identity; the " \
|
|
25
|
+
"other scope inputs a scan reads are fixed for the frame's body")
|
|
26
|
+
TypingState.memo_field(self, :@forwarded_block, :@body_locals, :@parameter_names,
|
|
27
|
+
reason: "functions of the frame's body and parameters alone")
|
|
28
|
+
|
|
29
|
+
# The body the frame runs, which issue #2555's escape scan reads ({ConstructionEscape.escaped?}).
|
|
30
|
+
attr_reader :body
|
|
31
|
+
# The method's parameters (nil for a class, module or file body), which issue #2608's holder scan reads for a
|
|
32
|
+
# zero-argument `super` ({RemoverHolderScan}).
|
|
33
|
+
attr_reader :parameters
|
|
34
|
+
|
|
18
35
|
# The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
|
|
19
36
|
# parameters and targets.
|
|
20
37
|
LOCAL_BINDINGS = Set[
|
|
@@ -37,6 +54,8 @@ module Rigor
|
|
|
37
54
|
@scans = nil
|
|
38
55
|
@last_line_closure = nil
|
|
39
56
|
@last_line_fallback = nil
|
|
57
|
+
@body_locals = nil
|
|
58
|
+
@parameter_names = nil
|
|
40
59
|
end
|
|
41
60
|
|
|
42
61
|
def matching_closure?(scope = nil)
|
|
@@ -88,6 +107,17 @@ module Rigor
|
|
|
88
107
|
@forwarded_block == name
|
|
89
108
|
end
|
|
90
109
|
|
|
110
|
+
# Issue #2354 — whether `name` is a local of the frame's own body: the body binds it (a write, or a block's,
|
|
111
|
+
# lambda's, `rescue`'s or pattern's own parameter or target) and no parameter of the method names it. A
|
|
112
|
+
# method parameter is the caller's object wherever the body also writes the name (`h = h.dup if f`, a
|
|
113
|
+
# later `h = {}`), so it never counts. Kept per name for the rest of the body.
|
|
114
|
+
def body_local?(name)
|
|
115
|
+
bound = (@body_locals ||= {})
|
|
116
|
+
return bound[name] if bound.key?(name)
|
|
117
|
+
|
|
118
|
+
bound[name] = !parameter_names.include?(name) && binds_local?(@body, name)
|
|
119
|
+
end
|
|
120
|
+
|
|
91
121
|
# Issue #1157 — whether a call's `&expr` block argument hands on the block THIS method was called with: the
|
|
92
122
|
# anonymous `&` of a method that declares an anonymous `&` parameter, or {#forwarded_block?} for `&name`.
|
|
93
123
|
# Anything else (`&:sym`, a proc made here, a rebound name) is a block of this frame's own making.
|
|
@@ -144,6 +174,33 @@ module Rigor
|
|
|
144
174
|
!block.nil? && block.name.nil?
|
|
145
175
|
end
|
|
146
176
|
|
|
177
|
+
# The names the method's own parameters bind: positional (destructured ones included), optional, rest,
|
|
178
|
+
# keyword, keyword-rest and block. Empty for a class, module or file body.
|
|
179
|
+
def parameter_names
|
|
180
|
+
@parameter_names ||= begin
|
|
181
|
+
parameters = @parameters
|
|
182
|
+
names = Set.new
|
|
183
|
+
if parameters.is_a?(Prism::ParametersNode)
|
|
184
|
+
[*parameters.requireds, *parameters.optionals, parameters.rest, *parameters.posts,
|
|
185
|
+
*parameters.keywords, parameters.keyword_rest, parameters.block].each do |parameter|
|
|
186
|
+
collect_parameter_names(parameter, names)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
names.freeze
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def collect_parameter_names(node, names)
|
|
194
|
+
case node
|
|
195
|
+
when Prism::MultiTargetNode
|
|
196
|
+
[*node.lefts, node.rest, *node.rights].each { |target| collect_parameter_names(target, names) }
|
|
197
|
+
when Prism::SplatNode then collect_parameter_names(node.expression, names)
|
|
198
|
+
else
|
|
199
|
+
name = node.respond_to?(:name) ? node.name : nil
|
|
200
|
+
names << name if name
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
147
204
|
# The method's own `&block` parameter's name, or nil.
|
|
148
205
|
def block_parameter_name
|
|
149
206
|
parameters = @parameters
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MatchRebinding
|
|
8
|
+
# Issue #2443, #1371 — the match globals a block enters with when the call that owns it matches before every
|
|
9
|
+
# yield. `String#scan`, `#gsub`, `#gsub!`, `#sub` and `#sub!` set the caller's `$~` to the current match before
|
|
10
|
+
# each run of their block, with a Regexp pattern or a String one, so the block reads that match on every run
|
|
11
|
+
# whatever the enclosing frame held; the frame-wide entry ({MatchRebinding.block_entry}) would otherwise leave
|
|
12
|
+
# it the narrowing at the call site or nothing at all.
|
|
13
|
+
#
|
|
14
|
+
# A receiver that is a String, or a `Dynamic` the analyzer cannot type ({Narrowing.string_match_receiver?}),
|
|
15
|
+
# enters with the call's own match edge ({Narrowing.yielded_match_scope}), which forgets what the frame held.
|
|
16
|
+
# A `Dynamic` receiver may be another object whose block-taking `scan` runs no match, and then the block reads
|
|
17
|
+
# the caller's `$~`; the name is taken as String's there, the overwhelmingly common case. A receiver known to
|
|
18
|
+
# be anything else — a project class, `Pathname`, a union with either — keeps the frame-wide entry: a method
|
|
19
|
+
# defined in Ruby runs its matches in a frame of its own, so its block reads the caller's `$~` as the frame
|
|
20
|
+
# rules leave it. So does an implicit-self call.
|
|
21
|
+
#
|
|
22
|
+
# The block form of `grep` and `grep_v` runs `pattern === element` in the caller's frame before each yield,
|
|
23
|
+
# which a Regexp pattern answers by setting `$~`: `grep` yields after a match, `grep_v` after a failed one. With
|
|
24
|
+
# a regex literal for the pattern, a `grep` block enters with that match edge and a `grep_v` block with the
|
|
25
|
+
# globals unbound. `Enumerable` is mixed into classes of every kind, so any explicit receiver counts but one
|
|
26
|
+
# whose `grep` the project defines ({ProjectMethodOwnership}): that method matches, if at all, in a frame of
|
|
27
|
+
# its own, and its block keeps the frame-wide entry as a project `scan`'s does. Where a refinement that defines
|
|
28
|
+
# the name is in effect at the call (issue #3198), the method that runs is the refinement's or `Enumerable`'s,
|
|
29
|
+
# and this does not tell which, so the block enters with what `Enumerable`'s would bind read untyped. Any other
|
|
30
|
+
# pattern (`grep(Integer)`) keeps the frame-wide entry too, since its `===` may leave `$~` alone.
|
|
31
|
+
module YieldingMatch
|
|
32
|
+
NAMES = Set[:scan, :gsub, :gsub!, :sub, :sub!].freeze
|
|
33
|
+
GREP_NAMES = Set[:grep, :grep_v].freeze
|
|
34
|
+
private_constant :NAMES, :GREP_NAMES
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# `scope` with the match globals `call_node`'s block enters with; `scope` itself for any other block.
|
|
39
|
+
# `operand_scope` types the receiver.
|
|
40
|
+
def entry(scope, block_node, call_node, operand_scope)
|
|
41
|
+
return scope unless own_block_call?(block_node, call_node)
|
|
42
|
+
|
|
43
|
+
name = call_node.name
|
|
44
|
+
if NAMES.include?(name)
|
|
45
|
+
string_entry(scope, call_node, operand_scope)
|
|
46
|
+
elsif GREP_NAMES.include?(name)
|
|
47
|
+
grep_entry(scope, call_node, operand_scope)
|
|
48
|
+
else
|
|
49
|
+
scope
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def string_entry(scope, call_node, operand_scope)
|
|
54
|
+
return scope unless Narrowing.string_match_receiver?(operand_scope.type_of(call_node.receiver), operand_scope)
|
|
55
|
+
|
|
56
|
+
Narrowing.yielded_match_scope(scope, pattern_argument(call_node))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def grep_entry(scope, call_node, operand_scope)
|
|
60
|
+
pattern = pattern_argument(call_node)
|
|
61
|
+
return scope unless pattern.is_a?(Prism::RegularExpressionNode) ||
|
|
62
|
+
pattern.is_a?(Prism::InterpolatedRegularExpressionNode)
|
|
63
|
+
return scope if project_method?(call_node, operand_scope)
|
|
64
|
+
|
|
65
|
+
entry = call_node.name == :grep_v ? scope.forget_match_globals : Narrowing.yielded_match_scope(scope, pattern)
|
|
66
|
+
refined?(call_node, operand_scope) ? entry.untyped_match_globals : entry
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# True when the receiver answers the call with a method the project defines, or is a carrier whose class
|
|
70
|
+
# cannot be resolved; an untyped receiver is not one.
|
|
71
|
+
def project_method?(call_node, operand_scope)
|
|
72
|
+
targets = ProjectMethodOwnership.targets(operand_scope.type_of(call_node.receiver))
|
|
73
|
+
return true if targets.nil?
|
|
74
|
+
|
|
75
|
+
targets.any? do |class_name, kind|
|
|
76
|
+
ProjectMethodOwnership.defines?(class_name, call_node.name, kind, operand_scope)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Issue #3198 — true when a refinement in effect at the call defines its name on any class: a refined method
|
|
81
|
+
# is Ruby's, and matches, if at all, in a frame of its own. The refined class is not compared with the
|
|
82
|
+
# receiver, so the call may still be `Enumerable`'s.
|
|
83
|
+
def refined?(call_node, operand_scope)
|
|
84
|
+
SelfPreservingBlock.refined_in_effect?(call_node, call_node.name, operand_scope)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The call's first argument, which each of these methods takes as its pattern; nil without one.
|
|
88
|
+
def pattern_argument(call_node)
|
|
89
|
+
call_node.arguments&.arguments&.first
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def own_block_call?(block_node, call_node)
|
|
93
|
+
call_node.is_a?(Prism::CallNode) && !call_node.receiver.nil? &&
|
|
94
|
+
block_node.is_a?(Prism::BlockNode) && call_node.block.equal?(block_node)
|
|
95
|
+
end
|
|
96
|
+
private_class_method :string_entry, :grep_entry, :project_method?, :refined?, :pattern_argument,
|
|
97
|
+
:own_block_call?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -7,11 +7,14 @@ require_relative "../type"
|
|
|
7
7
|
require_relative "block_call_timing"
|
|
8
8
|
require_relative "fresh_frame_blocks"
|
|
9
9
|
require_relative "guard_rebinding"
|
|
10
|
+
require_relative "project_method_ownership"
|
|
11
|
+
require_relative "self_preserving_block"
|
|
10
12
|
require_relative "stored_block_call"
|
|
11
13
|
require_relative "match_rebinding/frame"
|
|
12
14
|
require_relative "match_rebinding/operands"
|
|
13
15
|
require_relative "match_rebinding/calls"
|
|
14
16
|
require_relative "match_rebinding/self_calls"
|
|
17
|
+
require_relative "match_rebinding/yielding_match"
|
|
15
18
|
require_relative "last_line"
|
|
16
19
|
|
|
17
20
|
module Rigor
|
|
@@ -363,21 +366,41 @@ module Rigor
|
|
|
363
366
|
# ractor's root block with the globals unbound, since it reads a slot of its own, and a `define_method` body
|
|
364
367
|
# with a narrowed global untyped, since it reads the definer's slot whenever the method is called. `$_`, which
|
|
365
368
|
# shares the slot, enters on the same terms ({LastLine.block_entry}, issue #1359). A closure's body, which runs
|
|
366
|
-
# whenever it is called, enters with `$!`, `$@` and `$?` unbound ({FreshFrameBlocks.closure_entry}, issue #1360)
|
|
369
|
+
# whenever it is called, enters with `$!`, `$@` and `$?` unbound ({FreshFrameBlocks.closure_entry}, issue #1360),
|
|
370
|
+
# and with the match globals unbound whatever its body does ({FreshFrameBlocks.proc_body?}, issue #1371): the
|
|
371
|
+
# narrowing where a lambda or proc is written proves nothing about the `$~` it reads when it is called. The
|
|
372
|
+
# block of `Enumerator.new` or `Hash.new` on the core class runs later too, but often in the statement that
|
|
373
|
+
# makes it, so where none of the rules above forgets them it enters with a narrowed global untyped
|
|
374
|
+
# ({FreshFrameBlocks.deferred_constructor?}, issue #3195).
|
|
367
375
|
#
|
|
368
376
|
# Issue #1429 — the entry also restores a guard's narrowing of a global or constant where the body may run after
|
|
369
377
|
# code that rebinds it ({GuardRebinding.block_entry}).
|
|
378
|
+
#
|
|
379
|
+
# Issue #2443, #1371 — the block of a call that matches before every yield (`scan`, `gsub`, `sub`, … on a String
|
|
380
|
+
# or untyped receiver, `grep` and `grep_v` with a regex literal) enters with that call's own match edge in place
|
|
381
|
+
# of the entry above ({YieldingMatch}); a match its body runs still rebinds them through the statement-level
|
|
382
|
+
# rules.
|
|
370
383
|
def block_entry(scope, block_node, call_node = nil)
|
|
384
|
+
operand_scope = scope
|
|
371
385
|
scope = GuardRebinding.block_entry(scope, block_node, call_node)
|
|
372
386
|
return FreshFrameBlocks.entry(scope, call_node) if FreshFrameBlocks.fresh_entry?(call_node, scope)
|
|
373
387
|
|
|
374
388
|
scope = FreshFrameBlocks.closure_entry(scope, block_node, call_node)
|
|
375
389
|
scope = LastLine.block_entry(scope, block_node, call_node)
|
|
390
|
+
YieldingMatch.entry(frame_entry(scope, block_node, call_node), block_node, call_node, operand_scope)
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def frame_entry(scope, block_node, call_node)
|
|
376
394
|
return scope unless scope.match_globals_bound?
|
|
377
|
-
return scope
|
|
378
|
-
|
|
395
|
+
return scope.forget_match_globals if entry_forgets?(scope, block_node, call_node)
|
|
396
|
+
|
|
397
|
+
FreshFrameBlocks.deferred_constructor?(block_node, call_node, scope) ? scope.untyped_match_globals : scope
|
|
398
|
+
end
|
|
379
399
|
|
|
380
|
-
|
|
400
|
+
def entry_forgets?(scope, block_node, call_node)
|
|
401
|
+
FreshFrameBlocks.proc_body?(block_node, call_node, scope) ||
|
|
402
|
+
scope.match_rebinding_closure? || entry_may_match?(block_node.body, scope, call_node) ||
|
|
403
|
+
(call_node.is_a?(Prism::CallNode) && operands_may_rebind?(call_node, scope))
|
|
381
404
|
end
|
|
382
405
|
|
|
383
406
|
def entry_may_match?(body, scope, call_node)
|
|
@@ -386,7 +409,7 @@ module Rigor
|
|
|
386
409
|
|
|
387
410
|
body.is_a?(Prism::Node) && scan(body, scope, base: true)
|
|
388
411
|
end
|
|
389
|
-
private_class_method :entry_may_match?
|
|
412
|
+
private_class_method :frame_entry, :entry_forgets?, :entry_may_match?
|
|
390
413
|
end
|
|
391
414
|
end
|
|
392
415
|
end
|
|
@@ -7,7 +7,7 @@ module Rigor
|
|
|
7
7
|
# Canonical RBS-core aliases shipped by `core/builtin.rbs` whose body is `<Nominal> | _DuckType`.
|
|
8
8
|
# Matching an overload against an Integer literal should pick the `(int) -> Array[Elem]` body over
|
|
9
9
|
# the `(string) -> String` body because Integer satisfies `int`'s strict arm and not `string`'s.
|
|
10
|
-
# The translator
|
|
10
|
+
# The translator reads each alias's interface arm as `Dynamic[Top]` (interfaces are not structurally matched
|
|
11
11
|
# yet), so a dedicated pass 1.5 between strict and gradual consults this map to pick the alias
|
|
12
12
|
# whose strict arm matches.
|
|
13
13
|
#
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The positional argument lists a call with a precise union argument stands for (#2379): one per combination
|
|
9
|
+
# of the union arguments' members, since the runtime selects an overload per value. `RbsDispatch` selects an
|
|
10
|
+
# overload for each list and joins their returns.
|
|
11
|
+
module ArgumentDistribution
|
|
12
|
+
# The most lists {.lists} spells out when more than one argument is a union. One union argument is linear in
|
|
13
|
+
# its members and is not capped.
|
|
14
|
+
LIMIT = 8
|
|
15
|
+
private_constant :LIMIT
|
|
16
|
+
|
|
17
|
+
# {.lists}' answer past {LIMIT}: the call's overloads cannot be read per value, and it answers `Dynamic[top]`.
|
|
18
|
+
OVER_LIMIT = :over_limit
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# The lists, nil when no argument is a union, or {OVER_LIMIT}.
|
|
23
|
+
def lists(arg_types)
|
|
24
|
+
unions = arg_types.count { |arg| arg.is_a?(Type::Union) }
|
|
25
|
+
return nil if unions.zero?
|
|
26
|
+
|
|
27
|
+
if unions > 1
|
|
28
|
+
count = arg_types.reduce(1) { |product, arg| arg.is_a?(Type::Union) ? product * arg.members.size : product }
|
|
29
|
+
return OVER_LIMIT if count > LIMIT
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
arg_types.reduce([[]]) do |partials, arg|
|
|
33
|
+
members = arg.is_a?(Type::Union) ? arg.members : [arg]
|
|
34
|
+
partials.flat_map { |partial| members.map { |member| partial + [member] } }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "iterator_dispatch"
|
|
5
|
+
require_relative "core_enumerable_receiver"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -37,12 +38,12 @@ module Rigor
|
|
|
37
38
|
module BlockFolding
|
|
38
39
|
module_function
|
|
39
40
|
|
|
40
|
-
FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :take_while].freeze
|
|
41
|
+
FILTER_KEEP_ON_TRUTHY = Set[:select, :filter, :find_all, :take_while].freeze
|
|
41
42
|
FILTER_KEEP_ON_FALSEY = Set[:reject, :drop_while].freeze
|
|
42
43
|
|
|
43
|
-
# The filter methods `Hash` defines itself, returning a Hash. `
|
|
44
|
-
# are Enumerable's and return an Array of `[key, value]` pairs; `Set` and
|
|
45
|
-
#
|
|
44
|
+
# The filter methods `Hash` defines itself, returning a Hash. `find_all` / `take_while` /
|
|
45
|
+
# `drop_while` on a Hash are Enumerable's and return an Array of `[key, value]` pairs; `Set` and
|
|
46
|
+
# `Range` define none of the six, so all of theirs return an Array too.
|
|
46
47
|
HASH_RETURNING_FILTERS = Set[:select, :filter, :reject].freeze
|
|
47
48
|
|
|
48
49
|
# `filter_receiver_kind`'s answer for a nominal receiver; an unlisted class declines.
|
|
@@ -52,21 +53,22 @@ module Rigor
|
|
|
52
53
|
|
|
53
54
|
PREDICATE_METHODS = Set[:all?, :any?, :none?].freeze
|
|
54
55
|
|
|
55
|
-
# Methods whose answer is `nil` when the block always returns Ruby-falsey — `find` / `detect`
|
|
56
|
-
# short-circuit to nil when nothing matches, `find_index` / `index` likewise.
|
|
57
|
-
# on the falsey side
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
# Methods whose answer is `nil` when the block always returns Ruby-falsey — `find` / `detect` /
|
|
57
|
+
# `rfind` short-circuit to nil when nothing matches, `find_index` / `index` / `rindex` likewise.
|
|
58
|
+
# These methods only fold on the falsey side here; the truthy-block side requires per-position
|
|
59
|
+
# analysis.
|
|
60
|
+
FALSEY_BLOCK_NIL_METHODS = Set[:find, :detect, :rfind, :find_index, :index, :rindex].freeze
|
|
61
|
+
ARRAY_ONLY_FALSEY_NIL_METHODS = Set[:rfind, :index, :rindex].freeze
|
|
61
62
|
|
|
62
63
|
# Block-taking `count` returns the number of elements for which the block is truthy. With a
|
|
63
64
|
# Constant-falsey block the answer is unconditionally `Constant[0]`; with a Constant-truthy block on
|
|
64
65
|
# a finitely-sized receiver it is `Constant[size]`.
|
|
65
66
|
COUNT_METHOD = :count
|
|
66
67
|
|
|
67
|
-
# `min_by` / `max_by` pick
|
|
68
|
-
# but never WHETHER one is returned: only an empty receiver answers `nil
|
|
69
|
-
|
|
68
|
+
# `min_by` / `max_by` / `minmax_by` pick edge elements by the block's key, so the block's value
|
|
69
|
+
# decides WHICH element(s) but never WHETHER one is returned: only an empty receiver answers `nil`
|
|
70
|
+
# (or `[nil, nil]` for `minmax_by`).
|
|
71
|
+
EDGE_BY_METHODS = Set[:min_by, :max_by, :minmax_by].freeze
|
|
70
72
|
|
|
71
73
|
# `context.block_type` is the inferred return type of the call's block; `nil` means "no block at the
|
|
72
74
|
# call site" and disqualifies every rule here.
|
|
@@ -76,32 +78,44 @@ module Rigor
|
|
|
76
78
|
args = context.args
|
|
77
79
|
block_type = context.block_type
|
|
78
80
|
return nil if receiver.nil? || block_type.nil?
|
|
79
|
-
return fold_edge_by(receiver, args) if EDGE_BY_METHODS.include?(method_name)
|
|
81
|
+
return fold_edge_by(receiver, method_name, args) if EDGE_BY_METHODS.include?(method_name)
|
|
80
82
|
|
|
83
|
+
# Issue #3242 — the predicate, `find`-family and `count` folds read no receiver shape, so a class that
|
|
84
|
+
# may define the method itself declines (the filter folds already do, through `filter_receiver_kind`).
|
|
81
85
|
truthiness = constant_truthiness(block_type)
|
|
82
|
-
return nil if truthiness.nil?
|
|
86
|
+
return nil if truthiness.nil? || CoreEnumerableReceiver.project_receiver?(receiver)
|
|
83
87
|
|
|
84
88
|
if PREDICATE_METHODS.include?(method_name)
|
|
85
89
|
fold_predicate(receiver, method_name, truthiness, args)
|
|
86
90
|
elsif filter_method?(method_name)
|
|
87
91
|
fold_filter(receiver, method_name, truthiness)
|
|
88
92
|
elsif FALSEY_BLOCK_NIL_METHODS.include?(method_name)
|
|
89
|
-
fold_falsey_nil_short_circuit(method_name, truthiness, args)
|
|
93
|
+
fold_falsey_nil_short_circuit(receiver, method_name, truthiness, args)
|
|
90
94
|
elsif method_name == COUNT_METHOD
|
|
91
95
|
fold_count(receiver, truthiness, args)
|
|
92
96
|
end
|
|
93
97
|
end
|
|
94
98
|
|
|
95
|
-
# Issue #1333 — `min_by` / `max_by` with a block and no count on a
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
# cannot say which, so the answer is the whole element union. An empty receiver, one whose size is
|
|
100
|
-
# not static (`Array[T]`), and the count form (`min_by(2) { … }`, an Array) decline to RBS.
|
|
101
|
-
def fold_edge_by(receiver, args)
|
|
99
|
+
# Issue #1333 — `min_by` / `max_by` / `minmax_by` with a block and no count on a Tuple or constant
|
|
100
|
+
# integer `Range` answer the receiver's element type without `nil` when non-empty (or `[nil, nil]`
|
|
101
|
+
# for an empty `minmax_by`), avoiding false `nil` receivers on `min_by` / `minmax_by` calls.
|
|
102
|
+
def fold_edge_by(receiver, method_name, args)
|
|
102
103
|
return nil unless args.empty?
|
|
103
|
-
return nil unless receiver_emptiness(receiver) == :non_empty
|
|
104
104
|
|
|
105
|
+
emptiness = receiver_emptiness(receiver)
|
|
106
|
+
if method_name == :minmax_by && emptiness == :empty
|
|
107
|
+
nil_const = Type::Combinator.constant_of(nil)
|
|
108
|
+
return Type::Combinator.tuple_of(nil_const, nil_const)
|
|
109
|
+
end
|
|
110
|
+
return nil unless emptiness == :non_empty
|
|
111
|
+
|
|
112
|
+
element = edge_by_element_type(receiver)
|
|
113
|
+
return nil if element.nil?
|
|
114
|
+
|
|
115
|
+
method_name == :minmax_by ? Type::Combinator.tuple_of(element, element) : element
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def edge_by_element_type(receiver)
|
|
105
119
|
case receiver
|
|
106
120
|
when Type::Tuple then Type::Combinator.union(*receiver.elements)
|
|
107
121
|
when Type::Constant then IteratorDispatch.element_type_of(receiver) if receiver.value.is_a?(Range)
|
|
@@ -288,13 +302,13 @@ module Rigor
|
|
|
288
302
|
end
|
|
289
303
|
end
|
|
290
304
|
|
|
291
|
-
# `find` / `detect` / `find_index` / `index`
|
|
292
|
-
# provably falsey.
|
|
293
|
-
#
|
|
294
|
-
|
|
295
|
-
def fold_falsey_nil_short_circuit(_method_name, truthiness, args)
|
|
305
|
+
# `find` / `detect` / `find_index` (and Array-only `rfind` / `index` / `rindex`) short-circuit to
|
|
306
|
+
# nil when the block is provably falsey. Calls with a positional argument decline so the RBS tier
|
|
307
|
+
# answers value-search calls.
|
|
308
|
+
def fold_falsey_nil_short_circuit(receiver, method_name, truthiness, args)
|
|
296
309
|
return nil unless args.empty?
|
|
297
310
|
return nil unless truthiness == :falsey
|
|
311
|
+
return nil if ARRAY_ONLY_FALSEY_NIL_METHODS.include?(method_name) && filter_receiver_kind(receiver) != :array
|
|
298
312
|
|
|
299
313
|
Type::Combinator.constant_of(nil)
|
|
300
314
|
end
|
|
@@ -317,7 +331,7 @@ module Rigor
|
|
|
317
331
|
end
|
|
318
332
|
|
|
319
333
|
# Returns the receiver's known finite element count, or nil when the carrier does not pin a size.
|
|
320
|
-
# Tuple and HashShape are pinned by construction
|
|
334
|
+
# Tuple and HashShape are pinned by construction, and a `Constant` Range by its bounds. Other
|
|
321
335
|
# shapes (Array[T], Range[T], Nominal) decline so the RBS tier widens.
|
|
322
336
|
def finite_size(receiver)
|
|
323
337
|
case receiver
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# Issue #2686 — `Rigor::Plugin::Base.node_rule(node_class) { |node, ...| }` yields instances of the class it is
|
|
9
|
+
# given, which RBS cannot say: `[N < ::Prism::Node] (singleton(N))` does not resolve (RBS has no
|
|
10
|
+
# `singleton(<type variable>)`). `sig/rigor/plugin/base.rbs` declares per-class overloads for the classes
|
|
11
|
+
# the bundled plugins register and a `Prism::Node` fallback. Read through the overloads alone, two calls bind
|
|
12
|
+
# `node` wrong:
|
|
13
|
+
#
|
|
14
|
+
# - a class with no overload of its own (`node_rule(Prism::IfNode)`) takes the fallback, so `node` is
|
|
15
|
+
# `Prism::Node` and `node.predicate` reported `call.undefined-method`;
|
|
16
|
+
# - a class argument the engine cannot read (`def self.reg(k) = node_rule(k) { ... }`) matches every overload
|
|
17
|
+
# gradually, and the block probe took the first, `Prism::CallNode`.
|
|
18
|
+
#
|
|
19
|
+
# This rebinds the block's first parameter from the class argument:
|
|
20
|
+
#
|
|
21
|
+
# - `singleton(X)` binds `X`, and a union of class objects the union of their instances;
|
|
22
|
+
# - any other argument (an untyped parameter, a `Class` value) binds `Dynamic[top]`, so the body is not
|
|
23
|
+
# checked against a class the call may not register.
|
|
24
|
+
#
|
|
25
|
+
# The rest of the block's parameters keep the overload's binding; every overload declares them alike.
|
|
26
|
+
#
|
|
27
|
+
# A per-class overload set generated from Prism's node classes was the alternative. It was declined: the
|
|
28
|
+
# gemspec admits `prism >= 1.0, < 2.0`, and a generated set names classes later minors added, which an older
|
|
29
|
+
# Prism's RBS does not declare, so loading Rigor's own `sig/` would fail there.
|
|
30
|
+
#
|
|
31
|
+
# Only the declaration Rigor owns is read, by the method's RBS owner, so a project's own `node_rule` is not.
|
|
32
|
+
module ClassKeyedBlockParams
|
|
33
|
+
OWNER = "::Rigor::Plugin::Base"
|
|
34
|
+
METHOD = :node_rule
|
|
35
|
+
private_constant :OWNER, :METHOD
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# @param method_definition — the RBS declaration the probe read.
|
|
40
|
+
# @param params — the block parameters the overloads bound.
|
|
41
|
+
# @return the block parameters, the first rebound for `Base.node_rule`.
|
|
42
|
+
def rebind(method_definition, method_name, args, params)
|
|
43
|
+
return params unless method_name == METHOD
|
|
44
|
+
return params unless method_definition.defined_in.to_s == OWNER
|
|
45
|
+
|
|
46
|
+
[instance_of(args.first), *params.drop(1)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The instances the class argument names, or `Dynamic[top]`.
|
|
50
|
+
def instance_of(arg)
|
|
51
|
+
case arg
|
|
52
|
+
when Type::Singleton
|
|
53
|
+
Type::Combinator.nominal_of(arg.class_name)
|
|
54
|
+
when Type::Union
|
|
55
|
+
return Type::Combinator.untyped unless arg.members.all?(Type::Singleton)
|
|
56
|
+
|
|
57
|
+
Type::Combinator.union(*arg.members.map { |member| Type::Combinator.nominal_of(member.class_name) })
|
|
58
|
+
else
|
|
59
|
+
Type::Combinator.untyped
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -192,8 +192,8 @@ module Rigor
|
|
|
192
192
|
|
|
193
193
|
# #2115 — the numeric twin of `STRING_FOLD_BYTE_LIMIT`. A folded Integer (and each Integer part of a
|
|
194
194
|
# folded Rational / Complex) is at most this many bits, the largest bit length whose decimal
|
|
195
|
-
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
-
# (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
195
|
+
# rendering still fits in `STRING_FOLD_BYTE_LIMIT` digits: `2 ** 13_605` folds,
|
|
196
|
+
# `2 ** 13_606` (bit length 13_607) does not. Computing `2 ** 100_000_000` takes Ruby milliseconds, but
|
|
197
197
|
# rendering it in decimal — which describing, sorting and printing the `Constant` does — takes
|
|
198
198
|
# minutes. `**` / `pow` / `<<` / `>>` also check their operands before invoking, so the big value
|
|
199
199
|
# is never computed.
|
|
@@ -380,7 +380,7 @@ module Rigor
|
|
|
380
380
|
# Normalises an input type into one of:
|
|
381
381
|
# - `Array<Object>` for a `Constant` (1-element) or `Union[Constant…]` (n-element) — concrete values
|
|
382
382
|
# to enumerate.
|
|
383
|
-
# - `Type::IntegerRange` — bounded interval.
|
|
383
|
+
# - `Type::IntegerRange` / `Type::FloatRange` — bounded interval.
|
|
384
384
|
# - `nil` — the input shape is not foldable.
|
|
385
385
|
def numeric_set_of(type)
|
|
386
386
|
case type
|