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,329 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../source/constant_path"
|
|
6
|
+
require_relative "../../source/node_children"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
module LastLine
|
|
11
|
+
# Issue #1415 (ADR-117 WD5) — what a file shows about the `self` its implicit-self and `self.` readers run with,
|
|
12
|
+
# read in one walk of the file the first time a reader condition asks ({ImplicitSelf.reader?}), and kept only
|
|
13
|
+
# once complete. The walk runs only for a file that holds such a condition. `Inference::ScopeIndexer` keeps one
|
|
14
|
+
# per file on the discovery index ({Scope::DiscoveryIndex#implicit_self_evidence}), so a callee body another
|
|
15
|
+
# file wrote finds none of its readers here and declines.
|
|
16
|
+
#
|
|
17
|
+
# Each reader is placed in the body whose `self` it runs with: `main` (the script body, a top-level method, a
|
|
18
|
+
# `class << self` at the top level), or a class or module body and its methods. A reader has none, and
|
|
19
|
+
# declines, where the file may give it another `self`:
|
|
20
|
+
#
|
|
21
|
+
# - in a block of `instance_eval`, `instance_exec`, `class_eval`, `module_eval`, `class_exec`, `module_exec`,
|
|
22
|
+
# `define_method` or `define_singleton_method` (or a `send` that may name one), or of `Class.new`,
|
|
23
|
+
# `Module.new`, `Struct.new` or `Data.define`, whose block is a class body;
|
|
24
|
+
# - in a method written inside a block or another method, whose owner is the block's or method's `self`, in a
|
|
25
|
+
# module's instance method, whose `self` is whatever includes the module, and in `class << obj` for any `obj`
|
|
26
|
+
# but `self`, or in a `class << self` a method runs, whose `self` is the method's receiver.
|
|
27
|
+
#
|
|
28
|
+
# A class or module is dirty ({#dirty?}), and every reader whose `self`'s ancestry reaches it declines, when a
|
|
29
|
+
# body of it may give it an ancestor the discovery tables do not record: a superclass that is not a constant
|
|
30
|
+
# (`DelegateClass(File)`, `Struct.new(:a)`), and an `include`, `extend` or `prepend` other than a statement of
|
|
31
|
+
# the body with no receiver naming constants (`self.include(M)`, one in a method, a block or `class << self`,
|
|
32
|
+
# `singleton_class.include(M)`, `include(*mods)`). Dirtiness is kept by the declaration's last name segment,
|
|
33
|
+
# so a reopening, a subclass and an includer of the class see it too, and a same-named class elsewhere
|
|
34
|
+
# declines with it.
|
|
35
|
+
#
|
|
36
|
+
# A name is tainted, and every reader of it in the file declines, when the file may change the method that name
|
|
37
|
+
# reaches from `main`, `Object` or an object the analyzer does not follow:
|
|
38
|
+
#
|
|
39
|
+
# - both names: a mixin into `main` (`include`, `extend` or `prepend` on implicit `self` outside a class or
|
|
40
|
+
# module body, `class << self; include M; end` at the top level, `singleton_class.include(M)`), a mixin on
|
|
41
|
+
# any explicit receiver (`Object.include(M)`, `TOPLEVEL_BINDING.receiver.extend(M)`, `obj.extend(M)`), any
|
|
42
|
+
# mixin in an `Object`, `Kernel` or `BasicObject` body, a `send` that may name one, a method object taken
|
|
43
|
+
# for a mixin or a `self`-rebinding call (`method(:extend)`, `Module.instance_method(:include)`), any
|
|
44
|
+
# `using`, a proc handed to one of the `self`-rebinding calls above (`obj.instance_exec(&blk)`,
|
|
45
|
+
# `X.send(:define_method, :m, blk)`), whose body may be a block the file wrote anywhere, an eval of a
|
|
46
|
+
# String, whose code may do any of these, and a `const_set`, which may make a class a `class` body reopens;
|
|
47
|
+
# - one name: a `def` of it anywhere (`def self.gets` at the top level defines it on `main`, which
|
|
48
|
+
# {BlockCallTiming.project_defines_anywhere?} does not record), and a Symbol or String literal naming it,
|
|
49
|
+
# which a macro may define it through (`def_delegators :@io, :gets`, `attr_reader :gets`,
|
|
50
|
+
# `undef_method :gets`), other than `&:gets` and the first argument of a call that only names a method
|
|
51
|
+
# (`respond_to?(:gets)`, `send(:gets)`).
|
|
52
|
+
class SelfEvidence
|
|
53
|
+
# The context of a reader whose `self` is `main`.
|
|
54
|
+
MAIN = :main
|
|
55
|
+
UNKNOWN = :unknown
|
|
56
|
+
READER_NAMES = Set["gets", "readline"].freeze
|
|
57
|
+
MIXINS = Set[:include, :extend, :prepend].freeze
|
|
58
|
+
SENT_MIXINS = Set["include", "extend", "prepend", "using"].freeze
|
|
59
|
+
SENDS = Set[:send, :__send__, :public_send].freeze
|
|
60
|
+
REBINDERS = Set[
|
|
61
|
+
:instance_eval, :instance_exec, :class_eval, :module_eval, :class_exec, :module_exec, :define_method,
|
|
62
|
+
:define_singleton_method
|
|
63
|
+
].freeze
|
|
64
|
+
REBINDER_NAMES = REBINDERS.to_set(&:to_s).freeze
|
|
65
|
+
# `using` refines for the rest of the file, and `const_set` may make a class a `class` body then reopens
|
|
66
|
+
# (`Object.const_set(:W, Class.new(CSV))`), whatever the receiver.
|
|
67
|
+
FILE_WIDE = Set[:using, :const_set].freeze
|
|
68
|
+
# The rebinders a proc argument can give a body to (`define_method(:m, blk)`).
|
|
69
|
+
BODY_DEFINERS = Set[:define_method, :define_singleton_method].freeze
|
|
70
|
+
BODY_DEFINER_NAMES = BODY_DEFINERS.to_set(&:to_s).freeze
|
|
71
|
+
CONSTRUCTORS = { Class: :new, Module: :new, Struct: :new, Data: :define }.freeze
|
|
72
|
+
# The calls that take a method object, which a later `call` or `bind_call` runs with any receiver.
|
|
73
|
+
METHOD_OBJECTS = Set[:method, :public_method, :singleton_method, :instance_method, :public_instance_method]
|
|
74
|
+
.freeze
|
|
75
|
+
METHOD_OBJECT_NAMES = (SENT_MIXINS | REBINDER_NAMES).freeze
|
|
76
|
+
# Calls whose first argument names a method without putting one in place.
|
|
77
|
+
NAMING_CALLS = Set[:send, :__send__, :public_send, :respond_to?, :method, :public_method].freeze
|
|
78
|
+
ROOTS = Set["Object", "Kernel", "BasicObject"].freeze
|
|
79
|
+
private_constant :UNKNOWN, :READER_NAMES, :MIXINS, :SENT_MIXINS, :SENDS, :REBINDERS, :REBINDER_NAMES,
|
|
80
|
+
:FILE_WIDE, :BODY_DEFINERS, :BODY_DEFINER_NAMES, :METHOD_OBJECTS, :METHOD_OBJECT_NAMES,
|
|
81
|
+
:CONSTRUCTORS, :NAMING_CALLS, :ROOTS
|
|
82
|
+
|
|
83
|
+
def initialize(root)
|
|
84
|
+
@root = root
|
|
85
|
+
@scan = nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# {MAIN}, the `Prism::ClassNode` or `Prism::ModuleNode` whose body the implicit-self reader `call_node` runs
|
|
89
|
+
# in, or nil: the file may give it another `self`, or it is not one of this file's readers.
|
|
90
|
+
def context(call_node) = scan.context(call_node)
|
|
91
|
+
|
|
92
|
+
# True when a class or module of `class_name`'s last name segment is dirty in this file.
|
|
93
|
+
def dirty?(class_name) = scan.dirty?(class_name)
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def scan = (@scan ||= Scan.new(@root))
|
|
98
|
+
|
|
99
|
+
# One walk of the file, whose answers {SelfEvidence} keeps once the walk is complete.
|
|
100
|
+
class Scan
|
|
101
|
+
def initialize(root)
|
|
102
|
+
@contexts = {}.compare_by_identity
|
|
103
|
+
@dirty = Set.new
|
|
104
|
+
@tainted = Set.new
|
|
105
|
+
visit(root, MAIN, false, false, false)
|
|
106
|
+
@contexts.freeze
|
|
107
|
+
@dirty.freeze
|
|
108
|
+
@tainted.freeze
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def context(call_node)
|
|
112
|
+
return nil if @tainted.include?(call_node.name)
|
|
113
|
+
|
|
114
|
+
context = @contexts[call_node]
|
|
115
|
+
context unless context.is_a?(Prism::Node) && @dirty.include?(segment(context))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def dirty?(class_name) = @dirty.include?(class_name.to_s.split("::").last)
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# `context` is {MAIN}, a declaration node or {UNKNOWN}; `statement` is true for a statement of a class or
|
|
123
|
+
# module body itself; `block` is true inside a block or lambda of the body; `singleton` is true inside a
|
|
124
|
+
# `class << self` body.
|
|
125
|
+
def visit(node, context, statement, block, singleton)
|
|
126
|
+
case node
|
|
127
|
+
when Prism::ClassNode, Prism::ModuleNode then visit_declaration(node, context, block, singleton)
|
|
128
|
+
when Prism::SingletonClassNode then visit_singleton_class(node, context, block)
|
|
129
|
+
when Prism::DefNode then visit_def(node, context, block, singleton)
|
|
130
|
+
when Prism::CallNode then visit_call(node, context, statement, block, singleton)
|
|
131
|
+
when Prism::BlockNode, Prism::LambdaNode then visit_children(node, context, true, singleton)
|
|
132
|
+
when Prism::BlockArgumentNode then visit_block_argument(node, context, block, singleton)
|
|
133
|
+
when Prism::SymbolNode, Prism::StringNode then taint(node.unescaped)
|
|
134
|
+
else visit_children(node, context, block, singleton)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def visit_children(node, context, block, singleton)
|
|
139
|
+
node.rigor_each_child { |child| visit(child, context, false, block, singleton) }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def visit_declaration(node, context, block, singleton)
|
|
143
|
+
visit(node.constant_path, context, false, block, singleton)
|
|
144
|
+
superclass = node.is_a?(Prism::ClassNode) ? node.superclass : nil
|
|
145
|
+
if superclass
|
|
146
|
+
visit(superclass, context, false, block, singleton)
|
|
147
|
+
dirty(node) unless constant?(superclass)
|
|
148
|
+
end
|
|
149
|
+
visit_body(node.body, node)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def visit_body(body, declaration)
|
|
153
|
+
if body.is_a?(Prism::StatementsNode)
|
|
154
|
+
body.body.each { |statement| visit(statement, declaration, true, false, false) }
|
|
155
|
+
elsif body
|
|
156
|
+
visit(body, declaration, false, false, false)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# `class << self` keeps the body's `self` for the methods it defines; `class << obj` gives them `obj`, and
|
|
161
|
+
# so does `class << self` in a method or a block, whose `self` is the method's receiver or the block's.
|
|
162
|
+
def visit_singleton_class(node, context, block)
|
|
163
|
+
visit(node.expression, context, false, block, false)
|
|
164
|
+
inner = node.expression.is_a?(Prism::SelfNode) && !block ? context : UNKNOWN
|
|
165
|
+
visit(node.body, inner, false, false, true) if node.body
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def visit_def(node, context, block, singleton)
|
|
169
|
+
taint(node.name.to_s)
|
|
170
|
+
receiver = node.receiver
|
|
171
|
+
visit(receiver, context, false, block, singleton) if receiver
|
|
172
|
+
inner = def_context(receiver, context, block, singleton)
|
|
173
|
+
visit(node.parameters, inner, false, true, false) if node.parameters
|
|
174
|
+
visit(node.body, inner, false, true, false) if node.body
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def def_context(receiver, context, block, singleton)
|
|
178
|
+
return UNKNOWN if block || context == UNKNOWN
|
|
179
|
+
return context if receiver.is_a?(Prism::SelfNode)
|
|
180
|
+
return UNKNOWN unless receiver.nil?
|
|
181
|
+
|
|
182
|
+
context.is_a?(Prism::ModuleNode) && !singleton ? UNKNOWN : context
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def visit_call(node, context, statement, block, singleton)
|
|
186
|
+
classify(node, context, statement)
|
|
187
|
+
@contexts[node] = context if implicit_reader?(node) && context != UNKNOWN
|
|
188
|
+
visit(node.receiver, context, false, block, singleton) if node.receiver
|
|
189
|
+
visit_arguments(node, context, block, singleton)
|
|
190
|
+
visit_call_block(node, context, block, singleton)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def visit_arguments(node, context, block, singleton)
|
|
194
|
+
arguments = node.arguments&.arguments
|
|
195
|
+
return if arguments.nil?
|
|
196
|
+
|
|
197
|
+
arguments.each_with_index do |argument, index|
|
|
198
|
+
next if index.zero? && NAMING_CALLS.include?(node.name) && literal?(argument)
|
|
199
|
+
|
|
200
|
+
visit(argument, context, false, block, singleton)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def visit_call_block(node, context, block, singleton)
|
|
205
|
+
call_block = node.block
|
|
206
|
+
case call_block
|
|
207
|
+
when Prism::BlockNode then visit(call_block, rebinder?(node) ? UNKNOWN : context, false, true, singleton)
|
|
208
|
+
when Prism::BlockArgumentNode
|
|
209
|
+
taint_all if rebinder?(node) && !call_block.expression.is_a?(Prism::SymbolNode)
|
|
210
|
+
visit(call_block, context, false, block, singleton)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# `&:gets` runs the element's own reader.
|
|
215
|
+
def visit_block_argument(node, context, block, singleton)
|
|
216
|
+
expression = node.expression
|
|
217
|
+
return if expression.nil? || expression.is_a?(Prism::SymbolNode)
|
|
218
|
+
|
|
219
|
+
visit(expression, context, false, block, singleton)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def classify(node, context, statement)
|
|
223
|
+
name = node.name
|
|
224
|
+
if MIXINS.include?(name) then mixin(node, context, statement)
|
|
225
|
+
elsif taints_file?(name, node.arguments&.arguments) then taint_all
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# `using`, `const_set`, an eval of a String, which may run any of the shapes this walk reads, a proc handed
|
|
230
|
+
# to a definer, a `send` that may name a mixin, and a method object taken for a mixin or a rebinder.
|
|
231
|
+
def taints_file?(name, arguments)
|
|
232
|
+
return true if FILE_WIDE.include?(name)
|
|
233
|
+
return false if arguments.nil?
|
|
234
|
+
return true if EVALS.include?(name) || (BODY_DEFINERS.include?(name) && arguments.size > 1)
|
|
235
|
+
return sent_mixin?(arguments) if SENDS.include?(name)
|
|
236
|
+
|
|
237
|
+
METHOD_OBJECTS.include?(name) && literal?(arguments.first) &&
|
|
238
|
+
METHOD_OBJECT_NAMES.include?(arguments.first.unescaped)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def mixin(node, context, statement)
|
|
242
|
+
receiver = node.receiver
|
|
243
|
+
declaration = context.is_a?(Prism::ClassNode) || context.is_a?(Prism::ModuleNode)
|
|
244
|
+
if declaration && !root?(context) && (receiver.nil? || self_or_singleton_class?(receiver))
|
|
245
|
+
dirty(context) unless receiver.nil? && statement && constant_arguments?(node)
|
|
246
|
+
else
|
|
247
|
+
taint_all
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def sent_mixin?(arguments)
|
|
252
|
+
first = arguments&.first
|
|
253
|
+
case first
|
|
254
|
+
when nil then false
|
|
255
|
+
when Prism::SymbolNode, Prism::StringNode
|
|
256
|
+
name = first.unescaped
|
|
257
|
+
SENT_MIXINS.include?(name) || (BODY_DEFINER_NAMES.include?(name) && arguments.size > 2)
|
|
258
|
+
when Prism::SplatNode then true
|
|
259
|
+
else arguments.size > 1
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# A rebinder, a `send` that may name one (`obj.send(:instance_eval) { … }`), or a metaclass constructor.
|
|
264
|
+
def rebinder?(node)
|
|
265
|
+
name = node.name
|
|
266
|
+
return true if REBINDERS.include?(name)
|
|
267
|
+
return sent_rebinder?(node.arguments&.arguments&.first) if SENDS.include?(name)
|
|
268
|
+
|
|
269
|
+
receiver = node.receiver
|
|
270
|
+
constructor =
|
|
271
|
+
case receiver
|
|
272
|
+
when Prism::ConstantReadNode then receiver.name
|
|
273
|
+
when Prism::ConstantPathNode then receiver.name if receiver.parent.nil?
|
|
274
|
+
end
|
|
275
|
+
!constructor.nil? && CONSTRUCTORS[constructor] == name
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def sent_rebinder?(first)
|
|
279
|
+
literal?(first) ? REBINDER_NAMES.include?(first.unescaped) : true
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def implicit_reader?(node)
|
|
283
|
+
LastLine::READERS.include?(node.name) && node.block.nil? &&
|
|
284
|
+
(node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode))
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# `self.include(M)` and `singleton_class.include(M)`, which the discovery tables do not record.
|
|
288
|
+
def self_or_singleton_class?(receiver)
|
|
289
|
+
return true if receiver.is_a?(Prism::SelfNode)
|
|
290
|
+
|
|
291
|
+
receiver.is_a?(Prism::CallNode) && receiver.name == :singleton_class &&
|
|
292
|
+
(receiver.receiver.nil? || receiver.receiver.is_a?(Prism::SelfNode))
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def dirty(declaration)
|
|
296
|
+
@dirty << segment(declaration)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def segment(declaration) = declaration.constant_path.slice.split("::").last
|
|
300
|
+
|
|
301
|
+
def root?(declaration) = ROOTS.include?(segment(declaration))
|
|
302
|
+
|
|
303
|
+
# `extend self` adds the module's own methods, which the program's `def`s already answer for.
|
|
304
|
+
def constant_arguments?(node)
|
|
305
|
+
arguments = node.arguments&.arguments
|
|
306
|
+
!arguments.nil? && !arguments.empty? &&
|
|
307
|
+
arguments.all? { |argument| constant?(argument) || argument.is_a?(Prism::SelfNode) }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def constant?(node)
|
|
311
|
+
(node.is_a?(Prism::ConstantReadNode) || node.is_a?(Prism::ConstantPathNode)) &&
|
|
312
|
+
!Source::ConstantPath.qualified_name_or_nil(node).nil?
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def literal?(node) = node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
316
|
+
|
|
317
|
+
def taint(name)
|
|
318
|
+
@tainted << name.to_sym if READER_NAMES.include?(name)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def taint_all
|
|
322
|
+
@tainted.merge(LastLine::READERS)
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
private_constant :Scan
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
require_relative "../reflection"
|
|
7
|
+
require_relative "../type"
|
|
8
|
+
require_relative "block_call_timing"
|
|
9
|
+
require_relative "fresh_frame_blocks"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Inference
|
|
13
|
+
# Issue #1359 — which code may set `$_`, the last line read, that a scope's narrowing speaks for. Ruby keeps it in
|
|
14
|
+
# the method frame's special-variable slot beside `$~`, so the frame rules are the match globals'
|
|
15
|
+
# ({MatchRebinding}): a `def`, class, module or file body has a slot of its own; a block, and a closure made in
|
|
16
|
+
# the body, shares it; a call into a method defined in Ruby writes that method's slot, never its caller's; and
|
|
17
|
+
# the root block of a thread, fiber or ractor has one of its own ({FreshFrameBlocks}). Only a C-implemented
|
|
18
|
+
# reader sets its caller's `$_`, to the line it returns: `Kernel#gets` / `#readline` and the same methods of
|
|
19
|
+
# `IO`, `StringIO`, `ARGF` and `Zlib::GzipReader`; `IO.foreach` sets it to each line it yields. `each_line` and
|
|
20
|
+
# `readlines` leave it alone.
|
|
21
|
+
#
|
|
22
|
+
# A call is read two ways, as the match globals' calls are:
|
|
23
|
+
#
|
|
24
|
+
# - {.reads_line?}: the call certainly is such a reader, run in this frame, so a condition on its value narrows
|
|
25
|
+
# `$_` on both edges ({Narrowing}). Apart from a write, that is the only place `$_` is bound.
|
|
26
|
+
# - {.may_set?}: running the code may set `$_` in this frame, so a narrowing of it is forgotten: a reader's or
|
|
27
|
+
# `foreach`'s name on any receiver (a `Dynamic` one, or one whose method turns out to be defined in Ruby, which
|
|
28
|
+
# only costs the narrowing), a `send` that may name one, an eval of a String, a write, or a block argument that
|
|
29
|
+
# may run one.
|
|
30
|
+
#
|
|
31
|
+
# A reader that is not a condition leaves `$_` unbound, not bound to its `String?`: nothing proves the line
|
|
32
|
+
# non-nil there, and a `String?` would report correct code that checks the reader's value another way before
|
|
33
|
+
# it reads `$_` (`line = gets; return unless line; $_.chomp`). The scans run only while `$_` is bound.
|
|
34
|
+
module LastLine
|
|
35
|
+
# The C readers' names.
|
|
36
|
+
READERS = Set[:gets, :readline].freeze
|
|
37
|
+
# Every name that may set its caller's `$_`, on any receiver: the readers, and `IO.foreach`, which sets it to
|
|
38
|
+
# each line before it yields the line and to nil once the input ends. Compared as Strings where a literal
|
|
39
|
+
# names one.
|
|
40
|
+
SETTERS = (READERS | Set[:foreach]).freeze
|
|
41
|
+
SETTER_NAMES = SETTERS.to_set(&:to_s).freeze
|
|
42
|
+
# The `Kernel` functions `ruby -n` / `-p` adds, which edit `$_` in place of an implicit-self receiver.
|
|
43
|
+
LINE_EDITORS = Set[:sub, :gsub, :chop, :chomp].freeze
|
|
44
|
+
# `$stdin`, and `$<`, which is `ARGF`, while the file binds them to nothing but a reader.
|
|
45
|
+
READER_GLOBALS = Set[:$stdin, :$<].freeze
|
|
46
|
+
# The RBS owners of a C reader. `Kernel`'s is not one: it is private, and RBS places there any reader it does not
|
|
47
|
+
# declare (`CSV#gets`, an alias of its Ruby `shift`, reads as `Kernel`'s). `Kernel.gets` itself is read by the
|
|
48
|
+
# receiver's type.
|
|
49
|
+
READER_OWNERS = Set["::IO", "::StringIO", "::RBS::Unnamed::ARGFClass", "::Zlib::GzipReader"].freeze
|
|
50
|
+
# RBS declares `Tempfile < File`, but its methods are `DelegateClass(File)`'s Ruby forwarders, so its `gets`
|
|
51
|
+
# sets the forwarder's `$_` and never its caller's.
|
|
52
|
+
DELEGATING_CLASSES = %w[Tempfile].freeze
|
|
53
|
+
DELEGATING_ORDERINGS = Set[:equal, :subclass].freeze
|
|
54
|
+
SENDS = Set[:send, :__send__, :public_send].freeze
|
|
55
|
+
# These run a String of code in the frame that calls them; their block form is a block, scanned as one.
|
|
56
|
+
EVALS = Set[:eval, :instance_eval, :class_eval, :module_eval].freeze
|
|
57
|
+
WRITES = Set[
|
|
58
|
+
Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode,
|
|
59
|
+
Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableTargetNode
|
|
60
|
+
].freeze
|
|
61
|
+
LAST_LINE = :$_
|
|
62
|
+
# The calls that put a method in place under the name they are given: the new name, for `alias_method`.
|
|
63
|
+
DEFINERS = Set[:define_method, :define_singleton_method, :alias_method].freeze
|
|
64
|
+
DEFINER_NAMES = DEFINERS.to_set(&:to_s).freeze
|
|
65
|
+
ALL_READERS = READERS.to_a.freeze
|
|
66
|
+
# Compared as Strings: a literal with an invalid byte (`define_method("\xff")`) cannot become a Symbol.
|
|
67
|
+
READER_STRINGS = READERS.to_set(&:to_s).freeze
|
|
68
|
+
private_constant :DEFINERS, :DEFINER_NAMES, :ALL_READERS, :READER_STRINGS
|
|
69
|
+
private_constant :SETTER_NAMES, :LINE_EDITORS, :READER_GLOBALS, :READER_OWNERS, :DELEGATING_CLASSES,
|
|
70
|
+
:DELEGATING_ORDERINGS, :SENDS, :EVALS, :WRITES, :LAST_LINE
|
|
71
|
+
|
|
72
|
+
module_function
|
|
73
|
+
|
|
74
|
+
# True when `call_node` certainly is a C reader run in this frame, so `$_` holds what it returns: `gets` or
|
|
75
|
+
# `readline` without a block, on the `Kernel` module, on `$stdin` or `$<`, or on a receiver typed as a class
|
|
76
|
+
# whose reader RBS places in `IO`, `StringIO`, `ARGF` or `Zlib::GzipReader`, other than `Tempfile` (`STDIN` and
|
|
77
|
+
# `ARGF` read so by their types, and a project constant that shadows either reads by its own). A receiver typed
|
|
78
|
+
# `IO` or `File` that is a subclass instance at runtime is read as the class it is typed as.
|
|
79
|
+
#
|
|
80
|
+
# Issue #1415 (ADR-117 WD5) — an implicit-self or `self.` `gets` is one while the file shows no `self` whose
|
|
81
|
+
# reader may be Ruby's ({ImplicitSelf.reader?}). An implicit-self `readline` is not yet: it never returns
|
|
82
|
+
# falsey, so a `while readline` loop leaves only through a `break` or its `EOFError`, but the scope after the
|
|
83
|
+
# loop still joins the scope the body started from, where its narrowed line would report correct code.
|
|
84
|
+
#
|
|
85
|
+
# No reader is one whose name the program defines or patches in anywhere
|
|
86
|
+
# ({BlockCallTiming.project_defines_anywhere?}): a reopened `IO` or `Kernel`, or a project object bound to
|
|
87
|
+
# `$stdin` in another file, runs the program's Ruby method, which sets its own frame's `$_`.
|
|
88
|
+
def reads_line?(call_node, scope)
|
|
89
|
+
return false unless call_node.is_a?(Prism::CallNode) && READERS.include?(call_node.name)
|
|
90
|
+
return false unless call_node.block.nil?
|
|
91
|
+
|
|
92
|
+
name = call_node.name
|
|
93
|
+
return false if BlockCallTiming.project_defines_anywhere?(name, scope)
|
|
94
|
+
return false if scope.discovery.patched_line_readers.include?(name)
|
|
95
|
+
|
|
96
|
+
receiver = call_node.receiver
|
|
97
|
+
case receiver
|
|
98
|
+
when nil, Prism::SelfNode
|
|
99
|
+
name == :gets && ImplicitSelf.reader?(call_node, scope)
|
|
100
|
+
when Prism::GlobalVariableReadNode then reader_global?(receiver.name, name, scope)
|
|
101
|
+
else reader_receiver?(scope.type_of(receiver), name, scope)
|
|
102
|
+
end
|
|
103
|
+
rescue StandardError
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The edges of a condition whose value is `call_node`'s ({.reads_line?}): `pair`, the edges the condition
|
|
108
|
+
# narrows otherwise (nil for none), with `$_` bound on each to the line the call returned, narrowed as that
|
|
109
|
+
# value is — `String` where it is truthy and `nil` where it is not, for `gets`. The line is typed by the
|
|
110
|
+
# readers' RBS return, which every owner {.reads_line?} accepts declares alike (`String?` for `gets`, `String`
|
|
111
|
+
# for `readline`), rather than by the call's inferred type, which is `Dynamic[top]` on `$stdin`. A
|
|
112
|
+
# safe-navigation call answers nil without reading when its receiver is nil, so its falsey edge leaves `$_` as
|
|
113
|
+
# the call left it.
|
|
114
|
+
def predicate_scopes(call_node, scope, pair)
|
|
115
|
+
return pair unless reads_line?(call_node, scope)
|
|
116
|
+
|
|
117
|
+
line = line_type(call_node.name)
|
|
118
|
+
truthy, falsey = pair || [scope, scope]
|
|
119
|
+
truthy = truthy.with_global(LAST_LINE, Narrowing.narrow_truthy(line))
|
|
120
|
+
falsey = falsey.with_global(LAST_LINE, Narrowing.narrow_falsey(line)) unless call_node.safe_navigation?
|
|
121
|
+
[truthy, falsey]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The reader names `node` patches in through the `define_method` family, on any receiver, or nil: a
|
|
125
|
+
# `define_method`, `define_singleton_method` or `alias_method` call (or a `send`, `__send__` or `public_send`
|
|
126
|
+
# naming one) and an `alias` whose new name is `gets` or `readline`, or is not a literal, which may be either.
|
|
127
|
+
# `Inference::ScopeIndexer` gathers them for the file ({Scope::DiscoveryIndex#patched_line_readers}).
|
|
128
|
+
def patched_readers(node)
|
|
129
|
+
case node
|
|
130
|
+
when Prism::AliasMethodNode then reader_named(node.new_name)
|
|
131
|
+
when Prism::CallNode
|
|
132
|
+
arguments = node.arguments&.arguments
|
|
133
|
+
if DEFINERS.include?(node.name)
|
|
134
|
+
reader_named(arguments&.first)
|
|
135
|
+
elsif SENDS.include?(node.name) && arguments && definer_name?(arguments.first)
|
|
136
|
+
reader_named(arguments[1])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The readers a method-name argument may name: its own when it is a reader's literal, both when it is not a
|
|
142
|
+
# literal, and none for another literal or no argument at all.
|
|
143
|
+
def reader_named(name_node)
|
|
144
|
+
case name_node
|
|
145
|
+
when nil then nil
|
|
146
|
+
when Prism::SymbolNode, Prism::StringNode
|
|
147
|
+
name = name_node.unescaped
|
|
148
|
+
[name.to_sym] if READER_STRINGS.include?(name)
|
|
149
|
+
else ALL_READERS
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def definer_name?(name_node)
|
|
154
|
+
(name_node.is_a?(Prism::SymbolNode) || name_node.is_a?(Prism::StringNode)) &&
|
|
155
|
+
DEFINER_NAMES.include?(name_node.unescaped)
|
|
156
|
+
end
|
|
157
|
+
private_class_method :reader_named, :definer_name?
|
|
158
|
+
|
|
159
|
+
# True when running `node` may set `$_` in the frame it runs in, anywhere in `node` but a nested `def`, class
|
|
160
|
+
# or module body or a root block ({FreshFrameBlocks.any_frame_child?}): a call {.call_may_set?} counts, a write
|
|
161
|
+
# to `$_`, or a block argument {.block_argument_may_set?} counts. A block or lambda inside `node` counts, since
|
|
162
|
+
# it runs in the same frame. Kept on `scope`'s frame as {MatchRebinding.may_match?} is.
|
|
163
|
+
def may_set?(node, scope = nil)
|
|
164
|
+
return false unless node.is_a?(Prism::Node)
|
|
165
|
+
|
|
166
|
+
MatchRebinding.remember(node, scope, :last_line) { scan(node, scope) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def scan(node, scope)
|
|
170
|
+
return true if setting_node?(node, scope)
|
|
171
|
+
return false if MatchRebinding::OWN_FRAME_NODES.include?(node.class)
|
|
172
|
+
|
|
173
|
+
FreshFrameBlocks.any_frame_child?(node, scope) { |child| scan(child, scope) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def setting_node?(node, scope)
|
|
177
|
+
case node
|
|
178
|
+
when Prism::CallNode then call_may_set?(node)
|
|
179
|
+
when Prism::BlockArgumentNode then block_argument_may_set?(node, scope)
|
|
180
|
+
else WRITES.include?(node.class) && node.name == LAST_LINE
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
private_class_method :scan, :setting_node?
|
|
184
|
+
|
|
185
|
+
# True when `call_node` itself may set its caller's `$_`: a {SETTERS} name on any receiver, a {LINE_EDITORS}
|
|
186
|
+
# name on implicit `self`, a `send` whose name is not a literal or names a setter, or an eval of a String.
|
|
187
|
+
def call_may_set?(call_node)
|
|
188
|
+
name = call_node.name
|
|
189
|
+
return true if SETTERS.include?(name) || (call_node.receiver.nil? && LINE_EDITORS.include?(name))
|
|
190
|
+
|
|
191
|
+
arguments = call_node.arguments&.arguments
|
|
192
|
+
return sent_reader?(arguments&.first) if SENDS.include?(name)
|
|
193
|
+
|
|
194
|
+
EVALS.include?(name) && !arguments.nil?
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# True when a `&expr` block argument may pass a proc that sets `$_` in this frame: a `&:gets` or `&:readline`,
|
|
198
|
+
# which the method runs from C, or a proc made in this frame, as {MatchRebinding.block_argument_may_match?}
|
|
199
|
+
# reads one — not an anonymous `&`, nor the method's own `&block` it only forwards.
|
|
200
|
+
def block_argument_may_set?(block_argument, scope)
|
|
201
|
+
expression = block_argument.expression
|
|
202
|
+
case expression
|
|
203
|
+
when nil then false
|
|
204
|
+
when Prism::SymbolNode then SETTER_NAMES.include?(expression.unescaped)
|
|
205
|
+
when Prism::LocalVariableReadNode
|
|
206
|
+
frame = scope&.match_frame
|
|
207
|
+
frame.nil? || !frame.forwarded_block?(expression.name)
|
|
208
|
+
else true
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# True when the block `call_node` passes may set `$_` while the call runs, as {MatchRebinding.block_may_match?}
|
|
213
|
+
# reads a block: a root block never does, though a root `&expr` argument's expression runs here first.
|
|
214
|
+
def block_may_set?(call_node, scope)
|
|
215
|
+
block = call_node.block
|
|
216
|
+
if FreshFrameBlocks.root_call?(call_node, scope)
|
|
217
|
+
return block.is_a?(Prism::BlockArgumentNode) && may_set?(block.expression, scope)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
case block
|
|
221
|
+
when Prism::BlockNode then may_set?(block.body, scope)
|
|
222
|
+
when Prism::BlockArgumentNode then block_argument_may_set?(block, scope)
|
|
223
|
+
else false
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# True when a call's receiver chain or arguments, which Ruby runs before the method, may set `$_`.
|
|
228
|
+
def operands_may_set?(call_node, scope)
|
|
229
|
+
may_set?(call_node.receiver, scope) || may_set?(call_node.arguments, scope)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# True when a statement's own call may set `$_` in this frame, once its operands ran: it may itself
|
|
233
|
+
# ({.call_may_set?}), its block may ({.block_may_set?}), the frame makes a closure that may, which any call
|
|
234
|
+
# can run ({.closure?}), or it is an implicit-self or `self.` call in a frame that hands `$_`'s slot to code the
|
|
235
|
+
# analyzer does not trace ({.fallback?}) or that no body stamped. A call into a method defined in Ruby sets
|
|
236
|
+
# that method's `$_`, so any other call keeps the narrowing.
|
|
237
|
+
def call_rebinds?(call_node, scope)
|
|
238
|
+
return true if call_may_set?(call_node) || block_may_set?(call_node, scope)
|
|
239
|
+
|
|
240
|
+
frame = scope.match_frame
|
|
241
|
+
implicit = call_node.receiver.nil? || call_node.receiver.is_a?(Prism::SelfNode)
|
|
242
|
+
return implicit if frame.nil?
|
|
243
|
+
return true if frame.last_line_closure?(scope)
|
|
244
|
+
|
|
245
|
+
implicit && frame.last_line_fallback?(scope)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# True when `node`, a frame's body or parameters, makes a lambda, or a block a call keeps to run later, whose
|
|
249
|
+
# body {.may_set?} ({MatchRebinding.makes_closure?}).
|
|
250
|
+
def closure?(node, scope)
|
|
251
|
+
MatchRebinding.makes_closure?(node, scope) { |body| may_set?(body, scope) }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# True when `node`, a frame's body or parameters, hands `$_`'s slot to code the analyzer does not trace: a block
|
|
255
|
+
# or lambda whose body {.may_set?}, which the method it is handed to may keep and run from a later call,
|
|
256
|
+
# `binding`, or a forward of the method's own block ({MatchRebinding.hands_out_slot?}).
|
|
257
|
+
def fallback?(node, block_name, scope)
|
|
258
|
+
MatchRebinding.hands_out_slot?(node, block_name, scope) { |body| may_set?(body, scope) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# The scope a block or lambda body enters with, as {MatchRebinding.block_entry} gives the match globals': `$_`
|
|
262
|
+
# forgotten when the body may set it, which a later iteration then reads, when the frame makes a closure that
|
|
263
|
+
# may, or when the owning call may: its receiver chain and arguments run before the method yields, and
|
|
264
|
+
# `IO.foreach` sets `$_` to each line it yields.
|
|
265
|
+
def block_entry(scope, block_node, call_node = nil)
|
|
266
|
+
return scope unless scope.last_line_bound?
|
|
267
|
+
return scope unless scope.last_line_closure? || may_set?(block_node.body, scope) || call_sets?(call_node, scope)
|
|
268
|
+
|
|
269
|
+
scope.forget_last_line
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def call_sets?(call_node, scope)
|
|
273
|
+
call_node.is_a?(Prism::CallNode) && (call_may_set?(call_node) || operands_may_set?(call_node, scope))
|
|
274
|
+
end
|
|
275
|
+
private_class_method :call_sets?
|
|
276
|
+
|
|
277
|
+
# `scope` with `$_` forgotten when any of `bodies` may set it: a loop body that runs again after it ran, a
|
|
278
|
+
# `begin` body a `rescue` clause reads after any prefix of it ran, or a `case` clause's tests.
|
|
279
|
+
def forget_if_set(scope, *bodies)
|
|
280
|
+
return scope unless scope.last_line_bound? && bodies.any? { |body| may_set?(body, scope) }
|
|
281
|
+
|
|
282
|
+
scope.forget_last_line
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def line_type(reader)
|
|
286
|
+
string = Type::Combinator.nominal_of("String")
|
|
287
|
+
reader == :readline ? string : Type::Combinator.union(string, Type::Combinator.constant_of(nil))
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def sent_reader?(name_node)
|
|
291
|
+
case name_node
|
|
292
|
+
when nil then false
|
|
293
|
+
when Prism::SymbolNode, Prism::StringNode then SETTER_NAMES.include?(name_node.unescaped)
|
|
294
|
+
else true
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
private_class_method :line_type, :sent_reader?
|
|
298
|
+
|
|
299
|
+
def reader_receiver?(type, name, scope)
|
|
300
|
+
return type.class_name == "Kernel" if type.is_a?(Type::Singleton)
|
|
301
|
+
|
|
302
|
+
reader_type?(type, name, scope)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Issue #1362 (ADR-117 WD1) — a `$stdin` the file writes reads its declared `IO` joined with the written values,
|
|
306
|
+
# so a union whose every member is a reader's class (`IO | StringIO`) counts as one.
|
|
307
|
+
def reader_global?(global_name, name, scope)
|
|
308
|
+
return false unless READER_GLOBALS.include?(global_name)
|
|
309
|
+
|
|
310
|
+
bound = scope.global(global_name)
|
|
311
|
+
return true if bound.nil?
|
|
312
|
+
|
|
313
|
+
members = bound.is_a?(Type::Union) ? bound.members : [bound]
|
|
314
|
+
members.all? { |member| reader_type?(member, name, scope) }
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def reader_type?(type, name, scope)
|
|
318
|
+
type.is_a?(Type::Nominal) && reader_class?(type.class_name, name, scope)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# A class RBS does not know answers false: its ancestry is the program's or a gem's, and may hold a Ruby reader.
|
|
322
|
+
def reader_class?(class_name, name, scope)
|
|
323
|
+
return false if delegating_class?(class_name, scope)
|
|
324
|
+
|
|
325
|
+
definition = Reflection.instance_method_definition(class_name, name, scope: scope)
|
|
326
|
+
!definition.nil? && READER_OWNERS.include?(definition.defined_in.to_s)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def delegating_class?(class_name, scope)
|
|
330
|
+
DELEGATING_CLASSES.any? do |delegating|
|
|
331
|
+
DELEGATING_ORDERINGS.include?(scope.environment.class_ordering(class_name, delegating))
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
private_class_method :reader_receiver?, :reader_global?, :reader_type?, :reader_class?, :delegating_class?
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
require_relative "last_line/implicit_self"
|