rigortype 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +112 -14
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
require_relative "captured_locals"
|
|
7
|
+
require_relative "element_read_widening"
|
|
8
|
+
require_relative "jump_targets"
|
|
9
|
+
require_relative "mutation_widening"
|
|
10
|
+
require_relative "receiver_alias"
|
|
11
|
+
|
|
12
|
+
module Rigor
|
|
13
|
+
module Inference
|
|
14
|
+
# Whether an expression `StatementEvaluator` would otherwise type as a pure value — a call's receiver or
|
|
15
|
+
# argument, a literal, an interpolation, a `rescue` modifier — holds something the scope after it must see:
|
|
16
|
+
# a variable write whose binding outlives the expression, or a `next` / `break` whose path a jump join
|
|
17
|
+
# reads. Issue #1223: `out << (n += 1)` left `n` on its pre-write binding, straight-line and through
|
|
18
|
+
# ADR-56's block write-back, and `puts(x && next)` never reached the block's `next` join.
|
|
19
|
+
#
|
|
20
|
+
# A local write counts only when it binds past every block and lambda it is nested in within the
|
|
21
|
+
# expression (its `depth` reaches the expression's own scope): `puts(xs.map { |x| y = x })` binds `y` in
|
|
22
|
+
# the block alone, while `puts(xs.each { t = 1 })` rebinds the outer `t`. Every instance-variable,
|
|
23
|
+
# class-variable and global write counts wherever it is, and so does an index `||=` / `&&=` / `op=`,
|
|
24
|
+
# whose store widens its receiver. A jump counts only where it targets the construct around the
|
|
25
|
+
# expression ({JumpTargets}). A `def`, class or module body is a scope of its own, and `defined?`
|
|
26
|
+
# evaluates nothing, so neither is looked into.
|
|
27
|
+
#
|
|
28
|
+
# An in-place mutation counts on the same terms as a write ({.outliving_mutation?}): a call to a name the
|
|
29
|
+
# straight-line widening responds to ({MutationWidening::SHAPE_MUTATORS}) on a receiver naming a variable
|
|
30
|
+
# that outlives the expression. The mutator's widening runs in the evaluator's post-call effects, which a
|
|
31
|
+
# pure-value operand never reached, so a chained `b.push(2).size`, an argument `puts(b.push(2))` or
|
|
32
|
+
# `d.map!.with_index { … }` left `b` / `d` on the literal their assignment wrote.
|
|
33
|
+
#
|
|
34
|
+
# Short-circuiting and allocation-free outside a mutator-named call: the evaluator asks it of every call's
|
|
35
|
+
# operands, and asks it again of each operand it threads. A found effect stops the recursion without a
|
|
36
|
+
# `return` out of the child block, which would allocate once per frame it unwinds and make a deep literal
|
|
37
|
+
# quadratic.
|
|
38
|
+
module OperandEffects
|
|
39
|
+
LOCAL_WRITE_NODES = CapturedLocals::LOCAL_WRITE_NODES
|
|
40
|
+
OUTLIVING_WRITE_NODES = (
|
|
41
|
+
CapturedLocals::NON_LOCAL_WRITE_NODES |
|
|
42
|
+
Set[Prism::IndexOrWriteNode, Prism::IndexAndWriteNode, Prism::IndexOperatorWriteNode]
|
|
43
|
+
).freeze
|
|
44
|
+
INSTANCE_WRITE_NODES = Set[
|
|
45
|
+
Prism::InstanceVariableWriteNode, Prism::InstanceVariableOperatorWriteNode,
|
|
46
|
+
Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode, Prism::InstanceVariableTargetNode
|
|
47
|
+
].freeze
|
|
48
|
+
GLOBAL_WRITE_NODES = Set[
|
|
49
|
+
Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::GlobalVariableOrWriteNode,
|
|
50
|
+
Prism::GlobalVariableAndWriteNode, Prism::GlobalVariableTargetNode
|
|
51
|
+
].freeze
|
|
52
|
+
COMPOUND_LOCAL_WRITES = Set[
|
|
53
|
+
Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode
|
|
54
|
+
].freeze
|
|
55
|
+
COMPOUND_INSTANCE_WRITES = Set[
|
|
56
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableOrWriteNode,
|
|
57
|
+
Prism::InstanceVariableAndWriteNode
|
|
58
|
+
].freeze
|
|
59
|
+
JUMP_NODES = Set[Prism::NextNode, Prism::BreakNode].freeze
|
|
60
|
+
SCOPE_NODES = Set[Prism::BlockNode, Prism::LambdaNode].freeze
|
|
61
|
+
OPAQUE_NODES = Set[
|
|
62
|
+
Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode, Prism::DefinedNode
|
|
63
|
+
].freeze
|
|
64
|
+
private_constant :LOCAL_WRITE_NODES, :OUTLIVING_WRITE_NODES, :INSTANCE_WRITE_NODES, :GLOBAL_WRITE_NODES,
|
|
65
|
+
:COMPOUND_LOCAL_WRITES,
|
|
66
|
+
:COMPOUND_INSTANCE_WRITES, :JUMP_NODES, :SCOPE_NODES, :OPAQUE_NODES
|
|
67
|
+
|
|
68
|
+
module_function
|
|
69
|
+
|
|
70
|
+
def any?(node)
|
|
71
|
+
node.is_a?(Prism::Node) && effect?(node, 0, true)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# `nesting` counts the blocks and lambdas between `node` and the expression's root; `jumps` is false once
|
|
75
|
+
# the walk has crossed a construct that retargets a jump.
|
|
76
|
+
def effect?(node, nesting, jumps)
|
|
77
|
+
klass = node.class
|
|
78
|
+
return true if LOCAL_WRITE_NODES.include?(klass) && node.depth >= nesting
|
|
79
|
+
return true if OUTLIVING_WRITE_NODES.include?(klass)
|
|
80
|
+
return true if jumps && JUMP_NODES.include?(klass)
|
|
81
|
+
return true if klass == Prism::CallNode && outliving_mutation?(node, nesting)
|
|
82
|
+
return false if OPAQUE_NODES.include?(klass)
|
|
83
|
+
|
|
84
|
+
nesting += 1 if SCOPE_NODES.include?(klass)
|
|
85
|
+
jumps &&= !JumpTargets.boundary?(node)
|
|
86
|
+
found = false
|
|
87
|
+
node.rigor_each_child { |child| found ||= effect?(child, nesting, jumps) }
|
|
88
|
+
found
|
|
89
|
+
end
|
|
90
|
+
private_class_method :effect?
|
|
91
|
+
|
|
92
|
+
# True when `node` is a call the straight-line widening would answer for a variable that outlives the
|
|
93
|
+
# expression: a {MutationWidening::SHAPE_MUTATORS} name on a receiver whose {ReceiverAlias.mutated_reads},
|
|
94
|
+
# or the local an element read is rooted at ({ElementReadWidening.element_read_path}, `a[0] << e`), reach
|
|
95
|
+
# past the blocks between `node` and the expression's root. A block's own local — its parameter, a name it
|
|
96
|
+
# introduces — is that block's to widen.
|
|
97
|
+
def outliving_mutation?(node, nesting)
|
|
98
|
+
return false unless MutationWidening::SHAPE_MUTATORS.include?(node.name)
|
|
99
|
+
|
|
100
|
+
receiver = node.receiver
|
|
101
|
+
return false if receiver.nil? || receiver.is_a?(Prism::SelfNode)
|
|
102
|
+
|
|
103
|
+
path = ElementReadWidening.element_read_path(receiver)
|
|
104
|
+
return outliving_read?(path.first, nesting) if path
|
|
105
|
+
|
|
106
|
+
ReceiverAlias.mutated_reads(receiver).any? { |read| outliving_read?(read, nesting) }
|
|
107
|
+
end
|
|
108
|
+
private_class_method :outliving_mutation?
|
|
109
|
+
|
|
110
|
+
# A local read reaches the expression's own scope when its `depth` does; an `it` read is always the
|
|
111
|
+
# innermost block's parameter, so only one outside every block in the expression does. An instance
|
|
112
|
+
# variable, class variable or global always outlives it.
|
|
113
|
+
def outliving_read?(read, nesting)
|
|
114
|
+
case read
|
|
115
|
+
when Prism::LocalVariableReadNode then read.depth >= nesting
|
|
116
|
+
when Prism::ItLocalVariableReadNode then nesting.zero?
|
|
117
|
+
else true
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
private_class_method :outliving_read?
|
|
121
|
+
|
|
122
|
+
# The locals (bare names), instance variables and globals (sigil-prefixed names, as {CapturedLocals.bind}
|
|
123
|
+
# reads them) `node` writes on the terms {.any?} counts a write, in first-write order.
|
|
124
|
+
def written_variables(node)
|
|
125
|
+
names = []
|
|
126
|
+
collect_written(node, 0, names) if node.is_a?(Prism::Node)
|
|
127
|
+
names.uniq
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# The locals and instance variables `node` reads, named as {.written_variables} names them. A compound write
|
|
131
|
+
# (`x += 1`, `x ||= v`) reads the variable it writes.
|
|
132
|
+
def read_variables(node)
|
|
133
|
+
names = []
|
|
134
|
+
collect_read(node, 0, names) if node.is_a?(Prism::Node)
|
|
135
|
+
names.uniq
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def collect_read(node, nesting, names)
|
|
139
|
+
klass = node.class
|
|
140
|
+
if klass == Prism::LocalVariableReadNode || COMPOUND_LOCAL_WRITES.include?(klass)
|
|
141
|
+
names << node.name if node.depth >= nesting
|
|
142
|
+
elsif klass == Prism::InstanceVariableReadNode || COMPOUND_INSTANCE_WRITES.include?(klass)
|
|
143
|
+
names << node.name
|
|
144
|
+
end
|
|
145
|
+
return if OPAQUE_NODES.include?(klass)
|
|
146
|
+
|
|
147
|
+
nesting += 1 if SCOPE_NODES.include?(klass)
|
|
148
|
+
node.rigor_each_child { |child| collect_read(child, nesting, names) }
|
|
149
|
+
end
|
|
150
|
+
private_class_method :collect_read
|
|
151
|
+
|
|
152
|
+
def collect_written(node, nesting, names)
|
|
153
|
+
klass = node.class
|
|
154
|
+
if LOCAL_WRITE_NODES.include?(klass)
|
|
155
|
+
names << node.name if node.depth >= nesting
|
|
156
|
+
elsif INSTANCE_WRITE_NODES.include?(klass) || GLOBAL_WRITE_NODES.include?(klass)
|
|
157
|
+
names << node.name
|
|
158
|
+
end
|
|
159
|
+
return if OPAQUE_NODES.include?(klass)
|
|
160
|
+
|
|
161
|
+
nesting += 1 if SCOPE_NODES.include?(klass)
|
|
162
|
+
node.rigor_each_child { |child| collect_written(child, nesting, names) }
|
|
163
|
+
end
|
|
164
|
+
private_class_method :collect_written
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# Issue #1256 — the later operands of one expression `StatementEvaluator` threads and types as a whole (a
|
|
8
|
+
# call, a literal, a `rescue` modifier): the operands entered from a scope an earlier operand moved, rather
|
|
9
|
+
# than from the one the whole expression is typed from. `puts(b.unshift("s"), b.first.upcase)` enters
|
|
10
|
+
# `b.first.upcase` after the `unshift` widened `b`, and `[n += 1, n += 1]` its second element after the first
|
|
11
|
+
# write.
|
|
12
|
+
#
|
|
13
|
+
# Each such operand is recorded into the per-node scope index with the scope it was entered from, so the
|
|
14
|
+
# diagnostics read it there, and typed from that scope once, so the whole expression's value holds it
|
|
15
|
+
# ({#types}). The operand holding a write is still typed from its own entry: `out << (n += 1)` appends `1`.
|
|
16
|
+
#
|
|
17
|
+
# The walk takes an operand before anything below it, and compares an operand's entry against the scope its
|
|
18
|
+
# nearest taken ancestor is typed from, so each later operand is typed exactly once: from the bottom up, each
|
|
19
|
+
# holding the values of the ones below it. A threaded call below the root is still not typed as a whole
|
|
20
|
+
# (`StatementEvaluator#call_effects`); its later operands join the root's walk.
|
|
21
|
+
class OperandWalk
|
|
22
|
+
# Positions `ExpressionTyper` gives no value of their own: it reads their children directly, never the node,
|
|
23
|
+
# so a later one is recorded but not typed, and its children are compared against its ancestor's scope.
|
|
24
|
+
# A keyword hash is typed whole only as a plain hash: the `**h` form reads the splatted value itself
|
|
25
|
+
# (`ExpressionTyper#double_splat_hash_shape`), so its children are taken instead.
|
|
26
|
+
NON_VALUE_NODES = Set[
|
|
27
|
+
Prism::ArgumentsNode, Prism::AssocNode, Prism::AssocSplatNode, Prism::BlockArgumentNode, Prism::SplatNode,
|
|
28
|
+
Prism::KeywordHashNode
|
|
29
|
+
].freeze
|
|
30
|
+
private_constant :NON_VALUE_NODES
|
|
31
|
+
|
|
32
|
+
# The per-node scope index's `(node, scope) ->` recorder, or nil for an unrecorded pass.
|
|
33
|
+
attr_reader :recorder
|
|
34
|
+
|
|
35
|
+
def initialize(recorder)
|
|
36
|
+
@recorder = recorder
|
|
37
|
+
@entries = nil
|
|
38
|
+
@types = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The position the next taken operand lands at, for {#types}' `since:`.
|
|
42
|
+
def mark
|
|
43
|
+
@entries.nil? ? 0 : @entries.size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Takes `node`, entered from `entry`. Answers the slot {#resolve} fills, or nil for a {NON_VALUE_NODES}
|
|
47
|
+
# position.
|
|
48
|
+
def later(node, entry)
|
|
49
|
+
@recorder&.call(node, entry)
|
|
50
|
+
return nil if NON_VALUE_NODES.include?(node.class)
|
|
51
|
+
|
|
52
|
+
(@entries ||= []) << [node, entry, nil]
|
|
53
|
+
@entries.size - 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The value the handler that evaluated the operand in `slot` typed it to, from its entry.
|
|
57
|
+
def resolve(slot, type)
|
|
58
|
+
@entries[slot][2] = type
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The identity-comparing table of every taken operand's value, for `ExpressionTyper`'s `operand_types:`, or
|
|
62
|
+
# nil when the walk took none at or after `since` (a {#mark}). An operand the evaluator did not type is typed
|
|
63
|
+
# from its entry here, after every operand below it. A threaded call below the root asks for the operands
|
|
64
|
+
# taken under it once they are all taken; they are typed then and only then, and the table keeps growing,
|
|
65
|
+
# so the root's own request types only what is left.
|
|
66
|
+
def types(tracer, since: 0)
|
|
67
|
+
return nil if @entries.nil? || @entries.size <= since
|
|
68
|
+
|
|
69
|
+
@types ||= {}.compare_by_identity
|
|
70
|
+
(@entries.size - 1).downto(since) do |index|
|
|
71
|
+
node, entry, type = @entries[index]
|
|
72
|
+
next if @types.key?(node)
|
|
73
|
+
|
|
74
|
+
@types[node] = type || self.class.type_of(entry, node, tracer, @types)
|
|
75
|
+
end
|
|
76
|
+
@types
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# `node`'s type from `scope`, answering each node `operand_types` holds with its value there. `Scope#type_of`
|
|
80
|
+
# itself is plugin-facing and keeps its surface, so the table goes to the typer directly.
|
|
81
|
+
def self.type_of(scope, node, tracer, operand_types)
|
|
82
|
+
return scope.type_of(node, tracer: tracer) if operand_types.nil?
|
|
83
|
+
|
|
84
|
+
ExpressionTyper.new(scope: scope, tracer: tracer, operand_types: operand_types).type_of(node)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -43,20 +43,68 @@ module Rigor
|
|
|
43
43
|
# them would widen this channel into a taint that silences genuine diagnostics.
|
|
44
44
|
NIL_COLLAPSING_PREDICATES = %i[nil? !].freeze
|
|
45
45
|
|
|
46
|
+
# Issue #1094 — the binary comparisons that are the same statement as `nil?` when one operand is the `nil`
|
|
47
|
+
# literal: `x == nil`, `nil != x`, `x.equal?(nil)`, `nil === x`. Every one folds from the other operand's
|
|
48
|
+
# nil-freeness alone, so the derivation is exactly as sound as the unary one. A comparison against any
|
|
49
|
+
# other value folds from the carrier's value and stays out, for the reason the value predicates do.
|
|
50
|
+
NIL_COMPARISONS = %i[== != eql? equal? ===].freeze
|
|
51
|
+
|
|
52
|
+
# {.miss_answer}'s answer for an expression whose value on a miss cannot be told.
|
|
53
|
+
UNKNOWN_MISS = Object.new.freeze
|
|
54
|
+
|
|
55
|
+
# Issue #1302 — a local / ivar mark that also records what the bound value answers on a miss, so
|
|
56
|
+
# {.miss_answer} reads a predicate through the binding as it reads the inline form. `Scope` stores it in
|
|
57
|
+
# the mark table in place of the bare cause, so the answer is kept and dropped with the mark itself; a
|
|
58
|
+
# binding whose miss answer cannot be told stores the bare cause, as before.
|
|
59
|
+
BoundMark = Data.define(:cause, :miss)
|
|
60
|
+
|
|
61
|
+
# The marks a binding of today's single cause records, interned so that marking `v = h[k]` allocates
|
|
62
|
+
# nothing beyond the table it always rebuilt.
|
|
63
|
+
INTERNED_BOUND_MARKS = [nil, true, false].to_h do |miss|
|
|
64
|
+
[miss, BoundMark.new(cause: IMPLICITLY_RETURNS_NIL, miss: miss)]
|
|
65
|
+
end.freeze
|
|
66
|
+
private_constant :INTERNED_BOUND_MARKS
|
|
67
|
+
|
|
46
68
|
module_function
|
|
47
69
|
|
|
70
|
+
# What `Scope` stores for a binding marked with `cause` whose value answers `miss` on a miss.
|
|
71
|
+
def bound_mark(cause, miss)
|
|
72
|
+
return cause if miss.equal?(UNKNOWN_MISS)
|
|
73
|
+
|
|
74
|
+
(cause == IMPLICITLY_RETURNS_NIL && INTERNED_BOUND_MARKS[miss]) || BoundMark.new(cause: cause, miss: miss)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The cause and the recorded miss answer of an entry {.bound_mark} made (or nil for no entry).
|
|
78
|
+
def bound_cause(mark) = mark.is_a?(BoundMark) ? mark.cause : mark
|
|
79
|
+
def bound_miss(mark) = mark.is_a?(BoundMark) ? mark.miss : UNKNOWN_MISS
|
|
80
|
+
|
|
81
|
+
# The entry a control-flow join keeps for a name both arms mark: the answer survives only when the arms
|
|
82
|
+
# agree on it, since otherwise a miss can take either arm's.
|
|
83
|
+
def join_bound_marks(mine, theirs) = mine == theirs ? mine : bound_cause(mine)
|
|
84
|
+
|
|
85
|
+
# `rebound` — `scope` with the local `name` rebound to its type across a loop's iterations
|
|
86
|
+
# (`CapturedLocals.bind`) — re-marked with the mark `scope` holds for `name` or `optimistic`, the one an
|
|
87
|
+
# iteration's own rebind made. `scope`'s recorded miss answer is kept unless an iteration's mark joins it:
|
|
88
|
+
# that one comes without its answer, so the pair's cannot be told.
|
|
89
|
+
def carry_local_mark(scope, rebound, name, optimistic)
|
|
90
|
+
miss = optimistic ? UNKNOWN_MISS : scope.optimistic_local_miss(name)
|
|
91
|
+
rebound.with_optimistic_local(name, scope.optimistic_local(name) || optimistic, miss: miss)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# {.carry_local_mark} for an instance variable.
|
|
95
|
+
def carry_ivar_mark(scope, rebound, name, optimistic)
|
|
96
|
+
miss = optimistic ? UNKNOWN_MISS : scope.optimistic_ivar_miss(name)
|
|
97
|
+
rebound.with_optimistic_ivar(name, scope.optimistic_ivar(name) || optimistic, miss: miss)
|
|
98
|
+
end
|
|
99
|
+
|
|
48
100
|
# The effective optimistic-nil-free cause of an expression under `scope`, or nil when its nil-freeness is
|
|
49
101
|
# a property of the value rather than a bet. The single owner of the judgment: `ExpressionTyper`,
|
|
50
102
|
# `StatementEvaluator` and `AlwaysTruthyConditionCollector` all route here, so the three consumers the
|
|
51
103
|
# spec binds cannot drift apart.
|
|
52
104
|
#
|
|
53
105
|
# Resolution order — the mark recorded on the node itself, then the binding a bare local / ivar read (or
|
|
54
|
-
# a write in value position, `if (x = MAP[k])`) resolves through, then
|
|
55
|
-
# issue #313 added.
|
|
56
|
-
#
|
|
57
|
-
# @param node [Prism::Node, nil]
|
|
58
|
-
# @param scope [Rigor::Scope, nil]
|
|
59
|
-
# @return [Symbol, nil]
|
|
106
|
+
# a write in value position, `if (x = MAP[k])`) resolves through, then a safe-navigation call and the
|
|
107
|
+
# predicate-fold derivation issue #313 added.
|
|
60
108
|
def resolve(node, scope)
|
|
61
109
|
return nil if node.nil? || scope.nil?
|
|
62
110
|
|
|
@@ -67,20 +115,85 @@ module Rigor
|
|
|
67
115
|
when Prism::LocalVariableReadNode, Prism::LocalVariableWriteNode then scope.optimistic_local(node.name)
|
|
68
116
|
when Prism::InstanceVariableReadNode, Prism::InstanceVariableWriteNode then scope.optimistic_ivar(node.name)
|
|
69
117
|
when Prism::AndNode, Prism::OrNode then resolve(node.left, scope) || resolve(node.right, scope)
|
|
70
|
-
when Prism::CallNode then resolve_through_predicate(node, scope)
|
|
118
|
+
when Prism::CallNode then resolve_through_safe_navigation(node, scope) || resolve_through_predicate(node, scope)
|
|
71
119
|
when Prism::ParenthesesNode then resolve_through_parentheses(node, scope)
|
|
72
120
|
end
|
|
73
121
|
end
|
|
74
122
|
|
|
75
|
-
# `recv
|
|
76
|
-
# is
|
|
77
|
-
#
|
|
123
|
+
# `recv&.m` is `nil` exactly when `recv` is (or when `m` answers `nil`), so it restates `recv`'s presence
|
|
124
|
+
# and is as optimistic as `recv`. Only the call carrying the `&.` is derived: Ruby skips that one call and
|
|
125
|
+
# no more, so `recv&.m.n` sends `n` to the `nil` and raises (or answers `NilClass#n`), and neither it nor
|
|
126
|
+
# a plain read `recv.m` produces the `nil` a miss would — their own nil-freeness rests on the method's
|
|
127
|
+
# answer. A nil-collapsing predicate over the call (`recv&.m.nil?`) still resolves, through
|
|
128
|
+
# {.resolve_through_predicate} and back here.
|
|
129
|
+
def resolve_through_safe_navigation(node, scope)
|
|
130
|
+
resolve(node.receiver, scope) if node.safe_navigation?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# `recv.nil?` / `!recv` / `recv == nil` — the fold is a statement about `recv`, so it is exactly as
|
|
134
|
+
# optimistic as `recv` is.
|
|
78
135
|
def resolve_through_predicate(node, scope)
|
|
79
|
-
|
|
136
|
+
operand = nil_question_operand(node)
|
|
137
|
+
operand && resolve(operand, scope)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The operand a nil-collapsing predicate restates the nil-ness of, or nil when `node` is not one. A block
|
|
141
|
+
# or any argument means `nil?` / `!` is not the unary predicate it looks like (`x.!(y)` is a user-defined
|
|
142
|
+
# operator). A comparison needs exactly one positional argument and exactly one `nil` literal side, and
|
|
143
|
+
# answers the other side (`nil == nil` states nothing about a carrier).
|
|
144
|
+
def nil_question_operand(node)
|
|
80
145
|
return nil unless node.block.nil?
|
|
81
|
-
return nil unless node.arguments.nil? || node.arguments.arguments.empty?
|
|
82
146
|
|
|
83
|
-
|
|
147
|
+
arguments = node.arguments&.arguments || []
|
|
148
|
+
if NIL_COLLAPSING_PREDICATES.include?(node.name)
|
|
149
|
+
node.receiver if arguments.empty?
|
|
150
|
+
elsif NIL_COMPARISONS.include?(node.name) && arguments.size == 1
|
|
151
|
+
nil_comparison_operand(node.receiver, arguments.first)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def nil_comparison_operand(receiver, argument)
|
|
156
|
+
if argument.is_a?(Prism::NilNode) && !receiver.nil? && !receiver.is_a?(Prism::NilNode)
|
|
157
|
+
receiver
|
|
158
|
+
elsif receiver.is_a?(Prism::NilNode) && !argument.is_a?(Prism::NilNode)
|
|
159
|
+
argument
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# What a marked expression answers when the carrier its mark rests on misses, or {UNKNOWN_MISS}. The
|
|
164
|
+
# carrier itself — the read the mark is recorded on, or a `recv&.m` over a marked receiver — answers `nil`,
|
|
165
|
+
# and each nil-collapsing predicate over it answers what it answers for that value: `!` its negation,
|
|
166
|
+
# `nil?` / `== nil` whether it is `nil`, `!= nil` the reverse. A local / ivar read answers what its binding
|
|
167
|
+
# recorded beside the mark ({BoundMark}, issue #1302): `nil` for `x = h[k]` and for `x = recv&.m?`. An
|
|
168
|
+
# `&&` / `||`, a binding that recorded nothing, and anything else is unknown. `ExpressionTyper` widens a
|
|
169
|
+
# predicate's folded boolean only when the miss answers the other boolean (or cannot be told), so
|
|
170
|
+
# `!recv&.empty?` — `!false` on a hit, `!nil` on a miss — stays `true`, and so does `x = recv&.empty?; !x`.
|
|
171
|
+
def miss_answer(node, scope)
|
|
172
|
+
return nil if scope.optimistic_origins[node]
|
|
173
|
+
|
|
174
|
+
case node
|
|
175
|
+
when Prism::LocalVariableReadNode then scope.optimistic_local_miss(node.name)
|
|
176
|
+
when Prism::InstanceVariableReadNode then scope.optimistic_ivar_miss(node.name)
|
|
177
|
+
when Prism::CallNode then miss_answer_of_call(node, scope)
|
|
178
|
+
when Prism::ParenthesesNode
|
|
179
|
+
body = node.body
|
|
180
|
+
body.is_a?(Prism::StatementsNode) && body.body.size == 1 ? miss_answer(body.body.first, scope) : UNKNOWN_MISS
|
|
181
|
+
else UNKNOWN_MISS
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def miss_answer_of_call(node, scope)
|
|
186
|
+
return nil if node.safe_navigation? && resolve(node.receiver, scope)
|
|
187
|
+
|
|
188
|
+
operand = nil_question_operand(node)
|
|
189
|
+
inner = operand ? miss_answer(operand, scope) : UNKNOWN_MISS
|
|
190
|
+
return UNKNOWN_MISS if inner.equal?(UNKNOWN_MISS)
|
|
191
|
+
|
|
192
|
+
case node.name
|
|
193
|
+
when :! then !inner
|
|
194
|
+
when :!= then !inner.nil?
|
|
195
|
+
else inner.nil?
|
|
196
|
+
end
|
|
84
197
|
end
|
|
85
198
|
|
|
86
199
|
# `(x.nil?)` — a single-statement parenthesised body is its own value, and authors do parenthesise a
|
|
@@ -93,13 +206,37 @@ module Rigor
|
|
|
93
206
|
resolve(body.body.first, scope)
|
|
94
207
|
end
|
|
95
208
|
|
|
209
|
+
# The mark a multiple assignment's right-hand side hands to the slots it fills, in the shape
|
|
210
|
+
# `MultiTargetBinder.bind_marked`'s `optimistic:` takes. `true` when the whole right-hand side resolves
|
|
211
|
+
# to a mark: a miss makes it `nil`, and `k, v = nil` binds `nil` to every fixed slot, so each slot's
|
|
212
|
+
# nil-freeness is the same bet (the rule a nested target under an optimistic slot already follows). A
|
|
213
|
+
# literal array right-hand side (`x, y = h[k], 1`) is judged element-wise instead — an Array of each
|
|
214
|
+
# element's own marks, nested for a nested literal — since each element is its slot's value; a splat
|
|
215
|
+
# element makes the slot positions unknowable and declines. `false` when nothing is marked.
|
|
216
|
+
def destructuring_marks(node, scope)
|
|
217
|
+
return true if resolve(node, scope)
|
|
218
|
+
return false unless node.is_a?(Prism::ArrayNode) && node.elements.none?(Prism::SplatNode)
|
|
219
|
+
|
|
220
|
+
marks = node.elements.map { |element| destructuring_marks(element, scope) }
|
|
221
|
+
marks.any? { |mark| mark != false } ? marks : false
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Issue #1302 — the miss answer every slot {.destructuring_marks} marks records: `nil` when each marked
|
|
225
|
+
# part of the right-hand side answers `nil` on a miss, since `nil` destructures to `nil` in every slot, and
|
|
226
|
+
# {UNKNOWN_MISS} otherwise. A marked right-hand side a miss makes a boolean (`a, b = !h[k]`) binds it to
|
|
227
|
+
# the first slot only, so its slots are not told apart and all of them record nothing.
|
|
228
|
+
def destructuring_miss(node, scope)
|
|
229
|
+
return miss_answer(node, scope).nil? ? nil : UNKNOWN_MISS if resolve(node, scope)
|
|
230
|
+
return nil unless node.is_a?(Prism::ArrayNode)
|
|
231
|
+
|
|
232
|
+
node.elements.all? { |element| destructuring_miss(element, scope).nil? } ? nil : UNKNOWN_MISS
|
|
233
|
+
end
|
|
234
|
+
|
|
96
235
|
# Whether the overload the selector actually picked carries the ignored annotation. The judgment is
|
|
97
236
|
# per-overload, which is what makes it precise: `Array#first` is optimistic while `Array#first(3)` is
|
|
98
237
|
# not, and `String#[]` / `Enumerable#find` are honest because they already spell the miss as `?`.
|
|
99
238
|
#
|
|
100
|
-
# @param
|
|
101
|
-
# @param method_type [RBS::MethodType] the overload {OverloadSelector.select} returned
|
|
102
|
-
# @return [Boolean]
|
|
239
|
+
# @param method_type — the overload {OverloadSelector.select} returned
|
|
103
240
|
def optimistic_overload?(method_definition, method_type)
|
|
104
241
|
type_def = matching_type_def(method_definition, method_type)
|
|
105
242
|
return false unless type_def.respond_to?(:overload_annotations)
|
|
@@ -18,9 +18,8 @@ module Rigor
|
|
|
18
18
|
module OriginLookup
|
|
19
19
|
module_function
|
|
20
20
|
|
|
21
|
-
# @param
|
|
22
|
-
# @
|
|
23
|
-
# @return [Symbol, nil] the effective dynamic-origin cause, or nil when none is known
|
|
21
|
+
# @param node — the receiver expression's node (nil for an implicit-self receiver)
|
|
22
|
+
# @return the effective dynamic-origin cause, or nil when none is known
|
|
24
23
|
def origin_for(scope, node)
|
|
25
24
|
return nil if node.nil?
|
|
26
25
|
|
|
@@ -116,11 +116,10 @@ module Rigor
|
|
|
116
116
|
# single-level pass.
|
|
117
117
|
DEFAULT_ROUNDS = 3
|
|
118
118
|
|
|
119
|
-
# @param files
|
|
120
|
-
# @param
|
|
121
|
-
# @param
|
|
122
|
-
# @
|
|
123
|
-
# @return [Hash{[String,Symbol,Symbol] => Hash{Symbol => Rigor::Type}}] frozen.
|
|
119
|
+
# @param files — project `.rb` paths to scan for call sites.
|
|
120
|
+
# @param target_ruby — Prism parse target.
|
|
121
|
+
# @param max_rounds — the WD5 fixpoint cap (1 = single-level).
|
|
122
|
+
# @return => Hash{Symbol => Rigor::Type}}] frozen.
|
|
124
123
|
def self.collect(files:, environment:, target_ruby: nil, max_rounds: DEFAULT_ROUNDS, workers: 0)
|
|
125
124
|
new(files: files, environment: environment, target_ruby: target_ruby,
|
|
126
125
|
max_rounds: max_rounds, workers: workers).collect
|
|
@@ -266,7 +265,7 @@ module Rigor
|
|
|
266
265
|
method_visibilities: :discovered_method_visibilities,
|
|
267
266
|
methods: :discovered_methods,
|
|
268
267
|
data_member_layouts: :data_member_layouts,
|
|
269
|
-
struct_member_layouts: :struct_member_layouts
|
|
268
|
+
struct_member_layouts: :struct_member_layouts, deferred_ranges: :discovered_deferred_ranges
|
|
270
269
|
}.freeze
|
|
271
270
|
private_constant :DISCOVERY_FIELD
|
|
272
271
|
|
|
@@ -330,7 +329,8 @@ module Rigor
|
|
|
330
329
|
arg.is_a?(Prism::AssocSplatNode)
|
|
331
330
|
end
|
|
332
331
|
|
|
333
|
-
# @return [
|
|
332
|
+
# @return the callee's `[class_name, method_name, kind, def_node]`, or nil when the receiver's class
|
|
333
|
+
# or the method's `def` is not in the project index
|
|
334
334
|
def resolve_callee(call_node, scope, index)
|
|
335
335
|
if call_node.receiver.nil?
|
|
336
336
|
class_name, kind = implicit_self_target(scope)
|
|
@@ -92,13 +92,13 @@ module Rigor
|
|
|
92
92
|
|
|
93
93
|
# Collects and widens the constants every `pre_eval:` file declares.
|
|
94
94
|
#
|
|
95
|
-
# @param paths
|
|
96
|
-
# @param scope_builder
|
|
95
|
+
# @param paths — absolute paths to the `pre_eval:` files that exist on disk.
|
|
96
|
+
# @param scope_builder — `path -> Rigor::Scope`; the caller supplies a project-seeded, environment-
|
|
97
97
|
# bound scope so the rvalue typer resolves cross-file classes exactly as per-file analysis would.
|
|
98
|
-
# @param target_ruby
|
|
99
|
-
# @param buffer
|
|
98
|
+
# @param target_ruby — the Prism parse version (`Configuration#target_ruby`).
|
|
99
|
+
# @param buffer — editor-mode binding; when set, a listed file that
|
|
100
100
|
# matches the in-flight buffer is read from its physical bytes.
|
|
101
|
-
# @return
|
|
101
|
+
# @return frozen qualified-name -> published type table.
|
|
102
102
|
def collect(paths:, scope_builder:, target_ruby: nil, buffer: nil)
|
|
103
103
|
published = {}
|
|
104
104
|
conflicted = {}
|
|
@@ -165,6 +165,7 @@ module Rigor
|
|
|
165
165
|
when Type::Constant then widen_constant(type)
|
|
166
166
|
when Type::Refined then widen(type.base)
|
|
167
167
|
when Type::IntegerRange then Type::Combinator.nominal_of("Integer")
|
|
168
|
+
when Type::FloatRange then Type::Combinator.nominal_of("Float")
|
|
168
169
|
when Type::Tuple then Type::Combinator.nominal_of("Array")
|
|
169
170
|
when Type::HashShape then Type::Combinator.nominal_of("Hash")
|
|
170
171
|
when Type::Nominal then type.type_args.empty? ? type : Type::Combinator.nominal_of(type.class_name)
|
|
@@ -122,13 +122,12 @@ module Rigor
|
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
# @param scope
|
|
125
|
+
# @param scope — base scope for type inference.
|
|
126
126
|
def initialize(scope: nil)
|
|
127
127
|
@scope = scope || Scope.empty
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
# @param root
|
|
131
|
-
# @return [FileResult]
|
|
130
|
+
# @param root — the parsed AST
|
|
132
131
|
def scan(root)
|
|
133
132
|
scope_index = ScopeIndexer.index(root, default_scope: @scope)
|
|
134
133
|
tier_counts = TIERS.to_h { |t| [t, 0] }
|
|
@@ -156,7 +155,7 @@ module Rigor
|
|
|
156
155
|
when Type::Nominal, Type::Singleton,
|
|
157
156
|
Type::DataClass, Type::StructClass then :nominal
|
|
158
157
|
when Type::Tuple, Type::HashShape,
|
|
159
|
-
Type::IntegerRange, Type::App,
|
|
158
|
+
Type::IntegerRange, Type::FloatRange, Type::App,
|
|
160
159
|
Type::DataInstance, Type::StructInstance,
|
|
161
160
|
Type::BoundMethod then :shaped
|
|
162
161
|
when Type::Refined then :refined
|