rigortype 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- 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 +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- 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 +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -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 +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- 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/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- 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/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- 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 +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -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 +3 -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 +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- 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 +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- 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 +112 -14
- 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 +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- 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 +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- 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 +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -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 +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -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 +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- 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 +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -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/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- 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 +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- 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 +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- 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 +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- 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/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -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/match_data_folding.rb +159 -0
- 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 +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- 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/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- 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 +5 -6
- 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 +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- 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 +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -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/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- 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 +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- 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 +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- 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/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- 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 +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- 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 +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- 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 +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- 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 +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- 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/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- 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 +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -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/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- 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 +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- 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 +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -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/lib/rigor.rb +1 -0
- 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/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- 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 +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -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/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -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 +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- 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 +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- 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 +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -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 +2 -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/types.rb +1 -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-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- 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 +2 -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 +12 -7
- 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 +36 -37
- 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 +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -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 +10 -17
- 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/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- 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 +2 -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 +10 -5
- 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 +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../reflection"
|
|
4
|
+
require_relative "../../source/parameter_envelope"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Analysis
|
|
8
|
+
module CheckRules
|
|
9
|
+
# Issue #992 — the positional envelope `call.wrong-arity` may check a call against when no signature
|
|
10
|
+
# declares the method: the {Source::ParameterEnvelope} the project's own `def` records in
|
|
11
|
+
# `Scope::DiscoveryIndex#discovered_parameter_envelopes`, and only when nothing the project can see
|
|
12
|
+
# could make a different definition of that name the one that runs.
|
|
13
|
+
#
|
|
14
|
+
# Asked in two stages, because every condition past the first can only WITHHOLD a firing:
|
|
15
|
+
#
|
|
16
|
+
# 1. {#owner_envelope} walks the receiver's superclass chain to the first class whose own body, or one of
|
|
17
|
+
# whose project mixins, records the name. Every record at that level must agree — `include` and
|
|
18
|
+
# `prepend` share one table, so a mixin's definition may shadow the class's own — and none may be
|
|
19
|
+
# opaque. A call that fits this envelope is silent, and nothing below is consulted.
|
|
20
|
+
# 2. {#authoritative?} runs only for a call that would fire. It declines when a `method_missing` /
|
|
21
|
+
# `respond_to_missing?` hook or a dynamic-surface mark sits anywhere in the chain; when a mixin the
|
|
22
|
+
# project does not declare is not a known RBS module that lacks the name; when a `pre_eval:` patch or
|
|
23
|
+
# a plugin's synthetic method names it; when the owner does not make the method public; and when any
|
|
24
|
+
# project subclass of the receiver — whose instances the receiver's type admits — records a different
|
|
25
|
+
# or opaque envelope for it.
|
|
26
|
+
#
|
|
27
|
+
# Deliberately NOT in scope: a constructor reached through `Class#new` (nothing records `initialize` as
|
|
28
|
+
# `[:singleton, :new]`, so `.new` finds no owner unless the class writes `def self.new` itself); keyword
|
|
29
|
+
# arity (the envelope's required-keyword flag declines, as `compute_arity_envelope` declines an RBS
|
|
30
|
+
# function with required keywords); and a `class_eval` whose receiver is not a constant, which the
|
|
31
|
+
# discovery walk cannot name.
|
|
32
|
+
class SourceArity
|
|
33
|
+
MISSING_HOOKS = %i[method_missing respond_to_missing?].freeze
|
|
34
|
+
private_constant :MISSING_HOOKS
|
|
35
|
+
|
|
36
|
+
# One class on the walk: its own name, the project modules its mixins resolve to (transitively through
|
|
37
|
+
# their own `include`s), and the candidate-name lists of the mixins that resolve to no project module.
|
|
38
|
+
Level = Data.define(:class_name, :modules, :externals)
|
|
39
|
+
private_constant :Level
|
|
40
|
+
|
|
41
|
+
def initialize(scope, method_name, kind)
|
|
42
|
+
@scope = scope
|
|
43
|
+
@method_name = method_name.to_sym
|
|
44
|
+
@kind = kind
|
|
45
|
+
@name_memo = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The nearest definition's envelope, or nil when there is none or it is not one shape. Remembers the
|
|
49
|
+
# levels it walked for {#authoritative?}.
|
|
50
|
+
#
|
|
51
|
+
# Its ADR-46 reads are withheld ({Analysis::DependencyRecorder.withhold}) until the caller settles
|
|
52
|
+
# the verdict with {#settle_by_definitions} or {#settle_by_walk}.
|
|
53
|
+
def owner_envelope(class_name)
|
|
54
|
+
@owner_entries = []
|
|
55
|
+
@ambiguous = false
|
|
56
|
+
envelope, @withheld = Analysis::DependencyRecorder.withhold { walk_to_owner(class_name) }
|
|
57
|
+
envelope
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# True when the nearest level recorded the name but not as one envelope.
|
|
61
|
+
def ambiguous? = @ambiguous
|
|
62
|
+
|
|
63
|
+
# For a silent verdict that only a definition can overturn — the call fits, the owner takes a required
|
|
64
|
+
# keyword, or nothing on the chain records the name: the owner `def`s themselves (their symbol edges)
|
|
65
|
+
# and a definition appearing at a level the walk passed over (a name-keyed negative edge). A mixin or
|
|
66
|
+
# reopening added at the owner's level makes it opaque, and everything {#authoritative?} reads can only
|
|
67
|
+
# withhold, so none of these needs the file-level class edge the bucket reads would otherwise record.
|
|
68
|
+
def settle_by_definitions
|
|
69
|
+
return if @withheld.nil?
|
|
70
|
+
|
|
71
|
+
@owner_entries.each do |name, kind|
|
|
72
|
+
if kind == :singleton
|
|
73
|
+
@scope.user_singleton_def_site_for(name, @method_name)
|
|
74
|
+
else
|
|
75
|
+
@scope.user_def_site_for(name, @method_name)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
separator = @kind == :singleton ? "." : "#"
|
|
79
|
+
@passed.each { |name| Analysis::DependencyRecorder.read_missing(:method, "#{name}#{separator}#{@method_name}") }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# For every other verdict — a firing, or an opaque owner level whose opacity another file's edit can
|
|
83
|
+
# lift — the walk's reads are the dependency, so they are recorded as read.
|
|
84
|
+
def settle_by_walk
|
|
85
|
+
Analysis::DependencyRecorder.replay(@withheld) unless @withheld.nil?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Stage 2, for the class {#owner_envelope} last answered.
|
|
89
|
+
def authoritative?(class_name)
|
|
90
|
+
!load_order_dependent?(class_name) && !object_extension_may_shadow? &&
|
|
91
|
+
@levels.all? { |level| clean_level?(level) && public_at?(level.class_name) } &&
|
|
92
|
+
chain_free_of_hooks?(class_name) &&
|
|
93
|
+
subclasses_agree?(class_name, @envelope)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def walk_to_owner(class_name)
|
|
99
|
+
@levels = []
|
|
100
|
+
@passed = []
|
|
101
|
+
walked_whole_chain?(class_name) do |current|
|
|
102
|
+
level = level_for(current)
|
|
103
|
+
@levels << level
|
|
104
|
+
found = level_envelopes(level)
|
|
105
|
+
if found.empty?
|
|
106
|
+
@passed.concat([current] + level.modules)
|
|
107
|
+
next
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
envelope = found.first
|
|
111
|
+
@owner_entries = owner_entries(level)
|
|
112
|
+
unless found.all?(envelope) && !Source::ParameterEnvelope.opaque?(envelope)
|
|
113
|
+
@ambiguous = true
|
|
114
|
+
return nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
return @envelope = envelope
|
|
118
|
+
end
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def owner_entries(level)
|
|
123
|
+
own = @scope.parameter_envelopes_of(level.class_name).key?([@kind, @method_name])
|
|
124
|
+
entries = own ? [[level.class_name, @kind]] : []
|
|
125
|
+
level.modules.each do |mod|
|
|
126
|
+
entries << [mod, :instance] if @scope.parameter_envelopes_of(mod).key?([:instance, @method_name])
|
|
127
|
+
end
|
|
128
|
+
entries
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The survey corpus's own lesson. `PStore.new(path).transaction { … }` inside `module TDiary::IO`
|
|
132
|
+
# resolves `PStore` to the project's `TDiary::IO::PStore` when that class's file is loaded, and to the
|
|
133
|
+
# stdlib `::PStore` when it is not — and tdiary loads it only under one `io_class` setting, so under
|
|
134
|
+
# the default one the call is correct code. The analysis universe holds every file at once, so it
|
|
135
|
+
# always picks the inner class. Whenever the receiver's class is one of SEVERAL known classes the
|
|
136
|
+
# call site's `Module.nesting` spells with the same last segment, which one the receiver is depends
|
|
137
|
+
# on load order, and a verdict against the project class's `def` is not one the program backs.
|
|
138
|
+
def load_order_dependent?(class_name)
|
|
139
|
+
segment = class_name.to_s.split("::").last
|
|
140
|
+
candidates = Rigor::Reflection.lexical_nesting_chain(@scope).map { |entry| "#{entry}::#{segment}" }
|
|
141
|
+
candidates << segment
|
|
142
|
+
known = candidates.uniq.select { |name| known_class?(name) }
|
|
143
|
+
known.include?(class_name.to_s) && known.size > 1
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# A module some method body hands to `obj.extend` sits ahead of the object's class, so if it defines
|
|
147
|
+
# the name at all, that object answers with the module's definition.
|
|
148
|
+
def object_extension_may_shadow?
|
|
149
|
+
@scope.discovered_parameter_envelopes.any? do |_name, bucket|
|
|
150
|
+
bucket.key?(Scope::DiscoveryIndex::ENVELOPE_OBJECT_EXTENDED_MARK) && bucket.key?([:instance, @method_name])
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def known_class?(name)
|
|
155
|
+
@scope.discovered_classes.key?(name) || Rigor::Reflection.rbs_class_known?(name, scope: @scope)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# False when the walk stopped at the ADR-41 budget rather than at the top of the chain: budget
|
|
159
|
+
# exhaustion is uncertainty, and every caller reads it as a reason to decline.
|
|
160
|
+
def walked_whole_chain?(class_name)
|
|
161
|
+
current = class_name.to_s
|
|
162
|
+
seen = {}
|
|
163
|
+
while current && !seen[current]
|
|
164
|
+
return false if seen.size >= Scope::ANCESTOR_WALK_LIMIT
|
|
165
|
+
|
|
166
|
+
seen[current] = true
|
|
167
|
+
yield current
|
|
168
|
+
current = resolve(current, @scope.superclass_of(current))
|
|
169
|
+
end
|
|
170
|
+
true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def level_for(class_name)
|
|
174
|
+
modules = []
|
|
175
|
+
externals = []
|
|
176
|
+
collect_mixins(class_name, own_mixins(class_name), modules, externals, {})
|
|
177
|
+
Level.new(class_name: class_name, modules: modules, externals: externals)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Instance methods reach a receiver through `include` / `prepend`; class methods through `extend`, and
|
|
181
|
+
# an extended module's own `include`s reach the same singleton.
|
|
182
|
+
def own_mixins(class_name)
|
|
183
|
+
@kind == :singleton ? (@scope.discovered_extends[class_name] || []) : @scope.includes_of(class_name)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Issue #986 — a name the compact-header rename collision left ambiguous resolves to no ONE class,
|
|
187
|
+
# but BOTH classes it names are ancestors at runtime; only their MRO order is unknowable. Taking
|
|
188
|
+
# both as levels is what keeps this rule alive for the rest of the receiver: a method only one of
|
|
189
|
+
# them declares still has one envelope and still fires, a method they disagree about lands two on
|
|
190
|
+
# `#envelope_for`'s join and declines there, and the class's own `def`s, its superclass's and its
|
|
191
|
+
# unambiguous mixins are untouched. Letting the decline arrive as an unknown EXTERNAL mixin instead
|
|
192
|
+
# suppressed the rule for every level of the receiver.
|
|
193
|
+
def collect_mixins(owner, raw_names, modules, externals, seen)
|
|
194
|
+
raw_names.each do |raw|
|
|
195
|
+
resolved = resolve(owner, raw)
|
|
196
|
+
names = resolved ? [resolved] : @scope.ambiguous_ancestor_resolutions(owner, raw)
|
|
197
|
+
next externals << @scope.ancestor_name_candidates(owner, raw) if names.empty?
|
|
198
|
+
|
|
199
|
+
names.each do |name|
|
|
200
|
+
next if seen[name]
|
|
201
|
+
|
|
202
|
+
seen[name] = true
|
|
203
|
+
modules << name
|
|
204
|
+
collect_mixins(name, @scope.includes_of(name), modules, externals, seen)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def resolve(owner, raw)
|
|
210
|
+
return nil if raw.nil?
|
|
211
|
+
|
|
212
|
+
bucket = (@name_memo[owner] ||= {})
|
|
213
|
+
return bucket[raw] if bucket.key?(raw)
|
|
214
|
+
|
|
215
|
+
bucket[raw] = @scope.ancestor_name_candidates(owner, raw).find { |name| project_class?(name) }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def project_class?(name)
|
|
219
|
+
@scope.known_user_class?(name) || @scope.discovered_parameter_envelopes.key?(name)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# A module's methods reach the receiver as instance methods whichever side the receiver is on.
|
|
223
|
+
def level_envelopes(level)
|
|
224
|
+
own = @scope.parameter_envelopes_of(level.class_name)[[@kind, @method_name]]
|
|
225
|
+
mixed = level.modules.map { |mod| @scope.parameter_envelopes_of(mod)[[:instance, @method_name]] }
|
|
226
|
+
([own] + mixed).compact
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def clean_level?(level)
|
|
230
|
+
([level.class_name] + level.modules).none? { |name| dynamic_surface?(name) || project_patched?(name) } &&
|
|
231
|
+
level.externals.all? { |candidates| external_mixin_lacks_method?(candidates) }
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def dynamic_surface?(class_name)
|
|
235
|
+
Scope::DiscoveryIndex.rewritten_surface?(@scope.parameter_envelopes_of(class_name))
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def project_patched?(class_name)
|
|
239
|
+
environment = @scope.environment
|
|
240
|
+
patched = environment&.project_patched_methods
|
|
241
|
+
return true if patched && !patched.empty? && patch_names_method?(patched, class_name)
|
|
242
|
+
|
|
243
|
+
synthetic = environment&.synthetic_method_index
|
|
244
|
+
return false if synthetic.nil? || synthetic.empty?
|
|
245
|
+
|
|
246
|
+
synthetic.knows_class?(class_name) || !synthetic.lookup_instance(class_name, @method_name).empty? ||
|
|
247
|
+
!synthetic.lookup_singleton(class_name, @method_name).empty?
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def patch_names_method?(patched, class_name)
|
|
251
|
+
%i[instance singleton].any? do |kind|
|
|
252
|
+
!patched.lookup(class_name: class_name, method_name: @method_name, kind: kind).nil?
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# A mixin the project does not declare may define the name with any arity, and `prepend` lets it win
|
|
257
|
+
# over the class's own `def`. Only a module RBS knows, and whose declaration lacks the name, is
|
|
258
|
+
# evidence that it does not.
|
|
259
|
+
def external_mixin_lacks_method?(candidates)
|
|
260
|
+
name = candidates.find { |candidate| Rigor::Reflection.rbs_class_known?(candidate, scope: @scope) }
|
|
261
|
+
return false if name.nil?
|
|
262
|
+
|
|
263
|
+
Rigor::Reflection.instance_method_definition(name, @method_name, scope: @scope).nil?
|
|
264
|
+
rescue StandardError
|
|
265
|
+
false
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def public_at?(class_name)
|
|
269
|
+
visibility = @scope.discovered_method_visibility(class_name, @method_name)
|
|
270
|
+
visibility.nil? || visibility == :public
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# A hook anywhere in the chain, including above the owner, and on either side: a class that answers
|
|
274
|
+
# names it does not define is a class whose `def`s are not the whole story of what a call reaches.
|
|
275
|
+
def chain_free_of_hooks?(class_name)
|
|
276
|
+
walked_whole_chain?(class_name) do |current|
|
|
277
|
+
([current] + level_for(current).modules).each do |name|
|
|
278
|
+
envelopes = @scope.parameter_envelopes_of(name)
|
|
279
|
+
return false if dynamic_surface?(name)
|
|
280
|
+
return false if MISSING_HOOKS.any? { |hook| hook_recorded?(envelopes, hook) }
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def hook_recorded?(envelopes, hook)
|
|
286
|
+
envelopes.key?([:instance, hook]) || envelopes.key?([:singleton, hook])
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Every project class whose superclass chain reaches the receiver's class: a value typed as the
|
|
290
|
+
# receiver may be any of them, and each one's own definition of the name is what runs for it.
|
|
291
|
+
def subclasses_agree?(class_name, envelope)
|
|
292
|
+
each_subclass(class_name) do |subclass|
|
|
293
|
+
level = level_for(subclass)
|
|
294
|
+
return false unless clean_level?(level)
|
|
295
|
+
return false unless level_envelopes(level).all?(envelope)
|
|
296
|
+
end
|
|
297
|
+
true
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def each_subclass(class_name)
|
|
301
|
+
children = children_by_parent
|
|
302
|
+
queue = children.fetch(class_name.to_s, []).dup
|
|
303
|
+
seen = {}
|
|
304
|
+
until queue.empty?
|
|
305
|
+
subclass = queue.shift
|
|
306
|
+
next if seen[subclass]
|
|
307
|
+
|
|
308
|
+
seen[subclass] = true
|
|
309
|
+
yield subclass
|
|
310
|
+
queue.concat(children.fetch(subclass, []))
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def children_by_parent
|
|
315
|
+
@scope.discovered_superclasses.each_with_object({}) do |(child, raw_parent), children|
|
|
316
|
+
parent = resolve(child, raw_parent)
|
|
317
|
+
(children[parent] ||= []) << child if parent
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Analysis
|
|
7
|
+
module CheckRules
|
|
8
|
+
# Issue #1367 (ADR-117 Decision point 1, WD2) — what the interpreter's setter for each special global accepts:
|
|
9
|
+
# the envelope `global.write-type-mismatch` and `global.readonly-write` judge a write against.
|
|
10
|
+
#
|
|
11
|
+
# The setter is the authority, not the RBS declaration of the global. Where the two differ, the setter wins:
|
|
12
|
+
# `$;` is declared `Regexp | String | nil`, but its setter also takes any object with `to_str`, and `$stdout` is
|
|
13
|
+
# declared `IO`, but its setter takes anything that responds to `write`. Each entry names the CRuby function
|
|
14
|
+
# that enforces it and was confirmed on Ruby 4.0.5, whose error the comment quotes.
|
|
15
|
+
#
|
|
16
|
+
# A special whose setter accepts every value is absent, so a write to it is never checked: `$stdin`
|
|
17
|
+
# (ADR-117 WD2), `$_`, `$VERBOSE` / `$-v` / `$-w`, `$DEBUG` / `$-d` and `$=`. So is `$@`, whose setter raises
|
|
18
|
+
# `ArgumentError` or `TypeError` depending on whether `$!` is set when it runs. `$&`, `` $` ``, `$'`, `$+`
|
|
19
|
+
# and `$1`..`$9` cannot be assigned at all: that is a syntax error, not a write.
|
|
20
|
+
module SpecialGlobalSetters
|
|
21
|
+
# A setter that raises `TypeError` unless the value is an instance of one of `classes` (a subclass counts,
|
|
22
|
+
# since the setter tests the object's built-in type) or, when `conversion` names a method, responds to it:
|
|
23
|
+
# the implicit conversion the setter calls (`to_str`, `to_int`) or the method it asks `respond_to?` about
|
|
24
|
+
# (`write`). `accepts` is the phrase the diagnostic quotes.
|
|
25
|
+
Contract = Data.define(:classes, :conversion, :accepts) do
|
|
26
|
+
# Whether an instance of the core class `class_name` is one the setter takes by type. Both sides are core
|
|
27
|
+
# classes, whose hierarchy is Ruby's own, so the running interpreter answers it.
|
|
28
|
+
def accepts_class?(class_name)
|
|
29
|
+
klass = ::Object.const_get(class_name)
|
|
30
|
+
classes.any? { |accepted| klass <= ::Object.const_get(accepted) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# `rb_str_setter` (string.c), reached through `rb_deprecated_str_setter` and io.c's `deprecated_rs_setter`,
|
|
35
|
+
# tests `T_STRING` and calls no conversion: `$/ = 1` raises "value of $/ must be String", and so do a
|
|
36
|
+
# `Regexp` and an object with `to_str`.
|
|
37
|
+
STRING_OR_NIL = Contract.new(classes: %w[String NilClass].freeze, conversion: nil, accepts: "a String or nil")
|
|
38
|
+
# `rb_fs_setter` (string.c): nil, a String, a Regexp, or an object with `to_str` (`rb_fs_check`).
|
|
39
|
+
# `$; = 1` raises "value of $; must be String or Regexp".
|
|
40
|
+
FIELD_SEPARATOR = Contract.new(
|
|
41
|
+
classes: %w[String Regexp NilClass].freeze, conversion: :to_str,
|
|
42
|
+
accepts: "a String, a Regexp, nil, or an object with `to_str'"
|
|
43
|
+
)
|
|
44
|
+
# `match_setter` (re.c): nil or a MatchData (`Check_Type(val, T_MATCH)`). `$~ = 1` raises
|
|
45
|
+
# "wrong argument type Integer (expected MatchData)".
|
|
46
|
+
MATCH_DATA = Contract.new(classes: %w[MatchData NilClass].freeze, conversion: nil,
|
|
47
|
+
accepts: "a MatchData or nil")
|
|
48
|
+
# `set_arg0` (ruby.c) calls `StringValueCStr`: a String or an object with `to_str`, and not nil. `$0 = 1`
|
|
49
|
+
# raises "no implicit conversion of Integer into String".
|
|
50
|
+
PROGRAM_NAME = Contract.new(
|
|
51
|
+
classes: %w[String].freeze, conversion: :to_str, accepts: "a String or an object with `to_str'"
|
|
52
|
+
)
|
|
53
|
+
# `argf_lineno_setter` (io.c) calls `NUM2INT`: an Integer, a Float, or an object with `to_int`, and not nil.
|
|
54
|
+
# `$. = "1"` raises "no implicit conversion of String into Integer".
|
|
55
|
+
LINE_NUMBER = Contract.new(
|
|
56
|
+
classes: %w[Integer
|
|
57
|
+
Float].freeze, conversion: :to_int, accepts: "an Integer, a Float, or an object with `to_int'"
|
|
58
|
+
)
|
|
59
|
+
# `opt_i_set` (io.c) through `argf_inplace_mode_set`: nil or false turns in-place mode off; anything else
|
|
60
|
+
# goes through `StringValueCStr`. `$-i = 1` raises "no implicit conversion of Integer into String".
|
|
61
|
+
INPLACE_MODE = Contract.new(
|
|
62
|
+
classes: %w[String NilClass FalseClass].freeze, conversion: :to_str,
|
|
63
|
+
accepts: "a String, nil, false, or an object with `to_str'"
|
|
64
|
+
)
|
|
65
|
+
# `stdout_setter` / `stderr_setter` (io.c) call `must_respond_to(id_write, …)`, which asks `respond_to?` and so
|
|
66
|
+
# honours `respond_to_missing?`. `$stdout = 1` raises "$stdout must have write method, Integer given".
|
|
67
|
+
# ADR-117 WD2: this `_Writer` requirement is the only contract a stream write is checked against.
|
|
68
|
+
WRITER = Contract.new(classes: [].freeze, conversion: :write, accepts: "an object that responds to `write'")
|
|
69
|
+
|
|
70
|
+
CONTRACTS = {
|
|
71
|
+
"$/": STRING_OR_NIL,
|
|
72
|
+
"$-0": STRING_OR_NIL,
|
|
73
|
+
"$,": STRING_OR_NIL,
|
|
74
|
+
"$\\": STRING_OR_NIL,
|
|
75
|
+
"$;": FIELD_SEPARATOR,
|
|
76
|
+
"$-F": FIELD_SEPARATOR,
|
|
77
|
+
"$~": MATCH_DATA,
|
|
78
|
+
"$0": PROGRAM_NAME,
|
|
79
|
+
"$PROGRAM_NAME": PROGRAM_NAME,
|
|
80
|
+
"$.": LINE_NUMBER,
|
|
81
|
+
"$-i": INPLACE_MODE,
|
|
82
|
+
"$stdout": WRITER,
|
|
83
|
+
"$>": WRITER,
|
|
84
|
+
"$stderr": WRITER
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
# The specials whose setter is `rb_gvar_readonly_setter` — declared with `rb_define_readonly_variable`, as a
|
|
88
|
+
# virtual variable without a setter, or with that setter by name. Any write raises `NameError`
|
|
89
|
+
# ("$! is a read-only variable"), whatever the value.
|
|
90
|
+
#
|
|
91
|
+
# - `$!` (eval.c); `$$` and `$?` (process.c).
|
|
92
|
+
# - `$<`, `$FILENAME` and `$*` (io.c, ARGF).
|
|
93
|
+
# - `$:` / `$LOAD_PATH` / `$-I`, `$"` / `$LOADED_FEATURES` (load.c).
|
|
94
|
+
# - `$-W`, and the `-p` / `-l` / `-a` switches `$-p`, `$-l` and `$-a` (ruby.c).
|
|
95
|
+
READ_ONLY = Set[
|
|
96
|
+
:$!, :$$, :$?, :$<, :$FILENAME, :$*, :$:, :$LOAD_PATH, :$-I, :$", :$LOADED_FEATURES,
|
|
97
|
+
:$-W, :$-p, :$-l, :$-a
|
|
98
|
+
].freeze
|
|
99
|
+
|
|
100
|
+
# The literal nodes `global.write-type-mismatch` judges, by the core class of the object each evaluates to.
|
|
101
|
+
# An interpolated String, Symbol or Regexp is still a new instance of that class.
|
|
102
|
+
LITERAL_CLASSES = {
|
|
103
|
+
Prism::IntegerNode => "Integer", Prism::FloatNode => "Float", Prism::RationalNode => "Rational",
|
|
104
|
+
Prism::ImaginaryNode => "Complex", Prism::StringNode => "String", Prism::InterpolatedStringNode => "String",
|
|
105
|
+
Prism::SymbolNode => "Symbol", Prism::InterpolatedSymbolNode => "Symbol", Prism::ArrayNode => "Array",
|
|
106
|
+
Prism::HashNode => "Hash", Prism::RegularExpressionNode => "Regexp",
|
|
107
|
+
Prism::InterpolatedRegularExpressionNode => "Regexp", Prism::NilNode => "NilClass",
|
|
108
|
+
Prism::TrueNode => "TrueClass", Prism::FalseNode => "FalseClass"
|
|
109
|
+
}.freeze
|
|
110
|
+
|
|
111
|
+
# How the diagnostic names a literal of each class.
|
|
112
|
+
LITERAL_DESCRIPTIONS = { "NilClass" => "nil", "TrueClass" => "true", "FalseClass" => "false" }.freeze
|
|
113
|
+
|
|
114
|
+
module_function
|
|
115
|
+
|
|
116
|
+
# The setter contract a write of `name` is checked against, or nil when its setter accepts every value or the
|
|
117
|
+
# global is no special.
|
|
118
|
+
def contract_for(name)
|
|
119
|
+
CONTRACTS[name]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# The core class of the object `node` evaluates to when it is a literal ({LITERAL_CLASSES}), looking
|
|
123
|
+
# through parentheses around a single expression, or nil for any other expression.
|
|
124
|
+
def literal_class(node)
|
|
125
|
+
while node.is_a?(Prism::ParenthesesNode)
|
|
126
|
+
body = node.body
|
|
127
|
+
return nil unless body.is_a?(Prism::StatementsNode) && body.body.size == 1
|
|
128
|
+
|
|
129
|
+
node = body.body.first
|
|
130
|
+
end
|
|
131
|
+
LITERAL_CLASSES[node.class]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def literal_description(class_name)
|
|
135
|
+
LITERAL_DESCRIPTIONS.fetch(class_name) do
|
|
136
|
+
"#{class_name.start_with?('A', 'E', 'I', 'O', 'U') ? 'an' : 'a'} #{class_name} literal"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def read_only?(name)
|
|
141
|
+
READ_ONLY.include?(name)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "../../inference/narrowing"
|
|
6
7
|
require_relative "inferred_param_guard"
|
|
7
8
|
|
|
8
9
|
module Rigor
|
|
@@ -29,6 +30,12 @@ module Rigor
|
|
|
29
30
|
# The false-positive envelope mirrors `flow.always-truthy-condition`: clauses inside loops / blocks are
|
|
30
31
|
# skipped (mutation tracking through those is incomplete), and the rule reads the engine's own
|
|
31
32
|
# narrowing rather than recomputing it, so the diagnostic and the body typing can never diverge.
|
|
33
|
+
#
|
|
34
|
+
# Issue #1429 — a `:disjoint` clause is not reported when its `Bot` rests on a `Nominal` subject member whose
|
|
35
|
+
# class is disjoint from a condition's ({Inference::Narrowing.disjoint_nominal_guard?}): the `when C` or `in C`
|
|
36
|
+
# is code evidence that the subject can hold such an object (`io = STDOUT; case io when StringIO`), and Rigor's
|
|
37
|
+
# reading of the subject's type is what the evidence contradicts. The body scope stays `Bot`, so no call on the
|
|
38
|
+
# subject in it is checked. A subject that is a literal carrier (`x = 1; case x when String`) is still reported.
|
|
32
39
|
class UnreachableClauseCollector
|
|
33
40
|
LOOP_OR_BLOCK_NODE_CLASSES = [
|
|
34
41
|
Prism::WhileNode, Prism::UntilNode, Prism::ForNode, Prism::BlockNode
|
|
@@ -42,7 +49,7 @@ module Rigor
|
|
|
42
49
|
DEFENSIVE_EXIT_CALLS = %i[raise fail throw abort exit].freeze
|
|
43
50
|
|
|
44
51
|
# @!attribute kind
|
|
45
|
-
# @return
|
|
52
|
+
# @return `:disjoint` (this clause's type is disjoint
|
|
46
53
|
# from the still-possible subject), `:prior_exhaustion` (an
|
|
47
54
|
# earlier clause already covered the subject), or
|
|
48
55
|
# `:exhausted_else` (every subject value is covered, so the
|
|
@@ -63,7 +70,7 @@ module Rigor
|
|
|
63
70
|
|
|
64
71
|
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
65
72
|
# {RuleWalk} against; deleted when Track B completes.
|
|
66
|
-
# @return
|
|
73
|
+
# @return one entry per provably-dead `when` clause.
|
|
67
74
|
def collect(root)
|
|
68
75
|
walk(root, in_loop_or_block: false)
|
|
69
76
|
@results.freeze
|
|
@@ -139,13 +146,30 @@ module Rigor
|
|
|
139
146
|
return if scope.nil?
|
|
140
147
|
return unless scope.local(subject_name).is_a?(Type::Bot)
|
|
141
148
|
|
|
149
|
+
kind = when_clause_kind(clause, subject_name)
|
|
150
|
+
return if kind == :disjoint &&
|
|
151
|
+
disjoint_nominal_subject?(@scope_index[clause.conditions.first], subject_name, clause.conditions)
|
|
152
|
+
|
|
142
153
|
@results << Result.new(
|
|
143
154
|
clause: clause, body: clause.statements, subject_name: subject_name,
|
|
144
155
|
condition_source: clause.conditions.map(&:slice).join(", "),
|
|
145
|
-
kind:
|
|
156
|
+
kind: kind, keyword: "when"
|
|
146
157
|
)
|
|
147
158
|
end
|
|
148
159
|
|
|
160
|
+
# True when the subject, as `entry` binds it, holds a non-literal `Nominal` whose class the class one of
|
|
161
|
+
# `patterns` names is disjoint from (issue #1429, see the class comment).
|
|
162
|
+
def disjoint_nominal_subject?(entry, subject_name, patterns)
|
|
163
|
+
subject_type = entry&.local(subject_name)
|
|
164
|
+
return false if subject_type.nil?
|
|
165
|
+
|
|
166
|
+
patterns.any? do |condition|
|
|
167
|
+
class_name = Inference::Narrowing.lexical_class_name(condition, entry)
|
|
168
|
+
class_name &&
|
|
169
|
+
Inference::Narrowing.disjoint_nominal_guard?(subject_type, class_name, environment: entry.environment)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
149
173
|
# A dead `when` is `:prior_exhaustion` when the subject was already narrowed to `bot` BEFORE this
|
|
150
174
|
# clause (an earlier clause covered it) — read from the entry scope the engine recorded on the
|
|
151
175
|
# clause's first condition node (ADR-47 WD2). Otherwise the subject was still concrete entering the
|
|
@@ -170,13 +194,23 @@ module Rigor
|
|
|
170
194
|
return if scope.nil?
|
|
171
195
|
return unless scope.local(subject_name).is_a?(Type::Bot)
|
|
172
196
|
|
|
197
|
+
kind = in_clause_kind(clause, subject_name)
|
|
198
|
+
return if kind == :disjoint && disjoint_nominal_pattern?(clause, subject_name)
|
|
199
|
+
|
|
173
200
|
@results << Result.new(
|
|
174
201
|
clause: clause, body: clause.statements, subject_name: subject_name,
|
|
175
202
|
condition_source: clause.pattern.slice,
|
|
176
|
-
kind:
|
|
203
|
+
kind: kind, keyword: "in"
|
|
177
204
|
)
|
|
178
205
|
end
|
|
179
206
|
|
|
207
|
+
# {#disjoint_nominal_subject?} for a bare class pattern, `in C` or `in C => x`.
|
|
208
|
+
def disjoint_nominal_pattern?(clause, subject_name)
|
|
209
|
+
pattern = clause.pattern
|
|
210
|
+
pattern = pattern.value if pattern.is_a?(Prism::CapturePatternNode)
|
|
211
|
+
disjoint_nominal_subject?(@scope_index[clause.pattern], subject_name, [pattern])
|
|
212
|
+
end
|
|
213
|
+
|
|
180
214
|
def in_clause_kind(clause, subject_name)
|
|
181
215
|
entry = @scope_index[clause.pattern]
|
|
182
216
|
return :prior_exhaustion if entry && entry.local(subject_name).is_a?(Type::Bot)
|
|
@@ -52,7 +52,6 @@ module Rigor
|
|
|
52
52
|
# Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
|
|
53
53
|
# {RuleWalk} against. Walks the whole subtree once, checking each consumer node's value slots.
|
|
54
54
|
# Returns one {Result} per value-context use of a recovered-`void` call.
|
|
55
|
-
# @return [Array<Result>]
|
|
56
55
|
def collect(root)
|
|
57
56
|
walk(root)
|
|
58
57
|
@results.freeze
|