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
|
@@ -4,7 +4,9 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../source/constant_path"
|
|
6
6
|
require_relative "../source/node_children"
|
|
7
|
+
require_relative "ancestry_recorder"
|
|
7
8
|
require_relative "attribution"
|
|
9
|
+
require_relative "definition_context"
|
|
8
10
|
require_relative "envelope_index"
|
|
9
11
|
require_relative "file_collection"
|
|
10
12
|
require_relative "local_ownership"
|
|
@@ -13,6 +15,7 @@ require_relative "framework_units"
|
|
|
13
15
|
require_relative "plugin_facts"
|
|
14
16
|
require_relative "summary"
|
|
15
17
|
require_relative "unit_scan"
|
|
18
|
+
require_relative "visibility"
|
|
16
19
|
|
|
17
20
|
module Rigor
|
|
18
21
|
module Effects
|
|
@@ -21,8 +24,10 @@ module Rigor
|
|
|
21
24
|
# The scanner owns **identity** — which effect units the file defines and what each is keyed as — and
|
|
22
25
|
# delegates each unit's body to {UnitScan}. Keys follow the existing symbol tables (ADR-103 WD14):
|
|
23
26
|
# `Class#m` for an instance method, `Class.m` for a singleton one, and `<toplevel>#m` for a `def`
|
|
24
|
-
# outside any class body.
|
|
25
|
-
#
|
|
27
|
+
# outside any class body. The side is the one Ruby defines the method on, which the enclosing method
|
|
28
|
+
# does not decide: {DefinitionContext} carries it down the walk, and a definition it cannot place is
|
|
29
|
+
# no unit. Reopenings in one file join here; reopenings across files join when the runner merges the
|
|
30
|
+
# collections.
|
|
26
31
|
#
|
|
27
32
|
# Three kinds of unit exist beyond a plain `def`:
|
|
28
33
|
#
|
|
@@ -30,7 +35,9 @@ module Rigor
|
|
|
30
35
|
# skip it, so this is the minimal extension WD14 calls for, made here rather than in `ScopeIndexer`
|
|
31
36
|
# because nothing outside effects needs it yet.
|
|
32
37
|
# - `attr_reader` / `attr_writer` / `attr_accessor` — synthesised: a reader is ∅, a writer is
|
|
33
|
-
# `mutate.self
|
|
38
|
+
# `mutate.self` (`mutate.static` where `self` is the singleton class, since it writes the class
|
|
39
|
+
# object's ivar).
|
|
40
|
+
# Without them a caller's edge into an accessor would read as unresolved.
|
|
34
41
|
# - a nested `def` — its own unit under the same class, never contained in the enclosing method.
|
|
35
42
|
#
|
|
36
43
|
# **This walk exists only when collection is on.** ADR-103 WD13 prefers riding `ScopeIndexer`'s
|
|
@@ -47,77 +54,121 @@ module Rigor
|
|
|
47
54
|
# its own that no slice models yet.
|
|
48
55
|
DECLARATION_MACROS = %i[include prepend attr_reader attr_writer attr_accessor define_method].to_set.freeze
|
|
49
56
|
|
|
50
|
-
MUTATE_SELF = LabelSet.new(["mutate.self"])
|
|
51
|
-
private_constant :MUTATE_SELF
|
|
52
|
-
|
|
53
57
|
# A synthesised writer's summary is the same value at every `attr_accessor` in the project, so it is
|
|
54
|
-
# built once rather than per accessor.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
# built once rather than per accessor. A singleton writer sets an ivar on the class object, and is
|
|
59
|
+
# labelled as an ivar write in a body that runs on a class is.
|
|
60
|
+
WRITER_SUMMARY = Summary.new(bundles: { Origin.construct("attr-writer") => UnitScan::MUTATE_SELF })
|
|
61
|
+
SINGLETON_WRITER_SUMMARY = Summary.new(bundles: { Origin.construct("attr-writer") => UnitScan::MUTATE_STATIC })
|
|
62
|
+
private_constant :WRITER_SUMMARY, :SINGLETON_WRITER_SUMMARY
|
|
63
|
+
|
|
64
|
+
# Every argument is one collection input the scan reads; a context object would move the same list
|
|
65
|
+
# one call further out.
|
|
66
|
+
# rubocop:disable Metrics/ParameterLists
|
|
58
67
|
def self.scan(root:, path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
|
|
59
|
-
plugin_facts: PluginFacts.empty)
|
|
68
|
+
plugin_facts: PluginFacts.empty, unit_key: nil, unit_owner: nil)
|
|
60
69
|
new(path: path, calls: calls, attribution: attribution, envelopes: envelopes,
|
|
61
|
-
plugin_facts: plugin_facts).scan(root)
|
|
70
|
+
plugin_facts: plugin_facts, unit_key: unit_key, unit_owner: unit_owner).scan(root)
|
|
62
71
|
end
|
|
63
72
|
|
|
64
73
|
def initialize(path:, calls:, attribution: Attribution.empty, envelopes: EnvelopeIndex.empty,
|
|
65
|
-
plugin_facts: PluginFacts.empty)
|
|
74
|
+
plugin_facts: PluginFacts.empty, unit_key: nil, unit_owner: nil)
|
|
75
|
+
# rubocop:enable Metrics/ParameterLists
|
|
66
76
|
@path = path
|
|
77
|
+
# #392 — a template unit: the WHOLE file is one effect unit, keyed `view:<logical_name>`. A
|
|
78
|
+
# template has no `def` to key on and no owner class of its own, so the ordinary walk — which only
|
|
79
|
+
# ever mints a unit at a `def` — would report nothing for a file that calls into the project all
|
|
80
|
+
# the way down.
|
|
81
|
+
@unit_key = unit_key
|
|
82
|
+
# The declared `self` the unit's implicit-self calls resolve against — the plugin's `self_type:`.
|
|
83
|
+
# Without it a helper call in a template reads as an unresolved self-call and taints the unit.
|
|
84
|
+
@unit_owner = unit_owner
|
|
67
85
|
@calls = calls
|
|
68
86
|
@attribution = attribution
|
|
69
87
|
@envelopes = envelopes
|
|
70
88
|
@plugin_facts = plugin_facts
|
|
71
89
|
@summaries = {}
|
|
72
90
|
@edges = {}
|
|
73
|
-
@
|
|
74
|
-
|
|
91
|
+
@ancestry = AncestryRecorder.new
|
|
92
|
+
# #1048 — `{class name => Set[method name]}` for the `private` / `protected` members each class
|
|
93
|
+
# body declared. Read only by a UNIT callee rule, and only to decline.
|
|
94
|
+
@non_public = {}
|
|
75
95
|
# ADR-103 WD10 / #387 — the class-body facts the framework-edge strategies read, harvested only
|
|
76
96
|
# when a loaded plugin declared one. A run with no `effect_edges:` never allocates them.
|
|
77
97
|
@harvest = plugin_facts.edges? ? {} : nil
|
|
78
98
|
end
|
|
79
99
|
|
|
80
100
|
def scan(root)
|
|
81
|
-
|
|
101
|
+
return scan_template_unit(root) if @unit_key
|
|
102
|
+
|
|
103
|
+
walk(root, [], DefinitionContext::TOP_LEVEL)
|
|
82
104
|
synthesize_framework_units
|
|
83
105
|
FileCollection.new(
|
|
84
106
|
path: @path, summaries: @summaries, edges: @edges,
|
|
85
|
-
superclasses: @superclasses, includes: @includes
|
|
107
|
+
superclasses: @ancestry.superclasses, includes: @ancestry.includes
|
|
86
108
|
)
|
|
87
109
|
end
|
|
88
110
|
|
|
89
111
|
private
|
|
90
112
|
|
|
91
|
-
|
|
113
|
+
# The whole file body as one unit under {@unit_key}. Deliberately NOT combined with the ordinary
|
|
114
|
+
# walk: a compiled template is straight-line statements, a `def` inside one would be a method the
|
|
115
|
+
# render site cannot call, and a second keying rule over the same nodes would double-count every
|
|
116
|
+
# origin in the file.
|
|
117
|
+
def scan_template_unit(root)
|
|
118
|
+
summary, edges = UnitScan.new(
|
|
119
|
+
context: DefinitionContext::INSTANCE_METHOD_BODY, parameters: [], block_parameter: nil,
|
|
120
|
+
owned_locals: LocalOwnership.owned(root, [], singleton: false), calls: @calls,
|
|
121
|
+
attribution: @attribution, envelopes: @envelopes, plugin_facts: @plugin_facts,
|
|
122
|
+
owner_class: @unit_owner, method_name: @unit_key
|
|
123
|
+
).run(root)
|
|
124
|
+
merge_unit(@unit_key, summary, edges)
|
|
125
|
+
FileCollection.new(path: @path, summaries: @summaries, edges: @edges)
|
|
126
|
+
rescue StandardError
|
|
127
|
+
FileCollection.new(path: @path, summaries: { @unit_key => Summary.tainted("collector-error", @unit_key) })
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# `context` is the {DefinitionContext} of the class-body position `node` sits at. A `class <<` body
|
|
131
|
+
# and a block that rebinds `self` move it; a nested namespace starts afresh.
|
|
132
|
+
def walk(node, prefix, context)
|
|
92
133
|
return unless node.is_a?(Prism::Node)
|
|
93
134
|
|
|
94
135
|
case node
|
|
95
136
|
when Prism::ClassNode, Prism::ModuleNode
|
|
96
137
|
return walk_namespace(node, prefix)
|
|
97
|
-
when Prism::SingletonClassNode
|
|
98
|
-
return walk(node.body, prefix, true) if node.body
|
|
99
138
|
when Prism::DefNode
|
|
100
|
-
return enter_def(node, prefix,
|
|
139
|
+
return enter_def(node, prefix, context)
|
|
140
|
+
when Prism::ConstantWriteNode, Prism::ConstantPathWriteNode
|
|
141
|
+
@ancestry.record_constant_class(node, prefix)
|
|
142
|
+
when Prism::AliasMethodNode
|
|
143
|
+
return record_initialize_alias(prefix) if @ancestry.alias_to_initialize?(node, context)
|
|
101
144
|
when Prism::CallNode
|
|
102
145
|
harvest_class_body_macro(node, prefix)
|
|
103
|
-
return
|
|
146
|
+
return record_initialize_alias(prefix) if @ancestry.alias_to_initialize?(node, context)
|
|
147
|
+
return record_declaration(node, prefix, context) if declaration?(node)
|
|
104
148
|
end
|
|
105
149
|
|
|
106
|
-
|
|
150
|
+
rebinds = DefinitionContext.rebinds?(node)
|
|
151
|
+
node.rigor_each_child { |child| walk(child, prefix, rebinds ? context.for_child(node, child) : context) }
|
|
107
152
|
end
|
|
108
153
|
|
|
109
154
|
def walk_namespace(node, prefix)
|
|
110
155
|
nested = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
111
|
-
return node.rigor_each_child { |child| walk(child, prefix,
|
|
156
|
+
return node.rigor_each_child { |child| walk(child, prefix, DefinitionContext::CLASS_BODY) } if nested.nil?
|
|
157
|
+
|
|
158
|
+
@ancestry.record_superclass(nested.join("::"), node, prefix) if node.is_a?(Prism::ClassNode)
|
|
159
|
+
return if node.body.nil?
|
|
112
160
|
|
|
113
|
-
|
|
114
|
-
walk(node.body, nested,
|
|
161
|
+
@non_public[nested.join("::")] = Visibility.non_public_names(node.body)
|
|
162
|
+
walk(node.body, nested, DefinitionContext::CLASS_BODY)
|
|
115
163
|
end
|
|
116
164
|
|
|
117
|
-
def enter_def(node, prefix,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
165
|
+
def enter_def(node, prefix, context)
|
|
166
|
+
body_context = context.def_body(node)
|
|
167
|
+
return if body_context.nil?
|
|
168
|
+
|
|
169
|
+
scan = add_unit(class_name_for(prefix), node.name.to_s, body_context, node.body, node.parameters,
|
|
170
|
+
non_public: non_public?(prefix, node.name.to_s))
|
|
171
|
+
harvest_def(prefix, node.name.to_s, body_context.singleton?, scan) if @harvest && !prefix.empty?
|
|
121
172
|
end
|
|
122
173
|
|
|
123
174
|
# What the framework strategies need to know about a `def` the class body spelled out itself: that it
|
|
@@ -162,6 +213,10 @@ module Rigor
|
|
|
162
213
|
end || false
|
|
163
214
|
end
|
|
164
215
|
|
|
216
|
+
def non_public?(prefix, name)
|
|
217
|
+
prefix.empty? ? false : @non_public[class_name_for(prefix)]&.include?(name) || false
|
|
218
|
+
end
|
|
219
|
+
|
|
165
220
|
def harvest_for(prefix)
|
|
166
221
|
@harvest[class_name_for(prefix)] ||= { defs: [], units: {}, macros: {}, uniqueness: false }
|
|
167
222
|
end
|
|
@@ -184,21 +239,28 @@ module Rigor
|
|
|
184
239
|
# per unit (ADR-103 WD13): a unit the scanner cannot finish is recorded as non-exhaustive with
|
|
185
240
|
# `collector-error` and its siblings are unaffected.
|
|
186
241
|
#
|
|
242
|
+
# `context` is the {DefinitionContext} the body runs under, and the unit is keyed singleton exactly
|
|
243
|
+
# when that body runs on a class. The enclosing unit's own bit does not decide where the units nested
|
|
244
|
+
# in it land: `def self.outer` is scanned as a singleton method, and a `def inner` inside it is
|
|
245
|
+
# `Class#inner`.
|
|
246
|
+
#
|
|
187
247
|
# @return the finished scan, or nil when the unit failed soft
|
|
188
|
-
def add_unit(class_name, method_name,
|
|
189
|
-
key = "#{class_name}#{singleton ? '.' : '#'}#{method_name}"
|
|
248
|
+
def add_unit(class_name, method_name, context, body, parameters, non_public: false, shared_slot: false)
|
|
249
|
+
key = "#{class_name}#{context.singleton? ? '.' : '#'}#{method_name}"
|
|
190
250
|
names = parameter_names(parameters)
|
|
191
251
|
scan = UnitScan.new(
|
|
192
|
-
|
|
252
|
+
context: context, parameters: names,
|
|
193
253
|
block_parameter: block_parameter_name(parameters),
|
|
194
|
-
owned_locals: LocalOwnership.owned(body, names), calls: @calls,
|
|
254
|
+
owned_locals: LocalOwnership.owned(body, names, singleton: context.singleton?), calls: @calls,
|
|
195
255
|
attribution: @attribution, envelopes: @envelopes, plugin_facts: @plugin_facts,
|
|
196
|
-
owner_class: class_name, method_name: method_name
|
|
256
|
+
owner_class: class_name, method_name: method_name, non_public: non_public, shared_slot: shared_slot
|
|
197
257
|
)
|
|
198
258
|
summary, edges = scan.run(body)
|
|
199
259
|
merge_unit(key, summary, edges)
|
|
200
|
-
scan.nested.each do |name,
|
|
201
|
-
|
|
260
|
+
scan.nested.each do |name, nested_context, nested_body, nested_parameters, nested_shared_slot|
|
|
261
|
+
# A `def` inside a method is never an action, whatever the enclosing body's visibility.
|
|
262
|
+
add_unit(class_name, name, nested_context, nested_body, nested_parameters,
|
|
263
|
+
non_public: true, shared_slot: nested_shared_slot)
|
|
202
264
|
end
|
|
203
265
|
scan
|
|
204
266
|
rescue StandardError
|
|
@@ -213,51 +275,42 @@ module Rigor
|
|
|
213
275
|
node.receiver.nil? && DECLARATION_MACROS.include?(node.name)
|
|
214
276
|
end
|
|
215
277
|
|
|
216
|
-
def record_declaration(node, prefix)
|
|
278
|
+
def record_declaration(node, prefix, context)
|
|
217
279
|
class_name = class_name_for(prefix)
|
|
218
280
|
case node.name
|
|
219
|
-
when :include, :prepend then record_includes(class_name, node, prefix)
|
|
220
|
-
when :define_method then declare_define_method(class_name, node)
|
|
221
|
-
else synthesize_accessors(class_name, node)
|
|
281
|
+
when :include, :prepend then @ancestry.record_includes(class_name, node, prefix, context)
|
|
282
|
+
when :define_method then declare_define_method(class_name, node, context)
|
|
283
|
+
else synthesize_accessors(class_name, node, context.module_call_body)
|
|
222
284
|
end
|
|
223
285
|
end
|
|
224
286
|
|
|
225
|
-
def declare_define_method(class_name, node)
|
|
226
|
-
|
|
227
|
-
|
|
287
|
+
def declare_define_method(class_name, node, context)
|
|
288
|
+
name, body, parameters = UnitScan.define_method_unit(node)
|
|
289
|
+
body_context = context.module_call_body
|
|
290
|
+
return if name.nil? || body_context.nil?
|
|
228
291
|
|
|
229
|
-
|
|
230
|
-
add_unit(class_name, name,
|
|
292
|
+
# The block runs on the special-variable slot of the body that calls `define_method`, shared by every sibling.
|
|
293
|
+
add_unit(class_name, name, body_context, body, parameters, shared_slot: true)
|
|
231
294
|
end
|
|
232
295
|
|
|
233
|
-
|
|
296
|
+
# `attr_*` is a call on `self`, as `define_method` is, so it defines on the side `define_method` would:
|
|
297
|
+
# the class's own accessors inside `class << self`, and none where no key names the owner.
|
|
298
|
+
def synthesize_accessors(class_name, node, body_context)
|
|
299
|
+
return if body_context.nil?
|
|
300
|
+
|
|
301
|
+
singleton = body_context.singleton?
|
|
302
|
+
separator = singleton ? "." : "#"
|
|
234
303
|
symbol_arguments(node).each do |name|
|
|
235
|
-
merge_unit("#{class_name}
|
|
304
|
+
merge_unit("#{class_name}#{separator}#{name}", Summary.empty, []) unless node.name == :attr_writer
|
|
236
305
|
next if node.name == :attr_reader
|
|
237
306
|
|
|
238
|
-
merge_unit("#{class_name}
|
|
307
|
+
merge_unit("#{class_name}#{separator}#{name}=", singleton ? SINGLETON_WRITER_SUMMARY : WRITER_SUMMARY, [])
|
|
239
308
|
end
|
|
240
309
|
end
|
|
241
310
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def record_superclass(full_name, node, prefix)
|
|
248
|
-
superclass = node.superclass && Source::ConstantPath.qualified_name(node.superclass)
|
|
249
|
-
@superclasses[full_name] = lexical_candidates(superclass, prefix) if superclass
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# An ancestry name is recorded AS WRITTEN — `class Loud < Base` inside `module Tracer` names
|
|
253
|
-
# `Base`, not `Tracer::Base` — and a single file cannot say which constant that resolves to. So the
|
|
254
|
-
# scanner records the candidates Ruby's own lexical lookup would try, most-qualified first, and the
|
|
255
|
-
# propagator picks the one the merged project actually defines. Same shape as `ScopeIndexer`'s
|
|
256
|
-
# as-written superclass table, resolved at the same point: when the whole project is in view.
|
|
257
|
-
def lexical_candidates(name, prefix)
|
|
258
|
-
return [name] if prefix.empty? || name.start_with?("#{prefix.join('::')}::")
|
|
259
|
-
|
|
260
|
-
prefix.length.downto(1).map { |depth| "#{prefix.first(depth).join('::')}::#{name}" } + [name]
|
|
311
|
+
# The opaque-ancestry recordings the walk routes here; {AncestryRecorder} owns what they mean.
|
|
312
|
+
def record_initialize_alias(prefix)
|
|
313
|
+
@ancestry.record_initialize_alias(class_name_for(prefix))
|
|
261
314
|
end
|
|
262
315
|
|
|
263
316
|
def merge_unit(key, summary, edges)
|
|
@@ -274,10 +327,6 @@ module Rigor
|
|
|
274
327
|
[]
|
|
275
328
|
end
|
|
276
329
|
|
|
277
|
-
def constant_arguments(node)
|
|
278
|
-
node.arguments&.arguments&.filter_map { |argument| Source::ConstantPath.qualified_name(argument) } || []
|
|
279
|
-
end
|
|
280
|
-
|
|
281
330
|
def parameter_names(parameters)
|
|
282
331
|
return Set.new unless parameters.is_a?(Prism::ParametersNode)
|
|
283
332
|
|
|
@@ -35,7 +35,7 @@ module Rigor
|
|
|
35
35
|
ANNOTATION_BRACKETS.map do |opener, closer|
|
|
36
36
|
/%a#{Regexp.escape(opener)}\s*(?:pure\s*#{Regexp.escape(closer)}|rigor:v1:effect\b)/
|
|
37
37
|
end
|
|
38
|
-
)
|
|
38
|
+
).freeze
|
|
39
39
|
|
|
40
40
|
# A `virtual:<plugin-id>:<source path>` buffer is rbs-inline's (or a plugin's) synthesized RBS for
|
|
41
41
|
# a Ruby file the author actually wrote in. Naming that file is what a reader can act on, so the
|
|
@@ -306,7 +306,8 @@ module Rigor
|
|
|
306
306
|
)
|
|
307
307
|
end
|
|
308
308
|
|
|
309
|
-
# A synthesised default summary — an `attr_*` writer's `mutate.self
|
|
309
|
+
# A synthesised default summary — an `attr_*` writer's `mutate.self` (`mutate.static` for a
|
|
310
|
+
# singleton one), and the `Struct` / `Data`
|
|
310
311
|
# accessors that join it when discovery synthesises them — carries no information a reviewer acts
|
|
311
312
|
# on: it restates the `attr_accessor` line. Recognised by its origins being exactly the synthesised
|
|
312
313
|
# constructs, so a hand-written `def name=` keeps its row.
|
|
@@ -20,6 +20,8 @@ module Rigor
|
|
|
20
20
|
# from {#bundles} because nothing declared is ever proven: diagnostics read {#proven} only, so an
|
|
21
21
|
# attributed label can never manufacture an `effect.envelope-exceeded`.
|
|
22
22
|
# - {#exhaustive} — false when some call this method makes could not be resolved.
|
|
23
|
+
# - {#unclaimed} — true when some call it makes was described by nothing at all. A separate question
|
|
24
|
+
# from exhaustiveness, with a single reader; see {#unclaimed?}.
|
|
23
25
|
# - {#causes} — why, from {TaintCause}'s closed enum, as `[cause, detail]` pairs. Empty when exhaustive.
|
|
24
26
|
#
|
|
25
27
|
# {#proven} is the flat projection of {#bundles}: the join of every bundle. It is computed once at
|
|
@@ -53,10 +55,12 @@ module Rigor
|
|
|
53
55
|
|
|
54
56
|
attr_reader :bundles, :declared_bundles, :declared, :causes, :proven
|
|
55
57
|
|
|
56
|
-
def initialize(bundles: NO_BUNDLES, declared_bundles: NO_BUNDLES, exhaustive: true, causes: NO_CAUSES
|
|
58
|
+
def initialize(bundles: NO_BUNDLES, declared_bundles: NO_BUNDLES, exhaustive: true, causes: NO_CAUSES,
|
|
59
|
+
unclaimed: false)
|
|
57
60
|
@bundles = normalize_bundles(bundles)
|
|
58
61
|
@declared_bundles = normalize_bundles(declared_bundles)
|
|
59
62
|
@exhaustive = exhaustive ? true : false
|
|
63
|
+
@unclaimed = unclaimed ? true : false
|
|
60
64
|
@causes = normalize_causes(causes)
|
|
61
65
|
@proven = flatten(@bundles)
|
|
62
66
|
@declared = flatten(@declared_bundles)
|
|
@@ -69,6 +73,24 @@ module Rigor
|
|
|
69
73
|
@exhaustive
|
|
70
74
|
end
|
|
71
75
|
|
|
76
|
+
# Whether this method made a call NOTHING described — no catalogue row, no plugin row, no imported
|
|
77
|
+
# envelope, and no project definition for the edge to land on (#391).
|
|
78
|
+
#
|
|
79
|
+
# It is deliberately **not** a taint. "Every call was resolved" and "every callee's footprint is
|
|
80
|
+
# known" are different questions, and the model answers the first one everywhere: an unresolved
|
|
81
|
+
# ordinary edge is dropped rather than tainted, because most such calls are inherited or gem calls
|
|
82
|
+
# the catalogue has no row for, and tainting them would make almost every method in almost every
|
|
83
|
+
# project non-exhaustive. That posture is right for a report and for a judgment, both of which read
|
|
84
|
+
# the proven lane.
|
|
85
|
+
#
|
|
86
|
+
# It is wrong for EMISSION. `%a{pure}` written onto a method that calls an undescribed gem method
|
|
87
|
+
# is a contract nothing re-derives, enforced on that method's callers from then on. So the second
|
|
88
|
+
# question gets its own bit, read by `rigor sig-gen` and by nothing else: not by the report, not by
|
|
89
|
+
# the snapshot, not by `effect.envelope-exceeded`, and not by `trivial?`.
|
|
90
|
+
def unclaimed?
|
|
91
|
+
@unclaimed
|
|
92
|
+
end
|
|
93
|
+
|
|
72
94
|
# Whether this summary is worth showing at all: an exhaustive method whose whole proven footprint is
|
|
73
95
|
# frame-local mutation and which claims nothing. `mutate.local` is tolerated by every envelope
|
|
74
96
|
# (`%a{pure}` included), so a method that only mutates what its own frame allocated reads as pure
|
|
@@ -93,18 +115,19 @@ module Rigor
|
|
|
93
115
|
bundles: merge_bundles(@bundles, other.bundles),
|
|
94
116
|
declared_bundles: merge_bundles(@declared_bundles, other.declared_bundles),
|
|
95
117
|
exhaustive: @exhaustive && other.exhaustive?,
|
|
96
|
-
causes: @causes + other.causes
|
|
118
|
+
causes: @causes + other.causes,
|
|
119
|
+
unclaimed: @unclaimed || other.unclaimed?
|
|
97
120
|
)
|
|
98
121
|
end
|
|
99
122
|
|
|
100
123
|
def ==(other)
|
|
101
124
|
other.is_a?(Summary) && other.bundles == @bundles && other.declared_bundles == @declared_bundles &&
|
|
102
|
-
other.exhaustive? == @exhaustive && other.causes == @causes
|
|
125
|
+
other.exhaustive? == @exhaustive && other.causes == @causes && other.unclaimed? == @unclaimed
|
|
103
126
|
end
|
|
104
127
|
alias eql? ==
|
|
105
128
|
|
|
106
129
|
def hash
|
|
107
|
-
[self.class, @bundles, @declared_bundles, @exhaustive, @causes].hash
|
|
130
|
+
[self.class, @bundles, @declared_bundles, @exhaustive, @causes, @unclaimed].hash
|
|
108
131
|
end
|
|
109
132
|
|
|
110
133
|
def inspect
|