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
|
@@ -41,7 +41,10 @@ module Rigor
|
|
|
41
41
|
module Narrowing
|
|
42
42
|
TRUSTED_EQUALITY_LITERAL_CLASSES = [String, Symbol, Integer, TrueClass, FalseClass, NilClass].freeze
|
|
43
43
|
SINGLETON_LITERAL_CLASSES = [TrueClass, FalseClass, NilClass].freeze
|
|
44
|
-
|
|
44
|
+
# `scope` is the analysed file's seeded scope where the caller has one, and nil otherwise (the
|
|
45
|
+
# Fact-shaped and RSpec-matcher entries hold an environment but no scope). Only the #898 singleton
|
|
46
|
+
# path reads it, and only to withhold a `Bot`, so a nil scope is the pre-#898 answer exactly.
|
|
47
|
+
ClassNarrowingContext = Data.define(:exact, :polarity, :environment, :scope)
|
|
45
48
|
# A recognised `=~` regex pattern operand: the pattern `source` string and whether it was
|
|
46
49
|
# compiled in extended (`//x`) mode. Extraction from a literal `RegularExpressionNode` and from
|
|
47
50
|
# a value-pinned `Constant[Regexp]` share this carrier so the participation walk and the
|
|
@@ -139,9 +142,11 @@ module Rigor
|
|
|
139
142
|
# counterpart of the scope narrowing {.case_when_scopes} performs for the same condition
|
|
140
143
|
# shape, kept here so the branch a `case` expression's type drops and the clause whose
|
|
141
144
|
# body scope goes dead derive from one judgment.
|
|
142
|
-
def class_pattern_certainty(subject_type, class_name, environment:)
|
|
143
|
-
truthy_bot = narrow_class(subject_type, class_name, environment: environment
|
|
144
|
-
|
|
145
|
+
def class_pattern_certainty(subject_type, class_name, environment:, scope: nil)
|
|
146
|
+
truthy_bot = narrow_class(subject_type, class_name, environment: environment,
|
|
147
|
+
scope: scope).is_a?(Type::Bot)
|
|
148
|
+
falsey_bot = narrow_not_class(subject_type, class_name, environment: environment,
|
|
149
|
+
scope: scope).is_a?(Type::Bot)
|
|
145
150
|
|
|
146
151
|
return :no if truthy_bot && !falsey_bot
|
|
147
152
|
return :yes if !truthy_bot && falsey_bot
|
|
@@ -219,6 +224,54 @@ module Rigor
|
|
|
219
224
|
narrow_integer_comparison_dispatch(type, comparator, bound)
|
|
220
225
|
end
|
|
221
226
|
|
|
227
|
+
# Float-comparison fragment of `type` against a numeric literal `bound` (ADR-109 WD5). This
|
|
228
|
+
# is a TRUTHY-EDGE rule only: `x > c` is false for NaN, so the set that satisfies it is the
|
|
229
|
+
# range `Float[c..]`, but the set that fails it is that range's complement PLUS NaN, which no
|
|
230
|
+
# range spells. Callers apply it to the truthy edge and keep the entry type on the falsy edge.
|
|
231
|
+
# `x > c` narrows to the closed envelope `Float[c..]` rather than the exact `(c, ∞]` (one
|
|
232
|
+
# double wider; the exact form is deferred until a rule demands the removed point), `x < c`
|
|
233
|
+
# to the exact `Float[...c]`. Hooks in:
|
|
234
|
+
# - `Constant<Float>` is preserved when it satisfies the comparison, otherwise collapsed to
|
|
235
|
+
# `Bot` (a NaN constant satisfies nothing);
|
|
236
|
+
# - `FloatRange` becomes its intersection with the half-line, `Bot` when empty;
|
|
237
|
+
# - `Nominal[Float]` becomes the half-line itself;
|
|
238
|
+
# - `Union` narrows each member independently; Integer-rooted and other carriers flow
|
|
239
|
+
# through unchanged (the Integer rule above owns them).
|
|
240
|
+
def narrow_float_comparison(type, comparator, bound)
|
|
241
|
+
return type unless (bound.is_a?(Integer) || bound.is_a?(Float)) && %i[< <= > >=].include?(comparator)
|
|
242
|
+
|
|
243
|
+
bound = bound.to_f
|
|
244
|
+
return type if bound.nan? || bound.infinite?
|
|
245
|
+
|
|
246
|
+
narrow_float_comparison_dispatch(type, comparator, bound)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# The truthy edge of a numeric comparison: the Integer rule for Integer-rooted members, then
|
|
250
|
+
# the Float rule for Float-rooted ones. Never apply this to a falsy edge (see
|
|
251
|
+
# {.narrow_float_comparison}).
|
|
252
|
+
def narrow_numeric_comparison_truthy(type, comparator, bound)
|
|
253
|
+
narrow_float_comparison(narrow_integer_comparison(type, comparator, bound), comparator, bound)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Intersects the Float-rooted members of `type` with `range` (a `FloatRange`), leaving every
|
|
257
|
+
# other member unchanged. `x.nan?` narrows its FALSY edge with `non_nan_float`; `x.finite?`
|
|
258
|
+
# narrows its TRUTHY edge with `finite_float`; the other edge of each keeps the entry type
|
|
259
|
+
# (NaN is truthy for `nan?` and nothing else is known there; `!finite?` admits NaN and ±∞).
|
|
260
|
+
def narrow_float_to_range(type, range)
|
|
261
|
+
case type
|
|
262
|
+
when Type::Constant
|
|
263
|
+
type.value.is_a?(Float) && !range.covers?(type.value) ? Type::Combinator.bot : type
|
|
264
|
+
when Type::FloatRange
|
|
265
|
+
intersect_float_bounds(type, range.min, range.max, range.exclude_end?)
|
|
266
|
+
when Type::Nominal
|
|
267
|
+
float_nominal?(type) ? range : type
|
|
268
|
+
when Type::Union
|
|
269
|
+
Type::Combinator.union(*type.members.map { |m| narrow_float_to_range(m, range) })
|
|
270
|
+
else
|
|
271
|
+
type
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
222
275
|
# Equality fragment of `type` against an Integer `value`. `Constant<Integer>` is preserved
|
|
223
276
|
# when it equals `value`, otherwise collapses to `Bot`. `IntegerRange` covers? `value`
|
|
224
277
|
# narrows to `Constant[value]`; an out-of-range comparison collapses to `Bot`.
|
|
@@ -261,8 +314,9 @@ module Rigor
|
|
|
261
314
|
# preserved; disjoint hierarchies collapse to `Bot`. Classes the environment cannot
|
|
262
315
|
# resolve fall back to the conservative answer (the type unchanged) so the analyzer never
|
|
263
316
|
# asserts narrowing it cannot prove.
|
|
264
|
-
def narrow_class(type, class_name, exact: false, environment: Environment.default)
|
|
265
|
-
context = ClassNarrowingContext.new(exact: exact, polarity: :positive, environment: environment
|
|
317
|
+
def narrow_class(type, class_name, exact: false, environment: Environment.default, scope: nil)
|
|
318
|
+
context = ClassNarrowingContext.new(exact: exact, polarity: :positive, environment: environment,
|
|
319
|
+
scope: scope)
|
|
266
320
|
narrow_class_dispatch(type, class_name, context)
|
|
267
321
|
end
|
|
268
322
|
|
|
@@ -270,8 +324,9 @@ module Rigor
|
|
|
270
324
|
# Inhabitants that DO satisfy the predicate are removed; inhabitants that do not are
|
|
271
325
|
# preserved. Conservative on Top/Dynamic/Bot (preserved unchanged) because the analyzer
|
|
272
326
|
# cannot prove the negative without a richer carrier.
|
|
273
|
-
def narrow_not_class(type, class_name, exact: false, environment: Environment.default)
|
|
274
|
-
context = ClassNarrowingContext.new(exact: exact, polarity: :negative, environment: environment
|
|
327
|
+
def narrow_not_class(type, class_name, exact: false, environment: Environment.default, scope: nil)
|
|
328
|
+
context = ClassNarrowingContext.new(exact: exact, polarity: :negative, environment: environment,
|
|
329
|
+
scope: scope)
|
|
275
330
|
narrow_class_dispatch(type, class_name, context)
|
|
276
331
|
end
|
|
277
332
|
|
|
@@ -285,11 +340,11 @@ module Rigor
|
|
|
285
340
|
# Walk the current type's union members, keep each part disjoint from `base`, and add
|
|
286
341
|
# the removed-value Constant once when any current member covers it. `assert s is
|
|
287
342
|
# ~non-empty-string` over `s: String | nil` narrows to `Constant[""] | NilClass`.
|
|
288
|
-
# - `IntegerRange[a, b]` (v0.0.5+ slice). Complement is the two open halves
|
|
289
|
-
# a-1
|
|
343
|
+
# - `IntegerRange[a, b]` (v0.0.5+ slice). Complement is the two open halves
|
|
344
|
+
# `Integer[..a-1]` and `Integer[b+1..]`, each intersected with the integer-domain parts of
|
|
290
345
|
# `current_type`. Non-integer parts (nil, String, …) of a Union receiver survive
|
|
291
|
-
# unchanged. `assert n is ~
|
|
292
|
-
#
|
|
346
|
+
# unchanged. `assert n is ~Integer[5..10]` over `n: Integer | nil` narrows to `Integer[..4] |
|
|
347
|
+
# Integer[11..] | NilClass`.
|
|
293
348
|
# - `Type::Intersection[M1, M2, …]` (v0.0.5+ slice). De Morgan: `D \ (M1 ∩ M2) = (D \ M1)
|
|
294
349
|
# ∪ (D \ M2)`. Each member's complement is computed independently within
|
|
295
350
|
# `current_type` and the results are unioned. Members the algebra cannot complement
|
|
@@ -337,10 +392,6 @@ module Rigor
|
|
|
337
392
|
|
|
338
393
|
# Public predicate analyser. Returns `[truthy_scope, falsey_scope]`, always; when no
|
|
339
394
|
# narrowing rule matches the predicate node both entries are the receiver scope unchanged.
|
|
340
|
-
#
|
|
341
|
-
# @param node [Prism::Node, nil]
|
|
342
|
-
# @param scope [Rigor::Scope]
|
|
343
|
-
# @return [Array(Rigor::Scope, Rigor::Scope)]
|
|
344
395
|
def predicate_scopes(node, scope)
|
|
345
396
|
return [scope, scope] if node.nil?
|
|
346
397
|
|
|
@@ -366,11 +417,9 @@ module Rigor
|
|
|
366
417
|
# constants narrow as `is_a?`; integer/float-endpoint ranges narrow to `Numeric`;
|
|
367
418
|
# string-endpoint ranges and regexp literals narrow to `String`.
|
|
368
419
|
#
|
|
369
|
-
# @param subject
|
|
370
|
-
# @param conditions
|
|
420
|
+
# @param subject — the `case` subject.
|
|
421
|
+
# @param conditions — the `when`
|
|
371
422
|
# clause's `conditions` array.
|
|
372
|
-
# @param scope [Rigor::Scope]
|
|
373
|
-
# @return [Array(Rigor::Scope, Rigor::Scope)]
|
|
374
423
|
def case_when_scopes(subject, conditions, scope)
|
|
375
424
|
# C1 — `case x when /re/` runs `/re/ === x`, which sets the regex match-data globals
|
|
376
425
|
# exactly as a successful `=~` does. Narrow `$~`/`$&`/`$1..$N` on the clause body (the
|
|
@@ -480,7 +529,7 @@ module Rigor
|
|
|
480
529
|
end
|
|
481
530
|
|
|
482
531
|
# Complement of an `IntegerRange[a, b]` within `current_type`. Splits the range
|
|
483
|
-
# complement into the two open halves `
|
|
532
|
+
# complement into the two open halves `Integer[..a-1]` and `Integer[b+1..]` (skipping a
|
|
484
533
|
# half when its bound is infinity), then intersects each half with the integer-domain
|
|
485
534
|
# parts of `current_type`. Non-integer parts of a Union receiver (nil, String, …)
|
|
486
535
|
# survive unchanged.
|
|
@@ -505,8 +554,8 @@ module Rigor
|
|
|
505
554
|
Type::Combinator.union(*survivors)
|
|
506
555
|
end
|
|
507
556
|
|
|
508
|
-
# Returns the two open halves of an IntegerRange's complement: the left half
|
|
509
|
-
# a-1
|
|
557
|
+
# Returns the two open halves of an IntegerRange's complement: the left half
|
|
558
|
+
# `Integer[..a-1]` (when `a` is finite) and the right half `Integer[b+1..]` (when `b` is finite).
|
|
510
559
|
# Universal ranges (both bounds infinite) yield an empty array — the complement is
|
|
511
560
|
# empty.
|
|
512
561
|
def integer_range_complement_halves(range)
|
|
@@ -994,7 +1043,7 @@ module Rigor
|
|
|
994
1043
|
|
|
995
1044
|
def simple_dispatch_name?(name)
|
|
996
1045
|
%i[nil? ! is_a? kind_of? instance_of? == != === =~ match? key? has_key? empty? any?
|
|
997
|
-
none? respond_to?].include?(name)
|
|
1046
|
+
none? respond_to? nan? finite?].include?(name)
|
|
998
1047
|
end
|
|
999
1048
|
|
|
1000
1049
|
def dispatch_call_simple(node, scope, name)
|
|
@@ -1009,6 +1058,7 @@ module Rigor
|
|
|
1009
1058
|
when :key?, :has_key? then analyse_key_presence_predicate(node, scope)
|
|
1010
1059
|
when :empty?, :any?, :none? then analyse_array_emptiness_predicate(node, scope, name)
|
|
1011
1060
|
when :respond_to? then analyse_respond_to_predicate(node, scope)
|
|
1061
|
+
when :nan?, :finite? then analyse_float_class_predicate(node, scope, name)
|
|
1012
1062
|
end
|
|
1013
1063
|
end
|
|
1014
1064
|
|
|
@@ -1551,19 +1601,41 @@ module Rigor
|
|
|
1551
1601
|
return nil unless node.arguments.arguments.size == 2
|
|
1552
1602
|
|
|
1553
1603
|
low, high = node.arguments.arguments
|
|
1554
|
-
return nil unless
|
|
1604
|
+
return nil unless numeric_literal_node?(low) && numeric_literal_node?(high)
|
|
1555
1605
|
|
|
1556
1606
|
local_name = node.receiver.name
|
|
1557
1607
|
current = scope.local(local_name)
|
|
1558
1608
|
return nil if current.nil?
|
|
1559
1609
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1610
|
+
# Truthy edge only, for Integer and Float members alike (ADR-109 WD5): a Float literal
|
|
1611
|
+
# bound leaves the Integer members untouched, and vice versa.
|
|
1612
|
+
truthy = narrow_numeric_comparison_truthy(
|
|
1613
|
+
narrow_numeric_comparison_truthy(current, :>=, low.value),
|
|
1562
1614
|
:<=, high.value
|
|
1563
1615
|
)
|
|
1564
1616
|
[scope.with_local(local_name, truthy), scope]
|
|
1565
1617
|
end
|
|
1566
1618
|
|
|
1619
|
+
# `x.nan?` / `x.finite?` (ADR-109 WD5). `nan?` narrows the FALSY edge to the non-NaN Floats
|
|
1620
|
+
# and leaves the truthy edge alone (NaN is not a carrier); `finite?` narrows the TRUTHY
|
|
1621
|
+
# edge to the finite Floats and leaves the falsy edge alone (it admits NaN and ±∞). A
|
|
1622
|
+
# non-Float member is untouched on both edges.
|
|
1623
|
+
def analyse_float_class_predicate(node, scope, predicate)
|
|
1624
|
+
return nil unless argument_free?(node)
|
|
1625
|
+
return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
1626
|
+
|
|
1627
|
+
local_name = node.receiver.name
|
|
1628
|
+
current = scope.local(local_name)
|
|
1629
|
+
return nil if current.nil?
|
|
1630
|
+
|
|
1631
|
+
case predicate
|
|
1632
|
+
when :nan?
|
|
1633
|
+
[scope, scope.with_local(local_name, narrow_float_to_range(current, Type::Combinator.non_nan_float))]
|
|
1634
|
+
when :finite?
|
|
1635
|
+
[scope.with_local(local_name, narrow_float_to_range(current, Type::Combinator.finite_float)), scope]
|
|
1636
|
+
end
|
|
1637
|
+
end
|
|
1638
|
+
|
|
1567
1639
|
# Helper for {.narrow_integer_not_equal}. Only adjusts when the value sits exactly on
|
|
1568
1640
|
# one endpoint, so the result stays contiguous; otherwise the input range is preserved.
|
|
1569
1641
|
def narrow_integer_range_not_equal(range, value)
|
|
@@ -1628,20 +1700,96 @@ module Rigor
|
|
|
1628
1700
|
current = scope.local(local_name)
|
|
1629
1701
|
return nil if current.nil?
|
|
1630
1702
|
|
|
1631
|
-
truthy
|
|
1703
|
+
# ADR-109 WD5 — the Float rule runs on the truthy edge only: the falsy edge of `x > c`
|
|
1704
|
+
# admits NaN, so its Float-rooted members keep the entry type (the Integer rule below
|
|
1705
|
+
# leaves them untouched by construction).
|
|
1706
|
+
truthy = narrow_numeric_comparison_truthy(current, normalised_op, bound)
|
|
1632
1707
|
falsey = narrow_integer_comparison(current, INVERT_COMPARISON_OP[normalised_op], bound)
|
|
1633
1708
|
[scope.with_local(local_name, truthy), scope.with_local(local_name, falsey)]
|
|
1634
1709
|
end
|
|
1635
1710
|
|
|
1711
|
+
# An Integer or Float literal. Prism folds a leading minus into the literal, so `-1.5` is one
|
|
1712
|
+
# `FloatNode`.
|
|
1713
|
+
def numeric_literal_node?(node)
|
|
1714
|
+
node.is_a?(Prism::IntegerNode) || node.is_a?(Prism::FloatNode)
|
|
1715
|
+
end
|
|
1716
|
+
|
|
1636
1717
|
def comparison_local_literal(left, right, comparator)
|
|
1637
|
-
if left.is_a?(Prism::LocalVariableReadNode) &&
|
|
1718
|
+
if left.is_a?(Prism::LocalVariableReadNode) && numeric_literal_node?(right)
|
|
1638
1719
|
return [left.name, comparator, right.value]
|
|
1639
1720
|
end
|
|
1640
|
-
return nil unless right.is_a?(Prism::LocalVariableReadNode) &&
|
|
1721
|
+
return nil unless right.is_a?(Prism::LocalVariableReadNode) && numeric_literal_node?(left)
|
|
1641
1722
|
|
|
1642
1723
|
[right.name, REVERSE_COMPARISON_OP[comparator], left.value]
|
|
1643
1724
|
end
|
|
1644
1725
|
|
|
1726
|
+
def narrow_float_comparison_dispatch(type, comparator, bound)
|
|
1727
|
+
case type
|
|
1728
|
+
when Type::Constant
|
|
1729
|
+
# A non-Float constant belongs to another rule and is never dropped here. The test is
|
|
1730
|
+
# the positive comparison, negated once: NaN fails every comparison, which is Ruby's
|
|
1731
|
+
# answer too, and rewriting `!(v < c)` as `v >= c` would silently let NaN through.
|
|
1732
|
+
return type unless type.value.is_a?(Float)
|
|
1733
|
+
|
|
1734
|
+
float_constant_satisfies?(type.value, comparator, bound) ? type : Type::Combinator.bot
|
|
1735
|
+
when Type::FloatRange
|
|
1736
|
+
lo, hi, hi_exclusive = float_comparison_half_line(comparator, bound)
|
|
1737
|
+
intersect_float_bounds(type, lo, hi, hi_exclusive)
|
|
1738
|
+
when Type::Nominal
|
|
1739
|
+
return type unless float_nominal?(type)
|
|
1740
|
+
|
|
1741
|
+
lo, hi, hi_exclusive = float_comparison_half_line(comparator, bound)
|
|
1742
|
+
intersect_float_bounds(Type::Combinator.non_nan_float, lo, hi, hi_exclusive)
|
|
1743
|
+
when Type::Union
|
|
1744
|
+
Type::Combinator.union(
|
|
1745
|
+
*type.members.map { |m| narrow_float_comparison_dispatch(m, comparator, bound) }
|
|
1746
|
+
)
|
|
1747
|
+
else
|
|
1748
|
+
type
|
|
1749
|
+
end
|
|
1750
|
+
end
|
|
1751
|
+
|
|
1752
|
+
def float_nominal?(nominal)
|
|
1753
|
+
nominal.class_name == "Float" && nominal.type_args.empty?
|
|
1754
|
+
end
|
|
1755
|
+
|
|
1756
|
+
def float_constant_satisfies?(value, comparator, bound)
|
|
1757
|
+
case comparator
|
|
1758
|
+
when :< then value < bound
|
|
1759
|
+
when :<= then value <= bound
|
|
1760
|
+
when :> then value > bound
|
|
1761
|
+
when :>= then value >= bound
|
|
1762
|
+
end
|
|
1763
|
+
end
|
|
1764
|
+
|
|
1765
|
+
# `[lo, hi]` with `hi` exclusive or not: `x < c` is `[-∞, c)`, `x <= c` is `[-∞, c]`, and both
|
|
1766
|
+
# `x > c` and `x >= c` are the closed envelope `[c, ∞]` (WD5).
|
|
1767
|
+
def float_comparison_half_line(comparator, bound)
|
|
1768
|
+
case comparator
|
|
1769
|
+
when :< then [-Float::INFINITY, bound, true]
|
|
1770
|
+
when :<= then [-Float::INFINITY, bound, false]
|
|
1771
|
+
else [bound, Float::INFINITY, false]
|
|
1772
|
+
end
|
|
1773
|
+
end
|
|
1774
|
+
|
|
1775
|
+
# Intersects `range` with `[lo, hi]` (`hi` exclusive when `hi_exclusive`), keeping whichever
|
|
1776
|
+
# upper bound is tighter on the canonical closed form together with its own exclusivity, so
|
|
1777
|
+
# `Float[0.0...1.0] ∩ [-∞, 2.0]` still displays `Float[0.0...1.0]`. Empty is `Bot`.
|
|
1778
|
+
def intersect_float_bounds(range, lower, upper, upper_exclusive)
|
|
1779
|
+
new_min = [range.min, lower].max
|
|
1780
|
+
half_canonical_max = upper_exclusive ? upper.prev_float : upper
|
|
1781
|
+
if half_canonical_max < range.canonical_max
|
|
1782
|
+
new_max = upper
|
|
1783
|
+
exclusive = upper_exclusive
|
|
1784
|
+
else
|
|
1785
|
+
new_max = range.max
|
|
1786
|
+
exclusive = range.exclude_end?
|
|
1787
|
+
end
|
|
1788
|
+
return Type::Combinator.bot if new_min > (exclusive ? new_max.prev_float : new_max)
|
|
1789
|
+
|
|
1790
|
+
Type::Combinator.float_range(new_min, new_max, exclude_end: exclusive)
|
|
1791
|
+
end
|
|
1792
|
+
|
|
1645
1793
|
def narrow_integer_equal_dispatch(type, value)
|
|
1646
1794
|
case type
|
|
1647
1795
|
when Type::Constant then narrow_integer_equal_constant(type, value)
|
|
@@ -1854,8 +2002,10 @@ module Rigor
|
|
|
1854
2002
|
current = scope.type_of(node.receiver)
|
|
1855
2003
|
return nil if current.nil?
|
|
1856
2004
|
|
|
1857
|
-
truthy_type = narrow_class(current, class_name, exact: exact, environment: scope.environment
|
|
1858
|
-
|
|
2005
|
+
truthy_type = narrow_class(current, class_name, exact: exact, environment: scope.environment,
|
|
2006
|
+
scope: scope)
|
|
2007
|
+
falsey_type = narrow_not_class(current, class_name, exact: exact, environment: scope.environment,
|
|
2008
|
+
scope: scope)
|
|
1859
2009
|
|
|
1860
2010
|
[
|
|
1861
2011
|
scope.with_method_chain_narrowing(*address, truthy_type),
|
|
@@ -1907,13 +2057,25 @@ module Rigor
|
|
|
1907
2057
|
# shapes at once. Because the repair lives in the shared derivation, `is_a?`, `case`/`when`, `===`
|
|
1908
2058
|
# and the constant typer were fixed together rather than one guard shape at a time. A scope no
|
|
1909
2059
|
# declaration walk built still falls back to the peel; see {Reflection.lexical_nesting_chain}.
|
|
2060
|
+
#
|
|
2061
|
+
# A path whose head and tail have DIFFERENT owners is the one shape no `<prefix>::<full path>`
|
|
2062
|
+
# candidate can name (#656), so the whole-path loop falls through to the segment-wise walk
|
|
2063
|
+
# before it gives up: `A::B` guarded inside `P::Guard` with `P::A < P::Base` is `P::Base::B`,
|
|
2064
|
+
# which the loop above only ever reached as the top-level `A::B` — a real but different class,
|
|
2065
|
+
# and every reader call on the narrowed receiver was then checked against it. The walk is
|
|
2066
|
+
# {Reflection.resolve_constant_path_name}, shared with the constant typer so the guard and the
|
|
2067
|
+
# value it guards cannot name two classes, and it takes this scope's acceptance test so a
|
|
2068
|
+
# candidate is still adopted only when the environment or discovery knows it as a class.
|
|
1910
2069
|
def resolve_class_name_lexically(bare_name, scope)
|
|
1911
2070
|
chain = lexical_nesting_for(scope)
|
|
1912
2071
|
chain.each do |prefix|
|
|
1913
2072
|
candidate = "#{prefix}::#{bare_name}"
|
|
1914
2073
|
return candidate if class_known_to_scope?(scope, candidate)
|
|
1915
2074
|
end
|
|
1916
|
-
bare_name
|
|
2075
|
+
walked = Reflection.resolve_constant_path_name(bare_name, scope) do |candidate|
|
|
2076
|
+
class_known_to_scope?(scope, candidate)
|
|
2077
|
+
end
|
|
2078
|
+
walked || bare_name
|
|
1917
2079
|
end
|
|
1918
2080
|
|
|
1919
2081
|
# Combines the environment's RBS-known set with the scope's in-source
|
|
@@ -1937,11 +2099,11 @@ module Rigor
|
|
|
1937
2099
|
[
|
|
1938
2100
|
scope.with_local(
|
|
1939
2101
|
name,
|
|
1940
|
-
narrow_class(current, class_name, exact: exact, environment: scope.environment)
|
|
2102
|
+
narrow_class(current, class_name, exact: exact, environment: scope.environment, scope: scope)
|
|
1941
2103
|
),
|
|
1942
2104
|
scope.with_local(
|
|
1943
2105
|
name,
|
|
1944
|
-
narrow_not_class(current, class_name, exact: exact, environment: scope.environment)
|
|
2106
|
+
narrow_not_class(current, class_name, exact: exact, environment: scope.environment, scope: scope)
|
|
1945
2107
|
)
|
|
1946
2108
|
]
|
|
1947
2109
|
end
|
|
@@ -1996,8 +2158,10 @@ module Rigor
|
|
|
1996
2158
|
current = scope.type_of(chain_arg)
|
|
1997
2159
|
return nil if current.nil?
|
|
1998
2160
|
|
|
1999
|
-
truthy_type = narrow_class(current, class_name, exact: false, environment: scope.environment
|
|
2000
|
-
|
|
2161
|
+
truthy_type = narrow_class(current, class_name, exact: false, environment: scope.environment,
|
|
2162
|
+
scope: scope)
|
|
2163
|
+
falsey_type = narrow_not_class(current, class_name, exact: false, environment: scope.environment,
|
|
2164
|
+
scope: scope)
|
|
2001
2165
|
[
|
|
2002
2166
|
scope.with_method_chain_narrowing(*address, truthy_type),
|
|
2003
2167
|
scope.with_method_chain_narrowing(*address, falsey_type)
|
|
@@ -2012,7 +2176,8 @@ module Rigor
|
|
|
2012
2176
|
target_class = case_equality_target_class(receiver)
|
|
2013
2177
|
return nil if target_class.nil?
|
|
2014
2178
|
|
|
2015
|
-
narrowed = narrow_class(current, target_class, exact: false, environment: scope.environment
|
|
2179
|
+
narrowed = narrow_class(current, target_class, exact: false, environment: scope.environment,
|
|
2180
|
+
scope: scope)
|
|
2016
2181
|
[
|
|
2017
2182
|
scope.with_local(local_name, narrowed),
|
|
2018
2183
|
scope.with_local(local_name, current)
|
|
@@ -2283,8 +2448,9 @@ module Rigor
|
|
|
2283
2448
|
|
|
2284
2449
|
def class_when_result(scope, current, target, falsey_acc)
|
|
2285
2450
|
{
|
|
2286
|
-
truthy: narrow_class(current, target, exact: false, environment: scope.environment),
|
|
2287
|
-
falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment
|
|
2451
|
+
truthy: narrow_class(current, target, exact: false, environment: scope.environment, scope: scope),
|
|
2452
|
+
falsey: narrow_not_class(falsey_acc, target, exact: false, environment: scope.environment,
|
|
2453
|
+
scope: scope),
|
|
2288
2454
|
fully_narrowable: true
|
|
2289
2455
|
}
|
|
2290
2456
|
end
|
|
@@ -2368,6 +2534,11 @@ module Rigor
|
|
|
2368
2534
|
# through {#rooted_class_predicate_name}, which layers #614's extra decline on top of the
|
|
2369
2535
|
# same walk. A rooted `::Foo` names the top level and never a lexically nearer shadow, so
|
|
2370
2536
|
# it keeps the un-walked spelling. nil for any non-constant shape, as before.
|
|
2537
|
+
#
|
|
2538
|
+
# PUBLIC, alone among the helpers in this block (#655): `case`/`when`'s VALUE side lives in
|
|
2539
|
+
# `ExpressionTyper` and matched on the as-written spelling, so the two halves of one `case`
|
|
2540
|
+
# disagreed — the flow side narrowed through this walk while the value side resolved the
|
|
2541
|
+
# pattern against whatever the environment knew under that literal name.
|
|
2371
2542
|
def lexical_class_name(node, scope)
|
|
2372
2543
|
bare_name = static_class_name(node)
|
|
2373
2544
|
return nil if bare_name.nil?
|
|
@@ -2375,6 +2546,7 @@ module Rigor
|
|
|
2375
2546
|
|
|
2376
2547
|
resolve_class_name_lexically(bare_name, scope)
|
|
2377
2548
|
end
|
|
2549
|
+
public :lexical_class_name
|
|
2378
2550
|
|
|
2379
2551
|
# ----- narrow_class / narrow_not_class helpers -----
|
|
2380
2552
|
|
|
@@ -2437,8 +2609,7 @@ module Rigor
|
|
|
2437
2609
|
end
|
|
2438
2610
|
|
|
2439
2611
|
# Issue #657 — `Bot` is the assertion "this can never match", and only a KNOWN-disjoint ordering
|
|
2440
|
-
# supports it
|
|
2441
|
-
# the negative edge (where the pre-state survives) and an over-claim on the positive one:
|
|
2612
|
+
# supports it (the reason {#declines_bot?} exists, and the repro it was written against):
|
|
2442
2613
|
#
|
|
2443
2614
|
# module Taggable; def tag = "t"; end
|
|
2444
2615
|
# class Integer; include Taggable; end
|
|
@@ -2460,8 +2631,7 @@ module Rigor
|
|
|
2460
2631
|
def narrow_constant_to_class(constant, class_name, context)
|
|
2461
2632
|
rigor_class = constant.value.class.name
|
|
2462
2633
|
return constant if subclass_of?(rigor_class, class_name, context)
|
|
2463
|
-
return Type::Combinator.untyped if
|
|
2464
|
-
class_ordering(rigor_class, class_name, context) == :unknown
|
|
2634
|
+
return Type::Combinator.untyped if declines_bot?(rigor_class, class_name, context)
|
|
2465
2635
|
|
|
2466
2636
|
Type::Combinator.bot
|
|
2467
2637
|
end
|
|
@@ -2505,8 +2675,14 @@ module Rigor
|
|
|
2505
2675
|
end
|
|
2506
2676
|
end
|
|
2507
2677
|
|
|
2678
|
+
# A shape projects to `Array` / `Hash`, and reopening either of those to include a project module
|
|
2679
|
+
# leaves the ordering `:unknown` for the same reason #657's `Integer` does — so `[1, 2]` under `when
|
|
2680
|
+
# Taggable` reached the same unsupported `Bot`. The carrier differs; the missing evidence does not.
|
|
2508
2681
|
def narrow_shape_to_class(shape, projected_class, class_name, context)
|
|
2509
|
-
subclass_of?(projected_class, class_name, context)
|
|
2682
|
+
return shape if subclass_of?(projected_class, class_name, context)
|
|
2683
|
+
return Type::Combinator.untyped if declines_bot?(projected_class, class_name, context)
|
|
2684
|
+
|
|
2685
|
+
Type::Combinator.bot
|
|
2510
2686
|
end
|
|
2511
2687
|
|
|
2512
2688
|
def narrow_shape_not_class(shape, projected_class, class_name, context)
|
|
@@ -2517,8 +2693,69 @@ module Rigor
|
|
|
2517
2693
|
# subclass of `Module`). Asking `Foo.is_a?(Class)` returns true; `Foo.is_a?(Foo)`
|
|
2518
2694
|
# returns false unless `Foo` is `Class` itself. We approximate this by treating
|
|
2519
2695
|
# singletons uniformly as `Class` instances.
|
|
2696
|
+
#
|
|
2697
|
+
# The approximation answers through `Class`, so a target `Class` cannot be ordered against — a
|
|
2698
|
+
# project module, typically — lands on the same `:unknown` #657 collapsed to `Bot`, and `case Widget
|
|
2699
|
+
# when Meta` was reported unreachable where `Widget` had `extend Meta`.
|
|
2700
|
+
#
|
|
2701
|
+
# Issue #898 — that decline is silent about a target the environment DOES order against `Class`, and
|
|
2702
|
+
# `extend Comparable` is one: both names are in RBS, neither includes the other, so the ordering says
|
|
2703
|
+
# `:disjoint` and the collapse fired on an arm MRI takes (`Widget.is_a?(Comparable)` is true — the
|
|
2704
|
+
# `extend` put `Comparable` in `Widget`'s SINGLETON ancestry, which asking about `Class` cannot see).
|
|
2705
|
+
# {#singleton_extend_declines_bot?} supplies the missing half from the project's own `extend` record.
|
|
2520
2706
|
def narrow_singleton_to_class(singleton, class_name, context)
|
|
2521
|
-
subclass_of?("Class", class_name, context)
|
|
2707
|
+
return singleton if subclass_of?("Class", class_name, context)
|
|
2708
|
+
return Type::Combinator.untyped if declines_bot?("Class", class_name, context)
|
|
2709
|
+
return Type::Combinator.untyped if singleton_extend_declines_bot?(singleton, class_name, context)
|
|
2710
|
+
|
|
2711
|
+
Type::Combinator.bot
|
|
2712
|
+
end
|
|
2713
|
+
|
|
2714
|
+
# Whether the class object's own `extend` record forbids the `Bot` the `Class` approximation would
|
|
2715
|
+
# otherwise authorise (#898). An extended `M` puts every ancestor of `M` in the singleton's
|
|
2716
|
+
# ancestry, so the question per extended module is `M <= target`: `:equal` (`extend Comparable`
|
|
2717
|
+
# under `when Comparable`) and `:subclass` support the arm, and `:unknown` (`extend Meta` under
|
|
2718
|
+
# `when Comparable`, `Meta` being project source the environment never joined to RBS) cannot rule
|
|
2719
|
+
# it out. `:superclass` and `:disjoint` support NOTHING and must keep the collapse — `Integer`
|
|
2720
|
+
# under `when Integer` orders `:superclass` against an extended `Comparable`, and reading that as
|
|
2721
|
+
# support would retract `case Widget when Integer` from every class that extends a core module.
|
|
2722
|
+
#
|
|
2723
|
+
# The answer is `Dynamic[top]`, never the preserved `Singleton[C]`, and the negative edge
|
|
2724
|
+
# ({#narrow_singleton_not_class}) is untouched — so this record is read ONLY to withhold a claim,
|
|
2725
|
+
# never to make one. That asymmetry is deliberate, and it is what keeps the fix honest against an
|
|
2726
|
+
# extend record that is structurally incomplete: `Widget.extend(m)` at a call site and an `extend`
|
|
2727
|
+
# written in a file outside the analysed set are both invisible to the walk behind
|
|
2728
|
+
# {Scope#singleton_extends_of}, and the declaration-side record joined to it below is no more
|
|
2729
|
+
# complete — RBS says nothing about a class it does not declare. A shape that turned this table into a positive
|
|
2730
|
+
# `:subclass` verdict would have to be right about ABSENCE too, and it cannot be. Preserving
|
|
2731
|
+
# `Singleton[C]` would also hand the arm a receiver whose singleton method table has no `clamp` on
|
|
2732
|
+
# it, trading the unreachable-clause false positive for an undefined-method one — the same trap
|
|
2733
|
+
# #657 recorded on the `Constant` carrier.
|
|
2734
|
+
#
|
|
2735
|
+
# Issue #915 — two sources, read as one. The source record above sees only what Ruby source spells;
|
|
2736
|
+
# a project that declares `extend M` in its own `sig/` puts the same module in the same singleton
|
|
2737
|
+
# ancestry, and the environment's declaration-side answer
|
|
2738
|
+
# ({Environment#singleton_extended_modules}) is consulted alongside it. Neither is asked to be
|
|
2739
|
+
# complete; each can only add a module that IS there, which is all the one-directional read needs.
|
|
2740
|
+
def singleton_extend_declines_bot?(singleton, class_name, context)
|
|
2741
|
+
return false if context.exact
|
|
2742
|
+
|
|
2743
|
+
extended = singleton_ancestor_modules(singleton, context)
|
|
2744
|
+
return false if extended.empty?
|
|
2745
|
+
|
|
2746
|
+
extended.any? do |module_name|
|
|
2747
|
+
%i[equal subclass unknown].include?(class_ordering(module_name, class_name, context))
|
|
2748
|
+
end
|
|
2749
|
+
end
|
|
2750
|
+
|
|
2751
|
+
def singleton_ancestor_modules(singleton, context)
|
|
2752
|
+
scope = context.scope
|
|
2753
|
+
from_source = scope.nil? ? [] : scope.singleton_extends_of(singleton.class_name)
|
|
2754
|
+
from_rbs = context.environment&.singleton_extended_modules(singleton.class_name) || []
|
|
2755
|
+
return from_source if from_rbs.empty?
|
|
2756
|
+
return from_rbs if from_source.empty?
|
|
2757
|
+
|
|
2758
|
+
from_source | from_rbs
|
|
2522
2759
|
end
|
|
2523
2760
|
|
|
2524
2761
|
def narrow_singleton_not_class(singleton, class_name, context)
|
|
@@ -2534,6 +2771,22 @@ module Rigor
|
|
|
2534
2771
|
end
|
|
2535
2772
|
end
|
|
2536
2773
|
|
|
2774
|
+
# Whether the positive edge must decline the `Bot` that `subclass_of?` returning false would
|
|
2775
|
+
# otherwise authorise (#657). `subclass_of?` folds `:disjoint` and `:unknown` into one `false`, and
|
|
2776
|
+
# only the first is evidence that the guard can never match; the second is the environment saying it
|
|
2777
|
+
# cannot tell, which any file reopening either side can make true.
|
|
2778
|
+
#
|
|
2779
|
+
# This only ever ADDS a decline: every caller keeps its own `subclass_of?` test first, so the
|
|
2780
|
+
# preserving edge is untouched, and a `:disjoint`, `:equal`, `:subclass` or `:superclass` verdict
|
|
2781
|
+
# never reaches here. There is no ordering it turns INTO a match — the honest answer for
|
|
2782
|
+
# "cannot tell" is Dynamic, never the asked class.
|
|
2783
|
+
#
|
|
2784
|
+
# `instance_of?` is exempt because it compares the exact class: a module is never an object's class,
|
|
2785
|
+
# so that edge's `Bot` rests on the guard's own semantics rather than on the ordering.
|
|
2786
|
+
def declines_bot?(rigor_class_name, target_class_name, context)
|
|
2787
|
+
!context.exact && class_ordering(rigor_class_name, target_class_name, context) == :unknown
|
|
2788
|
+
end
|
|
2789
|
+
|
|
2537
2790
|
# Returns `true` when an instance of `rigor_class_name` satisfies
|
|
2538
2791
|
# `is_a?(target_class_name)` (or `instance_of?(target_class_name)` when `exact: true`).
|
|
2539
2792
|
# Falls back to the safe `false` when either name does not resolve through the analyzer
|
|
@@ -53,10 +53,6 @@ module Rigor
|
|
|
53
53
|
# Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read (or
|
|
54
54
|
# a write in value position, `if (x = MAP[k])`) resolves through, then the predicate-fold derivation
|
|
55
55
|
# issue #313 added.
|
|
56
|
-
#
|
|
57
|
-
# @param node [Prism::Node, nil]
|
|
58
|
-
# @param scope [Rigor::Scope, nil]
|
|
59
|
-
# @return [Symbol, nil]
|
|
60
56
|
def resolve(node, scope)
|
|
61
57
|
return nil if node.nil? || scope.nil?
|
|
62
58
|
|
|
@@ -97,9 +93,7 @@ module Rigor
|
|
|
97
93
|
# per-overload, which is what makes it precise: `Array#first` is optimistic while `Array#first(3)` is
|
|
98
94
|
# not, and `String#[]` / `Enumerable#find` are honest because they already spell the miss as `?`.
|
|
99
95
|
#
|
|
100
|
-
# @param
|
|
101
|
-
# @param method_type [RBS::MethodType] the overload {OverloadSelector.select} returned
|
|
102
|
-
# @return [Boolean]
|
|
96
|
+
# @param method_type — the overload {OverloadSelector.select} returned
|
|
103
97
|
def optimistic_overload?(method_definition, method_type)
|
|
104
98
|
type_def = matching_type_def(method_definition, method_type)
|
|
105
99
|
return false unless type_def.respond_to?(:overload_annotations)
|
|
@@ -18,9 +18,8 @@ module Rigor
|
|
|
18
18
|
module OriginLookup
|
|
19
19
|
module_function
|
|
20
20
|
|
|
21
|
-
# @param
|
|
22
|
-
# @
|
|
23
|
-
# @return [Symbol, nil] the effective dynamic-origin cause, or nil when none is known
|
|
21
|
+
# @param node — the receiver expression's node (nil for an implicit-self receiver)
|
|
22
|
+
# @return the effective dynamic-origin cause, or nil when none is known
|
|
24
23
|
def origin_for(scope, node)
|
|
25
24
|
return nil if node.nil?
|
|
26
25
|
|
|
@@ -116,11 +116,10 @@ module Rigor
|
|
|
116
116
|
# single-level pass.
|
|
117
117
|
DEFAULT_ROUNDS = 3
|
|
118
118
|
|
|
119
|
-
# @param files
|
|
120
|
-
# @param
|
|
121
|
-
# @param
|
|
122
|
-
# @
|
|
123
|
-
# @return [Hash{[String,Symbol,Symbol] => Hash{Symbol => Rigor::Type}}] frozen.
|
|
119
|
+
# @param files — project `.rb` paths to scan for call sites.
|
|
120
|
+
# @param target_ruby — Prism parse target.
|
|
121
|
+
# @param max_rounds — the WD5 fixpoint cap (1 = single-level).
|
|
122
|
+
# @return => Hash{Symbol => Rigor::Type}}] frozen.
|
|
124
123
|
def self.collect(files:, environment:, target_ruby: nil, max_rounds: DEFAULT_ROUNDS, workers: 0)
|
|
125
124
|
new(files: files, environment: environment, target_ruby: target_ruby,
|
|
126
125
|
max_rounds: max_rounds, workers: workers).collect
|
|
@@ -330,7 +329,7 @@ module Rigor
|
|
|
330
329
|
arg.is_a?(Prism::AssocSplatNode)
|
|
331
330
|
end
|
|
332
331
|
|
|
333
|
-
# @return
|
|
332
|
+
# @return , nil]
|
|
334
333
|
def resolve_callee(call_node, scope, index)
|
|
335
334
|
if call_node.receiver.nil?
|
|
336
335
|
class_name, kind = implicit_self_target(scope)
|
|
@@ -92,13 +92,13 @@ module Rigor
|
|
|
92
92
|
|
|
93
93
|
# Collects and widens the constants every `pre_eval:` file declares.
|
|
94
94
|
#
|
|
95
|
-
# @param paths
|
|
96
|
-
# @param scope_builder
|
|
95
|
+
# @param paths — absolute paths to the `pre_eval:` files that exist on disk.
|
|
96
|
+
# @param scope_builder — `path -> Rigor::Scope`; the caller supplies a project-seeded, environment-
|
|
97
97
|
# bound scope so the rvalue typer resolves cross-file classes exactly as per-file analysis would.
|
|
98
|
-
# @param target_ruby
|
|
99
|
-
# @param buffer
|
|
98
|
+
# @param target_ruby — the Prism parse version (`Configuration#target_ruby`).
|
|
99
|
+
# @param buffer — editor-mode binding; when set, a listed file that
|
|
100
100
|
# matches the in-flight buffer is read from its physical bytes.
|
|
101
|
-
# @return
|
|
101
|
+
# @return frozen qualified-name -> published type table.
|
|
102
102
|
def collect(paths:, scope_builder:, target_ruby: nil, buffer: nil)
|
|
103
103
|
published = {}
|
|
104
104
|
conflicted = {}
|
|
@@ -165,6 +165,7 @@ module Rigor
|
|
|
165
165
|
when Type::Constant then widen_constant(type)
|
|
166
166
|
when Type::Refined then widen(type.base)
|
|
167
167
|
when Type::IntegerRange then Type::Combinator.nominal_of("Integer")
|
|
168
|
+
when Type::FloatRange then Type::Combinator.nominal_of("Float")
|
|
168
169
|
when Type::Tuple then Type::Combinator.nominal_of("Array")
|
|
169
170
|
when Type::HashShape then Type::Combinator.nominal_of("Hash")
|
|
170
171
|
when Type::Nominal then type.type_args.empty? ? type : Type::Combinator.nominal_of(type.class_name)
|