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
|
@@ -25,6 +25,9 @@ require_relative "check_rules/ivar_write_collector"
|
|
|
25
25
|
require_relative "check_rules/main_pass_collector"
|
|
26
26
|
require_relative "check_rules/void_value_use_collector"
|
|
27
27
|
require_relative "check_rules/self_closedness_scanner"
|
|
28
|
+
require_relative "check_rules/source_arity"
|
|
29
|
+
require_relative "check_rules/lexical_method_sites"
|
|
30
|
+
require_relative "check_rules/special_global_setters"
|
|
28
31
|
|
|
29
32
|
module Rigor
|
|
30
33
|
module Analysis
|
|
@@ -83,11 +86,8 @@ module Rigor
|
|
|
83
86
|
# have already produced `scope_index` through
|
|
84
87
|
# `Rigor::Inference::ScopeIndexer.index(root, default_scope:)`.
|
|
85
88
|
#
|
|
86
|
-
# @param path
|
|
89
|
+
# @param path — used to populate
|
|
87
90
|
# `Diagnostic#path`; the rule does not open files.
|
|
88
|
-
# @param root [Prism::Node]
|
|
89
|
-
# @param scope_index [Hash{Prism::Node => Rigor::Scope}]
|
|
90
|
-
# @return [Array<Rigor::Analysis::Diagnostic>]
|
|
91
91
|
#
|
|
92
92
|
# ADR-53 B4 — when `node_collectors` is supplied, the converged
|
|
93
93
|
# {Plugin::NodeRuleWalk} traversal has already populated the built-in
|
|
@@ -138,24 +138,37 @@ module Rigor
|
|
|
138
138
|
# (`diagnose`'s `Source::NodeWalker.each` `case`), now invoked by
|
|
139
139
|
# {MainPassCollector} on the shared {RuleWalk}. Returns the
|
|
140
140
|
# diagnostics for one node, in the same emission order as before.
|
|
141
|
-
def main_pass_node_diagnostics(path, node, scope_index)
|
|
141
|
+
def main_pass_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
142
142
|
case node
|
|
143
143
|
when Prism::CallNode
|
|
144
|
-
call_node_diagnostics(path, node, scope_index)
|
|
144
|
+
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites)
|
|
145
145
|
when Prism::DefNode
|
|
146
146
|
[
|
|
147
|
-
|
|
147
|
+
refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites),
|
|
148
148
|
override_visibility_diagnostic(path, node, scope_index),
|
|
149
149
|
override_return_widened_diagnostic(path, node, scope_index),
|
|
150
150
|
override_param_narrowed_diagnostic(path, node, scope_index)
|
|
151
151
|
].compact
|
|
152
152
|
when Prism::IfNode, Prism::UnlessNode
|
|
153
153
|
[unreachable_branch_diagnostic(path, node, scope_index)].compact
|
|
154
|
+
when Prism::GlobalVariableWriteNode, Prism::GlobalVariableOperatorWriteNode, Prism::MultiWriteNode
|
|
155
|
+
special_global_write_diagnostics(path, node, scope_index, lexical_sites)
|
|
154
156
|
else
|
|
155
157
|
[]
|
|
156
158
|
end
|
|
157
159
|
end
|
|
158
160
|
|
|
161
|
+
# Issue #1120, maintainer ruling a′ — a `def` in a `refine X do … end` body redefines X's method by design, so
|
|
162
|
+
# X's declared return for the name is not its contract and a mismatch against it is not reported. Every other
|
|
163
|
+
# check in the body stands. The refine-body test runs only once a mismatch would report, so a file pays the
|
|
164
|
+
# {LexicalMethodSites} walk only then.
|
|
165
|
+
def refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites)
|
|
166
|
+
diagnostic = return_type_mismatch_diagnostic(path, node, scope_index)
|
|
167
|
+
return diagnostic if diagnostic.nil? || lexical_sites.nil?
|
|
168
|
+
|
|
169
|
+
lexical_sites.refinement_def?(node) ? nil : diagnostic
|
|
170
|
+
end
|
|
171
|
+
|
|
159
172
|
# Constructs the fresh, unpopulated built-in collector set keyed by
|
|
160
173
|
# role, including the main pass. Split out so the converged walk
|
|
161
174
|
# (ADR-53 B4) can build the collectors, drive them via a
|
|
@@ -163,9 +176,12 @@ module Rigor
|
|
|
163
176
|
# traversal, and hand the populated set back to {.diagnose} as
|
|
164
177
|
# `node_collectors:`. The main pass needs `path` because its per-node
|
|
165
178
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
166
|
-
def build_node_collectors(path, scope_index)
|
|
179
|
+
def build_node_collectors(path, scope_index, root = nil)
|
|
180
|
+
eval_ranges = receiver_eval_block_ranges(root)
|
|
181
|
+
lexical_sites = LexicalMethodSites.new(root)
|
|
182
|
+
main_pass = ->(node) { main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites) }
|
|
167
183
|
{
|
|
168
|
-
main_pass: MainPassCollector.new(
|
|
184
|
+
main_pass: MainPassCollector.new(main_pass),
|
|
169
185
|
void_value_use: VoidValueUseCollector.new(scope_index),
|
|
170
186
|
always_truthy: AlwaysTruthyConditionCollector.new(scope_index),
|
|
171
187
|
unreachable_clauses: UnreachableClauseCollector.new(scope_index),
|
|
@@ -198,7 +214,7 @@ module Rigor
|
|
|
198
214
|
# divergence aborts the run — the corpus-scale half of the
|
|
199
215
|
# equivalence harness (the curated half is `rule_walk_equivalence_spec`).
|
|
200
216
|
def run_node_collectors(path, root, scope_index)
|
|
201
|
-
collectors = build_node_collectors(path, scope_index)
|
|
217
|
+
collectors = build_node_collectors(path, scope_index, root)
|
|
202
218
|
RuleWalk.run(root, collectors.values)
|
|
203
219
|
shadow_verify_node_collectors(path, root, scope_index, collectors) if ENV["RIGOR_SHADOW_RULE_WALK"]
|
|
204
220
|
collectors
|
|
@@ -242,8 +258,10 @@ module Rigor
|
|
|
242
258
|
# produces them on the shared walk.
|
|
243
259
|
def main_pass_oracle(path, root, scope_index)
|
|
244
260
|
diagnostics = []
|
|
261
|
+
eval_ranges = receiver_eval_block_ranges(root)
|
|
262
|
+
lexical_sites = LexicalMethodSites.new(root)
|
|
245
263
|
Source::NodeWalker.each(root) do |node|
|
|
246
|
-
diagnostics.concat(main_pass_node_diagnostics(path, node, scope_index))
|
|
264
|
+
diagnostics.concat(main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites))
|
|
247
265
|
end
|
|
248
266
|
diagnostics
|
|
249
267
|
end
|
|
@@ -263,10 +281,10 @@ module Rigor
|
|
|
263
281
|
shadow_verify_node_collectors(path, root, scope_index, collectors)
|
|
264
282
|
end
|
|
265
283
|
|
|
266
|
-
def call_node_diagnostics(path, node, scope_index)
|
|
284
|
+
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
267
285
|
[
|
|
268
|
-
undefined_method_diagnostic(path, node, scope_index),
|
|
269
|
-
unresolved_toplevel_diagnostic(path, node, scope_index),
|
|
286
|
+
undefined_method_diagnostic(path, node, scope_index, lexical_sites),
|
|
287
|
+
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges),
|
|
270
288
|
wrong_arity_diagnostic(path, node, scope_index),
|
|
271
289
|
argument_type_diagnostic(path, node, scope_index),
|
|
272
290
|
nil_receiver_diagnostic(path, node, scope_index),
|
|
@@ -490,7 +508,7 @@ module Rigor
|
|
|
490
508
|
/\A#\s*rigor:(?<marker>disable-(?!file(?![\w-]))[\w-]+|enable(?:-[\w-]+)?)(?![\w-])(?<rest>.*)/
|
|
491
509
|
private_constant :UNKNOWN_SUPPRESSION_MARKER
|
|
492
510
|
|
|
493
|
-
# @return
|
|
511
|
+
# @return pair of
|
|
494
512
|
# `(line_suppressions, file_suppressions)`. Line
|
|
495
513
|
# suppressions are keyed by source line number; file
|
|
496
514
|
# suppressions apply to every line.
|
|
@@ -660,7 +678,7 @@ module Rigor
|
|
|
660
678
|
class << self
|
|
661
679
|
private
|
|
662
680
|
|
|
663
|
-
def undefined_method_diagnostic(path, call_node, scope_index) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
681
|
+
def undefined_method_diagnostic(path, call_node, scope_index, lexical_sites = nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
664
682
|
return nil if call_node.receiver.nil?
|
|
665
683
|
|
|
666
684
|
scope = scope_index[call_node]
|
|
@@ -668,10 +686,20 @@ module Rigor
|
|
|
668
686
|
|
|
669
687
|
# The two exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
670
688
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
671
|
-
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
672
|
-
#
|
|
673
|
-
#
|
|
674
|
-
#
|
|
689
|
+
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
690
|
+
#
|
|
691
|
+
# The union side of that placement IS reachable, contrary to what this comment used to say. The
|
|
692
|
+
# shape needs a non-nil union of at least two RBS-DECLARED classes — an undeclared arm is an
|
|
693
|
+
# ADR-26 open receiver and `union_arm_blocks_undefined_fire?` bails before the method lookup,
|
|
694
|
+
# which is why the earlier probes read silent and were misread as the gate being unreachable.
|
|
695
|
+
# `spec/integration/composite_receiver_plugin_typed_suppression_spec.rb` builds it. What keeps
|
|
696
|
+
# the gate from firing there today is that the dispatcher no longer records a plugin-typed call
|
|
697
|
+
# it declined (#1101), not that no such union can be built.
|
|
698
|
+
#
|
|
699
|
+
# Note what the gate would do if it were reached: the scalar `Ledger#settle` a plugin declares
|
|
700
|
+
# is exempt here, while `(Ledger | Invoice)#settle` is not, because the union rule's own
|
|
701
|
+
# `method_present_anywhere?` reads RBS and project source and never asks the registry. Closing
|
|
702
|
+
# that asymmetry is #653's union coverage, not this rule's placement.
|
|
675
703
|
# See {#call_site_exempt?}.
|
|
676
704
|
return nil if call_site_exempt?(call_node, scope)
|
|
677
705
|
|
|
@@ -717,7 +745,7 @@ module Rigor
|
|
|
717
745
|
# because the real type is empty (the real `DRb` has
|
|
718
746
|
# `start_service`), so enumerating it to prove a call
|
|
719
747
|
# "undefined" would be a false positive.
|
|
720
|
-
return nil if unenumerable_receiver?(class_name, scope)
|
|
748
|
+
return nil if unenumerable_receiver?(receiver_type, class_name, scope)
|
|
721
749
|
|
|
722
750
|
# Slice 7 phase 12 — suppress when the user has
|
|
723
751
|
# declared the method in source (`def` /
|
|
@@ -742,7 +770,7 @@ module Rigor
|
|
|
742
770
|
method_def = lookup_method(receiver_type, class_name, call_node.name, scope)
|
|
743
771
|
return nil if method_def
|
|
744
772
|
|
|
745
|
-
return nil if last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind)
|
|
773
|
+
return nil if last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind, lexical_sites)
|
|
746
774
|
|
|
747
775
|
definition_site = project_definition_site(scope, class_name, call_node.name, kind)
|
|
748
776
|
# Issue #735 — the site is evidence, and whose RBS this is decides what it is evidence OF. On a
|
|
@@ -757,9 +785,54 @@ module Rigor
|
|
|
757
785
|
build_undefined_method_diagnostic(path, call_node, receiver_type, definition_site, class_name)
|
|
758
786
|
end
|
|
759
787
|
|
|
760
|
-
#
|
|
788
|
+
# Issue #1120 — the method exists at THIS call site but not everywhere: a refinement whose `using` is in
|
|
789
|
+
# effect here, or a `def o.m` on the receiver local in this scope. Asked from {#last_resort_surface_answers?},
|
|
790
|
+
# once every project-wide table has come back "absent", so the file walk behind {LexicalMethodSites} runs
|
|
791
|
+
# only for a call about to fire.
|
|
792
|
+
# A refinement answers instance-side receivers only: a refined singleton (`refine X.singleton_class`) names
|
|
793
|
+
# no constant target, so nothing records it.
|
|
794
|
+
def lexically_defined_method?(class_name, call_node, scope, kind, lexical_sites)
|
|
795
|
+
return false if lexical_sites.nil?
|
|
796
|
+
return true if lexical_sites.singleton_local_def?(call_node)
|
|
797
|
+
return false unless kind == :instance
|
|
798
|
+
|
|
799
|
+
# ADR-46 — the answer below is a function of every refinement of this name in the project, so the
|
|
800
|
+
# consumer depends on the name whichever way it answers; a refine body edited in another file must
|
|
801
|
+
# re-check it (`IncrementalSession#refinement_affected`).
|
|
802
|
+
DependencyRecorder.read_name(:refinement, call_node.name) if DependencyRecorder.active?
|
|
803
|
+
modules = refining_modules(scope, class_name, call_node.name)
|
|
804
|
+
!modules.nil? && lexical_sites.refinement_active?(call_node, modules)
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# Issue #1120 — every module that refines `method_name` into `class_name` or one of its ancestors (a
|
|
808
|
+
# refinement of `Object` reaches a `String` receiver), or nil when none does. The table is empty on a
|
|
809
|
+
# project that refines nothing, which answers without a lookup.
|
|
810
|
+
def refining_modules(scope, class_name, method_name)
|
|
811
|
+
refinements = scope.discovered_refinements
|
|
812
|
+
return nil if refinements.empty?
|
|
813
|
+
|
|
814
|
+
modules = nil
|
|
815
|
+
refinements.each do |refined, methods|
|
|
816
|
+
names = methods[method_name]
|
|
817
|
+
next if names.nil? || !refined_receiver_class?(scope, class_name, refined)
|
|
818
|
+
|
|
819
|
+
(modules ||= []).concat(names)
|
|
820
|
+
end
|
|
821
|
+
modules
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
def refined_receiver_class?(scope, class_name, refined)
|
|
825
|
+
return true if class_name == refined
|
|
826
|
+
|
|
827
|
+
environment = scope.environment
|
|
828
|
+
!environment.nil? && environment.class_ordering(class_name, refined) == :subclass
|
|
829
|
+
end
|
|
830
|
+
|
|
831
|
+
# The probes that run only once every cheaper answer has come back "absent", kept together
|
|
761
832
|
# because they share that position and nothing else.
|
|
762
833
|
#
|
|
834
|
+
# - Issue #1120 — a method that exists at this call site only: a refinement in effect here, or a
|
|
835
|
+
# singleton `def` on the receiver local ({#lexically_defined_method?}).
|
|
763
836
|
# - Issue #739 — an instance-side MODULE receiver. A value typed as a mixin module is an instance of
|
|
764
837
|
# some class that includes it, and that class contributes an arbitrary surface: inside
|
|
765
838
|
# `module Attachable::InstanceMethods`, `self.project` is defined by the ActiveRecord model that
|
|
@@ -780,7 +853,8 @@ module Rigor
|
|
|
780
853
|
# it"; a project ancestor still might, and the typer has already resolved the site through
|
|
781
854
|
# exactly this walk. It is last because it is the only probe here that walks the class graph
|
|
782
855
|
# (see {#ancestry_declares_method?} for why that placement is load-bearing, not tidiness).
|
|
783
|
-
def last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind)
|
|
856
|
+
def last_resort_surface_answers?(receiver_type, class_name, call_node, scope, kind, lexical_sites = nil)
|
|
857
|
+
return true if lexically_defined_method?(class_name, call_node, scope, kind, lexical_sites)
|
|
784
858
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
785
859
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
786
860
|
return true if unknown_mixin_includer?(class_name, scope)
|
|
@@ -936,7 +1010,7 @@ module Rigor
|
|
|
936
1010
|
# Authored severity is `:warning`; the severity profile
|
|
937
1011
|
# remaps it (`strict` → `:error`, `balanced` →
|
|
938
1012
|
# `:warning`, `lenient` → `:off` / suppressed).
|
|
939
|
-
def unresolved_toplevel_diagnostic(path, call_node, scope_index)
|
|
1013
|
+
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil)
|
|
940
1014
|
return nil unless call_node.receiver.nil?
|
|
941
1015
|
|
|
942
1016
|
scope = scope_index[call_node]
|
|
@@ -947,10 +1021,51 @@ module Rigor
|
|
|
947
1021
|
return nil if scope.top_level_def_for(name)
|
|
948
1022
|
return nil if source_declared_method?(scope, "Object", name, :instance)
|
|
949
1023
|
return nil if Rigor::Reflection.instance_method_definition("Object", name, scope: scope)
|
|
1024
|
+
# `Target.class_eval { def added = 1; def use_added = added }` files the defs on Target
|
|
1025
|
+
# but leaves the eval body with a nil `self_type`, so `toplevel?` still holds. An eval
|
|
1026
|
+
# body is morally a class body, so ADR-34 stays silent there — including on a genuinely
|
|
1027
|
+
# undefined name inside the block. Ranges are computed once per file.
|
|
1028
|
+
return nil if call_inside_receiver_eval_ranges?(eval_ranges, call_node)
|
|
950
1029
|
|
|
951
1030
|
build_unresolved_toplevel_diagnostic(path, call_node)
|
|
952
1031
|
end
|
|
953
1032
|
|
|
1033
|
+
# ScopeIndexer keeps RECEIVER_EVAL_CALLS private; this list is the same family
|
|
1034
|
+
# (`class_eval` / `module_eval` / `class_exec` / `module_exec` / `instance_eval` /
|
|
1035
|
+
# `instance_exec`). Used only to decline ADR-34 inside those blocks.
|
|
1036
|
+
RECEIVER_EVAL_CALL_NAMES = %i[
|
|
1037
|
+
class_eval module_eval class_exec module_exec instance_eval instance_exec
|
|
1038
|
+
].freeze
|
|
1039
|
+
private_constant :RECEIVER_EVAL_CALL_NAMES
|
|
1040
|
+
EMPTY_EVAL_RANGES = [].freeze
|
|
1041
|
+
private_constant :EMPTY_EVAL_RANGES
|
|
1042
|
+
|
|
1043
|
+
def receiver_eval_block_ranges(root)
|
|
1044
|
+
return EMPTY_EVAL_RANGES if root.nil?
|
|
1045
|
+
|
|
1046
|
+
ranges = []
|
|
1047
|
+
Source::NodeWalker.each(root) do |node|
|
|
1048
|
+
next unless node.is_a?(Prism::CallNode)
|
|
1049
|
+
next unless RECEIVER_EVAL_CALL_NAMES.include?(node.name)
|
|
1050
|
+
|
|
1051
|
+
block = node.block
|
|
1052
|
+
next unless block.is_a?(Prism::BlockNode)
|
|
1053
|
+
|
|
1054
|
+
loc = block.location
|
|
1055
|
+
ranges << [loc.start_offset, loc.end_offset]
|
|
1056
|
+
end
|
|
1057
|
+
ranges
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
def call_inside_receiver_eval_ranges?(eval_ranges, call_node)
|
|
1061
|
+
return false if eval_ranges.nil? || eval_ranges.empty?
|
|
1062
|
+
|
|
1063
|
+
loc = call_node.location
|
|
1064
|
+
eval_ranges.any? do |start_offset, end_offset|
|
|
1065
|
+
loc.start_offset >= start_offset && loc.end_offset <= end_offset
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
954
1069
|
def build_unresolved_toplevel_diagnostic(path, call_node)
|
|
955
1070
|
Diagnostic.from_message_loc(
|
|
956
1071
|
call_node,
|
|
@@ -976,10 +1091,32 @@ module Rigor
|
|
|
976
1091
|
# the scalar rule enumerated `Module`'s own RBS instead and reported them.
|
|
977
1092
|
#
|
|
978
1093
|
# Kept beside `unbounded_receiver_surface?` rather than folded into it: that predicate has eight
|
|
979
|
-
# other callers
|
|
980
|
-
#
|
|
981
|
-
|
|
982
|
-
|
|
1094
|
+
# other callers, and #742 had no evidence that any of them spoke on these receivers.
|
|
1095
|
+
#
|
|
1096
|
+
# Issue #879 supplied it for exactly one — `call.wrong-arity`, which decided `v.hexdigest(1, 2, 3)`
|
|
1097
|
+
# against `Digest::Instance`'s declaration on the same run where `v.no_such_method_zzz` was declined
|
|
1098
|
+
# as unenumerable. One receiver, one run, two answers about whether that surface can be read. The
|
|
1099
|
+
# reporter could make no other caller speak: `raise` on a module- or `Class`-typed operand is silent,
|
|
1100
|
+
# and `Class`-typed `.new` with a wrong arity is silent too, so the other seven stay on
|
|
1101
|
+
# `unbounded_receiver_surface?` until something demonstrates otherwise.
|
|
1102
|
+
#
|
|
1103
|
+
# Arity takes {#unenumerable_receiver_shape?}, not this whole predicate: ADR-26's open receivers are
|
|
1104
|
+
# handled for the signature-reading rules by {#unauthoritative_inherited_signature?}, which is
|
|
1105
|
+
# deliberately narrower — a method the open class DECLARES itself keeps its arity check.
|
|
1106
|
+
def unenumerable_receiver?(receiver_type, class_name, scope)
|
|
1107
|
+
unenumerable_receiver_shape?(receiver_type, class_name, scope) ||
|
|
1108
|
+
unbounded_receiver_surface?(class_name, scope)
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
# The receiver shapes whose method surface cannot be enumerated at all, whatever the environment
|
|
1112
|
+
# declares: a generic metaclass value, and an instance-side mixin-module type — an instance of some
|
|
1113
|
+
# unknown class that includes the module, which may declare the name with any arity of its own
|
|
1114
|
+
# (#739's reasoning, applied to the signature rather than to the method's existence).
|
|
1115
|
+
#
|
|
1116
|
+
# Instance-side only, for #739's reason: `Singleton[M]` on a namespace module is a real, enumerable
|
|
1117
|
+
# surface, and `M.helper(1, 2, 3)` must keep firing.
|
|
1118
|
+
def unenumerable_receiver_shape?(receiver_type, class_name, scope)
|
|
1119
|
+
METACLASS_ARMS.include?(class_name) || module_mixin_receiver?(receiver_type, scope)
|
|
983
1120
|
end
|
|
984
1121
|
|
|
985
1122
|
# Issue #746 — a class that includes a module the environment does not know. `SudoMode::Form`
|
|
@@ -1030,7 +1167,7 @@ module Rigor
|
|
|
1030
1167
|
# about it instead of bailing. `Type::Refined` carries string-family
|
|
1031
1168
|
# refinements (`lowercase-string`, …) over an explicit `.base`;
|
|
1032
1169
|
# `Type::IntegerRange` carries the bounded-int refinements
|
|
1033
|
-
# (`non-negative-int`, `positive-int`, `
|
|
1170
|
+
# (`non-negative-int`, `positive-int`, `Integer[1..5]`), every one an
|
|
1034
1171
|
# Integer; `Type::Difference` (`A - B`) carries the non-empty /
|
|
1035
1172
|
# non-zero refinements (`non-empty-string` = `String - ""`,
|
|
1036
1173
|
# `non-empty-array` = `Array - []`, `non-zero-int` = `Integer - 0`)
|
|
@@ -1038,6 +1175,7 @@ module Rigor
|
|
|
1038
1175
|
# base (minuend) class dispatches.
|
|
1039
1176
|
when Type::Refined, Type::Difference then concrete_class_name(type.base)
|
|
1040
1177
|
when Type::IntegerRange then "Integer"
|
|
1178
|
+
when Type::FloatRange then "Float"
|
|
1041
1179
|
end
|
|
1042
1180
|
end
|
|
1043
1181
|
|
|
@@ -1097,7 +1235,11 @@ module Rigor
|
|
|
1097
1235
|
# audit), so declaring even its reader surface makes it RBS-known — and, without this, every member
|
|
1098
1236
|
# the declaration omits would become a false `call.undefined-method` for every project that locks
|
|
1099
1237
|
# activesupport, the overwhelming majority of which never opt into the plugin at all.
|
|
1100
|
-
|
|
1238
|
+
# `ActiveSupport::Concern` joined it in #534 item 7. `class_methods do … end` synthesizes a
|
|
1239
|
+
# `ClassMethods` constant and `append_features` / `prepend_features` are part of the real surface,
|
|
1240
|
+
# none of which the two-line declaration enumerates — and a concern module is the receiver of that
|
|
1241
|
+
# surface in every Rails app, so the same argument applies with the same force.
|
|
1242
|
+
GEM_OVERLAY_OPEN_RECEIVERS = Set["ActiveSupport::Duration", "ActiveSupport::Concern"].freeze
|
|
1101
1243
|
private_constant :GEM_OVERLAY_OPEN_RECEIVERS
|
|
1102
1244
|
|
|
1103
1245
|
# ADR-26 — whether `class_name` is declared "open" by a
|
|
@@ -1425,7 +1567,7 @@ module Rigor
|
|
|
1425
1567
|
# by `undefined_method_diagnostic`; it returns nil
|
|
1426
1568
|
# when the call's receiver / RBS coverage / call shape
|
|
1427
1569
|
# disqualifies the rule.
|
|
1428
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1570
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1429
1571
|
def wrong_arity_diagnostic(path, call_node, scope_index)
|
|
1430
1572
|
return nil if call_node.receiver.nil?
|
|
1431
1573
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -1441,6 +1583,12 @@ module Rigor
|
|
|
1441
1583
|
class_name = concrete_class_name(receiver_type)
|
|
1442
1584
|
return nil if class_name.nil?
|
|
1443
1585
|
|
|
1586
|
+
# Issue #879 — the receiver whose surface `call.undefined-method` already refuses to enumerate.
|
|
1587
|
+
# Deciding an arity against a declaration this rule cannot prove the receiver actually runs is the
|
|
1588
|
+
# same unsoundness in signature form: the includer may declare the name with a wider arity, and
|
|
1589
|
+
# then the call is correct code.
|
|
1590
|
+
return nil if unenumerable_receiver_shape?(receiver_type, class_name, scope)
|
|
1591
|
+
|
|
1444
1592
|
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
1445
1593
|
# `Struct.new(:a, :b).new(...)` chained: the inner
|
|
1446
1594
|
# `Struct.new(...)` is an anonymous Struct *subclass* whose
|
|
@@ -1451,24 +1599,129 @@ module Rigor
|
|
|
1451
1599
|
# `.new` against the real `Struct.new(*Symbol)` signature is a
|
|
1452
1600
|
# false positive. Skip arity-checking the chained position.
|
|
1453
1601
|
return nil if anonymous_struct_new_call?(call_node, class_name, kind)
|
|
1454
|
-
return nil if scope.discovered_method?(class_name, call_node.name, kind)
|
|
1455
|
-
|
|
1456
|
-
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
1457
|
-
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
1458
1602
|
|
|
1459
|
-
|
|
1460
|
-
return nil if method_def.nil?
|
|
1461
|
-
|
|
1462
|
-
arity_envelope = compute_arity_envelope(method_def)
|
|
1603
|
+
arity_envelope = arity_envelope_for(receiver_type, class_name, call_node, scope, kind)
|
|
1463
1604
|
return nil if arity_envelope.nil?
|
|
1464
1605
|
|
|
1465
1606
|
actual = (call_node.arguments&.arguments || []).size
|
|
1466
|
-
min, max = arity_envelope
|
|
1467
|
-
|
|
1607
|
+
min, max, source_arity = arity_envelope
|
|
1608
|
+
if actual.between?(min, max)
|
|
1609
|
+
source_arity&.settle_by_definitions
|
|
1610
|
+
return nil
|
|
1611
|
+
end
|
|
1612
|
+
# Issue #992 — a source envelope's remaining declines can only withhold, so they run only now.
|
|
1613
|
+
source_arity&.settle_by_walk
|
|
1614
|
+
return nil if source_arity && !source_arity.authoritative?(class_name)
|
|
1468
1615
|
|
|
1469
1616
|
build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
1470
1617
|
end
|
|
1471
1618
|
|
|
1619
|
+
# The `[min, max]` the call is checked against — a declared signature's, or (issue #992) the project
|
|
1620
|
+
# `def`'s own when no signature declares the method, with the {SourceArity} that found it as a third
|
|
1621
|
+
# element — or nil when neither may be trusted.
|
|
1622
|
+
#
|
|
1623
|
+
# Issue #991 — `discovered_method?` used to exempt the call outright before ever asking for a
|
|
1624
|
+
# declaration. `trustworthy_signature` is the same lookup `argument_type_diagnostic` treats as
|
|
1625
|
+
# authoritative over a source `def` (see its own comment beside that call), and the two rules must agree
|
|
1626
|
+
# on whose contract binds, so a declared method is checked against its declaration.
|
|
1627
|
+
def arity_envelope_for(receiver_type, class_name, call_node, scope, kind)
|
|
1628
|
+
if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
1629
|
+
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
1630
|
+
|
|
1631
|
+
method_def = trustworthy_signature(receiver_type, class_name, call_node, scope)
|
|
1632
|
+
if method_def && !Rigor::RbsExtended.inferred_signature?(method_def)
|
|
1633
|
+
return nil if undeclared_constructor?(class_name, call_node, kind, method_def)
|
|
1634
|
+
|
|
1635
|
+
return compute_arity_envelope(method_def)
|
|
1636
|
+
end
|
|
1637
|
+
return nil unless source_lane_open?(method_def, receiver_type, class_name, call_node, scope)
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
source_arity_envelope(class_name, call_node, scope, kind)
|
|
1641
|
+
end
|
|
1642
|
+
|
|
1643
|
+
# Issue #992 — whether a class RBS knows may still be checked against its project `def`. Two lanes:
|
|
1644
|
+
#
|
|
1645
|
+
# - the member resolved and carries `%a{rigor:v1:inferred-signature}`: under ADR-93 the bundled
|
|
1646
|
+
# `rigor-rbs-inline` plugin emits a full skeleton for EVERY `def` in a file carrying at least one
|
|
1647
|
+
# annotation (#823), with every type slot defaulted, so nobody asserted anything about it. Its arity
|
|
1648
|
+
# is NOT read here — the `def`'s envelope is, through the same {SourceArity} walk as a method with no
|
|
1649
|
+
# member at all, so one `def` is never judged by two readings of its parameter list;
|
|
1650
|
+
# - no member resolved, and the class's declaration is the project's own `sig/`
|
|
1651
|
+
# (`project_declared_class?`): a partial sidecar of a project class whose `def` lives in source.
|
|
1652
|
+
#
|
|
1653
|
+
# Everything else declines. A bundled declaration (core / stdlib / gem) describes a class the project
|
|
1654
|
+
# does not own, so a project `def` on it is an ADR-17 monkey-patch that another library may patch too;
|
|
1655
|
+
# a lookup that raised, or a name an open receiver only inherited (`trustworthy_signature` answered
|
|
1656
|
+
# nil for those while a raw lookup does not), is not "no member".
|
|
1657
|
+
def source_lane_open?(method_def, receiver_type, class_name, call_node, scope)
|
|
1658
|
+
return true if method_def
|
|
1659
|
+
|
|
1660
|
+
lookup_method(receiver_type, class_name, call_node.name, scope).nil? &&
|
|
1661
|
+
Rigor::Reflection.project_declared_class?(class_name, scope: scope)
|
|
1662
|
+
end
|
|
1663
|
+
|
|
1664
|
+
# Issue #992 — stage 1 of {SourceArity}: the nearest project definition's envelope, after the
|
|
1665
|
+
# receiver-level declines that need no walk. Stage 2 (`SourceArity#authoritative?`) runs only once the
|
|
1666
|
+
# count is known to fall outside it.
|
|
1667
|
+
def source_arity_envelope(class_name, call_node, scope, kind)
|
|
1668
|
+
return nil unless scope.discovered_parameter_envelopes.key?(Scope::DiscoveryIndex::ENVELOPE_PROJECT_WIDE)
|
|
1669
|
+
return nil if unbounded_receiver_surface?(class_name, scope)
|
|
1670
|
+
return nil if plugin_typed_call?(call_node, scope)
|
|
1671
|
+
# An instance of a project module is an instance of whatever includes it (#739's reasoning, which
|
|
1672
|
+
# `module_mixin_receiver?` can only apply to a module RBS declares).
|
|
1673
|
+
return nil if kind == :instance && project_module?(class_name, scope)
|
|
1674
|
+
return nil if project_module_class_receiver?(call_node, scope)
|
|
1675
|
+
|
|
1676
|
+
arity = SourceArity.new(scope, call_node.name, kind)
|
|
1677
|
+
envelope = arity.owner_envelope(class_name)
|
|
1678
|
+
if envelope.nil? || envelope[2]
|
|
1679
|
+
envelope.nil? && arity.ambiguous? ? arity.settle_by_walk : arity.settle_by_definitions
|
|
1680
|
+
return nil
|
|
1681
|
+
end
|
|
1682
|
+
|
|
1683
|
+
[envelope[0], envelope[1] || Float::INFINITY, arity]
|
|
1684
|
+
end
|
|
1685
|
+
|
|
1686
|
+
# `self.class.f(1)` inside a project module's instance method: the engine types `self.class` as the
|
|
1687
|
+
# module's singleton, but at runtime it is the includer's class, whose class methods are its own. The
|
|
1688
|
+
# source-lane twin of {#mixin_self_class_receiver?}, which can only see a module RBS declares.
|
|
1689
|
+
def project_module_class_receiver?(call_node, scope)
|
|
1690
|
+
receiver = call_node.receiver
|
|
1691
|
+
return false unless receiver.is_a?(Prism::CallNode) && receiver.name == :class && receiver.arguments.nil?
|
|
1692
|
+
return false if receiver.receiver.nil?
|
|
1693
|
+
|
|
1694
|
+
inner = concrete_class_name(scope.type_of(receiver.receiver))
|
|
1695
|
+
!inner.nil? && project_module?(inner, scope)
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
# Read off the table directly rather than through `Scope#parameter_envelopes_of`: a class does not turn
|
|
1699
|
+
# into a module without its declaration (and so the receiver's own typing) changing, so this answer
|
|
1700
|
+
# needs no ADR-46 class edge of its own.
|
|
1701
|
+
def project_module?(class_name, scope)
|
|
1702
|
+
bucket = scope.discovered_parameter_envelopes[class_name.to_s]
|
|
1703
|
+
!bucket.nil? && bucket.key?(Scope::DiscoveryIndex::ENVELOPE_MODULE_MARK)
|
|
1704
|
+
end
|
|
1705
|
+
|
|
1706
|
+
# Issue #917 — no loaded RBS declares a constructor for this class. The definition builder
|
|
1707
|
+
# synthesizes `.new` from the nearest `initialize` in the ancestry, and when nothing declares
|
|
1708
|
+
# one that is `BasicObject#initialize: () -> void`, so `.new` derives a nullary envelope that
|
|
1709
|
+
# is an artifact of the fallback rather than a statement about the class. `Gem::Specification`
|
|
1710
|
+
# is declared in full by the vendored rubygems sigs except for its constructor, and every
|
|
1711
|
+
# argument at the real one was reported. The rule needs "this class declares a nullary
|
|
1712
|
+
# constructor", and only a `defined_in` naming a class other than `BasicObject` is that
|
|
1713
|
+
# evidence — except on `BasicObject` and `Object` themselves, where the declaration is their
|
|
1714
|
+
# own and `.new` really does take no arguments.
|
|
1715
|
+
def undeclared_constructor?(class_name, call_node, kind, method_def)
|
|
1716
|
+
return false unless kind == :singleton && call_node.name == :new
|
|
1717
|
+
return false unless method_def.respond_to?(:defined_in)
|
|
1718
|
+
|
|
1719
|
+
defined_in = method_def.defined_in
|
|
1720
|
+
return false unless defined_in.nil? || strip_root(defined_in.to_s) == "BasicObject"
|
|
1721
|
+
|
|
1722
|
+
!%w[BasicObject Object].include?(strip_root(class_name))
|
|
1723
|
+
end
|
|
1724
|
+
|
|
1472
1725
|
# True for the outer `.new` of a chained `Struct.new(...).new`:
|
|
1473
1726
|
# `class_name`/`kind` already pin the receiver to
|
|
1474
1727
|
# `Singleton[Struct]`, and the receiver node is itself a
|
|
@@ -1590,7 +1843,10 @@ module Rigor
|
|
|
1590
1843
|
# {DeclarationSourcedGuard} exactly what it asked before — but it
|
|
1591
1844
|
# now asks it through the predicate the argument-type gates share
|
|
1592
1845
|
# (issue #324), which is what keeps the two rules from drifting.
|
|
1593
|
-
|
|
1846
|
+
#
|
|
1847
|
+
# Issue #1362 — a local copied from a global still on its declared seed (`sep = $/`) is asked against the
|
|
1848
|
+
# file's writes to that global instead: its declared `nil` is withheld, and one the file writes still fires.
|
|
1849
|
+
return nil if DeclarationSourcedGuard.withholds_nil?(call_node.receiver, scope)
|
|
1594
1850
|
|
|
1595
1851
|
# ADR-67 WD6b — an inferred-parameter receiver's type (incl. any nil constituent unioned in from a
|
|
1596
1852
|
# nil call site) is an open-call-site lower bound; a possible-nil firing against it is an FP by
|
|
@@ -2115,6 +2371,200 @@ module Rigor
|
|
|
2115
2371
|
)
|
|
2116
2372
|
end
|
|
2117
2373
|
|
|
2374
|
+
# Issue #1367 (ADR-117 Decision point 1, WD2) — `global.write-type-mismatch` and `global.readonly-write`: a
|
|
2375
|
+
# write to a special global that the interpreter's setter rejects, so the write raises every time it runs.
|
|
2376
|
+
# The envelope is {SpecialGlobalSetters}: the setter, not the global's RBS declaration.
|
|
2377
|
+
#
|
|
2378
|
+
# - `global.readonly-write` needs no type. `$g = v`, `$g op= v` and a target of a multiple assignment
|
|
2379
|
+
# (`$g, x = …`, `*$g`) always write, so a write to a read-only special raises.
|
|
2380
|
+
# - `global.write-type-mismatch` checks `$g = v` only when `v` is a literal node
|
|
2381
|
+
# ({SpecialGlobalSetters.literal_class}), whose class the syntax fixes. Any other value is never reported,
|
|
2382
|
+
# whatever its inferred type: a class RBS declares without `write` or `to_str` does not prove the object
|
|
2383
|
+
# lacks it (a singleton `def`, an `extend`, or a run-time `alias to_str to_s`, as URI has).
|
|
2384
|
+
# - `$g ||= v` and `$g &&= v` write only when the current value is falsy (truthy), so neither rule checks
|
|
2385
|
+
# them, nor a `for` index or a `rescue => $g` reference.
|
|
2386
|
+
# - A special that any project file aliases (`alias $stdout $out`, either side) is exempt from both.
|
|
2387
|
+
def special_global_write_diagnostics(path, node, scope_index, lexical_sites)
|
|
2388
|
+
case node
|
|
2389
|
+
when Prism::GlobalVariableWriteNode
|
|
2390
|
+
[readonly_global_write_diagnostic(path, node, scope_index) ||
|
|
2391
|
+
global_write_type_diagnostic(path, node, scope_index, lexical_sites)].compact
|
|
2392
|
+
when Prism::GlobalVariableOperatorWriteNode
|
|
2393
|
+
[readonly_global_write_diagnostic(path, node, scope_index)].compact
|
|
2394
|
+
when Prism::MultiWriteNode
|
|
2395
|
+
multi_write_global_targets(node).filter_map do |target|
|
|
2396
|
+
readonly_global_write_diagnostic(path, target, scope_index)
|
|
2397
|
+
end
|
|
2398
|
+
else
|
|
2399
|
+
[]
|
|
2400
|
+
end
|
|
2401
|
+
end
|
|
2402
|
+
|
|
2403
|
+
# The `GlobalVariableTargetNode`s a multiple assignment writes, through a splat (`*$g`) and nested
|
|
2404
|
+
# destructuring (`(a, $g), b = …`).
|
|
2405
|
+
def multi_write_global_targets(node)
|
|
2406
|
+
[*node.lefts, node.rest, *node.rights].flat_map do |target|
|
|
2407
|
+
case target
|
|
2408
|
+
when Prism::GlobalVariableTargetNode then [target]
|
|
2409
|
+
when Prism::SplatNode
|
|
2410
|
+
target.expression.is_a?(Prism::GlobalVariableTargetNode) ? [target.expression] : []
|
|
2411
|
+
when Prism::MultiTargetNode then multi_write_global_targets(target)
|
|
2412
|
+
else []
|
|
2413
|
+
end
|
|
2414
|
+
end
|
|
2415
|
+
end
|
|
2416
|
+
|
|
2417
|
+
def readonly_global_write_diagnostic(path, node, scope_index)
|
|
2418
|
+
return nil unless SpecialGlobalSetters.read_only?(node.name)
|
|
2419
|
+
|
|
2420
|
+
scope = scope_index[node]
|
|
2421
|
+
return nil if scope.nil? || global_aliased?(scope, node.name)
|
|
2422
|
+
|
|
2423
|
+
location = node.respond_to?(:name_loc) ? node.name_loc : node.location
|
|
2424
|
+
Diagnostic.from_location(
|
|
2425
|
+
location,
|
|
2426
|
+
rule: RULE_GLOBAL_READONLY_WRITE,
|
|
2427
|
+
path: path,
|
|
2428
|
+
message: "`#{node.name}' is a read-only variable; this write raises NameError at runtime",
|
|
2429
|
+
severity: :error
|
|
2430
|
+
)
|
|
2431
|
+
end
|
|
2432
|
+
|
|
2433
|
+
def global_write_type_diagnostic(path, node, scope_index, lexical_sites)
|
|
2434
|
+
contract = SpecialGlobalSetters.contract_for(node.name)
|
|
2435
|
+
return nil if contract.nil?
|
|
2436
|
+
|
|
2437
|
+
class_name = SpecialGlobalSetters.literal_class(node.value)
|
|
2438
|
+
return nil if class_name.nil?
|
|
2439
|
+
|
|
2440
|
+
scope = scope_index[node]
|
|
2441
|
+
return nil if scope.nil?
|
|
2442
|
+
return nil unless literal_rejected?(class_name, contract, node, scope, lexical_sites)
|
|
2443
|
+
return nil if global_aliased?(scope, node.name)
|
|
2444
|
+
|
|
2445
|
+
build_global_write_type_diagnostic(path, node, contract, class_name)
|
|
2446
|
+
end
|
|
2447
|
+
|
|
2448
|
+
# The project's {Inference::GlobalWriteCensus} with the `pre_eval:` files' own.
|
|
2449
|
+
def global_write_census(scope)
|
|
2450
|
+
census = scope.discovered_global_write_census
|
|
2451
|
+
pre_eval = scope.environment&.project_patched_methods&.write_census
|
|
2452
|
+
pre_eval.nil? || pre_eval.empty? ? census : census | pre_eval
|
|
2453
|
+
end
|
|
2454
|
+
|
|
2455
|
+
# Whether any project or `pre_eval:` file aliases `name`, on either side, which can make it another variable.
|
|
2456
|
+
def global_aliased?(scope, name)
|
|
2457
|
+
Inference::GlobalWriteCensus.aliased?(global_write_census(scope), name)
|
|
2458
|
+
end
|
|
2459
|
+
|
|
2460
|
+
# A literal is rejected when its class is none the setter takes and, for a setter that also converts
|
|
2461
|
+
# (`to_str`, `to_int`) or asks for `write`, the literal's object cannot answer that method.
|
|
2462
|
+
def literal_rejected?(class_name, contract, node, scope, lexical_sites)
|
|
2463
|
+
return false if contract.accepts_class?(class_name)
|
|
2464
|
+
return true if contract.conversion.nil?
|
|
2465
|
+
|
|
2466
|
+
!literal_may_answer?(class_name, contract.conversion, node, scope, lexical_sites)
|
|
2467
|
+
end
|
|
2468
|
+
|
|
2469
|
+
# The escape hatches through which an object answers a method its class does not define: the setter's
|
|
2470
|
+
# `respond_to?` and implicit conversion both consult them.
|
|
2471
|
+
GLOBAL_WRITE_HATCHES = %i[method_missing respond_to_missing? respond_to?].freeze
|
|
2472
|
+
private_constant :GLOBAL_WRITE_HATCHES
|
|
2473
|
+
|
|
2474
|
+
# Where RBS core declares the hatches every object inherits unchanged.
|
|
2475
|
+
DEFAULT_HATCH_OWNERS = %w[::BasicObject ::Kernel ::Object].freeze
|
|
2476
|
+
private_constant :DEFAULT_HATCH_OWNERS
|
|
2477
|
+
|
|
2478
|
+
# Whether the literal's object may answer `method_name` after all:
|
|
2479
|
+
#
|
|
2480
|
+
# - RBS gives its class the method, or a hatch of its own (a project `sig/` reopening or `include` counts);
|
|
2481
|
+
# - the program defines the method or a hatch anywhere, in any spelling, on any receiver
|
|
2482
|
+
# ({Inference::GlobalWriteCensus}): no census of where a definition lands can be complete
|
|
2483
|
+
# (`class << nil`, `K = Integer; class K`, `[Integer].each { |k| k.define_method(:write) }`);
|
|
2484
|
+
# - an ancestor's surface is rewritten from outside (`Integer.include(M)`, `Object.include(M)`, a string
|
|
2485
|
+
# `class_eval`: the `ENVELOPE_DYNAMIC_MARK`), or it or `Object` mixes in a module RBS does not rule out;
|
|
2486
|
+
# - for `write` only, a refinement that may add it is in effect at the write ({#refined_writer_in_effect?}).
|
|
2487
|
+
def literal_may_answer?(class_name, method_name, node, scope, lexical_sites)
|
|
2488
|
+
definition = Rigor::Reflection.instance_definition(class_name, scope: scope)
|
|
2489
|
+
return true if definition.nil?
|
|
2490
|
+
return true if definition.methods.key?(method_name) || overridden_hatch?(definition)
|
|
2491
|
+
|
|
2492
|
+
ancestors = definition.ancestors.ancestors.to_set { |ancestor| ancestor.name.to_s.delete_prefix("::") }
|
|
2493
|
+
names = [method_name, *GLOBAL_WRITE_HATCHES]
|
|
2494
|
+
census = global_write_census(scope)
|
|
2495
|
+
program_may_answer?(ancestors, names, census, scope) ||
|
|
2496
|
+
(method_name == :write && refined_writer_in_effect?(census, node, lexical_sites))
|
|
2497
|
+
end
|
|
2498
|
+
|
|
2499
|
+
def overridden_hatch?(definition)
|
|
2500
|
+
GLOBAL_WRITE_HATCHES.any? do |name|
|
|
2501
|
+
method = definition.methods[name]
|
|
2502
|
+
method && !DEFAULT_HATCH_OWNERS.include?(method.defined_in.to_s)
|
|
2503
|
+
end
|
|
2504
|
+
end
|
|
2505
|
+
|
|
2506
|
+
def program_may_answer?(ancestors, names, census, scope)
|
|
2507
|
+
return true if Inference::GlobalWriteCensus.defines_any_of?(census, names)
|
|
2508
|
+
|
|
2509
|
+
dynamic_mark = Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK
|
|
2510
|
+
return true if ancestors.any? { |owner| scope.parameter_envelopes_of(owner).key?(dynamic_mark) }
|
|
2511
|
+
|
|
2512
|
+
main_mixins = Inference::GlobalWriteCensus.main_mixins(census)
|
|
2513
|
+
main_mixins.any? { |mixin| rbs_mixin_may_answer?(mixin, names, scope) } ||
|
|
2514
|
+
mixin_may_answer?(ancestors, names, scope)
|
|
2515
|
+
end
|
|
2516
|
+
|
|
2517
|
+
# A module an ancestor of the literal's class mixes in through a class body (`class Integer; include
|
|
2518
|
+
# Writable; end`), followed through the modules it mixes in in turn. A project module answers when its
|
|
2519
|
+
# surface is rewritten beyond literal names (a `send(:include, …)`, a non-constant mixin); its literal
|
|
2520
|
+
# definitions are the census's. Any other module answers when RBS does not know it or declares one of
|
|
2521
|
+
# `names`.
|
|
2522
|
+
def mixin_may_answer?(ancestors, names, scope)
|
|
2523
|
+
queue = ancestors.flat_map { |owner| scope.includes_of(owner) }
|
|
2524
|
+
seen = Set.new
|
|
2525
|
+
until queue.empty?
|
|
2526
|
+
mixin = queue.shift
|
|
2527
|
+
next unless seen.add?(mixin)
|
|
2528
|
+
|
|
2529
|
+
bucket = scope.parameter_envelopes_of(mixin)
|
|
2530
|
+
if bucket.key?(Scope::DiscoveryIndex::ENVELOPE_MODULE_MARK)
|
|
2531
|
+
return true if bucket.key?(Scope::DiscoveryIndex::ENVELOPE_DYNAMIC_MARK)
|
|
2532
|
+
|
|
2533
|
+
queue.concat(scope.includes_of(mixin))
|
|
2534
|
+
elsif rbs_mixin_may_answer?(mixin, names, scope)
|
|
2535
|
+
return true
|
|
2536
|
+
end
|
|
2537
|
+
end
|
|
2538
|
+
false
|
|
2539
|
+
end
|
|
2540
|
+
|
|
2541
|
+
def rbs_mixin_may_answer?(mixin, names, scope)
|
|
2542
|
+
definition = Rigor::Reflection.instance_definition(mixin, scope: scope)
|
|
2543
|
+
definition.nil? || definition.methods.key?(names.first) || overridden_hatch?(definition)
|
|
2544
|
+
end
|
|
2545
|
+
|
|
2546
|
+
# For `write` only: some refinement may add `write` (a `def`, `define_method` or `alias_method` in a `refine`
|
|
2547
|
+
# block, or an `import_methods`), and a `using` is in effect at the write. Which class it refines is not asked:
|
|
2548
|
+
# the target may be computed (`[Array].each { |k| refine(k) { … } }`) or a constant alias (`T = Array;
|
|
2549
|
+
# refine(T)`), so a refined `write` on any class declines every literal. `respond_to?(:write)` honours an
|
|
2550
|
+
# active refinement on Ruby 4.0.5; a refined `respond_to_missing?` or `respond_to?`, and every implicit
|
|
2551
|
+
# conversion, do not, so a refinement that adds only those leaves the write reported.
|
|
2552
|
+
def refined_writer_in_effect?(census, node, lexical_sites)
|
|
2553
|
+
Inference::GlobalWriteCensus.refines_write?(census) &&
|
|
2554
|
+
(lexical_sites.nil? || lexical_sites.using_in_effect?(node))
|
|
2555
|
+
end
|
|
2556
|
+
|
|
2557
|
+
def build_global_write_type_diagnostic(path, node, contract, class_name)
|
|
2558
|
+
Diagnostic.from_name_loc(
|
|
2559
|
+
node,
|
|
2560
|
+
rule: RULE_GLOBAL_WRITE_TYPE_MISMATCH,
|
|
2561
|
+
path: path,
|
|
2562
|
+
message: "`#{node.name}' accepts only #{contract.accepts}; this write assigns " \
|
|
2563
|
+
"#{SpecialGlobalSetters.literal_description(class_name)}, which raises TypeError at runtime",
|
|
2564
|
+
severity: :error
|
|
2565
|
+
)
|
|
2566
|
+
end
|
|
2567
|
+
|
|
2118
2568
|
# v0.1.2 — `flow.unreachable-branch`. Fires when an `IfNode` / `UnlessNode` whose predicate is a
|
|
2119
2569
|
# literal `true` / `false` / `nil` (or a literal numeric / string / symbol whose Ruby truthiness is
|
|
2120
2570
|
# known at-a-glance) has an observable dead branch. The diagnostic points at the dead branch (not
|
|
@@ -2385,8 +2835,10 @@ module Rigor
|
|
|
2385
2835
|
# overload requires the dispatcher's full
|
|
2386
2836
|
# acceptance plumbing.
|
|
2387
2837
|
# - The selected overload must have NO
|
|
2388
|
-
# rest_positionals
|
|
2389
|
-
#
|
|
2838
|
+
# rest_positionals and NO trailing positionals —
|
|
2839
|
+
# the two shapes under which argument N no longer
|
|
2840
|
+
# binds to positional parameter N. Keyword
|
|
2841
|
+
# parameters do not disqualify it (#673).
|
|
2390
2842
|
# - The call must use plain positional arguments
|
|
2391
2843
|
# (no splat / kw / block-pass / forwarded).
|
|
2392
2844
|
# - Per-argument: skip when EITHER side is `Dynamic`
|
|
@@ -2451,7 +2903,7 @@ module Rigor
|
|
|
2451
2903
|
return nil if method_def.nil?
|
|
2452
2904
|
|
|
2453
2905
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
2454
|
-
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides)
|
|
2906
|
+
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
2455
2907
|
return nil if mismatch.nil?
|
|
2456
2908
|
return nil if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
|
|
2457
2909
|
|
|
@@ -2477,14 +2929,50 @@ module Rigor
|
|
|
2477
2929
|
# overload rejects) plus, on non-coerce methods, the non-nil channel
|
|
2478
2930
|
# (a single-concrete-class argument every overload rejects). See
|
|
2479
2931
|
# {#multi_overload_argument_mismatch}.
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2932
|
+
#
|
|
2933
|
+
# Each argument is typed from its own index entry, which is the call's unless an earlier operand moved
|
|
2934
|
+
# the scope it was entered from (issue #1256): `(k = 0; 5)[k]` checks `k` as the `0` it holds there.
|
|
2935
|
+
def argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2936
|
+
mismatch =
|
|
2937
|
+
if method_types.size == 1
|
|
2938
|
+
first_argument_mismatch(method_types.first, call_node, scope, param_overrides, scope_index)
|
|
2939
|
+
else
|
|
2940
|
+
multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2941
|
+
end
|
|
2942
|
+
return nil if mismatch && keyword_bearing?(method_types) && nil_tainted?(mismatch[:actual])
|
|
2943
|
+
|
|
2944
|
+
mismatch
|
|
2945
|
+
end
|
|
2946
|
+
|
|
2947
|
+
# Issue #673 widened {#argument_check_eligible?} to admit keyword-bearing signatures, whose
|
|
2948
|
+
# POSITIONAL arguments had gone unchecked. The widening is staged, and this is the half held
|
|
2949
|
+
# back: on such a signature only an argument refuted on its CLASS fires; one carrying `nil`
|
|
2950
|
+
# does not.
|
|
2951
|
+
#
|
|
2952
|
+
# Measured, not assumed. Across redmine, mastodon, tdiary-core, faraday, liquid and
|
|
2953
|
+
# rubocop-ast the widening produced exactly two new verdicts, both on correct code and both
|
|
2954
|
+
# resting on nullability: `Addrinfo.foreach(host, nil, nil, :STREAM)` (redmine — a nil service
|
|
2955
|
+
# is legal at runtime; rbs declares the parameter `String | ::Integer`) and
|
|
2956
|
+
# `Nokogiri::HTML5(html)` guarded by `return if html.nil?` one line above (mastodon — the guard
|
|
2957
|
+
# is on a method call, so the type stays `Dynamic[top]?`). Zero class-refuted new verdicts were
|
|
2958
|
+
# false. Nullability is the property a hand-written signature most often over-declares, so it
|
|
2959
|
+
# stays behind the old envelope until it has evidence of its own.
|
|
2960
|
+
#
|
|
2961
|
+
# Signatures WITHOUT keywords are untouched: `"a" + nil` fires exactly as before.
|
|
2962
|
+
def keyword_bearing?(method_types)
|
|
2963
|
+
method_types.any? do |method_type|
|
|
2964
|
+
function = method_type.type
|
|
2965
|
+
next false unless function.respond_to?(:required_keywords)
|
|
2966
|
+
|
|
2967
|
+
!function.required_keywords.empty? || !function.optional_keywords.empty? ||
|
|
2968
|
+
!function.rest_keywords.nil?
|
|
2485
2969
|
end
|
|
2486
2970
|
end
|
|
2487
2971
|
|
|
2972
|
+
def nil_tainted?(actual)
|
|
2973
|
+
nil_member?(actual) || (actual.is_a?(Type::Union) && union_contains_nil?(actual))
|
|
2974
|
+
end
|
|
2975
|
+
|
|
2488
2976
|
# Multi-overload argument-type-mismatch. The dispatcher's per-overload
|
|
2489
2977
|
# acceptance plumbing is not run here; instead the FP-safe shape mirrors
|
|
2490
2978
|
# the "absent on every arm" union-undefined-method rule: an argument is
|
|
@@ -2501,32 +2989,52 @@ module Rigor
|
|
|
2501
2989
|
# stays deferred), and decides acceptance on the RBS param type
|
|
2502
2990
|
# ({#param_accepts_arg_class?}) so it sees through the `int` / `string`
|
|
2503
2991
|
# interface-aliases the translator degrades.
|
|
2504
|
-
def multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
|
|
2992
|
+
def multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2505
2993
|
functions = method_types.map(&:type)
|
|
2506
2994
|
return nil unless functions.all? { |function| argument_check_eligible?(function) }
|
|
2507
2995
|
|
|
2508
2996
|
coerce_method = COERCE_DISPATCH_METHODS.include?(call_node.name)
|
|
2509
2997
|
arguments = call_node.arguments&.arguments || []
|
|
2510
2998
|
arguments.each_with_index do |arg, index|
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
if nil_member?(arg_type) # pure nil only — not a `T | nil` union
|
|
2517
|
-
nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2518
|
-
elsif !coerce_method
|
|
2519
|
-
non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2520
|
-
end
|
|
2999
|
+
params = checkable_overload_params(method_types, index, param_overrides, scope)
|
|
3000
|
+
next if params.nil?
|
|
3001
|
+
|
|
3002
|
+
mismatch = overload_argument_mismatch(arg, params, param_overrides,
|
|
3003
|
+
argument_scope(arg, scope, scope_index), coerce_method)
|
|
2521
3004
|
return mismatch if mismatch
|
|
2522
3005
|
end
|
|
2523
3006
|
nil
|
|
2524
3007
|
end
|
|
2525
3008
|
|
|
3009
|
+
# The mismatch (or nil) for one argument against the parameter every overload has at its position. Issue
|
|
3010
|
+
# #1362 — a builtin global's seed, or its copy, is judged by the file's writes to the global.
|
|
3011
|
+
def overload_argument_mismatch(arg, params, param_overrides, arg_scope, coerce_method)
|
|
3012
|
+
written = DeclarationSourcedGuard.written_type(arg, arg_scope)
|
|
3013
|
+
arg_type = written || arg_scope.type_of(arg)
|
|
3014
|
+
if nil_member?(arg_type) # pure nil only — not a `T | nil` union
|
|
3015
|
+
nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, arg_scope, written: !written.nil?)
|
|
3016
|
+
elsif !coerce_method
|
|
3017
|
+
non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, arg_scope)
|
|
3018
|
+
end
|
|
3019
|
+
end
|
|
3020
|
+
|
|
3021
|
+
# The parameter set at `index` that a verdict may rest on, or nil when there is none — the two
|
|
3022
|
+
# reasons being arity divergence (an overload with no param there, which is `call.wrong-arity`'s
|
|
3023
|
+
# concern) and an overload whose param names a class carrying no definition ({#stub_typed_param?},
|
|
3024
|
+
# issue #661). Both channels below fire only when EVERY overload rejects, so either one is enough
|
|
3025
|
+
# to unseat that premise.
|
|
3026
|
+
def checkable_overload_params(method_types, index, param_overrides, scope)
|
|
3027
|
+
params = overload_positional_params(method_types, index)
|
|
3028
|
+
return nil if params.nil?
|
|
3029
|
+
return nil if params.any? { |param| stub_typed_param?(param, param_overrides, scope) }
|
|
3030
|
+
|
|
3031
|
+
params
|
|
3032
|
+
end
|
|
3033
|
+
|
|
2526
3034
|
# The nil channel: a pure `nil` argument no overload admits (ADR-58
|
|
2527
3035
|
# parity excuses a declaration-sourced ivar nil).
|
|
2528
|
-
def nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2529
|
-
return nil if declaration_sourced_nil_argument?(arg, scope)
|
|
3036
|
+
def nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope, written: false)
|
|
3037
|
+
return nil if !written && declaration_sourced_nil_argument?(arg, scope)
|
|
2530
3038
|
return nil if params.any? { |param| param_admits_nil?(param, param_overrides, scope) }
|
|
2531
3039
|
|
|
2532
3040
|
{ node: arg, name: nil, expected: overload_param_expected_label(params), actual: arg_type }
|
|
@@ -2568,6 +3076,53 @@ module Rigor
|
|
|
2568
3076
|
param.map_type { |type| type.sub(substitution) }
|
|
2569
3077
|
end
|
|
2570
3078
|
|
|
3079
|
+
# ADR-26 WD8's PARAMETER-side twin (issue #661). `sig/app.rbs` declaring
|
|
3080
|
+
# `(ActiveSupport::TimeWithZone) -> void` in a project that never brings ActiveSupport's RBS in
|
|
3081
|
+
# names a class that carries no definition anywhere in the environment. Rigor still refuted
|
|
3082
|
+
# `App.take_twz(Time.now)` against it — `expected ActiveSupport::TimeWithZone, got Time` on a call
|
|
3083
|
+
# that works — because the acceptance walk compares nominals and `Time`'s ancestry does not list a
|
|
3084
|
+
# name nothing declares.
|
|
3085
|
+
#
|
|
3086
|
+
# WD8 already declines the two signature rules on such a RECEIVER, on the ground that there is
|
|
3087
|
+
# nothing authoritative to check the call against. It is the same one position over: with no
|
|
3088
|
+
# ancestry there is no fact that could make an argument satisfy the parameter either, so the
|
|
3089
|
+
# rejection is the missing signature restated as an error rather than a finding about the program.
|
|
3090
|
+
# Under AGENTS.md's false-positives-outrank-worst-case rule the honest answer is to decline, and
|
|
3091
|
+
# the report it costs is one the analyzer never had evidence for.
|
|
3092
|
+
#
|
|
3093
|
+
# Both shapes the missing name arrives in are covered, deliberately as ONE criterion. A name an
|
|
3094
|
+
# INSTANCE-method signature references is minted as an empty stub so the class builds
|
|
3095
|
+
# ({Environment::RbsLoader.stub_missing_referenced_types}) and reads back as RBS-known; the same
|
|
3096
|
+
# name in a SINGLETON signature is not, because that pass mirrors rbs's own membership test and
|
|
3097
|
+
# `validate_type_params` never reaches singleton members. That asymmetry is an artifact of which
|
|
3098
|
+
# pass ran, not of what is knowable, and the issue's own repro is the singleton half.
|
|
3099
|
+
#
|
|
3100
|
+
# A `rigor:v1:param` override is a real Rigor type the author wrote against the call site's own
|
|
3101
|
+
# method, so it is consulted first and checked normally.
|
|
3102
|
+
def stub_typed_param?(param, param_overrides, scope)
|
|
3103
|
+
return false if param_overrides[param.name]
|
|
3104
|
+
|
|
3105
|
+
undefined_param_class?(param.type, scope)
|
|
3106
|
+
end
|
|
3107
|
+
|
|
3108
|
+
# Only the arms a verdict can rest on directly: a union member, an optional's inner type, an
|
|
3109
|
+
# expanded alias, and the class instance / singleton itself. Deliberately NOT recursive into type
|
|
3110
|
+
# ARGUMENTS — `Array[Missing]` is still refuted by an `Integer` argument on the `Array` alone, and
|
|
3111
|
+
# declining there would be a lost report bought for nothing.
|
|
3112
|
+
def undefined_param_class?(rbs_type, scope)
|
|
3113
|
+
case rbs_type
|
|
3114
|
+
when RBS::Types::Union then rbs_type.types.any? { |member| undefined_param_class?(member, scope) }
|
|
3115
|
+
when RBS::Types::Optional then undefined_param_class?(rbs_type.type, scope)
|
|
3116
|
+
when RBS::Types::Alias
|
|
3117
|
+
expanded = scope.environment&.rbs_loader&.expand_type_alias(rbs_type)
|
|
3118
|
+
!expanded.nil? && undefined_param_class?(expanded, scope)
|
|
3119
|
+
when RBS::Types::ClassInstance, RBS::Types::ClassSingleton
|
|
3120
|
+
name = rbs_type.name
|
|
3121
|
+
synthesized_stub_receiver?(name, scope) || !Rigor::Reflection.rbs_class_known?(name.to_s, scope: scope)
|
|
3122
|
+
else false
|
|
3123
|
+
end
|
|
3124
|
+
end
|
|
3125
|
+
|
|
2571
3126
|
# The class names whose instances `nil` IS — `NilClass` and every
|
|
2572
3127
|
# ancestor. A parameter typed as any other class instance rejects nil.
|
|
2573
3128
|
NIL_COMPATIBLE_CLASS_NAMES = %w[NilClass Object BasicObject Kernel].to_set.freeze
|
|
@@ -2746,7 +3301,7 @@ module Rigor
|
|
|
2746
3301
|
DeclarationSourcedGuard.marked?(arg, scope)
|
|
2747
3302
|
end
|
|
2748
3303
|
|
|
2749
|
-
def first_argument_mismatch(method_type, call_node, scope, param_overrides)
|
|
3304
|
+
def first_argument_mismatch(method_type, call_node, scope, param_overrides, scope_index)
|
|
2750
3305
|
function = method_type.type
|
|
2751
3306
|
return nil unless argument_check_eligible?(function)
|
|
2752
3307
|
|
|
@@ -2756,12 +3311,17 @@ module Rigor
|
|
|
2756
3311
|
param = params[index]
|
|
2757
3312
|
next if param.nil? # arity mismatch is the wrong-arity rule's concern.
|
|
2758
3313
|
|
|
2759
|
-
mismatch = single_argument_mismatch(param, arg, scope, param_overrides)
|
|
3314
|
+
mismatch = single_argument_mismatch(param, arg, argument_scope(arg, scope, scope_index), param_overrides)
|
|
2760
3315
|
return mismatch if mismatch
|
|
2761
3316
|
end
|
|
2762
3317
|
nil
|
|
2763
3318
|
end
|
|
2764
3319
|
|
|
3320
|
+
# The scope `arg` is checked from: its own index entry ({#argument_mismatch}), else the call's.
|
|
3321
|
+
def argument_scope(arg, call_scope, scope_index)
|
|
3322
|
+
scope_index[arg] || call_scope
|
|
3323
|
+
end
|
|
3324
|
+
|
|
2765
3325
|
# The mismatch (or nil) for one positional argument against one
|
|
2766
3326
|
# parameter. The nil channel decides a pure `nil` argument on the RBS
|
|
2767
3327
|
# parameter type — seeing through the `string` / `int` interface-alias
|
|
@@ -2770,6 +3330,19 @@ module Rigor
|
|
|
2770
3330
|
# the original translated-acceptance check, with a `rigor:v1:param`
|
|
2771
3331
|
# override taking precedence over the RBS-declared type.
|
|
2772
3332
|
def single_argument_mismatch(param, arg, scope, param_overrides)
|
|
3333
|
+
return nil if stub_typed_param?(param, param_overrides, scope)
|
|
3334
|
+
|
|
3335
|
+
# Issue #1362 (ADR-58 parity, ADR-117 Decision point 2) — a read of a builtin global still on its declared
|
|
3336
|
+
# seed, or a local copied from one, is judged by the file's writes to the global, as the argument was
|
|
3337
|
+
# before the join: the declared members are not diagnostic fuel.
|
|
3338
|
+
written = DeclarationSourcedGuard.written_type(arg, scope)
|
|
3339
|
+
return written_argument_mismatch(param, arg, written, scope, param_overrides) if written
|
|
3340
|
+
|
|
3341
|
+
read_argument_mismatch(param, arg, scope, param_overrides)
|
|
3342
|
+
end
|
|
3343
|
+
|
|
3344
|
+
# {#single_argument_mismatch} for an argument that reads no builtin global's seed directly.
|
|
3345
|
+
def read_argument_mismatch(param, arg, scope, param_overrides)
|
|
2773
3346
|
arg_type = scope.type_of(arg)
|
|
2774
3347
|
|
|
2775
3348
|
if nil_member?(arg_type)
|
|
@@ -2783,10 +3356,42 @@ module Rigor
|
|
|
2783
3356
|
return nil if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
2784
3357
|
return nil if arg_type.is_a?(Type::Dynamic) || arg_type.is_a?(Type::Top)
|
|
2785
3358
|
return nil unless argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
3359
|
+
# Issue #1362 — a value that mixes such a global with something else (`c ? $stdout : StringIO.new`) is judged
|
|
3360
|
+
# without the declared members the file never writes, as it was before the join.
|
|
3361
|
+
return nil if DeclarationSourcedGuard.declared_only_rejection?(arg_type, param_type, scope) do |stripped|
|
|
3362
|
+
!plain_argument_rejects?(param, stripped, scope, param_overrides)
|
|
3363
|
+
end
|
|
2786
3364
|
|
|
2787
3365
|
{ node: arg, name: param.name, expected: param_type, actual: arg_type }
|
|
2788
3366
|
end
|
|
2789
3367
|
|
|
3368
|
+
# The mismatch for an argument judged by `written`, the file's writes to the builtin globals it reads the
|
|
3369
|
+
# seeds of, through the channels {#single_argument_mismatch} runs, without the ADR-58 withholding: before
|
|
3370
|
+
# the join the global carried no mark.
|
|
3371
|
+
def written_argument_mismatch(param, arg, written, scope, param_overrides)
|
|
3372
|
+
return nil unless plain_argument_rejects?(param, written, scope, param_overrides)
|
|
3373
|
+
|
|
3374
|
+
expected =
|
|
3375
|
+
if nil_member?(written)
|
|
3376
|
+
overload_param_expected_label([param])
|
|
3377
|
+
else
|
|
3378
|
+
param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3379
|
+
end
|
|
3380
|
+
{ node: arg, name: param.name, expected: expected, actual: written }
|
|
3381
|
+
end
|
|
3382
|
+
|
|
3383
|
+
# True when `param` rejects an argument of type `type` on the terms of {#single_argument_mismatch}'s nil and
|
|
3384
|
+
# non-nil channels, without the ADR-58 withholding.
|
|
3385
|
+
def plain_argument_rejects?(param, type, scope, param_overrides)
|
|
3386
|
+
return !param_admits_nil?(param, param_overrides, scope) if nil_member?(type)
|
|
3387
|
+
|
|
3388
|
+
param_type = param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3389
|
+
return false if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
3390
|
+
return false if type.is_a?(Type::Dynamic) || type.is_a?(Type::Top)
|
|
3391
|
+
|
|
3392
|
+
Inference::Acceptance.accepts(param_type, type, mode: :gradual).no?
|
|
3393
|
+
end
|
|
3394
|
+
|
|
2790
3395
|
# The parameter rejects the argument AND the rejection is not a
|
|
2791
3396
|
# withheld declaration-sourced-nil case.
|
|
2792
3397
|
def argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
@@ -2824,17 +3429,29 @@ module Rigor
|
|
|
2824
3429
|
Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual).yes?
|
|
2825
3430
|
end
|
|
2826
3431
|
|
|
3432
|
+
# What this rule needs is that argument N binds to positional
|
|
3433
|
+
# parameter N, so the only disqualifiers are the two shapes that
|
|
3434
|
+
# break that mapping: a rest positional (arguments past it belong
|
|
3435
|
+
# to the rest, not to a later parameter) and trailing positionals
|
|
3436
|
+
# (the last arguments bind backwards from the end).
|
|
3437
|
+
#
|
|
3438
|
+
# Issue #673 — a keyword parameter used to disqualify the whole
|
|
3439
|
+
# signature, which silently unchecked the POSITIONAL arguments of
|
|
3440
|
+
# every keyword-bearing signature in the bundle:
|
|
3441
|
+
# `time.beginning_of_week("monday")` fired while
|
|
3442
|
+
# `time.next_week("monday")`, `(?Symbol, ?same_time: bool)`, did not,
|
|
3443
|
+
# though both raise the same way at runtime. Keywords cannot shift a
|
|
3444
|
+
# positional argument's index, and a call that PASSES keywords never
|
|
3445
|
+
# reaches here at all — `plain_positional_call?` rejects a
|
|
3446
|
+
# `KeywordHashNode` argument (`**opts` included) before the signature
|
|
3447
|
+
# is consulted.
|
|
2827
3448
|
def argument_check_eligible?(function)
|
|
2828
3449
|
# See `arity_eligible?`: `UntypedFunction` lacks
|
|
2829
3450
|
# the per-arity accessors. Treat it as ineligible
|
|
2830
3451
|
# for argument-type-mismatch diagnostics.
|
|
2831
3452
|
return false unless function.respond_to?(:required_keywords)
|
|
2832
3453
|
|
|
2833
|
-
function.rest_positionals.nil? &&
|
|
2834
|
-
function.required_keywords.empty? &&
|
|
2835
|
-
function.optional_keywords.empty? &&
|
|
2836
|
-
function.rest_keywords.nil? &&
|
|
2837
|
-
function.trailing_positionals.empty?
|
|
3454
|
+
function.rest_positionals.nil? && function.trailing_positionals.empty?
|
|
2838
3455
|
end
|
|
2839
3456
|
|
|
2840
3457
|
def translate_param_type(rbs_type, environment)
|
|
@@ -2911,6 +3528,14 @@ module Rigor
|
|
|
2911
3528
|
#
|
|
2912
3529
|
# - Skips methods without an RBS declaration. The rule has no contract to compare against for
|
|
2913
3530
|
# source-only methods.
|
|
3531
|
+
# - Skips a `def` whose only declaration was resolved through an ANCESTOR. `Reflection`'s lookup
|
|
3532
|
+
# reads the fully resolved method table, so a signature written about a base class answers for
|
|
3533
|
+
# every subclass that inherits the name; comparing an override's body against it reports the
|
|
3534
|
+
# override wrong for disagreeing with a contract that was never written about it (#744, and
|
|
3535
|
+
# the four redmine sites in #856). ADR-35 already specifies this rule as comparing a method's
|
|
3536
|
+
# OWN declared return, and the sibling override rules gate on `defined_on?` — this one did not.
|
|
3537
|
+
# The inherited-declaration signal belongs to the ADR-35 family, which requires both sides
|
|
3538
|
+
# authored. Decided in ADR-110 WD3.
|
|
2914
3539
|
# - Skips methods whose enclosing class isn't a `Type::Singleton` self_type that we can name
|
|
2915
3540
|
# (top-level / module-level methods land outside the rule).
|
|
2916
3541
|
# - Skips methods whose body's last expression is absent or types as `Dynamic[top]` (the
|
|
@@ -2933,15 +3558,28 @@ module Rigor
|
|
|
2933
3558
|
declared = declared_return_type(def_node, scope_index)
|
|
2934
3559
|
return nil if declared.nil?
|
|
2935
3560
|
|
|
2936
|
-
|
|
3561
|
+
# Issue #1362 — a body that ends on a builtin global still on its declared seed, or on a local copied from
|
|
3562
|
+
# one, is judged by the file's writes to the global, as for an argument ({#single_argument_mismatch}).
|
|
3563
|
+
inferred = DeclarationSourcedGuard.written_type(last_expr, inner_scope) || inner_scope.type_of(last_expr)
|
|
2937
3564
|
return nil if dynamic_top?(inferred)
|
|
2938
3565
|
|
|
2939
3566
|
severity = compare_return(declared, inferred)
|
|
2940
3567
|
return nil if severity.nil?
|
|
3568
|
+
return nil if declared_only_return?(last_expr, inferred, declared, inner_scope)
|
|
2941
3569
|
|
|
2942
3570
|
build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
2943
3571
|
end
|
|
2944
3572
|
|
|
3573
|
+
# Issue #1362 — a body that ends on a value mixing a builtin global's seed with something else is judged
|
|
3574
|
+
# without the declared members the file never writes, as for an argument.
|
|
3575
|
+
def declared_only_return?(last_expr, inferred, declared, scope)
|
|
3576
|
+
return false unless DeclarationSourcedGuard.global_sources(last_expr, scope).empty?
|
|
3577
|
+
|
|
3578
|
+
DeclarationSourcedGuard.declared_only_rejection?(inferred, declared, scope) do |stripped|
|
|
3579
|
+
compare_return(declared, stripped).nil?
|
|
3580
|
+
end
|
|
3581
|
+
end
|
|
3582
|
+
|
|
2945
3583
|
# The body of a `def` is the last `Prism::StatementsNode` child (or a single expression for
|
|
2946
3584
|
# one-liner defs). Take the last statement; that's the implicit return.
|
|
2947
3585
|
def body_last_expression(body)
|
|
@@ -2978,6 +3616,7 @@ module Rigor
|
|
|
2978
3616
|
Reflection.singleton_method_definition(self_type.class_name, def_node.name, scope: scope)
|
|
2979
3617
|
end
|
|
2980
3618
|
return nil if method_def.nil?
|
|
3619
|
+
return nil unless defined_on?(method_def, self_type.class_name)
|
|
2981
3620
|
|
|
2982
3621
|
override = Rigor::RbsExtended.read_return_type_override(method_def, environment: scope.environment)
|
|
2983
3622
|
return override if override
|
|
@@ -3156,9 +3795,15 @@ module Rigor
|
|
|
3156
3795
|
names
|
|
3157
3796
|
end
|
|
3158
3797
|
|
|
3798
|
+
# Issue #732 — the predicate is asked of the SCOPE, not re-implemented here. This rule kept its own
|
|
3799
|
+
# three-table copy until #723 widened the scope's with `discovered_methods` and left this one behind,
|
|
3800
|
+
# so the two disagreed about exactly one shape (a project class with no instance def, no superclass
|
|
3801
|
+
# and no include — `class Base; def self.build = :built; end`) with nothing observing the fork. #682
|
|
3802
|
+
# made `Scope` the single owner of the candidate ORDER; this makes it the single owner of the
|
|
3803
|
+
# predicate that filters those candidates too.
|
|
3159
3804
|
def resolve_override_ancestor_name(scope, subclass_qualified, raw_ancestor)
|
|
3160
3805
|
resolved = scope.ancestor_name_candidates(subclass_qualified, raw_ancestor)
|
|
3161
|
-
.find { |candidate| known_user_class?(
|
|
3806
|
+
.find { |candidate| scope.known_user_class?(candidate) }
|
|
3162
3807
|
return resolved if resolved
|
|
3163
3808
|
|
|
3164
3809
|
# ADR-46 slice 3 — the override checker reads the class graph
|
|
@@ -3172,12 +3817,6 @@ module Rigor
|
|
|
3172
3817
|
nil
|
|
3173
3818
|
end
|
|
3174
3819
|
|
|
3175
|
-
def known_user_class?(scope, name)
|
|
3176
|
-
scope.discovered_superclasses.key?(name) ||
|
|
3177
|
-
scope.discovered_def_nodes.key?(name) ||
|
|
3178
|
-
scope.discovered_includes.key?(name)
|
|
3179
|
-
end
|
|
3180
|
-
|
|
3181
3820
|
def build_override_visibility_diagnostic(path, def_node, parent_class, parent_visibility, override_visibility)
|
|
3182
3821
|
Diagnostic.from_name_loc(
|
|
3183
3822
|
def_node,
|