rigortype 0.2.6 → 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 +4 -3
- data/docs/manual/02-cli-reference.md +2 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/08-skills.md +21 -0
- 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 +45 -20
- 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 +70 -36
- 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 +433 -503
- 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 -1115
- 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 +208 -309
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +15 -15
- 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 +141 -173
- 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 +107 -83
- 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 -236
- 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 +20 -36
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +23 -30
- 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 +182 -328
- 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
- data/skills/rigor-ask/SKILL.md +21 -1
- data/skills/rigor-baseline-reduce/SKILL.md +16 -0
- data/skills/rigor-ci-setup/SKILL.md +96 -249
- data/skills/rigor-doctor/SKILL.md +39 -49
- data/skills/rigor-doctor/references/01-checks.md +52 -0
- data/skills/rigor-editor-setup/SKILL.md +14 -0
- data/skills/rigor-mcp-setup/SKILL.md +14 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
- data/skills/rigor-plugin-author/SKILL.md +16 -0
- data/skills/rigor-plugin-review/SKILL.md +174 -0
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
- data/skills/rigor-plugin-tune/SKILL.md +21 -2
- data/skills/rigor-project-init/SKILL.md +16 -0
- data/skills/rigor-protection-uplift/SKILL.md +15 -0
- data/skills/rigor-rbs-setup/SKILL.md +15 -0
- data/skills/rigor-upgrade/SKILL.md +16 -0
- metadata +8 -4
|
@@ -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,23 +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
|
|
54
|
+
@class_superclasses = {}
|
|
66
55
|
end
|
|
67
56
|
|
|
68
|
-
# Lifts legacy plain-`Array[Type]` observation entries
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
# as `{ [class, method] => [[type1, type2], ...] }`;
|
|
72
|
-
# the wrapper keeps those passing while internal code
|
|
73
|
-
# 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.
|
|
74
60
|
def normalize_observations(map)
|
|
75
61
|
return map if map.empty?
|
|
76
62
|
|
|
@@ -116,13 +102,12 @@ module Rigor
|
|
|
116
102
|
@namespace_kinds = {}
|
|
117
103
|
@module_function_methods = Set.new
|
|
118
104
|
@class_shells = Set.new
|
|
105
|
+
@class_superclasses = {}
|
|
119
106
|
defs = collect_method_definitions(parse_result.value)
|
|
120
107
|
candidates_from_defs = defs.filter_map do |def_node, class_name, kind|
|
|
121
|
-
# An analyzer bug typing one def's body must cost only that
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
# (worker_session.rb); sig-gen recovers per-def so the rest of
|
|
125
|
-
# 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.
|
|
126
111
|
|
|
127
112
|
classify_def(path, def_node, class_name, kind, scope_index)
|
|
128
113
|
rescue StandardError
|
|
@@ -132,29 +117,20 @@ module Rigor
|
|
|
132
117
|
candidates_from_defs + collect_attr_candidates(parse_result.value, path, scope_index, obs_ivar_map)
|
|
133
118
|
end
|
|
134
119
|
|
|
135
|
-
# Walks the AST collecting `(def_node, class_name, kind)`
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
# singleton-side methods via `def self.foo` and
|
|
140
|
-
# `class << self; def foo; end`; top-level / DSL-block
|
|
141
|
-
# 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).
|
|
142
124
|
#
|
|
143
|
-
# ADR-14 gap-#3 follow-up tracks two extra pieces during
|
|
144
|
-
#
|
|
145
|
-
# 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:
|
|
146
127
|
#
|
|
147
|
-
# - `@namespace_kinds[qualified_name]` records whether
|
|
148
|
-
#
|
|
149
|
-
# `module Foo` (`:module`). Used by the writer's
|
|
150
|
-
# `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
|
|
151
130
|
# each intermediate segment AND the leaf.
|
|
152
|
-
# - `@module_function_methods` records `(class_name,
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
# The renderer emits `def self?.name` for these, the
|
|
156
|
-
# RBS spelling that matches the dual instance +
|
|
157
|
-
# 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.
|
|
158
134
|
def collect_method_definitions(root)
|
|
159
135
|
out = []
|
|
160
136
|
walk_defs(root, [], false, false, out)
|
|
@@ -177,8 +153,7 @@ module Rigor
|
|
|
177
153
|
return
|
|
178
154
|
when Prism::ConstantWriteNode
|
|
179
155
|
register_data_struct_shell(node, prefix)
|
|
180
|
-
# fall through to recurse into the RHS so a trailing
|
|
181
|
-
# `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.
|
|
182
157
|
when Prism::StatementsNode
|
|
183
158
|
walk_statements(node, prefix, in_singleton_class, module_function_active, out)
|
|
184
159
|
return
|
|
@@ -195,28 +170,37 @@ module Rigor
|
|
|
195
170
|
|
|
196
171
|
full = (prefix + [name]).join("::")
|
|
197
172
|
@namespace_kinds[full] = node.is_a?(Prism::ClassNode) ? :class : :module
|
|
173
|
+
record_superclass(node, full)
|
|
198
174
|
walk_namespace_body(node, prefix + [name], out)
|
|
199
175
|
true
|
|
200
176
|
end
|
|
201
177
|
|
|
202
|
-
# ADR-14
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# RBS
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
|
|
212
|
-
|
|
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.
|
|
187
|
+
def record_superclass(node, full)
|
|
188
|
+
return unless node.is_a?(Prism::ClassNode)
|
|
189
|
+
|
|
190
|
+
superclass = qualified_constant_path(node.superclass)
|
|
191
|
+
@class_superclasses[full] = superclass if superclass
|
|
192
|
+
end
|
|
193
|
+
|
|
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)`).
|
|
213
200
|
#
|
|
214
|
-
# The walker records the fully-qualified constant name
|
|
215
|
-
# in `@
|
|
216
|
-
#
|
|
217
|
-
# AND in `@namespace_kinds` so the leaf's `class`
|
|
218
|
-
# keyword wins over the intermediate-segment `module`
|
|
219
|
-
# 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.
|
|
220
204
|
def register_data_struct_shell(node, prefix)
|
|
221
205
|
return unless data_or_struct_call?(node.value)
|
|
222
206
|
|
|
@@ -240,16 +224,11 @@ module Rigor
|
|
|
240
224
|
DATA_STRUCT_SHELL_HEADS[receiver.name.to_s] == value.name
|
|
241
225
|
end
|
|
242
226
|
|
|
243
|
-
# Module / class bodies are walked through the
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
# inherit `module_function` via `Module`'s ancestor
|
|
249
|
-
# chain but don't honour it the same way at runtime, so
|
|
250
|
-
# tracking is only meaningful inside `ModuleNode`
|
|
251
|
-
# bodies. Generator emits `def self?.name` for the
|
|
252
|
-
# 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.
|
|
253
232
|
def walk_namespace_body(namespace_node, prefix, out)
|
|
254
233
|
return if namespace_node.body.nil?
|
|
255
234
|
|
|
@@ -282,24 +261,20 @@ module Rigor
|
|
|
282
261
|
out << [node, class_name, kind]
|
|
283
262
|
end
|
|
284
263
|
|
|
285
|
-
# Wraps `MethodCandidate.new` so every candidate carries
|
|
286
|
-
# the
|
|
287
|
-
#
|
|
288
|
-
# emission consults both to pick `module` vs `class`
|
|
289
|
-
# for each segment and to emit empty
|
|
290
|
-
# `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.
|
|
291
267
|
def build_candidate(**)
|
|
292
268
|
MethodCandidate.new(
|
|
293
269
|
namespace_kinds: @namespace_kinds,
|
|
294
270
|
class_shells: @class_shells.to_a,
|
|
271
|
+
class_superclasses: @class_superclasses,
|
|
295
272
|
**
|
|
296
273
|
)
|
|
297
274
|
end
|
|
298
275
|
|
|
299
|
-
# Returns "def self." (kind: :singleton),
|
|
300
|
-
# "def
|
|
301
|
-
# `module_function` region — both instance + singleton
|
|
302
|
-
# 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).
|
|
303
278
|
def method_def_prefix(class_name, method_name, kind)
|
|
304
279
|
return "def self." if kind == :singleton
|
|
305
280
|
return "def self?." if @module_function_methods.include?([class_name, method_name])
|
|
@@ -307,16 +282,11 @@ module Rigor
|
|
|
307
282
|
"def "
|
|
308
283
|
end
|
|
309
284
|
|
|
310
|
-
# Slice-4 follow-up surfaced by the Rigor self-dogfood:
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
# type contract downstream consumers (Steep, IDE, gem
|
|
316
|
-
# users) read. The default now skips private and
|
|
317
|
-
# protected methods; the `:include_private` flag
|
|
318
|
-
# restores the slice-4 behaviour for callers that want
|
|
319
|
-
# 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.
|
|
320
290
|
def visibility_excludes?(def_node, class_name, kind, scope_index)
|
|
321
291
|
return false if kind == :singleton
|
|
322
292
|
return false if @include_private
|
|
@@ -328,34 +298,25 @@ module Rigor
|
|
|
328
298
|
%i[private protected].include?(visibility)
|
|
329
299
|
end
|
|
330
300
|
|
|
331
|
-
# Ruby's `initialize` return value is never meaningful;
|
|
332
|
-
#
|
|
333
|
-
# body-typing path types the last expression (often an
|
|
334
|
-
# 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 `[]` /
|
|
335
303
|
# `{}`), which produces nonsense return types.
|
|
336
304
|
#
|
|
337
|
-
# Skipping `initialize` entirely is correct ONLY for
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
#
|
|
343
|
-
# runtime `def initialize(...)` and compares against
|
|
344
|
-
# the inherited `Object#initialize: () -> void`. The
|
|
345
|
-
# mismatch surfaces a `Ruby::MethodParameterMismatch`
|
|
346
|
-
# 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.
|
|
347
311
|
#
|
|
348
|
-
# Returning `nil` here causes `classify_def` to skip
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
# `(<param shape>) -> void` stub matching the
|
|
352
|
-
# 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.
|
|
353
315
|
def initialize_excludes?(def_node, kind)
|
|
354
316
|
return false unless kind == :instance
|
|
355
317
|
return false unless def_node.name == :initialize
|
|
356
318
|
|
|
357
|
-
# Default constructor with no params — skip; the
|
|
358
|
-
# Object#initialize RBS fallback covers it.
|
|
319
|
+
# Default constructor with no params — skip; the Object#initialize RBS fallback covers it.
|
|
359
320
|
params = def_node.parameters
|
|
360
321
|
params.nil? || trivial_initialize_params?(params)
|
|
361
322
|
end
|
|
@@ -372,21 +333,18 @@ module Rigor
|
|
|
372
333
|
kind == :instance && def_node.name == :initialize && !trivial_initialize_params?(def_node.parameters)
|
|
373
334
|
end
|
|
374
335
|
|
|
375
|
-
# Emits `def initialize: (<shape>) -> void`. The return
|
|
376
|
-
# is
|
|
377
|
-
#
|
|
378
|
-
# the runtime shape (required / optional / rest /
|
|
379
|
-
# 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).
|
|
380
339
|
#
|
|
381
|
-
# When `--params=observed` populates `@observations` for
|
|
382
|
-
# `
|
|
383
|
-
# `
|
|
384
|
-
#
|
|
385
|
-
# the per-position / per-keyword union of observed
|
|
386
|
-
# types; otherwise every position keeps `untyped` per
|
|
387
|
-
# 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.
|
|
388
344
|
def initialize_stub_candidate(path, def_node, class_name)
|
|
389
|
-
|
|
345
|
+
params = def_node.parameters
|
|
346
|
+
rbs = "def initialize: (#{render_initialize_param_list(params, class_name)})" \
|
|
347
|
+
"#{block_signature_suffix(params)} -> void"
|
|
390
348
|
build_candidate(
|
|
391
349
|
path: path, class_name: class_name, method_name: :initialize,
|
|
392
350
|
kind: :instance, classification: Classification::NEW_METHOD,
|
|
@@ -413,14 +371,25 @@ module Rigor
|
|
|
413
371
|
parts << "*untyped" if params.rest
|
|
414
372
|
params.keywords.each { |kw| parts << render_keyword_param(kw, observations) }
|
|
415
373
|
parts << "**untyped" if params.keyword_rest
|
|
416
|
-
parts << "?{ (?) -> void }" if params.block
|
|
417
374
|
parts.join(", ")
|
|
418
375
|
end
|
|
419
376
|
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
#
|
|
423
|
-
#
|
|
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
|
|
424
393
|
# overload the stub cannot express.
|
|
425
394
|
def initialize_observations(class_name, params)
|
|
426
395
|
return [] if @observations.empty?
|
|
@@ -478,11 +447,9 @@ module Rigor
|
|
|
478
447
|
end
|
|
479
448
|
end
|
|
480
449
|
|
|
481
|
-
# Required positionals only; the MVP's body-typing path
|
|
482
|
-
#
|
|
483
|
-
#
|
|
484
|
-
# `sig.skipped.complex-shape` reason until slices 3+
|
|
485
|
-
# 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.
|
|
486
453
|
def simple_parameter_shape?(params)
|
|
487
454
|
return true if params.nil?
|
|
488
455
|
return false unless params.is_a?(Prism::ParametersNode)
|
|
@@ -494,27 +461,18 @@ module Rigor
|
|
|
494
461
|
params.block.nil?
|
|
495
462
|
end
|
|
496
463
|
|
|
497
|
-
# Mirrors the `def.return-type-mismatch` rule's body-type
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
# parameter bindings (typed `untyped` per the indexer's
|
|
501
|
-
# default) come from `with_local` inside
|
|
502
|
-
# `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
|
|
503
467
|
# body proves *given an untyped argument tuple*.
|
|
504
468
|
#
|
|
505
|
-
# Post-dogfood enhancement: walk the body's AST for
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
#
|
|
511
|
-
#
|
|
512
|
-
# `V | nil` via `return nil unless ...`). The walk
|
|
513
|
-
# excludes nested `DefNode` / lambda / block scopes
|
|
514
|
-
# whose returns belong to different methods.
|
|
515
|
-
# Delegates to {Rigor::Inference::DefReturnTyper} — the same
|
|
516
|
-
# body-typing + explicit-return-union the `rigor annotate`
|
|
517
|
-
# 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.
|
|
518
476
|
def infer_return_type(def_node, scope_index)
|
|
519
477
|
Inference::DefReturnTyper.call(def_node, scope_index)
|
|
520
478
|
end
|
|
@@ -523,13 +481,10 @@ module Rigor
|
|
|
523
481
|
return true if type.is_a?(Type::Dynamic)
|
|
524
482
|
return true if type.respond_to?(:top?) && type.top?.yes?
|
|
525
483
|
|
|
526
|
-
# Post-dogfood: when explicit-return union absorbs
|
|
527
|
-
# Dynamic
|
|
528
|
-
# `
|
|
529
|
-
#
|
|
530
|
-
# `sig.skipped.untyped-return` case — obscures rather
|
|
531
|
-
# than helps — so the skip check considers the erased
|
|
532
|
-
# 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.
|
|
533
488
|
type.respond_to?(:erase_to_rbs) && type.erase_to_rbs == "untyped"
|
|
534
489
|
end
|
|
535
490
|
|
|
@@ -596,26 +551,17 @@ module Rigor
|
|
|
596
551
|
nil
|
|
597
552
|
end
|
|
598
553
|
|
|
599
|
-
# ADR-14 § "What 'more precise' means". The MVP uses the
|
|
600
|
-
#
|
|
601
|
-
# reserved by `Inference::Acceptance` and lands in a
|
|
602
|
-
# 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
|
|
603
556
|
# never classify a same-string round-trip as tighter.
|
|
604
557
|
#
|
|
605
|
-
# The `loses_declared_union_member?` guard added after
|
|
606
|
-
# the
|
|
607
|
-
#
|
|
608
|
-
#
|
|
609
|
-
#
|
|
610
|
-
#
|
|
611
|
-
#
|
|
612
|
-
# `false | true` shapes / `Float | Integer` numeric
|
|
613
|
-
# alternates routinely look "tighter" while actually
|
|
614
|
-
# dropping reachable branches. Treating those as
|
|
615
|
-
# equivalent matches the project rule that an
|
|
616
|
-
# inferred tightening contradicting an existing RBS
|
|
617
|
-
# member set is suspected incomplete inference until
|
|
618
|
-
# 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.
|
|
619
565
|
def tighter?(declared, inferred)
|
|
620
566
|
return false if inferred.is_a?(Type::Dynamic)
|
|
621
567
|
return false if loses_declared_lenience?(declared, inferred)
|
|
@@ -627,24 +573,16 @@ module Rigor
|
|
|
627
573
|
!backward.yes?
|
|
628
574
|
end
|
|
629
575
|
|
|
630
|
-
# Composite guard: refuse to classify as tighter-return
|
|
631
|
-
#
|
|
632
|
-
# inferred form removes. Three cases all signal
|
|
633
|
-
# 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:
|
|
634
578
|
#
|
|
635
|
-
# 1. Top-level union losing one or more declared
|
|
636
|
-
# members. `return nil unless ...` paths, two-valued
|
|
579
|
+
# 1. Top-level union losing one or more declared members. `return nil unless ...` paths, two-valued
|
|
637
580
|
# booleans, `Float | Integer` numeric alternates.
|
|
638
|
-
# 2. Generic collection narrowed to a fixed shape.
|
|
639
|
-
#
|
|
640
|
-
# HashShape — the body's last expression was a
|
|
641
|
-
# 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
|
|
642
583
|
# contract.
|
|
643
|
-
# 3. `untyped` type-arg replaced by a concrete form.
|
|
644
|
-
#
|
|
645
|
-
# author's intentional value-type lenience; the
|
|
646
|
-
# inference's narrower Union should not override
|
|
647
|
-
# 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.
|
|
648
586
|
def loses_declared_lenience?(declared, inferred)
|
|
649
587
|
loses_declared_union_member?(declared, inferred) ||
|
|
650
588
|
narrows_collection_to_shape?(declared, inferred) ||
|
|
@@ -679,19 +617,13 @@ module Rigor
|
|
|
679
617
|
inferred.is_a?(Type::Tuple) || inferred.is_a?(Type::HashShape)
|
|
680
618
|
end
|
|
681
619
|
|
|
682
|
-
# Heuristic added after the third-round self-dogfood:
|
|
683
|
-
# `
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
# `Integer` because callers add events through other
|
|
690
|
-
# methods. The signal is "the body's last expression
|
|
691
|
-
# is NOT a directly-authored literal but the inferred
|
|
692
|
-
# type IS a Constant"; in that case the precision
|
|
693
|
-
# came from inference over an internal computation,
|
|
694
|
-
# 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.
|
|
695
627
|
def computed_literal_tightening?(inferred, def_node)
|
|
696
628
|
return false unless inferred.is_a?(Type::Constant)
|
|
697
629
|
|
|
@@ -749,22 +681,17 @@ module Rigor
|
|
|
749
681
|
"#{prefix}#{def_node.name}: #{head} -> #{paren_wrap_union(elaborated_rbs(inferred))}"
|
|
750
682
|
end
|
|
751
683
|
|
|
752
|
-
# Routes the inferred carrier through {TypeElaborator}
|
|
753
|
-
#
|
|
754
|
-
#
|
|
755
|
-
#
|
|
756
|
-
# elaborator consults the class's RBS-declared
|
|
757
|
-
# 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`.
|
|
758
688
|
def elaborated_rbs(type)
|
|
759
689
|
TypeElaborator.elaborate(type, environment: @environment).erase_to_rbs
|
|
760
690
|
end
|
|
761
691
|
|
|
762
|
-
# RBS / Steep require return-position unions to be
|
|
763
|
-
#
|
|
764
|
-
#
|
|
765
|
-
# the parser because the trailing `| 1` isn't a valid
|
|
766
|
-
# method-type start. Wrap when the erased form is a
|
|
767
|
-
# 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
|
|
768
695
|
# forms (e.g. `Array[A | B]`) parse without wrapping.
|
|
769
696
|
def paren_wrap_union(rendered)
|
|
770
697
|
top_level_union?(rendered) ? "(#{rendered})" : rendered
|
|
@@ -790,14 +717,11 @@ module Rigor
|
|
|
790
717
|
params.is_a?(Prism::ParametersNode) ? params.requireds.size : 0
|
|
791
718
|
end
|
|
792
719
|
|
|
793
|
-
# Per ADR-5 clause 2 the default is `untyped` for every
|
|
794
|
-
#
|
|
795
|
-
#
|
|
796
|
-
#
|
|
797
|
-
#
|
|
798
|
-
# Observations from arities other than the def's count
|
|
799
|
-
# are discarded — they describe a different overload
|
|
800
|
-
# 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.
|
|
801
725
|
def render_param_list(class_name, method_name, arity)
|
|
802
726
|
tuples = matching_observations(class_name, method_name, arity)
|
|
803
727
|
return Array.new(arity, "untyped").join(", ") if tuples.empty?
|
|
@@ -816,25 +740,18 @@ module Rigor
|
|
|
816
740
|
return "untyped" if types.empty?
|
|
817
741
|
return elaborated_rbs(types.first) if types.size == 1
|
|
818
742
|
|
|
819
|
-
# `Type::Combinator.union` dedupes by structural type
|
|
820
|
-
#
|
|
821
|
-
#
|
|
822
|
-
#
|
|
823
|
-
# `Constant<"Alice">` / `Constant<"Bob">` envelopes),
|
|
824
|
-
# so the sig-gen layer only needs to elaborate bare
|
|
825
|
-
# 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.
|
|
826
747
|
elaborated_rbs(Type::Combinator.union(*types))
|
|
827
748
|
end
|
|
828
749
|
|
|
829
|
-
# ADR-14 slice 4 — `attr_reader` / `attr_writer` /
|
|
830
|
-
#
|
|
831
|
-
#
|
|
832
|
-
#
|
|
833
|
-
#
|
|
834
|
-
# `Scope#class_ivars_for(class_name)`. `attr_reader` adds
|
|
835
|
-
# one reader candidate; `attr_writer` adds one
|
|
836
|
-
# `name=`-method writer candidate; `attr_accessor` adds
|
|
837
|
-
# 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.
|
|
838
755
|
ATTR_METHOD_NAMES = %i[attr_reader attr_writer attr_accessor].freeze
|
|
839
756
|
private_constant :ATTR_METHOD_NAMES
|
|
840
757
|
|
|
@@ -845,11 +762,9 @@ module Rigor
|
|
|
845
762
|
}.freeze
|
|
846
763
|
private_constant :ATTR_KINDS
|
|
847
764
|
|
|
848
|
-
# Per-file context the attr_* walker threads through its
|
|
849
|
-
#
|
|
850
|
-
#
|
|
851
|
-
# built by {#build_observed_ivar_map}; it is empty when sig-gen
|
|
852
|
-
# 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`.
|
|
853
768
|
AttrWalkContext = Struct.new(:path, :scope_index, :obs_ivar_map, :out, keyword_init: true)
|
|
854
769
|
private_constant :AttrWalkContext
|
|
855
770
|
|
|
@@ -874,8 +789,7 @@ module Rigor
|
|
|
874
789
|
walk_attr_calls(node.body, prefix, true, ctx) if node.body
|
|
875
790
|
return
|
|
876
791
|
when Prism::DefNode
|
|
877
|
-
# Skip method bodies — attr_* there would refer to
|
|
878
|
-
# 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
|
|
879
793
|
# class-level declaration.
|
|
880
794
|
return
|
|
881
795
|
when Prism::CallNode
|
|
@@ -905,21 +819,15 @@ module Rigor
|
|
|
905
819
|
Source::Literals.symbol_arguments(call_node)
|
|
906
820
|
end
|
|
907
821
|
|
|
908
|
-
# Returns a closure that looks up `:@<attr_name>` in the
|
|
909
|
-
#
|
|
910
|
-
#
|
|
911
|
-
#
|
|
912
|
-
# before any statement evaluation runs, so the lookup
|
|
913
|
-
# works even when attr_* declarations come before the
|
|
914
|
-
# 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.
|
|
915
826
|
#
|
|
916
|
-
# When `obs_ivar_map` is non-empty (i.e. `--params=observed`
|
|
917
|
-
#
|
|
918
|
-
#
|
|
919
|
-
#
|
|
920
|
-
# as `untyped` — the observation-derived type is substituted.
|
|
921
|
-
# This lets `attr_reader :name` emit a concrete type when
|
|
922
|
-
# `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
|
|
923
831
|
# observation scan.
|
|
924
832
|
def ivar_type_lookup(scope_index, class_name, obs_ivar_map = {})
|
|
925
833
|
any_scope = scope_index.each_value.first
|
|
@@ -933,13 +841,10 @@ module Rigor
|
|
|
933
841
|
end
|
|
934
842
|
end
|
|
935
843
|
|
|
936
|
-
# Build a { class_name => { attr_name_sym => Type } } map that
|
|
937
|
-
#
|
|
938
|
-
#
|
|
939
|
-
#
|
|
940
|
-
# supplied). Matches the pattern `@ivar_name = param_name` where
|
|
941
|
-
# `param_name` is a required / optional positional or keyword
|
|
942
|
-
# 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`.
|
|
943
848
|
def build_observed_ivar_map(root)
|
|
944
849
|
return {} if @observations.empty?
|
|
945
850
|
|
|
@@ -970,9 +875,8 @@ module Rigor
|
|
|
970
875
|
node.compact_child_nodes.each { |c| collect_init_ivar_obs(c, prefix, result) }
|
|
971
876
|
end
|
|
972
877
|
|
|
973
|
-
# Derive { attr_name_sym => Type } for a single `def initialize`
|
|
974
|
-
#
|
|
975
|
-
# 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.
|
|
976
880
|
def ivar_obs_from_initialize(class_name, def_node)
|
|
977
881
|
obs_list = @observations[[class_name, :initialize]]
|
|
978
882
|
return {} if obs_list.nil? || obs_list.empty?
|
|
@@ -986,8 +890,8 @@ module Rigor
|
|
|
986
890
|
build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
987
891
|
end
|
|
988
892
|
|
|
989
|
-
# Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }`
|
|
990
|
-
#
|
|
893
|
+
# Map `{ ivar_name => param_name }` → `{ attr_name_sym => Type }` by looking up each param's observation
|
|
894
|
+
# types and unioning them.
|
|
991
895
|
def build_ivar_obs_type_map(ivar_to_param, param_index, obs_list)
|
|
992
896
|
ivar_to_param.filter_map do |ivar_name, param_name|
|
|
993
897
|
types = collect_param_obs_types(obs_list, param_name, param_index[param_name])
|
|
@@ -998,8 +902,8 @@ module Rigor
|
|
|
998
902
|
end.to_h
|
|
999
903
|
end
|
|
1000
904
|
|
|
1001
|
-
# Collect observed argument types for a single parameter across all
|
|
1002
|
-
#
|
|
905
|
+
# Collect observed argument types for a single parameter across all call-site observations. Returns an
|
|
906
|
+
# array of Type objects (may be empty).
|
|
1003
907
|
def collect_param_obs_types(obs_list, param_name, param_info)
|
|
1004
908
|
case param_info[:kind]
|
|
1005
909
|
when :positional then obs_list.filter_map { |obs| obs.positional[param_info[:index]] }
|
|
@@ -1008,9 +912,8 @@ module Rigor
|
|
|
1008
912
|
end
|
|
1009
913
|
end
|
|
1010
914
|
|
|
1011
|
-
# Map param_name_sym → { kind: :positional, index: N } or
|
|
1012
|
-
#
|
|
1013
|
-
# 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.
|
|
1014
917
|
def build_init_param_index(parameters)
|
|
1015
918
|
index = {}
|
|
1016
919
|
offset = 0
|
|
@@ -1031,10 +934,9 @@ module Rigor
|
|
|
1031
934
|
index
|
|
1032
935
|
end
|
|
1033
936
|
|
|
1034
|
-
# Walk a def body for direct `@ivar = local_var` assignments
|
|
1035
|
-
#
|
|
1036
|
-
#
|
|
1037
|
-
# 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.
|
|
1038
940
|
def scan_ivar_param_assignments(node, param_names, result)
|
|
1039
941
|
return unless node.is_a?(Prism::Node)
|
|
1040
942
|
|
|
@@ -1118,14 +1020,11 @@ module Rigor
|
|
|
1118
1020
|
)
|
|
1119
1021
|
end
|
|
1120
1022
|
|
|
1121
|
-
# Slice 4 emits attr_* in the long-form `def` spelling so
|
|
1122
|
-
# the
|
|
1123
|
-
#
|
|
1124
|
-
#
|
|
1125
|
-
#
|
|
1126
|
-
# (slice 2) treats existing `attr_*` declarations as
|
|
1127
|
-
# user-authored so a paired source-side `attr_reader`
|
|
1128
|
-
# 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.
|
|
1129
1028
|
def render_attr_rbs_line(method_name, variant, ivar_type)
|
|
1130
1029
|
erased = elaborated_rbs(ivar_type)
|
|
1131
1030
|
wrapped = paren_wrap_union(erased)
|