rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +29 -10
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +92 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +48 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
|
@@ -42,8 +42,8 @@ module Rigor
|
|
|
42
42
|
# `IoBoundary`. The catalog is frozen after the first build and consulted by the `dynamic_return` rule
|
|
43
43
|
# at every gated call site. RBI files share the catalog with project-source sigs — both produce
|
|
44
44
|
# `MethodSignature` entries keyed by `(class_name, method_name, kind)`. When a key collides across
|
|
45
|
-
# files, the last-walked sig wins (
|
|
46
|
-
#
|
|
45
|
+
# files, the last-walked sig wins (`Dir.glob` results are sorted, so the walk order is stable).
|
|
46
|
+
# Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
|
|
47
47
|
# `sorbet/rbi/gems/` — lands in a later slice once the catalog gains per-source provenance.
|
|
48
48
|
#
|
|
49
49
|
# The plugin emits `plugin.sorbet.parse-error` warnings for malformed sig blocks (no block / empty
|
|
@@ -79,12 +79,10 @@ module Rigor
|
|
|
79
79
|
"rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
|
|
80
80
|
"enforce_sigil" => { kind: :boolean, default: true }
|
|
81
81
|
},
|
|
82
|
-
# #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig`
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# `T::Helpers` / `T::Generic`, and `rbs_complete_ancestors:` carries the Struct/Enum family
|
|
87
|
-
# for the subclass bridge.
|
|
82
|
+
# #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig` reaches the class object
|
|
83
|
+
# through `extend T::Sig` (or the `T::Struct` / `T::Enum` family, which carry the edge themselves):
|
|
84
|
+
# `rbs_complete_extends:` is that extend-edge allow-list, and `rbs_complete_ancestors:` carries the
|
|
85
|
+
# Struct/Enum family for the subclass bridge.
|
|
88
86
|
signature_paths: ["sig"],
|
|
89
87
|
rbs_complete_extends: %w[T::Sig T::Helpers T::Generic],
|
|
90
88
|
rbs_complete_ancestors: %w[T::Struct T::InexactStruct T::ImmutableStruct T::Enum],
|
|
@@ -122,8 +120,7 @@ module Rigor
|
|
|
122
120
|
@configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
|
|
123
121
|
@rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
|
|
124
122
|
# Schema default `true` — only files marked `# typed: true` / `:strict` / `:strong` contribute
|
|
125
|
-
# their sigs. Set to `false` to record every file's sigs regardless of sigil
|
|
126
|
-
# pre-this-config).
|
|
123
|
+
# their sigs. Set to `false` to record every file's sigs regardless of sigil.
|
|
127
124
|
@enforce_sigil = config.fetch("enforce_sigil")
|
|
128
125
|
# Per-call-site assertion gating: `@sigil_by_path` (built during catalog harvest) is consulted so
|
|
129
126
|
# `T.let` / `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only fire in files Sorbet itself
|
|
@@ -135,20 +132,20 @@ module Rigor
|
|
|
135
132
|
@parse_errors_by_path = {}
|
|
136
133
|
@catalog_built = false
|
|
137
134
|
# ADR-11 slice 6 — Prism nodes for `T.absurd` calls we observed in the `dynamic_return` rule to be
|
|
138
|
-
# *reachable* (i.e., their discriminant didn't narrow to `bot`).
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
135
|
+
# *reachable* (i.e., their discriminant didn't narrow to `bot`). The node rule below surfaces these
|
|
136
|
+
# as `plugin.sorbet.absurd-reachable` warnings. The Hash is keyed by node identity
|
|
137
|
+
# (`compare_by_identity`) because the runner only parses each file once per run, so identity is
|
|
138
|
+
# stable across the two plugin hooks.
|
|
142
139
|
@reachable_absurd_nodes = {}.compare_by_identity
|
|
143
140
|
# ADR-11 light follow-up — `T.reveal_type` calls observed in the `dynamic_return` rule, paired
|
|
144
141
|
# with the display string for the inferred type at the call site. Mirrors the absurd-node
|
|
145
|
-
# compare-by-identity hash;
|
|
142
|
+
# compare-by-identity hash; the node rule below surfaces each entry as a
|
|
146
143
|
# `plugin.sorbet.reveal-type` `:info` diagnostic.
|
|
147
144
|
@reveal_type_calls = {}.compare_by_identity
|
|
148
145
|
# T.bind / T.assert_type! priority slice 1 — `T.assert_type!` calls observed in the
|
|
149
146
|
# `dynamic_return` rule whose static subtype check FAILED, paired with the inferred + asserted
|
|
150
|
-
# type display strings. Same compare-by-identity discipline.
|
|
151
|
-
#
|
|
147
|
+
# type display strings. Same compare-by-identity discipline. The node rule below surfaces matching
|
|
148
|
+
# entries as
|
|
152
149
|
# `plugin.sorbet.assert-type-mismatch` `:error` diagnostics.
|
|
153
150
|
@assert_type_mismatches = {}.compare_by_identity
|
|
154
151
|
end
|
|
@@ -245,7 +242,7 @@ module Rigor
|
|
|
245
242
|
# analysis treats code after it as unreachable; the legacy contribution's `exceptional: :raises`
|
|
246
243
|
# slot was never consumed on the dispatcher path — only `return_type` survives the merge). When
|
|
247
244
|
# the discriminant *isn't* narrowed to `bot` at this scope, also records the call node so
|
|
248
|
-
#
|
|
245
|
+
# the node rule can surface a `plugin.sorbet.absurd-reachable` warning.
|
|
249
246
|
if AbsurdRecognizer.absurd_call?(call_node)
|
|
250
247
|
@reachable_absurd_nodes[call_node] = true unless AbsurdRecognizer.exhaustive?(call_node, scope)
|
|
251
248
|
return Rigor::Type::Combinator.bot
|
|
@@ -255,7 +252,7 @@ module Rigor
|
|
|
255
252
|
# cheaper to recognise (no catalog walk required) and they win over any cataloged signature: the
|
|
256
253
|
# user explicitly asserted the type at the call site. The light follow-up extends the recogniser
|
|
257
254
|
# to `T.must_because` (alias of `T.must`) and `T.reveal_type` (passes the type through; the
|
|
258
|
-
# human-facing diagnostic is recorded here for
|
|
255
|
+
# human-facing diagnostic is recorded here for the node rule to emit).
|
|
259
256
|
#
|
|
260
257
|
# Per-call-site sigil gating: with `enforce_sigil: true` (default), assertions only fire in files
|
|
261
258
|
# Sorbet itself would enforce. Files at `# typed: false` (or sigil-less, which Sorbet treats as
|
|
@@ -347,19 +344,96 @@ module Rigor
|
|
|
347
344
|
when Rigor::Type::Singleton
|
|
348
345
|
chain_lookup(self_type.class_name, method_name, anchor_kind: :singleton, mixin_kind: :extend)
|
|
349
346
|
when Rigor::Type::Nominal
|
|
350
|
-
chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
347
|
+
chain_lookup(self_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
|
|
348
|
+
struct_prop_lookup(self_type.class_name, method_name, scope)
|
|
351
349
|
end
|
|
352
350
|
rescue StandardError
|
|
353
351
|
nil
|
|
354
352
|
end
|
|
355
353
|
|
|
354
|
+
# Issue #2541 — after the sig catalog (the class and its mixins) declines an instance-side call, a
|
|
355
|
+
# `prop` / `const` accessor the class or a superclass declares under a `T::Struct`-family root.
|
|
356
|
+
# The accessor's type is translated here rather than at harvest: a relative constant in it resolves the
|
|
357
|
+
# way Ruby's lexical lookup would from the declaring class body — under each enclosing namespace,
|
|
358
|
+
# innermost first, then at the top level — against the classes the walk declared and the RBS
|
|
359
|
+
# environment. A constant that resolves nowhere reads `Dynamic[top]`, never a guessed class.
|
|
360
|
+
def struct_prop_lookup(class_name, method_name, scope)
|
|
361
|
+
signature = @catalog.lookup_struct_prop(class_name: class_name, method_name: method_name)
|
|
362
|
+
return signature if signature.nil?
|
|
363
|
+
|
|
364
|
+
source, nesting = @catalog.struct_prop_type(signature)
|
|
365
|
+
return signature if source.nil?
|
|
366
|
+
|
|
367
|
+
signature.with(return_type: struct_prop_type(source, nesting, signature.class_name, scope))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def struct_prop_type(source, nesting, class_name, scope)
|
|
371
|
+
@struct_prop_types ||= {}
|
|
372
|
+
@struct_prop_types[[source, nesting, class_name, scope.nil?]] ||= begin
|
|
373
|
+
node = Prism.parse(source).value.statements&.body&.first
|
|
374
|
+
resolve = ->(name) { resolve_struct_constant(name, nesting, class_name, scope) }
|
|
375
|
+
TypeTranslator.translate(node, resolve)
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Ruby's lookup for a relative constant in the class body: the lexical nesting, innermost first; then
|
|
380
|
+
# the class's ancestors; then the top level. The nesting step answers a class it finds, and declines
|
|
381
|
+
# (nil, so the constant reads `Dynamic[top]`) when it meets a constant that is not a declared class.
|
|
382
|
+
# Ancestry is not modelled precisely: a class or constant of that name under any ancestor declines.
|
|
383
|
+
# The top level answers only when the struct's ancestry is closed — no class on its walked superclass
|
|
384
|
+
# chain `include`s or `prepend`s anything, whose constants could shadow it — and no other walked
|
|
385
|
+
# `*::<name>` exists.
|
|
386
|
+
def resolve_struct_constant(name, nesting, class_name, scope)
|
|
387
|
+
environment = scope&.environment
|
|
388
|
+
return name.delete_prefix("::") if name.start_with?("::")
|
|
389
|
+
|
|
390
|
+
nesting.size.downto(1) do |depth|
|
|
391
|
+
candidate = "#{nesting.first(depth).join('::')}::#{name}"
|
|
392
|
+
return candidate if struct_constant_class?(candidate, environment)
|
|
393
|
+
return nil if struct_constant_bound?(candidate, scope)
|
|
394
|
+
end
|
|
395
|
+
@catalog.constant_ancestry(class_name).each do |ancestor|
|
|
396
|
+
candidate = "#{ancestor}::#{name}"
|
|
397
|
+
return nil if struct_constant_class?(candidate, environment) || struct_constant_bound?(candidate, scope)
|
|
398
|
+
end
|
|
399
|
+
return nil if struct_constant_elsewhere?(name)
|
|
400
|
+
return nil if @catalog.mixes_in_ancestry?(class_name)
|
|
401
|
+
|
|
402
|
+
struct_constant_class?(name, environment) ? name : nil
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def struct_constant_class?(name, environment)
|
|
406
|
+
@catalog.namespace?(name) || !environment&.nominal_for_name(name).nil?
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# A walked constant write, or any constant the engine types at the rooted path `::<name>` — an RBS
|
|
410
|
+
# constant declaration included. The probe goes through the plugin-facing `Scope#type_of` on a parsed
|
|
411
|
+
# constant-path node; a name nothing declares types as `Dynamic[top]` and is not bound.
|
|
412
|
+
def struct_constant_bound?(name, scope)
|
|
413
|
+
return true if @catalog.constant?(name)
|
|
414
|
+
return false if scope.nil?
|
|
415
|
+
|
|
416
|
+
node = Prism.parse("::#{name}").value.statements&.body&.first
|
|
417
|
+
type = scope.type_of(node)
|
|
418
|
+
!(type.nil? || type.is_a?(Rigor::Type::Dynamic))
|
|
419
|
+
rescue StandardError
|
|
420
|
+
true
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def struct_constant_elsewhere?(name)
|
|
424
|
+
@struct_constant_names ||= @catalog.declared_constant_names
|
|
425
|
+
suffix = "::#{name}"
|
|
426
|
+
@struct_constant_names.any? { |declared| declared.end_with?(suffix) }
|
|
427
|
+
end
|
|
428
|
+
|
|
356
429
|
def instance_chain_lookup(receiver_node, method_name, scope)
|
|
357
430
|
return nil if scope.nil?
|
|
358
431
|
|
|
359
432
|
receiver_type = scope.type_of(receiver_node)
|
|
360
433
|
return nil unless receiver_type.is_a?(Rigor::Type::Nominal)
|
|
361
434
|
|
|
362
|
-
chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
435
|
+
chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include) ||
|
|
436
|
+
struct_prop_lookup(receiver_type.class_name, method_name, scope)
|
|
363
437
|
rescue StandardError
|
|
364
438
|
# `scope.type_of` can raise on unrecognised synthetic nodes; degrade to "no contribution" rather
|
|
365
439
|
# than bubbling the failure into the dispatcher.
|
|
@@ -502,8 +576,7 @@ module Rigor
|
|
|
502
576
|
# ADR-88 WD2 — deterministic fold order matters now that the catalog VALUE is digested into the
|
|
503
577
|
# incremental fact-surface fingerprint: a duplicate `(class, method, kind)` sig's last-wins winner
|
|
504
578
|
# must not vary by machine / run. `Dir.glob` sorts its results by default on Ruby 3.0+, so the fold
|
|
505
|
-
# order is already stable
|
|
506
|
-
# this walk relies on it rather than re-sorting.
|
|
579
|
+
# order is already stable; this walk relies on it rather than re-sorting.
|
|
507
580
|
Dir.glob("**/*#{ext}", base: absolute).each do |relative|
|
|
508
581
|
harvest_file(canonicalize(File.join(absolute, relative)), catalog, sigil_by_path, parse_errors_by_path)
|
|
509
582
|
end
|
|
@@ -552,7 +625,7 @@ module Rigor
|
|
|
552
625
|
catalog
|
|
553
626
|
end
|
|
554
627
|
|
|
555
|
-
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
|
|
628
|
+
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path, struct_catalog: catalog)
|
|
556
629
|
# ADR-88 WD2 — store Marshal-clean `{kind:, line:, column:}` tuples, not the `ParseError` (it holds a
|
|
557
630
|
# live Prism node the producer bundle could not serialise). `diagnostics_for_file` rebuilds the
|
|
558
631
|
# diagnostic from the tuple, at the same 1-based line / `start_column + 1` position `from_node` gave.
|
|
@@ -11,9 +11,8 @@ module Rigor
|
|
|
11
11
|
#
|
|
12
12
|
# 1. **Collect pass.** Walk the file once to gather every state name declared inside a
|
|
13
13
|
# `state_machine do ... end` block (`state :draft`, `state :submitted`, ...).
|
|
14
|
-
# 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)`
|
|
15
|
-
#
|
|
16
|
-
# suggestions.
|
|
14
|
+
# 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)` reference against
|
|
15
|
+
# the collected state set. `DidYouMean::SpellChecker` drives the did-you-mean suggestions.
|
|
17
16
|
#
|
|
18
17
|
# Useful for `aasm` / `statesman` / hand-rolled DSLs and any framework where declarations and uses
|
|
19
18
|
# live in the same file. The same skeleton lifts to GraphQL types, ActiveModel validations, route
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
require "rigor/
|
|
5
|
+
require "rigor/reflection"
|
|
6
6
|
|
|
7
7
|
require_relative "serializer_index"
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Plugin
|
|
11
11
|
class Typelizer < Rigor::Plugin::Base
|
|
12
|
-
# A
|
|
13
|
-
# one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
|
|
12
|
+
# A collector over one file, built on {Rigor::Reflection.class_declarations_in}: the classes it declares, and
|
|
13
|
+
# whether each one's own body says `include Typelizer::DSL` / `extend Typelizer::DSL`.
|
|
14
14
|
#
|
|
15
15
|
# A `module` that includes the DSL is deliberately not recorded: typelizer registers the module's own
|
|
16
16
|
# name and `target_serializers` then calls `.descendants` on it, which a plain Module lacks. A class that
|
|
@@ -22,8 +22,6 @@ module Rigor
|
|
|
22
22
|
# `included do include Typelizer::DSL end` and a `def` that includes it credit nothing to the lexical
|
|
23
23
|
# class. A `class << self` body is a different node and is not entered either.
|
|
24
24
|
class SerializerCollector
|
|
25
|
-
include Rigor::Inference::DeclarationWalk::Collector
|
|
26
|
-
|
|
27
25
|
DSL_MODULE = "Typelizer::DSL"
|
|
28
26
|
DSL_CALLS = %i[include extend].freeze
|
|
29
27
|
|
|
@@ -33,17 +31,10 @@ module Rigor
|
|
|
33
31
|
@dsl = {}
|
|
34
32
|
end
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
unless name.empty?
|
|
41
|
-
superclass = superclass_name(node.superclass)
|
|
42
|
-
@entries[name] = superclass if superclass || !@entries.key?(name)
|
|
43
|
-
@nestings[name] = context.nesting || []
|
|
44
|
-
@dsl[name] = true if dsl_in?(node.body) && !body_context.singleton_cref
|
|
45
|
-
end
|
|
46
|
-
Rigor::Inference::DeclarationWalk::DESCEND
|
|
34
|
+
# Walks `root` (a parsed file's `Prism::Node`) and records what it finds; answers the collector itself.
|
|
35
|
+
def collect(root)
|
|
36
|
+
Rigor::Reflection.class_declarations_in(root).each { |declaration| record(declaration) }
|
|
37
|
+
self
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
# The classes this file declares, as {SerializerIndex::ClassEntry} values.
|
|
@@ -58,6 +49,15 @@ module Rigor
|
|
|
58
49
|
|
|
59
50
|
private
|
|
60
51
|
|
|
52
|
+
def record(declaration)
|
|
53
|
+
name = declaration.name
|
|
54
|
+
node = declaration.node
|
|
55
|
+
superclass = superclass_name(node.superclass)
|
|
56
|
+
@entries[name] = superclass if superclass || !@entries.key?(name)
|
|
57
|
+
@nestings[name] = declaration.nesting
|
|
58
|
+
@dsl[name] = true if dsl_in?(node.body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
61
|
# Whether a statement reachable without entering a def, block, lambda or nested declaration is a
|
|
62
62
|
# receiverless `include` / `extend` of the DSL module.
|
|
63
63
|
def dsl_in?(node)
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
|
-
require "rigor/inference/declaration_walk"
|
|
6
|
-
|
|
7
5
|
require_relative "serializer_collector"
|
|
8
6
|
require_relative "serializer_index"
|
|
9
7
|
|
|
10
8
|
module Rigor
|
|
11
9
|
module Plugin
|
|
12
10
|
class Typelizer < Rigor::Plugin::Base
|
|
13
|
-
# Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the
|
|
14
|
-
# {SerializerCollector} over each and merges the results into one
|
|
11
|
+
# Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the
|
|
12
|
+
# plugin's `IoBoundary`, runs a {SerializerCollector} over each and merges the results into one
|
|
13
|
+
# {SerializerIndex}.
|
|
15
14
|
class SerializerDiscoverer
|
|
16
15
|
def initialize(io_boundary:, dirs:, project_paths: [])
|
|
17
16
|
@io_boundary = io_boundary
|
|
@@ -29,10 +28,7 @@ module Rigor
|
|
|
29
28
|
parsed = Prism.parse(contents)
|
|
30
29
|
next unless parsed.success?
|
|
31
30
|
|
|
32
|
-
collector = SerializerCollector.new
|
|
33
|
-
Rigor::Inference::DeclarationWalk.run(
|
|
34
|
-
parsed.value, [collector], Rigor::Inference::DeclarationWalk::Context.root(nesting: [])
|
|
35
|
-
)
|
|
31
|
+
collector = SerializerCollector.new.collect(parsed.value)
|
|
36
32
|
in_dirs = dir_files.include?(path)
|
|
37
33
|
entries.concat(collector.class_entries.map { |entry| entry.with(in_dirs: in_dirs) })
|
|
38
34
|
end
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Plugin
|
|
5
5
|
class Typelizer < Rigor::Plugin::Base
|
|
6
|
-
# The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the
|
|
7
|
-
# object the `:serializer_index` producer caches.
|
|
6
|
+
# The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the
|
|
7
|
+
# {SerializerCollector} found them. A frozen value object the `:serializer_index` producer caches.
|
|
8
8
|
#
|
|
9
9
|
# {#roots} answers the one question, conservatively — a wrong "no" costs a missing root, a wrong "yes"
|
|
10
10
|
# hides a dead class.
|
|
11
11
|
class SerializerIndex
|
|
12
12
|
# A class the project declares. `superclass` is the constant as written (`nil` when absent) and
|
|
13
13
|
# `nesting` the `Module.nesting` chain (innermost first) its header was written under, which is where
|
|
14
|
-
# Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`;
|
|
15
|
-
# typelizer `dirs` declares it (only such a class is published as a root).
|
|
14
|
+
# Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`;
|
|
15
|
+
# `in_dirs` when a file under the typelizer `dirs` declares it (only such a class is published as a root).
|
|
16
16
|
ClassEntry = Data.define(:name, :superclass, :nesting, :dsl, :in_dirs)
|
|
17
17
|
|
|
18
18
|
def initialize(classes:)
|
|
@@ -66,9 +66,6 @@ module Rigor
|
|
|
66
66
|
|
|
67
67
|
# Always an Array of Bucket — `initialize` freezes what it is given and every caller hands it
|
|
68
68
|
# Bucket instances (parse_row rows, from_diagnostics groups, `without`'s reject, or `[]`).
|
|
69
|
-
# sig-gen gap: #2321 — `initialize` assigns `buckets.freeze`, an expression derived from the
|
|
70
|
-
# parameter rather than a bare read of it, so the reader is skipped (untyped return) and the element type
|
|
71
|
-
# is hand-written.
|
|
72
69
|
attr_reader buckets: Array[Bucket]
|
|
73
70
|
|
|
74
71
|
# `project_root` is `Pathname.new`'d, so a `Pathname` is accepted as well as a `String`
|
|
@@ -2,8 +2,8 @@ module Rigor
|
|
|
2
2
|
module Analysis
|
|
3
3
|
module CheckRules
|
|
4
4
|
def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
|
|
5
|
-
def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root) -> Hash[Symbol, untyped]
|
|
6
|
-
def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) ->
|
|
5
|
+
def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root, ?plugin_api_surface: bool) -> Hash[Symbol, untyped]
|
|
6
|
+
def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver
|
|
7
7
|
def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
|
|
8
8
|
def self?.filter_suppressed: (Array[Diagnostic] diagnostics, comments: untyped, disabled_rules: Array[String]) -> Array[Diagnostic]
|
|
9
9
|
end
|
|
@@ -3,7 +3,7 @@ module Rigor
|
|
|
3
3
|
module Reachability
|
|
4
4
|
class ScanCache
|
|
5
5
|
def self.open: (untyped, ?target_ruby: untyped) -> Rigor::Analysis::Reachability::ScanCache
|
|
6
|
-
def self.source_digest: () -> String
|
|
6
|
+
def self.source_digest: () -> ("unreadable" | String)
|
|
7
7
|
def initialize: (path: untyped, header: untyped) -> void
|
|
8
8
|
def save: () -> nil
|
|
9
9
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Rigor::Cache::Descriptor::PluginEntry
|
|
2
|
+
def initialize: (id: untyped, version: untyped, ?config_hash: untyped) -> void
|
|
3
|
+
def to_h: () -> Hash[String, untyped]
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class Rigor::Cache::Descriptor
|
|
7
|
+
def initialize: (?files: untyped, ?gems: untyped, ?plugins: untyped, ?configs: untyped, ?dependencies: untyped, ?globs: untyped) -> void
|
|
8
|
+
def self.compose: (*untyped) -> Rigor::Cache::Descriptor
|
|
9
|
+
def cache_key_for: (producer_id: untyped, ?params: untyped) -> String
|
|
10
|
+
def to_canonical_bytes: () -> String
|
|
11
|
+
def to_canonical_hash: () -> Hash[String, untyped]
|
|
12
|
+
def ==: (untyped) -> (false | true)
|
|
13
|
+
def hash: () -> Integer
|
|
14
|
+
end
|
|
@@ -33,7 +33,7 @@ module Rigor
|
|
|
33
33
|
def self.load_file: (untyped path) -> Registry
|
|
34
34
|
|
|
35
35
|
attr_reader vocabulary_version: Integer
|
|
36
|
-
# sig-gen gap: #
|
|
36
|
+
# sig-gen gap: #2325 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
|
|
37
37
|
# type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
|
|
38
38
|
# `map(&:to_s)`); every declared label, sorted. Implied ancestors are recognised by {#known?} but not listed
|
|
39
39
|
# here.
|
|
@@ -42,7 +42,7 @@ module Rigor
|
|
|
42
42
|
# Unmarked residue: `@roots` is computed from `@known` in `initialize`, not assigned from a
|
|
43
43
|
# parameter, so no parameter credit applies.
|
|
44
44
|
attr_reader roots: Array[String]
|
|
45
|
-
# sig-gen gap: #
|
|
45
|
+
# sig-gen gap: #2325 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
|
|
46
46
|
# a declared type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
|
|
47
47
|
# keys and texts `to_s`'d); one line per root, from the data file's `descriptions:`.
|
|
48
48
|
attr_reader descriptions: Hash[String, String]
|
data/sig/rigor/environment.rbs
CHANGED
|
@@ -19,7 +19,9 @@ module Rigor
|
|
|
19
19
|
def hkt_scan_failure: () -> [String, String, String?, Symbol]?
|
|
20
20
|
|
|
21
21
|
def self.default: () -> Environment
|
|
22
|
-
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?) -> Environment
|
|
22
|
+
def self.for_project: (?root: String, ?libraries: Array[String], ?signature_paths: Array[String | _ToPath]?, ?cache_store: untyped?, ?plugin_registry: untyped?, ?dependency_source_index: untyped?, ?rbs_extended_reporter: untyped?, ?boundary_cross_reporter: untyped?, ?source_rbs_synthesis_reporter: untyped?, ?bundler_bundle_path: String?, ?bundler_auto_detect: bool, ?synthetic_method_index: untyped?, ?project_patched_methods: untyped?, ?eager: bool) -> Environment
|
|
23
|
+
# authored: the return is the `signature_paths:` argument's type (authored intent) or the default ["sig"]; sig-gen types the `||` fallback untyped.
|
|
24
|
+
def self.project_signature_root_candidates: (Array[String | _ToPath]? signature_paths) -> Array[String | _ToPath]
|
|
23
25
|
# authored: the return is the `signature_paths:` argument's type (authored intent) or the default [Pathname]; sig-gen types the `||` fallback untyped.
|
|
24
26
|
def self.project_signature_roots: (Array[String | _ToPath]? signature_paths, ?root: String) -> Array[String | _ToPath]
|
|
25
27
|
|
|
@@ -36,6 +38,7 @@ module Rigor
|
|
|
36
38
|
def rbs_module?: (String | Symbol name) -> bool
|
|
37
39
|
def singleton_extended_modules: (String | Symbol name) -> Array[String]
|
|
38
40
|
def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
|
|
41
|
+
def prepare_for_typing!: () -> Environment
|
|
39
42
|
def reflection: () -> untyped?
|
|
40
43
|
def missing_rbs_gem_owner: (String | Symbol root_constant_name) -> String?
|
|
41
44
|
|
data/sig/rigor/inference.rbs
CHANGED
|
@@ -3,7 +3,8 @@ module Rigor
|
|
|
3
3
|
type closure_classification = :non_escaping | :escaping | :unknown
|
|
4
4
|
|
|
5
5
|
class ExpressionTyper
|
|
6
|
-
def self.harvest_return_memo: () -> Hash[untyped, Array[untyped]]
|
|
6
|
+
def self.harvest_return_memo: (?Hash[untyped, Hash[untyped, untyped]]? bucket) -> Hash[untyped, Array[untyped]]
|
|
7
|
+
def self.current_return_memo: () -> Hash[untyped, Hash[untyped, untyped]]?
|
|
7
8
|
def initialize: (scope: Scope, ?tracer: FallbackTracer?) -> void
|
|
8
9
|
def type_of: (untyped node) -> Type::t
|
|
9
10
|
def return_type_for: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
|
|
@@ -172,6 +173,8 @@ module Rigor
|
|
|
172
173
|
def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
|
|
173
174
|
def self?.constant_writes_for_file: (untyped root) -> Hash[String, untyped]
|
|
174
175
|
def self?.constant_literal_value: (untyped rvalue) -> Array[untyped]?
|
|
176
|
+
# authored: the `Set[String]` values are source paths by contract; a Set's element type stays `untyped` in sig-gen (#2177, #2317), so the generator
|
|
177
|
+
# would never propose it.
|
|
175
178
|
def self?.finalize_constant_writes: (Hash[String, Hash[String, untyped]] writes) -> [Hash[String, Type::Constant], Hash[String, Set[String]]]
|
|
176
179
|
def self?.propagate: (untyped node, Hash[untyped, Scope] table, Scope parent_scope) -> void
|
|
177
180
|
|
|
@@ -4,15 +4,10 @@ module Rigor
|
|
|
4
4
|
# the read-before-write nil soundness gate does not widen their ivars. See
|
|
5
5
|
# lib/rigor/plugin/additional_initializer.rb for the full contract.
|
|
6
6
|
class AdditionalInitializer
|
|
7
|
-
# sig-gen gap: #2321 — `attr_reader` rows over ivars `initialize` assigns from expressions derived from its
|
|
8
|
-
# parameters are skipped (untyped return); the constructor validates `receiver_constraint` is a non-empty String
|
|
9
|
-
# and normalizes both lists with `map(&:to_sym)`, so the stored types are hand-written.
|
|
10
7
|
attr_reader receiver_constraint: String
|
|
11
8
|
|
|
12
|
-
# sig-gen gap: #2321 — as above.
|
|
13
9
|
attr_reader methods: Array[Symbol]
|
|
14
10
|
|
|
15
|
-
# sig-gen gap: #2321 — as above.
|
|
16
11
|
attr_reader block_methods: Array[Symbol]
|
|
17
12
|
|
|
18
13
|
# The validators accept Symbol or non-empty String elements; `initialize` normalizes to
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -4,9 +4,20 @@
|
|
|
4
4
|
# declared surface and give plugin subclasses (once they enter a check
|
|
5
5
|
# target) method resolution for the DSL + hook surface.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# ADR-128 WD2 — every hook position takes a plugin-facing interface
|
|
8
|
+
# (`sig/rigor/plugin/interfaces.rbs`), a concrete Prism node class, a
|
|
9
|
+
# `Rigor::Type::t` or a core type, instead of `untyped`. A hook's `scope`
|
|
10
|
+
# is non-nil where every engine call site passes one: the per-file hooks
|
|
11
|
+
# receive the seeded `Scope.empty(...)`, `MethodDispatcher#try_plugin_contribution`
|
|
12
|
+
# returns before consulting a plugin when its scope is nil, and
|
|
13
|
+
# `StatementEvaluator#apply_plugin_assertions` has already read its own. The
|
|
14
|
+
# existing `scope.nil?` guards in plugin source stay valid either way.
|
|
15
|
+
#
|
|
16
|
+
# Collaborators that have no RBS yet (FlowContribution) stay `untyped`;
|
|
17
|
+
# the precision ceiling is the sparse `sig/` tree. A parameter row stays
|
|
18
|
+
# lenient even where its type IS declared — `cache_for`'s `?descriptor:` and
|
|
19
|
+
# `IoBoundary#replay` both take a `Cache::Descriptor` and both read `untyped`
|
|
20
|
+
# — by ADR-107 clause 2, not for want of a declaration.
|
|
10
21
|
class Rigor::Plugin::Base
|
|
11
22
|
# --- class-level declaration DSLs ---------------------------------
|
|
12
23
|
|
|
@@ -17,16 +28,32 @@ class Rigor::Plugin::Base
|
|
|
17
28
|
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped, ?generation_cap: untyped) { (untyped params) -> untyped } -> Symbol
|
|
18
29
|
def self.producers: () -> Hash[Symbol, untyped]
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
# One overload per node class the bundled plugins name, so the block's `node` is that class; `Prism::Node` is the
|
|
32
|
+
# fallback for any other (RBS cannot write `singleton(N)` over a type variable). The block runs `instance_exec`'d
|
|
33
|
+
# on the plugin: `file_context` is whatever the `node_file_context` block returned (plugin-defined), and a rule
|
|
34
|
+
# may answer one diagnostic, an array of them, or nil.
|
|
35
|
+
def self.node_rule: (singleton(::Prism::CallNode) node_type) { (::Prism::CallNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::CallNode)
|
|
36
|
+
| (singleton(::Prism::ClassNode) node_type) { (::Prism::ClassNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ClassNode)
|
|
37
|
+
| (singleton(::Prism::ProgramNode) node_type) { (::Prism::ProgramNode node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::ProgramNode)
|
|
38
|
+
| (singleton(::Prism::Node) node_type) { (::Prism::Node node, Rigor::Plugin::_Scope scope, String path, untyped file_context, Rigor::Plugin::NodeContext context) -> (Rigor::Analysis::Diagnostic | Array[Rigor::Analysis::Diagnostic] | nil) } -> singleton(::Prism::Node)
|
|
21
39
|
def self.node_rules: () -> Array[untyped]
|
|
22
40
|
|
|
23
|
-
def self.node_file_context: () { (
|
|
24
|
-
def self.node_file_context_block: () -> (^(
|
|
41
|
+
def self.node_file_context: () { (::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped } -> void
|
|
42
|
+
def self.node_file_context_block: () -> (^(::Prism::ProgramNode root, Rigor::Plugin::_Scope scope) -> untyped)?
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
# A rule with a static `methods:` Array only ever runs its block on a `CallNode`: the name gate
|
|
45
|
+
# (`Registry#build_name_gates`, `Base#dynamic_return_rule_applies?`) matches on the call's name, which
|
|
46
|
+
# none of the other nodes in the union below has, so the gate declines them (#2681, #2764). Every other
|
|
47
|
+
# declaration shape is offered the whole union. A callable `methods:` also only ever sees a `CallNode`
|
|
48
|
+
# at runtime, since the same gate declines the nameless nodes, but RBS cannot tell a callable from an
|
|
49
|
+
# Array argument by overload alone, so it keeps the union, which is sound.
|
|
50
|
+
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, methods: Array[untyped], ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
|
|
51
|
+
| (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(String path) -> Array[untyped])?) { (::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode call_node, Rigor::Plugin::_Scope scope) -> Rigor::Type::t? } -> nil
|
|
27
52
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
53
|
|
|
29
|
-
|
|
54
|
+
# The block answers the facts that hold after the call returns normally, or nil. The element is a
|
|
55
|
+
# `Rigor::FlowContribution::Fact`, which has no RBS declaration yet, so it stays `untyped`.
|
|
56
|
+
def self.narrowing_facts: (methods: Array[untyped]) { (::Prism::CallNode call_node, Rigor::Plugin::_Scope scope) -> Array[untyped]? } -> nil
|
|
30
57
|
# The engine-facing reader for the declared `narrowing_facts` rules. Named
|
|
31
58
|
# `type_specifiers` before ADR-80; that spelling was removed in 0.3.0.
|
|
32
59
|
def self.narrowing_facts_rules: () -> Array[untyped]
|
|
@@ -35,15 +62,16 @@ class Rigor::Plugin::Base
|
|
|
35
62
|
|
|
36
63
|
# --- instance surface --------------------------------------------
|
|
37
64
|
|
|
38
|
-
attr_reader services:
|
|
39
|
-
|
|
65
|
+
attr_reader services: Rigor::Plugin::_Services
|
|
66
|
+
# Schema-driven per plugin, so the values stay `untyped` (ADR-128 WD2).
|
|
67
|
+
attr_reader config: Hash[String, untyped]
|
|
40
68
|
|
|
41
|
-
def initialize: (services:
|
|
69
|
+
def initialize: (services: Rigor::Plugin::_Services, ?config: Hash[String, untyped]) -> void
|
|
42
70
|
|
|
43
71
|
# Overridable lifecycle / extension hooks (default no-ops).
|
|
44
|
-
def init: (
|
|
45
|
-
def prepare: (
|
|
46
|
-
def diagnostics_for_file: (path:
|
|
72
|
+
def init: (Rigor::Plugin::_Services services) -> nil
|
|
73
|
+
def prepare: (Rigor::Plugin::_Services services) -> nil
|
|
74
|
+
def diagnostics_for_file: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[Rigor::Analysis::Diagnostic]
|
|
47
75
|
# authored: hook-contract widening — the element type comes from a plugin gem sig-gen never
|
|
48
76
|
# sees, so no engine work would let it answer; the declared `Array[Rigor::Plugin::TemplateUnit]`
|
|
49
77
|
# over a bare-`[]` default body is the author's contract.
|
|
@@ -54,12 +82,19 @@ class Rigor::Plugin::Base
|
|
|
54
82
|
def declared_members: (untyped class_name) -> Array[untyped]
|
|
55
83
|
|
|
56
84
|
# Engine-executed dispatch over the declared DSLs.
|
|
57
|
-
def node_rule_diagnostics: (path:
|
|
85
|
+
def node_rule_diagnostics: (path: String, scope: Rigor::Plugin::_Scope, root: ::Prism::ProgramNode) -> Array[untyped]
|
|
58
86
|
# sig-gen gap: #1007 — the true `return result if result` branch (base.rb:594) flows through an
|
|
59
87
|
# `instance_exec`'d, plugin-supplied Proc that sig-gen's inference does not fold into the return
|
|
60
88
|
# join, so it proposes the literal fallback `nil` instead of the honest contract.
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
# `call_node` is a `CallNode` except where the engine dispatches a compound write or a block argument. The
|
|
90
|
+
# compound writes (`x.attr op= v`, `x.attr ||= v`, `x.attr &&= v`, and the `x[i]` forms) reach a rule through
|
|
91
|
+
# `ExpressionTyper#attribute_compound_read_type`, `#call_operator_write_type_for` and `#index_read_dispatch`;
|
|
92
|
+
# the `&:symbol` block arguments through `ExpressionTyper#symbol_block_return_type` and `#apply_hash_block`.
|
|
93
|
+
# A rule the name gate does not screen out (a `receivers:`-only rule, for one) is offered those nodes, so the
|
|
94
|
+
# `dynamic_return` block union above is the full set. A rule that reads a `CallNode`-only member (`receiver`,
|
|
95
|
+
# `name`, `arguments`) must guard with `call_node.is_a?(::Prism::CallNode)` first (#2717).
|
|
96
|
+
def dynamic_return_type: (call_node: ::Prism::CallNode | ::Prism::CallOperatorWriteNode | ::Prism::CallOrWriteNode | ::Prism::CallAndWriteNode | ::Prism::IndexOperatorWriteNode | ::Prism::IndexOrWriteNode | ::Prism::IndexAndWriteNode | ::Prism::BlockArgumentNode, scope: Rigor::Plugin::_Scope, receiver_type: Rigor::Type::t) -> Rigor::Type::t?
|
|
97
|
+
def narrowing_facts_for: (call_node: ::Prism::CallNode, scope: Rigor::Plugin::_Scope) -> Array[untyped]
|
|
63
98
|
|
|
64
99
|
# Authoring helpers.
|
|
65
100
|
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> Rigor::Analysis::Diagnostic
|
|
@@ -84,5 +119,5 @@ class Rigor::Plugin::Base
|
|
|
84
119
|
def effect_ancestry: () -> Array[Rigor::Plugin::EffectAncestry]
|
|
85
120
|
def io_boundary: () -> Rigor::Plugin::IoBoundary
|
|
86
121
|
def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> Proc
|
|
87
|
-
def plugin_entry: () ->
|
|
122
|
+
def plugin_entry: () -> Rigor::Cache::Descriptor::PluginEntry
|
|
88
123
|
end
|
|
@@ -19,7 +19,7 @@ module Rigor
|
|
|
19
19
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), and `initialize`
|
|
20
20
|
# stores the private `validate_receiver!` helper's result (#2312).
|
|
21
21
|
attr_reader receiver: String
|
|
22
|
-
# sig-gen gap: #
|
|
22
|
+
# sig-gen gap: #2325 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
|
|
23
23
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `String | Symbol`), stored as
|
|
24
24
|
# `method.to_sym`.
|
|
25
25
|
attr_reader method: Symbol
|
|
@@ -28,7 +28,7 @@ module Rigor
|
|
|
28
28
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `Array()` of anything
|
|
29
29
|
# answering `to_s`), stored from the private `normalize_labels` helper (#2312).
|
|
30
30
|
attr_reader labels: Array[String]
|
|
31
|
-
# sig-gen gap: #
|
|
31
|
+
# sig-gen gap: #2325 — `narrow:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
|
|
32
32
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
|
|
33
33
|
attr_reader narrow: String?
|
|
34
34
|
attr_reader discharge: bool
|
|
@@ -41,7 +41,7 @@ module Rigor
|
|
|
41
41
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), stored from the
|
|
42
42
|
# private `validate_taint!` helper (#2312).
|
|
43
43
|
attr_reader taint: String?
|
|
44
|
-
# sig-gen gap: #
|
|
44
|
+
# sig-gen gap: #2325 — `callee:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
|
|
45
45
|
# intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
|
|
46
46
|
attr_reader callee: String?
|
|
47
47
|
# sig-gen gap: #2312 — `callee_fallbacks:` is `untyped` (input `untyped`, looks deliberate; a declared type is
|