rigortype 0.3.9 → 0.4.1
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 +5 -4
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +49 -37
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +88 -26
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- 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/definer_resolution.rb +266 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +488 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +179 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +674 -124
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +196 -13
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- 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 +580 -47
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +112 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -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.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +14 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +177 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +132 -1
|
@@ -6,6 +6,7 @@ require_relative "../reflection"
|
|
|
6
6
|
require_relative "../source/node_walker"
|
|
7
7
|
require_relative "../source/constant_path"
|
|
8
8
|
require_relative "../inference/singleton_object_constant"
|
|
9
|
+
require_relative "shadow_harness"
|
|
9
10
|
require_relative "../type"
|
|
10
11
|
require_relative "diagnostic"
|
|
11
12
|
require_relative "dependency_recorder"
|
|
@@ -25,6 +26,9 @@ require_relative "check_rules/ivar_write_collector"
|
|
|
25
26
|
require_relative "check_rules/main_pass_collector"
|
|
26
27
|
require_relative "check_rules/void_value_use_collector"
|
|
27
28
|
require_relative "check_rules/self_closedness_scanner"
|
|
29
|
+
require_relative "check_rules/source_arity"
|
|
30
|
+
require_relative "check_rules/lexical_method_sites"
|
|
31
|
+
require_relative "check_rules/special_global_setters"
|
|
28
32
|
|
|
29
33
|
module Rigor
|
|
30
34
|
module Analysis
|
|
@@ -57,10 +61,9 @@ module Rigor
|
|
|
57
61
|
# ADR-87 WD4 — the canonical rule-id table lives in the light `check_rules/rule_ids.rb` (required at the
|
|
58
62
|
# top of this file) so {Analysis::RuleCatalog} can read it without loading the engine.
|
|
59
63
|
|
|
60
|
-
# ADR-35 slice 1 —
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
OVERRIDE_ANCESTOR_WALK_LIMIT = 100
|
|
64
|
+
# ADR-35 slice 1 — the public > protected > private ordering used to decide whether an override reduces
|
|
65
|
+
# visibility. The override rules' ancestor walk is bounded by `Scope::ANCESTOR_WALK_LIMIT`, the
|
|
66
|
+
# resolution chain's.
|
|
64
67
|
VISIBILITY_RANK = { public: 2, protected: 1, private: 0 }.freeze
|
|
65
68
|
|
|
66
69
|
# Resolves a user-supplied rule token (`undefined-method`,
|
|
@@ -135,24 +138,37 @@ module Rigor
|
|
|
135
138
|
# (`diagnose`'s `Source::NodeWalker.each` `case`), now invoked by
|
|
136
139
|
# {MainPassCollector} on the shared {RuleWalk}. Returns the
|
|
137
140
|
# diagnostics for one node, in the same emission order as before.
|
|
138
|
-
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)
|
|
139
142
|
case node
|
|
140
143
|
when Prism::CallNode
|
|
141
|
-
call_node_diagnostics(path, node, scope_index)
|
|
144
|
+
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites)
|
|
142
145
|
when Prism::DefNode
|
|
143
146
|
[
|
|
144
|
-
|
|
147
|
+
refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites),
|
|
145
148
|
override_visibility_diagnostic(path, node, scope_index),
|
|
146
149
|
override_return_widened_diagnostic(path, node, scope_index),
|
|
147
150
|
override_param_narrowed_diagnostic(path, node, scope_index)
|
|
148
151
|
].compact
|
|
149
152
|
when Prism::IfNode, Prism::UnlessNode
|
|
150
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)
|
|
151
156
|
else
|
|
152
157
|
[]
|
|
153
158
|
end
|
|
154
159
|
end
|
|
155
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
|
+
|
|
156
172
|
# Constructs the fresh, unpopulated built-in collector set keyed by
|
|
157
173
|
# role, including the main pass. Split out so the converged walk
|
|
158
174
|
# (ADR-53 B4) can build the collectors, drive them via a
|
|
@@ -160,9 +176,12 @@ module Rigor
|
|
|
160
176
|
# traversal, and hand the populated set back to {.diagnose} as
|
|
161
177
|
# `node_collectors:`. The main pass needs `path` because its per-node
|
|
162
178
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
163
|
-
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) }
|
|
164
183
|
{
|
|
165
|
-
main_pass: MainPassCollector.new(
|
|
184
|
+
main_pass: MainPassCollector.new(main_pass),
|
|
166
185
|
void_value_use: VoidValueUseCollector.new(scope_index),
|
|
167
186
|
always_truthy: AlwaysTruthyConditionCollector.new(scope_index),
|
|
168
187
|
unreachable_clauses: UnreachableClauseCollector.new(scope_index),
|
|
@@ -195,9 +214,9 @@ module Rigor
|
|
|
195
214
|
# divergence aborts the run — the corpus-scale half of the
|
|
196
215
|
# equivalence harness (the curated half is `rule_walk_equivalence_spec`).
|
|
197
216
|
def run_node_collectors(path, root, scope_index)
|
|
198
|
-
collectors = build_node_collectors(path, scope_index)
|
|
217
|
+
collectors = build_node_collectors(path, scope_index, root)
|
|
199
218
|
RuleWalk.run(root, collectors.values)
|
|
200
|
-
shadow_verify_node_collectors(path, root, scope_index, collectors) if
|
|
219
|
+
shadow_verify_node_collectors(path, root, scope_index, collectors) if ShadowHarness.enabled?
|
|
201
220
|
collectors
|
|
202
221
|
end
|
|
203
222
|
|
|
@@ -239,8 +258,10 @@ module Rigor
|
|
|
239
258
|
# produces them on the shared walk.
|
|
240
259
|
def main_pass_oracle(path, root, scope_index)
|
|
241
260
|
diagnostics = []
|
|
261
|
+
eval_ranges = receiver_eval_block_ranges(root)
|
|
262
|
+
lexical_sites = LexicalMethodSites.new(root)
|
|
242
263
|
Source::NodeWalker.each(root) do |node|
|
|
243
|
-
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))
|
|
244
265
|
end
|
|
245
266
|
diagnostics
|
|
246
267
|
end
|
|
@@ -260,10 +281,10 @@ module Rigor
|
|
|
260
281
|
shadow_verify_node_collectors(path, root, scope_index, collectors)
|
|
261
282
|
end
|
|
262
283
|
|
|
263
|
-
def call_node_diagnostics(path, node, scope_index)
|
|
284
|
+
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil)
|
|
264
285
|
[
|
|
265
|
-
undefined_method_diagnostic(path, node, scope_index),
|
|
266
|
-
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),
|
|
267
288
|
wrong_arity_diagnostic(path, node, scope_index),
|
|
268
289
|
argument_type_diagnostic(path, node, scope_index),
|
|
269
290
|
nil_receiver_diagnostic(path, node, scope_index),
|
|
@@ -657,7 +678,7 @@ module Rigor
|
|
|
657
678
|
class << self
|
|
658
679
|
private
|
|
659
680
|
|
|
660
|
-
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
|
|
661
682
|
return nil if call_node.receiver.nil?
|
|
662
683
|
|
|
663
684
|
scope = scope_index[call_node]
|
|
@@ -665,10 +686,20 @@ module Rigor
|
|
|
665
686
|
|
|
666
687
|
# The two exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
667
688
|
# branch below because a plugin is consulted ONCE for the whole receiver, union included, so the
|
|
668
|
-
# gate belongs where that one consult is rather than duplicated into each shape's path.
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
#
|
|
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.
|
|
672
703
|
# See {#call_site_exempt?}.
|
|
673
704
|
return nil if call_site_exempt?(call_node, scope)
|
|
674
705
|
|
|
@@ -739,7 +770,7 @@ module Rigor
|
|
|
739
770
|
method_def = lookup_method(receiver_type, class_name, call_node.name, scope)
|
|
740
771
|
return nil if method_def
|
|
741
772
|
|
|
742
|
-
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)
|
|
743
774
|
|
|
744
775
|
definition_site = project_definition_site(scope, class_name, call_node.name, kind)
|
|
745
776
|
# Issue #735 — the site is evidence, and whose RBS this is decides what it is evidence OF. On a
|
|
@@ -754,9 +785,54 @@ module Rigor
|
|
|
754
785
|
build_undefined_method_diagnostic(path, call_node, receiver_type, definition_site, class_name)
|
|
755
786
|
end
|
|
756
787
|
|
|
757
|
-
#
|
|
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
|
|
758
832
|
# because they share that position and nothing else.
|
|
759
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?}).
|
|
760
836
|
# - Issue #739 — an instance-side MODULE receiver. A value typed as a mixin module is an instance of
|
|
761
837
|
# some class that includes it, and that class contributes an arbitrary surface: inside
|
|
762
838
|
# `module Attachable::InstanceMethods`, `self.project` is defined by the ActiveRecord model that
|
|
@@ -777,7 +853,8 @@ module Rigor
|
|
|
777
853
|
# it"; a project ancestor still might, and the typer has already resolved the site through
|
|
778
854
|
# exactly this walk. It is last because it is the only probe here that walks the class graph
|
|
779
855
|
# (see {#ancestry_declares_method?} for why that placement is load-bearing, not tidiness).
|
|
780
|
-
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)
|
|
781
858
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
782
859
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
783
860
|
return true if unknown_mixin_includer?(class_name, scope)
|
|
@@ -933,7 +1010,7 @@ module Rigor
|
|
|
933
1010
|
# Authored severity is `:warning`; the severity profile
|
|
934
1011
|
# remaps it (`strict` → `:error`, `balanced` →
|
|
935
1012
|
# `:warning`, `lenient` → `:off` / suppressed).
|
|
936
|
-
def unresolved_toplevel_diagnostic(path, call_node, scope_index)
|
|
1013
|
+
def unresolved_toplevel_diagnostic(path, call_node, scope_index, eval_ranges = nil)
|
|
937
1014
|
return nil unless call_node.receiver.nil?
|
|
938
1015
|
|
|
939
1016
|
scope = scope_index[call_node]
|
|
@@ -944,10 +1021,51 @@ module Rigor
|
|
|
944
1021
|
return nil if scope.top_level_def_for(name)
|
|
945
1022
|
return nil if source_declared_method?(scope, "Object", name, :instance)
|
|
946
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)
|
|
947
1029
|
|
|
948
1030
|
build_unresolved_toplevel_diagnostic(path, call_node)
|
|
949
1031
|
end
|
|
950
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
|
+
|
|
951
1069
|
def build_unresolved_toplevel_diagnostic(path, call_node)
|
|
952
1070
|
Diagnostic.from_message_loc(
|
|
953
1071
|
call_node,
|
|
@@ -1449,7 +1567,7 @@ module Rigor
|
|
|
1449
1567
|
# by `undefined_method_diagnostic`; it returns nil
|
|
1450
1568
|
# when the call's receiver / RBS coverage / call shape
|
|
1451
1569
|
# disqualifies the rule.
|
|
1452
|
-
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1570
|
+
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1453
1571
|
def wrong_arity_diagnostic(path, call_node, scope_index)
|
|
1454
1572
|
return nil if call_node.receiver.nil?
|
|
1455
1573
|
return nil unless plain_positional_call?(call_node)
|
|
@@ -1481,25 +1599,110 @@ module Rigor
|
|
|
1481
1599
|
# `.new` against the real `Struct.new(*Symbol)` signature is a
|
|
1482
1600
|
# false positive. Skip arity-checking the chained position.
|
|
1483
1601
|
return nil if anonymous_struct_new_call?(call_node, class_name, kind)
|
|
1484
|
-
return nil if scope.discovered_method?(class_name, call_node.name, kind)
|
|
1485
|
-
|
|
1486
|
-
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
1487
|
-
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
1488
1602
|
|
|
1489
|
-
|
|
1490
|
-
return nil if method_def.nil?
|
|
1491
|
-
return nil if undeclared_constructor?(class_name, call_node, kind, method_def)
|
|
1492
|
-
|
|
1493
|
-
arity_envelope = compute_arity_envelope(method_def)
|
|
1603
|
+
arity_envelope = arity_envelope_for(receiver_type, class_name, call_node, scope, kind)
|
|
1494
1604
|
return nil if arity_envelope.nil?
|
|
1495
1605
|
|
|
1496
1606
|
actual = (call_node.arguments&.arguments || []).size
|
|
1497
|
-
min, max = arity_envelope
|
|
1498
|
-
|
|
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)
|
|
1499
1615
|
|
|
1500
1616
|
build_arity_diagnostic(path, call_node, class_name, min, max, actual)
|
|
1501
1617
|
end
|
|
1502
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
|
+
|
|
1503
1706
|
# Issue #917 — no loaded RBS declares a constructor for this class. The definition builder
|
|
1504
1707
|
# synthesizes `.new` from the nearest `initialize` in the ancestry, and when nothing declares
|
|
1505
1708
|
# one that is `BasicObject#initialize: () -> void`, so `.new` derives a nullary envelope that
|
|
@@ -1640,7 +1843,10 @@ module Rigor
|
|
|
1640
1843
|
# {DeclarationSourcedGuard} exactly what it asked before — but it
|
|
1641
1844
|
# now asks it through the predicate the argument-type gates share
|
|
1642
1845
|
# (issue #324), which is what keeps the two rules from drifting.
|
|
1643
|
-
|
|
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)
|
|
1644
1850
|
|
|
1645
1851
|
# ADR-67 WD6b — an inferred-parameter receiver's type (incl. any nil constituent unioned in from a
|
|
1646
1852
|
# nil call site) is an open-call-site lower bound; a possible-nil firing against it is an FP by
|
|
@@ -2165,6 +2371,200 @@ module Rigor
|
|
|
2165
2371
|
)
|
|
2166
2372
|
end
|
|
2167
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
|
+
|
|
2168
2568
|
# v0.1.2 — `flow.unreachable-branch`. Fires when an `IfNode` / `UnlessNode` whose predicate is a
|
|
2169
2569
|
# literal `true` / `false` / `nil` (or a literal numeric / string / symbol whose Ruby truthiness is
|
|
2170
2570
|
# known at-a-glance) has an observable dead branch. The diagnostic points at the dead branch (not
|
|
@@ -2231,6 +2631,9 @@ module Rigor
|
|
|
2231
2631
|
# `Prism::SelfNode` are also skipped — Ruby 2.7+ permits `self.foo` for private methods.
|
|
2232
2632
|
# - Receiver MUST resolve to a `Type::Nominal` so the rule has a single class identity to query.
|
|
2233
2633
|
# Unions / Dynamic / shape carriers are skipped.
|
|
2634
|
+
# - Issue #1568 — a module the class PREPENDS sits ahead of it in Ruby's order, so a definition there
|
|
2635
|
+
# is what the call reaches; the class's own private `def` answers only when nothing prepended
|
|
2636
|
+
# defines the name ({#reached_definer_visibility}).
|
|
2234
2637
|
def visibility_mismatch_diagnostic(path, call_node, scope_index)
|
|
2235
2638
|
return nil unless explicit_non_self_receiver?(call_node.receiver)
|
|
2236
2639
|
|
|
@@ -2247,9 +2650,33 @@ module Rigor
|
|
|
2247
2650
|
visibility = scope.discovered_method_visibility(receiver_type.class_name, call_node.name)
|
|
2248
2651
|
return nil unless visibility == :private
|
|
2249
2652
|
|
|
2653
|
+
reached = reached_definer_visibility(scope, receiver_type.class_name, call_node.name)
|
|
2654
|
+
return nil if reached == :undecided
|
|
2655
|
+
return nil unless reached.nil? || reached == :private
|
|
2656
|
+
|
|
2250
2657
|
build_visibility_mismatch_diagnostic(path, call_node, receiver_type)
|
|
2251
2658
|
end
|
|
2252
2659
|
|
|
2660
|
+
# Issue #1568, ADR-119 C1c — the recorded visibility of the definition a call on `class_name` reaches: the
|
|
2661
|
+
# first project entry on the class's {Scope::ResolutionChain} from its head that records the name, so a
|
|
2662
|
+
# module the class PREPENDS answers ahead of the class. `Inference::DefinerResolution` reads it with the
|
|
2663
|
+
# `:visibility` question's default answer. It is `:undecided` wherever the chain does not settle the order
|
|
2664
|
+
# (an unpositioned or forked mixin, a conditional include) or an ancestor ahead of the definer may define
|
|
2665
|
+
# the name (one RBS does not know), and the caller declines; a definer whose visibility slot ADR-119's
|
|
2666
|
+
# producers contest is not alone (`DefinerResolution` asks the next candidate too), so it is `:undecided`
|
|
2667
|
+
# there without a read of the table here. nil is nothing recording the name at all, and the class's own
|
|
2668
|
+
# visibility stands. An ancestor the project does not declare is passed over, as the method lookup passes it.
|
|
2669
|
+
def reached_definer_visibility(scope, class_name, method_name)
|
|
2670
|
+
case Inference::DefinerResolution.resolve(scope, class_name, method_name, :instance, question: :visibility)
|
|
2671
|
+
in Inference::DefinerResolution::Known(answer:)
|
|
2672
|
+
answer
|
|
2673
|
+
in Inference::DefinerResolution::UNKNOWN
|
|
2674
|
+
:undecided
|
|
2675
|
+
in Inference::DefinerResolution::ABSENT
|
|
2676
|
+
nil
|
|
2677
|
+
end
|
|
2678
|
+
end
|
|
2679
|
+
|
|
2253
2680
|
def explicit_non_self_receiver?(receiver)
|
|
2254
2681
|
return false if receiver.nil?
|
|
2255
2682
|
return false if receiver.is_a?(Prism::SelfNode)
|
|
@@ -2503,7 +2930,7 @@ module Rigor
|
|
|
2503
2930
|
return nil if method_def.nil?
|
|
2504
2931
|
|
|
2505
2932
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
2506
|
-
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides)
|
|
2933
|
+
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
2507
2934
|
return nil if mismatch.nil?
|
|
2508
2935
|
return nil if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
|
|
2509
2936
|
|
|
@@ -2529,12 +2956,15 @@ module Rigor
|
|
|
2529
2956
|
# overload rejects) plus, on non-coerce methods, the non-nil channel
|
|
2530
2957
|
# (a single-concrete-class argument every overload rejects). See
|
|
2531
2958
|
# {#multi_overload_argument_mismatch}.
|
|
2532
|
-
|
|
2959
|
+
#
|
|
2960
|
+
# Each argument is typed from its own index entry, which is the call's unless an earlier operand moved
|
|
2961
|
+
# the scope it was entered from (issue #1256): `(k = 0; 5)[k]` checks `k` as the `0` it holds there.
|
|
2962
|
+
def argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2533
2963
|
mismatch =
|
|
2534
2964
|
if method_types.size == 1
|
|
2535
|
-
first_argument_mismatch(method_types.first, call_node, scope, param_overrides)
|
|
2965
|
+
first_argument_mismatch(method_types.first, call_node, scope, param_overrides, scope_index)
|
|
2536
2966
|
else
|
|
2537
|
-
multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
|
|
2967
|
+
multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2538
2968
|
end
|
|
2539
2969
|
return nil if mismatch && keyword_bearing?(method_types) && nil_tainted?(mismatch[:actual])
|
|
2540
2970
|
|
|
@@ -2586,28 +3016,35 @@ module Rigor
|
|
|
2586
3016
|
# stays deferred), and decides acceptance on the RBS param type
|
|
2587
3017
|
# ({#param_accepts_arg_class?}) so it sees through the `int` / `string`
|
|
2588
3018
|
# interface-aliases the translator degrades.
|
|
2589
|
-
def multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
|
|
3019
|
+
def multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2590
3020
|
functions = method_types.map(&:type)
|
|
2591
3021
|
return nil unless functions.all? { |function| argument_check_eligible?(function) }
|
|
2592
3022
|
|
|
2593
3023
|
coerce_method = COERCE_DISPATCH_METHODS.include?(call_node.name)
|
|
2594
3024
|
arguments = call_node.arguments&.arguments || []
|
|
2595
3025
|
arguments.each_with_index do |arg, index|
|
|
2596
|
-
arg_type = scope.type_of(arg)
|
|
2597
3026
|
params = checkable_overload_params(method_types, index, param_overrides, scope)
|
|
2598
3027
|
next if params.nil?
|
|
2599
3028
|
|
|
2600
|
-
mismatch =
|
|
2601
|
-
|
|
2602
|
-
nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2603
|
-
elsif !coerce_method
|
|
2604
|
-
non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2605
|
-
end
|
|
3029
|
+
mismatch = overload_argument_mismatch(arg, params, param_overrides,
|
|
3030
|
+
argument_scope(arg, scope, scope_index), coerce_method)
|
|
2606
3031
|
return mismatch if mismatch
|
|
2607
3032
|
end
|
|
2608
3033
|
nil
|
|
2609
3034
|
end
|
|
2610
3035
|
|
|
3036
|
+
# The mismatch (or nil) for one argument against the parameter every overload has at its position. Issue
|
|
3037
|
+
# #1362 — a builtin global's seed, or its copy, is judged by the file's writes to the global.
|
|
3038
|
+
def overload_argument_mismatch(arg, params, param_overrides, arg_scope, coerce_method)
|
|
3039
|
+
written = DeclarationSourcedGuard.written_type(arg, arg_scope)
|
|
3040
|
+
arg_type = written || arg_scope.type_of(arg)
|
|
3041
|
+
if nil_member?(arg_type) # pure nil only — not a `T | nil` union
|
|
3042
|
+
nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, arg_scope, written: !written.nil?)
|
|
3043
|
+
elsif !coerce_method
|
|
3044
|
+
non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, arg_scope)
|
|
3045
|
+
end
|
|
3046
|
+
end
|
|
3047
|
+
|
|
2611
3048
|
# The parameter set at `index` that a verdict may rest on, or nil when there is none — the two
|
|
2612
3049
|
# reasons being arity divergence (an overload with no param there, which is `call.wrong-arity`'s
|
|
2613
3050
|
# concern) and an overload whose param names a class carrying no definition ({#stub_typed_param?},
|
|
@@ -2623,8 +3060,8 @@ module Rigor
|
|
|
2623
3060
|
|
|
2624
3061
|
# The nil channel: a pure `nil` argument no overload admits (ADR-58
|
|
2625
3062
|
# parity excuses a declaration-sourced ivar nil).
|
|
2626
|
-
def nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
2627
|
-
return nil if declaration_sourced_nil_argument?(arg, scope)
|
|
3063
|
+
def nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope, written: false)
|
|
3064
|
+
return nil if !written && declaration_sourced_nil_argument?(arg, scope)
|
|
2628
3065
|
return nil if params.any? { |param| param_admits_nil?(param, param_overrides, scope) }
|
|
2629
3066
|
|
|
2630
3067
|
{ node: arg, name: nil, expected: overload_param_expected_label(params), actual: arg_type }
|
|
@@ -2891,7 +3328,7 @@ module Rigor
|
|
|
2891
3328
|
DeclarationSourcedGuard.marked?(arg, scope)
|
|
2892
3329
|
end
|
|
2893
3330
|
|
|
2894
|
-
def first_argument_mismatch(method_type, call_node, scope, param_overrides)
|
|
3331
|
+
def first_argument_mismatch(method_type, call_node, scope, param_overrides, scope_index)
|
|
2895
3332
|
function = method_type.type
|
|
2896
3333
|
return nil unless argument_check_eligible?(function)
|
|
2897
3334
|
|
|
@@ -2901,12 +3338,17 @@ module Rigor
|
|
|
2901
3338
|
param = params[index]
|
|
2902
3339
|
next if param.nil? # arity mismatch is the wrong-arity rule's concern.
|
|
2903
3340
|
|
|
2904
|
-
mismatch = single_argument_mismatch(param, arg, scope, param_overrides)
|
|
3341
|
+
mismatch = single_argument_mismatch(param, arg, argument_scope(arg, scope, scope_index), param_overrides)
|
|
2905
3342
|
return mismatch if mismatch
|
|
2906
3343
|
end
|
|
2907
3344
|
nil
|
|
2908
3345
|
end
|
|
2909
3346
|
|
|
3347
|
+
# The scope `arg` is checked from: its own index entry ({#argument_mismatch}), else the call's.
|
|
3348
|
+
def argument_scope(arg, call_scope, scope_index)
|
|
3349
|
+
scope_index[arg] || call_scope
|
|
3350
|
+
end
|
|
3351
|
+
|
|
2910
3352
|
# The mismatch (or nil) for one positional argument against one
|
|
2911
3353
|
# parameter. The nil channel decides a pure `nil` argument on the RBS
|
|
2912
3354
|
# parameter type — seeing through the `string` / `int` interface-alias
|
|
@@ -2917,6 +3359,17 @@ module Rigor
|
|
|
2917
3359
|
def single_argument_mismatch(param, arg, scope, param_overrides)
|
|
2918
3360
|
return nil if stub_typed_param?(param, param_overrides, scope)
|
|
2919
3361
|
|
|
3362
|
+
# Issue #1362 (ADR-58 parity, ADR-117 Decision point 2) — a read of a builtin global still on its declared
|
|
3363
|
+
# seed, or a local copied from one, is judged by the file's writes to the global, as the argument was
|
|
3364
|
+
# before the join: the declared members are not diagnostic fuel.
|
|
3365
|
+
written = DeclarationSourcedGuard.written_type(arg, scope)
|
|
3366
|
+
return written_argument_mismatch(param, arg, written, scope, param_overrides) if written
|
|
3367
|
+
|
|
3368
|
+
read_argument_mismatch(param, arg, scope, param_overrides)
|
|
3369
|
+
end
|
|
3370
|
+
|
|
3371
|
+
# {#single_argument_mismatch} for an argument that reads no builtin global's seed directly.
|
|
3372
|
+
def read_argument_mismatch(param, arg, scope, param_overrides)
|
|
2920
3373
|
arg_type = scope.type_of(arg)
|
|
2921
3374
|
|
|
2922
3375
|
if nil_member?(arg_type)
|
|
@@ -2930,10 +3383,42 @@ module Rigor
|
|
|
2930
3383
|
return nil if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
2931
3384
|
return nil if arg_type.is_a?(Type::Dynamic) || arg_type.is_a?(Type::Top)
|
|
2932
3385
|
return nil unless argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
3386
|
+
# Issue #1362 — a value that mixes such a global with something else (`c ? $stdout : StringIO.new`) is judged
|
|
3387
|
+
# without the declared members the file never writes, as it was before the join.
|
|
3388
|
+
return nil if DeclarationSourcedGuard.declared_only_rejection?(arg_type, param_type, scope) do |stripped|
|
|
3389
|
+
!plain_argument_rejects?(param, stripped, scope, param_overrides)
|
|
3390
|
+
end
|
|
2933
3391
|
|
|
2934
3392
|
{ node: arg, name: param.name, expected: param_type, actual: arg_type }
|
|
2935
3393
|
end
|
|
2936
3394
|
|
|
3395
|
+
# The mismatch for an argument judged by `written`, the file's writes to the builtin globals it reads the
|
|
3396
|
+
# seeds of, through the channels {#single_argument_mismatch} runs, without the ADR-58 withholding: before
|
|
3397
|
+
# the join the global carried no mark.
|
|
3398
|
+
def written_argument_mismatch(param, arg, written, scope, param_overrides)
|
|
3399
|
+
return nil unless plain_argument_rejects?(param, written, scope, param_overrides)
|
|
3400
|
+
|
|
3401
|
+
expected =
|
|
3402
|
+
if nil_member?(written)
|
|
3403
|
+
overload_param_expected_label([param])
|
|
3404
|
+
else
|
|
3405
|
+
param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3406
|
+
end
|
|
3407
|
+
{ node: arg, name: param.name, expected: expected, actual: written }
|
|
3408
|
+
end
|
|
3409
|
+
|
|
3410
|
+
# True when `param` rejects an argument of type `type` on the terms of {#single_argument_mismatch}'s nil and
|
|
3411
|
+
# non-nil channels, without the ADR-58 withholding.
|
|
3412
|
+
def plain_argument_rejects?(param, type, scope, param_overrides)
|
|
3413
|
+
return !param_admits_nil?(param, param_overrides, scope) if nil_member?(type)
|
|
3414
|
+
|
|
3415
|
+
param_type = param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3416
|
+
return false if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
3417
|
+
return false if type.is_a?(Type::Dynamic) || type.is_a?(Type::Top)
|
|
3418
|
+
|
|
3419
|
+
Inference::Acceptance.accepts(param_type, type, mode: :gradual).no?
|
|
3420
|
+
end
|
|
3421
|
+
|
|
2937
3422
|
# The parameter rejects the argument AND the rejection is not a
|
|
2938
3423
|
# withheld declaration-sourced-nil case.
|
|
2939
3424
|
def argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
@@ -3100,15 +3585,28 @@ module Rigor
|
|
|
3100
3585
|
declared = declared_return_type(def_node, scope_index)
|
|
3101
3586
|
return nil if declared.nil?
|
|
3102
3587
|
|
|
3103
|
-
|
|
3588
|
+
# Issue #1362 — a body that ends on a builtin global still on its declared seed, or on a local copied from
|
|
3589
|
+
# one, is judged by the file's writes to the global, as for an argument ({#single_argument_mismatch}).
|
|
3590
|
+
inferred = DeclarationSourcedGuard.written_type(last_expr, inner_scope) || inner_scope.type_of(last_expr)
|
|
3104
3591
|
return nil if dynamic_top?(inferred)
|
|
3105
3592
|
|
|
3106
3593
|
severity = compare_return(declared, inferred)
|
|
3107
3594
|
return nil if severity.nil?
|
|
3595
|
+
return nil if declared_only_return?(last_expr, inferred, declared, inner_scope)
|
|
3108
3596
|
|
|
3109
3597
|
build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
3110
3598
|
end
|
|
3111
3599
|
|
|
3600
|
+
# Issue #1362 — a body that ends on a value mixing a builtin global's seed with something else is judged
|
|
3601
|
+
# without the declared members the file never writes, as for an argument.
|
|
3602
|
+
def declared_only_return?(last_expr, inferred, declared, scope)
|
|
3603
|
+
return false unless DeclarationSourcedGuard.global_sources(last_expr, scope).empty?
|
|
3604
|
+
|
|
3605
|
+
DeclarationSourcedGuard.declared_only_rejection?(inferred, declared, scope) do |stripped|
|
|
3606
|
+
compare_return(declared, stripped).nil?
|
|
3607
|
+
end
|
|
3608
|
+
end
|
|
3609
|
+
|
|
3112
3610
|
# The body of a `def` is the last `Prism::StatementsNode` child (or a single expression for
|
|
3113
3611
|
# one-liner defs). Take the last statement; that's the implicit return.
|
|
3114
3612
|
def body_last_expression(body)
|
|
@@ -3138,8 +3636,11 @@ module Rigor
|
|
|
3138
3636
|
self_type = scope.self_type
|
|
3139
3637
|
return nil unless self_type.respond_to?(:class_name)
|
|
3140
3638
|
|
|
3639
|
+
side = def_node.receiver.nil? ? receiverless_def_side(scope, self_type.class_name, def_node) : :singleton
|
|
3640
|
+
return nil if side.nil?
|
|
3641
|
+
|
|
3141
3642
|
method_def =
|
|
3142
|
-
if
|
|
3643
|
+
if side == :instance
|
|
3143
3644
|
Reflection.instance_method_definition(self_type.class_name, def_node.name, scope: scope)
|
|
3144
3645
|
else
|
|
3145
3646
|
Reflection.singleton_method_definition(self_type.class_name, def_node.name, scope: scope)
|
|
@@ -3153,14 +3654,26 @@ module Rigor
|
|
|
3153
3654
|
declared_return_union(method_def, scope.environment)
|
|
3154
3655
|
end
|
|
3155
3656
|
|
|
3156
|
-
#
|
|
3157
|
-
#
|
|
3158
|
-
#
|
|
3159
|
-
#
|
|
3160
|
-
#
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3657
|
+
# Which side a receiverless `def` defines, from the indexer's own record of THAT node: the instance table
|
|
3658
|
+
# holding it makes it an instance def, the singleton table (`class << self` bodies, and a `def` under
|
|
3659
|
+
# `Const.instance_eval` that the indexer attributes to the singleton) a singleton def. A node neither table
|
|
3660
|
+
# holds (a `def` in a block whose `self` the engine does not model, such as `E.class_eval do` inside
|
|
3661
|
+
# `class << self`, a redefinition a later `def` replaced) or both do is `nil`, and the caller declines: no
|
|
3662
|
+
# world is consulted, but the record cannot say which signature the body answers to.
|
|
3663
|
+
def receiverless_def_side(scope, class_name, def_node)
|
|
3664
|
+
instance = same_def_node?(scope.user_def_for(class_name, def_node.name), def_node)
|
|
3665
|
+
singleton = same_def_node?(scope.singleton_def_for(class_name, def_node.name), def_node)
|
|
3666
|
+
return :instance if instance && !singleton
|
|
3667
|
+
return :singleton if singleton && !instance
|
|
3668
|
+
|
|
3669
|
+
nil
|
|
3670
|
+
end
|
|
3671
|
+
|
|
3672
|
+
# The tables hold the live node or one re-resolved from a `DefHandle`, so identity is the name and span.
|
|
3673
|
+
def same_def_node?(recorded, def_node)
|
|
3674
|
+
recorded.is_a?(Prism::DefNode) && recorded.name == def_node.name &&
|
|
3675
|
+
recorded.location.start_offset == def_node.location.start_offset &&
|
|
3676
|
+
recorded.location.end_offset == def_node.location.end_offset
|
|
3164
3677
|
end
|
|
3165
3678
|
|
|
3166
3679
|
# `type_vars:` — ADR-35 WD9 tier 1. When the caller supplies a generic-instantiation
|
|
@@ -3268,82 +3781,57 @@ module Rigor
|
|
|
3268
3781
|
override_rank < parent_rank
|
|
3269
3782
|
end
|
|
3270
3783
|
|
|
3271
|
-
#
|
|
3272
|
-
#
|
|
3273
|
-
#
|
|
3274
|
-
#
|
|
3275
|
-
#
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3784
|
+
# The project ancestors whose definitions `class_name`'s own `def` OVERRIDES are the entries AFTER the
|
|
3785
|
+
# class on its {Scope::ResolutionChain}, nearest first; {#nearest_ancestor_visibility} and
|
|
3786
|
+
# {#nearest_ancestor_method_def} each read the first of them that answers their question through
|
|
3787
|
+
# `Inference::DefinerResolution` (ADR-119 C1c), `from:` the position after the class. Both are instance
|
|
3788
|
+
# side only: the singleton override rules do not exist, and the singleton side's own read lands with
|
|
3789
|
+
# ADR-119's singleton design.
|
|
3790
|
+
#
|
|
3791
|
+
# Issue #1568 — the walk this replaced was breadth-first from the class's direct ancestors, and those
|
|
3792
|
+
# include the modules the class PREPENDS. A prepended module sits BEFORE the class in Ruby's order: its
|
|
3793
|
+
# `def` overrides the class's, not the other way round, so `prepend P` with a public `P#foo` and a
|
|
3794
|
+
# private `C#foo` reported `C#foo` as reducing `P#foo`'s visibility. It also reached `Base` before a
|
|
3795
|
+
# module included through an included module (#1567's order).
|
|
3796
|
+
#
|
|
3797
|
+
# ADR-119 WD3 — for the two override lints *absent* always counts: a chain that does not settle (a mixin
|
|
3798
|
+
# whose position the tables cannot vouch for, a fork whose worlds disagree, a conditional or
|
|
3799
|
+
# block-scoped `include`), or an external ancestor ahead of the parent that RBS does not know or knows
|
|
3800
|
+
# and declares the name in, is `UNKNOWN`, and the lint stays silent rather than answer from the
|
|
3801
|
+
# breadth-first order the walk used before (`Scope::ResolutionChain::MasterOrder`) where the order is
|
|
3802
|
+
# not known. The two lints read only the `[owner, value]` of a `Known`.
|
|
3803
|
+
#
|
|
3804
|
+
# ADR-46 slice 3 — an ancestor the project does not declare ends the walk's reach into it, and a class
|
|
3805
|
+
# defined LATER under that name would change the answer; `DefinerResolution` files the negative class
|
|
3806
|
+
# edge of each external entry it tests.
|
|
3807
|
+
def overridden_ancestors_start(scope, class_name)
|
|
3808
|
+
own = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods).index_of(class_name.to_s)
|
|
3809
|
+
own && (own + 1)
|
|
3294
3810
|
end
|
|
3295
3811
|
|
|
3296
3812
|
# `[defining_class, visibility]` for the nearest user-source ancestor that defines an instance
|
|
3297
3813
|
# method `method_name`, or nil.
|
|
3298
3814
|
def nearest_ancestor_visibility(scope, class_name, method_name)
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
end
|
|
3319
|
-
raw_super = scope.superclass_of(class_name)
|
|
3320
|
-
if raw_super
|
|
3321
|
-
resolved_super = resolve_override_ancestor_name(scope, class_name, raw_super)
|
|
3322
|
-
names << resolved_super if resolved_super
|
|
3815
|
+
from = overridden_ancestors_start(scope, class_name)
|
|
3816
|
+
return nil if from.nil?
|
|
3817
|
+
|
|
3818
|
+
case Inference::DefinerResolution.resolve(scope, class_name, method_name, :instance,
|
|
3819
|
+
question: :override, from: from) do |ancestor|
|
|
3820
|
+
# Stop at the nearest ancestor that DEFINES the method; its visibility may be nil (unknown) —
|
|
3821
|
+
# the caller treats unknown as "cannot prove a reduction" and stays silent. Never fabricate
|
|
3822
|
+
# `:public` from a missing entry (that produced a large false-positive cluster on
|
|
3823
|
+
# cross-file Rails concerns). The `:unrecorded` marker stands for that nil in the answer.
|
|
3824
|
+
next unless scope.user_def_for(ancestor, method_name)
|
|
3825
|
+
|
|
3826
|
+
scope.discovered_method_visibility(ancestor, method_name) || :unrecorded
|
|
3827
|
+
end
|
|
3828
|
+
in Inference::DefinerResolution::Known(answer: [owner, visibility])
|
|
3829
|
+
[owner, visibility == :unrecorded ? nil : visibility]
|
|
3830
|
+
in Inference::DefinerResolution::UNKNOWN # the case/in contract needs one arm per answer
|
|
3831
|
+
nil
|
|
3832
|
+
in Inference::DefinerResolution::ABSENT # rubocop:disable Lint/DuplicateBranch -- see above
|
|
3833
|
+
nil
|
|
3323
3834
|
end
|
|
3324
|
-
names
|
|
3325
|
-
end
|
|
3326
|
-
|
|
3327
|
-
# Issue #732 — the predicate is asked of the SCOPE, not re-implemented here. This rule kept its own
|
|
3328
|
-
# three-table copy until #723 widened the scope's with `discovered_methods` and left this one behind,
|
|
3329
|
-
# so the two disagreed about exactly one shape (a project class with no instance def, no superclass
|
|
3330
|
-
# and no include — `class Base; def self.build = :built; end`) with nothing observing the fork. #682
|
|
3331
|
-
# made `Scope` the single owner of the candidate ORDER; this makes it the single owner of the
|
|
3332
|
-
# predicate that filters those candidates too.
|
|
3333
|
-
def resolve_override_ancestor_name(scope, subclass_qualified, raw_ancestor)
|
|
3334
|
-
resolved = scope.ancestor_name_candidates(subclass_qualified, raw_ancestor)
|
|
3335
|
-
.find { |candidate| scope.known_user_class?(candidate) }
|
|
3336
|
-
return resolved if resolved
|
|
3337
|
-
|
|
3338
|
-
# ADR-46 slice 3 — the override checker reads the class graph
|
|
3339
|
-
# directly (not through the recorder's `Scope` choke points), and
|
|
3340
|
-
# short-circuits when the ancestor resolves to no project class, so
|
|
3341
|
-
# an incremental re-check has no edge telling it to re-check this
|
|
3342
|
-
# subclass when that ancestor is later defined. Record a negative
|
|
3343
|
-
# class edge (keyed on the unqualified name) so the appeared-class
|
|
3344
|
-
# widening picks it up.
|
|
3345
|
-
DependencyRecorder.read_missing(:class, raw_ancestor.to_s.split("::").last) if DependencyRecorder.active?
|
|
3346
|
-
nil
|
|
3347
3835
|
end
|
|
3348
3836
|
|
|
3349
3837
|
def build_override_visibility_diagnostic(path, def_node, parent_class, parent_visibility, override_visibility)
|
|
@@ -3423,9 +3911,23 @@ module Rigor
|
|
|
3423
3911
|
# `[defining_class, RBS::Definition::Method]` for the nearest project-discovered ancestor whose
|
|
3424
3912
|
# RBS declares `method_name` (not the starting class's own declaration), or nil.
|
|
3425
3913
|
def nearest_ancestor_method_def(scope, class_name, method_name)
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3914
|
+
from = overridden_ancestors_start(scope, class_name)
|
|
3915
|
+
return nil if from.nil?
|
|
3916
|
+
|
|
3917
|
+
case Inference::DefinerResolution.resolve(scope, class_name, method_name, :instance,
|
|
3918
|
+
question: :override, from: from) do |ancestor|
|
|
3919
|
+
method_def = safe_instance_method_definition(ancestor, method_name, scope)
|
|
3920
|
+
# The signature text is the answer two worlds compare: an `RBS::Definition::Method` has no
|
|
3921
|
+
# structural equality of its own.
|
|
3922
|
+
method_def.method_types.map(&:to_s) if method_def && !defined_on?(method_def, class_name)
|
|
3923
|
+
end
|
|
3924
|
+
in Inference::DefinerResolution::Known(owner:)
|
|
3925
|
+
method_def = safe_instance_method_definition(owner, method_name, scope)
|
|
3926
|
+
method_def && [owner, method_def]
|
|
3927
|
+
in Inference::DefinerResolution::UNKNOWN # the case/in contract needs one arm per answer
|
|
3928
|
+
nil
|
|
3929
|
+
in Inference::DefinerResolution::ABSENT # rubocop:disable Lint/DuplicateBranch -- see above
|
|
3930
|
+
nil
|
|
3429
3931
|
end
|
|
3430
3932
|
end
|
|
3431
3933
|
|