rigortype 0.3.9 → 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/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 +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- 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 +243 -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 +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- 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 +212 -25
- 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 +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -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 +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- 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 +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- 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.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- 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 +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- 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 +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- 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 +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- 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/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- 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/index_write_widening.rb +16 -3
- 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 +167 -17
- 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 +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- 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/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- 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 +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- 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 +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- 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 +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- 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 +239 -4
- 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/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -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 +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- 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.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- 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 +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -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/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 +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- 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 +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -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 +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- 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 +12 -25
- 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 +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- 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-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
module MatchRebinding
|
|
10
|
+
# The frame a body runs in, stamped on its entry scope ({Scope#with_match_frame}) and shared by every scope
|
|
11
|
+
# derived from it, blocks included, since they run in the same frame. Each answer is computed on the first
|
|
12
|
+
# ask — only code run while a match global is narrowed asks — and kept for the rest of the body: whether the
|
|
13
|
+
# body makes a matching closure, whether its implicit-self calls keep the reset ({#self_call_fallback?}), the
|
|
14
|
+
# method's forwarded `&block` name, and {MatchRebinding.may_match?} per node ({#memo}). The same slot holds
|
|
15
|
+
# `$_` (issue #1359), whose frame-wide answers ({#last_line_closure?}, {#last_line_fallback?}) are kept here
|
|
16
|
+
# too, asked only while `$_` is narrowed.
|
|
17
|
+
class Frame
|
|
18
|
+
# The nodes that bind a local in a body: the writes, and a block's, lambda's, `rescue`'s or pattern's own
|
|
19
|
+
# parameters and targets.
|
|
20
|
+
LOCAL_BINDINGS = Set[
|
|
21
|
+
Prism::LocalVariableWriteNode, Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode,
|
|
22
|
+
Prism::LocalVariableAndWriteNode, Prism::LocalVariableTargetNode, Prism::RequiredParameterNode,
|
|
23
|
+
Prism::OptionalParameterNode, Prism::RestParameterNode, Prism::RequiredKeywordParameterNode,
|
|
24
|
+
Prism::OptionalKeywordParameterNode, Prism::KeywordRestParameterNode, Prism::BlockParameterNode,
|
|
25
|
+
Prism::BlockLocalVariableNode
|
|
26
|
+
].freeze
|
|
27
|
+
private_constant :LOCAL_BINDINGS
|
|
28
|
+
|
|
29
|
+
# @param parameters — a method's parameters, whose default expressions run in its frame; nil for a class,
|
|
30
|
+
# module or file body.
|
|
31
|
+
def initialize(body, parameters = nil)
|
|
32
|
+
@body = body
|
|
33
|
+
@parameters = parameters
|
|
34
|
+
@matching_closure = nil
|
|
35
|
+
@self_call_fallback = nil
|
|
36
|
+
@forwarded_block = nil
|
|
37
|
+
@scans = nil
|
|
38
|
+
@last_line_closure = nil
|
|
39
|
+
@last_line_fallback = nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def matching_closure?(scope = nil)
|
|
43
|
+
if @matching_closure.nil?
|
|
44
|
+
@matching_closure = MatchRebinding.matching_closure?(@body, scope) ||
|
|
45
|
+
MatchRebinding.matching_closure?(@parameters, scope)
|
|
46
|
+
end
|
|
47
|
+
@matching_closure
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# True when an implicit-self call in this frame forgets the match globals as it did before issue #1364,
|
|
51
|
+
# because the body or a parameter default hands the frame's slot to code the analyzer does not trace
|
|
52
|
+
# ({MatchRebinding.self_call_fallback?}). Kept as {#memo} keeps a scan: the broad reading resolves constants
|
|
53
|
+
# and the variables a lookup argument names through `scope`.
|
|
54
|
+
def self_call_fallback?(scope = nil)
|
|
55
|
+
return fallback_in_frame?(scope) if scope.nil?
|
|
56
|
+
|
|
57
|
+
kept = @self_call_fallback
|
|
58
|
+
return kept[2] if kept && kept[0].equal?(scope.locals) && kept[1].equal?(scope.ivars)
|
|
59
|
+
|
|
60
|
+
result = fallback_in_frame?(scope)
|
|
61
|
+
@self_call_fallback = [scope.locals, scope.ivars, result]
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# {#matching_closure?} for `$_` ({LastLine.closure?}).
|
|
66
|
+
def last_line_closure?(scope = nil)
|
|
67
|
+
if @last_line_closure.nil?
|
|
68
|
+
@last_line_closure = LastLine.closure?(@body, scope) || LastLine.closure?(@parameters, scope)
|
|
69
|
+
end
|
|
70
|
+
@last_line_closure
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# {#self_call_fallback?} for `$_` ({LastLine.fallback?}). Its reading is by name alone, so it is kept once.
|
|
74
|
+
def last_line_fallback?(scope = nil)
|
|
75
|
+
if @last_line_fallback.nil?
|
|
76
|
+
block_name = block_parameter_name
|
|
77
|
+
@last_line_fallback = LastLine.fallback?(@body, block_name, scope) ||
|
|
78
|
+
LastLine.fallback?(@parameters, block_name, scope)
|
|
79
|
+
end
|
|
80
|
+
@last_line_fallback
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# True when `name` is the method's own `&block` parameter and the body never binds that name — no write, no
|
|
84
|
+
# block or lambda parameter shadowing it — so every read of it in the frame is the block the caller passed,
|
|
85
|
+
# which the caller made in its own frame.
|
|
86
|
+
def forwarded_block?(name)
|
|
87
|
+
@forwarded_block = forwarded_block_name || false if @forwarded_block.nil?
|
|
88
|
+
@forwarded_block == name
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# The scan of `node` under `scope`, kept while `scope`'s local and instance-variable tables are the same
|
|
92
|
+
# objects: a lookup argument's answer reads them ({MatchRebinding.may_match?}), and a rebuild that leaves
|
|
93
|
+
# them alone passes the same tables on. `kind` keeps another reading of the same node apart (issue #1365:
|
|
94
|
+
# {MatchRebinding.operands_may_rebind?}, and the code an eval runs), which the statement pass and each
|
|
95
|
+
# typing pass over the call ask again.
|
|
96
|
+
def memo(node, scope, kind = nil)
|
|
97
|
+
scans = kind.nil? ? (@scans ||= {}.compare_by_identity) : kind_scans(kind)
|
|
98
|
+
locals = scope.locals
|
|
99
|
+
ivars = scope.ivars
|
|
100
|
+
kept = scans[node]
|
|
101
|
+
return kept[2] if kept && kept[0].equal?(locals) && kept[1].equal?(ivars)
|
|
102
|
+
|
|
103
|
+
result = yield
|
|
104
|
+
scans[node] = [locals, ivars, result]
|
|
105
|
+
result
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def kind_scans(kind)
|
|
111
|
+
tables = (@kind_scans ||= {})
|
|
112
|
+
tables[kind] ||= {}.compare_by_identity
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def fallback_in_frame?(scope)
|
|
116
|
+
block_name = block_parameter_name
|
|
117
|
+
MatchRebinding.self_call_fallback?(@body, block_name, scope) ||
|
|
118
|
+
MatchRebinding.self_call_fallback?(@parameters, block_name, scope)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def forwarded_block_name
|
|
122
|
+
name = block_parameter_name
|
|
123
|
+
return nil if name.nil? || binds_local?(@body, name)
|
|
124
|
+
|
|
125
|
+
name
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The method's own `&block` parameter's name, or nil.
|
|
129
|
+
def block_parameter_name
|
|
130
|
+
parameters = @parameters
|
|
131
|
+
block = parameters.is_a?(Prism::ParametersNode) ? parameters.block : nil
|
|
132
|
+
block&.name
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# `node` is a body or one of its descendants; a method with an empty body passes nil.
|
|
136
|
+
def binds_local?(node, name)
|
|
137
|
+
return false if node.nil?
|
|
138
|
+
return true if LOCAL_BINDINGS.include?(node.class) && node.name == name
|
|
139
|
+
return false if OWN_FRAME_NODES.include?(node.class)
|
|
140
|
+
|
|
141
|
+
found = false
|
|
142
|
+
node.rigor_each_child { |child| found ||= binds_local?(child, name) }
|
|
143
|
+
found
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../source/node_children"
|
|
6
|
+
require_relative "../../type"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
module MatchRebinding
|
|
11
|
+
# Whether an operand of a call, a `when` condition or a pattern is a Regexp — the one question that decides
|
|
12
|
+
# whether `===`, `case` and the lookup names run a match. Two readings, because the constructs differ in
|
|
13
|
+
# what they usually hold:
|
|
14
|
+
#
|
|
15
|
+
# - a `when` condition, a pattern value and a `===` receiver are patterns far more often than not, so
|
|
16
|
+
# anything but a value known not to be a Regexp counts ({.pattern_value?}, {.pattern_matches?});
|
|
17
|
+
# - a `[]` / `split` / `index` argument is a lookup key far more often than not, so only a value known to be
|
|
18
|
+
# a Regexp counts ({.regexp_argument?}).
|
|
19
|
+
#
|
|
20
|
+
# A constant resolves through the scope. One that does not resolve is read as a class, the usual CamelCase
|
|
21
|
+
# referent, so it does not count either way — except under the `broad` reading, where it may be a Regexp
|
|
22
|
+
# defined in another file (#1373) and counts ({MatchRebinding.broad_may_match?}).
|
|
23
|
+
#
|
|
24
|
+
# Outside a block, a statement's operands are read on two further terms (issue #1365), because the flow scope
|
|
25
|
+
# types them but its types can be stale (#1380): a call the name table forgot on before keeps the narrowing only
|
|
26
|
+
# on a non-Regexp literal ({.keep_literal?}), and a call in a position that never forgot forgets only on an
|
|
27
|
+
# operand known to be a Regexp ({.known_regexp_operand?}).
|
|
28
|
+
module Operands
|
|
29
|
+
REGEX_LITERALS = Set[Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode].freeze
|
|
30
|
+
# Values whose `===` cannot run a match. An interpolated String or Symbol is still a String or Symbol; the
|
|
31
|
+
# code it embeds is scanned like any other.
|
|
32
|
+
NON_REGEXP_LITERALS = Set[
|
|
33
|
+
Prism::SymbolNode, Prism::InterpolatedSymbolNode, Prism::StringNode, Prism::InterpolatedStringNode,
|
|
34
|
+
Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode, Prism::ImaginaryNode,
|
|
35
|
+
Prism::NilNode, Prism::TrueNode, Prism::FalseNode
|
|
36
|
+
].freeze
|
|
37
|
+
# The literals {.keep_literal?} accepts: each is never a Regexp, whatever a flow type says.
|
|
38
|
+
KEEP_LITERALS = Set[
|
|
39
|
+
Prism::SymbolNode, Prism::StringNode, Prism::IntegerNode, Prism::FloatNode, Prism::RationalNode,
|
|
40
|
+
Prism::ImaginaryNode, Prism::NilNode, Prism::TrueNode, Prism::FalseNode
|
|
41
|
+
].freeze
|
|
42
|
+
CONSTANT_NODES = Set[Prism::ConstantReadNode, Prism::ConstantPathNode].freeze
|
|
43
|
+
PINNED_NODES = Set[Prism::PinnedVariableNode, Prism::PinnedExpressionNode].freeze
|
|
44
|
+
REGEXP_CONSTRUCTORS = Set[:new, :union, :compile].freeze
|
|
45
|
+
# The classes a Regexp is an instance of, so a value of one of these types may be a Regexp.
|
|
46
|
+
REGEXP_ANCESTORS = Set["Regexp", "Object", "BasicObject", "Kernel"].freeze
|
|
47
|
+
# How a class relates to `Regexp` when every instance of it is one.
|
|
48
|
+
REGEXP_CLASS_ORDERINGS = Set[:subclass, :equal].freeze
|
|
49
|
+
private_constant :REGEX_LITERALS, :NON_REGEXP_LITERALS, :KEEP_LITERALS, :CONSTANT_NODES, :PINNED_NODES,
|
|
50
|
+
:REGEXP_CONSTRUCTORS, :REGEXP_ANCESTORS, :REGEXP_CLASS_ORDERINGS
|
|
51
|
+
|
|
52
|
+
module_function
|
|
53
|
+
|
|
54
|
+
# True when a statement's operand is never a Regexp by its syntax alone: a Symbol, a String without
|
|
55
|
+
# interpolation, a number, `nil`, `true`, `false`, or a range of those. A call the name table forgot on before
|
|
56
|
+
# issue #1365 (`row[:name]`, `csv.split(",")`, `list.index(3)`) keeps the narrowing only when every argument is
|
|
57
|
+
# one, because a flow type that says "String" can be stale while the value is a Regexp (#1380).
|
|
58
|
+
def keep_literal?(node)
|
|
59
|
+
return true if KEEP_LITERALS.include?(node.class)
|
|
60
|
+
return false unless node.is_a?(Prism::RangeNode)
|
|
61
|
+
|
|
62
|
+
[node.left, node.right].all? { |bound| bound.nil? || KEEP_LITERALS.include?(bound.class) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# True when `node` is a constant naming a class or module (`String === s`), whose `===` is `Module#===`.
|
|
66
|
+
def class_constant?(node, scope)
|
|
67
|
+
CONSTANT_NODES.include?(node.class) && node_type(node, scope).is_a?(Type::Singleton)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# True when a statement's operand is known to be a Regexp: a regex literal, a splat whose elements are known
|
|
71
|
+
# to be ones, or a value whose type in the flow scope is one ({.regexp_type?}; `Regexp.new(src)` types as
|
|
72
|
+
# one). `Dynamic[top]`, `Object` and any other type are not: a position that never forgot before issue #1365
|
|
73
|
+
# forgets only on evidence, so an argument that is a Regexp without the analyzer knowing it is a gap there,
|
|
74
|
+
# as it was before.
|
|
75
|
+
def known_regexp_operand?(node, scope)
|
|
76
|
+
return true if REGEX_LITERALS.include?(node.class)
|
|
77
|
+
return splat_known_regexp?(node.expression, scope) if node.is_a?(Prism::SplatNode)
|
|
78
|
+
return false if NON_REGEXP_LITERALS.include?(node.class)
|
|
79
|
+
|
|
80
|
+
regexp_type?(node_type(node, scope), scope)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# `*args` passes each element; an anonymous `*` forwards elements nothing is known about.
|
|
84
|
+
def splat_known_regexp?(expression, scope)
|
|
85
|
+
return false if expression.nil?
|
|
86
|
+
|
|
87
|
+
type = node_type(expression, scope)
|
|
88
|
+
case type
|
|
89
|
+
when Type::Tuple then type.elements.any? { |element| regexp_type?(element, scope) }
|
|
90
|
+
when Type::Nominal
|
|
91
|
+
type.class_name == "Array" ? type.type_args.any? { |element| regexp_type?(element, scope) } : false
|
|
92
|
+
else false
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
private_class_method :splat_known_regexp?
|
|
96
|
+
|
|
97
|
+
# True when a value of `type` may be a Regexp on the analyzer's evidence: a Regexp constant, `Regexp` or a
|
|
98
|
+
# class the environment places below it, a union or intersection with such a member, a refinement or
|
|
99
|
+
# difference over one, or a `Dynamic` whose static facet is one.
|
|
100
|
+
def regexp_type?(type, scope)
|
|
101
|
+
case type
|
|
102
|
+
when Type::Constant then type.value.is_a?(Regexp)
|
|
103
|
+
when Type::Nominal then regexp_class?(type.class_name, scope)
|
|
104
|
+
when Type::Union, Type::Intersection then type.members.any? { |member| regexp_type?(member, scope) }
|
|
105
|
+
when Type::Dynamic then regexp_type?(type.static_facet, scope)
|
|
106
|
+
when Type::Difference, Type::Refined then regexp_type?(type.base, scope)
|
|
107
|
+
else false
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def regexp_class?(class_name, scope)
|
|
112
|
+
return true if class_name == "Regexp"
|
|
113
|
+
|
|
114
|
+
environment = scope&.environment
|
|
115
|
+
!environment.nil? && REGEXP_CLASS_ORDERINGS.include?(environment.class_ordering(class_name, "Regexp"))
|
|
116
|
+
end
|
|
117
|
+
private_class_method :regexp_class?
|
|
118
|
+
|
|
119
|
+
def regexp_constructor?(node)
|
|
120
|
+
node.is_a?(Prism::CallNode) && REGEXP_CONSTRUCTORS.include?(node.name) &&
|
|
121
|
+
node.receiver.is_a?(Prism::ConstantReadNode) && node.receiver.name == :Regexp
|
|
122
|
+
end
|
|
123
|
+
private_class_method :regexp_constructor?
|
|
124
|
+
|
|
125
|
+
def node_type(node, scope)
|
|
126
|
+
scope&.type_of(node)
|
|
127
|
+
rescue StandardError
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
private_class_method :node_type
|
|
131
|
+
|
|
132
|
+
# True when a `===` receiver or a `when` condition may be a Regexp: a regex literal, a constant bound to
|
|
133
|
+
# one, a splat of a constant holding one, or any other expression (`when re`), but not a literal that is
|
|
134
|
+
# not one. `broad` counts an unresolved constant too.
|
|
135
|
+
def pattern_value?(node, scope, broad: false)
|
|
136
|
+
klass = node.class
|
|
137
|
+
return true if REGEX_LITERALS.include?(klass)
|
|
138
|
+
return false if non_regexp_literal?(node)
|
|
139
|
+
return constant_may_be_regexp?(node_type(node, scope), broad) if CONSTANT_NODES.include?(klass)
|
|
140
|
+
return splat_regexp?(node, scope, broad) if node.is_a?(Prism::SplatNode)
|
|
141
|
+
|
|
142
|
+
true
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# True when an `in` / `=>` pattern holds a value that may be a Regexp: a regex literal, a pinned variable
|
|
146
|
+
# or expression, or a constant bound to one. Its structure, captures and other literals run no match, and
|
|
147
|
+
# an `if` / `unless` guard is ordinary code, scanned with the rest of the body. `broad` counts an unresolved
|
|
148
|
+
# constant too.
|
|
149
|
+
def pattern_matches?(node, scope, broad: false)
|
|
150
|
+
return false if node.nil?
|
|
151
|
+
|
|
152
|
+
klass = node.class
|
|
153
|
+
return true if REGEX_LITERALS.include?(klass) || PINNED_NODES.include?(klass)
|
|
154
|
+
return constant_may_be_regexp?(node_type(node, scope), broad) if CONSTANT_NODES.include?(klass)
|
|
155
|
+
if node.is_a?(Prism::IfNode) || node.is_a?(Prism::UnlessNode)
|
|
156
|
+
return pattern_matches?(node.statements, scope, broad: broad)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
found = false
|
|
160
|
+
node.rigor_each_child { |child| found ||= pattern_matches?(child, scope, broad: broad) }
|
|
161
|
+
found
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# True when an argument is known to be a Regexp: a regex literal, a constant bound to one, a local or
|
|
165
|
+
# instance variable bound to one where the block is written, or `Regexp.new` / `.union` / `.compile`. A
|
|
166
|
+
# block parameter is bound only inside the block, so `row[f]` does not count.
|
|
167
|
+
def regexp_argument?(node, scope)
|
|
168
|
+
case node
|
|
169
|
+
when Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode then true
|
|
170
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode then regexp_type?(node_type(node, scope), scope)
|
|
171
|
+
when Prism::LocalVariableReadNode then regexp_type?(scope&.local(node.name), scope)
|
|
172
|
+
when Prism::InstanceVariableReadNode then regexp_type?(scope&.ivar(node.name), scope)
|
|
173
|
+
when Prism::CallNode then regexp_constructor?(node)
|
|
174
|
+
else false
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# `when *KEYS` runs `===` on each element of `KEYS`; a splat of anything but a constant counts.
|
|
179
|
+
def splat_regexp?(splat, scope, broad)
|
|
180
|
+
expression = splat.expression
|
|
181
|
+
return true unless CONSTANT_NODES.include?(expression.class)
|
|
182
|
+
|
|
183
|
+
type = node_type(expression, scope)
|
|
184
|
+
(broad && unresolved?(type)) || elements_regexp?(type)
|
|
185
|
+
end
|
|
186
|
+
private_class_method :splat_regexp?
|
|
187
|
+
|
|
188
|
+
def elements_regexp?(type)
|
|
189
|
+
case type
|
|
190
|
+
when Type::Tuple then type.elements.any? { |element| constant_regexp?(element) }
|
|
191
|
+
when Type::Nominal then type.type_args.any? { |argument| constant_regexp?(argument) }
|
|
192
|
+
when Type::Constant then type.value.is_a?(Array) && type.value.any?(Regexp)
|
|
193
|
+
when Type::Union then type.members.any? { |member| elements_regexp?(member) }
|
|
194
|
+
else false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
private_class_method :elements_regexp?
|
|
198
|
+
|
|
199
|
+
def non_regexp_literal?(node)
|
|
200
|
+
return true if NON_REGEXP_LITERALS.include?(node.class)
|
|
201
|
+
return false unless node.is_a?(Prism::RangeNode)
|
|
202
|
+
|
|
203
|
+
[node.left, node.right].all? { |bound| bound.nil? || non_regexp_literal?(bound) }
|
|
204
|
+
end
|
|
205
|
+
private_class_method :non_regexp_literal?
|
|
206
|
+
|
|
207
|
+
def constant_may_be_regexp?(type, broad)
|
|
208
|
+
constant_regexp?(type) || (broad && unresolved?(type))
|
|
209
|
+
end
|
|
210
|
+
private_class_method :constant_may_be_regexp?
|
|
211
|
+
|
|
212
|
+
# A constant that does not resolve reads as `Dynamic`, or as nil without a scope.
|
|
213
|
+
def unresolved?(type)
|
|
214
|
+
case type
|
|
215
|
+
when nil, Type::Dynamic then true
|
|
216
|
+
when Type::Union then type.members.any? { |member| unresolved?(member) }
|
|
217
|
+
else false
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
private_class_method :unresolved?
|
|
221
|
+
|
|
222
|
+
# True when a constant's type may be a Regexp. A class or module, a Tuple or HashShape, any other value,
|
|
223
|
+
# and a constant that does not resolve do not.
|
|
224
|
+
def constant_regexp?(type)
|
|
225
|
+
case type
|
|
226
|
+
when Type::Constant then type.value.is_a?(Regexp)
|
|
227
|
+
when Type::Nominal then REGEXP_ANCESTORS.include?(type.class_name)
|
|
228
|
+
when Type::Union then type.members.any? { |member| constant_regexp?(member) }
|
|
229
|
+
else false
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
private_class_method :constant_regexp?
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MatchRebinding
|
|
8
|
+
# Method names that may run a regex match, read by name alone: a Symbol or String literal naming one counts
|
|
9
|
+
# ({SelfCalls.method_name_literal?}), since the method it is handed to may run it from C (`inject(:=~)`). A call
|
|
10
|
+
# itself is read with its arguments ({Calls}, issue #1365).
|
|
11
|
+
MATCH_CAPABLE_METHODS = %i[
|
|
12
|
+
=~ match match? gsub gsub! sub sub! scan split slice slice!
|
|
13
|
+
[] partition rpartition index rindex === grep grep_v
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
# The calls past that table that may rebind the frame they are made from, read by name alone (issue #1364), as
|
|
17
|
+
# the broad reading of a frame's blocks counts them ({MatchRebinding.broad_may_match?}), and as the calls an
|
|
18
|
+
# implicit-self call forgot on before issue #1365, which {Calls.base_named?} keeps forgetting on.
|
|
19
|
+
module SelfCalls
|
|
20
|
+
# Builtins that set their caller's `$~` and the table misses: `x !~ re`, `~re`, and `start_with?(re)`,
|
|
21
|
+
# `byteindex(re)`, `byterindex(re)`, `s[re] = v` — the ones a `String` or `Regexp` subclass inherits too.
|
|
22
|
+
SELF_MATCHING = Set[:!~, :~, :start_with?, :byteindex, :byterindex, :[]=].freeze
|
|
23
|
+
# Predicates that run `pattern === element` from C when given an argument, even when the class's `each` is
|
|
24
|
+
# written in Ruby.
|
|
25
|
+
PATTERN_PREDICATES = Set[:any?, :all?, :none?, :one?].freeze
|
|
26
|
+
# These run a String of code in the frame that calls them: `eval` always, the other three in their String
|
|
27
|
+
# form only, since their block form is a block ({MatchRebinding.block_may_match?}).
|
|
28
|
+
EVALS = Set[:eval].freeze
|
|
29
|
+
STRING_EVALS = Set[:instance_eval, :class_eval, :module_eval].freeze
|
|
30
|
+
# These run the method they name, which counts when it is one of {METHOD_NAMES} or not a literal.
|
|
31
|
+
SENDS = Set[:send, :__send__, :public_send].freeze
|
|
32
|
+
# Every name above and in the table, as Strings: a literal compares by its bytes, which an invalid one
|
|
33
|
+
# (`send("\xff")`) cannot turn into a Symbol.
|
|
34
|
+
METHOD_NAMES = (
|
|
35
|
+
MATCH_CAPABLE_METHODS.to_set | SELF_MATCHING | PATTERN_PREDICATES | EVALS | STRING_EVALS | SENDS
|
|
36
|
+
).to_set(&:to_s).freeze
|
|
37
|
+
private_constant :SELF_MATCHING, :PATTERN_PREDICATES, :EVALS, :STRING_EVALS, :SENDS, :METHOD_NAMES
|
|
38
|
+
|
|
39
|
+
module_function
|
|
40
|
+
|
|
41
|
+
# True when `call_node`, on any receiver, is a call past the table that may rebind the frame it is made
|
|
42
|
+
# from: a {SELF_MATCHING} name; a {PATTERN_PREDICATES} name with an argument; an eval of a String
|
|
43
|
+
# ({EVALS}, or {STRING_EVALS} with an argument); or a {SENDS} call whose name is not a literal, or names a
|
|
44
|
+
# method that counts.
|
|
45
|
+
def named_match?(call_node)
|
|
46
|
+
name = call_node.name
|
|
47
|
+
arguments = call_node.arguments&.arguments
|
|
48
|
+
return true if SELF_MATCHING.include?(name) || EVALS.include?(name)
|
|
49
|
+
return !arguments.nil? if PATTERN_PREDICATES.include?(name) || STRING_EVALS.include?(name)
|
|
50
|
+
return sent_name_matches?(arguments&.first) if SENDS.include?(name)
|
|
51
|
+
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# True when a {SENDS} call's name argument is not a Symbol or String literal, or names a method that counts.
|
|
56
|
+
def sent_name_matches?(name_node)
|
|
57
|
+
return method_name_literal?(name_node) if literal_name?(name_node)
|
|
58
|
+
|
|
59
|
+
true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# True when `node` is a Symbol or String literal naming the table's methods or the ones above:
|
|
63
|
+
# `inject(:=~)` runs `=~` from C in the frame that calls it.
|
|
64
|
+
def method_name_literal?(node)
|
|
65
|
+
literal_name?(node) && METHOD_NAMES.include?(node.unescaped)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# True for `send(:binding)`, or a {SENDS} call whose name is not a literal and so may be `:binding`.
|
|
69
|
+
def sends_binding?(call_node)
|
|
70
|
+
return false unless SENDS.include?(call_node.name)
|
|
71
|
+
|
|
72
|
+
name_node = call_node.arguments&.arguments&.first
|
|
73
|
+
!literal_name?(name_node) || name_node.unescaped == "binding"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def literal_name?(node)
|
|
77
|
+
node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
78
|
+
end
|
|
79
|
+
private_class_method :literal_name?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|