rigortype 0.4.6 → 0.4.7
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/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../analysis/dependency_recorder"
|
|
6
|
+
require_relative "../source/node_children"
|
|
7
|
+
require_relative "../type"
|
|
8
|
+
require_relative "guard_rebinding"
|
|
9
|
+
require_relative "project_method_ownership"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Inference
|
|
13
|
+
# Issue #2314 — whether running a node may reach a project method that writes a given global. Both places that
|
|
14
|
+
# end a global's binding at a call ask it: the statement walk past a statement call
|
|
15
|
+
# (`StatementEvaluator#forget_reached_globals`), and a `case` whose global subject a `when` test may rebind
|
|
16
|
+
# (`StatementEvaluator#when_test_rebinds_subject?`, issue #2211), so the two read a global alike.
|
|
17
|
+
#
|
|
18
|
+
# The writers come from {GlobalWriteCensus}: a `[:global_writer, name, method]` entry for each write of `name`
|
|
19
|
+
# inside a method body (`method` is the `def`'s name, or the literal name a `define_method` block defines), and
|
|
20
|
+
# `[:global_writer, name, nil]` for one inside a lambda or a block a call keeps to run later
|
|
21
|
+
# ({StoredBlockCall}), which any call may run. A write at the top level or in a class body runs where it is
|
|
22
|
+
# written, so no call reaches it.
|
|
23
|
+
#
|
|
24
|
+
# {.may_write?} answers true when the global has a writer and:
|
|
25
|
+
#
|
|
26
|
+
# - the node may run code other than Ruby core and the standard library ({GuardRebinding.may_rebind?}, the
|
|
27
|
+
# question #2260 asked of a `when` test), and
|
|
28
|
+
# - some writer is anonymous, or the node's own call has a receiver no target resolves (a `Dynamic` value), or a
|
|
29
|
+
# search by method name reaches a writer ({Search}).
|
|
30
|
+
#
|
|
31
|
+
# The search reads method names, not receivers: from the node it follows every call, by name, into every project
|
|
32
|
+
# `def` of that name on any class, transitively, and reaches a writer when it meets a call named after one. A
|
|
33
|
+
# call naming its method by a computed value (`send(name)`, `eval(code)`) reaches. A name no project `def` defines
|
|
34
|
+
# follows `method_missing`, `new` follows `initialize`, and an alias name (`alias`, `alias_method`, a
|
|
35
|
+
# `def_delegator` alias) follows the name it stands for. A nested `def` is not read, since defining a method runs
|
|
36
|
+
# nothing. Past {Search::DEF_BUDGET} bodies the search answers that it reaches. A `define_method` method that calls
|
|
37
|
+
# a writer is not followed (only its own writes count), and a method core code calls back without the program
|
|
38
|
+
# spelling it (`puts obj` runs `obj.to_s`) is read as the core method alone, as {GuardRebinding} reads it.
|
|
39
|
+
module GlobalWriterReach
|
|
40
|
+
EMPTY = Set.new.freeze
|
|
41
|
+
|
|
42
|
+
module_function
|
|
43
|
+
|
|
44
|
+
# The globals `scope` binds that some project method may write, other than one already `Dynamic[top]`. Each
|
|
45
|
+
# bound global's census key is recorded whatever the answer, so a file that starts writing one re-checks.
|
|
46
|
+
def candidates(scope)
|
|
47
|
+
table = writer_table(scope)
|
|
48
|
+
globals = scope.globals
|
|
49
|
+
globals.each_key { |name| record_writers(name) } if Analysis::DependencyRecorder.active?
|
|
50
|
+
return EMPTY if table.empty? || globals.empty?
|
|
51
|
+
|
|
52
|
+
table.each_key.select do |name|
|
|
53
|
+
type = globals[name]
|
|
54
|
+
!type.nil? && !type.is_a?(Type::Dynamic)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# True when running `node`, typed in `scope`, may reach a project method that writes `name` (see the module
|
|
59
|
+
# comment).
|
|
60
|
+
def may_write?(node, name, scope)
|
|
61
|
+
writers = writers(scope, name)
|
|
62
|
+
return false if writers.empty?
|
|
63
|
+
return false unless GuardRebinding.may_rebind?(node, scope)
|
|
64
|
+
return true if writers.include?(nil) || unresolved_call?(node, scope)
|
|
65
|
+
|
|
66
|
+
Search.new(scope, writers).reaches?(node)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The method names that write `name` (nil for an anonymous writer). Records the census key, so a file that
|
|
70
|
+
# starts or stops writing the global re-checks the reader. A guard on the `case` site: every `when` test built so
|
|
71
|
+
# far also records the key through a statement call's {.candidates} or a callee's method edge.
|
|
72
|
+
def writers(scope, name)
|
|
73
|
+
record_writers(name) if Analysis::DependencyRecorder.active?
|
|
74
|
+
writer_table(scope)[name] || EMPTY
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def record_writers(name)
|
|
78
|
+
Analysis::DependencyRecorder.read_name(:global_census, "global_writer:#{name}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# `{ global => Set[method name or nil] }` over the project's census and the `pre_eval:` files' own.
|
|
82
|
+
def writer_table(scope) = census_tables(scope).first
|
|
83
|
+
|
|
84
|
+
# `{ alias name => Set[the method names it stands for] }`: an `alias`, an `alias_method` or a `def_delegator`
|
|
85
|
+
# with a literal alias name, from the same census.
|
|
86
|
+
def alias_table(scope) = census_tables(scope).last
|
|
87
|
+
|
|
88
|
+
# The two tables above, kept for the census objects they were built from.
|
|
89
|
+
def census_tables(scope)
|
|
90
|
+
census = scope.discovered_global_write_census
|
|
91
|
+
pre_eval = scope.environment&.project_patched_methods&.write_census
|
|
92
|
+
cached = @census_tables
|
|
93
|
+
return cached[2] if cached && cached[0].equal?(census) && cached[1].equal?(pre_eval)
|
|
94
|
+
|
|
95
|
+
tables = build_tables([census, pre_eval])
|
|
96
|
+
@census_tables = [census, pre_eval, tables.freeze]
|
|
97
|
+
tables
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
TABLE_KINDS = { global_writer: 0, method_alias: 1 }.freeze
|
|
101
|
+
private_constant :TABLE_KINDS
|
|
102
|
+
|
|
103
|
+
def build_tables(censuses)
|
|
104
|
+
tables = [{}, {}]
|
|
105
|
+
censuses.each do |entries|
|
|
106
|
+
entries&.each do |kind, key, value|
|
|
107
|
+
index = TABLE_KINDS[kind]
|
|
108
|
+
(tables[index][key] ||= Set.new) << value if index
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
tables.each { |table| table.each_value(&:freeze).freeze }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# True when `node` is a call whose receiver no target resolves: the method it runs may be any.
|
|
115
|
+
def unresolved_call?(node, scope)
|
|
116
|
+
return false unless node.is_a?(Prism::CallNode)
|
|
117
|
+
|
|
118
|
+
receiver = node.receiver
|
|
119
|
+
return false if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
120
|
+
|
|
121
|
+
targets = ProjectMethodOwnership.targets(GuardRebinding::ScanScope.dispatch_type(receiver, scope))
|
|
122
|
+
targets.nil? || targets.empty?
|
|
123
|
+
rescue StandardError
|
|
124
|
+
true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# `{ method name => [[class name, kind], …] }` over the project's `def` tables, kept for the tables it was
|
|
128
|
+
# built from.
|
|
129
|
+
def def_index(scope)
|
|
130
|
+
instance = scope.discovered_def_nodes
|
|
131
|
+
singleton = scope.discovered_singleton_def_nodes
|
|
132
|
+
cached = @def_index
|
|
133
|
+
return cached[2] if cached && cached[0].equal?(instance) && cached[1].equal?(singleton)
|
|
134
|
+
|
|
135
|
+
index = {}
|
|
136
|
+
{ instance: instance, singleton: singleton }.each do |kind, table|
|
|
137
|
+
table.each do |class_name, methods|
|
|
138
|
+
methods.each_key { |method_name| (index[method_name] ||= []) << [class_name, kind] }
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
@def_index = [instance, singleton, index.freeze]
|
|
142
|
+
index
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# A breadth-first search by method name from a node to a writer's name.
|
|
146
|
+
class Search
|
|
147
|
+
# The most `def` bodies one search reads before it answers that it reaches.
|
|
148
|
+
DEF_BUDGET = 64
|
|
149
|
+
SEND_CALLS = %i[send __send__ public_send method public_method instance_method].to_set.freeze
|
|
150
|
+
CODE_CALLS = %i[eval instance_eval instance_exec class_eval class_exec module_eval module_exec].to_set.freeze
|
|
151
|
+
|
|
152
|
+
class Reached < StandardError
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def initialize(scope, writers)
|
|
156
|
+
@scope = scope
|
|
157
|
+
@writers = writers
|
|
158
|
+
@aliases = GlobalWriterReach.alias_table(scope)
|
|
159
|
+
@recording = Analysis::DependencyRecorder.active?
|
|
160
|
+
@seen = Set.new
|
|
161
|
+
@queue = []
|
|
162
|
+
@read = 0
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def reaches?(node)
|
|
166
|
+
scan(node)
|
|
167
|
+
until @queue.empty?
|
|
168
|
+
name = @queue.shift
|
|
169
|
+
definitions = definitions_of(name)
|
|
170
|
+
enqueue(:method_missing) if definitions.empty?
|
|
171
|
+
definitions.each { |definition| read_def(definition) }
|
|
172
|
+
end
|
|
173
|
+
false
|
|
174
|
+
rescue Reached
|
|
175
|
+
true
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
private
|
|
179
|
+
|
|
180
|
+
def read_def(definition)
|
|
181
|
+
@read += 1
|
|
182
|
+
reached! if @read > DEF_BUDGET
|
|
183
|
+
scan(definition.parameters)
|
|
184
|
+
scan(definition.body)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def definitions_of(name)
|
|
188
|
+
Analysis::DependencyRecorder.read_name(:defines, name) if Analysis::DependencyRecorder.active?
|
|
189
|
+
targets = GlobalWriterReach.def_index(@scope)[name] || []
|
|
190
|
+
targets.filter_map do |class_name, kind|
|
|
191
|
+
kind == :singleton ? @scope.singleton_def_for(class_name, name) : @scope.user_def_for(class_name, name)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def scan(node)
|
|
196
|
+
return unless node.is_a?(Prism::Node)
|
|
197
|
+
# A nested `def` defines a method; it runs nothing here, and its name is followed where it is called.
|
|
198
|
+
return if node.is_a?(Prism::DefNode)
|
|
199
|
+
|
|
200
|
+
case node
|
|
201
|
+
when Prism::CallNode then scan_call(node)
|
|
202
|
+
when Prism::BlockArgumentNode then scan_block_argument(node)
|
|
203
|
+
when Prism::ForNode then enqueue(:each)
|
|
204
|
+
end
|
|
205
|
+
implicit_names(node).each { |name| enqueue(name) }
|
|
206
|
+
node.rigor_each_child { |child| scan(child) }
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def scan_call(node)
|
|
210
|
+
enqueue(node.name)
|
|
211
|
+
# `new` runs the class's `initialize`.
|
|
212
|
+
enqueue(:initialize) if node.name == :new
|
|
213
|
+
arguments = node.arguments&.arguments || []
|
|
214
|
+
if SEND_CALLS.include?(node.name)
|
|
215
|
+
name = literal_name(arguments.first)
|
|
216
|
+
name ? enqueue(name) : reached!
|
|
217
|
+
elsif CODE_CALLS.include?(node.name) && !(arguments.empty? && node.block.is_a?(Prism::BlockNode))
|
|
218
|
+
reached!
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def scan_block_argument(node)
|
|
223
|
+
name = literal_name(node.expression)
|
|
224
|
+
enqueue(name) if name
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# The methods an operator or compound write calls without spelling them.
|
|
228
|
+
def implicit_names(node)
|
|
229
|
+
names = []
|
|
230
|
+
names << node.binary_operator if node.respond_to?(:binary_operator)
|
|
231
|
+
names.push(node.read_name, node.write_name) if node.respond_to?(:read_name) && node.respond_to?(:write_name)
|
|
232
|
+
names
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def enqueue(name)
|
|
236
|
+
return if name.nil? || @seen.include?(name)
|
|
237
|
+
|
|
238
|
+
reached! if @writers.include?(name)
|
|
239
|
+
@seen << name
|
|
240
|
+
@queue << name
|
|
241
|
+
# An alias runs the method it names.
|
|
242
|
+
Analysis::DependencyRecorder.read_name(:global_census, "method_alias:#{name}") if @recording
|
|
243
|
+
@aliases[name]&.each { |original| enqueue(original) }
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def literal_name(node)
|
|
247
|
+
return unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
248
|
+
|
|
249
|
+
text = node.unescaped
|
|
250
|
+
text.to_sym if text.valid_encoding?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def reached!
|
|
254
|
+
raise Reached
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
private_constant :Search
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
@@ -41,8 +41,7 @@ module Rigor
|
|
|
41
41
|
|
|
42
42
|
# Reference to a formal parameter the enclosing `Definition#params` declared. The reducer
|
|
43
43
|
# substitutes this node with the matching positional arg from the `App` being reduced; an
|
|
44
|
-
# unknown name raises during reduction
|
|
45
|
-
# earlier).
|
|
44
|
+
# unknown name raises during reduction.
|
|
46
45
|
Param = Data.define(:name) do
|
|
47
46
|
def initialize(name:)
|
|
48
47
|
raise ArgumentError, "name must be a Symbol, got #{name.class}" unless name.is_a?(Symbol)
|
|
@@ -43,7 +43,7 @@ module Rigor
|
|
|
43
43
|
# `Rigor::Type::*` carrier: `nil` / `true` / `false` produce `Constant` carriers; `bool`
|
|
44
44
|
# produces the `Constant<true> | Constant<false>` union; `untyped` produces
|
|
45
45
|
# `Combinator.untyped` (i.e. `Dynamic[Top]`). Nominal class names produce raw `Type::Nominal`
|
|
46
|
-
# carriers (no `name_scope` resolution
|
|
46
|
+
# carriers (no `name_scope` resolution; the reducer trusts the name verbatim).
|
|
47
47
|
module HktBodyParser
|
|
48
48
|
class ParseError < StandardError; end
|
|
49
49
|
|
|
@@ -151,10 +151,8 @@ module Rigor
|
|
|
151
151
|
# test := type_expr ("<:" | "==") type_expr
|
|
152
152
|
#
|
|
153
153
|
# Parens delimit a conditional unambiguously — bare `(type_expr)` grouping is not
|
|
154
|
-
# supported
|
|
155
|
-
#
|
|
156
|
-
# membership tests are programmatically supported via `HktBody::TestMembership` but the
|
|
157
|
-
# parser does not yet recognise the `in` keyword (no concrete demand yet).
|
|
154
|
+
# supported (no use case yet). Branches can be unions; test sides are single arms (wrap
|
|
155
|
+
# in `App[my_union, ...]` if you need a union there).
|
|
158
156
|
def parse_conditional
|
|
159
157
|
expect!(:lparen)
|
|
160
158
|
test = parse_test
|
|
@@ -152,8 +152,8 @@ module Rigor
|
|
|
152
152
|
def subtype_verdict(left, right)
|
|
153
153
|
return :yes if left == right
|
|
154
154
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
155
|
+
# Two Nominals of different class names are statically disjoint (`:no`); any other shape
|
|
156
|
+
# pair is `:maybe`.
|
|
157
157
|
return :no if disjoint_nominals?(left, right)
|
|
158
158
|
return :no if disjoint_constants?(left, right)
|
|
159
159
|
|
|
@@ -9,11 +9,9 @@ module Rigor
|
|
|
9
9
|
# annotations in shipped `.rbs` files.
|
|
10
10
|
#
|
|
11
11
|
# The registry stores registration metadata (arity, variance, bound) and the definition body
|
|
12
|
-
# as both a raw String and an evaluable `HktBody` node tree. `HktReducer`
|
|
13
|
-
# `
|
|
14
|
-
#
|
|
15
|
-
# carries its `bound` directly; the registry exists at this slice solely so the parser
|
|
16
|
-
# round-trip and downstream slices have a stable target API.
|
|
12
|
+
# as both a raw String and an evaluable `HktBody` node tree. `HktReducer` and `HktBodyParser`
|
|
13
|
+
# reduce `Type::App` instances against the definition. The carrier never needs to read from the
|
|
14
|
+
# registry because `Type::App` carries its `bound` directly.
|
|
17
15
|
#
|
|
18
16
|
# The registry is immutable after construction. Callers that need to extend it (e.g. plugin
|
|
19
17
|
# registrations layered on top of stdlib registrations) MUST build a new registry via `merge`
|
|
@@ -9,8 +9,7 @@ require_relative "unknown_store_widening"
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Inference
|
|
11
11
|
# Closes the "`params[:f] ||= []; params[:f] << x`" precision gap surfaced by the Redmine
|
|
12
|
-
# 6.1.2 `Query#as_params` survey
|
|
13
|
-
# "Indexed-collection narrowing through `Hash[k] ||= default`").
|
|
12
|
+
# 6.1.2 `Query#as_params` survey.
|
|
14
13
|
#
|
|
15
14
|
# After `receiver[key] ||= default` the next read at `receiver[key]` is known non-nil, but
|
|
16
15
|
# Rigor types each `Hash#[]` independently and the subsequent `<<` / `[]=` / other mutator
|
|
@@ -206,11 +205,9 @@ module Rigor
|
|
|
206
205
|
args&.size == 1 ? stable_address(node.receiver, args.first) : nil
|
|
207
206
|
end
|
|
208
207
|
|
|
209
|
-
# Companion invalidator for single-hop method-chain narrowings
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
# receiver — matching the ROADMAP's "any intervening method call against the same
|
|
213
|
-
# receiver" criterion. A call against `x.last` (the OUTER receiver is a `CallNode`, not a
|
|
208
|
+
# Companion invalidator for single-hop method-chain narrowings. Drops every `(receiver, *)`
|
|
209
|
+
# chain narrowing rooted at the call's OUTER stable receiver, for any intervening method call
|
|
210
|
+
# against the same receiver. A call against `x.last` (the OUTER receiver is a `CallNode`, not a
|
|
214
211
|
# stable root) does NOT drop narrowings keyed on `x`, so the worked-site `x.last << y`
|
|
215
212
|
# pattern correctly preserves the chain narrowing for any further `x.last` read in the same
|
|
216
213
|
# body. Always-safe (only forgets; never invents).
|
|
@@ -19,8 +19,8 @@ module Rigor
|
|
|
19
19
|
# the calls in a condition operand, where a narrowing an earlier operand or clause made would otherwise survive
|
|
20
20
|
# them: `if @x.nil? && reset!` read `@x` as `nil` in the body although `reset!` assigns it.
|
|
21
21
|
module InterveningIvarReset
|
|
22
|
-
# Nodes whose body does not run where it is written, or runs on another `self`: a `def`, and a class
|
|
23
|
-
# module body, where `@x` is the class's own instance variable.
|
|
22
|
+
# Nodes whose body does not run where it is written, or runs on another `self`: a `def`, and a class,
|
|
23
|
+
# module or `class << …` body, where `@x` is the class's own instance variable.
|
|
24
24
|
NON_RUNNING_NODES = [Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
25
25
|
# Issue #1402 — the nodes that run code the program does not show on `self`'s behalf: `yield` runs the
|
|
26
26
|
# method's block, and `super` the superclass's method on `self`.
|
|
@@ -180,12 +180,17 @@ module Rigor
|
|
|
180
180
|
# takes the mark when widened, so a `nil` only the seed brings back is not fuel for
|
|
181
181
|
# `call.possible-nil-receiver` (ADR-58), as a declared one is not. A narrowing that kept `nil`
|
|
182
182
|
# (`return if @tag`, `@tag.nil?`) widens unmarked, so a read the program really makes on `nil` still reports.
|
|
183
|
-
|
|
183
|
+
# Issue #2039 — `mark: :unproven` marks as `mark: true` does only when the block answers nil (no write set is
|
|
184
|
+
# proven) and `direct_writes` (called at most once, answering a `Set`) does not name the variable: a call
|
|
185
|
+
# proven to write it, or whose callee visibly assigns it a possibly-nil value ({DirectIvarWrites}), widens it
|
|
186
|
+
# unmarked.
|
|
187
|
+
def widen(scope, mark: false, direct_writes: nil)
|
|
184
188
|
seed = class_ivar_seed(scope)
|
|
185
189
|
return scope if seed.nil? || seed.empty?
|
|
186
190
|
|
|
187
191
|
computed = false
|
|
188
192
|
writes = nil
|
|
193
|
+
direct = lazy_set(direct_writes)
|
|
189
194
|
seed.reduce(scope) do |widened, (name, seed_type)|
|
|
190
195
|
local_type = scope.ivar(name)
|
|
191
196
|
next widened if local_type.nil? || local_type == seed_type
|
|
@@ -198,11 +203,28 @@ module Rigor
|
|
|
198
203
|
|
|
199
204
|
joined = Type::Combinator.union(local_type, seed_type)
|
|
200
205
|
widened = widened.with_ivar(name, joined)
|
|
201
|
-
marked = (mark
|
|
206
|
+
marked = widen_marks?(mark, writes, direct, name, local_type) || scope.declaration_sourced?(:ivar, name)
|
|
202
207
|
marked ? widened.seed_declaration_sourced_ivar(name, joined) : widened
|
|
203
208
|
end
|
|
204
209
|
end
|
|
205
210
|
|
|
211
|
+
# Whether {.widen} stamps the declaration mark on `name`'s binding, narrowed to `local_type`: one whose
|
|
212
|
+
# narrowing ruled `nil` out, under `mark: true`, or under `mark: :unproven` when no write set is proven
|
|
213
|
+
# (`writes` nil) and the callee does not visibly write it (`direct`).
|
|
214
|
+
def widen_marks?(mark, writes, direct, name, local_type)
|
|
215
|
+
return false unless mark && nil_free?(local_type)
|
|
216
|
+
|
|
217
|
+
mark != :unproven || (writes.nil? && !direct.call.include?(name))
|
|
218
|
+
end
|
|
219
|
+
private_class_method :widen_marks?
|
|
220
|
+
|
|
221
|
+
# A callable answering `callable`'s `Set`, computed on the first call; an empty one when `callable` is nil.
|
|
222
|
+
def lazy_set(callable)
|
|
223
|
+
value = nil
|
|
224
|
+
-> { value ||= callable ? callable.call : Set.new }
|
|
225
|
+
end
|
|
226
|
+
private_class_method :lazy_set
|
|
227
|
+
|
|
206
228
|
# Issue #1963 — `scope` past the calls `node`, a condition operand, makes: {.widen} over the names every
|
|
207
229
|
# candidate call in it may write, or every name when one of them is not proven. A call's literal block may run,
|
|
208
230
|
# so its calls count; a `def`, a class or module body and (unless `run_lambdas`) a lambda literal run nothing
|
|
@@ -22,6 +22,11 @@ module Rigor
|
|
|
22
22
|
# - `[:own_write, class_name, name]` — an `@x` write inside an instance body of the class.
|
|
23
23
|
# {ScopeIndexer#merge_constant_literal_tables} appends the file's path, so a reader can tell whether a second
|
|
24
24
|
# file writes the same ivar of the same class.
|
|
25
|
+
# - `[:own_write_type, class_name, name, value]` — issues #2079 / #2170: the same write with its coarse value, a
|
|
26
|
+
# literal's as `[:lit, value]` and anything else as {DYNAMIC}, and a class-body `attr_writer` /
|
|
27
|
+
# `attr_accessor` of the name as {DYNAMIC}. The path is appended as for `:own_write`. The class-ivar census
|
|
28
|
+
# of a file joins another file's entries, and those of the modules and superclasses on the class's chain,
|
|
29
|
+
# into the ivars it already holds ({ScopeIndexer::IvarContributions}).
|
|
25
30
|
class IvarWriterCensus
|
|
26
31
|
SEND_CALLS = %i[send __send__ public_send].to_set.freeze
|
|
27
32
|
# The methods a `send` reaching which is an open-world writer.
|
|
@@ -34,6 +39,10 @@ module Rigor
|
|
|
34
39
|
attr_writer attr_accessor send public_send __send__
|
|
35
40
|
].to_set.freeze
|
|
36
41
|
NO_ARGUMENTS = [].freeze
|
|
42
|
+
# Issue #2079 — the coarse value of a write that is not a literal.
|
|
43
|
+
DYNAMIC = :dynamic
|
|
44
|
+
# The writer declarations a class body records as a {DYNAMIC} write of each name it spells.
|
|
45
|
+
WRITER_DECLARATIONS = %i[attr_writer attr_accessor].to_set.freeze
|
|
37
46
|
|
|
38
47
|
# The call names any rule here reads, so every other call returns at one Set probe.
|
|
39
48
|
# (`define_method` is in {SINGLETON_CLASS_CALLS}.)
|
|
@@ -44,6 +53,7 @@ module Rigor
|
|
|
44
53
|
@instance_ranges = []
|
|
45
54
|
@owner_names = {}.compare_by_identity
|
|
46
55
|
@own_writes = {}
|
|
56
|
+
@typed_writes = Set.new
|
|
47
57
|
end
|
|
48
58
|
|
|
49
59
|
# Fed each node of a pre-order descent. `owner` is the class whose body the node sits in, as its qualified name
|
|
@@ -67,6 +77,7 @@ module Rigor
|
|
|
67
77
|
owner = instance_owner(node)
|
|
68
78
|
return if owner.nil?
|
|
69
79
|
|
|
80
|
+
record_typed_write(owner, node.name, coarse_value(node))
|
|
70
81
|
seen = (@own_writes[owner] ||= {})
|
|
71
82
|
return if seen[node.name]
|
|
72
83
|
|
|
@@ -74,9 +85,49 @@ module Rigor
|
|
|
74
85
|
@census << [:own_write, owner_name(owner), node.name].freeze
|
|
75
86
|
end
|
|
76
87
|
|
|
88
|
+
def record_typed_write(owner, name, value)
|
|
89
|
+
entry = [:own_write_type, owner_name(owner), name, value].freeze
|
|
90
|
+
@census << entry if @typed_writes.add?(entry)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Issue #2079 — a literal's value as `[:lit, value]`; any other value, an operator write and a multiple
|
|
94
|
+
# assignment target as {DYNAMIC}.
|
|
95
|
+
def coarse_value(node)
|
|
96
|
+
value = node.value if node.respond_to?(:value) && !node.is_a?(Prism::InstanceVariableOperatorWriteNode)
|
|
97
|
+
case value
|
|
98
|
+
when Prism::NilNode then [:lit, nil].freeze
|
|
99
|
+
when Prism::TrueNode then [:lit, true].freeze
|
|
100
|
+
when Prism::FalseNode then [:lit, false].freeze
|
|
101
|
+
when Prism::IntegerNode, Prism::FloatNode then [:lit, value.value].freeze
|
|
102
|
+
when Prism::SymbolNode, Prism::StringNode then literal_text(value)
|
|
103
|
+
else DYNAMIC
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def literal_text(node)
|
|
108
|
+
text = node.unescaped
|
|
109
|
+
return DYNAMIC unless text.valid_encoding?
|
|
110
|
+
|
|
111
|
+
[:lit, node.is_a?(Prism::SymbolNode) ? text.to_sym : text.dup.freeze].freeze
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Issue #2079 — `attr_writer :x` / `attr_accessor :x` in a class body writes `@x` with whatever the caller hands.
|
|
115
|
+
def record_writer_declaration(node, owner)
|
|
116
|
+
return unless owner && WRITER_DECLARATIONS.include?(node.name) && self_receiver?(node)
|
|
117
|
+
# A guard: an instance body's own `attr_writer` call raises (the method is `Module`'s), so only the class
|
|
118
|
+
# body's declares a writer.
|
|
119
|
+
return unless instance_owner(node).nil?
|
|
120
|
+
|
|
121
|
+
(node.arguments&.arguments || NO_ARGUMENTS).each do |argument|
|
|
122
|
+
name = literal_name(argument)
|
|
123
|
+
record_typed_write(owner, :"@#{name}", DYNAMIC) if name
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
77
127
|
def owner_name(owner) = owner.is_a?(Array) ? (@owner_names[owner] ||= owner.join("::")) : owner
|
|
78
128
|
|
|
79
129
|
def visit_call(node, owner)
|
|
130
|
+
record_writer_declaration(node, owner)
|
|
80
131
|
remember(node.block.location, owner) if owner && define_method_block?(node)
|
|
81
132
|
if self_receiver?(node) || own_singleton_class_call?(node)
|
|
82
133
|
return if @instance_ranges.empty? || !(own_singleton_class_call?(node) || open_world?(node))
|
|
@@ -5,6 +5,7 @@ require "prism"
|
|
|
5
5
|
require_relative "../type"
|
|
6
6
|
require_relative "mutation_widening"
|
|
7
7
|
require_relative "key_presence_guard/scan"
|
|
8
|
+
require_relative "../run_scoped_slots"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
module Inference
|
|
@@ -58,7 +59,7 @@ module Rigor
|
|
|
58
59
|
).to_set.freeze
|
|
59
60
|
|
|
60
61
|
STATE_KEY = :__rigor_key_presence_guard
|
|
61
|
-
CACHE_KEY = :__rigor_key_presence_guard_rewalk
|
|
62
|
+
CACHE_KEY = RunScopedSlots.register(:__rigor_key_presence_guard_rewalk)
|
|
62
63
|
private_constant :STATE_KEY, :CACHE_KEY
|
|
63
64
|
|
|
64
65
|
# The re-walk's state: the file's root, the end offset of each recorded guard, and the per-file scans.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../run_scoped_slots"
|
|
4
|
+
|
|
3
5
|
module Rigor
|
|
4
6
|
module Inference
|
|
5
7
|
# The value the statement evaluator last gave a loop node, and the scope it evaluated the loop from, so
|
|
@@ -13,15 +15,17 @@ module Rigor
|
|
|
13
15
|
# evaluation would. It is not compared with `Scope#==`, which ignores several fields (`match_frame`,
|
|
14
16
|
# `declared_refinements`, `struct_fold_safe_locals`, the opaque-block flags, `lexical_nesting`, the origins tables,
|
|
15
17
|
# `discovery`): across the cost spec's 10933 reads every match was an identity match, and `lib/` read no record at
|
|
16
|
-
# all (issue #2262). It lives in a per-thread map
|
|
17
|
-
#
|
|
18
|
+
# all (issue #2262). It lives in a per-thread map keyed by the loop node. The map's keys are weak but its values
|
|
19
|
+
# are strong, and a recorded scope reaches its discovery, its environment and the parse tree that holds the key,
|
|
20
|
+
# so a record never goes on its own (issue #2412): the map is a run-scoped slot ({RunScopedSlots}), emptied when
|
|
21
|
+
# the run that filled it ends.
|
|
18
22
|
module LoopValueRecord
|
|
19
|
-
KEY = :rigor_loop_value_record
|
|
23
|
+
KEY = RunScopedSlots.register(:rigor_loop_value_record)
|
|
20
24
|
private_constant :KEY
|
|
21
25
|
|
|
22
26
|
module_function
|
|
23
27
|
|
|
24
|
-
COUNT_KEY = :rigor_loop_value_count
|
|
28
|
+
COUNT_KEY = RunScopedSlots.register(:rigor_loop_value_count)
|
|
25
29
|
private_constant :COUNT_KEY
|
|
26
30
|
|
|
27
31
|
def record(node, scope, type)
|
|
@@ -15,6 +15,12 @@ module Rigor
|
|
|
15
15
|
# `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
|
|
16
16
|
# too, asked only while `$_` is narrowed.
|
|
17
17
|
class Frame
|
|
18
|
+
# The body the frame runs, which issue #2555's escape scan reads ({ConstructionEscape.escaped?}).
|
|
19
|
+
attr_reader :body
|
|
20
|
+
# The method's parameters (nil for a class, module or file body), which issue #2608's holder scan reads for a
|
|
21
|
+
# zero-argument `super` ({RemoverHolderScan}).
|
|
22
|
+
attr_reader :parameters
|
|
23
|
+
|
|
18
24
|
# The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
|
|
19
25
|
# parameters and targets.
|
|
20
26
|
LOCAL_BINDINGS = Set[
|
|
@@ -37,6 +43,8 @@ module Rigor
|
|
|
37
43
|
@scans = nil
|
|
38
44
|
@last_line_closure = nil
|
|
39
45
|
@last_line_fallback = nil
|
|
46
|
+
@body_locals = nil
|
|
47
|
+
@parameter_names = nil
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def matching_closure?(scope = nil)
|
|
@@ -88,6 +96,17 @@ module Rigor
|
|
|
88
96
|
@forwarded_block == name
|
|
89
97
|
end
|
|
90
98
|
|
|
99
|
+
# Issue #2354 — whether `name` is a local of the frame's own body: the body binds it (a write, or a block's,
|
|
100
|
+
# lambda's, `rescue`'s or pattern's own parameter or target) and no parameter of the method names it. A
|
|
101
|
+
# method parameter is the caller's object wherever the body also writes the name (`h = h.dup if f`, a
|
|
102
|
+
# later `h = {}`), so it never counts. Kept per name for the rest of the body.
|
|
103
|
+
def body_local?(name)
|
|
104
|
+
bound = (@body_locals ||= {})
|
|
105
|
+
return bound[name] if bound.key?(name)
|
|
106
|
+
|
|
107
|
+
bound[name] = !parameter_names.include?(name) && binds_local?(@body, name)
|
|
108
|
+
end
|
|
109
|
+
|
|
91
110
|
# Issue #1157 — whether a call's `&expr` block argument hands on the block THIS method was called with: the
|
|
92
111
|
# anonymous `&` of a method that declares an anonymous `&` parameter, or {#forwarded_block?} for `&name`.
|
|
93
112
|
# Anything else (`&:sym`, a proc made here, a rebound name) is a block of this frame's own making.
|
|
@@ -144,6 +163,33 @@ module Rigor
|
|
|
144
163
|
!block.nil? && block.name.nil?
|
|
145
164
|
end
|
|
146
165
|
|
|
166
|
+
# The names the method's own parameters bind: positional (destructured ones included), optional, rest,
|
|
167
|
+
# keyword, keyword-rest and block. Empty for a class, module or file body.
|
|
168
|
+
def parameter_names
|
|
169
|
+
@parameter_names ||= begin
|
|
170
|
+
parameters = @parameters
|
|
171
|
+
names = Set.new
|
|
172
|
+
if parameters.is_a?(Prism::ParametersNode)
|
|
173
|
+
[*parameters.requireds, *parameters.optionals, parameters.rest, *parameters.posts,
|
|
174
|
+
*parameters.keywords, parameters.keyword_rest, parameters.block].each do |parameter|
|
|
175
|
+
collect_parameter_names(parameter, names)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
names.freeze
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def collect_parameter_names(node, names)
|
|
183
|
+
case node
|
|
184
|
+
when Prism::MultiTargetNode
|
|
185
|
+
[*node.lefts, node.rest, *node.rights].each { |target| collect_parameter_names(target, names) }
|
|
186
|
+
when Prism::SplatNode then collect_parameter_names(node.expression, names)
|
|
187
|
+
else
|
|
188
|
+
name = node.respond_to?(:name) ? node.name : nil
|
|
189
|
+
names << name if name
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
147
193
|
# The method's own `&block` parameter's name, or nil.
|
|
148
194
|
def block_parameter_name
|
|
149
195
|
parameters = @parameters
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MatchRebinding
|
|
8
|
+
# Issue #2443 — the match globals a block enters with when the call that owns it matches before every yield.
|
|
9
|
+
# `String#scan`, `#gsub`, `#gsub!`, `#sub` and `#sub!` set the caller's `$~` to the current match before each
|
|
10
|
+
# run of their block, with a Regexp pattern or a String one, so the block reads that match on every run
|
|
11
|
+
# whatever the enclosing frame held; the frame-wide entry ({MatchRebinding.block_entry}) would otherwise leave
|
|
12
|
+
# it the narrowing at the call site (issue #1371) or nothing at all, and `Regexp.last_match.begin(0)` in the
|
|
13
|
+
# block read `MatchData?`.
|
|
14
|
+
#
|
|
15
|
+
# A receiver that is a String, or a `Dynamic` the analyzer cannot type ({Narrowing.string_match_receiver?}),
|
|
16
|
+
# enters with the call's own match edge ({Narrowing.yielded_match_scope}). A `Dynamic` receiver may be another
|
|
17
|
+
# object whose block-taking `scan` runs no match, and then the block reads the caller's `$~`; narrowing it
|
|
18
|
+
# anyway can only drop a diagnostic, never add one (ADR-5). A receiver known to be anything else — a project
|
|
19
|
+
# class, `Pathname`, a union with either — keeps the frame-wide entry: a method defined in Ruby runs its
|
|
20
|
+
# matches in a frame of its own, so its block reads the caller's `$~` as the frame rules leave it. So does an
|
|
21
|
+
# implicit-self call.
|
|
22
|
+
module YieldingMatch
|
|
23
|
+
NAMES = Set[:scan, :gsub, :gsub!, :sub, :sub!].freeze
|
|
24
|
+
private_constant :NAMES
|
|
25
|
+
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# `scope` with the match globals `call_node`'s block enters with; `scope` itself for any other block.
|
|
29
|
+
# `operand_scope` types the receiver.
|
|
30
|
+
def entry(scope, block_node, call_node, operand_scope)
|
|
31
|
+
return scope unless yielding_call?(block_node, call_node) &&
|
|
32
|
+
Narrowing.string_match_receiver?(operand_scope.type_of(call_node.receiver), operand_scope)
|
|
33
|
+
|
|
34
|
+
Narrowing.yielded_match_scope(scope)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def yielding_call?(block_node, call_node)
|
|
38
|
+
call_node.is_a?(Prism::CallNode) && NAMES.include?(call_node.name) && !call_node.receiver.nil? &&
|
|
39
|
+
block_node.is_a?(Prism::BlockNode) && call_node.block.equal?(block_node)
|
|
40
|
+
end
|
|
41
|
+
private_class_method :yielding_call?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|