rigortype 0.3.8 → 0.3.9
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 +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -21,10 +21,13 @@ module Rigor
|
|
|
21
21
|
module_function
|
|
22
22
|
|
|
23
23
|
def reorder(overloads, self_type:, environment:)
|
|
24
|
-
return overloads if environment.nil?
|
|
24
|
+
return overloads if environment.nil? || overloads.size <= 1
|
|
25
25
|
|
|
26
26
|
self_class_name = self_type_class_name(self_type)
|
|
27
27
|
return overloads if self_class_name.nil?
|
|
28
|
+
# A list with no affinity arm behind a non-affinity arm is already in order -- the common case,
|
|
29
|
+
# answered without the partition's three arrays (#775).
|
|
30
|
+
return overloads unless out_of_order?(overloads, self_class_name, environment)
|
|
28
31
|
|
|
29
32
|
affinity, other = overloads.partition do |mt|
|
|
30
33
|
overload_param_classes_in_ancestry?(mt, self_class_name, environment)
|
|
@@ -41,6 +44,19 @@ module Rigor
|
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
# True when some affinity arm follows a non-affinity arm, i.e. the stable partition would move it.
|
|
48
|
+
def out_of_order?(overloads, self_class_name, environment)
|
|
49
|
+
other_seen = false
|
|
50
|
+
overloads.each do |mt|
|
|
51
|
+
if overload_param_classes_in_ancestry?(mt, self_class_name, environment)
|
|
52
|
+
return true if other_seen
|
|
53
|
+
else
|
|
54
|
+
other_seen = true
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
44
60
|
# `RBS::Types::UntypedFunction` (a `(?)` method type, e.g. core's `Proc#call`) declares no
|
|
45
61
|
# parameters and exposes none of the per-arity accessors. It has no param classes to compare
|
|
46
62
|
# against the receiver, so it carries no affinity either way.
|
|
@@ -48,10 +64,15 @@ module Rigor
|
|
|
48
64
|
fun = method_type.type
|
|
49
65
|
return false unless fun.respond_to?(:required_positionals)
|
|
50
66
|
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
required = fun.required_positionals
|
|
68
|
+
optional = fun.optional_positionals
|
|
69
|
+
trailing = fun.trailing_positionals
|
|
70
|
+
return false if required.empty? && optional.empty? && trailing.empty?
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
# The three lists are checked in place rather than concatenated (#775).
|
|
73
|
+
required.all? { |param| param_class_in_ancestry?(param.type, self_class_name, environment) } &&
|
|
74
|
+
optional.all? { |param| param_class_in_ancestry?(param.type, self_class_name, environment) } &&
|
|
75
|
+
trailing.all? { |param| param_class_in_ancestry?(param.type, self_class_name, environment) }
|
|
55
76
|
end
|
|
56
77
|
|
|
57
78
|
# Walks Optional and Union one level so `(Numeric?)` and `(Integer | Float)` still classify when
|
|
@@ -58,7 +58,6 @@ module Rigor
|
|
|
58
58
|
CONSTANT_FOLD_BIT_CAP = 256
|
|
59
59
|
private_constant :CONSTANT_FOLD_ELEMENT_CAP, :CONSTANT_FOLD_BIT_CAP
|
|
60
60
|
|
|
61
|
-
# @return [Rigor::Type, nil]
|
|
62
61
|
def try_dispatch(context)
|
|
63
62
|
return nil unless REDUCE_METHODS.include?(context.method_name)
|
|
64
63
|
return nil if context.block_type
|
|
@@ -84,9 +83,8 @@ module Rigor
|
|
|
84
83
|
# Executes the reduction on real constant values, or returns nil to decline. The caller falls
|
|
85
84
|
# through to the nominal fold on a nil return, so every guard here is precision-additive only.
|
|
86
85
|
#
|
|
87
|
-
# @param seed
|
|
86
|
+
# @param seed — the optional seed type; only a foldable `Constant` seed is
|
|
88
87
|
# honoured, any other seed declines.
|
|
89
|
-
# @return [Rigor::Type::Constant, nil]
|
|
90
88
|
def try_constant_reduce(receiver, operator, seed)
|
|
91
89
|
return nil unless CONSTANT_FOLD_OPERATORS.include?(operator)
|
|
92
90
|
|
|
@@ -102,8 +100,6 @@ module Rigor
|
|
|
102
100
|
# Extracts the receiver's elements as a Ruby Array of foldable constant values, or nil to decline.
|
|
103
101
|
# Checks the size cap BEFORE enumerating so an unbounded / huge `Constant[Range]` (e.g.
|
|
104
102
|
# `(1..1_000_000)`) never calls `to_a`.
|
|
105
|
-
#
|
|
106
|
-
# @return [Array, nil]
|
|
107
103
|
def constant_members(receiver)
|
|
108
104
|
case receiver
|
|
109
105
|
when Type::Constant
|
|
@@ -140,7 +136,7 @@ module Rigor
|
|
|
140
136
|
elements.map(&:value)
|
|
141
137
|
end
|
|
142
138
|
|
|
143
|
-
# @return
|
|
139
|
+
# @return `[seed_value, present?]`. A nil seed type yields `[nil, false]`
|
|
144
140
|
# (no-seed form). A foldable `Constant` seed yields `[value, true]`. Any other seed yields
|
|
145
141
|
# `[nil, false]` so the caller can decline.
|
|
146
142
|
def constant_seed(seed)
|
|
@@ -153,8 +149,6 @@ module Rigor
|
|
|
153
149
|
# Runs the actual reduction, guarded by the rescue harness and the magnitude cap. Empty-collection
|
|
154
150
|
# semantics match Ruby: `[].reduce(s, :op) == s` (seed form) and `[].reduce(:op) == nil` (no-seed
|
|
155
151
|
# form → declines so the nominal fold's no-nil contract stands; see `fold_result`).
|
|
156
|
-
#
|
|
157
|
-
# @return [Rigor::Type::Constant, nil]
|
|
158
152
|
def execute_constant_reduce(members, operator, seed_value, seed_present)
|
|
159
153
|
result =
|
|
160
154
|
if seed_present
|
|
@@ -27,7 +27,7 @@ module Rigor
|
|
|
27
27
|
module SetFolding
|
|
28
28
|
module_function
|
|
29
29
|
|
|
30
|
-
# @return
|
|
30
|
+
# @return folded result, or nil to defer.
|
|
31
31
|
def try_dispatch(context)
|
|
32
32
|
receiver = context.receiver
|
|
33
33
|
method_name = context.method_name
|
|
@@ -186,7 +186,7 @@ module Rigor
|
|
|
186
186
|
itself: :shape_self
|
|
187
187
|
}.freeze
|
|
188
188
|
|
|
189
|
-
# @return
|
|
189
|
+
# @return the precise element/value type, or `nil` to defer to the next
|
|
190
190
|
# dispatcher tier.
|
|
191
191
|
# Per-carrier dispatch table. Adding a new carrier here is a one-row change; the helper methods
|
|
192
192
|
# stay private. Anonymous Type subclasses are not expected.
|
|
@@ -31,8 +31,7 @@ module Rigor
|
|
|
31
31
|
|
|
32
32
|
module_function
|
|
33
33
|
|
|
34
|
-
# @
|
|
35
|
-
# @return [Rigor::Type, nil] `Nominal[C]` for `C.instance`, or nil to decline.
|
|
34
|
+
# @return `Nominal[C]` for `C.instance`, or nil to decline.
|
|
36
35
|
def try_dispatch(context)
|
|
37
36
|
return nil unless context.method_name == SELECTOR
|
|
38
37
|
return nil unless context.args.empty?
|
|
@@ -45,7 +45,7 @@ module Rigor
|
|
|
45
45
|
# Issue #595 / #525 — the shared materialisation test; see {StructMaterialization}.
|
|
46
46
|
extend StructMaterialization
|
|
47
47
|
|
|
48
|
-
# @return
|
|
48
|
+
# @return the folded result, or nil to defer.
|
|
49
49
|
def try_dispatch(context)
|
|
50
50
|
receiver = context.receiver
|
|
51
51
|
|
|
@@ -344,10 +344,9 @@ module Rigor
|
|
|
344
344
|
# local's true member state at every later read on the path. A non-fold-safe local is left untouched
|
|
345
345
|
# (its reads do not fold, so the binding is never consulted for folding).
|
|
346
346
|
#
|
|
347
|
-
# @param call_node
|
|
348
|
-
# @param assigned_type
|
|
349
|
-
# @
|
|
350
|
-
# @return [Rigor::Scope] the (possibly) rebound scope
|
|
347
|
+
# @param call_node — the `local.member = v` call
|
|
348
|
+
# @param assigned_type — the setter's assigned value type (the call's own result)
|
|
349
|
+
# @return the (possibly) rebound scope
|
|
351
350
|
def apply_setter_writeback(call_node:, assigned_type:, scope:)
|
|
352
351
|
return scope if scope.nil? || assigned_type.nil?
|
|
353
352
|
|
|
@@ -4,6 +4,8 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../../type"
|
|
6
6
|
require_relative "../../source/constant_path"
|
|
7
|
+
require_relative "../../source/node_children"
|
|
8
|
+
require_relative "../struct_fold_safety"
|
|
7
9
|
|
|
8
10
|
module Rigor
|
|
9
11
|
module Inference
|
|
@@ -25,6 +27,10 @@ module Rigor
|
|
|
25
27
|
module StructMaterialization
|
|
26
28
|
module_function
|
|
27
29
|
|
|
30
|
+
# Nodes one factory-body scan may visit before it gives up and refuses. A factory is a handful of
|
|
31
|
+
# nodes; the bound exists so a pathological body cannot turn a per-call-site gate into a walk.
|
|
32
|
+
FACTORY_BODY_SCAN_BUDGET = 200
|
|
33
|
+
|
|
28
34
|
# Issue #595 / #525 — the ONE materialisation test. Both places that must answer "was this receiver
|
|
29
35
|
# expression's struct newly built?" go through it: this module's direct member-read gate above, and
|
|
30
36
|
# `ExpressionTyper`'s caller-side `:self`-grant arm. They had forked answers once (the grant arm was
|
|
@@ -32,20 +38,95 @@ module Rigor
|
|
|
32
38
|
# composed `x.dup_self.with(indent: 9).shout` shape survived — a correctly whitelisted `.with` fold
|
|
33
39
|
# firing off a receiver the other gate should never have called fresh.
|
|
34
40
|
#
|
|
35
|
-
#
|
|
36
|
-
# @param
|
|
37
|
-
# @param receiver [Rigor::Type, nil] the carrier the expression produced.
|
|
38
|
-
# @param scope [Rigor::Scope, nil]
|
|
41
|
+
# @param node — the receiver EXPRESSION.
|
|
42
|
+
# @param receiver — the carrier the expression produced.
|
|
39
43
|
def materialization_call?(node, receiver, scope)
|
|
40
44
|
return false unless node.is_a?(Prism::CallNode)
|
|
41
45
|
|
|
42
46
|
case node.name
|
|
43
47
|
when :new, :[] then struct_class_expression?(node.receiver, scope)
|
|
44
48
|
when :with then !hand_written_with?(receiver, scope)
|
|
49
|
+
else fresh_factory_call?(node, scope)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Issue #599 — the FACTORY-METHOD idiom, the one shape #595's whitelist knowingly paid for as a
|
|
54
|
+
# missed error: `def build = Pair.new("hi", [1, 2])` then `build.items`. The whitelist could not see
|
|
55
|
+
# through `build`, so the chain declined and a typo on it went unreported.
|
|
56
|
+
#
|
|
57
|
+
# It is recovered by asking the callee, under two bounds that keep the answer cheap and fail-closed.
|
|
58
|
+
#
|
|
59
|
+
# 1. RESOLUTION is one table read on a shape whose target cannot be an arbitrary object: a
|
|
60
|
+
# receiverless send resolved through the confidence-gated top-level def table, or `Const.name`
|
|
61
|
+
# resolved through the singleton-side ancestor walk. An INSTANCE-side receiver is refused
|
|
62
|
+
# outright — `x.dup_self` is the #595 bug shape, and what `x` holds at the call is exactly what
|
|
63
|
+
# this gate has no way to know.
|
|
64
|
+
# 2. ACCEPTANCE is decided on the callee's RETURN POSITION, not on a self-alias scan of its body.
|
|
65
|
+
# "Cannot return `self`" is not the property freshness needs: the factory's `self` is the module
|
|
66
|
+
# or `main`, never the struct, while `def get = GLOBAL` over a mutated constant returns a
|
|
67
|
+
# long-lived instance with no `self` anywhere in it. What the gate needs is that the returned
|
|
68
|
+
# object was built BY THIS CALL, so the body's tail must itself be a materialisation
|
|
69
|
+
# ({#fresh_materialization_tail?}) and no `return` may hand back anything else. That subsumes the
|
|
70
|
+
# self-alias refusal — a body returning `self` has a `SelfNode` tail, not a `.new` — while
|
|
71
|
+
# admitting the idiom the issue is about.
|
|
72
|
+
def fresh_factory_call?(node, scope)
|
|
73
|
+
return false if scope.nil?
|
|
74
|
+
|
|
75
|
+
body = factory_def_body(node, scope)
|
|
76
|
+
return false unless body.is_a?(Prism::StatementsNode)
|
|
77
|
+
|
|
78
|
+
fresh_materialization_tail?(body.body.last, scope) && !early_return?(body)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The two CHEAPLY RESOLVABLE callee shapes, each a single table read against the frozen discovery
|
|
82
|
+
# index. `bindable_top_level_def_for` is the confidence-gated accessor — the only one the inference
|
|
83
|
+
# may bind through — so a call inside a block whose `self` is unmodelled declines here as it does
|
|
84
|
+
# everywhere else. Anything else, an instance-side receiver above all, resolves to nil.
|
|
85
|
+
def factory_def_body(node, scope)
|
|
86
|
+
def_node =
|
|
87
|
+
case node.receiver
|
|
88
|
+
when nil then scope.bindable_top_level_def_for(node.name)
|
|
89
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
90
|
+
owner = Source::ConstantPath.qualified_name_or_nil(node.receiver)
|
|
91
|
+
owner && scope.singleton_def_through_ancestors(owner, node.name).first
|
|
92
|
+
end
|
|
93
|
+
def_node&.body
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# A materialisation written in the CALLEE's body, judged syntactically. Deliberately narrower than
|
|
97
|
+
# {#struct_class_expression?}: that predicate's local-variable arm reads the binding off the scope it
|
|
98
|
+
# is handed, which is the CALLER's — a local of the same name in the callee body is a different
|
|
99
|
+
# binding entirely, so the arm is dropped rather than answered from the wrong scope.
|
|
100
|
+
def fresh_materialization_tail?(node, scope)
|
|
101
|
+
return false unless node.is_a?(Prism::CallNode)
|
|
102
|
+
return false unless %i[new []].include?(node.name)
|
|
103
|
+
|
|
104
|
+
case node.receiver
|
|
105
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
106
|
+
name = Source::ConstantPath.qualified_name_or_nil(node.receiver)
|
|
107
|
+
!name.nil? && !scope.struct_member_layout(name).nil?
|
|
108
|
+
when Prism::CallNode then inline_struct_factory?(node.receiver)
|
|
45
109
|
else false
|
|
46
110
|
end
|
|
47
111
|
end
|
|
48
112
|
|
|
113
|
+
# An explicit `return` anywhere in the body hands back an expression the tail check never saw, so the
|
|
114
|
+
# body is refused rather than scanned arm by arm. Nested `def` / `class` / `module` bodies are a
|
|
115
|
+
# different method's returns; a block's `return` is this method's, so the walk descends into it. The
|
|
116
|
+
# visit budget is the fail-closed bound: a body too large to scan is not proven, and unproven is not
|
|
117
|
+
# fresh.
|
|
118
|
+
def early_return?(node, budget = [FACTORY_BODY_SCAN_BUDGET])
|
|
119
|
+
return true if node.is_a?(Prism::ReturnNode)
|
|
120
|
+
return false if Inference::StructFoldSafety.scope_boundary?(node)
|
|
121
|
+
|
|
122
|
+
budget[0] -= 1
|
|
123
|
+
return true if budget[0].negative?
|
|
124
|
+
|
|
125
|
+
found = false
|
|
126
|
+
node.rigor_each_child { |child| found ||= early_return?(child, budget) }
|
|
127
|
+
found
|
|
128
|
+
end
|
|
129
|
+
|
|
49
130
|
# `.with` copies the receiver into a new instance — unless the struct wrote its own, which is free to
|
|
50
131
|
# return `self` and would reopen the very hole above.
|
|
51
132
|
#
|
|
@@ -68,8 +68,7 @@ module Rigor
|
|
|
68
68
|
|
|
69
69
|
module_function
|
|
70
70
|
|
|
71
|
-
# @
|
|
72
|
-
# @return [Rigor::Type, nil] the receiver-independent return type, or nil to decline.
|
|
71
|
+
# @return the receiver-independent return type, or nil to decline.
|
|
73
72
|
def try_dispatch(context)
|
|
74
73
|
return nil unless context.receiver.is_a?(Type::Dynamic)
|
|
75
74
|
|
|
@@ -24,6 +24,7 @@ require_relative "method_dispatcher/array_to_h_folding"
|
|
|
24
24
|
require_relative "method_dispatcher/file_folding"
|
|
25
25
|
require_relative "method_dispatcher/shellwords_folding"
|
|
26
26
|
require_relative "method_dispatcher/math_folding"
|
|
27
|
+
require_relative "method_dispatcher/random_folding"
|
|
27
28
|
require_relative "method_dispatcher/time_folding"
|
|
28
29
|
require_relative "method_dispatcher/regexp_folding"
|
|
29
30
|
require_relative "method_dispatcher/cgi_folding"
|
|
@@ -54,19 +55,18 @@ module Rigor
|
|
|
54
55
|
module MethodDispatcher # rubocop:disable Metrics/ModuleLength
|
|
55
56
|
module_function
|
|
56
57
|
|
|
57
|
-
# @param receiver_type
|
|
58
|
+
# @param receiver_type — type of the receiver expression, or
|
|
58
59
|
# `nil` for an implicit-self call.
|
|
59
|
-
# @param
|
|
60
|
-
# @param
|
|
61
|
-
# @param block_type [Rigor::Type, nil] inferred return type of the
|
|
60
|
+
# @param arg_types — positional argument types.
|
|
61
|
+
# @param block_type — inferred return type of the
|
|
62
62
|
# accompanying `do ... end` / `{ ... }` block (Slice 6 phase C
|
|
63
63
|
# sub-phase 2). When non-nil, the dispatcher prefers an
|
|
64
64
|
# overload that declares a block, and binds the method's
|
|
65
65
|
# block-return type variable to `block_type` so a return type
|
|
66
66
|
# like `Array[U]` resolves to `Array[block_type]`.
|
|
67
|
-
# @param environment
|
|
67
|
+
# @param environment — required for
|
|
68
68
|
# RBS-backed dispatch; when nil only constant folding can fire.
|
|
69
|
-
# @return
|
|
69
|
+
# @return inferred result type, or `nil` for "no rule".
|
|
70
70
|
def dispatch(receiver_type:, method_name:, arg_types:,
|
|
71
71
|
block_type: nil, environment: nil,
|
|
72
72
|
call_node: nil, scope: nil)
|
|
@@ -335,10 +335,14 @@ module Rigor
|
|
|
335
335
|
[receiver_type.class_name, :instance]
|
|
336
336
|
when Type::Singleton, Type::StructClass, Type::DataClass
|
|
337
337
|
[receiver_type.class_name, :singleton]
|
|
338
|
-
else
|
|
338
|
+
else NO_DISCOVERED_LOOKUP
|
|
339
339
|
end
|
|
340
340
|
end
|
|
341
341
|
|
|
342
|
+
# The shared "no discovered-method key" pair; callers destructure it (#775).
|
|
343
|
+
NO_DISCOVERED_LOOKUP = [nil, nil].freeze
|
|
344
|
+
private_constant :NO_DISCOVERED_LOOKUP
|
|
345
|
+
|
|
342
346
|
# ADR-5 robustness — returns `Dynamic[Top]` when the receiver is an instance or singleton
|
|
343
347
|
# of a type Rigor synthesized (a missing-namespace module or a referenced-type stub). The
|
|
344
348
|
# stub has no methods, so the call would otherwise reach the user-class fallback and
|
|
@@ -759,6 +763,7 @@ module Rigor
|
|
|
759
763
|
"File" => FileFolding,
|
|
760
764
|
"Shellwords" => ShellwordsFolding,
|
|
761
765
|
"Math" => MathFolding,
|
|
766
|
+
"Random" => RandomFolding,
|
|
762
767
|
"Time" => TimeFolding,
|
|
763
768
|
"Regexp" => RegexpFolding,
|
|
764
769
|
"CGI" => CGIFolding,
|
|
@@ -1350,12 +1355,6 @@ module Rigor
|
|
|
1350
1355
|
# method definition, or selected overload does not provide statically declared block
|
|
1351
1356
|
# parameter types. Callers MUST treat the empty array as "no information"; the binder
|
|
1352
1357
|
# falls back to `Dynamic[Top]` for every parameter slot in that case.
|
|
1353
|
-
#
|
|
1354
|
-
# @param receiver_type [Rigor::Type, nil]
|
|
1355
|
-
# @param method_name [Symbol]
|
|
1356
|
-
# @param arg_types [Array<Rigor::Type>]
|
|
1357
|
-
# @param environment [Rigor::Environment, nil]
|
|
1358
|
-
# @return [Array<Rigor::Type>]
|
|
1359
1358
|
def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil)
|
|
1360
1359
|
return [] if receiver_type.nil?
|
|
1361
1360
|
|
|
@@ -55,14 +55,13 @@ module Rigor
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
class MethodParameterBinder
|
|
58
|
-
# @param
|
|
59
|
-
# @param class_path [String, nil] the qualified name of the class the method is defined in
|
|
58
|
+
# @param class_path — the qualified name of the class the method is defined in
|
|
60
59
|
# (e.g., `"Foo::Bar"`), or `nil` for a top-level `def` outside any class. When `nil` (or
|
|
61
60
|
# when the class is unknown to RBS), every parameter falls back to `Dynamic[Top]`.
|
|
62
|
-
# @param singleton
|
|
61
|
+
# @param singleton — `true` when the def is a singleton method (either `def
|
|
63
62
|
# self.foo` or a `def foo` inside `class << self`); routes the lookup through
|
|
64
63
|
# `RbsLoader#singleton_method`.
|
|
65
|
-
# @param source_path
|
|
64
|
+
# @param source_path — the project-relative path of the file the method is
|
|
66
65
|
# defined in. Used to match ADR-28 path-scoped protocol contracts; `nil` (the default
|
|
67
66
|
# for synthetic / probe scopes) disables the contract tier.
|
|
68
67
|
def initialize(environment:, class_path:, singleton:, source_path: nil)
|
|
@@ -72,8 +71,7 @@ module Rigor
|
|
|
72
71
|
@source_path = source_path
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
# @
|
|
76
|
-
# @return [Hash{Symbol => Rigor::Type}] ordered map from parameter name to bound type.
|
|
74
|
+
# @return ordered map from parameter name to bound type.
|
|
77
75
|
# Anonymous parameters (`*` and `**` without a name) are skipped.
|
|
78
76
|
def bind(def_node)
|
|
79
77
|
slots = collect_slots(def_node.parameters)
|
|
@@ -50,9 +50,7 @@ module Rigor
|
|
|
50
50
|
module MultiTargetBinder
|
|
51
51
|
module_function
|
|
52
52
|
|
|
53
|
-
# @param
|
|
54
|
-
# @param rhs_type [Rigor::Type] type of the right-hand side
|
|
55
|
-
# @return [Hash{Symbol => Rigor::Type}]
|
|
53
|
+
# @param rhs_type — type of the right-hand side
|
|
56
54
|
def bind(target_node, rhs_type)
|
|
57
55
|
bindings = {}
|
|
58
56
|
visit(target_node, rhs_type, bindings)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../type"
|
|
4
|
+
require_relative "content_join"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
# The re-join half of the mutation widening (issue #580): what happens to the SECOND and later
|
|
9
|
+
# content mutations of one collection, once {MutationWidening} has already replaced the literal
|
|
10
|
+
# carrier with a `Nominal`.
|
|
11
|
+
#
|
|
12
|
+
# Split out of {MutationWidening} because it answers a different question. That module decides
|
|
13
|
+
# what a mutation FALSIFIES about a literal shape; this one decides when a carrier is still open
|
|
14
|
+
# enough to LEARN from, which is a provenance question the type system has no field for and this
|
|
15
|
+
# file answers off the carrier itself.
|
|
16
|
+
module MutationRejoin
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
# Re-opens a carrier this seam already widened once, so the SECOND and later stores into the
|
|
20
|
+
# same collection are seen (issue #580).
|
|
21
|
+
#
|
|
22
|
+
# Before this arm the widening was a one-way door: the first content mutation replaced the
|
|
23
|
+
# literal `Tuple` / `HashShape` with a `Nominal`, `widen_for_mutator` had no `Nominal` arm, and
|
|
24
|
+
# `a = []; a.push(1); a.push("s")` closed at `Array[Integer | Dynamic[top]]` with the `String`
|
|
25
|
+
# arm simply absent. The `Dynamic` arm kept that from being a WRONG answer — nothing folds and
|
|
26
|
+
# nothing dispatches off a union carrying it — but the carrier still did not say what the
|
|
27
|
+
# program holds, and every precision lever that would eventually take the gradual arm off had
|
|
28
|
+
# to inherit the missing store.
|
|
29
|
+
#
|
|
30
|
+
# `nil` when the re-join changed nothing, so a non-adder (`pop`, `sort!`) on an already-widened
|
|
31
|
+
# carrier does not rebind the binding at all: a rebind drops the per-slot narrowings `with_local`
|
|
32
|
+
# keys to the old value, and paying that for an identical type is a straight loss.
|
|
33
|
+
def widen_nominal(nominal, method_name, values: :widen, arg_types: MutationWidening::NO_ARG_TYPES)
|
|
34
|
+
return nil unless regrowable_carrier?(nominal)
|
|
35
|
+
|
|
36
|
+
pre_state = values == :widen ? value_pin_widened_args(nominal) : nominal
|
|
37
|
+
rejoined =
|
|
38
|
+
case nominal.class_name
|
|
39
|
+
when "Array"
|
|
40
|
+
return nil unless MutationWidening::ARRAY_MUTATORS.include?(method_name)
|
|
41
|
+
|
|
42
|
+
MutationWidening.join_added_elements(pre_state, method_name, arg_types,
|
|
43
|
+
pinned_evidence(ContentJoin.collection_element_types(pre_state)))
|
|
44
|
+
when "Hash"
|
|
45
|
+
return nil unless MutationWidening::HASH_MUTATORS.include?(method_name)
|
|
46
|
+
|
|
47
|
+
seed_keys, seed_values = ContentJoin.hash_shape_key_values(pre_state)
|
|
48
|
+
MutationWidening.join_added_pairs(pre_state, method_name, arg_types,
|
|
49
|
+
[pinned_evidence(seed_keys), pinned_evidence(seed_values)])
|
|
50
|
+
end
|
|
51
|
+
rejoined = keep_precise_parameters(pre_state, rejoined)
|
|
52
|
+
rejoined == nominal ? nil : rejoined
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Re-grows only the parameters that were already gradual, restoring every other one.
|
|
56
|
+
#
|
|
57
|
+
# {#regrowable_carrier?} asks the carrier as a whole, but a `Hash` has two parameters and they
|
|
58
|
+
# can disagree: `Hash.new(0)` is `Hash[Dynamic[top], Integer]` — an open key side over a value
|
|
59
|
+
# side the default-arg fold PROVED. Re-joining both put the gradual floor on the value side and
|
|
60
|
+
# cost the counter idiom (`h = Hash.new(0); h[:x] += 1; h[:x]`) its `Integer` for a
|
|
61
|
+
# `Dynamic[top] | Integer`, a precision loss for evidence the key side alone was missing.
|
|
62
|
+
#
|
|
63
|
+
# The restored side comes from the pin-widened pre-state rather than the original carrier, so a
|
|
64
|
+
# slot-rewriting mutator still falsifies the pinning it falsifies.
|
|
65
|
+
def keep_precise_parameters(pre_state, rejoined)
|
|
66
|
+
return rejoined unless rejoined.is_a?(Type::Nominal) && rejoined.type_args.size == pre_state.type_args.size
|
|
67
|
+
|
|
68
|
+
args = pre_state.type_args.each_with_index.map do |arg, i|
|
|
69
|
+
gradual_parameter?(arg) ? rejoined.type_args[i] : arg
|
|
70
|
+
end
|
|
71
|
+
Type::Combinator.nominal_of(rejoined.class_name, type_args: args)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# The class claim a re-opened carrier still makes, for {ContentJoin.admissible_evidence} to gate
|
|
75
|
+
# the added members against.
|
|
76
|
+
#
|
|
77
|
+
# It is the carrier's VALUE-PINNED arms and nothing else, because pinning is the only trace a
|
|
78
|
+
# literal seed's own elements leave once the carrier is nominal. Reading the whole element list
|
|
79
|
+
# instead gets both ends wrong. {ContentJoin.admissible_evidence} answers "everything is
|
|
80
|
+
# admissible" for a seed carrying a `Dynamic`, and a re-opened carrier ALWAYS carries one — its
|
|
81
|
+
# own gradual floor — so the gate would be off exactly where it is needed: haml's
|
|
82
|
+
# `temple = [:multi]` grew an `Array[…]` arm past its hand-written `-> Array[:multi]` and brought
|
|
83
|
+
# the #561 `def.return-type-mismatch` back. Gating on the full list is the opposite error — after
|
|
84
|
+
# `a = []; a.push(1)` the carrier reads `Array[Integer | Dynamic[top]]`, and a class set of
|
|
85
|
+
# `{Integer}` floors the `"s"` the next line stores, which is the whole answer this issue is
|
|
86
|
+
# about. The pinning tells the two apart: `:multi` is a claim the author's literal made, `Integer`
|
|
87
|
+
# is this seam's own join talking back to itself.
|
|
88
|
+
#
|
|
89
|
+
# No pinned arm means no claim to contradict — an empty-seeded accumulator — and the added
|
|
90
|
+
# evidence is admitted as itself.
|
|
91
|
+
def pinned_evidence(members)
|
|
92
|
+
members.flat_map { |m| ContentJoin.union_members(m) }.grep(Type::Constant)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Which nominals may re-grow — the FP boundary this whole issue turns on, read off the carrier
|
|
96
|
+
# itself rather than out of a side table.
|
|
97
|
+
#
|
|
98
|
+
# A carrier that already carries a gradual arm on the parameter the mutation feeds is one this
|
|
99
|
+
# seam (or a declaration) has already declared open. Growing it is free of the #561 blast
|
|
100
|
+
# radius by construction: adding a member to a union that already contains `Dynamic[top]`
|
|
101
|
+
# cannot make the union reject a declared type it accepted, cannot manufacture a constant fold
|
|
102
|
+
# (a union carrying `Dynamic` never folds), and cannot make a dispatch that was quiet fire.
|
|
103
|
+
# What it CAN do is stop dropping evidence — which is the whole point.
|
|
104
|
+
#
|
|
105
|
+
# A PRECISE nominal is refused, and that is exactly the case the issue names: haml's
|
|
106
|
+
# hand-written `-> Array[:multi]`, or an `Array[Symbol]` an RBS signature declares. Its element
|
|
107
|
+
# set is a claim somebody made about the collection, not a residue of this seam's own
|
|
108
|
+
# one-store guesswork, and joining a foreign member into it is what drew eight
|
|
109
|
+
# `def.return-type-mismatch` on correct code (PR #561). The seed-admissibility gate in
|
|
110
|
+
# {ContentJoin.admissible_evidence} stays the guard for everything that does get through.
|
|
111
|
+
#
|
|
112
|
+
# This is a superset of "mutation-widened": a DECLARED `Array[untyped]` or
|
|
113
|
+
# `Array[Integer | untyped]` matches too. Deliberate — the soundness argument above is about
|
|
114
|
+
# the carrier being open, not about who opened it, and a declared gradual arm is the author
|
|
115
|
+
# saying the collection may hold anything. Distinguishing the two needs provenance travelling
|
|
116
|
+
# WITH the value (a field on `Type::Nominal`, reopening structural equality, `Marshal`, and the
|
|
117
|
+
# cache schema); nothing on this axis needs it yet.
|
|
118
|
+
def regrowable_carrier?(nominal)
|
|
119
|
+
return false unless %w[Array Hash].include?(nominal.class_name)
|
|
120
|
+
|
|
121
|
+
nominal.type_args.any? { |arg| gradual_parameter?(arg) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def gradual_parameter?(type)
|
|
125
|
+
ContentJoin.union_members(type).any?(Type::Dynamic)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The value-rewriting counterpart of {MutationWidening.widen_tuple}'s `values: :widen`. A mutator that lands a
|
|
129
|
+
# new value in an EXISTING slot falsifies that slot's pinning on a re-opened carrier exactly as
|
|
130
|
+
# it does on a literal one, so `d = [:multi]; d << x; d[0] = "s"` must not keep reading `:multi`
|
|
131
|
+
# back out of slot 0.
|
|
132
|
+
def value_pin_widened_args(nominal)
|
|
133
|
+
Type::Combinator.nominal_of(nominal.class_name,
|
|
134
|
+
type_args: nominal.type_args.map { |a| Type::Combinator.widen_value_pinned(a) })
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Rebinds `name` to the widened carrier, carrying across what a re-join must not lose.
|
|
138
|
+
def rebind(scope, builder, name, widened, pre_state:, kind:)
|
|
139
|
+
rebound = scope.public_send(builder, name, widened)
|
|
140
|
+
pre_state.is_a?(Type::Nominal) ? carry_slot_narrowings(rebound, scope, kind, name) : rebound
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Re-installs the per-slot `receiver[key] ||= default` narrowings that rebinding the receiver
|
|
144
|
+
# drops.
|
|
145
|
+
#
|
|
146
|
+
# `Scope#with_local` drops them because a rebind normally means a DIFFERENT object, and the
|
|
147
|
+
# slot's non-nil guarantee was about the old one. A re-join (issue #580) is the opposite: the
|
|
148
|
+
# binding still names the same object, and only the summary of what it holds grew. Before the
|
|
149
|
+
# `Nominal` arm existed the second store into an already-widened carrier rebound nothing at
|
|
150
|
+
# all, so the narrowing survived by accident — Redmine's `params[:f] ||= []` then
|
|
151
|
+
# `params[:op] ||= {}` idiom lost `:f`'s narrowing the moment `:op` re-joined, and the next
|
|
152
|
+
# `params[:f] << :status` dispatched on nil again. Carrying them keeps the re-join's scope
|
|
153
|
+
# effect to the one thing it actually learned.
|
|
154
|
+
#
|
|
155
|
+
# The call recording its OWN key runs after the widening (`eval_index_or_write` orders it that
|
|
156
|
+
# way deliberately), so a carried entry never shadows a fresher one for the same slot.
|
|
157
|
+
# Method-chain narrowings are deliberately NOT carried: `x.last` is a claim about a position a
|
|
158
|
+
# content mutation really can move.
|
|
159
|
+
def carry_slot_narrowings(rebound, pre_scope, kind, name)
|
|
160
|
+
pre_scope.indexed_narrowings.reduce(rebound) do |acc, (key, type)|
|
|
161
|
+
next acc unless key.receiver_kind == kind && key.receiver_name == name.to_sym
|
|
162
|
+
|
|
163
|
+
acc.with_indexed_narrowing(key.receiver_kind, key.receiver_name, key.key, type)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|