rigortype 0.3.9 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/manual/02-cli-reference.md +63 -2
- data/docs/manual/03-configuration.md +7 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +520 -49
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_session.rb +36 -4
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +1 -0
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +20 -11
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +167 -11
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +107 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +26 -7
- data/lib/rigor/analysis/runner.rb +278 -20
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +53 -10
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +6 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/incremental_snapshot.rb +33 -1
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +0 -1
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/configuration/severity_profile.rb +19 -3
- data/lib/rigor/configuration.rb +66 -3
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +43 -31
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/lockfile_resolver.rb +17 -0
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_loader.rb +399 -133
- data/lib/rigor/environment.rb +103 -38
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +73 -27
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +387 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1532 -496
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +167 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +881 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/multi_target_binder.rb +340 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +540 -120
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +3 -2
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +4615 -551
- data/lib/rigor/inference/statement_evaluator.rb +3176 -490
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +168 -5
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +3 -1
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +1 -2
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +19 -6
- data/lib/rigor/reflection.rb +72 -105
- data/lib/rigor/scope/discovery_index.rb +135 -2
- data/lib/rigor/scope.rb +859 -49
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +558 -31
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +88 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +12 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +1 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +135 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +9 -7
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +33 -8
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +108 -1
|
@@ -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
|
|
@@ -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,7 +214,7 @@ 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
219
|
shadow_verify_node_collectors(path, root, scope_index, collectors) if ENV["RIGOR_SHADOW_RULE_WALK"]
|
|
201
220
|
collectors
|
|
@@ -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
1602
|
|
|
1486
|
-
|
|
1487
|
-
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
1488
|
-
|
|
1489
|
-
method_def = trustworthy_signature(receiver_type, class_name, call_node, scope)
|
|
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
|
|
@@ -2503,7 +2903,7 @@ module Rigor
|
|
|
2503
2903
|
return nil if method_def.nil?
|
|
2504
2904
|
|
|
2505
2905
|
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)
|
|
2906
|
+
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
2507
2907
|
return nil if mismatch.nil?
|
|
2508
2908
|
return nil if inferred_param_mismatch_verdict?(call_node, mismatch, scope)
|
|
2509
2909
|
|
|
@@ -2529,12 +2929,15 @@ module Rigor
|
|
|
2529
2929
|
# overload rejects) plus, on non-coerce methods, the non-nil channel
|
|
2530
2930
|
# (a single-concrete-class argument every overload rejects). See
|
|
2531
2931
|
# {#multi_overload_argument_mismatch}.
|
|
2532
|
-
|
|
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)
|
|
2533
2936
|
mismatch =
|
|
2534
2937
|
if method_types.size == 1
|
|
2535
|
-
first_argument_mismatch(method_types.first, call_node, scope, param_overrides)
|
|
2938
|
+
first_argument_mismatch(method_types.first, call_node, scope, param_overrides, scope_index)
|
|
2536
2939
|
else
|
|
2537
|
-
multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides)
|
|
2940
|
+
multi_overload_argument_mismatch(method_types, call_node, scope, param_overrides, scope_index)
|
|
2538
2941
|
end
|
|
2539
2942
|
return nil if mismatch && keyword_bearing?(method_types) && nil_tainted?(mismatch[:actual])
|
|
2540
2943
|
|
|
@@ -2586,28 +2989,35 @@ module Rigor
|
|
|
2586
2989
|
# stays deferred), and decides acceptance on the RBS param type
|
|
2587
2990
|
# ({#param_accepts_arg_class?}) so it sees through the `int` / `string`
|
|
2588
2991
|
# interface-aliases the translator degrades.
|
|
2589
|
-
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)
|
|
2590
2993
|
functions = method_types.map(&:type)
|
|
2591
2994
|
return nil unless functions.all? { |function| argument_check_eligible?(function) }
|
|
2592
2995
|
|
|
2593
2996
|
coerce_method = COERCE_DISPATCH_METHODS.include?(call_node.name)
|
|
2594
2997
|
arguments = call_node.arguments&.arguments || []
|
|
2595
2998
|
arguments.each_with_index do |arg, index|
|
|
2596
|
-
arg_type = scope.type_of(arg)
|
|
2597
2999
|
params = checkable_overload_params(method_types, index, param_overrides, scope)
|
|
2598
3000
|
next if params.nil?
|
|
2599
3001
|
|
|
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
|
|
3002
|
+
mismatch = overload_argument_mismatch(arg, params, param_overrides,
|
|
3003
|
+
argument_scope(arg, scope, scope_index), coerce_method)
|
|
2606
3004
|
return mismatch if mismatch
|
|
2607
3005
|
end
|
|
2608
3006
|
nil
|
|
2609
3007
|
end
|
|
2610
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
|
+
|
|
2611
3021
|
# The parameter set at `index` that a verdict may rest on, or nil when there is none — the two
|
|
2612
3022
|
# reasons being arity divergence (an overload with no param there, which is `call.wrong-arity`'s
|
|
2613
3023
|
# concern) and an overload whose param names a class carrying no definition ({#stub_typed_param?},
|
|
@@ -2623,8 +3033,8 @@ module Rigor
|
|
|
2623
3033
|
|
|
2624
3034
|
# The nil channel: a pure `nil` argument no overload admits (ADR-58
|
|
2625
3035
|
# 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)
|
|
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)
|
|
2628
3038
|
return nil if params.any? { |param| param_admits_nil?(param, param_overrides, scope) }
|
|
2629
3039
|
|
|
2630
3040
|
{ node: arg, name: nil, expected: overload_param_expected_label(params), actual: arg_type }
|
|
@@ -2891,7 +3301,7 @@ module Rigor
|
|
|
2891
3301
|
DeclarationSourcedGuard.marked?(arg, scope)
|
|
2892
3302
|
end
|
|
2893
3303
|
|
|
2894
|
-
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)
|
|
2895
3305
|
function = method_type.type
|
|
2896
3306
|
return nil unless argument_check_eligible?(function)
|
|
2897
3307
|
|
|
@@ -2901,12 +3311,17 @@ module Rigor
|
|
|
2901
3311
|
param = params[index]
|
|
2902
3312
|
next if param.nil? # arity mismatch is the wrong-arity rule's concern.
|
|
2903
3313
|
|
|
2904
|
-
mismatch = single_argument_mismatch(param, arg, scope, param_overrides)
|
|
3314
|
+
mismatch = single_argument_mismatch(param, arg, argument_scope(arg, scope, scope_index), param_overrides)
|
|
2905
3315
|
return mismatch if mismatch
|
|
2906
3316
|
end
|
|
2907
3317
|
nil
|
|
2908
3318
|
end
|
|
2909
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
|
+
|
|
2910
3325
|
# The mismatch (or nil) for one positional argument against one
|
|
2911
3326
|
# parameter. The nil channel decides a pure `nil` argument on the RBS
|
|
2912
3327
|
# parameter type — seeing through the `string` / `int` interface-alias
|
|
@@ -2917,6 +3332,17 @@ module Rigor
|
|
|
2917
3332
|
def single_argument_mismatch(param, arg, scope, param_overrides)
|
|
2918
3333
|
return nil if stub_typed_param?(param, param_overrides, scope)
|
|
2919
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)
|
|
2920
3346
|
arg_type = scope.type_of(arg)
|
|
2921
3347
|
|
|
2922
3348
|
if nil_member?(arg_type)
|
|
@@ -2930,10 +3356,42 @@ module Rigor
|
|
|
2930
3356
|
return nil if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
2931
3357
|
return nil if arg_type.is_a?(Type::Dynamic) || arg_type.is_a?(Type::Top)
|
|
2932
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
|
|
2933
3364
|
|
|
2934
3365
|
{ node: arg, name: param.name, expected: param_type, actual: arg_type }
|
|
2935
3366
|
end
|
|
2936
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
|
+
|
|
2937
3395
|
# The parameter rejects the argument AND the rejection is not a
|
|
2938
3396
|
# withheld declaration-sourced-nil case.
|
|
2939
3397
|
def argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
@@ -3100,15 +3558,28 @@ module Rigor
|
|
|
3100
3558
|
declared = declared_return_type(def_node, scope_index)
|
|
3101
3559
|
return nil if declared.nil?
|
|
3102
3560
|
|
|
3103
|
-
|
|
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)
|
|
3104
3564
|
return nil if dynamic_top?(inferred)
|
|
3105
3565
|
|
|
3106
3566
|
severity = compare_return(declared, inferred)
|
|
3107
3567
|
return nil if severity.nil?
|
|
3568
|
+
return nil if declared_only_return?(last_expr, inferred, declared, inner_scope)
|
|
3108
3569
|
|
|
3109
3570
|
build_return_type_mismatch_diagnostic(path, def_node, declared, inferred, severity)
|
|
3110
3571
|
end
|
|
3111
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
|
+
|
|
3112
3583
|
# The body of a `def` is the last `Prism::StatementsNode` child (or a single expression for
|
|
3113
3584
|
# one-liner defs). Take the last statement; that's the implicit return.
|
|
3114
3585
|
def body_last_expression(body)
|