rigortype 0.3.8 → 0.3.9
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 +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
data/lib/rigor/scope.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Rigor
|
|
|
21
21
|
attr_reader :environment, :locals, :fact_store, :self_type,
|
|
22
22
|
:ivars, :cvars, :globals,
|
|
23
23
|
:indexed_narrowings, :method_chain_narrowings,
|
|
24
|
-
:declaration_sourced,
|
|
24
|
+
:declaration_sourced, :published_constant_sourced,
|
|
25
25
|
:source_path, :discovery, :struct_fold_safe_locals,
|
|
26
26
|
:opaque_block_self, :lexical_nesting,
|
|
27
27
|
:dynamic_origins, :local_origins, :ivar_origins,
|
|
@@ -49,12 +49,21 @@ module Rigor
|
|
|
49
49
|
def discovered_method_visibilities = @discovery.discovered_method_visibilities
|
|
50
50
|
def discovered_superclasses = @discovery.discovered_superclasses
|
|
51
51
|
def discovered_includes = @discovery.discovered_includes
|
|
52
|
+
def discovered_extends = @discovery.discovered_extends
|
|
52
53
|
def discovered_class_sources = @discovery.discovered_class_sources
|
|
53
54
|
# Issue #644 — `{qualified constant name => Set[declaring file]}`; seeded only on an ADR-46 recording run.
|
|
54
55
|
def constant_sources = @discovery.constant_sources
|
|
55
56
|
def published_constant_names = @discovery.published_constant_names
|
|
56
57
|
def local_constant_names = @discovery.local_constant_names
|
|
57
58
|
|
|
59
|
+
# Issue #667 — the instance-variable names of `class_name` whose class-ivar seed comes from a foreign
|
|
60
|
+
# published constant (`@mode = AppConfig::MODE` in `initialize`, read in a sibling method).
|
|
61
|
+
# `StatementEvaluator#seed_instance_ivars` stamps the flow mark for these at method-body entry, the way
|
|
62
|
+
# ADR-58's seed stamps its own; a method-local write or narrowing then drops it through `with_ivar`.
|
|
63
|
+
def published_constant_ivars_for(class_name)
|
|
64
|
+
@discovery.published_constant_ivars[class_name] || EMPTY_PUBLISHED_CONSTANT_IVARS
|
|
65
|
+
end
|
|
66
|
+
|
|
58
67
|
# Issue #644 — true when the constant reference `name` (as written: `MODE` or `AppConfig::MODE`) is
|
|
59
68
|
# published by the cross-file value-constant table and is NOT assigned by the file being analysed: a
|
|
60
69
|
# value this file's author cannot see the declaration of. {Analysis::CheckRules::PublishedConstantGuard}
|
|
@@ -76,14 +85,27 @@ module Rigor
|
|
|
76
85
|
# over already-qualified names rather than a second lexical ladder: the engine's ladder
|
|
77
86
|
# (`Reflection.resolve_constant_type`) answers with a TYPE and not with the candidate that won, and
|
|
78
87
|
# reimplementing the walk here is the very thing that made the receiver resolution wrong once already.
|
|
88
|
+
#
|
|
89
|
+
# Issue #667 — a third answer sits beside those two. `MODE2 = AppConfig::MODE` is assigned by this file,
|
|
90
|
+
# so the exemption above releases it, yet its value is still one the author never saw: the alias only
|
|
91
|
+
# renames the foreign declaration. `published_constant_alias_names` holds the last segments of exactly
|
|
92
|
+
# those aliases, and matching on the last segment over-answers in the WITHHOLDING direction, which is the
|
|
93
|
+
# safe one.
|
|
79
94
|
def published_constant?(name)
|
|
80
95
|
names = @discovery.published_constant_names
|
|
96
|
+
return true if published_constant_alias?(name)
|
|
81
97
|
return false if names.empty?
|
|
82
98
|
return false unless names.include?(name.split("::").last)
|
|
83
99
|
|
|
84
100
|
!locally_declared_constant?(name)
|
|
85
101
|
end
|
|
86
102
|
|
|
103
|
+
def published_constant_alias?(name)
|
|
104
|
+
aliases = @discovery.published_constant_alias_names
|
|
105
|
+
!aliases.empty? && aliases.include?(name.split("::").last)
|
|
106
|
+
end
|
|
107
|
+
private :published_constant_alias?
|
|
108
|
+
|
|
87
109
|
def locally_declared_constant?(reference_name)
|
|
88
110
|
local = @discovery.local_constant_names
|
|
89
111
|
return false if local.empty?
|
|
@@ -153,9 +175,21 @@ module Rigor
|
|
|
153
175
|
# threaded by reference through transitions; reset per method body (a fresh entry scope drops it), so the
|
|
154
176
|
# name keys never collide across bodies.
|
|
155
177
|
EMPTY_ORIGINS = {}.freeze
|
|
178
|
+
# Issue #667 — the set of variable references currently bound to a value COPIED out of a constant the
|
|
179
|
+
# project published and this file does not declare. Members are frozen `[kind, name]` pairs, the same
|
|
180
|
+
# spelling {EMPTY_DECLARATION_SOURCED} uses, but a DELIBERATELY SEPARATE carrier: this mark joins by
|
|
181
|
+
# UNION where ADR-58's kinds intersect, and it answers a different question (what a value's *constancy*
|
|
182
|
+
# rests on, not what a binding's *optionality* rests on) for a different consumer. Folding it into the
|
|
183
|
+
# ADR-58 set would be the third establishing transition its *Non-transitivity* passage reserves as a
|
|
184
|
+
# decision, on a Set that already carries two opposite join policies.
|
|
185
|
+
EMPTY_PUBLISHED_CONSTANT_SOURCED = Set.new.freeze
|
|
186
|
+
# Issue #667 — the empty answer of {#published_constant_ivars_for}, so a class with no such ivar (every
|
|
187
|
+
# class in a project that publishes nothing) allocates none.
|
|
188
|
+
EMPTY_PUBLISHED_CONSTANT_IVARS = Set.new.freeze
|
|
156
189
|
private_constant :EMPTY_VAR_BINDINGS, :EMPTY_INDEXED_NARROWINGS,
|
|
157
190
|
:EMPTY_CHAIN_NARROWINGS, :EMPTY_DECLARATION_SOURCED,
|
|
158
|
-
:EMPTY_FOLD_SAFE, :EMPTY_ORIGINS
|
|
191
|
+
:EMPTY_FOLD_SAFE, :EMPTY_ORIGINS, :EMPTY_PUBLISHED_CONSTANT_SOURCED,
|
|
192
|
+
:EMPTY_PUBLISHED_CONSTANT_IVARS
|
|
159
193
|
|
|
160
194
|
class << self
|
|
161
195
|
def empty(environment: Environment.default, source_path: nil)
|
|
@@ -207,6 +241,7 @@ module Rigor
|
|
|
207
241
|
indexed_narrowings: EMPTY_INDEXED_NARROWINGS,
|
|
208
242
|
method_chain_narrowings: EMPTY_CHAIN_NARROWINGS,
|
|
209
243
|
declaration_sourced: EMPTY_DECLARATION_SOURCED,
|
|
244
|
+
published_constant_sourced: EMPTY_PUBLISHED_CONSTANT_SOURCED,
|
|
210
245
|
source_path: nil,
|
|
211
246
|
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
212
247
|
opaque_block_self: false,
|
|
@@ -231,6 +266,7 @@ module Rigor
|
|
|
231
266
|
@indexed_narrowings = indexed_narrowings
|
|
232
267
|
@method_chain_narrowings = method_chain_narrowings
|
|
233
268
|
@declaration_sourced = declaration_sourced
|
|
269
|
+
@published_constant_sourced = published_constant_sourced
|
|
234
270
|
@source_path = source_path
|
|
235
271
|
@struct_fold_safe_locals = struct_fold_safe_locals
|
|
236
272
|
@opaque_block_self = opaque_block_self
|
|
@@ -314,6 +350,10 @@ module Rigor
|
|
|
314
350
|
indexed_narrowings: new_indexed_narrowings,
|
|
315
351
|
method_chain_narrowings: new_chain_narrowings,
|
|
316
352
|
declaration_sourced: drop_declaration_sourced_for(:local, name),
|
|
353
|
+
# Issue #667 — rebinding is flow-live for the published-constant mark too: the new value need
|
|
354
|
+
# not be a copy of anything. `with_published_constant_mark` re-stamps afterward when the write's
|
|
355
|
+
# rvalue is one.
|
|
356
|
+
published_constant_sourced: drop_published_constant_sourced_for(:local, name),
|
|
317
357
|
local_origins: drop_origin(@local_origins, name),
|
|
318
358
|
optimistic_locals: drop_origin(@optimistic_locals, name))
|
|
319
359
|
end
|
|
@@ -419,6 +459,7 @@ module Rigor
|
|
|
419
459
|
indexed_narrowings: new_indexed_narrowings,
|
|
420
460
|
method_chain_narrowings: new_chain_narrowings,
|
|
421
461
|
declaration_sourced: drop_declaration_sourced_for(:ivar, name),
|
|
462
|
+
published_constant_sourced: drop_published_constant_sourced_for(:ivar, name),
|
|
422
463
|
ivar_origins: drop_origin(@ivar_origins, name),
|
|
423
464
|
optimistic_ivars: drop_origin(@optimistic_ivars, name))
|
|
424
465
|
end
|
|
@@ -445,6 +486,22 @@ module Rigor
|
|
|
445
486
|
rebuild(declaration_sourced: add_declaration_sourced(:local, name))
|
|
446
487
|
end
|
|
447
488
|
|
|
489
|
+
# Issue #667 — record that `name` is currently bound to a value copied out of a foreign published
|
|
490
|
+
# constant. Always applied AFTER the `with_local` / `with_ivar` that binds the value (both drop the mark
|
|
491
|
+
# unconditionally), exactly as {#with_local_declaration_mark} is.
|
|
492
|
+
def with_published_constant_mark(kind, name)
|
|
493
|
+
rebuild(published_constant_sourced: add_published_constant_sourced(kind, name))
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Issue #667 — true when `(kind, name)`'s current binding is a copy of a constant the project published
|
|
497
|
+
# and this file does not declare. Asked through
|
|
498
|
+
# {Analysis::CheckRules::PublishedConstantGuard}, never directly from a rule.
|
|
499
|
+
def published_constant_sourced?(kind, name)
|
|
500
|
+
return false if @published_constant_sourced.empty?
|
|
501
|
+
|
|
502
|
+
@published_constant_sourced.include?([kind.to_sym, name.to_sym])
|
|
503
|
+
end
|
|
504
|
+
|
|
448
505
|
# ADR-58 WD1 — true when `(kind, name)`'s binding optionality is purely declaration-sourced (no flow-live
|
|
449
506
|
# write/narrowing has touched it).
|
|
450
507
|
def declaration_sourced?(kind, name)
|
|
@@ -744,6 +801,34 @@ module Rigor
|
|
|
744
801
|
@discovery.discovered_includes[class_name.to_s] || []
|
|
745
802
|
end
|
|
746
803
|
|
|
804
|
+
# Issue #898 — the module names `extend`ed onto `class_name`'s SINGLETON, as written, gathered up the
|
|
805
|
+
# as-written superclass chain because a singleton class inherits its superclass's singleton class
|
|
806
|
+
# (`class Base; extend Comparable; end; class Widget < Base; end` leaves `Widget.is_a?(Comparable)`
|
|
807
|
+
# true). Empty for a class the project never extends, and for every scope that saw no seeding pass.
|
|
808
|
+
#
|
|
809
|
+
# This is EVIDENCE FOR a singleton ancestor, never against one: the walk sees only what a constant
|
|
810
|
+
# argument to a receiverless `extend` (or, since #915, a receiverless `include` / `prepend` inside a
|
|
811
|
+
# `class << self` body) written inside a declaration body spells, so a runtime `Widget.extend(m)` and an
|
|
812
|
+
# `extend` in a file outside the analysed set are both absent from it, as is an `extend` declared only
|
|
813
|
+
# in RBS — that one reaches the same consumer through `Environment#singleton_extended_modules` instead.
|
|
814
|
+
# Its one consumer ({Inference::Narrowing.narrow_class}) therefore reads it
|
|
815
|
+
# only to WITHHOLD a `Bot`, and never to assert that a guard matches.
|
|
816
|
+
def singleton_extends_of(class_name)
|
|
817
|
+
table = @discovery.discovered_extends
|
|
818
|
+
return [] if table.empty?
|
|
819
|
+
|
|
820
|
+
names = []
|
|
821
|
+
current = class_name.to_s
|
|
822
|
+
seen = {}
|
|
823
|
+
while current && !seen[current] && seen.size <= ANCESTOR_WALK_LIMIT
|
|
824
|
+
seen[current] = true
|
|
825
|
+
record_class_dependency(current) if Analysis::DependencyRecorder.active?
|
|
826
|
+
names.concat(table[current] || [])
|
|
827
|
+
current = @discovery.discovered_superclasses[current]
|
|
828
|
+
end
|
|
829
|
+
names.uniq
|
|
830
|
+
end
|
|
831
|
+
|
|
747
832
|
# ADR-24 slice 2 — the user-side ancestor walk: resolves `method_name` against `class_name`'s own `def`s,
|
|
748
833
|
# then breadth-first through its included / prepended modules (mixins first, as Ruby orders them) and its
|
|
749
834
|
# superclass chain. Returns `[def_node, owner]`, or `[nil, nil]` when nothing in the project defines it.
|
|
@@ -810,9 +895,56 @@ module Rigor
|
|
|
810
895
|
[nil, nil]
|
|
811
896
|
end
|
|
812
897
|
|
|
898
|
+
# Issue #633 — the ancestors a project class reaches that the project itself does NOT declare: the
|
|
899
|
+
# `< StandardError` / `< Array` superclasses and the `include Comparable` mixins the two walks above
|
|
900
|
+
# deliberately drop, gathered breadth-first over the project ancestry so an inherited edge counts too.
|
|
901
|
+
#
|
|
902
|
+
# Each entry is the CANDIDATE LIST for one as-written name ({#ancestor_name_candidates}: the nesting
|
|
903
|
+
# spellings first, the bare name last), not a resolved class — resolving it means asking the RBS
|
|
904
|
+
# environment, which this frozen-index walk does not read. The caller takes the first candidate its own
|
|
905
|
+
# oracle knows and ignores the rest, the same "most-qualified first" order the project-side resolution
|
|
906
|
+
# uses.
|
|
907
|
+
def external_ancestor_name_candidates(class_name, name_memo: {})
|
|
908
|
+
groups = []
|
|
909
|
+
queue = [class_name.to_s]
|
|
910
|
+
seen = {}
|
|
911
|
+
visited = 0
|
|
912
|
+
until queue.empty?
|
|
913
|
+
current = queue.shift
|
|
914
|
+
next if current.nil? || seen[current]
|
|
915
|
+
|
|
916
|
+
seen[current] = true
|
|
917
|
+
visited += 1
|
|
918
|
+
break if visited > ANCESTOR_WALK_LIMIT
|
|
919
|
+
|
|
920
|
+
collect_external_ancestors(current, queue, groups, name_memo)
|
|
921
|
+
end
|
|
922
|
+
groups
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# One node of {#external_ancestor_name_candidates}: the project-declared ancestors continue the walk,
|
|
926
|
+
# everything else is reported as a candidate list.
|
|
927
|
+
def collect_external_ancestors(current, queue, groups, name_memo)
|
|
928
|
+
raw_names = includes_of(current).dup
|
|
929
|
+
raw_super = superclass_of(current)
|
|
930
|
+
raw_names << raw_super if raw_super
|
|
931
|
+
raw_names.each do |raw|
|
|
932
|
+
resolved = resolve_ancestor_class_name(current, raw, name_memo)
|
|
933
|
+
if resolved
|
|
934
|
+
queue.push(resolved)
|
|
935
|
+
else
|
|
936
|
+
groups << ancestor_name_candidates(current, raw)
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
end
|
|
940
|
+
private :collect_external_ancestors
|
|
941
|
+
|
|
813
942
|
# The BFS node cap; a hierarchy past it gives up rather than walking unboundedly (ADR-41 WD4).
|
|
814
943
|
ANCESTOR_WALK_LIMIT = 100
|
|
815
944
|
|
|
945
|
+
EMPTY_HEADER_NESTING = [].freeze
|
|
946
|
+
private_constant :EMPTY_HEADER_NESTING
|
|
947
|
+
|
|
816
948
|
def ancestor_walk_gave_up
|
|
817
949
|
Inference::BudgetTrace.hit(Inference::BudgetTrace::ANCESTOR_WALK_LIMIT)
|
|
818
950
|
[nil, nil]
|
|
@@ -929,10 +1061,25 @@ module Rigor
|
|
|
929
1061
|
return [raw.delete_prefix("::")] if raw.start_with?("::")
|
|
930
1062
|
|
|
931
1063
|
recorded = @discovery.discovered_header_nestings[subclass_qualified.to_s]
|
|
932
|
-
entries = recorded
|
|
1064
|
+
entries = recorded ? recorded_header_nesting(recorded, raw) : peeled_header_nesting(subclass_qualified)
|
|
933
1065
|
entries.map { |entry| "#{entry}::#{raw_ancestor}" } << raw_ancestor.to_s
|
|
934
1066
|
end
|
|
935
1067
|
|
|
1068
|
+
# Issue #728 — the chain of the declaration site that WROTE `raw`, which is the cref Ruby resolves that
|
|
1069
|
+
# one name in. A class's sites need not agree: `class Foo < Base` at the top level and a rooted
|
|
1070
|
+
# `class ::Foo; include Helper; end` inside `module Outer` are two sites of `Foo`, and the union of
|
|
1071
|
+
# their chains put `Outer::Base` — a class Ruby never looks at — ahead of `::Base` for the superclass
|
|
1072
|
+
# the top-level site wrote. That is a WRONG CLASS, not a wider candidate list.
|
|
1073
|
+
#
|
|
1074
|
+
# The unkeyed entry is the union, and answers a name no site recorded under its own key: an `include`
|
|
1075
|
+
# attributed to this class from OUTSIDE its declaration (`Recv.class_eval { include M }`, which
|
|
1076
|
+
# `Inference::ScopeIndexer#walk_class_includes` names but the header walk cannot see), or a dynamic
|
|
1077
|
+
# mixin argument. It is the pre-#728 answer, so those are unchanged rather than degraded.
|
|
1078
|
+
def recorded_header_nesting(bucket, raw)
|
|
1079
|
+
bucket[raw] || bucket[DiscoveryIndex::UNKEYED_HEADER_NESTING] || EMPTY_HEADER_NESTING
|
|
1080
|
+
end
|
|
1081
|
+
private :recorded_header_nesting
|
|
1082
|
+
|
|
936
1083
|
# The pre-#682 reading of a qualified class name: every proper prefix of it, innermost first, as if the
|
|
937
1084
|
# class had been declared one `module` keyword per segment.
|
|
938
1085
|
def peeled_header_nesting(subclass_qualified)
|
|
@@ -974,6 +1121,21 @@ module Rigor
|
|
|
974
1121
|
end
|
|
975
1122
|
private :record_class_dependency
|
|
976
1123
|
|
|
1124
|
+
# Issue #639 — the EXISTENCE edge for a bare class reference, `class:<last segment>`, the hit-side twin of
|
|
1125
|
+
# the miss-side key `Inference::ExpressionTyper` already records. ADR-46 slice 1c left the existence hit
|
|
1126
|
+
# edgeless on the reasoning that a file merely referencing `Post` depends on `Post`'s methods, not on its
|
|
1127
|
+
# bare existence; that stops holding the moment the DECLARING FILE IS DELETED, because existence is then
|
|
1128
|
+
# exactly what changed and a reference-only consumer had recorded nothing to be re-checked by.
|
|
1129
|
+
#
|
|
1130
|
+
# NAME-keyed rather than a positive edge to the declaring file, which is the distinction the ADR's cost
|
|
1131
|
+
# argument turns on: the declared-class set of a file does not move when a method body in it is edited,
|
|
1132
|
+
# so a bare referent is re-checked when the class appears or disappears and at no other time. A file edge
|
|
1133
|
+
# would have re-checked every bare referent of every class the file declares on any edit to it.
|
|
1134
|
+
def record_class_existence(class_name)
|
|
1135
|
+
segment = class_name.to_s.delete_prefix("::").split("::").last
|
|
1136
|
+
Analysis::DependencyRecorder.read_name(:class, segment) if segment
|
|
1137
|
+
end
|
|
1138
|
+
|
|
977
1139
|
# Issue #644 — the positive ADR-46 edge for a cross-file VALUE constant. `Reflection.constant_type_at`
|
|
978
1140
|
# calls this the moment a candidate resolves through `in_source_constants`, so the reader depends on the
|
|
979
1141
|
# file that wrote the constant: editing the literal, or deleting the file, re-checks the reader. Recorded
|
|
@@ -1125,7 +1287,8 @@ module Rigor
|
|
|
1125
1287
|
@globals == other.globals &&
|
|
1126
1288
|
@indexed_narrowings == other.indexed_narrowings &&
|
|
1127
1289
|
@method_chain_narrowings == other.method_chain_narrowings &&
|
|
1128
|
-
@declaration_sourced == other.declaration_sourced
|
|
1290
|
+
@declaration_sourced == other.declaration_sourced &&
|
|
1291
|
+
@published_constant_sourced == other.published_constant_sourced
|
|
1129
1292
|
end
|
|
1130
1293
|
alias eql? ==
|
|
1131
1294
|
|
|
@@ -1142,6 +1305,7 @@ module Rigor
|
|
|
1142
1305
|
indexed_narrowings: @indexed_narrowings,
|
|
1143
1306
|
method_chain_narrowings: @method_chain_narrowings,
|
|
1144
1307
|
declaration_sourced: @declaration_sourced,
|
|
1308
|
+
published_constant_sourced: @published_constant_sourced,
|
|
1145
1309
|
source_path: @source_path,
|
|
1146
1310
|
struct_fold_safe_locals: @struct_fold_safe_locals,
|
|
1147
1311
|
opaque_block_self: @opaque_block_self,
|
|
@@ -1163,6 +1327,7 @@ module Rigor
|
|
|
1163
1327
|
indexed_narrowings: indexed_narrowings,
|
|
1164
1328
|
method_chain_narrowings: method_chain_narrowings,
|
|
1165
1329
|
declaration_sourced: declaration_sourced,
|
|
1330
|
+
published_constant_sourced: published_constant_sourced,
|
|
1166
1331
|
source_path: source_path,
|
|
1167
1332
|
struct_fold_safe_locals: struct_fold_safe_locals,
|
|
1168
1333
|
opaque_block_self: opaque_block_self,
|
|
@@ -1208,6 +1373,12 @@ module Rigor
|
|
|
1208
1373
|
# path made the binding flow-live (a method-local nil write / failed-guard narrowing), the merge is
|
|
1209
1374
|
# flow-live and `possible-nil-receiver` fires as before.
|
|
1210
1375
|
declaration_sourced: join_declaration_sourced(other),
|
|
1376
|
+
# Issue #667 — UNION, the opposite of the line above, which is the first reason this mark does not
|
|
1377
|
+
# ride the ADR-58 Set. The mark only ever WITHHOLDS a firing, so keeping it when either arm bound the
|
|
1378
|
+
# name from a published constant is the false-positive-safe merge: `if c then m = MODE else m = MODE2
|
|
1379
|
+
# end; m == :x` must not warn because one arm's copy is invisible to the reader's author. ADR-67
|
|
1380
|
+
# WD6b's `:inferred_param` taint takes the same direction for the same reason.
|
|
1381
|
+
published_constant_sourced: join_published_constant_sourced(other),
|
|
1211
1382
|
source_path: source_path,
|
|
1212
1383
|
# Issue #589 — the fold-safe set MUST survive a merge. It was simply absent from this constructor
|
|
1213
1384
|
# call, so it fell back to the empty default and every `if` / `while` in a method silently revoked
|
|
@@ -1339,6 +1510,34 @@ module Rigor
|
|
|
1339
1510
|
end
|
|
1340
1511
|
|
|
1341
1512
|
# ADR-58 WD1 — set/clear the declaration-sourced provenance mark.
|
|
1513
|
+
# Issue #667 — the #667 carrier's add/drop pair. Deliberately not reusing
|
|
1514
|
+
# {#add_declaration_sourced} / {#drop_declaration_sourced_for}: the two sets are joined by opposite
|
|
1515
|
+
# policies, so sharing the storage would make every future edit to either one a decision about both.
|
|
1516
|
+
def add_published_constant_sourced(kind, name)
|
|
1517
|
+
ref = [kind.to_sym, name.to_sym].freeze
|
|
1518
|
+
return @published_constant_sourced if @published_constant_sourced.include?(ref)
|
|
1519
|
+
|
|
1520
|
+
(@published_constant_sourced.dup << ref).freeze
|
|
1521
|
+
end
|
|
1522
|
+
|
|
1523
|
+
def drop_published_constant_sourced_for(kind, name)
|
|
1524
|
+
return @published_constant_sourced if @published_constant_sourced.empty?
|
|
1525
|
+
|
|
1526
|
+
ref = [kind.to_sym, name.to_sym]
|
|
1527
|
+
return @published_constant_sourced unless @published_constant_sourced.include?(ref)
|
|
1528
|
+
|
|
1529
|
+
(@published_constant_sourced - [ref]).freeze
|
|
1530
|
+
end
|
|
1531
|
+
|
|
1532
|
+
def join_published_constant_sourced(other)
|
|
1533
|
+
mine = @published_constant_sourced
|
|
1534
|
+
theirs = other.published_constant_sourced
|
|
1535
|
+
return mine if mine.equal?(theirs) || theirs.empty?
|
|
1536
|
+
return theirs if mine.empty?
|
|
1537
|
+
|
|
1538
|
+
(mine | theirs).freeze
|
|
1539
|
+
end
|
|
1540
|
+
|
|
1342
1541
|
def add_declaration_sourced(kind, name)
|
|
1343
1542
|
ref = [kind.to_sym, name.to_sym]
|
|
1344
1543
|
return @declaration_sourced if @declaration_sourced.include?(ref)
|
|
@@ -4,9 +4,11 @@ require "prism"
|
|
|
4
4
|
|
|
5
5
|
require_relative "../configuration"
|
|
6
6
|
require_relative "../environment"
|
|
7
|
+
require_relative "../project_environment"
|
|
7
8
|
require_relative "../scope"
|
|
8
9
|
require_relative "../reflection"
|
|
9
10
|
require_relative "../type"
|
|
11
|
+
require_relative "../source/constant_path"
|
|
10
12
|
require_relative "../source/literals"
|
|
11
13
|
require_relative "../source/node_children"
|
|
12
14
|
require_relative "../inference/def_return_typer"
|
|
@@ -39,17 +41,23 @@ module Rigor
|
|
|
39
41
|
# - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
|
|
40
42
|
# strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
|
|
41
43
|
# reserves the eventual `:strict` mode).
|
|
44
|
+
# - A declared `void` return is never compared as a value type. It is the author's statement that the
|
|
45
|
+
# return is not part of the contract, which no inference can synthesize, so the method classifies
|
|
46
|
+
# `equivalent` carrying `void` itself as the declared spelling (#836; see {#declares_void?}).
|
|
47
|
+
# - A proposal that erases to an RBS literal never tightens an existing declaration: the declared type is
|
|
48
|
+
# the author's abstraction over the body and the literal is what it hides (#837; see
|
|
49
|
+
# {#pins_literal_over_declaration?}). A method with no declaration is unaffected — clause 1 still emits
|
|
50
|
+
# the strictest carrier the body proves.
|
|
42
51
|
class Generator # rubocop:disable Metrics/ClassLength
|
|
43
52
|
# Methods the generator rendered into RBS that `rbs` itself rejects. Populated by {#build_candidate}; each
|
|
44
53
|
# one is a Rigor rendering DEFECT, not a property of the user's code, so the CLI reports them as such.
|
|
45
54
|
UnrenderableMethod = Data.define(:path, :class_name, :method_name, :rbs, :error)
|
|
46
55
|
|
|
47
|
-
# @return
|
|
56
|
+
# @return empty on a healthy run; read after {#run}.
|
|
48
57
|
attr_reader :unrenderable, :unresolvable_superclasses
|
|
49
58
|
|
|
50
|
-
# @param
|
|
51
|
-
# @param
|
|
52
|
-
# @param observations [Hash{[String, Symbol] => Array<Array<Rigor::Type>>}]
|
|
59
|
+
# @param paths — files / directories to scan.
|
|
60
|
+
# @param observations — => Array<Array<Rigor::Type>>}]
|
|
53
61
|
# ADR-14 slice 3 — per-target-method arg-tuple observations
|
|
54
62
|
# produced by {ObservationCollector}. An empty Hash (the default)
|
|
55
63
|
# means "no observations available; emit `untyped` for every
|
|
@@ -84,10 +92,9 @@ module Rigor
|
|
|
84
92
|
map.transform_values { |entries| entries.map { |entry| ObservedCall.from(entry) } }
|
|
85
93
|
end
|
|
86
94
|
|
|
87
|
-
# @return [Array<MethodCandidate>]
|
|
88
95
|
def run
|
|
89
|
-
@environment = build_environment
|
|
90
96
|
resolved = resolve_paths(@paths)
|
|
97
|
+
@environment = build_environment(resolved)
|
|
91
98
|
candidates = resolved.flat_map { |path| analyse_file(path, @environment) }
|
|
92
99
|
demote_overridden_base_methods(
|
|
93
100
|
demote_unresolvable_superclasses(resolve_superclass_spellings(candidates))
|
|
@@ -311,11 +318,13 @@ module Rigor
|
|
|
311
318
|
)
|
|
312
319
|
end
|
|
313
320
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
# Issue #821 — the SAME type universe `rigor check` analyses against: the project's `sig/`, the plugins,
|
|
322
|
+
# the bundle's per-gem signatures and the `rbs collection`. Built with two keywords only, the generator
|
|
323
|
+
# could not see a gem-declared superclass, and the skip guard above (`record_unresolvable_superclass`)
|
|
324
|
+
# then declined to emit every class descending from one — every Rails model, on a project whose
|
|
325
|
+
# collection was installed and whose `check` read it.
|
|
326
|
+
def build_environment(source_files = [])
|
|
327
|
+
ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
|
|
319
328
|
end
|
|
320
329
|
|
|
321
330
|
def resolve_paths(args)
|
|
@@ -413,14 +422,22 @@ module Rigor
|
|
|
413
422
|
end
|
|
414
423
|
end
|
|
415
424
|
|
|
425
|
+
# [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
|
|
426
|
+
# `Source::ConstantPath.declaration_prefix`, the same reader the engine's declaration walks use, so a
|
|
427
|
+
# ROOTED header re-anchors here too. Appending the rendered name to the enclosing prefix wrote
|
|
428
|
+
# `class ::Foo` inside `module MyApp` out as `MyApp::Foo` — sig-gen's own copy of #708, and the one
|
|
429
|
+
# copy that persists its answer into a file the user keeps.
|
|
430
|
+
#
|
|
431
|
+
# `prefix` stays the nesting the header is WRITTEN in for {#record_superclass}: `::` re-anchors the
|
|
432
|
+
# declaration, not the superclass expression beside it, which Ruby still evaluates in the enclosing cref.
|
|
416
433
|
def descend_into_namespace?(node, prefix, out)
|
|
417
|
-
|
|
418
|
-
return false unless
|
|
434
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
435
|
+
return false unless child_prefix
|
|
419
436
|
|
|
420
|
-
full =
|
|
437
|
+
full = child_prefix.join("::")
|
|
421
438
|
@namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
|
|
422
439
|
record_superclass(node, full, prefix)
|
|
423
|
-
walk_namespace_body(node,
|
|
440
|
+
walk_namespace_body(node, child_prefix, out)
|
|
424
441
|
true
|
|
425
442
|
end
|
|
426
443
|
|
|
@@ -836,7 +853,14 @@ module Rigor
|
|
|
836
853
|
)
|
|
837
854
|
end
|
|
838
855
|
|
|
856
|
+
# The RBS spelling carried for a `void`-declared method, so `--diff` shows the author's own word rather
|
|
857
|
+
# than the `top` the translator would erase it to.
|
|
858
|
+
VOID_RETURN_RBS = "void"
|
|
859
|
+
private_constant :VOID_RETURN_RBS
|
|
860
|
+
|
|
839
861
|
def compare_against_declared(path, def_node, class_name, kind, inferred, method_def)
|
|
862
|
+
return equivalent(path, def_node, class_name, kind, inferred, VOID_RETURN_RBS) if declares_void?(method_def)
|
|
863
|
+
|
|
840
864
|
declared = build_declared_return(method_def)
|
|
841
865
|
declared_rbs = declared&.erase_to_rbs
|
|
842
866
|
inferred_rbs = inferred.erase_to_rbs
|
|
@@ -845,7 +869,7 @@ module Rigor
|
|
|
845
869
|
return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
|
|
846
870
|
end
|
|
847
871
|
|
|
848
|
-
unless tighter?(declared, inferred) && !
|
|
872
|
+
unless tighter?(declared, inferred) && !literal_decline?(inferred, def_node)
|
|
849
873
|
return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
|
|
850
874
|
end
|
|
851
875
|
|
|
@@ -861,6 +885,28 @@ module Rigor
|
|
|
861
885
|
)
|
|
862
886
|
end
|
|
863
887
|
|
|
888
|
+
# Issue #836 — a declared `void` is return INTENT, never a wide value type waiting to be narrowed, so it
|
|
889
|
+
# is not compared against the body at all.
|
|
890
|
+
#
|
|
891
|
+
# `void` says the return value is not part of the method's contract. No synthesis can produce it: a type
|
|
892
|
+
# built from a body is always the type of the body's last expression, so `void` exists only on the
|
|
893
|
+
# CHECKING side, against a declaration the author wrote. That puts it in the same position ADR-107 gives
|
|
894
|
+
# a parameter type — authored intent inference cannot derive — and proposing the body's value for it is
|
|
895
|
+
# a category error rather than a false positive to tune.
|
|
896
|
+
#
|
|
897
|
+
# Mechanically the misreading came from the translator: `docs/type-specification/special-types.md`
|
|
898
|
+
# § `void` records that the engine maps RBS `void` to `top`, `Type::Top` accepts everything, so
|
|
899
|
+
# {#tighter?} answered "yes" for every `void`-declared method whose body happens to return a typed
|
|
900
|
+
# value. Seven of the fifteen tighter-returns in Rigor's own `sig/` were this shape, and the ADR-107 G3
|
|
901
|
+
# provenance gate inherited the misclassification with them.
|
|
902
|
+
#
|
|
903
|
+
# ANY overload declaring `void` is enough. One proposal is rendered for the whole method, so an overload
|
|
904
|
+
# set that mixes `void` with a value return still has an author saying "not part of the contract" about
|
|
905
|
+
# the return sig-gen would rewrite.
|
|
906
|
+
def declares_void?(method_def)
|
|
907
|
+
method_def.method_types.any? { |mt| mt.type.return_type.is_a?(RBS::Types::Bases::Void) }
|
|
908
|
+
end
|
|
909
|
+
|
|
864
910
|
def build_declared_return(method_def)
|
|
865
911
|
translated = method_def.method_types.filter_map { |mt| translate_method_type_return(mt) }
|
|
866
912
|
return nil if translated.empty?
|
|
@@ -943,6 +989,48 @@ module Rigor
|
|
|
943
989
|
inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
|
|
944
990
|
end
|
|
945
991
|
|
|
992
|
+
# The two reasons a `Type::Constant` the body proves is not the method's contract. Both say the same
|
|
993
|
+
# thing from opposite ends: the precision is real about this body and says nothing about the promise
|
|
994
|
+
# the method makes to its callers.
|
|
995
|
+
def literal_decline?(inferred, def_node)
|
|
996
|
+
computed_literal_tightening?(inferred, def_node) || pins_literal_over_declaration?(inferred)
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
# Issue #837 — a proposal that erases to an RBS LITERAL type never tightens an existing declaration.
|
|
1000
|
+
#
|
|
1001
|
+
# `Type::Top#describe` really does return `"top"`, but `describe` is the surface every `Rigor::Type::*`
|
|
1002
|
+
# class implements and every one of them declares `String`. The declared nominal is the author's
|
|
1003
|
+
# abstraction over the body, and the literal is the implementation detail that abstraction deliberately
|
|
1004
|
+
# hides — the same reading #836 gave a declared `void`. ADR-107 § Decision puts both on the intent
|
|
1005
|
+
# side: no synthesis produces an abstraction, because a type built from a body is always the type of
|
|
1006
|
+
# the body's last expression, so the choice to be wider than the body only ever exists in a declaration
|
|
1007
|
+
# someone wrote.
|
|
1008
|
+
#
|
|
1009
|
+
# The generator already answered this way whenever the declaration happened to be a UNION.
|
|
1010
|
+
# `Configuration.discover` declares `String?` against a body proving `".rigor.dist.yml" |
|
|
1011
|
+
# ".rigor.yml" | nil` and {#loses_declared_union_member?} refuses it, because no literal covers the
|
|
1012
|
+
# declared `String`. Whether the author wrote `String` or `String?` is not a decision about literals,
|
|
1013
|
+
# so the two shapes now answer alike.
|
|
1014
|
+
#
|
|
1015
|
+
# Scoped by the ERASURE, not by the carrier. `Type::Constant` also carries values RBS has no literal
|
|
1016
|
+
# spelling for (`Float`, `Regexp`, `Set`, `Pathname`, `Date`, …) and those erase to the class name, so
|
|
1017
|
+
# `def pi: () -> Numeric` against a body of `3.14` still proposes the ordinary nominal `Float`. A union
|
|
1018
|
+
# is refused when ANY member is a literal: a mixed `("a" | Float)` pins `"a"` just as a bare one does.
|
|
1019
|
+
# A literal nested inside a shape carrier (`[1, 2]`, a `HashShape` value) is a different question,
|
|
1020
|
+
# already answered for the collection declarations that matter by {#narrows_collection_to_shape?}.
|
|
1021
|
+
def pins_literal_over_declaration?(inferred)
|
|
1022
|
+
members = inferred.is_a?(Type::Union) ? inferred.members : [inferred]
|
|
1023
|
+
members.any? { |member| rbs_literal?(member) }
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
# `Type::Constant#erase_to_rbs` spells the values RBS writes as literal types (`true` / `false` /
|
|
1027
|
+
# `nil`, an Integer, a Symbol, a String) as that literal and every other carrier as its value's class
|
|
1028
|
+
# name. Asking which of the two branches it took keeps the list of literal-spellable classes from
|
|
1029
|
+
# forking a second copy into this file.
|
|
1030
|
+
def rbs_literal?(type)
|
|
1031
|
+
type.is_a?(Type::Constant) && type.erase_to_rbs != type.value.class.name
|
|
1032
|
+
end
|
|
1033
|
+
|
|
946
1034
|
# Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
|
|
947
1035
|
# `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
|
|
948
1036
|
# `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` — the carrier knows the empty-tuple cardinality
|
|
@@ -950,6 +1038,11 @@ module Rigor
|
|
|
950
1038
|
# signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
|
|
951
1039
|
# Constant"; in that case the precision came from inference over an internal computation, not the
|
|
952
1040
|
# author's contract, so refuse to tighten.
|
|
1041
|
+
#
|
|
1042
|
+
# Since #837 the literal-ERASING half of this is refused whichever expression produced it. What stays
|
|
1043
|
+
# this guard's own is the `Constant` that erases to a class name: `def average = @total / 2.0` folds to
|
|
1044
|
+
# `Constant<2.5>` and would propose the perfectly ordinary `Float`, which is still the fold's answer
|
|
1045
|
+
# about one body rather than the method's contract.
|
|
953
1046
|
def computed_literal_tightening?(inferred, def_node)
|
|
954
1047
|
return false unless inferred.is_a?(Type::Constant)
|
|
955
1048
|
|
|
@@ -1082,9 +1175,9 @@ module Rigor
|
|
|
1082
1175
|
|
|
1083
1176
|
case node
|
|
1084
1177
|
when Prism::ClassNode, Prism::ModuleNode
|
|
1085
|
-
|
|
1086
|
-
if
|
|
1087
|
-
walk_attr_calls(node.body,
|
|
1178
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
1179
|
+
if child_prefix
|
|
1180
|
+
walk_attr_calls(node.body, child_prefix, false, ctx) if node.body
|
|
1088
1181
|
return
|
|
1089
1182
|
end
|
|
1090
1183
|
when Prism::SingletonClassNode
|
|
@@ -1166,9 +1259,9 @@ module Rigor
|
|
|
1166
1259
|
|
|
1167
1260
|
case node
|
|
1168
1261
|
when Prism::ClassNode, Prism::ModuleNode
|
|
1169
|
-
|
|
1170
|
-
if
|
|
1171
|
-
collect_init_ivar_obs(node.body,
|
|
1262
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
1263
|
+
if child_prefix
|
|
1264
|
+
collect_init_ivar_obs(node.body, child_prefix, result) if node.body
|
|
1172
1265
|
return
|
|
1173
1266
|
end
|
|
1174
1267
|
when Prism::DefNode
|
|
@@ -21,20 +21,19 @@ module Rigor
|
|
|
21
21
|
# in multiple files for additive member contributions, but the writer only needs one canonical target per
|
|
22
22
|
# class.
|
|
23
23
|
class LayoutIndex
|
|
24
|
-
# @param signature_paths
|
|
24
|
+
# @param signature_paths —
|
|
25
25
|
# the `.rigor.yml`-configured signature directories.
|
|
26
26
|
# When `nil` or empty, falls back to `<project_root>/sig`
|
|
27
27
|
# if it exists (matching `Environment.for_project`'s
|
|
28
28
|
# auto-detection convention).
|
|
29
|
-
# @param project_root [String, Pathname]
|
|
30
29
|
def initialize(signature_paths:, project_root: Dir.pwd)
|
|
31
30
|
@signature_paths = resolve_paths(signature_paths, project_root)
|
|
32
31
|
@index = nil
|
|
33
32
|
end
|
|
34
33
|
|
|
35
|
-
# @param class_name
|
|
34
|
+
# @param class_name — fully-qualified Ruby class
|
|
36
35
|
# name (e.g. `"Rigor::Type::Top"`).
|
|
37
|
-
# @return
|
|
36
|
+
# @return absolute path of the sig file
|
|
38
37
|
# that already declares this class, or `nil` when no
|
|
39
38
|
# existing declaration is found.
|
|
40
39
|
def file_for(class_name)
|
|
@@ -29,11 +29,9 @@ module Rigor
|
|
|
29
29
|
|
|
30
30
|
module_function
|
|
31
31
|
|
|
32
|
-
# @param
|
|
33
|
-
# @param
|
|
34
|
-
# @param
|
|
35
|
-
# @param keyword_init [Boolean] the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
|
|
36
|
-
# @return [Shape]
|
|
32
|
+
# @param members — ordered member names from the ADR-48 layout.
|
|
33
|
+
# @param member_types — erased RBS spellings; a member with no entry renders `untyped`.
|
|
34
|
+
# @param keyword_init — the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
|
|
37
35
|
def of(kind:, members:, member_types: {}, keyword_init: false)
|
|
38
36
|
types = members.to_h { |member| [member, member_types[member] || "untyped"] }
|
|
39
37
|
accessors = kind == :struct ? struct_accessors(members, types) : readers(members, types)
|