rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
|
@@ -16,30 +16,21 @@ module Rigor
|
|
|
16
16
|
module SigGen
|
|
17
17
|
# Core generator for `rigor sig-gen` (ADR-14 slice 1 — MVP).
|
|
18
18
|
#
|
|
19
|
-
# Walks every `.rb` file under the input paths, builds a
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# inferred return, looks up the project's existing RBS
|
|
24
|
-
# declaration (if any), and emits one {MethodCandidate} per
|
|
25
|
-
# def.
|
|
19
|
+
# Walks every `.rb` file under the input paths, builds a per-node scope index via
|
|
20
|
+
# {Rigor::Inference::ScopeIndexer}, finds every `Prism::DefNode` whose enclosing class is nameable, types the
|
|
21
|
+
# body's last expression to derive an inferred return, looks up the project's existing RBS declaration (if
|
|
22
|
+
# any), and emits one {MethodCandidate} per def.
|
|
26
23
|
#
|
|
27
24
|
# The MVP keeps the scope deliberately narrow:
|
|
28
|
-
# - Only instance methods inside a `class` / `module` body
|
|
29
|
-
# are
|
|
30
|
-
#
|
|
31
|
-
# - Parameter signatures are hard-coded to `untyped` per
|
|
32
|
-
# ADR-14 § "Robustness principle compliance" clause 2;
|
|
25
|
+
# - Only instance methods inside a `class` / `module` body are considered. Top-level / DSL-block / singleton
|
|
26
|
+
# defs are skipped (`sig.skipped.complex-shape`).
|
|
27
|
+
# - Parameter signatures are hard-coded to `untyped` per ADR-14 § "Robustness principle compliance" clause 2;
|
|
33
28
|
# `--params=observed` arrives in slice 3.
|
|
34
|
-
# - Optional / rest / keyword / block params disqualify the
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# - Tighter-return detection compares the RBS-erased
|
|
40
|
-
# spellings only when the existing declared return
|
|
41
|
-
# strictly accepts the inferred one (acceptance check
|
|
42
|
-
# under the engine's current `:gradual` mode; ADR-14
|
|
29
|
+
# - Optional / rest / keyword / block params disqualify the def (`sig.skipped.complex-shape`).
|
|
30
|
+
# - A `Dynamic[top]` inferred return becomes `sig.skipped.untyped-return` — emitting `untyped` would obscure
|
|
31
|
+
# rather than help.
|
|
32
|
+
# - Tighter-return detection compares the RBS-erased spellings only when the existing declared return
|
|
33
|
+
# strictly accepts the inferred one (acceptance check under the engine's current `:gradual` mode; ADR-14
|
|
43
34
|
# reserves the eventual `:strict` mode).
|
|
44
35
|
class Generator # rubocop:disable Metrics/ClassLength
|
|
45
36
|
# @param configuration [Rigor::Configuration]
|
|
@@ -54,24 +45,18 @@ module Rigor
|
|
|
54
45
|
@paths = paths
|
|
55
46
|
@observations = normalize_observations(observations)
|
|
56
47
|
@include_private = include_private
|
|
57
|
-
# Per-file scratch state. `analyse_file` resets each
|
|
58
|
-
# one
|
|
59
|
-
#
|
|
60
|
-
# initialising empty here gives downstream consumers
|
|
61
|
-
# (`build_candidate`, `method_def_prefix`) a never-nil
|
|
62
|
-
# invariant without per-call-site defensive guards.
|
|
48
|
+
# Per-file scratch state. `analyse_file` resets each one to a fresh container for every file walked so
|
|
49
|
+
# candidates from one file don't leak into another; initialising empty here gives downstream consumers
|
|
50
|
+
# (`build_candidate`, `method_def_prefix`) a never-nil invariant without per-call-site defensive guards.
|
|
63
51
|
@namespace_kinds = {}
|
|
64
52
|
@module_function_methods = Set.new
|
|
65
53
|
@class_shells = Set.new
|
|
66
54
|
@class_superclasses = {}
|
|
67
55
|
end
|
|
68
56
|
|
|
69
|
-
# Lifts legacy plain-`Array[Type]` observation entries
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# as `{ [class, method] => [[type1, type2], ...] }`;
|
|
73
|
-
# the wrapper keeps those passing while internal code
|
|
74
|
-
# always sees the new shape.
|
|
57
|
+
# Lifts legacy plain-`Array[Type]` observation entries into {ObservedCall} carriers. Specs from the
|
|
58
|
+
# slice-3 generation predate the carrier and pass observations as `{ [class, method] => [[type1, type2],
|
|
59
|
+
# ...] }`; the wrapper keeps those passing while internal code always sees the new shape.
|
|
75
60
|
def normalize_observations(map)
|
|
76
61
|
return map if map.empty?
|
|
77
62
|
|
|
@@ -120,11 +105,9 @@ module Rigor
|
|
|
120
105
|
@class_superclasses = {}
|
|
121
106
|
defs = collect_method_definitions(parse_result.value)
|
|
122
107
|
candidates_from_defs = defs.filter_map do |def_node, class_name, kind|
|
|
123
|
-
# An analyzer bug typing one def's body must cost only that
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
# (worker_session.rb); sig-gen recovers per-def so the rest of
|
|
127
|
-
# the file's candidates still emit.
|
|
108
|
+
# An analyzer bug typing one def's body must cost only that def's candidate, never the whole
|
|
109
|
+
# `rigor sig-gen` run. The `check` path recovers each *file* this way (worker_session.rb); sig-gen
|
|
110
|
+
# recovers per-def so the rest of the file's candidates still emit.
|
|
128
111
|
|
|
129
112
|
classify_def(path, def_node, class_name, kind, scope_index)
|
|
130
113
|
rescue StandardError
|
|
@@ -134,29 +117,20 @@ module Rigor
|
|
|
134
117
|
candidates_from_defs + collect_attr_candidates(parse_result.value, path, scope_index, obs_ivar_map)
|
|
135
118
|
end
|
|
136
119
|
|
|
137
|
-
# Walks the AST collecting `(def_node, class_name, kind)`
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
# singleton-side methods via `def self.foo` and
|
|
142
|
-
# `class << self; def foo; end`; top-level / DSL-block
|
|
143
|
-
# defs still degrade silently (no nameable receiver).
|
|
120
|
+
# Walks the AST collecting `(def_node, class_name, kind)` tuples for every `def` Rigor can re-type. Slice 1
|
|
121
|
+
# covered instance `def foo` methods inside a nameable `class` / `module` body. Slice 4 extends this to
|
|
122
|
+
# singleton-side methods via `def self.foo` and `class << self; def foo; end`; top-level / DSL-block defs
|
|
123
|
+
# still degrade silently (no nameable receiver).
|
|
144
124
|
#
|
|
145
|
-
# ADR-14 gap-#3 follow-up tracks two extra pieces during
|
|
146
|
-
#
|
|
147
|
-
# without guessing:
|
|
125
|
+
# ADR-14 gap-#3 follow-up tracks two extra pieces during the same walk so the Writer can emit kind-correct
|
|
126
|
+
# RBS without guessing:
|
|
148
127
|
#
|
|
149
|
-
# - `@namespace_kinds[qualified_name]` records whether
|
|
150
|
-
#
|
|
151
|
-
# `module Foo` (`:module`). Used by the writer's
|
|
152
|
-
# `wrap_in_modules` step to emit the right keyword for
|
|
128
|
+
# - `@namespace_kinds[qualified_name]` records whether each segment came from `class Foo` (`:class`) or
|
|
129
|
+
# `module Foo` (`:module`). Used by the writer's `wrap_in_modules` step to emit the right keyword for
|
|
153
130
|
# each intermediate segment AND the leaf.
|
|
154
|
-
# - `@module_function_methods` records `(class_name,
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
# The renderer emits `def self?.name` for these, the
|
|
158
|
-
# RBS spelling that matches the dual instance +
|
|
159
|
-
# singleton dispatch the runtime produces.
|
|
131
|
+
# - `@module_function_methods` records `(class_name, method_name)` pairs where a `module_function` (no
|
|
132
|
+
# args) call preceded the `def` inside a module body. The renderer emits `def self?.name` for these, the
|
|
133
|
+
# RBS spelling that matches the dual instance + singleton dispatch the runtime produces.
|
|
160
134
|
def collect_method_definitions(root)
|
|
161
135
|
out = []
|
|
162
136
|
walk_defs(root, [], false, false, out)
|
|
@@ -179,8 +153,7 @@ module Rigor
|
|
|
179
153
|
return
|
|
180
154
|
when Prism::ConstantWriteNode
|
|
181
155
|
register_data_struct_shell(node, prefix)
|
|
182
|
-
# fall through to recurse into the RHS so a trailing
|
|
183
|
-
# `do ... end` block carrying defs is still walked.
|
|
156
|
+
# fall through to recurse into the RHS so a trailing `do ... end` block carrying defs is still walked.
|
|
184
157
|
when Prism::StatementsNode
|
|
185
158
|
walk_statements(node, prefix, in_singleton_class, module_function_active, out)
|
|
186
159
|
return
|
|
@@ -202,20 +175,15 @@ module Rigor
|
|
|
202
175
|
true
|
|
203
176
|
end
|
|
204
177
|
|
|
205
|
-
# ADR-14: a generated subclass declaration MUST carry its
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
# `
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
# relative to the emitted namespace, matching Ruby's lexical
|
|
215
|
-
# scope). A computed superclass (`Struct.new`, `Data.define`,
|
|
216
|
-
# `Class.new`, any `CallNode`) is left unrecorded — those flow
|
|
217
|
-
# through the {#register_data_struct_shell} shell path or are
|
|
218
|
-
# simply un-representable, and guessing would misfold.
|
|
178
|
+
# ADR-14: a generated subclass declaration MUST carry its superclass, or the sidecar `sig/` misrepresents
|
|
179
|
+
# the class (inherited members vanish → receiver dispatch degrades to `Dynamic`) and, worse, a nested
|
|
180
|
+
# reference to an inherited type re-declares the class as a bare namespace on the RBS side and can
|
|
181
|
+
# collapse the whole env (the 2026-07-04 redmine `GitAdapter < AbstractAdapter` crash). Only a plain
|
|
182
|
+
# constant superclass is emittable: `class X < Foo` / `class X < Foo::Bar` yields the source token verbatim
|
|
183
|
+
# (RBS resolves it relative to the emitted namespace, matching Ruby's lexical scope). A computed
|
|
184
|
+
# superclass (`Struct.new`, `Data.define`, `Class.new`, any `CallNode`) is left unrecorded — those flow
|
|
185
|
+
# through the {#register_data_struct_shell} shell path or are simply un-representable, and guessing would
|
|
186
|
+
# misfold.
|
|
219
187
|
def record_superclass(node, full)
|
|
220
188
|
return unless node.is_a?(Prism::ClassNode)
|
|
221
189
|
|
|
@@ -223,24 +191,16 @@ module Rigor
|
|
|
223
191
|
@class_superclasses[full] = superclass if superclass
|
|
224
192
|
end
|
|
225
193
|
|
|
226
|
-
# ADR-14 gap-#3 (e): recognises
|
|
227
|
-
#
|
|
228
|
-
# `Const
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
# though no `class Const ... end` block appears in
|
|
233
|
-
# source. Without it, references to `Const` in return
|
|
234
|
-
# types fail to resolve under Steep (the canonical case
|
|
235
|
-
# is `GemResolver::Resolved | GemResolver::Unresolvable`
|
|
236
|
-
# where `Unresolvable = Data.define(:gem_name, :reason)`).
|
|
194
|
+
# ADR-14 gap-#3 (e): recognises `Const = Data.define(...)` and `Const = Struct.new(...)` as class
|
|
195
|
+
# declarations. The runtime side stamps a brand-new anonymous class at the RHS and binds it to `Const`, so
|
|
196
|
+
# the generated RBS needs an explicit `class Const` declaration even though no `class Const ... end` block
|
|
197
|
+
# appears in source. Without it, references to `Const` in return types fail to resolve under Steep (the
|
|
198
|
+
# canonical case is `GemResolver::Resolved | GemResolver::Unresolvable` where
|
|
199
|
+
# `Unresolvable = Data.define(:gem_name, :reason)`).
|
|
237
200
|
#
|
|
238
|
-
# The walker records the fully-qualified constant name
|
|
239
|
-
# in `@
|
|
240
|
-
#
|
|
241
|
-
# AND in `@namespace_kinds` so the leaf's `class`
|
|
242
|
-
# keyword wins over the intermediate-segment `module`
|
|
243
|
-
# default.
|
|
201
|
+
# The walker records the fully-qualified constant name in `@class_shells` (carried through to every
|
|
202
|
+
# candidate so the writer's tree-builder picks it up) AND in `@namespace_kinds` so the leaf's `class`
|
|
203
|
+
# keyword wins over the intermediate-segment `module` default.
|
|
244
204
|
def register_data_struct_shell(node, prefix)
|
|
245
205
|
return unless data_or_struct_call?(node.value)
|
|
246
206
|
|
|
@@ -264,16 +224,11 @@ module Rigor
|
|
|
264
224
|
DATA_STRUCT_SHELL_HEADS[receiver.name.to_s] == value.name
|
|
265
225
|
end
|
|
266
226
|
|
|
267
|
-
# Module / class bodies are walked through the
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
# inherit `module_function` via `Module`'s ancestor
|
|
273
|
-
# chain but don't honour it the same way at runtime, so
|
|
274
|
-
# tracking is only meaningful inside `ModuleNode`
|
|
275
|
-
# bodies. Generator emits `def self?.name` for the
|
|
276
|
-
# marked defs.
|
|
227
|
+
# Module / class bodies are walked through the `walk_statements` path so `module_function` (no-args)
|
|
228
|
+
# encountered as one statement applies to every subsequent sibling def in the same body. The directive is
|
|
229
|
+
# module-scoped semantically — classes inherit `module_function` via `Module`'s ancestor chain but don't
|
|
230
|
+
# honour it the same way at runtime, so tracking is only meaningful inside `ModuleNode` bodies. Generator
|
|
231
|
+
# emits `def self?.name` for the marked defs.
|
|
277
232
|
def walk_namespace_body(namespace_node, prefix, out)
|
|
278
233
|
return if namespace_node.body.nil?
|
|
279
234
|
|
|
@@ -306,12 +261,9 @@ module Rigor
|
|
|
306
261
|
out << [node, class_name, kind]
|
|
307
262
|
end
|
|
308
263
|
|
|
309
|
-
# Wraps `MethodCandidate.new` so every candidate carries
|
|
310
|
-
# the
|
|
311
|
-
#
|
|
312
|
-
# emission consults both to pick `module` vs `class`
|
|
313
|
-
# for each segment and to emit empty
|
|
314
|
-
# `Const = Data.define(...)` declarations.
|
|
264
|
+
# Wraps `MethodCandidate.new` so every candidate carries the per-file `@namespace_kinds` map AND the
|
|
265
|
+
# `@class_shells` set — the Writer's nested-syntax emission consults both to pick `module` vs `class` for
|
|
266
|
+
# each segment and to emit empty `Const = Data.define(...)` declarations.
|
|
315
267
|
def build_candidate(**)
|
|
316
268
|
MethodCandidate.new(
|
|
317
269
|
namespace_kinds: @namespace_kinds,
|
|
@@ -321,10 +273,8 @@ module Rigor
|
|
|
321
273
|
)
|
|
322
274
|
end
|
|
323
275
|
|
|
324
|
-
# Returns "def self." (kind: :singleton),
|
|
325
|
-
# "def
|
|
326
|
-
# `module_function` region — both instance + singleton
|
|
327
|
-
# dispatch at runtime), or "def " (plain instance).
|
|
276
|
+
# Returns "def self." (kind: :singleton), "def self?." (instance method declared inside a
|
|
277
|
+
# `module_function` region — both instance + singleton dispatch at runtime), or "def " (plain instance).
|
|
328
278
|
def method_def_prefix(class_name, method_name, kind)
|
|
329
279
|
return "def self." if kind == :singleton
|
|
330
280
|
return "def self?." if @module_function_methods.include?([class_name, method_name])
|
|
@@ -332,16 +282,11 @@ module Rigor
|
|
|
332
282
|
"def "
|
|
333
283
|
end
|
|
334
284
|
|
|
335
|
-
# Slice-4 follow-up surfaced by the Rigor self-dogfood:
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
# type contract downstream consumers (Steep, IDE, gem
|
|
341
|
-
# users) read. The default now skips private and
|
|
342
|
-
# protected methods; the `:include_private` flag
|
|
343
|
-
# restores the slice-4 behaviour for callers that want
|
|
344
|
-
# every method.
|
|
285
|
+
# Slice-4 follow-up surfaced by the Rigor self-dogfood: most `lib/rigor/cli/*` files have a small public
|
|
286
|
+
# surface (`run`) and many private helpers. Emitting the private helpers into a `sig/` file is noise —
|
|
287
|
+
# private methods are implementation details, not part of the type contract downstream consumers (Steep,
|
|
288
|
+
# IDE, gem users) read. The default now skips private and protected methods; the `:include_private` flag
|
|
289
|
+
# restores the slice-4 behaviour for callers that want every method.
|
|
345
290
|
def visibility_excludes?(def_node, class_name, kind, scope_index)
|
|
346
291
|
return false if kind == :singleton
|
|
347
292
|
return false if @include_private
|
|
@@ -353,34 +298,25 @@ module Rigor
|
|
|
353
298
|
%i[private protected].include?(visibility)
|
|
354
299
|
end
|
|
355
300
|
|
|
356
|
-
# Ruby's `initialize` return value is never meaningful;
|
|
357
|
-
#
|
|
358
|
-
# body-typing path types the last expression (often an
|
|
359
|
-
# ivar assignment whose rvalue happens to be `[]` /
|
|
301
|
+
# Ruby's `initialize` return value is never meaningful; the conventional RBS spelling is `() -> void`. The
|
|
302
|
+
# body-typing path types the last expression (often an ivar assignment whose rvalue happens to be `[]` /
|
|
360
303
|
# `{}`), which produces nonsense return types.
|
|
361
304
|
#
|
|
362
|
-
# Skipping `initialize` entirely is correct ONLY for
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
# runtime `def initialize(...)` and compares against
|
|
369
|
-
# the inherited `Object#initialize: () -> void`. The
|
|
370
|
-
# mismatch surfaces a `Ruby::MethodParameterMismatch`
|
|
371
|
-
# warning even when `rigor check` itself is clean.
|
|
305
|
+
# Skipping `initialize` entirely is correct ONLY for default constructors — the
|
|
306
|
+
# `Object#initialize: () -> void` RBS fallback then covers the lookup. When the class has a non-trivial
|
|
307
|
+
# `initialize(argv:, ...)` (i.e. any parameter), partial-class sigs trip Steep's method-parameter-mismatch
|
|
308
|
+
# check: Steep sees the runtime `def initialize(...)` and compares against the inherited
|
|
309
|
+
# `Object#initialize: () -> void`. The mismatch surfaces a `Ruby::MethodParameterMismatch` warning even
|
|
310
|
+
# when `rigor check` itself is clean.
|
|
372
311
|
#
|
|
373
|
-
# Returning `nil` here causes `classify_def` to skip
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
# `(<param shape>) -> void` stub matching the
|
|
377
|
-
# runtime parameter list.
|
|
312
|
+
# Returning `nil` here causes `classify_def` to skip emission; returning `:emit_stub` causes
|
|
313
|
+
# `initialize_stub_candidate` to emit a permissive `(<param shape>) -> void` stub matching the runtime
|
|
314
|
+
# parameter list.
|
|
378
315
|
def initialize_excludes?(def_node, kind)
|
|
379
316
|
return false unless kind == :instance
|
|
380
317
|
return false unless def_node.name == :initialize
|
|
381
318
|
|
|
382
|
-
# Default constructor with no params — skip; the
|
|
383
|
-
# Object#initialize RBS fallback covers it.
|
|
319
|
+
# Default constructor with no params — skip; the Object#initialize RBS fallback covers it.
|
|
384
320
|
params = def_node.parameters
|
|
385
321
|
params.nil? || trivial_initialize_params?(params)
|
|
386
322
|
end
|
|
@@ -397,21 +333,18 @@ module Rigor
|
|
|
397
333
|
kind == :instance && def_node.name == :initialize && !trivial_initialize_params?(def_node.parameters)
|
|
398
334
|
end
|
|
399
335
|
|
|
400
|
-
# Emits `def initialize: (<shape>) -> void`. The return
|
|
401
|
-
# is
|
|
402
|
-
#
|
|
403
|
-
# the runtime shape (required / optional / rest /
|
|
404
|
-
# keyword / keyword-rest / block).
|
|
336
|
+
# Emits `def initialize: (<shape>) -> void`. The return is always `void` because Ruby's `initialize`
|
|
337
|
+
# return value is never meaningful. The parameter list mirrors the runtime shape (required / optional /
|
|
338
|
+
# rest / keyword / keyword-rest / block).
|
|
405
339
|
#
|
|
406
|
-
# When `--params=observed` populates `@observations` for
|
|
407
|
-
# `
|
|
408
|
-
# `
|
|
409
|
-
#
|
|
410
|
-
# the per-position / per-keyword union of observed
|
|
411
|
-
# types; otherwise every position keeps `untyped` per
|
|
412
|
-
# ADR-5 clause 2.
|
|
340
|
+
# When `--params=observed` populates `@observations` for `[class_name, :initialize]` (via the
|
|
341
|
+
# `ObservationCollector`'s `.new` → `:initialize` routing), positional and keyword arg types come from the
|
|
342
|
+
# per-position / per-keyword union of observed types; otherwise every position keeps `untyped` per ADR-5
|
|
343
|
+
# clause 2.
|
|
413
344
|
def initialize_stub_candidate(path, def_node, class_name)
|
|
414
|
-
|
|
345
|
+
params = def_node.parameters
|
|
346
|
+
rbs = "def initialize: (#{render_initialize_param_list(params, class_name)})" \
|
|
347
|
+
"#{block_signature_suffix(params)} -> void"
|
|
415
348
|
build_candidate(
|
|
416
349
|
path: path, class_name: class_name, method_name: :initialize,
|
|
417
350
|
kind: :instance, classification: Classification::NEW_METHOD,
|
|
@@ -438,14 +371,25 @@ module Rigor
|
|
|
438
371
|
parts << "*untyped" if params.rest
|
|
439
372
|
params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
|
|
440
373
|
parts << "**untyped" if params.keyword_rest
|
|
441
|
-
parts << "?{ (?) -> void }" if params.block
|
|
442
374
|
parts.join(", ")
|
|
443
375
|
end
|
|
444
376
|
|
|
445
|
-
#
|
|
446
|
-
#
|
|
447
|
-
#
|
|
448
|
-
#
|
|
377
|
+
# The RBS block suffix for a `def` that takes a `&block` parameter, e.g. ` ?{ (*untyped) -> untyped }`.
|
|
378
|
+
# A block belongs AFTER the parameter parens in RBS (`(params) ?{ block } -> ret`), not inside them —
|
|
379
|
+
# emitting it as a comma-joined member produced `(**untyped, ?{ (?) -> void })`, which RBS rejects
|
|
380
|
+
# (`optional keyword argument type is expected`) and which then collapsed the whole env build. sig-gen
|
|
381
|
+
# never observes the block's own signature, so it is rendered maximally lenient (ADR-5): an optional
|
|
382
|
+
# block (`?{`, since a `&block` need not be passed) taking `*untyped` and returning `untyped`. Returns
|
|
383
|
+
# "" when the method takes no block.
|
|
384
|
+
def block_signature_suffix(params)
|
|
385
|
+
return "" unless params.is_a?(Prism::ParametersNode)
|
|
386
|
+
return "" if params.block.nil?
|
|
387
|
+
|
|
388
|
+
" ?{ (*untyped) -> untyped }"
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Picks observations under `[class_name, :initialize]` whose positional arity matches the def's accepted
|
|
392
|
+
# range (required..required+optional). Looser arities don't get used because they describe a different
|
|
449
393
|
# overload the stub cannot express.
|
|
450
394
|
def initialize_observations(class_name, params)
|
|
451
395
|
return [] if @observations.empty?
|
|
@@ -503,11 +447,9 @@ module Rigor
|
|
|
503
447
|
end
|
|
504
448
|
end
|
|
505
449
|
|
|
506
|
-
# Required positionals only; the MVP's body-typing path
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
# `sig.skipped.complex-shape` reason until slices 3+
|
|
510
|
-
# widen the param policy.
|
|
450
|
+
# Required positionals only; the MVP's body-typing path gives well-defined returns for that shape.
|
|
451
|
+
# Optional / rest / keyword / block parameters route through the `sig.skipped.complex-shape` reason until
|
|
452
|
+
# slices 3+ widen the param policy.
|
|
511
453
|
def simple_parameter_shape?(params)
|
|
512
454
|
return true if params.nil?
|
|
513
455
|
return false unless params.is_a?(Prism::ParametersNode)
|
|
@@ -519,27 +461,18 @@ module Rigor
|
|
|
519
461
|
params.block.nil?
|
|
520
462
|
end
|
|
521
463
|
|
|
522
|
-
# Mirrors the `def.return-type-mismatch` rule's body-type
|
|
523
|
-
#
|
|
524
|
-
#
|
|
525
|
-
# parameter bindings (typed `untyped` per the indexer's
|
|
526
|
-
# default) come from `with_local` inside
|
|
527
|
-
# `StatementEvaluator`; the result is the carrier the
|
|
464
|
+
# Mirrors the `def.return-type-mismatch` rule's body-type extraction: type the implicit-return expression
|
|
465
|
+
# under the scope the indexer associated with the body. The parameter bindings (typed `untyped` per the
|
|
466
|
+
# indexer's default) come from `with_local` inside `StatementEvaluator`; the result is the carrier the
|
|
528
467
|
# body proves *given an untyped argument tuple*.
|
|
529
468
|
#
|
|
530
|
-
# Post-dogfood enhancement: walk the body's AST for
|
|
531
|
-
#
|
|
532
|
-
#
|
|
533
|
-
#
|
|
534
|
-
#
|
|
535
|
-
#
|
|
536
|
-
#
|
|
537
|
-
# `V | nil` via `return nil unless ...`). The walk
|
|
538
|
-
# excludes nested `DefNode` / lambda / block scopes
|
|
539
|
-
# whose returns belong to different methods.
|
|
540
|
-
# Delegates to {Rigor::Inference::DefReturnTyper} — the same
|
|
541
|
-
# body-typing + explicit-return-union the `rigor annotate`
|
|
542
|
-
# def-line annotator uses.
|
|
469
|
+
# Post-dogfood enhancement: walk the body's AST for explicit `return X` statements and union their value
|
|
470
|
+
# types with the implicit-return expression's type. The earlier MVP only typed the implicit-return path,
|
|
471
|
+
# which routinely produced single-branch artefacts like `parse_options: () -> nil` (the actual runtime
|
|
472
|
+
# return is `options | nil`) or `find: () -> V` (actually `V | nil` via `return nil unless ...`). The walk
|
|
473
|
+
# excludes nested `DefNode` / lambda / block scopes whose returns belong to different methods. Delegates
|
|
474
|
+
# to {Rigor::Inference::DefReturnTyper} — the same body-typing + explicit-return-union the `rigor
|
|
475
|
+
# annotate` def-line annotator uses.
|
|
543
476
|
def infer_return_type(def_node, scope_index)
|
|
544
477
|
Inference::DefReturnTyper.call(def_node, scope_index)
|
|
545
478
|
end
|
|
@@ -548,13 +481,10 @@ module Rigor
|
|
|
548
481
|
return true if type.is_a?(Type::Dynamic)
|
|
549
482
|
return true if type.respond_to?(:top?) && type.top?.yes?
|
|
550
483
|
|
|
551
|
-
# Post-dogfood: when explicit-return union absorbs
|
|
552
|
-
# Dynamic
|
|
553
|
-
# `
|
|
554
|
-
#
|
|
555
|
-
# `sig.skipped.untyped-return` case — obscures rather
|
|
556
|
-
# than helps — so the skip check considers the erased
|
|
557
|
-
# form too.
|
|
484
|
+
# Post-dogfood: when explicit-return union absorbs Dynamic and the carrier ends up as a Union
|
|
485
|
+
# containing `Dynamic[top]`, the Bug-1 erasure rule renders it as `untyped`. Emitting
|
|
486
|
+
# `def m: () -> untyped` is the `sig.skipped.untyped-return` case — obscures rather than helps — so the
|
|
487
|
+
# skip check considers the erased form too.
|
|
558
488
|
type.respond_to?(:erase_to_rbs) && type.erase_to_rbs == "untyped"
|
|
559
489
|
end
|
|
560
490
|
|
|
@@ -621,26 +551,17 @@ module Rigor
|
|
|
621
551
|
nil
|
|
622
552
|
end
|
|
623
553
|
|
|
624
|
-
# ADR-14 § "What 'more precise' means". The MVP uses the
|
|
625
|
-
#
|
|
626
|
-
# reserved by `Inference::Acceptance` and lands in a
|
|
627
|
-
# follow-up. The "different spelling" guard ensures we
|
|
554
|
+
# ADR-14 § "What 'more precise' means". The MVP uses the engine's gradual-mode acceptance — `:strict` is
|
|
555
|
+
# reserved by `Inference::Acceptance` and lands in a follow-up. The "different spelling" guard ensures we
|
|
628
556
|
# never classify a same-string round-trip as tighter.
|
|
629
557
|
#
|
|
630
|
-
# The `loses_declared_union_member?` guard added after
|
|
631
|
-
# the
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
#
|
|
635
|
-
#
|
|
636
|
-
#
|
|
637
|
-
# `false | true` shapes / `Float | Integer` numeric
|
|
638
|
-
# alternates routinely look "tighter" while actually
|
|
639
|
-
# dropping reachable branches. Treating those as
|
|
640
|
-
# equivalent matches the project rule that an
|
|
641
|
-
# inferred tightening contradicting an existing RBS
|
|
642
|
-
# member set is suspected incomplete inference until
|
|
643
|
-
# proven otherwise.
|
|
558
|
+
# The `loses_declared_union_member?` guard added after the Rigor self-dogfood pass refuses to classify as
|
|
559
|
+
# tighter-return when the declared form is a top-level Union and the inferred form collapses one or more
|
|
560
|
+
# of its declared members. The body-typing path in slice 1 only inspects the implicit-return expression,
|
|
561
|
+
# so methods with `return nil unless ...` / boolean `false | true` shapes / `Float | Integer` numeric
|
|
562
|
+
# alternates routinely look "tighter" while actually dropping reachable branches. Treating those as
|
|
563
|
+
# equivalent matches the project rule that an inferred tightening contradicting an existing RBS member set
|
|
564
|
+
# is suspected incomplete inference until proven otherwise.
|
|
644
565
|
def tighter?(declared, inferred)
|
|
645
566
|
return false if inferred.is_a?(Type::Dynamic)
|
|
646
567
|
return false if loses_declared_lenience?(declared, inferred)
|
|
@@ -652,24 +573,16 @@ module Rigor
|
|
|
652
573
|
!backward.yes?
|
|
653
574
|
end
|
|
654
575
|
|
|
655
|
-
# Composite guard: refuse to classify as tighter-return
|
|
656
|
-
#
|
|
657
|
-
# inferred form removes. Three cases all signal
|
|
658
|
-
# incomplete inference rather than precision gain:
|
|
576
|
+
# Composite guard: refuse to classify as tighter-return when the declared RBS expresses lenience that the
|
|
577
|
+
# inferred form removes. Three cases all signal incomplete inference rather than precision gain:
|
|
659
578
|
#
|
|
660
|
-
# 1. Top-level union losing one or more declared
|
|
661
|
-
# members. `return nil unless ...` paths, two-valued
|
|
579
|
+
# 1. Top-level union losing one or more declared members. `return nil unless ...` paths, two-valued
|
|
662
580
|
# booleans, `Float | Integer` numeric alternates.
|
|
663
|
-
# 2. Generic collection narrowed to a fixed shape.
|
|
664
|
-
#
|
|
665
|
-
# HashShape — the body's last expression was a
|
|
666
|
-
# literal whose specific shape is not the method's
|
|
581
|
+
# 2. Generic collection narrowed to a fixed shape. `Array[T]` → `Tuple[T, ...]`, `Hash[K, V]` →
|
|
582
|
+
# HashShape — the body's last expression was a literal whose specific shape is not the method's
|
|
667
583
|
# contract.
|
|
668
|
-
# 3. `untyped` type-arg replaced by a concrete form.
|
|
669
|
-
#
|
|
670
|
-
# author's intentional value-type lenience; the
|
|
671
|
-
# inference's narrower Union should not override
|
|
672
|
-
# it.
|
|
584
|
+
# 3. `untyped` type-arg replaced by a concrete form. Declared `Hash[String, untyped]` carries the author's
|
|
585
|
+
# intentional value-type lenience; the inference's narrower Union should not override it.
|
|
673
586
|
def loses_declared_lenience?(declared, inferred)
|
|
674
587
|
loses_declared_union_member?(declared, inferred) ||
|
|
675
588
|
narrows_collection_to_shape?(declared, inferred) ||
|
|
@@ -704,19 +617,13 @@ module Rigor
|
|
|
704
617
|
inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
|
|
705
618
|
end
|
|
706
619
|
|
|
707
|
-
# Heuristic added after the third-round self-dogfood:
|
|
708
|
-
# `
|
|
709
|
-
#
|
|
710
|
-
#
|
|
711
|
-
#
|
|
712
|
-
#
|
|
713
|
-
#
|
|
714
|
-
# `Integer` because callers add events through other
|
|
715
|
-
# methods. The signal is "the body's last expression
|
|
716
|
-
# is NOT a directly-authored literal but the inferred
|
|
717
|
-
# type IS a Constant"; in that case the precision
|
|
718
|
-
# came from inference over an internal computation,
|
|
719
|
-
# not the author's contract, so refuse to tighten.
|
|
620
|
+
# Heuristic added after the third-round self-dogfood: `FallbackTracer#size` body is `@events.size`, where
|
|
621
|
+
# `@events` is initialised to `[]` and never assigned again at the class-ivar pre-pass level. The
|
|
622
|
+
# `Type::Tuple[]` (size 0) folds `.size` to `Constant<0>` — the carrier knows the empty-tuple cardinality
|
|
623
|
+
# exactly. But the runtime contract is `Integer` because callers add events through other methods. The
|
|
624
|
+
# signal is "the body's last expression is NOT a directly-authored literal but the inferred type IS a
|
|
625
|
+
# Constant"; in that case the precision came from inference over an internal computation, not the
|
|
626
|
+
# author's contract, so refuse to tighten.
|
|
720
627
|
def computed_literal_tightening?(inferred, def_node)
|
|
721
628
|
return false unless inferred.is_a?(Type::Constant)
|
|
722
629
|
|
|
@@ -774,22 +681,17 @@ module Rigor
|
|
|
774
681
|
"#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
|
|
775
682
|
end
|
|
776
683
|
|
|
777
|
-
# Routes the inferred carrier through {TypeElaborator}
|
|
778
|
-
#
|
|
779
|
-
#
|
|
780
|
-
#
|
|
781
|
-
# elaborator consults the class's RBS-declared
|
|
782
|
-
# type-parameter list via `Reflection.class_type_param_names`.
|
|
684
|
+
# Routes the inferred carrier through {TypeElaborator} so bare generic nominals (`Array` / `Hash` / `Set`
|
|
685
|
+
# / `Range` / `Enumerable`) get their `untyped` type parameters filled in before erasing to RBS. The
|
|
686
|
+
# elaborator consults the class's RBS-declared type-parameter list via
|
|
687
|
+
# `Reflection.class_type_param_names`.
|
|
783
688
|
def elaborated_rbs(type)
|
|
784
689
|
TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs
|
|
785
690
|
end
|
|
786
691
|
|
|
787
|
-
# RBS / Steep require return-position unions to be
|
|
788
|
-
#
|
|
789
|
-
#
|
|
790
|
-
# the parser because the trailing `| 1` isn't a valid
|
|
791
|
-
# method-type start. Wrap when the erased form is a
|
|
792
|
-
# top-level union; single types and already-bracketed
|
|
692
|
+
# RBS / Steep require return-position unions to be parenthesised when they appear bare at the top level of
|
|
693
|
+
# a method type — `def m: () -> 0 | 1` fails the parser because the trailing `| 1` isn't a valid
|
|
694
|
+
# method-type start. Wrap when the erased form is a top-level union; single types and already-bracketed
|
|
793
695
|
# forms (e.g. `Array[A | B]`) parse without wrapping.
|
|
794
696
|
def paren_wrap_union(rendered)
|
|
795
697
|
top_level_union?(rendered) ? "(#{rendered})" : rendered
|
|
@@ -815,14 +717,11 @@ module Rigor
|
|
|
815
717
|
params.is_a?(Prism::ParametersNode) ? params.requireds.size : 0
|
|
816
718
|
end
|
|
817
719
|
|
|
818
|
-
# Per ADR-5 clause 2 the default is `untyped` for every
|
|
819
|
-
#
|
|
820
|
-
#
|
|
821
|
-
#
|
|
822
|
-
#
|
|
823
|
-
# Observations from arities other than the def's count
|
|
824
|
-
# are discarded — they describe a different overload
|
|
825
|
-
# the MVP does not emit.
|
|
720
|
+
# Per ADR-5 clause 2 the default is `untyped` for every position. Observed-policy callers
|
|
721
|
+
# (`--params=observed`) pass an `observations:` map at construction time; the generator unions
|
|
722
|
+
# per-position arg types whose tuple arity matches the def's required-positional count. Observations from
|
|
723
|
+
# arities other than the def's count are discarded — they describe a different overload the MVP does not
|
|
724
|
+
# emit.
|
|
826
725
|
def render_param_list(class_name, method_name, arity)
|
|
827
726
|
tuples = matching_observations(class_name, method_name, arity)
|
|
828
727
|
return Array.new(arity, "untyped").join(", ") if tuples.empty?
|
|
@@ -841,25 +740,18 @@ module Rigor
|
|
|
841
740
|
return "untyped" if types.empty?
|
|
842
741
|
return elaborated_rbs(types.first) if types.size == 1
|
|
843
742
|
|
|
844
|
-
# `Type::Combinator.union` dedupes by structural type
|
|
845
|
-
#
|
|
846
|
-
#
|
|
847
|
-
#
|
|
848
|
-
# `Constant<"Alice">` / `Constant<"Bob">` envelopes),
|
|
849
|
-
# so the sig-gen layer only needs to elaborate bare
|
|
850
|
-
# generics before erasing.
|
|
743
|
+
# `Type::Combinator.union` dedupes by structural type equality. The carrier-level `erase_to_rbs` now
|
|
744
|
+
# absorbs `untyped` members and dedupes the post-erase strings (`String | String` → `String` for
|
|
745
|
+
# distinct `Constant<"Alice">` / `Constant<"Bob">` envelopes), so the sig-gen layer only needs to
|
|
746
|
+
# elaborate bare generics before erasing.
|
|
851
747
|
elaborated_rbs(Type::Combinator.union(*types))
|
|
852
748
|
end
|
|
853
749
|
|
|
854
|
-
# ADR-14 slice 4 — `attr_reader` / `attr_writer` /
|
|
855
|
-
#
|
|
856
|
-
#
|
|
857
|
-
#
|
|
858
|
-
#
|
|
859
|
-
# `Scope#class_ivars_for(class_name)`. `attr_reader` adds
|
|
860
|
-
# one reader candidate; `attr_writer` adds one
|
|
861
|
-
# `name=`-method writer candidate; `attr_accessor` adds
|
|
862
|
-
# both.
|
|
750
|
+
# ADR-14 slice 4 — `attr_reader` / `attr_writer` / `attr_accessor` recognition. Each Symbol-named entry in
|
|
751
|
+
# the call's argument list yields one or two {MethodCandidate}s whose inferred return type is the
|
|
752
|
+
# corresponding instance-variable's accumulated type from `Scope#class_ivars_for(class_name)`.
|
|
753
|
+
# `attr_reader` adds one reader candidate; `attr_writer` adds one `name=`-method writer candidate;
|
|
754
|
+
# `attr_accessor` adds both.
|
|
863
755
|
ATTR_METHOD_NAMES = %i[attr_reader attr_writer attr_accessor].freeze
|
|
864
756
|
private_constant :ATTR_METHOD_NAMES
|
|
865
757
|
|
|
@@ -870,11 +762,9 @@ module Rigor
|
|
|
870
762
|
}.freeze
|
|
871
763
|
private_constant :ATTR_KINDS
|
|
872
764
|
|
|
873
|
-
# Per-file context the attr_* walker threads through its
|
|
874
|
-
#
|
|
875
|
-
#
|
|
876
|
-
# built by {#build_observed_ivar_map}; it is empty when sig-gen
|
|
877
|
-
# is invoked without `--params=observed`.
|
|
765
|
+
# Per-file context the attr_* walker threads through its recursive descent. Keeps parameter lists in
|
|
766
|
+
# check. `obs_ivar_map` carries the observation-derived fallback types built by
|
|
767
|
+
# {#build_observed_ivar_map}; it is empty when sig-gen is invoked without `--params=observed`.
|
|
878
768
|
AttrWalkContext = Struct.new(:path, :scope_index, :obs_ivar_map, :out, keyword_init: true)
|
|
879
769
|
private_constant :AttrWalkContext
|
|
880
770
|
|
|
@@ -899,8 +789,7 @@ module Rigor
|
|
|
899
789
|
walk_attr_calls(node.body, prefix, true, ctx) if node.body
|
|
900
790
|
return
|
|
901
791
|
when Prism::DefNode
|
|
902
|
-
# Skip method bodies — attr_* there would refer to
|
|
903
|
-
# whatever the method is doing dynamically, not a
|
|
792
|
+
# Skip method bodies — attr_* there would refer to whatever the method is doing dynamically, not a
|
|
904
793
|
# class-level declaration.
|
|
905
794
|
return
|
|
906
795
|
when Prism::CallNode
|
|
@@ -930,21 +819,15 @@ module Rigor
|
|
|
930
819
|
Source::Literals.symbol_arguments(call_node)
|
|
931
820
|
end
|
|
932
821
|
|
|
933
|
-
# Returns a closure that looks up `:@<attr_name>` in the
|
|
934
|
-
#
|
|
935
|
-
#
|
|
936
|
-
#
|
|
937
|
-
# before any statement evaluation runs, so the lookup
|
|
938
|
-
# works even when attr_* declarations come before the
|
|
939
|
-
# corresponding ivar writes lexically.
|
|
822
|
+
# Returns a closure that looks up `:@<attr_name>` in the class-ivar accumulator carried by the first scope
|
|
823
|
+
# the indexer associated with this file. The accumulator is populated by
|
|
824
|
+
# `ScopeIndexer#build_class_ivar_index` before any statement evaluation runs, so the lookup works even
|
|
825
|
+
# when attr_* declarations come before the corresponding ivar writes lexically.
|
|
940
826
|
#
|
|
941
|
-
# When `obs_ivar_map` is non-empty (i.e. `--params=observed`
|
|
942
|
-
#
|
|
943
|
-
#
|
|
944
|
-
#
|
|
945
|
-
# as `untyped` — the observation-derived type is substituted.
|
|
946
|
-
# This lets `attr_reader :name` emit a concrete type when
|
|
947
|
-
# `ClassName.new("alice")` call sites are visible to the
|
|
827
|
+
# When `obs_ivar_map` is non-empty (i.e. `--params=observed` was used), it acts as a fallback: if the ivar
|
|
828
|
+
# pre-pass resolved the type to `nil` or `Dynamic[top]` — typically because `@ivar = param` inside
|
|
829
|
+
# `initialize` typed the param as `untyped` — the observation-derived type is substituted. This lets
|
|
830
|
+
# `attr_reader :name` emit a concrete type when `ClassName.new("alice")` call sites are visible to the
|
|
948
831
|
# observation scan.
|
|
949
832
|
def ivar_type_lookup(scope_index, class_name, obs_ivar_map = {})
|
|
950
833
|
any_scope = scope_index.each_value.first
|
|
@@ -958,13 +841,10 @@ module Rigor
|
|
|
958
841
|
end
|
|
959
842
|
end
|
|
960
843
|
|
|
961
|
-
# Build a { class_name => { attr_name_sym => Type } } map that
|
|
962
|
-
#
|
|
963
|
-
#
|
|
964
|
-
#
|
|
965
|
-
# supplied). Matches the pattern `@ivar_name = param_name` where
|
|
966
|
-
# `param_name` is a required / optional positional or keyword
|
|
967
|
-
# parameter of `initialize`.
|
|
844
|
+
# Build a { class_name => { attr_name_sym => Type } } map that records observation-derived types for
|
|
845
|
+
# ivars assigned directly from `def initialize` parameters. Only populated when `@observations` is
|
|
846
|
+
# non-empty (i.e. `--params=observed` was supplied). Matches the pattern `@ivar_name = param_name` where
|
|
847
|
+
# `param_name` is a required / optional positional or keyword parameter of `initialize`.
|
|
968
848
|
def build_observed_ivar_map(root)
|
|
969
849
|
return {} if @observations.empty?
|
|
970
850
|
|
|
@@ -995,9 +875,8 @@ module Rigor
|
|
|
995
875
|
node.compact_child_nodes.each { |c| collect_init_ivar_obs(c, prefix, result) }
|
|
996
876
|
end
|
|
997
877
|
|
|
998
|
-
# Derive { attr_name_sym => Type } for a single `def initialize`
|
|
999
|
-
#
|
|
1000
|
-
# available `[class_name, :initialize]` observations.
|
|
878
|
+
# Derive { attr_name_sym => Type } for a single `def initialize` by matching `@ivar = param_name`
|
|
879
|
+
# assignments against the available `[class_name, :initialize]` observations.
|
|
1001
880
|
def ivar_obs_from_initialize(class_name, def_node)
|
|
1002
881
|
obs_list = @observations[[class_name, :initialize]]
|
|
1003
882
|
return {} if obs_list.nil? || obs_list.empty?
|
|
@@ -1011,8 +890,8 @@ module Rigor
|
|
|
1011
890
|
build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
1012
891
|
end
|
|
1013
892
|
|
|
1014
|
-
# Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }`
|
|
1015
|
-
#
|
|
893
|
+
# Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }` by looking up each param's observation
|
|
894
|
+
# types and unioning them.
|
|
1016
895
|
def build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
1017
896
|
ivar_to_param.filter_map do |ivar_name, param_name|
|
|
1018
897
|
types = collect_param_obs_types(obs_list, param_name, param_index[param_name])
|
|
@@ -1023,8 +902,8 @@ module Rigor
|
|
|
1023
902
|
end.to_h
|
|
1024
903
|
end
|
|
1025
904
|
|
|
1026
|
-
# Collect observed argument types for a single parameter across all
|
|
1027
|
-
#
|
|
905
|
+
# Collect observed argument types for a single parameter across all call-site observations. Returns an
|
|
906
|
+
# array of Type objects (may be empty).
|
|
1028
907
|
def collect_param_obs_types(obs_list, param_name, param_info)
|
|
1029
908
|
case param_info[:kind]
|
|
1030
909
|
when :positional then obs_list.filter_map { |obs| obs.positional[param_info[:index]] }
|
|
@@ -1033,9 +912,8 @@ module Rigor
|
|
|
1033
912
|
end
|
|
1034
913
|
end
|
|
1035
914
|
|
|
1036
|
-
# Map param_name_sym → { kind: :positional, index: N } or
|
|
1037
|
-
#
|
|
1038
|
-
# required / optional keywords of a ParametersNode.
|
|
915
|
+
# Map param_name_sym → { kind: :positional, index: N } or { kind: :keyword } for required / optional
|
|
916
|
+
# positionals and required / optional keywords of a ParametersNode.
|
|
1039
917
|
def build_init_param_index(parameters)
|
|
1040
918
|
index = {}
|
|
1041
919
|
offset = 0
|
|
@@ -1056,10 +934,9 @@ module Rigor
|
|
|
1056
934
|
index
|
|
1057
935
|
end
|
|
1058
936
|
|
|
1059
|
-
# Walk a def body for direct `@ivar = local_var` assignments
|
|
1060
|
-
#
|
|
1061
|
-
#
|
|
1062
|
-
# Does not recurse into nested defs / classes / modules.
|
|
937
|
+
# Walk a def body for direct `@ivar = local_var` assignments where `local_var` is one of the listed
|
|
938
|
+
# parameter names. Records ivar_name (Symbol with `@` prefix) → param_name. Does not recurse into nested
|
|
939
|
+
# defs / classes / modules.
|
|
1063
940
|
def scan_ivar_param_assignments(node, param_names, result)
|
|
1064
941
|
return unless node.is_a?(Prism::Node)
|
|
1065
942
|
|
|
@@ -1143,14 +1020,11 @@ module Rigor
|
|
|
1143
1020
|
)
|
|
1144
1021
|
end
|
|
1145
1022
|
|
|
1146
|
-
# Slice 4 emits attr_* in the long-form `def` spelling so
|
|
1147
|
-
# the
|
|
1148
|
-
#
|
|
1149
|
-
#
|
|
1150
|
-
#
|
|
1151
|
-
# (slice 2) treats existing `attr_*` declarations as
|
|
1152
|
-
# user-authored so a paired source-side `attr_reader`
|
|
1153
|
-
# never produces a duplicate `def` insertion.
|
|
1023
|
+
# Slice 4 emits attr_* in the long-form `def` spelling so the existing writer's `MethodDefinition`-based
|
|
1024
|
+
# merge path applies without extra wiring. Users who prefer the idiomatic `attr_reader name: Type` short
|
|
1025
|
+
# form can normalise post-emit; the writer-side member detection (slice 2) treats existing `attr_*`
|
|
1026
|
+
# declarations as user-authored so a paired source-side `attr_reader` never produces a duplicate `def`
|
|
1027
|
+
# insertion.
|
|
1154
1028
|
def render_attr_rbs_line(method_name, variant, ivar_type)
|
|
1155
1029
|
erased = elaborated_rbs(ivar_type)
|
|
1156
1030
|
wrapped = paren_wrap_union(erased)
|