rigortype 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +132 -14
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- 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 +19 -1
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +525 -13
- 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 +95 -6
- data/docs/manual/03-configuration.md +18 -0
- data/docs/manual/04-diagnostics.md +15 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/10-mcp-server.md +3 -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 +46 -8
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +261 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +138 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +3 -3
- 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/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
- data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
- 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/source_arity.rb +323 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +718 -79
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_recorder.rb +23 -0
- 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 +15 -1
- data/lib/rigor/analysis/incremental.rb +42 -0
- data/lib/rigor/analysis/incremental_session.rb +117 -26
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -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 +3 -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 +131 -1
- data/lib/rigor/analysis/run_cache_key.rb +21 -12
- 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 +340 -33
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
- data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
- data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +407 -51
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +91 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +399 -0
- data/lib/rigor/analysis/worker_session.rb +62 -20
- data/lib/rigor/bleeding_edge.rb +10 -24
- data/lib/rigor/builtins/hkt_builtins.rb +3 -2
- data/lib/rigor/builtins/imported_refinements.rb +159 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +21 -13
- data/lib/rigor/builtins/static_return_refinements.rb +7 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +63 -20
- data/lib/rigor/cache/engine_source.rb +33 -5
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +72 -6
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- 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 +112 -14
- 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 +76 -32
- data/lib/rigor/ci_detector.rb +1 -0
- 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 +4 -4
- data/lib/rigor/cli/docs_command.rb +6 -6
- 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 +4 -4
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +1 -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 +212 -31
- data/lib/rigor/cli/skill_command.rb +2 -2
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +35 -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 +29 -11
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/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 +89 -9
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +25 -9
- data/lib/rigor/configuration.rb +109 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +12 -5
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +15 -10
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +11 -4
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +9 -9
- data/lib/rigor/effects/envelope_index.rb +20 -8
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +5 -7
- data/lib/rigor/effects/identity.rb +20 -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/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +22 -1
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +47 -37
- data/lib/rigor/effects/propagator.rb +297 -19
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +122 -73
- data/lib/rigor/effects/signature_sources.rb +4 -6
- data/lib/rigor/effects/snapshot.rb +12 -11
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +393 -38
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/effects/visibility.rb +101 -0
- 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 +68 -4
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- 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 +674 -79
- data/lib/rigor/environment.rb +174 -24
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +292 -17
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +75 -30
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +7 -6
- data/lib/rigor/inference/budget_trace.rb +3 -6
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +389 -18
- data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +180 -0
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +2124 -468
- data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +447 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +16 -7
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +168 -21
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
- 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/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -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/match_data_folding.rb +159 -0
- 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 +165 -111
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
- 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/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
- 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 +5 -6
- 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 +225 -25
- data/lib/rigor/inference/method_parameter_binder.rb +12 -9
- data/lib/rigor/inference/multi_target_binder.rb +341 -55
- data/lib/rigor/inference/mutation_rejoin.rb +171 -0
- data/lib/rigor/inference/mutation_widening.rb +156 -85
- data/lib/rigor/inference/narrowing.rb +835 -162
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +153 -16
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +9 -4
- data/lib/rigor/inference/project_patched_scanner.rb +10 -6
- 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 +93 -4
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +76 -0
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer.rb +5228 -627
- data/lib/rigor/inference/statement_evaluator.rb +3452 -595
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +97 -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/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- 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 +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
- 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 +4 -4
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +7 -8
- 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/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +258 -26
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +65 -11
- 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 +33 -13
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- 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 +94 -11
- data/lib/rigor/plugin/registry.rb +76 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- 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 +14 -12
- data/lib/rigor/protection/kill_signature.rb +1 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +7 -11
- 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 +88 -21
- data/lib/rigor/reflection/constant_ancestors.rb +97 -0
- data/lib/rigor/reflection/constant_path.rb +139 -0
- data/lib/rigor/reflection.rb +93 -110
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +165 -2
- data/lib/rigor/scope.rb +1041 -32
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +673 -53
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- 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/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +18 -16
- 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 +128 -14
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -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/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +189 -41
- data/lib/rigor/sig_gen.rb +3 -0
- 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 +0 -6
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- 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 +144 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +6 -6
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +4 -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/lib/rigor.rb +1 -0
- 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/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
- 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 +14 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -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/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -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 +10 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- 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 +322 -41
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- 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 +290 -19
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -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 +2 -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/types.rb +1 -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-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- 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 +2 -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 +12 -7
- 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 +36 -37
- 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 +0 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -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 +10 -17
- 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/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
- 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 +2 -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 +10 -5
- 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 +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +17 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +9 -14
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +51 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +9 -6
- data/sig/rigor/scope.rbs +148 -12
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +33 -1
- data/sig/rigor.rbs +39 -20
- data/skills/rigor-ask/SKILL.md +16 -11
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +15 -10
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +43 -14
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +226 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +133 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -4,16 +4,26 @@ 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"
|
|
13
15
|
require_relative "../inference/scope_indexer"
|
|
14
16
|
require_relative "../inference/rbs_type_translator"
|
|
17
|
+
require_relative "alias_index"
|
|
15
18
|
require_relative "meta_class_shape"
|
|
16
19
|
require_relative "rbs_validity"
|
|
20
|
+
require_relative "classification"
|
|
21
|
+
require_relative "effect_annotation"
|
|
22
|
+
require_relative "declaration_equivalence"
|
|
23
|
+
require_relative "inline_declarations"
|
|
24
|
+
require_relative "layout_index"
|
|
25
|
+
require_relative "method_candidate"
|
|
26
|
+
require_relative "writer"
|
|
17
27
|
|
|
18
28
|
module Rigor
|
|
19
29
|
module SigGen
|
|
@@ -39,26 +49,48 @@ module Rigor
|
|
|
39
49
|
# - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
|
|
40
50
|
# strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
|
|
41
51
|
# reserves the eventual `:strict` mode).
|
|
52
|
+
# - A declared `void` return is never compared as a value type. It is the author's statement that the
|
|
53
|
+
# return is not part of the contract, which no inference can synthesize, so the method classifies
|
|
54
|
+
# `equivalent` carrying `void` itself as the declared spelling (#836; see {#declares_void?}).
|
|
55
|
+
# - A declared `untyped` return (the RBS `Bases::Any` spelling, including the one ADR-93 synthesizes for
|
|
56
|
+
# a parameter-only inline `# @rbs` annotation) is the ABSENCE of a return statement, not the author's
|
|
57
|
+
# word about it — the opposite of `void`. It is treated like no declaration at all: the inferred return
|
|
58
|
+
# is proposed as `tighter-return`, carrying `untyped` as the declared spelling so `--diff` still shows a
|
|
59
|
+
# declaration existed (#995; see {#declared_untyped?}).
|
|
60
|
+
# - A `tighter-return` over the class's own declaration replaces the return and nothing else: the rest of
|
|
61
|
+
# the line is the declaration's, spelled as written, so `--overwrite` never changes which calls the
|
|
62
|
+
# signature accepts. A declaration with more than one overload, or one spelled as an `alias`, is left
|
|
63
|
+
# alone (#1436; see {#tighter_return_line}).
|
|
64
|
+
# - A proposal that erases to an RBS literal never tightens an existing declaration: the declared type is
|
|
65
|
+
# the author's abstraction over the body and the literal is what it hides (#837; see
|
|
66
|
+
# {#pins_literal_over_declaration?}). A method with no declaration is unaffected — clause 1 still emits
|
|
67
|
+
# the strictest carrier the body proves.
|
|
42
68
|
class Generator # rubocop:disable Metrics/ClassLength
|
|
43
69
|
# Methods the generator rendered into RBS that `rbs` itself rejects. Populated by {#build_candidate}; each
|
|
44
70
|
# one is a Rigor rendering DEFECT, not a property of the user's code, so the CLI reports them as such.
|
|
45
71
|
UnrenderableMethod = Data.define(:path, :class_name, :method_name, :rbs, :error)
|
|
46
72
|
|
|
47
|
-
# @return
|
|
73
|
+
# @return empty on a healthy run; read after {#run}.
|
|
48
74
|
attr_reader :unrenderable, :unresolvable_superclasses
|
|
49
75
|
|
|
50
|
-
# @param
|
|
51
|
-
# @param
|
|
52
|
-
# @param observations [Hash{[String, Symbol] => Array<Array<Rigor::Type>>}]
|
|
76
|
+
# @param paths — files / directories to scan.
|
|
77
|
+
# @param observations — => Array<Array<Rigor::Type>>}]
|
|
53
78
|
# ADR-14 slice 3 — per-target-method arg-tuple observations
|
|
54
79
|
# produced by {ObservationCollector}. An empty Hash (the default)
|
|
55
80
|
# means "no observations available; emit `untyped` for every
|
|
56
81
|
# parameter position" per ADR-5 clause 2.
|
|
57
|
-
|
|
82
|
+
# @param overwrite — `--overwrite`: a member declared inline whose `sig/` copy disagrees is proposed as an
|
|
83
|
+
# `inline-overwrite` of the whole member instead of refused (ADR-112 WD4).
|
|
84
|
+
def initialize(configuration:, paths:, observations: {}, include_private: false, effect_annotator: nil,
|
|
85
|
+
overwrite: false)
|
|
58
86
|
@configuration = configuration
|
|
87
|
+
@overwrite = overwrite
|
|
59
88
|
@paths = paths
|
|
60
89
|
@observations = normalize_observations(observations)
|
|
61
90
|
@include_private = include_private
|
|
91
|
+
# ADR-103 WD9 — `nil` when the project's `effects:` opt-in is off, which is what keeps an
|
|
92
|
+
# effects-off run byte-identical to one from before the annotation slot was filled.
|
|
93
|
+
@effect_annotator = effect_annotator
|
|
62
94
|
# Per-file scratch state. `analyse_file` resets each one to a fresh container for every file walked so
|
|
63
95
|
# candidates from one file don't leak into another; initialising empty here gives downstream consumers
|
|
64
96
|
# (`build_candidate`, `method_def_prefix`) a never-nil invariant without per-call-site defensive guards.
|
|
@@ -84,18 +116,103 @@ module Rigor
|
|
|
84
116
|
map.transform_values { |entries| entries.map { |entry| ObservedCall.from(entry) } }
|
|
85
117
|
end
|
|
86
118
|
|
|
87
|
-
# @return [Array<MethodCandidate>]
|
|
88
119
|
def run
|
|
89
|
-
@environment = build_environment
|
|
90
120
|
resolved = resolve_paths(@paths)
|
|
121
|
+
@environment = build_environment(resolved)
|
|
91
122
|
candidates = resolved.flat_map { |path| analyse_file(path, @environment) }
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
annotate_effects(
|
|
124
|
+
demote_overridden_base_methods(
|
|
125
|
+
demote_unresolvable_superclasses(demote_inline_generic_classes(resolve_superclass_spellings(candidates)))
|
|
126
|
+
)
|
|
94
127
|
)
|
|
95
128
|
end
|
|
96
129
|
|
|
97
130
|
private
|
|
98
131
|
|
|
132
|
+
# ADR-103 WD9 — the annotation-emission slot ADR-14 reserved, run LAST so a candidate a later
|
|
133
|
+
# demotion turns into a skip never carries an annotation it will not print. Only emittable rows are
|
|
134
|
+
# asked: an annotation is a claim attached to a line, and a row that produces no line makes none.
|
|
135
|
+
def annotate_effects(candidates)
|
|
136
|
+
return candidates if @effect_annotator.nil?
|
|
137
|
+
|
|
138
|
+
candidates.map do |candidate|
|
|
139
|
+
next candidate unless Classification::EMITTABLE.include?(candidate.classification)
|
|
140
|
+
|
|
141
|
+
annotations, reason = @effect_annotator.annotate(
|
|
142
|
+
class_name: candidate.class_name, method_name: candidate.method_name, kind: candidate.kind,
|
|
143
|
+
path: candidate.path
|
|
144
|
+
)
|
|
145
|
+
reason.nil? && annotations.empty? ? candidate : candidate.with_effect_annotation(annotations, reason)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# ADR-112 WD4 — nothing is written that would open a class an inline declaration makes generic (`# @rbs
|
|
150
|
+
# generic T`), or a namespace nested in one, in a file the project's `sig/` does not already declare it in;
|
|
151
|
+
# nor into a `sig/` declaration of the class whose type parameters are named otherwise (`class Box[U]`
|
|
152
|
+
# beside an inline `Box[T]`, which rbs accepts), where a copied `-> T` would name a parameter nothing binds.
|
|
153
|
+
#
|
|
154
|
+
# The writer spells a class header without type parameters, and rbs rejects a class whose declarations
|
|
155
|
+
# disagree on them (`GenericParameterMismatchError`): the class, and every class whose signature mentions
|
|
156
|
+
# it, fails its definition build and reads `Dynamic[top]` — while `rigor check` still exits 0. Skipping
|
|
157
|
+
# costs the generic class its `sig/` members; the inline declaration still binds when the source is
|
|
158
|
+
# analysed. A class `sig/` already declares is written into that declaration, whose parameters are the
|
|
159
|
+
# project's own.
|
|
160
|
+
def demote_inline_generic_classes(candidates)
|
|
161
|
+
generic = inline_declarations.generic_classes
|
|
162
|
+
return candidates if generic.empty?
|
|
163
|
+
|
|
164
|
+
layout = LayoutIndex.new(signature_paths: @configuration.signature_paths)
|
|
165
|
+
candidates.map do |candidate|
|
|
166
|
+
next candidate unless Classification::EMITTABLE.include?(candidate.classification)
|
|
167
|
+
next candidate unless unsafe_for_generic?(candidate.class_name, generic, layout)
|
|
168
|
+
|
|
169
|
+
demoted_candidate(candidate, :inline_generic_class)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def unsafe_for_generic?(class_name, generic, layout)
|
|
174
|
+
return false if class_name.nil?
|
|
175
|
+
|
|
176
|
+
file = layout.file_for(class_name)
|
|
177
|
+
return opens_generic_class?(class_name, generic) if file.nil?
|
|
178
|
+
|
|
179
|
+
generic.key?(class_name) && signature_type_params(file, class_name) != generic.fetch(class_name)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# The type parameter names `file` declares `class_name` with; nil when it cannot be read, which counts as
|
|
183
|
+
# a mismatch — writing into a declaration whose parameters are unknown is the unsafe direction.
|
|
184
|
+
def signature_type_params(file, class_name)
|
|
185
|
+
@signature_type_params ||= {}
|
|
186
|
+
@signature_type_params.fetch([file, class_name]) do
|
|
187
|
+
@signature_type_params[[file, class_name]] = read_signature_type_params(file, class_name)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def read_signature_type_params(file, class_name)
|
|
192
|
+
_buffer, _directives, decls = ::RBS::Parser.parse_signature(File.read(file))
|
|
193
|
+
find_declaration(decls, [], class_name)&.type_params&.map(&:name)
|
|
194
|
+
rescue StandardError
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def find_declaration(decls, prefix, class_name)
|
|
199
|
+
decls.each do |decl|
|
|
200
|
+
next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
|
|
201
|
+
|
|
202
|
+
name = (prefix + [decl.name.to_s.delete_prefix("::")]).join("::")
|
|
203
|
+
return decl if name == class_name
|
|
204
|
+
|
|
205
|
+
found = find_declaration(decl.members, name.split("::"), class_name)
|
|
206
|
+
return found if found
|
|
207
|
+
end
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def opens_generic_class?(class_name, generic)
|
|
212
|
+
segments = class_name.split("::")
|
|
213
|
+
(1..segments.size).any? { |n| generic.key?(segments.first(n).join("::")) }
|
|
214
|
+
end
|
|
215
|
+
|
|
99
216
|
# Issue #744 — a base class's method is NOT emitted when a project subclass overrides it and the
|
|
100
217
|
# override is not emitted itself.
|
|
101
218
|
#
|
|
@@ -222,7 +339,8 @@ module Rigor
|
|
|
222
339
|
inferred_return: candidate.inferred_return, declared_return_rbs: candidate.declared_return_rbs,
|
|
223
340
|
rbs: candidate.rbs, skip_reason: candidate.skip_reason,
|
|
224
341
|
namespace_kinds: candidate.namespace_kinds, class_shells: candidate.class_shells,
|
|
225
|
-
class_superclasses: superclasses
|
|
342
|
+
class_superclasses: superclasses, declared_rbs: candidate.declared_rbs,
|
|
343
|
+
declared_annotations: candidate.declared_annotations
|
|
226
344
|
)
|
|
227
345
|
end
|
|
228
346
|
|
|
@@ -311,11 +429,13 @@ module Rigor
|
|
|
311
429
|
)
|
|
312
430
|
end
|
|
313
431
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
432
|
+
# Issue #821 — the SAME type universe `rigor check` analyses against: the project's `sig/`, the plugins,
|
|
433
|
+
# the bundle's per-gem signatures and the `rbs collection`. Built with two keywords only, the generator
|
|
434
|
+
# could not see a gem-declared superclass, and the skip guard above (`record_unresolvable_superclass`)
|
|
435
|
+
# then declined to emit every class descending from one — every Rails model, on a project whose
|
|
436
|
+
# collection was installed and whose `check` read it.
|
|
437
|
+
def build_environment(source_files = [])
|
|
438
|
+
ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
|
|
319
439
|
end
|
|
320
440
|
|
|
321
441
|
def resolve_paths(args)
|
|
@@ -413,14 +533,22 @@ module Rigor
|
|
|
413
533
|
end
|
|
414
534
|
end
|
|
415
535
|
|
|
536
|
+
# [#722](https://github.com/rigortype/rigor/issues/722) residue 3 — the qualified name is built by
|
|
537
|
+
# `Source::ConstantPath.declaration_prefix`, the same reader the engine's declaration walks use, so a
|
|
538
|
+
# ROOTED header re-anchors here too. Appending the rendered name to the enclosing prefix wrote
|
|
539
|
+
# `class ::Foo` inside `module MyApp` out as `MyApp::Foo` — sig-gen's own copy of #708, and the one
|
|
540
|
+
# copy that persists its answer into a file the user keeps.
|
|
541
|
+
#
|
|
542
|
+
# `prefix` stays the nesting the header is WRITTEN in for {#record_superclass}: `::` re-anchors the
|
|
543
|
+
# declaration, not the superclass expression beside it, which Ruby still evaluates in the enclosing cref.
|
|
416
544
|
def descend_into_namespace?(node, prefix, out)
|
|
417
|
-
|
|
418
|
-
return false unless
|
|
545
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
546
|
+
return false unless child_prefix
|
|
419
547
|
|
|
420
|
-
full =
|
|
548
|
+
full = child_prefix.join("::")
|
|
421
549
|
@namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
|
|
422
550
|
record_superclass(node, full, prefix)
|
|
423
|
-
walk_namespace_body(node,
|
|
551
|
+
walk_namespace_body(node, child_prefix, out)
|
|
424
552
|
true
|
|
425
553
|
end
|
|
426
554
|
|
|
@@ -661,10 +789,22 @@ module Rigor
|
|
|
661
789
|
# `ObservationCollector`'s `.new` → `:initialize` routing), positional and keyword arg types come from the
|
|
662
790
|
# per-position / per-keyword union of observed types; otherwise every position keeps `untyped` per ADR-5
|
|
663
791
|
# clause 2.
|
|
664
|
-
|
|
792
|
+
#
|
|
793
|
+
# Issue #1436 — a constructor the class's own `sig/` already declares is `equivalent`, like any other
|
|
794
|
+
# declared member, unless the stub clears an `untyped` the declaration still carries: that is the
|
|
795
|
+
# `--params=observed` upgrade `--overwrite` applies ({Writer.fewer_untyped?}, the writer's own test).
|
|
796
|
+
# Without observations the stub is all `untyped` and never qualifies, so it is never listed as new.
|
|
797
|
+
def initialize_stub_candidate(path, def_node, class_name, scope)
|
|
665
798
|
params = def_node.parameters
|
|
666
799
|
rbs = "def initialize: (#{render_param_list(params, class_name, :initialize)})" \
|
|
667
800
|
"#{block_signature_suffix(params)} -> void"
|
|
801
|
+
method_def = lookup_existing_method(class_name, :initialize, :instance, scope&.environment, scope)
|
|
802
|
+
member = signature_member(method_def, class_name)
|
|
803
|
+
if member && !Writer.fewer_untyped?(rbs, member.location&.source.to_s)
|
|
804
|
+
declared = declares_void?(method_def) ? VOID_RETURN_RBS : build_declared_return(method_def)&.erase_to_rbs
|
|
805
|
+
return equivalent(path, def_node, class_name, :instance, Type::Combinator.untyped, declared)
|
|
806
|
+
end
|
|
807
|
+
|
|
668
808
|
build_candidate(
|
|
669
809
|
path: path, class_name: class_name, method_name: :initialize,
|
|
670
810
|
kind: :instance, classification: Classification::NEW_METHOD,
|
|
@@ -689,12 +829,12 @@ module Rigor
|
|
|
689
829
|
|
|
690
830
|
observations = arity_matched_observations(class_name, method_name, params)
|
|
691
831
|
parts = []
|
|
692
|
-
params.requireds.each_index { |i| parts << positional_type(observations, i, "") }
|
|
832
|
+
params.requireds.each_index { |i| parts << positional_type(observations, i, "", class_name) }
|
|
693
833
|
offset = params.requireds.size
|
|
694
|
-
params.optionals.each_index { |i| parts << positional_type(observations, offset + i, "?") }
|
|
834
|
+
params.optionals.each_index { |i| parts << positional_type(observations, offset + i, "?", class_name) }
|
|
695
835
|
parts << "*untyped" if params.rest
|
|
696
836
|
parts.concat(Array.new(params.posts.size, "untyped"))
|
|
697
|
-
params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
|
|
837
|
+
params.keywords.each { |kw| parts << render_keyword_param(kw, observations, class_name) }
|
|
698
838
|
parts.concat(keyword_rest_parts(params))
|
|
699
839
|
parts.join(", ")
|
|
700
840
|
end
|
|
@@ -744,15 +884,15 @@ module Rigor
|
|
|
744
884
|
|
|
745
885
|
# A bare union is a valid positional type in RBS (`(String | Integer, ?String | Integer)`), so unlike the
|
|
746
886
|
# return position it is not parenthesised.
|
|
747
|
-
def positional_type(observations, index, prefix)
|
|
887
|
+
def positional_type(observations, index, prefix, owner = nil)
|
|
748
888
|
types = observations.filter_map { |obs| obs.positional[index] }
|
|
749
|
-
"#{prefix}#{union_erase(types)}"
|
|
889
|
+
"#{prefix}#{union_erase(types, owner: owner)}"
|
|
750
890
|
end
|
|
751
891
|
|
|
752
|
-
def render_keyword_param(keyword, observations)
|
|
892
|
+
def render_keyword_param(keyword, observations, owner = nil)
|
|
753
893
|
optional_marker = keyword.is_a?(Prism::OptionalKeywordParameterNode) ? "?" : ""
|
|
754
894
|
types = observations.filter_map { |obs| obs.keyword[keyword.name] }
|
|
755
|
-
rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(types))
|
|
895
|
+
rendered = types.empty? ? "untyped" : paren_wrap_union(union_erase(types, owner: owner))
|
|
756
896
|
"#{optional_marker}#{keyword.name}: #{rendered}"
|
|
757
897
|
end
|
|
758
898
|
|
|
@@ -772,7 +912,15 @@ module Rigor
|
|
|
772
912
|
def classify_def(path, def_node, class_name, kind, scope_index)
|
|
773
913
|
return nil if visibility_excludes?(def_node, class_name, kind, scope_index)
|
|
774
914
|
return nil if initialize_excludes?(def_node, kind)
|
|
775
|
-
|
|
915
|
+
|
|
916
|
+
inline = inline_declarations.lookup(path, class_name, def_node.name, kind)
|
|
917
|
+
if inline
|
|
918
|
+
return skipped(path, def_node, class_name, kind, :inline_declared) if skip_inline_declared?
|
|
919
|
+
return inline_def_candidate(path, def_node, class_name, kind, scope_index, inline) if inline.declared?
|
|
920
|
+
end
|
|
921
|
+
if non_trivial_initialize?(def_node, kind)
|
|
922
|
+
return initialize_stub_candidate(path, def_node, class_name, scope_index[def_node])
|
|
923
|
+
end
|
|
776
924
|
|
|
777
925
|
inferred = infer_return_type(def_node, scope_index)
|
|
778
926
|
return skipped(path, def_node, class_name, kind, :untyped_return) if inferred.nil? || dynamic_top?(inferred)
|
|
@@ -787,6 +935,242 @@ module Rigor
|
|
|
787
935
|
end
|
|
788
936
|
end
|
|
789
937
|
|
|
938
|
+
# ADR-112 WD4 — the inline declarations of every analysed file, read once per run. See
|
|
939
|
+
# {InlineDeclarations} for why the synthesized text rather than the environment.
|
|
940
|
+
def inline_declarations
|
|
941
|
+
@inline_declarations ||= InlineDeclarations.build(@environment)
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
def skip_inline_declared?
|
|
945
|
+
@configuration.sig_gen_inline_declared == :skip
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
# ADR-112 WD4 — a `def` the author declared inline is written from that declaration, as rbs-inline reads it,
|
|
949
|
+
# not from what the body infers: `rigor check` reads the `sig/` member beside the inline one (ADR-112 WD5),
|
|
950
|
+
# so a line built from inference (`untyped` parameters, a return the body happens to prove) would override
|
|
951
|
+
# or contradict the contract the author wrote beside the code.
|
|
952
|
+
#
|
|
953
|
+
# The one inferred part is a return the author left unwritten (`# @rbs name: String`, no `return:`) —
|
|
954
|
+
# ADR-107's mixed provenance, parameters authored and return generated — filled from the body; a body that
|
|
955
|
+
# types as `untyped` there is skipped exactly as an undeclared one is. `initialize` returns `void` by
|
|
956
|
+
# definition, whatever its last expression is.
|
|
957
|
+
def inline_def_candidate(path, def_node, class_name, kind, scope_index, inline)
|
|
958
|
+
types = inline.method_types
|
|
959
|
+
return_defaulted = inline.return_inferred
|
|
960
|
+
if return_defaulted && initialize_def?(def_node, kind)
|
|
961
|
+
void = ::RBS::Types::Bases::Void.new(location: nil)
|
|
962
|
+
types = types.map { |mt| defaulted_return?(mt) ? with_return_type(mt, void) : mt }
|
|
963
|
+
return_defaulted = false
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
scope = scope_index[def_node]
|
|
967
|
+
method_def = lookup_existing_method(class_name, def_node.name, kind, scope&.environment, scope)
|
|
968
|
+
head = "#{method_def_prefix(class_name, def_node.name, kind)}#{def_node.name}: "
|
|
969
|
+
if return_defaulted
|
|
970
|
+
return mixed_provenance_candidate(path, def_node, class_name, kind, scope_index, inline, method_def, head)
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
inline_candidate(path, class_name, def_node.name, kind, head + types.join(" | "), inline, method_def)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# A parameter-only annotation (`# @rbs name: String`, no `return:`): parameters authored, return inferred.
|
|
977
|
+
# Only the authored half is weighed against `sig/`. When the parameters differ the method is refused, even
|
|
978
|
+
# under `--overwrite` — the body is typed under the parameters the environment binds, which are `sig/`'s
|
|
979
|
+
# once it declares the member, so a return inferred for the new line would describe parameters about to
|
|
980
|
+
# change. When they match, the return is an ordinary inferred proposal: {#compare_against_declared}
|
|
981
|
+
# leaves a declared `void`, a deliberately wider type and a declined literal alone, and proposes a
|
|
982
|
+
# strictly narrower return as `tighter-return`, spelled with the authored parameters.
|
|
983
|
+
def mixed_provenance_candidate(path, def_node, class_name, kind, scope_index, inline, method_def, head) # rubocop:disable Metrics/ParameterLists
|
|
984
|
+
types = inline.method_types
|
|
985
|
+
existing = signature_member(method_def, class_name)
|
|
986
|
+
if existing && parameters_differ?(existing, types, def_node.name, class_name)
|
|
987
|
+
return skipped(path, def_node, class_name, kind, :inline_differs)
|
|
988
|
+
end
|
|
989
|
+
|
|
990
|
+
inferred, overloads = inferred_overloads(def_node, scope_index, class_name, types)
|
|
991
|
+
return skipped(path, def_node, class_name, kind, :untyped_return) if inferred.nil?
|
|
992
|
+
|
|
993
|
+
rbs = head + overloads.join(" | ")
|
|
994
|
+
if existing && annotations_present?(existing, inline) && existing_types(existing, def_node.name)
|
|
995
|
+
return compare_against_declared(path, def_node, class_name, kind, inferred, method_def, rendered: rbs)
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
inline_candidate(path, class_name, def_node.name, kind, rbs, inline, method_def, inferred)
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
# `[inferred return, overload strings]`, or `[nil, nil]` when the body types as `untyped`.
|
|
1002
|
+
def inferred_overloads(def_node, scope_index, class_name, types)
|
|
1003
|
+
inferred = infer_return_type(def_node, scope_index)
|
|
1004
|
+
return [nil, nil] if inferred.nil? || dynamic_top?(inferred)
|
|
1005
|
+
|
|
1006
|
+
[inferred, with_defaulted_return(types, paren_wrap_union(elaborated_rbs(inferred, owner: class_name)))]
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1009
|
+
# Whether a `sig/` member's overloads, returns aside, differ from the inline declaration's. Such a member is
|
|
1010
|
+
# refused even under `--overwrite` when the inline return is to be inferred (see {#inline_def_candidate});
|
|
1011
|
+
# deleting the `sig/` member lets the next run infer it under the inline parameters.
|
|
1012
|
+
def parameters_differ?(existing, types, method_name, class_name)
|
|
1013
|
+
written = existing_types(existing, method_name)
|
|
1014
|
+
return false if written.nil?
|
|
1015
|
+
|
|
1016
|
+
!equivalence(class_name).same_parameters?(written, types)
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
def initialize_def?(def_node, kind)
|
|
1020
|
+
kind == :instance && def_node.name == :initialize
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
def defaulted_return?(method_type)
|
|
1024
|
+
method_type.type.return_type.is_a?(::RBS::Types::Bases::Any)
|
|
1025
|
+
end
|
|
1026
|
+
|
|
1027
|
+
def with_return_type(method_type, return_type)
|
|
1028
|
+
method_type.update(type: method_type.type.with_return_type(return_type))
|
|
1029
|
+
end
|
|
1030
|
+
|
|
1031
|
+
# A class name no project declares, standing in for the inferred return while `RBS::MethodType#to_s` spells
|
|
1032
|
+
# the rest of an overload, so the return itself keeps sig-gen's own spelling (`[Float, String]`, a folded
|
|
1033
|
+
# alias) rather than being re-spelled through a parse.
|
|
1034
|
+
INFERRED_RETURN_PLACEHOLDER = "Rigor__SigGenInferredReturn"
|
|
1035
|
+
private_constant :INFERRED_RETURN_PLACEHOLDER
|
|
1036
|
+
|
|
1037
|
+
# Each overload whose return rbs-inline defaulted to `untyped` takes `returned` (already spelled for a
|
|
1038
|
+
# return position); the rest stay as written.
|
|
1039
|
+
def with_defaulted_return(method_types, returned)
|
|
1040
|
+
method_types.map do |method_type|
|
|
1041
|
+
defaulted_return?(method_type) ? spell_with_return(method_type, returned) : method_type.to_s
|
|
1042
|
+
end
|
|
1043
|
+
end
|
|
1044
|
+
|
|
1045
|
+
# One overload spelled with `returned` (already spelled for a return position) as its return.
|
|
1046
|
+
def spell_with_return(method_type, returned)
|
|
1047
|
+
placeholder = ::RBS::Parser.parse_type(INFERRED_RETURN_PLACEHOLDER)
|
|
1048
|
+
with_return_type(method_type, placeholder).to_s.sub(/-> #{INFERRED_RETURN_PLACEHOLDER}\z/o, "-> #{returned}")
|
|
1049
|
+
end
|
|
1050
|
+
|
|
1051
|
+
# Classifies an inline-declared member's `rbs` line against what `sig/` already says for it (ADR-112 WD4, as
|
|
1052
|
+
# decided on #1422):
|
|
1053
|
+
#
|
|
1054
|
+
# - nothing on this class itself (or only the inline declaration) — `new-method`;
|
|
1055
|
+
# - the same types, and every annotation the author wrote inline — `equivalent`;
|
|
1056
|
+
# - anything else — the two declarations disagree, and neither is presumed right. Without `--overwrite`
|
|
1057
|
+
# the member is REFUSED (`sig.skipped.inline-differs`): nothing is written, and `--write` / `--check`
|
|
1058
|
+
# exit 1 until a person reconciles them. With `--overwrite` it is `inline-overwrite`, which replaces the
|
|
1059
|
+
# whole `sig/` member with the inline line — never a mix of the two, whose slots (and type variables)
|
|
1060
|
+
# need not correspond.
|
|
1061
|
+
def inline_candidate(path, class_name, method_name, kind, rbs, inline, method_def, inferred = nil) # rubocop:disable Metrics/ParameterLists
|
|
1062
|
+
fields = { path: path, class_name: class_name, method_name: method_name, kind: kind,
|
|
1063
|
+
inferred_return: inferred }
|
|
1064
|
+
existing = signature_member(method_def, class_name)
|
|
1065
|
+
if existing.nil?
|
|
1066
|
+
build_candidate(**fields, classification: Classification::NEW_METHOD, rbs: rbs,
|
|
1067
|
+
declared_annotations: inline.annotation_lines)
|
|
1068
|
+
elsif current_copy?(existing, rbs, inline, method_name, class_name)
|
|
1069
|
+
build_candidate(**fields, classification: Classification::EQUIVALENT)
|
|
1070
|
+
elsif @overwrite
|
|
1071
|
+
# An `attr_*` member is replaced in its own spelling, by the inline attribute's.
|
|
1072
|
+
line = attr_member?(existing) && inline.attr_line ? inline.attr_line : rbs
|
|
1073
|
+
build_candidate(**fields, classification: Classification::INLINE_OVERWRITE, rbs: line,
|
|
1074
|
+
declared_annotations: inline.annotation_lines,
|
|
1075
|
+
declared_rbs: squish(existing.location&.source))
|
|
1076
|
+
else
|
|
1077
|
+
build_candidate(**fields, classification: Classification::SKIPPED, skip_reason: :inline_differs)
|
|
1078
|
+
end
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
# The member a project `.rbs` declares for `class_name` itself — not an ancestor's, and not the inline
|
|
1082
|
+
# declaration, whose buffer is the synthesizer's `virtual:` one. An `overloading?` member (`def x: … |
|
|
1083
|
+
# ...`) is not a counterpart either: it adds overloads to the inline declaration rather than restating
|
|
1084
|
+
# it, and sig-gen leaves it alone.
|
|
1085
|
+
def signature_member(method_def, class_name)
|
|
1086
|
+
return nil if method_def.nil?
|
|
1087
|
+
|
|
1088
|
+
method_def.defs.filter_map do |type_def|
|
|
1089
|
+
next unless type_def.defined_in.to_s.delete_prefix("::") == class_name
|
|
1090
|
+
|
|
1091
|
+
member = type_def.member
|
|
1092
|
+
next if member.respond_to?(:overloading?) && member.overloading?
|
|
1093
|
+
|
|
1094
|
+
member unless member.location&.buffer&.name.to_s.start_with?("virtual:")
|
|
1095
|
+
end.first
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
# The two declarations are compared as types ({DeclarationEquivalence}): parameter names, union spelling
|
|
1099
|
+
# and a leading `::` that resolves to the same constant do not count; overload order does. The copy must
|
|
1100
|
+
# also carry every annotation the author wrote inline. A `sig/` member whose text is unavailable is left
|
|
1101
|
+
# as it is rather than refused for good.
|
|
1102
|
+
def current_copy?(member, rbs, inline, method_name, class_name)
|
|
1103
|
+
written = existing_types(member, method_name)
|
|
1104
|
+
mine = member_types(rbs)
|
|
1105
|
+
return true if written.nil? || mine.nil?
|
|
1106
|
+
|
|
1107
|
+
equivalence(class_name).same?(written, mine) && annotations_present?(member, inline)
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
def annotations_present?(member, inline)
|
|
1111
|
+
(inline.annotations - member.annotations.map { |annotation| annotation.string.to_s.strip }).empty?
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
def attr_member?(member)
|
|
1115
|
+
member.is_a?(::RBS::AST::Members::AttrReader) || member.is_a?(::RBS::AST::Members::AttrWriter) ||
|
|
1116
|
+
member.is_a?(::RBS::AST::Members::AttrAccessor)
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
# The overloads a `sig/` member declares for `method_name`: a `def`'s, parsed from its own text; an
|
|
1120
|
+
# attribute's reader (`() -> T`) or writer (`(T) -> T`), from its type.
|
|
1121
|
+
def existing_types(member, method_name)
|
|
1122
|
+
return member_types(member.location&.source) if member.is_a?(::RBS::AST::Members::MethodDefinition)
|
|
1123
|
+
return nil unless attr_member?(member)
|
|
1124
|
+
|
|
1125
|
+
shape = method_name.to_s.end_with?("=") ? "(#{member.type}) -> #{member.type}" : "() -> #{member.type}"
|
|
1126
|
+
[::RBS::Parser.parse_method_type(shape)]
|
|
1127
|
+
rescue ::RBS::BaseError
|
|
1128
|
+
nil
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def equivalence(class_name)
|
|
1132
|
+
@equivalence ||= {}
|
|
1133
|
+
@equivalence[class_name] ||= DeclarationEquivalence.new(resolve: type_name_resolver(class_name))
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
# A type name as written, resolved the way RBS resolves it inside `class_name`'s declaration: an absolute
|
|
1137
|
+
# `::Foo` is `Foo`; a relative `Foo` is the innermost `…::Foo` the environment knows, so inside `module NS`
|
|
1138
|
+
# it is `NS::Foo` when `NS` declares one, and `::Foo` only when it does not.
|
|
1139
|
+
def type_name_resolver(class_name)
|
|
1140
|
+
nesting = class_name.split("::")
|
|
1141
|
+
lambda do |name|
|
|
1142
|
+
next name.delete_prefix("::") if name.start_with?("::")
|
|
1143
|
+
|
|
1144
|
+
nesting.size.downto(1).map { |n| (nesting.first(n) + [name]).join("::") }
|
|
1145
|
+
.find { |candidate| class_known?(candidate) } || name
|
|
1146
|
+
end
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
def class_known?(name)
|
|
1150
|
+
@class_known ||= {}
|
|
1151
|
+
@class_known.fetch(name) { @class_known[name] = Reflection.rbs_class_known?(name, environment: @environment) }
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
# A member line's overloads, parsed with type names unresolved so the two sides compare as written.
|
|
1155
|
+
def member_types(source)
|
|
1156
|
+
member_overloads(source)&.map(&:method_type)
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
# The same, with each overload's own annotations (`%a{pure} (Integer) -> String`).
|
|
1160
|
+
def member_overloads(source)
|
|
1161
|
+
return nil if source.nil?
|
|
1162
|
+
|
|
1163
|
+
_buffer, _directives, decls = ::RBS::Parser.parse_signature("class Rigor__SigGenProbe\n#{source}\nend\n")
|
|
1164
|
+
parsed = decls.first&.members&.first
|
|
1165
|
+
parsed.is_a?(::RBS::AST::Members::MethodDefinition) ? parsed.overloads : nil
|
|
1166
|
+
rescue ::RBS::BaseError
|
|
1167
|
+
nil
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
def squish(text)
|
|
1171
|
+
text.to_s.gsub(/\s+/, " ").strip
|
|
1172
|
+
end
|
|
1173
|
+
|
|
790
1174
|
# Mirrors the `def.return-type-mismatch` rule's body-type extraction: type the implicit-return expression
|
|
791
1175
|
# under the scope the indexer associated with the body. The parameter bindings (typed `untyped` per the
|
|
792
1176
|
# indexer's default) come from `with_local` inside `StatementEvaluator`; the result is the carrier the
|
|
@@ -796,9 +1180,10 @@ module Rigor
|
|
|
796
1180
|
# types with the implicit-return expression's type. The earlier MVP only typed the implicit-return path,
|
|
797
1181
|
# which routinely produced single-branch artefacts like `parse_options: () -> nil` (the actual runtime
|
|
798
1182
|
# return is `options | nil`) or `find: () -> V` (actually `V | nil` via `return nil unless ...`). The walk
|
|
799
|
-
#
|
|
800
|
-
#
|
|
801
|
-
#
|
|
1183
|
+
# includes a `return` inside an ordinary block, which exits the method, and excludes the
|
|
1184
|
+
# {Rigor::Inference::ReturnBarrier} bodies — nested `def`, lambda, `define_method` — whose returns belong to a
|
|
1185
|
+
# different method (issue #1382). Delegates to {Rigor::Inference::DefReturnTyper} — the same body-typing +
|
|
1186
|
+
# explicit-return-union the `rigor annotate` def-line annotator uses.
|
|
802
1187
|
def infer_return_type(def_node, scope_index)
|
|
803
1188
|
Inference::DefReturnTyper.call(def_node, scope_index)
|
|
804
1189
|
end
|
|
@@ -836,8 +1221,21 @@ module Rigor
|
|
|
836
1221
|
)
|
|
837
1222
|
end
|
|
838
1223
|
|
|
839
|
-
|
|
1224
|
+
# The RBS spelling carried for a `void`-declared method, so `--diff` shows the author's own word rather
|
|
1225
|
+
# than the `top` the translator would erase it to.
|
|
1226
|
+
VOID_RETURN_RBS = "void"
|
|
1227
|
+
private_constant :VOID_RETURN_RBS
|
|
1228
|
+
|
|
1229
|
+
# @param rendered — the line a proposal is spelled as when it is not the ordinary inferred one: a
|
|
1230
|
+
# parameter-only inline annotation keeps its authored parameters ({#mixed_provenance_candidate}).
|
|
1231
|
+
def compare_against_declared(path, def_node, class_name, kind, inferred, method_def, rendered: nil)
|
|
1232
|
+
return equivalent(path, def_node, class_name, kind, inferred, VOID_RETURN_RBS) if declares_void?(method_def)
|
|
1233
|
+
|
|
840
1234
|
declared = build_declared_return(method_def)
|
|
1235
|
+
if declared_untyped?(declared)
|
|
1236
|
+
return declared_untyped_candidate(path, def_node, class_name, kind, inferred, method_def, rendered: rendered)
|
|
1237
|
+
end
|
|
1238
|
+
|
|
841
1239
|
declared_rbs = declared&.erase_to_rbs
|
|
842
1240
|
inferred_rbs = inferred.erase_to_rbs
|
|
843
1241
|
|
|
@@ -845,10 +1243,21 @@ module Rigor
|
|
|
845
1243
|
return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
|
|
846
1244
|
end
|
|
847
1245
|
|
|
848
|
-
unless tighter?(declared, inferred) && !
|
|
1246
|
+
unless tighter?(declared, inferred) && !literal_decline?(inferred, def_node)
|
|
849
1247
|
return equivalent(path, def_node, class_name, kind, inferred, declared_rbs)
|
|
850
1248
|
end
|
|
851
1249
|
|
|
1250
|
+
tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, declared_rbs, rendered)
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# Both tightening branches end here. The proposal is the declaration with its return replaced
|
|
1254
|
+
# ({#tighter_return_line}); when there is no single declared overload to replace it in, the method is
|
|
1255
|
+
# `equivalent` — the declaration stands, as it does for any narrowing the generator declines. `declared_rbs`
|
|
1256
|
+
# carries the class's own declaration so `--diff` and `--check` show the line the proposal replaces.
|
|
1257
|
+
def tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, declared_return, rendered) # rubocop:disable Metrics/ParameterLists
|
|
1258
|
+
line = rendered || tighter_return_line(def_node, method_def, class_name, kind, inferred)
|
|
1259
|
+
return equivalent(path, def_node, class_name, kind, inferred, declared_return) if line.nil?
|
|
1260
|
+
|
|
852
1261
|
build_candidate(
|
|
853
1262
|
path: path,
|
|
854
1263
|
class_name: class_name,
|
|
@@ -856,11 +1265,111 @@ module Rigor
|
|
|
856
1265
|
kind: kind,
|
|
857
1266
|
classification: Classification::TIGHTER_RETURN,
|
|
858
1267
|
inferred_return: inferred,
|
|
859
|
-
declared_return_rbs:
|
|
860
|
-
|
|
1268
|
+
declared_return_rbs: declared_return,
|
|
1269
|
+
declared_rbs: declared_line(method_def, class_name),
|
|
1270
|
+
rbs: line
|
|
861
1271
|
)
|
|
862
1272
|
end
|
|
863
1273
|
|
|
1274
|
+
# Issue #1436 — a `tighter-return` changes the return and only the return. It used to be rendered like a
|
|
1275
|
+
# new method, from the `def`'s runtime shape: every parameter `untyped`, `(?)` spelled out as
|
|
1276
|
+
# `(*untyped)`, names and block types dropped. Under `--overwrite` that replaced a declared `(Integer x)`
|
|
1277
|
+
# with `(untyped)` — a change to which calls the signature accepts, made by a proposal that claims to be
|
|
1278
|
+
# about the return. The class's own declaration is kept whole instead: its visibility, the overload's
|
|
1279
|
+
# annotations, type parameters, parameters and block, with only the return replaced.
|
|
1280
|
+
#
|
|
1281
|
+
# Three shapes are declined (`nil`):
|
|
1282
|
+
# - several overloads. The body is typed once, so the inferred return covers every overload together and
|
|
1283
|
+
# says nothing about which overload returns what: collapsing them into one line would drop the
|
|
1284
|
+
# overloads, and giving each the joint return would widen the overloads the author wrote narrower.
|
|
1285
|
+
# - a name the class's own `sig/` spells as an `alias`. Its parameters are the aliased method's, and a
|
|
1286
|
+
# proposal would read as a rewrite of that method's line rather than of the alias. An alias only an
|
|
1287
|
+
# ancestor declares takes the override path below, like any other ancestor declaration.
|
|
1288
|
+
# - nothing else: when only an ancestor declares the method, the proposal is a new override on this
|
|
1289
|
+
# class, and is rendered from the `def`'s own shape like any new method. The ancestor's parameters
|
|
1290
|
+
# describe the ancestor's `def`, not this one, which may take other arguments (ADR-14 clause 2).
|
|
1291
|
+
def tighter_return_line(def_node, method_def, class_name, kind, inferred)
|
|
1292
|
+
member = signature_member(method_def, class_name)
|
|
1293
|
+
return render_rbs_line(def_node, inferred, class_name, kind) if member.nil?
|
|
1294
|
+
return nil if method_def.alias_of
|
|
1295
|
+
|
|
1296
|
+
overload = sole_declared_overload(member, method_def, def_node.name)
|
|
1297
|
+
return nil if overload.nil?
|
|
1298
|
+
|
|
1299
|
+
returned = paren_wrap_union(elaborated_rbs(inferred, owner: class_name))
|
|
1300
|
+
annotations = overload.annotations.map { |a| "#{a.location&.source || "%a{#{a.string}}"} " }.join
|
|
1301
|
+
visibility = member.respond_to?(:visibility) && member.visibility ? "#{member.visibility} " : ""
|
|
1302
|
+
"#{visibility}#{method_def_prefix(class_name, def_node.name, kind)}#{def_node.name}: " \
|
|
1303
|
+
"#{annotations}#{spell_with_return(overload.method_type, returned)}"
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
# The one overload the class's own `sig/` member declares, parsed from its text with its annotations so it
|
|
1307
|
+
# keeps the spelling the author wrote (`Integer`, not the resolved `::Integer`). An attribute declares one
|
|
1308
|
+
# implicitly and carries no overload annotations.
|
|
1309
|
+
def sole_declared_overload(member, method_def, method_name)
|
|
1310
|
+
return nil unless method_def.method_types.size == 1
|
|
1311
|
+
|
|
1312
|
+
overloads =
|
|
1313
|
+
if member.is_a?(::RBS::AST::Members::MethodDefinition)
|
|
1314
|
+
member_overloads(member.location&.source)
|
|
1315
|
+
else
|
|
1316
|
+
existing_types(member, method_name)&.map do |method_type|
|
|
1317
|
+
::RBS::AST::Members::MethodDefinition::Overload.new(method_type: method_type, annotations: [])
|
|
1318
|
+
end
|
|
1319
|
+
end
|
|
1320
|
+
overloads&.size == 1 ? overloads.first : nil
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
# The class's own declaration, as the `-` line of `--diff` and `--check` shows it. A method only an
|
|
1324
|
+
# ancestor declares has none: the proposal adds an override rather than replacing a line.
|
|
1325
|
+
def declared_line(method_def, class_name)
|
|
1326
|
+
source = signature_member(method_def, class_name)&.location&.source
|
|
1327
|
+
source && squish(source)
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
# Issue #836 — a declared `void` is return INTENT, never a wide value type waiting to be narrowed, so it
|
|
1331
|
+
# is not compared against the body at all.
|
|
1332
|
+
#
|
|
1333
|
+
# `void` says the return value is not part of the method's contract. No synthesis can produce it: a type
|
|
1334
|
+
# built from a body is always the type of the body's last expression, so `void` exists only on the
|
|
1335
|
+
# CHECKING side, against a declaration the author wrote. That puts it in the same position ADR-107 gives
|
|
1336
|
+
# a parameter type — authored intent inference cannot derive — and proposing the body's value for it is
|
|
1337
|
+
# a category error rather than a false positive to tune.
|
|
1338
|
+
#
|
|
1339
|
+
# Mechanically the misreading came from the translator: `docs/type-specification/special-types.md`
|
|
1340
|
+
# § `void` records that the engine maps RBS `void` to `top`, `Type::Top` accepts everything, so
|
|
1341
|
+
# {#tighter?} answered "yes" for every `void`-declared method whose body happens to return a typed
|
|
1342
|
+
# value. Seven of the fifteen tighter-returns in Rigor's own `sig/` were this shape, and the ADR-107 G3
|
|
1343
|
+
# provenance gate inherited the misclassification with them.
|
|
1344
|
+
#
|
|
1345
|
+
# ANY overload declaring `void` is enough. One proposal is rendered for the whole method, so an overload
|
|
1346
|
+
# set that mixes `void` with a value return still has an author saying "not part of the contract" about
|
|
1347
|
+
# the return sig-gen would rewrite.
|
|
1348
|
+
def declares_void?(method_def)
|
|
1349
|
+
method_def.method_types.any? { |mt| mt.type.return_type.is_a?(RBS::Types::Bases::Void) }
|
|
1350
|
+
end
|
|
1351
|
+
|
|
1352
|
+
# Issue #995 — a declared `untyped` (RBS `Bases::Any`, translated by
|
|
1353
|
+
# {Inference::RbsTypeTranslator} to `Type::Dynamic`) says nothing at all, unlike `void` (#836), which is
|
|
1354
|
+
# the author's word that the value is not part of the contract. `tighter?`'s acceptance check cannot
|
|
1355
|
+
# tell the two apart — `untyped` accepts and is accepted by everything, so its backward check, which
|
|
1356
|
+
# exists to refuse a declaration that carries real information the inferred form would narrow, also
|
|
1357
|
+
# refused this one — and the candidate fell into `equivalent`: neither emitted nor counted among the
|
|
1358
|
+
# skips. ADR-93's inline `# @rbs param: Type` annotation (no return clause) synthesizes exactly this
|
|
1359
|
+
# spelling for every method it documents, so the silent drop hit the parameter-annotated, return-bare
|
|
1360
|
+
# shape ADR-14 exists to help.
|
|
1361
|
+
def declared_untyped?(declared)
|
|
1362
|
+
declared.is_a?(Type::Dynamic)
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
# Proposed without {#compare_against_declared}'s tightening guards: a declared `untyped` return carries no
|
|
1366
|
+
# information to weigh {#tighter?} or {#literal_decline?} against, the same position a method with no
|
|
1367
|
+
# declaration at all is in. The line is built as for any tightening ({#tighter_return_line}), and
|
|
1368
|
+
# `declared_return_rbs` carries `"untyped"` so the `[tighter, was: untyped]` print tag says one existed.
|
|
1369
|
+
def declared_untyped_candidate(path, def_node, class_name, kind, inferred, method_def, rendered: nil)
|
|
1370
|
+
tighter_return_candidate(path, def_node, class_name, kind, inferred, method_def, "untyped", rendered)
|
|
1371
|
+
end
|
|
1372
|
+
|
|
864
1373
|
def build_declared_return(method_def)
|
|
865
1374
|
translated = method_def.method_types.filter_map { |mt| translate_method_type_return(mt) }
|
|
866
1375
|
return nil if translated.empty?
|
|
@@ -868,10 +1377,19 @@ module Rigor
|
|
|
868
1377
|
translated.size == 1 ? translated.first : Type::Combinator.union(*translated)
|
|
869
1378
|
end
|
|
870
1379
|
|
|
1380
|
+
# `alias_expander:` matters here as much as it does at any dispatch site (see
|
|
1381
|
+
# {Inference::RbsTypeTranslator}'s own doc comment): without it, a declared return spelled as a
|
|
1382
|
+
# project alias (`Type::t`, `Environment::ordering`, `Inference::closure_classification`) degrades
|
|
1383
|
+
# to `Dynamic[Top]` exactly like a bare `untyped` would, and {#declared_untyped?} cannot tell an
|
|
1384
|
+
# author's meaningful alias apart from an absent declaration. That collapsed a well-typed, already
|
|
1385
|
+
# exact declaration into a spurious {#declared_untyped_candidate} the moment #995 made "declared
|
|
1386
|
+
# untyped" a proposable position — the ADR-107 G3 gate caught it on this repo's own `sig/` tree
|
|
1387
|
+
# (`Environment#class_ordering`'s `-> ordering` and every `-> Type::t` return in `type.rbs` /
|
|
1388
|
+
# `inference.rbs` / `scope.rbs`).
|
|
871
1389
|
def translate_method_type_return(method_type)
|
|
872
1390
|
Inference::RbsTypeTranslator.translate(
|
|
873
1391
|
method_type.type.return_type,
|
|
874
|
-
self_type: nil, instance_type: nil, type_vars: {}
|
|
1392
|
+
self_type: nil, instance_type: nil, type_vars: {}, alias_expander: @environment&.rbs_loader
|
|
875
1393
|
)
|
|
876
1394
|
rescue StandardError
|
|
877
1395
|
nil
|
|
@@ -943,6 +1461,48 @@ module Rigor
|
|
|
943
1461
|
inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
|
|
944
1462
|
end
|
|
945
1463
|
|
|
1464
|
+
# The two reasons a `Type::Constant` the body proves is not the method's contract. Both say the same
|
|
1465
|
+
# thing from opposite ends: the precision is real about this body and says nothing about the promise
|
|
1466
|
+
# the method makes to its callers.
|
|
1467
|
+
def literal_decline?(inferred, def_node)
|
|
1468
|
+
computed_literal_tightening?(inferred, def_node) || pins_literal_over_declaration?(inferred)
|
|
1469
|
+
end
|
|
1470
|
+
|
|
1471
|
+
# Issue #837 — a proposal that erases to an RBS LITERAL type never tightens an existing declaration.
|
|
1472
|
+
#
|
|
1473
|
+
# `Type::Top#describe` really does return `"top"`, but `describe` is the surface every `Rigor::Type::*`
|
|
1474
|
+
# class implements and every one of them declares `String`. The declared nominal is the author's
|
|
1475
|
+
# abstraction over the body, and the literal is the implementation detail that abstraction deliberately
|
|
1476
|
+
# hides — the same reading #836 gave a declared `void`. ADR-107 § Decision puts both on the intent
|
|
1477
|
+
# side: no synthesis produces an abstraction, because a type built from a body is always the type of
|
|
1478
|
+
# the body's last expression, so the choice to be wider than the body only ever exists in a declaration
|
|
1479
|
+
# someone wrote.
|
|
1480
|
+
#
|
|
1481
|
+
# The generator already answered this way whenever the declaration happened to be a UNION.
|
|
1482
|
+
# `Configuration.discover` declares `String?` against a body proving `".rigor.dist.yml" |
|
|
1483
|
+
# ".rigor.yml" | nil` and {#loses_declared_union_member?} refuses it, because no literal covers the
|
|
1484
|
+
# declared `String`. Whether the author wrote `String` or `String?` is not a decision about literals,
|
|
1485
|
+
# so the two shapes now answer alike.
|
|
1486
|
+
#
|
|
1487
|
+
# Scoped by the ERASURE, not by the carrier. `Type::Constant` also carries values RBS has no literal
|
|
1488
|
+
# spelling for (`Float`, `Regexp`, `Set`, `Pathname`, `Date`, …) and those erase to the class name, so
|
|
1489
|
+
# `def pi: () -> Numeric` against a body of `3.14` still proposes the ordinary nominal `Float`. A union
|
|
1490
|
+
# is refused when ANY member is a literal: a mixed `("a" | Float)` pins `"a"` just as a bare one does.
|
|
1491
|
+
# A literal nested inside a shape carrier (`[1, 2]`, a `HashShape` value) is a different question,
|
|
1492
|
+
# already answered for the collection declarations that matter by {#narrows_collection_to_shape?}.
|
|
1493
|
+
def pins_literal_over_declaration?(inferred)
|
|
1494
|
+
members = inferred.is_a?(Type::Union) ? inferred.members : [inferred]
|
|
1495
|
+
members.any? { |member| rbs_literal?(member) }
|
|
1496
|
+
end
|
|
1497
|
+
|
|
1498
|
+
# `Type::Constant#erase_to_rbs` spells the values RBS writes as literal types (`true` / `false` /
|
|
1499
|
+
# `nil`, an Integer, a Symbol, a String) as that literal and every other carrier as its value's class
|
|
1500
|
+
# name. Asking which of the two branches it took keeps the list of literal-spellable classes from
|
|
1501
|
+
# forking a second copy into this file.
|
|
1502
|
+
def rbs_literal?(type)
|
|
1503
|
+
type.is_a?(Type::Constant) && type.erase_to_rbs != type.value.class.name
|
|
1504
|
+
end
|
|
1505
|
+
|
|
946
1506
|
# Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
|
|
947
1507
|
# `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
|
|
948
1508
|
# `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` — the carrier knows the empty-tuple cardinality
|
|
@@ -950,6 +1510,11 @@ module Rigor
|
|
|
950
1510
|
# signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
|
|
951
1511
|
# Constant"; in that case the precision came from inference over an internal computation, not the
|
|
952
1512
|
# author's contract, so refuse to tighten.
|
|
1513
|
+
#
|
|
1514
|
+
# Since #837 the literal-ERASING half of this is refused whichever expression produced it. What stays
|
|
1515
|
+
# this guard's own is the `Constant` that erases to a class name: `def average = @total / 2.0` folds to
|
|
1516
|
+
# `Constant<2.5>` and would propose the perfectly ordinary `Float`, which is still the fold's answer
|
|
1517
|
+
# about one body rather than the method's contract.
|
|
953
1518
|
def computed_literal_tightening?(inferred, def_node)
|
|
954
1519
|
return false unless inferred.is_a?(Type::Constant)
|
|
955
1520
|
|
|
@@ -1004,15 +1569,54 @@ module Rigor
|
|
|
1004
1569
|
params = def_node.parameters
|
|
1005
1570
|
head = "(#{render_param_list(params, class_name, def_node.name)})#{block_signature_suffix(params)}"
|
|
1006
1571
|
prefix = method_def_prefix(class_name, def_node.name, kind)
|
|
1007
|
-
"#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
|
|
1572
|
+
"#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred, owner: class_name))}"
|
|
1008
1573
|
end
|
|
1009
1574
|
|
|
1010
1575
|
# Routes the inferred carrier through {TypeElaborator} so bare generic nominals (`Array` / `Hash` / `Set`
|
|
1011
1576
|
# / `Range` / `Enumerable`) get their `untyped` type parameters filled in before erasing to RBS. The
|
|
1012
1577
|
# elaborator consults the class's RBS-declared type-parameter list via
|
|
1013
1578
|
# `Reflection.class_type_param_names`.
|
|
1014
|
-
|
|
1015
|
-
|
|
1579
|
+
# `owner:` is the class or module the rendered member belongs to. {AliasIndex} needs it to judge whether
|
|
1580
|
+
# a matching alias is one this reader would expect to see; a call site that cannot supply it renders the
|
|
1581
|
+
# type unfolded, which is the same output as before #1002.
|
|
1582
|
+
def elaborated_rbs(type, owner: nil)
|
|
1583
|
+
alias_index.fold(TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs, owner)
|
|
1584
|
+
end
|
|
1585
|
+
|
|
1586
|
+
# Issue #1002 — the reverse of {#translate_method_type_return}'s `alias_expander:`. Folding happens here,
|
|
1587
|
+
# at the single seam every rendered type passes through (`render_rbs_line`, `union_erase` for parameter
|
|
1588
|
+
# and ivar positions, the attr path), so `--print`, `--diff`, `--write` and `--format=json` cannot drift
|
|
1589
|
+
# from one another: they all read the `rbs` string a candidate was built with. Deliberately NOT in
|
|
1590
|
+
# {Type::Union#erase_to_rbs} — that carrier method is the type model's RBS surface, shared with
|
|
1591
|
+
# diagnostics, `rigor type-of` and the engine's own comparisons, and none of those should start naming a
|
|
1592
|
+
# project alias. Sig-gen's rendering preference is a CLI concern and stays on the CLI side.
|
|
1593
|
+
def alias_index
|
|
1594
|
+
@alias_index ||= AliasIndex.build(
|
|
1595
|
+
environment: @environment,
|
|
1596
|
+
signature_paths: project_signature_paths,
|
|
1597
|
+
excluded_paths: plugin_signature_paths
|
|
1598
|
+
)
|
|
1599
|
+
end
|
|
1600
|
+
|
|
1601
|
+
# The project's OWN signature paths, resolved exactly as `Environment.for_project` resolves its
|
|
1602
|
+
# `resolved_paths` — the configured list, or `<root>/sig` when the project configured none. The
|
|
1603
|
+
# loader's `signature_paths` is NOT the same list: it also carries plugin signature paths, gem `sig/`
|
|
1604
|
+
# directories discovered under the project's bundle, the `rbs collection` tree and Rigor's own gem
|
|
1605
|
+
# overlays, none of which declare vocabulary this project chose.
|
|
1606
|
+
def project_signature_paths
|
|
1607
|
+
configured = @configuration.signature_paths
|
|
1608
|
+
return configured unless configured.nil?
|
|
1609
|
+
|
|
1610
|
+
default = Pathname(Dir.pwd) / "sig"
|
|
1611
|
+
default.directory? ? [default] : []
|
|
1612
|
+
end
|
|
1613
|
+
|
|
1614
|
+
# A loaded plugin's own signature directory is the plugin's vocabulary, not the project's, whether the
|
|
1615
|
+
# plugin tier contributed it or the project wired it into `signature_paths:` itself (#697).
|
|
1616
|
+
def plugin_signature_paths
|
|
1617
|
+
@environment&.plugin_registry&.signature_paths || []
|
|
1618
|
+
rescue StandardError
|
|
1619
|
+
[]
|
|
1016
1620
|
end
|
|
1017
1621
|
|
|
1018
1622
|
# RBS / Steep require return-position unions to be parenthesised when they appear bare at the top level of
|
|
@@ -1038,15 +1642,15 @@ module Rigor
|
|
|
1038
1642
|
false
|
|
1039
1643
|
end
|
|
1040
1644
|
|
|
1041
|
-
def union_erase(types)
|
|
1645
|
+
def union_erase(types, owner: nil)
|
|
1042
1646
|
return "untyped" if types.empty?
|
|
1043
|
-
return elaborated_rbs(types.first) if types.size == 1
|
|
1647
|
+
return elaborated_rbs(types.first, owner: owner) if types.size == 1
|
|
1044
1648
|
|
|
1045
1649
|
# `Type::Combinator.union` dedupes by structural type equality. The carrier-level `erase_to_rbs` now
|
|
1046
1650
|
# absorbs `untyped` members and dedupes the post-erase strings (`String | String` → `String` for
|
|
1047
1651
|
# distinct `Constant<"Alice">` / `Constant<"Bob">` envelopes), so the sig-gen layer only needs to
|
|
1048
1652
|
# elaborate bare generics before erasing.
|
|
1049
|
-
elaborated_rbs(Type::Combinator.union(*types))
|
|
1653
|
+
elaborated_rbs(Type::Combinator.union(*types), owner: owner)
|
|
1050
1654
|
end
|
|
1051
1655
|
|
|
1052
1656
|
# ADR-14 slice 4 — `attr_reader` / `attr_writer` / `attr_accessor` recognition. Each Symbol-named entry in
|
|
@@ -1062,6 +1666,7 @@ module Rigor
|
|
|
1062
1666
|
attr_writer: [:writer],
|
|
1063
1667
|
attr_accessor: %i[reader writer]
|
|
1064
1668
|
}.freeze
|
|
1669
|
+
Ractor.make_shareable(ATTR_KINDS)
|
|
1065
1670
|
private_constant :ATTR_KINDS
|
|
1066
1671
|
|
|
1067
1672
|
# Per-file context the attr_* walker threads through its recursive descent. Keeps parameter lists in
|
|
@@ -1082,9 +1687,9 @@ module Rigor
|
|
|
1082
1687
|
|
|
1083
1688
|
case node
|
|
1084
1689
|
when Prism::ClassNode, Prism::ModuleNode
|
|
1085
|
-
|
|
1086
|
-
if
|
|
1087
|
-
walk_attr_calls(node.body,
|
|
1690
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
1691
|
+
if child_prefix
|
|
1692
|
+
walk_attr_calls(node.body, child_prefix, false, ctx) if node.body
|
|
1088
1693
|
return
|
|
1089
1694
|
end
|
|
1090
1695
|
when Prism::SingletonClassNode
|
|
@@ -1166,9 +1771,9 @@ module Rigor
|
|
|
1166
1771
|
|
|
1167
1772
|
case node
|
|
1168
1773
|
when Prism::ClassNode, Prism::ModuleNode
|
|
1169
|
-
|
|
1170
|
-
if
|
|
1171
|
-
collect_init_ivar_obs(node.body,
|
|
1774
|
+
child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
|
|
1775
|
+
if child_prefix
|
|
1776
|
+
collect_init_ivar_obs(node.body, child_prefix, result) if node.body
|
|
1172
1777
|
return
|
|
1173
1778
|
end
|
|
1174
1779
|
when Prism::DefNode
|
|
@@ -1270,6 +1875,12 @@ module Rigor
|
|
|
1270
1875
|
end
|
|
1271
1876
|
|
|
1272
1877
|
def build_attr_candidate(class_name, method_name, variant, ivar_type, ctx)
|
|
1878
|
+
inline = inline_declarations.lookup(ctx.path, class_name, method_name, :instance)
|
|
1879
|
+
if inline
|
|
1880
|
+
return attr_skipped(ctx.path, class_name, method_name, :inline_declared) if skip_inline_declared?
|
|
1881
|
+
return inline_attr_candidate(class_name, method_name, inline, ctx) if inline.declared?
|
|
1882
|
+
end
|
|
1883
|
+
|
|
1273
1884
|
if ivar_type.nil? || dynamic_top?(ivar_type)
|
|
1274
1885
|
return attr_skipped(ctx.path, class_name, method_name, :untyped_return)
|
|
1275
1886
|
end
|
|
@@ -1284,6 +1895,15 @@ module Rigor
|
|
|
1284
1895
|
end
|
|
1285
1896
|
end
|
|
1286
1897
|
|
|
1898
|
+
# ADR-112 WD4 — an attribute typed inline (`attr_reader :name #: String`) is written from that type, in
|
|
1899
|
+
# the long `def` form the rest of the attr path uses.
|
|
1900
|
+
def inline_attr_candidate(class_name, method_name, inline, ctx)
|
|
1901
|
+
scope = ctx.scope_index.each_value.first
|
|
1902
|
+
method_def = lookup_existing_method(class_name, method_name, :instance, scope&.environment, scope)
|
|
1903
|
+
rbs = "def #{method_name}: #{inline.method_types.join(' | ')}"
|
|
1904
|
+
inline_candidate(ctx.path, class_name, method_name, :instance, rbs, inline, method_def)
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1287
1907
|
def attr_new_candidate(path, class_name, method_name, variant, ivar_type)
|
|
1288
1908
|
build_candidate(
|
|
1289
1909
|
path: path,
|
|
@@ -1292,7 +1912,7 @@ module Rigor
|
|
|
1292
1912
|
kind: :instance,
|
|
1293
1913
|
classification: Classification::NEW_METHOD,
|
|
1294
1914
|
inferred_return: ivar_type,
|
|
1295
|
-
rbs: render_attr_rbs_line(method_name, variant, ivar_type)
|
|
1915
|
+
rbs: render_attr_rbs_line(method_name, variant, ivar_type, class_name)
|
|
1296
1916
|
)
|
|
1297
1917
|
end
|
|
1298
1918
|
|
|
@@ -1309,7 +1929,7 @@ module Rigor
|
|
|
1309
1929
|
path: path, class_name: class_name, method_name: method_name,
|
|
1310
1930
|
kind: :instance, classification: Classification::TIGHTER_RETURN,
|
|
1311
1931
|
inferred_return: ivar_type, declared_return_rbs: declared_rbs,
|
|
1312
|
-
rbs: render_attr_rbs_line(method_name, variant, ivar_type)
|
|
1932
|
+
rbs: render_attr_rbs_line(method_name, variant, ivar_type, class_name)
|
|
1313
1933
|
)
|
|
1314
1934
|
end
|
|
1315
1935
|
|
|
@@ -1333,8 +1953,8 @@ module Rigor
|
|
|
1333
1953
|
# form can normalise post-emit; the writer-side member detection (slice 2) treats existing `attr_*`
|
|
1334
1954
|
# declarations as user-authored so a paired source-side `attr_reader` never produces a duplicate `def`
|
|
1335
1955
|
# insertion.
|
|
1336
|
-
def render_attr_rbs_line(method_name, variant, ivar_type)
|
|
1337
|
-
erased = elaborated_rbs(ivar_type)
|
|
1956
|
+
def render_attr_rbs_line(method_name, variant, ivar_type, owner = nil)
|
|
1957
|
+
erased = elaborated_rbs(ivar_type, owner: owner)
|
|
1338
1958
|
wrapped = paren_wrap_union(erased)
|
|
1339
1959
|
case variant
|
|
1340
1960
|
when :reader then "def #{method_name}: () -> #{wrapped}"
|
|
@@ -1354,7 +1974,7 @@ module Rigor
|
|
|
1354
1974
|
types = meta_member_types(class_name, layout.member_names)
|
|
1355
1975
|
shape = MetaClassShape.of(
|
|
1356
1976
|
kind: layout.kind, members: layout.member_names, keyword_init: layout.keyword_init,
|
|
1357
|
-
member_types: types.transform_values { |type| paren_wrap_union(union_erase([type])) }
|
|
1977
|
+
member_types: types.transform_values { |type| paren_wrap_union(union_erase([type], owner: class_name)) }
|
|
1358
1978
|
)
|
|
1359
1979
|
shape.member_decls.filter_map do |member|
|
|
1360
1980
|
meta_member_candidate(path, class_name, member, types, scope, environment)
|