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
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# ADR-82 — the effective {DynamicOrigin} cause of a receiver *expression*, unifying the two provenance
|
|
8
|
+
# lookups so a chain and a bare variable resolve the same way:
|
|
9
|
+
#
|
|
10
|
+
# - the cause recorded on the expression's own node (`Scope#dynamic_origins`), covering a call node whose
|
|
11
|
+
# dispatch recorded a cause (WD2's user-method tiers, or WD6's chain inheritance), and
|
|
12
|
+
# - for a bare local / instance-variable read, the cause propagated from its binding (WD1's
|
|
13
|
+
# `Scope#local_origins` / `#ivar_origins`) — the read node itself carries none, the cause was recorded
|
|
14
|
+
# on the assignment's rhs.
|
|
15
|
+
#
|
|
16
|
+
# Consulted by `ProtectionScanner` (to label a hole) and `ExpressionTyper` (WD6 — to inherit a Dynamic
|
|
17
|
+
# receiver's cause onto the call it produces, so provenance survives a method chain).
|
|
18
|
+
module OriginLookup
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# @param scope [Rigor::Scope]
|
|
22
|
+
# @param node [Prism::Node, nil] the receiver expression's node (nil for an implicit-self receiver)
|
|
23
|
+
# @return [Symbol, nil] the effective dynamic-origin cause, or nil when none is known
|
|
24
|
+
def origin_for(scope, node)
|
|
25
|
+
return nil if node.nil?
|
|
26
|
+
|
|
27
|
+
scope.dynamic_origins[node] || propagated(scope, node)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def propagated(scope, node)
|
|
31
|
+
case node
|
|
32
|
+
when Prism::LocalVariableReadNode then scope.local_origin(node.name)
|
|
33
|
+
when Prism::InstanceVariableReadNode then scope.ivar_origin(node.name)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -7,10 +7,10 @@ require_relative "../source/node_walker"
|
|
|
7
7
|
|
|
8
8
|
module Rigor
|
|
9
9
|
module Inference
|
|
10
|
-
# Pure argument-type classification for {ParameterInferenceCollector} — no
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
10
|
+
# Pure argument-type classification for {ParameterInferenceCollector} — no collector state,
|
|
11
|
+
# so it lives outside the orchestration class. Decides which argument types may seed a
|
|
12
|
+
# parameter (concrete enough for the protection metric to bite) and widens a literal
|
|
13
|
+
# argument to its nominal.
|
|
14
14
|
module ParameterArgTypes
|
|
15
15
|
module_function
|
|
16
16
|
|
|
@@ -20,10 +20,9 @@ module Rigor
|
|
|
20
20
|
FalseClass => "FalseClass", NilClass => "NilClass"
|
|
21
21
|
}.freeze
|
|
22
22
|
|
|
23
|
-
# A parameter holds a *value of* a type across its lifetime, not a pinned
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# collapses to `String`.
|
|
23
|
+
# A parameter holds a *value of* a type across its lifetime, not a pinned literal — so a
|
|
24
|
+
# `Constant<"text">` argument widens to its nominal (`String`); recurses through unions so
|
|
25
|
+
# `Constant<"a"> | Constant<"b">` collapses to `String`.
|
|
27
26
|
def widen_for_param(type)
|
|
28
27
|
case type
|
|
29
28
|
when Type::Constant
|
|
@@ -41,10 +40,10 @@ module Rigor
|
|
|
41
40
|
nil
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
# The dispatch class for a receiver type, for the subset the collector
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
43
|
+
# The dispatch class for a receiver type, for the subset the collector resolves to a user
|
|
44
|
+
# `def` (mirrors `CheckRules#concrete_class_name` for the carriers a user-method receiver
|
|
45
|
+
# is typed as). `class_name_of` is the Nominal/Singleton-only variant for an implicit-self
|
|
46
|
+
# `self_type`.
|
|
48
47
|
def concrete_class_name(type)
|
|
49
48
|
case type
|
|
50
49
|
when Type::Nominal, Type::Singleton then type.class_name
|
|
@@ -57,9 +56,8 @@ module Rigor
|
|
|
57
56
|
type.class_name if type.is_a?(Type::Nominal) || type.is_a?(Type::Singleton)
|
|
58
57
|
end
|
|
59
58
|
|
|
60
|
-
# Whether `type` is too gradual to seed (not a concrete dispatch target) —
|
|
61
|
-
#
|
|
62
|
-
# concrete only when every arm is).
|
|
59
|
+
# Whether `type` is too gradual to seed (not a concrete dispatch target) — the negation of
|
|
60
|
+
# `ProtectionScanner#concrete_receiver?` (a union is concrete only when every arm is).
|
|
63
61
|
def non_concrete?(type)
|
|
64
62
|
case type
|
|
65
63
|
when Type::Dynamic, Type::Top, Type::Bot then true
|
|
@@ -71,55 +69,50 @@ module Rigor
|
|
|
71
69
|
|
|
72
70
|
# ADR-67 WD3 + WD5 — call-site parameter type inference (capped fixpoint).
|
|
73
71
|
#
|
|
74
|
-
# A `def` parameter with no RBS signature types `untyped` (the gradual entry
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# parameter is typed one hop further per round (round 1 alone is the
|
|
84
|
-
# single-level pass).
|
|
72
|
+
# A `def` parameter with no RBS signature types `untyped` (the gradual entry point), so a
|
|
73
|
+
# param flowing into an ivar or a receiver drains everything downstream to `Dynamic`. This
|
|
74
|
+
# pass closes that hole: it walks every project file, types each call's positional
|
|
75
|
+
# arguments in their lexical scope, resolves which user-defined `def` each call targets,
|
|
76
|
+
# and records the **union of resolved call-site argument types** per parameter — TypeProf's
|
|
77
|
+
# "a parameter's type is the union of every actual argument across all call sites". WD5: it
|
|
78
|
+
# iterates (cap {DEFAULT_ROUNDS}), re-seeding each round with the previous round's inferred
|
|
79
|
+
# parameters, so a parameter passed *another* parameter is typed one hop further per round
|
|
80
|
+
# (round 1 alone is the single-level pass).
|
|
85
81
|
#
|
|
86
82
|
# The result is keyed by `[class_name, method_name, kind]` — the same triple
|
|
87
|
-
# `StatementEvaluator#build_method_entry_scope` reconstructs from the lexical
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
# WD1), so being wrong cannot manufacture a false positive at a caller.
|
|
83
|
+
# `StatementEvaluator#build_method_entry_scope` reconstructs from the lexical class path —
|
|
84
|
+
# so the consumer can seed an undeclared parameter with its inferred type. The table is
|
|
85
|
+
# precision-additive only: it never feeds a parameter-boundary diagnostic (an inferred type
|
|
86
|
+
# lives solely as a body local, and the boundary rules consult RBS, not body locals — see
|
|
87
|
+
# ADR-67 WD1), so being wrong cannot manufacture a false positive at a caller.
|
|
93
88
|
#
|
|
94
|
-
# Soundness (WD4): the inferred type is a sound over-approximation only when
|
|
95
|
-
#
|
|
96
|
-
# `
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
89
|
+
# Soundness (WD4): the inferred type is a sound over-approximation only when every
|
|
90
|
+
# contributed call site resolves to a concrete argument type. Any `Dynamic` / `Top` /
|
|
91
|
+
# `Bot` argument (a `send`/dynamic-dispatch caller, or a parameter not yet typed in an
|
|
92
|
+
# earlier round) **poisons** the parameter, which then contributes nothing this round (it
|
|
93
|
+
# may type in a later round once its own argument resolves). Unresolved call sites do not
|
|
94
|
+
# contribute.
|
|
100
95
|
#
|
|
101
|
-
# What it does NOT do yet (deferred — the check-wiring slice): feeding the
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# consumer is the protection metric, which runs no diagnostics).
|
|
96
|
+
# What it does NOT do yet (deferred — the check-wiring slice): feeding the table into the
|
|
97
|
+
# `check` walk (only `coverage --protection` consumes it today), keyword / optional / rest
|
|
98
|
+
# / block parameters, inherited-method receivers, top-level helpers, and a rigorous
|
|
99
|
+
# closed-call-site-set proof (the union is optimistic over resolved sites — acceptable
|
|
100
|
+
# because the only consumer is the protection metric, which runs no diagnostics).
|
|
107
101
|
class ParameterInferenceCollector
|
|
108
102
|
EMPTY = {}.freeze
|
|
109
103
|
|
|
110
|
-
# A defensive widening cap (ADR-41): a parameter unioned from more than
|
|
111
|
-
#
|
|
112
|
-
#
|
|
104
|
+
# A defensive widening cap (ADR-41): a parameter unioned from more than this many distinct
|
|
105
|
+
# concrete call-site types is widened to `untyped` (poisoned) rather than carrying an
|
|
106
|
+
# unbounded union.
|
|
113
107
|
MAX_CALL_SITE_TYPES = 16
|
|
114
108
|
|
|
115
|
-
# WD5 — the call-site union is a worklist fixpoint: each round re-types the
|
|
116
|
-
#
|
|
117
|
-
# parameter
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
# is the single-level pass.
|
|
109
|
+
# WD5 — the call-site union is a worklist fixpoint: each round re-types the project with
|
|
110
|
+
# the previous round's inferred parameters seeded, so a parameter passed *another*
|
|
111
|
+
# parameter is typed one hop further per round. Capped (no true-convergence requirement —
|
|
112
|
+
# the metric tolerates a bounded approximation, and the table can oscillate at the margin
|
|
113
|
+
# since a newly resolved receiver can surface a fresh untyped-argument call site). The cap
|
|
114
|
+
# matches the `Inference::BodyFixpoint` cap-3 convention. Round 1 alone is the
|
|
115
|
+
# single-level pass.
|
|
123
116
|
DEFAULT_ROUNDS = 3
|
|
124
117
|
|
|
125
118
|
# @param files [Array<String>] project `.rb` paths to scan for call sites.
|
|
@@ -136,10 +129,10 @@ module Rigor
|
|
|
136
129
|
@environment = environment
|
|
137
130
|
@target_ruby = target_ruby
|
|
138
131
|
@max_rounds = max_rounds
|
|
139
|
-
# Reset per round (see {#run_round}). `[[class, method, kind], param_sym]`
|
|
140
|
-
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
132
|
+
# Reset per round (see {#run_round}). `[[class, method, kind], param_sym]` => [Type] of
|
|
133
|
+
# observed concrete arguments (a default-block Hash, not a `{}` literal, so the
|
|
134
|
+
# analyzer types its reads generically — {#finalize}), plus the ids widened to
|
|
135
|
+
# `untyped` (an untyped / over-cap argument).
|
|
143
136
|
@type_observations = Hash.new { |hash, id| hash[id] = [] }
|
|
144
137
|
@poisoned_params = Set.new
|
|
145
138
|
end
|
|
@@ -159,8 +152,8 @@ module Rigor
|
|
|
159
152
|
|
|
160
153
|
private
|
|
161
154
|
|
|
162
|
-
# Parse every project file once; rounds re-index the cached ASTs against an
|
|
163
|
-
#
|
|
155
|
+
# Parse every project file once; rounds re-index the cached ASTs against an evolving seed
|
|
156
|
+
# rather than re-parsing.
|
|
164
157
|
def parse_all
|
|
165
158
|
@files.filter_map do |path|
|
|
166
159
|
result = Prism.parse(File.read(path), filepath: path, version: @target_ruby)
|
|
@@ -170,8 +163,8 @@ module Rigor
|
|
|
170
163
|
end
|
|
171
164
|
end
|
|
172
165
|
|
|
173
|
-
# One fixpoint round: re-type every file with `seed_table` (the previous
|
|
174
|
-
#
|
|
166
|
+
# One fixpoint round: re-type every file with `seed_table` (the previous round's inferred
|
|
167
|
+
# parameters) seeded, collecting the next round's table.
|
|
175
168
|
def run_round(parsed, discovery_tables, seed_table)
|
|
176
169
|
@type_observations = Hash.new { |hash, id| hash[id] = [] }
|
|
177
170
|
@poisoned_params = Set.new
|
|
@@ -185,10 +178,10 @@ module Rigor
|
|
|
185
178
|
finalize
|
|
186
179
|
end
|
|
187
180
|
|
|
188
|
-
# A scope carrying the cross-file discovery index (so `Foo.new` receivers
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
181
|
+
# A scope carrying the cross-file discovery index (so `Foo.new` receivers and
|
|
182
|
+
# implicit-self calls resolve to a user `def`) plus the prior round's inferred parameters
|
|
183
|
+
# (so an argument that reads a parameter types to its current inferred type — the WD5
|
|
184
|
+
# propagation).
|
|
192
185
|
def build_seed_scope(discovery_tables, seed_table)
|
|
193
186
|
base = Scope.empty(environment: @environment)
|
|
194
187
|
tables = discovery_tables
|
|
@@ -209,9 +202,8 @@ module Rigor
|
|
|
209
202
|
end
|
|
210
203
|
tables
|
|
211
204
|
rescue StandardError
|
|
212
|
-
# Discovery is best-effort; a malformed corner of the project must not
|
|
213
|
-
#
|
|
214
|
-
# resolves fewer call sites.
|
|
205
|
+
# Discovery is best-effort; a malformed corner of the project must not crash the
|
|
206
|
+
# protection scan. Without discovery the collector simply resolves fewer call sites.
|
|
215
207
|
{}
|
|
216
208
|
end
|
|
217
209
|
|
|
@@ -240,35 +232,41 @@ module Rigor
|
|
|
240
232
|
return if callee.nil?
|
|
241
233
|
|
|
242
234
|
class_name, method, kind, def_node = callee
|
|
243
|
-
requireds =
|
|
244
|
-
|
|
235
|
+
requireds = leading_requireds(def_node)
|
|
236
|
+
# Enough positional arguments to fill every leading required (extra args map to trailing
|
|
237
|
+
# optional / rest parameters, which this pass does not infer). Fewer would be an arity error
|
|
238
|
+
# or a mis-resolved callee; skip rather than mis-attribute.
|
|
239
|
+
return if requireds.nil? || args.size < requireds.size
|
|
245
240
|
|
|
246
241
|
key = [class_name, method, kind]
|
|
247
|
-
|
|
248
|
-
arg_scope = index[
|
|
249
|
-
accumulate(key,
|
|
242
|
+
requireds.each_with_index do |param, i|
|
|
243
|
+
arg_scope = index[args[i]]
|
|
244
|
+
accumulate(key, param.name, arg_scope&.type_of(args[i]))
|
|
250
245
|
end
|
|
251
246
|
end
|
|
252
247
|
|
|
253
|
-
# The plain positional arguments
|
|
254
|
-
#
|
|
255
|
-
#
|
|
256
|
-
#
|
|
248
|
+
# The plain leading positional arguments. A trailing keyword hash (`f(x, k: 1)`) or block-pass
|
|
249
|
+
# (`f(x, &blk)`) is dropped — it occupies no positional slot, so the leading args still map to
|
|
250
|
+
# the leading required parameters. Returns nil when the call carries a splat (`f(*xs, x)`) or
|
|
251
|
+
# forwarding (`f(...)`) argument: those make an argument's position depend on runtime length,
|
|
252
|
+
# breaking the positional-index ↔ parameter mapping, so the call site is skipped.
|
|
257
253
|
def positional_args(call_node)
|
|
258
254
|
arguments = call_node.arguments
|
|
259
255
|
return [] if arguments.nil?
|
|
260
256
|
|
|
261
257
|
list = arguments.arguments
|
|
262
|
-
return nil if list.any? { |arg|
|
|
258
|
+
return nil if list.any? { |arg| position_breaking_argument?(arg) }
|
|
263
259
|
|
|
264
|
-
list
|
|
260
|
+
list.reject { |arg| non_positional_argument?(arg) }
|
|
265
261
|
end
|
|
266
262
|
|
|
267
|
-
def
|
|
268
|
-
arg.is_a?(Prism::SplatNode) ||
|
|
269
|
-
|
|
263
|
+
def position_breaking_argument?(arg)
|
|
264
|
+
arg.is_a?(Prism::SplatNode) || arg.is_a?(Prism::ForwardingArgumentsNode)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def non_positional_argument?(arg)
|
|
268
|
+
arg.is_a?(Prism::KeywordHashNode) ||
|
|
270
269
|
arg.is_a?(Prism::BlockArgumentNode) ||
|
|
271
|
-
arg.is_a?(Prism::ForwardingArgumentsNode) ||
|
|
272
270
|
arg.is_a?(Prism::AssocNode) ||
|
|
273
271
|
arg.is_a?(Prism::AssocSplatNode)
|
|
274
272
|
end
|
|
@@ -307,16 +305,19 @@ module Rigor
|
|
|
307
305
|
per_class && per_class[method]
|
|
308
306
|
end
|
|
309
307
|
|
|
310
|
-
# The required-positional parameters
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
def
|
|
308
|
+
# The leading required-positional parameters — the ones a call's leading positional arguments
|
|
309
|
+
# map to. Ruby orders parameters requireds-first, so optional / rest / keyword / block
|
|
310
|
+
# parameters that FOLLOW the requireds do not disturb the leading positional-index ↔
|
|
311
|
+
# required-parameter mapping; this covers the common `def f(x, opts = {}, **kw)` /
|
|
312
|
+
# `def f(x, y, &block)` shapes the earlier all-required contract skipped entirely. Returns nil
|
|
313
|
+
# only when a required slot is a destructured `(a, b)` (no bindable name) or a post-rest
|
|
314
|
+
# required (`def f(a, *b, c)` — `c` maps to a trailing arg, breaking the leading-prefix
|
|
315
|
+
# assumption for the whole call), which would mis-attribute arguments.
|
|
316
|
+
def leading_requireds(def_node)
|
|
315
317
|
params = def_node.parameters
|
|
316
318
|
return [] if params.nil?
|
|
317
319
|
return nil unless params.is_a?(Prism::ParametersNode)
|
|
318
|
-
return nil unless params.
|
|
319
|
-
params.keywords.empty? && params.keyword_rest.nil? && params.block.nil?
|
|
320
|
+
return nil unless params.posts.empty?
|
|
320
321
|
return nil unless params.requireds.all?(Prism::RequiredParameterNode)
|
|
321
322
|
|
|
322
323
|
params.requireds
|
|
@@ -335,26 +336,25 @@ module Rigor
|
|
|
335
336
|
end
|
|
336
337
|
end
|
|
337
338
|
|
|
338
|
-
# A poisoned parameter is dropped from the observation store and recorded
|
|
339
|
-
#
|
|
340
|
-
# param]` pair.
|
|
339
|
+
# A poisoned parameter is dropped from the observation store and recorded so later call
|
|
340
|
+
# sites short-circuit. `id` is the `[[class, method, kind], param]` pair.
|
|
341
341
|
def poison(id)
|
|
342
342
|
@poisoned_params << id
|
|
343
343
|
@type_observations.delete(id)
|
|
344
344
|
end
|
|
345
345
|
|
|
346
346
|
def finalize
|
|
347
|
-
# `result` is a default-block Hash (not a `{}` literal) so the analyzer
|
|
348
|
-
#
|
|
349
|
-
#
|
|
347
|
+
# `result` is a default-block Hash (not a `{}` literal) so the analyzer types its reads
|
|
348
|
+
# generically rather than folding the empty shape — the nesting writes stay plain
|
|
349
|
+
# assignments, no literal-fold conditions.
|
|
350
350
|
result = Hash.new { |hash, key| hash[key] = {} }
|
|
351
351
|
@type_observations.each do |id, observations|
|
|
352
352
|
next if @poisoned_params.include?(id)
|
|
353
353
|
next if observations.empty?
|
|
354
354
|
|
|
355
355
|
union = Type::Combinator.union(*observations)
|
|
356
|
-
# A union that collapsed to a non-concrete shape (e.g. a gradual arm
|
|
357
|
-
#
|
|
356
|
+
# A union that collapsed to a non-concrete shape (e.g. a gradual arm leaked in) is no
|
|
357
|
+
# better than `untyped`; drop it.
|
|
358
358
|
next if ParameterArgTypes.non_concrete?(union)
|
|
359
359
|
|
|
360
360
|
key, param = id
|
|
@@ -6,15 +6,13 @@ require_relative "scope_indexer"
|
|
|
6
6
|
|
|
7
7
|
module Rigor
|
|
8
8
|
module Inference
|
|
9
|
-
# Measures the *type quality* of inferred expressions — not whether the
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# Measures the *type quality* of inferred expressions — not whether the engine recognises an AST node
|
|
10
|
+
# class (that is `CoverageScanner`'s job), but whether the type it produces carries useful static
|
|
11
|
+
# information.
|
|
12
12
|
#
|
|
13
|
-
# Each visited *expression* node is classified into one of eight
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# wrappers, clause headers — are skipped; see
|
|
17
|
-
# {NON_EXPRESSION_NODE_TYPES}):
|
|
13
|
+
# Each visited *expression* node is classified into one of eight precision tiers (non-expression syntax
|
|
14
|
+
# nodes — argument / parameter lists, parameter declarations, hash pairs, statement wrappers, clause
|
|
15
|
+
# headers — are skipped; see {NON_EXPRESSION_NODE_TYPES}):
|
|
18
16
|
#
|
|
19
17
|
# :constant — Constant[T]: literal value known exactly
|
|
20
18
|
# :nominal — Nominal/Singleton: class identity known
|
|
@@ -25,42 +23,35 @@ module Rigor
|
|
|
25
23
|
# :dynamic_top — Dynamic[Top]: completely opaque (the "untyped" hole)
|
|
26
24
|
# :top — Top: universal supertype (no information)
|
|
27
25
|
#
|
|
28
|
-
# The summary exposes `precision_ratio` (constant+nominal+shaped+refined+bot
|
|
29
|
-
#
|
|
26
|
+
# The summary exposes `precision_ratio` (constant+nominal+shaped+refined+bot over total) and
|
|
27
|
+
# `opaque_ratio` (dynamic_top+top over total).
|
|
30
28
|
#
|
|
31
|
-
# For Union types the *worst* member tier is used, since the union is only
|
|
32
|
-
#
|
|
33
|
-
#
|
|
29
|
+
# For Union types the *worst* member tier is used, since the union is only as precise as its
|
|
30
|
+
# least-precise constituent. Intersection uses the *best* member (the most specific side wins).
|
|
31
|
+
# Difference follows its base type.
|
|
34
32
|
class PrecisionScanner
|
|
35
33
|
TIERS = %i[
|
|
36
34
|
constant nominal shaped refined bot
|
|
37
35
|
dynamic_specific dynamic_top top
|
|
38
36
|
].freeze
|
|
39
37
|
|
|
40
|
-
# Prism node classes that do not denote a value-producing
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# points below the true expression-level precision. We exclude
|
|
47
|
-
# them from BOTH numerator and denominator so the ratio measures
|
|
48
|
-
# what it claims to — the type quality of actual expressions.
|
|
38
|
+
# Prism node classes that do not denote a value-producing expression, so typing them is meaningless —
|
|
39
|
+
# they have no runtime value to carry a type. Counting them (they always fall to the `dynamic_top`
|
|
40
|
+
# fallback) silently diluted the precision ratio: on a real survey target (shugo/textbringer) they
|
|
41
|
+
# were ~49% of every "opaque" node, dragging the headline number ~13 points below the true
|
|
42
|
+
# expression-level precision. We exclude them from BOTH numerator and denominator so the ratio
|
|
43
|
+
# measures what it claims to — the type quality of actual expressions.
|
|
49
44
|
#
|
|
50
|
-
# The set is deliberately CONSERVATIVE: only nodes that are
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# and
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# clause-header nodes whose body, not the header, carries the
|
|
58
|
-
# value. Anything that *could* be a value expression (`BlockNode`,
|
|
59
|
-
# `BeginNode`, `ImplicitNode`, `ParenthesesNode`, splats, …) is
|
|
60
|
-
# left in so a genuine inference gap stays visible.
|
|
45
|
+
# The set is deliberately CONSERVATIVE: only nodes that are unambiguously non-expressions in Ruby's
|
|
46
|
+
# grammar are listed — argument / parameter list containers and the parameter declarations inside
|
|
47
|
+
# them; the `key => value` pair node (its key and value are themselves walked and counted); the
|
|
48
|
+
# program / statements sequence wrappers (their value is the last child, already counted — listing
|
|
49
|
+
# them avoids double-counting); and the clause-header nodes whose body, not the header, carries the
|
|
50
|
+
# value. Anything that *could* be a value expression (`BlockNode`, `BeginNode`, `ImplicitNode`,
|
|
51
|
+
# `ParenthesesNode`, splats, …) is left in so a genuine inference gap stays visible.
|
|
61
52
|
#
|
|
62
|
-
# Compared by class NAME so a Prism version that lacks one of the
|
|
63
|
-
#
|
|
53
|
+
# Compared by class NAME so a Prism version that lacks one of the newer node classes does not break
|
|
54
|
+
# loading.
|
|
64
55
|
NON_EXPRESSION_NODE_TYPES = %w[
|
|
65
56
|
Prism::ProgramNode
|
|
66
57
|
Prism::StatementsNode
|
|
@@ -2,33 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Rigor
|
|
4
4
|
module Inference
|
|
5
|
-
# ADR-17 § "Inference contract" — project-wide patched-method
|
|
6
|
-
#
|
|
7
|
-
# the user's `.rigor.yml` `pre_eval:` list.
|
|
5
|
+
# ADR-17 § "Inference contract" — project-wide patched-method registry populated by the pre-eval
|
|
6
|
+
# pre-pass (slice 2) from the user's `.rigor.yml` `pre_eval:` list.
|
|
8
7
|
#
|
|
9
|
-
# Each entry records one `def` declaration the pre-pass
|
|
10
|
-
#
|
|
11
|
-
# `
|
|
12
|
-
#
|
|
13
|
-
# project-side `lib/core_ext/string_extensions.rb` patches
|
|
14
|
-
# are visible to cross-file dispatch.
|
|
8
|
+
# Each entry records one `def` declaration the pre-pass observed inside a class / module body. The
|
|
9
|
+
# dispatcher's `try_project_patched_method` tier consults this registry between the plugin tier and
|
|
10
|
+
# the dependency-source tier so project-side `lib/core_ext/string_extensions.rb` patches are visible
|
|
11
|
+
# to cross-file dispatch.
|
|
15
12
|
#
|
|
16
|
-
# The dispatcher answers `Dynamic[T]` (with a heuristic static
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# per-field contract.
|
|
13
|
+
# The dispatcher answers `Dynamic[T]` (with a heuristic static facet) when `Entry#return_type` is
|
|
14
|
+
# non-nil, or `Dynamic[Top]` when the heuristic declined (`nil`). See {Entry} for the per-field
|
|
15
|
+
# contract.
|
|
20
16
|
class ProjectPatchedMethods
|
|
21
|
-
# Frozen value-object recording one `def` observed by the
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# `
|
|
26
|
-
#
|
|
27
|
-
# {Analysis::DependencySourceInference::ReturnTypeHeuristic}-
|
|
28
|
-
# extracted static facet (a `Rigor::Type::*`) or `nil`
|
|
29
|
-
# when the heuristic declined. The dispatcher wraps a
|
|
30
|
-
# non-nil `return_type` in `Dynamic[T]`; a `nil`
|
|
31
|
-
# `return_type` falls back to `Dynamic[top]`.
|
|
17
|
+
# Frozen value-object recording one `def` observed by the pre-pass. `class_name` is the qualified
|
|
18
|
+
# prefix (`"String"`, `"Foo::Bar"`); `method_name` is the declared name; `kind` is `:instance` or
|
|
19
|
+
# `:singleton`; `source_path` / `source_line` carry attribution for diagnostics; `return_type` is
|
|
20
|
+
# the {Analysis::DependencySourceInference::ReturnTypeHeuristic}-extracted static facet (a
|
|
21
|
+
# `Rigor::Type::*`) or `nil` when the heuristic declined. The dispatcher wraps a non-nil
|
|
22
|
+
# `return_type` in `Dynamic[T]`; a `nil` `return_type` falls back to `Dynamic[top]`.
|
|
32
23
|
Entry = Data.define(:class_name, :method_name, :kind, :source_path, :source_line, :return_type) do
|
|
33
24
|
def initialize(class_name:, method_name:, kind:, source_path:, source_line:, return_type: nil)
|
|
34
25
|
super
|
|
@@ -37,11 +28,9 @@ module Rigor
|
|
|
37
28
|
|
|
38
29
|
attr_reader :by_key
|
|
39
30
|
|
|
40
|
-
# @param entries [Array<Entry>] flat list of declarations
|
|
41
|
-
#
|
|
42
|
-
# `
|
|
43
|
-
# `pre-eval.duplicate-declaration` diagnostic emission
|
|
44
|
-
# stays decoupled from registry behaviour.
|
|
31
|
+
# @param entries [Array<Entry>] flat list of declarations observed during the pre-pass.
|
|
32
|
+
# First-write-wins on `(class_name, method_name, kind)` duplicates so the
|
|
33
|
+
# `pre-eval.duplicate-declaration` diagnostic emission stays decoupled from registry behaviour.
|
|
45
34
|
def initialize(entries: [])
|
|
46
35
|
@by_key = entries.each_with_object({}) do |entry, acc|
|
|
47
36
|
key = [entry.class_name, entry.method_name, entry.kind]
|
|
@@ -50,9 +39,8 @@ module Rigor
|
|
|
50
39
|
freeze
|
|
51
40
|
end
|
|
52
41
|
|
|
53
|
-
# @return [Entry, nil] the recorded entry for the given
|
|
54
|
-
# `
|
|
55
|
-
# when no pre-eval file declared it.
|
|
42
|
+
# @return [Entry, nil] the recorded entry for the given `(class_name, method_name, kind)` triple,
|
|
43
|
+
# or `nil` when no pre-eval file declared it.
|
|
56
44
|
def lookup(class_name:, method_name:, kind:)
|
|
57
45
|
@by_key[[class_name, method_name, kind]]
|
|
58
46
|
end
|
|
@@ -8,22 +8,17 @@ require_relative "../source/constant_path"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
module Inference
|
|
11
|
-
# ADR-17 slice 2 — pre-pass scanner. Walks every file the user
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# {ProjectPatchedMethods} registry the dispatcher consults
|
|
15
|
-
# below the plugin tier.
|
|
11
|
+
# ADR-17 slice 2 — pre-pass scanner. Walks every file the user listed under `pre_eval:` and
|
|
12
|
+
# harvests every `def` / `def self.` declaration inside a class / module body into a
|
|
13
|
+
# {ProjectPatchedMethods} registry the dispatcher consults below the plugin tier.
|
|
16
14
|
#
|
|
17
|
-
# The walker is intentionally a strict subset of
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# `pre-eval.parse-error` diagnostic accumulated alongside
|
|
22
|
-
# the registry; per ADR-17 § "Failure modes" a parse failure
|
|
23
|
-
# in a pre-eval file MUST NOT abort the rest of the run.
|
|
15
|
+
# The walker is intentionally a strict subset of {Rigor::Inference::ScopeIndexer}'s machinery: it
|
|
16
|
+
# only needs `class C; def m; end; end` shape recognition, not full inference. Parse errors degrade
|
|
17
|
+
# to a fail-soft `:warning` `pre-eval.parse-error` diagnostic accumulated alongside the registry;
|
|
18
|
+
# per ADR-17 § "Failure modes" a parse failure in a pre-eval file MUST NOT abort the rest of the run.
|
|
24
19
|
module ProjectPatchedScanner
|
|
25
|
-
# Frozen scan outcome carrying the populated registry and
|
|
26
|
-
#
|
|
20
|
+
# Frozen scan outcome carrying the populated registry and the per-file warnings the runner emits
|
|
21
|
+
# at run start.
|
|
27
22
|
class Result < Data.define(:registry, :diagnostics)
|
|
28
23
|
def initialize(registry:, diagnostics: [])
|
|
29
24
|
super(
|
|
@@ -35,18 +30,13 @@ module Rigor
|
|
|
35
30
|
|
|
36
31
|
module_function
|
|
37
32
|
|
|
38
|
-
# @param paths [Array<String>] absolute paths to the
|
|
39
|
-
# pre-eval
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# the buffer's physical bytes if a pre-eval entry matches
|
|
46
|
-
# the logical path, so users editing a monkey-patch file
|
|
47
|
-
# see the in-flight version in their analysis.
|
|
48
|
-
# @return [Result] the populated registry plus any
|
|
49
|
-
# per-file warnings.
|
|
33
|
+
# @param paths [Array<String>] absolute paths to the pre-eval files. The runner has already
|
|
34
|
+
# validated that each path exists (slice-1 `pre-eval.file-not-found` `:error` covers missing
|
|
35
|
+
# entries); the scanner does NOT re-check existence.
|
|
36
|
+
# @param buffer [Rigor::Analysis::BufferBinding, nil] editor-mode buffer binding. When set, the
|
|
37
|
+
# scanner reads the buffer's physical bytes if a pre-eval entry matches the logical path, so
|
|
38
|
+
# users editing a monkey-patch file see the in-flight version in their analysis.
|
|
39
|
+
# @return [Result] the populated registry plus any per-file warnings.
|
|
50
40
|
def scan(paths, buffer: nil)
|
|
51
41
|
entries = []
|
|
52
42
|
diagnostics = []
|
|
@@ -58,13 +48,10 @@ module Rigor
|
|
|
58
48
|
)
|
|
59
49
|
end
|
|
60
50
|
|
|
61
|
-
# ADR-17 § "Failure modes" — when two pre-eval entries
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# behaviour is unchanged; the diagnostic just makes the
|
|
66
|
-
# shadowing visible so users notice when a later patch
|
|
67
|
-
# is silently masked.
|
|
51
|
+
# ADR-17 § "Failure modes" — when two pre-eval entries declare the same `(class_name, method_name,
|
|
52
|
+
# kind)` triple, emit one `:info` `pre-eval.duplicate-declaration` diagnostic per collision. The
|
|
53
|
+
# registry's first-write-wins behaviour is unchanged; the diagnostic just makes the shadowing
|
|
54
|
+
# visible so users notice when a later patch is silently masked.
|
|
68
55
|
def duplicate_declaration_diagnostics(entries)
|
|
69
56
|
seen = {}
|
|
70
57
|
entries.each_with_object([]) do |entry, acc|
|
|
@@ -129,10 +116,9 @@ module Rigor
|
|
|
129
116
|
end
|
|
130
117
|
private_class_method :parse_error_diagnostic
|
|
131
118
|
|
|
132
|
-
# Builds a diagnostic Hash-shape the runner translates to a
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# the runner adapts at the call site.
|
|
119
|
+
# Builds a diagnostic Hash-shape the runner translates to a `Rigor::Analysis::Diagnostic`. The
|
|
120
|
+
# scanner intentionally does NOT depend on the analysis layer (it's a pre-pass); the runner
|
|
121
|
+
# adapts at the call site.
|
|
136
122
|
def build_diagnostic(path:, line:, column:, severity:, rule:, message:)
|
|
137
123
|
{ path: path, line: line, column: column, severity: severity, rule: rule, message: message }
|
|
138
124
|
end
|