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
data/lib/rigor/scope.rb
CHANGED
|
@@ -10,11 +10,9 @@ require_relative "inference/flow_tracer"
|
|
|
10
10
|
require_relative "inference/statement_evaluator"
|
|
11
11
|
|
|
12
12
|
module Rigor
|
|
13
|
-
# Immutable analyzer scope: holds local-variable bindings and a reference
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
# #type_of(node), the Rigor counterpart of PHPStan's
|
|
17
|
-
# $scope->getType($node).
|
|
13
|
+
# Immutable analyzer scope: holds local-variable bindings and a reference to the surrounding Environment. State
|
|
14
|
+
# changes return new scopes through explicit transition methods (#with_local). The central query is
|
|
15
|
+
# #type_of(node), the Rigor counterpart of PHPStan's $scope->getType($node).
|
|
18
16
|
#
|
|
19
17
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
20
18
|
# rubocop:disable Metrics/ClassLength,Metrics/ParameterLists
|
|
@@ -24,19 +22,15 @@ module Rigor
|
|
|
24
22
|
:indexed_narrowings, :method_chain_narrowings,
|
|
25
23
|
:declaration_sourced,
|
|
26
24
|
:source_path, :discovery, :struct_fold_safe_locals,
|
|
27
|
-
:dynamic_origins
|
|
25
|
+
:dynamic_origins, :local_origins, :ivar_origins
|
|
28
26
|
|
|
29
|
-
# ADR-53 Track A — the seed-time discovery tables live on the
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# are unaffected by the extraction. The whole index is swapped in
|
|
33
|
-
# one transition through {#with_discovery}.
|
|
27
|
+
# ADR-53 Track A — the seed-time discovery tables live on the {DiscoveryIndex} the scope carries by a single
|
|
28
|
+
# reference; the per-table readers stay on Scope so engine call sites and plugins are unaffected by the
|
|
29
|
+
# extraction. The whole index is swapped in one transition through {#with_discovery}.
|
|
34
30
|
#
|
|
35
|
-
# `declared_types` carries the identity-comparing
|
|
36
|
-
# `
|
|
37
|
-
# `
|
|
38
|
-
# dispatch (a `module Foo` / `class Bar` header types as
|
|
39
|
-
# `Singleton[<qualified path>]` rather than `Dynamic[Top]`).
|
|
31
|
+
# `declared_types` carries the identity-comparing `Prism::Node => Rigor::Type` declaration overrides
|
|
32
|
+
# `ExpressionTyper#type_of(node)` MUST consult before any other dispatch (a `module Foo` / `class Bar` header
|
|
33
|
+
# types as `Singleton[<qualified path>]` rather than `Dynamic[Top]`).
|
|
40
34
|
def declared_types = @discovery.declared_types
|
|
41
35
|
def class_ivars = @discovery.class_ivars
|
|
42
36
|
def class_cvars = @discovery.class_cvars
|
|
@@ -53,68 +47,63 @@ module Rigor
|
|
|
53
47
|
def discovered_class_sources = @discovery.discovered_class_sources
|
|
54
48
|
def data_member_layouts = @discovery.data_member_layouts
|
|
55
49
|
def struct_member_layouts = @discovery.struct_member_layouts
|
|
56
|
-
# ADR-67 WD3 — call-site-inferred parameter types, keyed by
|
|
57
|
-
# `
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# parameter always wins). Empty unless a collection pass seeded it.
|
|
50
|
+
# ADR-67 WD3 — call-site-inferred parameter types, keyed by `[class_name, method_name, kind]`.
|
|
51
|
+
# `build_method_entry_scope` consults this to seed an undeclared `def` parameter with the union of its resolved
|
|
52
|
+
# call-site argument types (precision-additive; an RBS-declared parameter always wins). Empty unless a
|
|
53
|
+
# collection pass seeded it.
|
|
61
54
|
def param_inferred_types = @discovery.param_inferred_types
|
|
62
55
|
|
|
63
|
-
# Narrowing key for an indexed read `receiver[key]` where both
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# read at the same address MUST observe.
|
|
56
|
+
# Narrowing key for an indexed read `receiver[key]` where both the receiver and the key are stable enough to
|
|
57
|
+
# address. The value of the map at this key is the narrowed type the next read at the same address MUST
|
|
58
|
+
# observe.
|
|
67
59
|
#
|
|
68
|
-
# - `receiver_kind` ∈ `{:local, :ivar}` — the analyzer only
|
|
69
|
-
#
|
|
60
|
+
# - `receiver_kind` ∈ `{:local, :ivar}` — the analyzer only tracks reads against a local or instance variable
|
|
61
|
+
# today.
|
|
70
62
|
# - `receiver_name` is the variable's Symbol.
|
|
71
|
-
# - `key` is the Ruby value of the literal index (Symbol /
|
|
72
|
-
#
|
|
73
|
-
# not recorded; they have no stable address.
|
|
63
|
+
# - `key` is the Ruby value of the literal index (Symbol / String / Integer). Non-literal keys
|
|
64
|
+
# (`params[field]`) are not recorded; they have no stable address.
|
|
74
65
|
IndexedKey = Data.define(:receiver_kind, :receiver_name, :key)
|
|
75
66
|
|
|
76
|
-
# Narrowing key for a no-arg / no-block method-call chain
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# key is the narrowed type the next read of the same chain
|
|
80
|
-
# MUST observe — typically the post-`is_a?(C)` narrowing
|
|
81
|
-
# established on a predicate edge.
|
|
67
|
+
# Narrowing key for a no-arg / no-block method-call chain `receiver.method_name` (a "single-hop" chain per A1
|
|
68
|
+
# from the ROADMAP § Future cycles slice). The value of the map at this key is the narrowed type the next read
|
|
69
|
+
# of the same chain MUST observe — typically the post-`is_a?(C)` narrowing established on a predicate edge.
|
|
82
70
|
#
|
|
83
|
-
# - `receiver_kind` ∈ `{:local, :ivar}` — the analyzer only
|
|
84
|
-
#
|
|
85
|
-
# today (Law-of-Demeter-style single-hop).
|
|
71
|
+
# - `receiver_kind` ∈ `{:local, :ivar}` — the analyzer only tracks chains rooted at a local or instance
|
|
72
|
+
# variable today (Law-of-Demeter-style single-hop).
|
|
86
73
|
# - `receiver_name` is the root variable's Symbol.
|
|
87
74
|
# - `method_name` is the no-arg method invoked on the root.
|
|
88
75
|
#
|
|
89
|
-
# Chains with arguments (`x.first(3)`), with a block
|
|
90
|
-
# (`x.
|
|
91
|
-
#
|
|
92
|
-
# different reasons (args / block alter the call's return;
|
|
93
|
-
# multi-hop loses the LoD guarantee).
|
|
76
|
+
# Chains with arguments (`x.first(3)`), with a block (`x.detect { ... }`), or with intermediate links
|
|
77
|
+
# (`x.foo.bar`) are NOT recorded; each loses stability for different reasons (args / block alter the call's
|
|
78
|
+
# return; multi-hop loses the LoD guarantee).
|
|
94
79
|
ChainKey = Data.define(:receiver_kind, :receiver_name, :method_name)
|
|
95
80
|
|
|
96
81
|
EMPTY_VAR_BINDINGS = {}.freeze
|
|
97
82
|
EMPTY_INDEXED_NARROWINGS = {}.freeze
|
|
98
83
|
EMPTY_CHAIN_NARROWINGS = {}.freeze
|
|
99
|
-
# ADR-58 WD1 — the set of variable references whose binding's `nil`
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# program's cross-method invariant is assumed per the robustness
|
|
107
|
-
# principle. Any flow-live touch (write / narrowing) drops the mark, so
|
|
108
|
-
# the diagnostic keeps firing exactly as before on flow-observed nil.
|
|
84
|
+
# ADR-58 WD1 — the set of variable references whose binding's `nil` constituent is *declaration-sourced*: it
|
|
85
|
+
# arrives only via the class-ivar index seed (a ctor `@x = nil` written in another method), never through a
|
|
86
|
+
# method-local write, narrowing, or parameter. Members are frozen `[kind, name]` pairs (`[:ivar, :@x]`,
|
|
87
|
+
# `[:local, :r]`). `possible-nil-receiver` consults this set and declines to fire when the receiver's
|
|
88
|
+
# optionality is purely declaration-sourced — the working program's cross-method invariant is assumed per the
|
|
89
|
+
# robustness principle. Any flow-live touch (write / narrowing) drops the mark, so the diagnostic keeps firing
|
|
90
|
+
# exactly as before on flow-observed nil.
|
|
109
91
|
EMPTY_DECLARATION_SOURCED = Set.new.freeze
|
|
110
|
-
# ADR-48 Struct slice 3 — the per-body set of local names whose struct
|
|
111
|
-
#
|
|
112
|
-
# A static per-scope context like {#source_path}: inherited unchanged
|
|
92
|
+
# ADR-48 Struct slice 3 — the per-body set of local names whose struct member reads are fold-safe (provably
|
|
93
|
+
# never mutated / aliased / escaped). A static per-scope context like {#source_path}: inherited unchanged
|
|
113
94
|
# through flow transitions and ignored by `==` / `hash`.
|
|
114
95
|
EMPTY_FOLD_SAFE = Set.new.freeze
|
|
96
|
+
# ADR-82 WD1 — provenance propagation across the receiver-node lookup. `local_origins` / `ivar_origins`
|
|
97
|
+
# map a `name` (Symbol) to the {Inference::DynamicOrigin} cause of the `Dynamic` value currently bound to
|
|
98
|
+
# it, so a downstream dispatch whose receiver is a bare `x` / `@x` read resolves to why that value is
|
|
99
|
+
# dynamic (the cause was recorded on the *assignment*'s rhs node, which the receiver-read node is not).
|
|
100
|
+
# Advisory metadata like {#dynamic_origins}: ignored by `==` / `hash` (never varies a flow decision) and
|
|
101
|
+
# threaded by reference through transitions; reset per method body (a fresh entry scope drops it), so the
|
|
102
|
+
# name keys never collide across bodies.
|
|
103
|
+
EMPTY_ORIGINS = {}.freeze
|
|
115
104
|
private_constant :EMPTY_VAR_BINDINGS, :EMPTY_INDEXED_NARROWINGS,
|
|
116
105
|
:EMPTY_CHAIN_NARROWINGS, :EMPTY_DECLARATION_SOURCED,
|
|
117
|
-
:EMPTY_FOLD_SAFE
|
|
106
|
+
:EMPTY_FOLD_SAFE, :EMPTY_ORIGINS
|
|
118
107
|
|
|
119
108
|
class << self
|
|
120
109
|
def empty(environment: Environment.default, source_path: nil)
|
|
@@ -141,7 +130,9 @@ module Rigor
|
|
|
141
130
|
declaration_sourced: EMPTY_DECLARATION_SOURCED,
|
|
142
131
|
source_path: nil,
|
|
143
132
|
struct_fold_safe_locals: EMPTY_FOLD_SAFE,
|
|
144
|
-
dynamic_origins: {}.compare_by_identity
|
|
133
|
+
dynamic_origins: {}.compare_by_identity,
|
|
134
|
+
local_origins: EMPTY_ORIGINS,
|
|
135
|
+
ivar_origins: EMPTY_ORIGINS
|
|
145
136
|
)
|
|
146
137
|
@environment = environment
|
|
147
138
|
@locals = locals
|
|
@@ -157,9 +148,31 @@ module Rigor
|
|
|
157
148
|
@source_path = source_path
|
|
158
149
|
@struct_fold_safe_locals = struct_fold_safe_locals
|
|
159
150
|
@dynamic_origins = dynamic_origins
|
|
151
|
+
@local_origins = local_origins
|
|
152
|
+
@ivar_origins = ivar_origins
|
|
160
153
|
freeze
|
|
161
154
|
end
|
|
162
155
|
|
|
156
|
+
# ADR-82 WD1 — the propagated origin of the `Dynamic` value currently bound to a local / instance
|
|
157
|
+
# variable, or `nil` when none is tracked. Consulted by `Inference::ProtectionScanner` when a dispatch's
|
|
158
|
+
# receiver is a bare `x` / `@x` read whose own node carries no origin.
|
|
159
|
+
def local_origin(name) = @local_origins[name.to_sym]
|
|
160
|
+
def ivar_origin(name) = @ivar_origins[name.to_sym]
|
|
161
|
+
|
|
162
|
+
# Records the cause of the `Dynamic` value being bound to `name`. A `nil` cause is a no-op (the common
|
|
163
|
+
# case — most bindings are concrete or have no recorded origin), so callers need not pre-check.
|
|
164
|
+
def with_local_origin(name, cause)
|
|
165
|
+
return self if cause.nil?
|
|
166
|
+
|
|
167
|
+
rebuild(local_origins: @local_origins.merge(name.to_sym => cause).freeze)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def with_ivar_origin(name, cause)
|
|
171
|
+
return self if cause.nil?
|
|
172
|
+
|
|
173
|
+
rebuild(ivar_origins: @ivar_origins.merge(name.to_sym => cause).freeze)
|
|
174
|
+
end
|
|
175
|
+
|
|
163
176
|
def local(name)
|
|
164
177
|
@locals[name.to_sym]
|
|
165
178
|
end
|
|
@@ -169,79 +182,65 @@ module Rigor
|
|
|
169
182
|
Inference::FlowTracer.bind(name, type) if Inference::FlowTracer.active?
|
|
170
183
|
new_locals = @locals.merge(name.to_sym => type).freeze
|
|
171
184
|
new_fact_store = fact_store.invalidate_target(Analysis::FactStore::Target.local(name))
|
|
172
|
-
# Rebinding `name` invalidates every "after `receiver[key]
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
# method-chain narrowings: `x.last` after `x = something_new`
|
|
178
|
-
# is a call on the new binding and any prior `is_a?`-driven
|
|
179
|
-
# narrowing keyed on `(local, :x, :last)` no longer holds.
|
|
185
|
+
# Rebinding `name` invalidates every "after `receiver[key] ||= default`" narrowing keyed on it — the slot at
|
|
186
|
+
# `name[*]` is reachable through the old binding only, so the next read against the new binding does not
|
|
187
|
+
# inherit the earlier non-nil guarantee. The same logic applies to method-chain narrowings: `x.last` after
|
|
188
|
+
# `x = something_new` is a call on the new binding and any prior `is_a?`-driven narrowing keyed on
|
|
189
|
+
# `(local, :x, :last)` no longer holds.
|
|
180
190
|
new_indexed_narrowings = drop_indexed_narrowings_for(:local, name)
|
|
181
191
|
new_chain_narrowings = drop_chain_narrowings_for(:local, name)
|
|
182
|
-
# ADR-58 WD1 — rebinding a local is a flow-live touch: any prior
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
# declaration-sourced ivar read; the default is to drop it.
|
|
192
|
+
# ADR-58 WD1 — rebinding a local is a flow-live touch: any prior declaration-sourced mark on `name` no
|
|
193
|
+
# longer holds (the new value may carry a method-local nil). `with_declaration_sourced_local` re-establishes
|
|
194
|
+
# the mark afterward when the RHS is a pure copy of a declaration-sourced ivar read; the default is to drop
|
|
195
|
+
# it.
|
|
187
196
|
rebuild(locals: new_locals, fact_store: new_fact_store,
|
|
188
197
|
indexed_narrowings: new_indexed_narrowings,
|
|
189
198
|
method_chain_narrowings: new_chain_narrowings,
|
|
190
|
-
declaration_sourced: drop_declaration_sourced_for(:local, name)
|
|
199
|
+
declaration_sourced: drop_declaration_sourced_for(:local, name),
|
|
200
|
+
local_origins: drop_origin(@local_origins, name))
|
|
191
201
|
end
|
|
192
202
|
|
|
193
203
|
def with_fact(fact)
|
|
194
204
|
rebuild(fact_store: fact_store.with_fact(fact))
|
|
195
205
|
end
|
|
196
206
|
|
|
197
|
-
# Slice A-engine. Returns a scope with `self_type` set to `type`,
|
|
198
|
-
#
|
|
199
|
-
#
|
|
200
|
-
# consults it when typing `Prism::SelfNode` and implicit-self
|
|
201
|
-
# `Prism::CallNode` receivers.
|
|
207
|
+
# Slice A-engine. Returns a scope with `self_type` set to `type`, preserving locals and facts.
|
|
208
|
+
# `StatementEvaluator` injects this at class-body and method-body boundaries; `ExpressionTyper` consults it
|
|
209
|
+
# when typing `Prism::SelfNode` and implicit-self `Prism::CallNode` receivers.
|
|
202
210
|
def with_self_type(type)
|
|
203
211
|
rebuild(self_type: type)
|
|
204
212
|
end
|
|
205
213
|
|
|
206
|
-
# ADR-28 / ADR-52 slice 5a — per-file source path carried on
|
|
207
|
-
# the scope
|
|
208
|
-
#
|
|
209
|
-
# (`dynamic_return`'s `file_methods:` gate, sigil checks) can
|
|
210
|
-
# resolve "which file does this call site belong to?" without
|
|
211
|
-
# thread-locals.
|
|
214
|
+
# ADR-28 / ADR-52 slice 5a — per-file source path carried on the scope. The analyzer stamps the current file's
|
|
215
|
+
# path onto the seed scope; nested rebuilds propagate it so plugin rules (`dynamic_return`'s `file_methods:`
|
|
216
|
+
# gate, sigil checks) can resolve "which file does this call site belong to?" without thread-locals.
|
|
212
217
|
def with_source_path(path)
|
|
213
218
|
rebuild(source_path: path)
|
|
214
219
|
end
|
|
215
220
|
|
|
216
|
-
# ADR-48 Struct slice 3 — installs the per-body fold-safe-local set
|
|
217
|
-
#
|
|
218
|
-
# unchanged through subsequent flow transitions.
|
|
221
|
+
# ADR-48 Struct slice 3 — installs the per-body fold-safe-local set ({Inference::StructFoldSafety}). Set once
|
|
222
|
+
# at body entry; inherited unchanged through subsequent flow transitions.
|
|
219
223
|
def with_struct_fold_safe(locals)
|
|
220
224
|
rebuild(struct_fold_safe_locals: locals)
|
|
221
225
|
end
|
|
222
226
|
|
|
223
|
-
# True when `name`'s `Struct` member reads are fold-safe in this body
|
|
224
|
-
#
|
|
227
|
+
# True when `name`'s `Struct` member reads are fold-safe in this body (the local is provably never mutated /
|
|
228
|
+
# aliased / escaped).
|
|
225
229
|
def struct_fold_safe?(name)
|
|
226
230
|
@struct_fold_safe_locals.include?(name.to_sym)
|
|
227
231
|
end
|
|
228
232
|
|
|
229
|
-
# ADR-53 Track A — swaps the whole discovery index in one transition.
|
|
230
|
-
#
|
|
231
|
-
# off-`Scope` through `scope.discovery.with(table_name: table)`.
|
|
233
|
+
# ADR-53 Track A — swaps the whole discovery index in one transition. The sole seeding path; the per-table
|
|
234
|
+
# writers it replaced are derived off-`Scope` through `scope.discovery.with(table_name: table)`.
|
|
232
235
|
def with_discovery(index)
|
|
233
236
|
rebuild(discovery: index)
|
|
234
237
|
end
|
|
235
238
|
|
|
236
|
-
# Slice 7 phase 1 — instance/class/global variable bindings.
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
# single method body (each `def` enters with a fresh binding
|
|
242
|
-
# map), so reads in other methods of the same class fall
|
|
243
|
-
# through to `Dynamic[Top]`. Cross-method ivar/cvar inference
|
|
244
|
-
# is a follow-up slice.
|
|
239
|
+
# Slice 7 phase 1 — instance/class/global variable bindings. `ivar(name)` / `cvar(name)` / `global(name)`
|
|
240
|
+
# return the type currently bound for the named variable, or `nil` when the variable has not been written in
|
|
241
|
+
# the analyzed slice of the program. The first cut tracks bindings only within a single method body (each
|
|
242
|
+
# `def` enters with a fresh binding map), so reads in other methods of the same class fall through to
|
|
243
|
+
# `Dynamic[Top]`. Cross-method ivar/cvar inference is a follow-up slice.
|
|
245
244
|
def ivar(name)
|
|
246
245
|
@ivars[name.to_sym]
|
|
247
246
|
end
|
|
@@ -257,44 +256,40 @@ module Rigor
|
|
|
257
256
|
def with_ivar(name, type)
|
|
258
257
|
new_indexed_narrowings = drop_indexed_narrowings_for(:ivar, name)
|
|
259
258
|
new_chain_narrowings = drop_chain_narrowings_for(:ivar, name)
|
|
260
|
-
# ADR-58 WD1 — a method-local ivar write or narrowing is flow-live:
|
|
261
|
-
#
|
|
262
|
-
# observe flow-live provenance and fire as before. The seed path uses
|
|
259
|
+
# ADR-58 WD1 — a method-local ivar write or narrowing is flow-live: drop any declaration-sourced mark so
|
|
260
|
+
# subsequent reads of `@name` observe flow-live provenance and fire as before. The seed path uses
|
|
263
261
|
# `seed_declaration_sourced_ivar` to (re-)establish the mark.
|
|
264
262
|
rebuild(ivars: @ivars.merge(name.to_sym => type).freeze,
|
|
265
263
|
indexed_narrowings: new_indexed_narrowings,
|
|
266
264
|
method_chain_narrowings: new_chain_narrowings,
|
|
267
|
-
declaration_sourced: drop_declaration_sourced_for(:ivar, name)
|
|
265
|
+
declaration_sourced: drop_declaration_sourced_for(:ivar, name),
|
|
266
|
+
ivar_origins: drop_origin(@ivar_origins, name))
|
|
268
267
|
end
|
|
269
268
|
|
|
270
|
-
# ADR-58 WD1 — used by the method-entry seed to mark an ivar whose only
|
|
271
|
-
#
|
|
272
|
-
# type AND records the declaration-sourced mark in one transition.
|
|
269
|
+
# ADR-58 WD1 — used by the method-entry seed to mark an ivar whose only provenance is the class-ivar index.
|
|
270
|
+
# Unlike `with_ivar` this binds the type AND records the declaration-sourced mark in one transition.
|
|
273
271
|
def seed_declaration_sourced_ivar(name, type)
|
|
274
272
|
rebuild(ivars: @ivars.merge(name.to_sym => type).freeze,
|
|
275
273
|
declaration_sourced: add_declaration_sourced(:ivar, name))
|
|
276
274
|
end
|
|
277
275
|
|
|
278
|
-
# ADR-58 WD1 — a local assignment `r = @right` whose RHS is a pure read
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
# type and stamps the local's mark in one transition (the plain
|
|
282
|
-
# `with_local` would have dropped it).
|
|
276
|
+
# ADR-58 WD1 — a local assignment `r = @right` whose RHS is a pure read of a declaration-sourced ivar inherits
|
|
277
|
+
# the mark, so the survey's exact rotation/traversal shape (`r = @right; r.key`) does not fire. Binds the type
|
|
278
|
+
# and stamps the local's mark in one transition (the plain `with_local` would have dropped it).
|
|
283
279
|
def with_declaration_sourced_local(name, type)
|
|
284
280
|
written = with_local(name, type)
|
|
285
281
|
written.with_local_declaration_mark(name)
|
|
286
282
|
end
|
|
287
283
|
|
|
288
|
-
# ADR-58 WD1 — re-stamp the local mark on a scope produced by
|
|
289
|
-
# `
|
|
290
|
-
#
|
|
291
|
-
# post-write receiver without reaching into a private method.
|
|
284
|
+
# ADR-58 WD1 — re-stamp the local mark on a scope produced by `with_local` (which always drops it). Public so
|
|
285
|
+
# the sibling `with_declaration_sourced_local` can call it across the new post-write receiver without reaching
|
|
286
|
+
# into a private method.
|
|
292
287
|
def with_local_declaration_mark(name)
|
|
293
288
|
rebuild(declaration_sourced: add_declaration_sourced(:local, name))
|
|
294
289
|
end
|
|
295
290
|
|
|
296
|
-
# ADR-58 WD1 — true when `(kind, name)`'s binding optionality is purely
|
|
297
|
-
#
|
|
291
|
+
# ADR-58 WD1 — true when `(kind, name)`'s binding optionality is purely declaration-sourced (no flow-live
|
|
292
|
+
# write/narrowing has touched it).
|
|
298
293
|
def declaration_sourced?(kind, name)
|
|
299
294
|
@declaration_sourced.include?([kind.to_sym, name.to_sym])
|
|
300
295
|
end
|
|
@@ -307,14 +302,11 @@ module Rigor
|
|
|
307
302
|
rebuild(globals: @globals.merge(name.to_sym => type).freeze)
|
|
308
303
|
end
|
|
309
304
|
|
|
310
|
-
# Regex match-data globals (`$~`, `$&`, `$1..$9`, the pre/post-match
|
|
311
|
-
#
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
# safe — only drops facts, so a subsequent read falls back to the
|
|
316
|
-
# default `String | nil`. Program-level `$GLOBAL = ...` seeds use other
|
|
317
|
-
# names and are untouched.
|
|
305
|
+
# Regex match-data globals (`$~`, `$&`, `$1..$9`, the pre/post-match and last-paren back-references). Narrowed
|
|
306
|
+
# on a successful-`=~` / `case`-`when` match edge (see `Narrowing#regex_match_predicate_scopes`); any
|
|
307
|
+
# subsequent method call could run another match and rebind every one of them, so `eval_call` forgets the
|
|
308
|
+
# narrowed facts here. Always safe — only drops facts, so a subsequent read falls back to the default
|
|
309
|
+
# `String | nil`. Program-level `$GLOBAL = ...` seeds use other names and are untouched.
|
|
318
310
|
MATCH_DATA_GLOBALS = %i[$~ $& $` $' $+ $1 $2 $3 $4 $5 $6 $7 $8 $9].freeze
|
|
319
311
|
private_constant :MATCH_DATA_GLOBALS
|
|
320
312
|
|
|
@@ -324,44 +316,33 @@ module Rigor
|
|
|
324
316
|
rebuild(globals: @globals.except(*MATCH_DATA_GLOBALS).freeze)
|
|
325
317
|
end
|
|
326
318
|
|
|
327
|
-
# Slice 7 phase 2 — class-level ivar accumulator. Keyed by
|
|
328
|
-
# the
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
# class. `StatementEvaluator#build_method_entry_scope`
|
|
332
|
-
# seeds the method body's `ivars` map from this table so a
|
|
333
|
-
# `def get; @x; end` reads the type written in a sibling
|
|
334
|
-
# `def init; @x = 1; end`.
|
|
319
|
+
# Slice 7 phase 2 — class-level ivar accumulator. Keyed by the qualified class name (e.g. `"Rigor::Scope"`);
|
|
320
|
+
# the value is a `Hash[Symbol, Type::t]` of every ivar that appears as a write target inside any def body of
|
|
321
|
+
# that class. `StatementEvaluator#build_method_entry_scope` seeds the method body's `ivars` map from this
|
|
322
|
+
# table so a `def get; @x; end` reads the type written in a sibling `def init; @x = 1; end`.
|
|
335
323
|
#
|
|
336
|
-
# `ScopeIndexer` populates the table once at index time
|
|
337
|
-
#
|
|
338
|
-
# frozen and shared by structural reference across every
|
|
339
|
-
# derived scope.
|
|
324
|
+
# `ScopeIndexer` populates the table once at index time through a separate pre-pass over the program. The map
|
|
325
|
+
# is frozen and shared by structural reference across every derived scope.
|
|
340
326
|
def class_ivars_for(class_name)
|
|
341
327
|
return EMPTY_VAR_BINDINGS if class_name.nil?
|
|
342
328
|
|
|
343
329
|
@discovery.class_ivars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
344
330
|
end
|
|
345
331
|
|
|
346
|
-
# Slice 7 phase 6 — class-level cvar accumulator (same shape
|
|
347
|
-
#
|
|
348
|
-
#
|
|
349
|
-
# bodies because Ruby cvars are visible from each).
|
|
332
|
+
# Slice 7 phase 6 — class-level cvar accumulator (same shape as `class_ivars` but populated from
|
|
333
|
+
# `Prism::ClassVariableWriteNode` writes, and seeded on BOTH instance and singleton method bodies because Ruby
|
|
334
|
+
# cvars are visible from each).
|
|
350
335
|
def class_cvars_for(class_name)
|
|
351
336
|
return EMPTY_VAR_BINDINGS if class_name.nil?
|
|
352
337
|
|
|
353
338
|
@discovery.class_cvars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
354
339
|
end
|
|
355
340
|
|
|
356
|
-
# Slice 7 phase 12 — in-source method discovery. Maps a
|
|
357
|
-
#
|
|
358
|
-
# `
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
# `rigor check` undefined-method and wrong-arity rules
|
|
362
|
-
# consult this map to suppress diagnostics for methods the
|
|
363
|
-
# user has defined dynamically, even when no RBS sig
|
|
364
|
-
# describes them.
|
|
341
|
+
# Slice 7 phase 12 — in-source method discovery. Maps a qualified class name to a `Hash[Symbol, Symbol]` of
|
|
342
|
+
# `method_name => :instance | :singleton`. Populated by `ScopeIndexer` from every `Prism::DefNode` and
|
|
343
|
+
# recognised `define_method` invocation inside class/module bodies. The `rigor check` undefined-method and
|
|
344
|
+
# wrong-arity rules consult this map to suppress diagnostics for methods the user has defined dynamically,
|
|
345
|
+
# even when no RBS sig describes them.
|
|
365
346
|
def discovered_method?(class_name, method_name, kind)
|
|
366
347
|
table = @discovery.discovered_methods[class_name.to_s]
|
|
367
348
|
return false unless table
|
|
@@ -369,30 +350,20 @@ module Rigor
|
|
|
369
350
|
table[method_name.to_sym] == kind
|
|
370
351
|
end
|
|
371
352
|
|
|
372
|
-
# ADR-34 § "Decision" — predicate identifying a toplevel-shaped
|
|
373
|
-
#
|
|
374
|
-
#
|
|
375
|
-
#
|
|
376
|
-
#
|
|
377
|
-
#
|
|
378
|
-
# gate unconditionally). Used by
|
|
379
|
-
# `CheckRules#unresolved_toplevel_diagnostic` to gate the
|
|
380
|
-
# `call.unresolved-toplevel` rule so it fires only outside
|
|
381
|
-
# class / module bodies, where Rails-DSL metaprogramming
|
|
382
|
-
# leniency (ADR-24 WD3 → WD4) does not apply.
|
|
353
|
+
# ADR-34 § "Decision" — predicate identifying a toplevel-shaped scope (no enclosing `class` / `module` body).
|
|
354
|
+
# True at the top of a file AND inside a top-level `def` body (since toplevel defs leave `self_type` nil per
|
|
355
|
+
# the existing scope-construction contract — the same nil-`self_type` signal ADR-24's self-call return
|
|
356
|
+
# adoption historically keyed on before ADR-57 opened the gate unconditionally). Used by
|
|
357
|
+
# `CheckRules#unresolved_toplevel_diagnostic` to gate the `call.unresolved-toplevel` rule so it fires only
|
|
358
|
+
# outside class / module bodies, where Rails-DSL metaprogramming leniency (ADR-24 WD3 → WD4) does not apply.
|
|
383
359
|
def toplevel?
|
|
384
360
|
@self_type.nil?
|
|
385
361
|
end
|
|
386
362
|
|
|
387
|
-
# v0.0.2 #5 — per-class table mapping
|
|
388
|
-
# `
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
# `define_method`-introduced methods do not contribute a
|
|
392
|
-
# static body the engine can re-type). Consumed by
|
|
393
|
-
# `ExpressionTyper` to do inter-procedural return-type
|
|
394
|
-
# inference when the receiver class is user-defined and
|
|
395
|
-
# has no RBS sig.
|
|
363
|
+
# v0.0.2 #5 — per-class table mapping `method_name (Symbol) → Prism::DefNode`. Populated by `ScopeIndexer`
|
|
364
|
+
# alongside `discovered_methods` for instance-side defs only (singleton-side and `define_method`-introduced
|
|
365
|
+
# methods do not contribute a static body the engine can re-type). Consumed by `ExpressionTyper` to do
|
|
366
|
+
# inter-procedural return-type inference when the receiver class is user-defined and has no RBS sig.
|
|
396
367
|
def user_def_for(class_name, method_name)
|
|
397
368
|
table = @discovery.discovered_def_nodes[class_name.to_s]
|
|
398
369
|
node = table && table[method_name.to_sym]
|
|
@@ -400,15 +371,12 @@ module Rigor
|
|
|
400
371
|
node
|
|
401
372
|
end
|
|
402
373
|
|
|
403
|
-
# Module-singleton call resolution (ADR-57 follow-up) — companion of
|
|
404
|
-
#
|
|
405
|
-
# `
|
|
406
|
-
# `
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
# `self` as `Nominal`), so singleton bodies live in a parallel table
|
|
410
|
-
# the `ScopeIndexer` populates alongside it. Records the same
|
|
411
|
-
# cross-file dependency edge as the instance path (ADR-46).
|
|
374
|
+
# Module-singleton call resolution (ADR-57 follow-up) — companion of {#user_def_for} for SINGLETON-side defs
|
|
375
|
+
# (`def self.x`, `def Foo.x`, `class << self` bodies, and `module_function` defs). Returns the
|
|
376
|
+
# `Prism::DefNode` for `class_name.method_name` invoked on the module/class constant itself, or nil. The
|
|
377
|
+
# `discovered_def_nodes` table is deliberately instance-side only (its ancestor walk binds `self` as
|
|
378
|
+
# `Nominal`), so singleton bodies live in a parallel table the `ScopeIndexer` populates alongside it. Records
|
|
379
|
+
# the same cross-file dependency edge as the instance path (ADR-46).
|
|
412
380
|
def singleton_def_for(class_name, method_name)
|
|
413
381
|
table = @discovery.discovered_singleton_def_nodes[class_name.to_s]
|
|
414
382
|
node = table && table[method_name.to_sym]
|
|
@@ -416,15 +384,14 @@ module Rigor
|
|
|
416
384
|
node
|
|
417
385
|
end
|
|
418
386
|
|
|
419
|
-
# ADR-46 slice 1 — note the cross-file dependency this resolution
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
#
|
|
423
|
-
# consumer. Gated on the recorder being active — no-op on a normal run.
|
|
387
|
+
# ADR-46 slice 1 — note the cross-file dependency this resolution creates: the file defining
|
|
388
|
+
# `class_name#method_name` (the consumer's analysis reads its body via `infer_user_method_return`), or, when
|
|
389
|
+
# unresolved, a negative edge so a later definition re-checks the consumer. Gated on the recorder being
|
|
390
|
+
# active — no-op on a normal run.
|
|
424
391
|
def record_cross_file_method(class_name, method_name, node)
|
|
425
392
|
if node
|
|
426
|
-
# ADR-46 slice 4 — pass the symbol so the recorder tracks this as a
|
|
427
|
-
#
|
|
393
|
+
# ADR-46 slice 4 — pass the symbol so the recorder tracks this as a method-call (symbol-granularity) edge
|
|
394
|
+
# rather than a file-level edge.
|
|
428
395
|
Analysis::DependencyRecorder.read_site(
|
|
429
396
|
@discovery.discovered_def_sources.dig(class_name.to_s, method_name.to_sym),
|
|
430
397
|
"#{class_name}##{method_name}"
|
|
@@ -435,12 +402,9 @@ module Rigor
|
|
|
435
402
|
end
|
|
436
403
|
private :record_cross_file_method
|
|
437
404
|
|
|
438
|
-
# v0.0.3 A — top-level def lookup for implicit-self
|
|
439
|
-
#
|
|
440
|
-
#
|
|
441
|
-
# <method_name>` in the file, or nil. The sentinel key
|
|
442
|
-
# is owned by `Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY`;
|
|
443
|
-
# consumers should treat its presence as an opaque
|
|
405
|
+
# v0.0.3 A — top-level def lookup for implicit-self calls. Returns the `Prism::DefNode` for a top-level (or
|
|
406
|
+
# DSL-block-nested, outside any class body) `def <method_name>` in the file, or nil. The sentinel key is owned
|
|
407
|
+
# by `Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY`; consumers should treat its presence as an opaque
|
|
444
408
|
# implementation detail and go through this accessor.
|
|
445
409
|
def top_level_def_for(method_name)
|
|
446
410
|
table = @discovery.discovered_def_nodes[Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY]
|
|
@@ -449,14 +413,12 @@ module Rigor
|
|
|
449
413
|
node
|
|
450
414
|
end
|
|
451
415
|
|
|
452
|
-
# ADR-46 slice 3 — a top-level (`def helper` outside any class) call has
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
# so a later top-level definition re-checks this consumer (the
|
|
459
|
-
# `call.unresolved-toplevel` stale-diagnostic gap).
|
|
416
|
+
# ADR-46 slice 3 — a top-level (`def helper` outside any class) call has NO class ancestry to walk, so unlike
|
|
417
|
+
# {#user_def_for} a miss here records no positive ancestry edge that would re-check the consumer when the
|
|
418
|
+
# method later appears. Record the cross-file edge explicitly: the file defining the top-level method
|
|
419
|
+
# (symbol-granularity, so a body / removal edit re-checks the caller), or, on a miss, a negative `toplevel:`
|
|
420
|
+
# edge so a later top-level definition re-checks this consumer (the `call.unresolved-toplevel`
|
|
421
|
+
# stale-diagnostic gap).
|
|
460
422
|
def record_cross_file_toplevel(method_name, node)
|
|
461
423
|
key = Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
462
424
|
if node
|
|
@@ -470,16 +432,12 @@ module Rigor
|
|
|
470
432
|
end
|
|
471
433
|
private :record_cross_file_toplevel
|
|
472
434
|
|
|
473
|
-
# Companion to {#user_def_for}: returns the `"path:line"` where
|
|
474
|
-
#
|
|
475
|
-
#
|
|
476
|
-
#
|
|
477
|
-
#
|
|
478
|
-
#
|
|
479
|
-
# this to name the defining file when a project monkey-patch on a
|
|
480
|
-
# core/stdlib/gem class is called cross-file, so the diagnostic
|
|
481
|
-
# can point at `pre_eval:` (ADR-17) instead of reading as a bare
|
|
482
|
-
# unresolved call.
|
|
435
|
+
# Companion to {#user_def_for}: returns the `"path:line"` where the project defines `class_name#method_name`
|
|
436
|
+
# (instance-side), or nil. Populated only by the cross-file project pre-pass
|
|
437
|
+
# ({Inference::ScopeIndexer.discovered_def_index_for_paths}) — a `Prism::Location` hides its source file, so
|
|
438
|
+
# the site is recorded at scan time. `CheckRules#undefined_method_diagnostic` consults this to name the
|
|
439
|
+
# defining file when a project monkey-patch on a core/stdlib/gem class is called cross-file, so the diagnostic
|
|
440
|
+
# can point at `pre_eval:` (ADR-17) instead of reading as a bare unresolved call.
|
|
483
441
|
def user_def_site_for(class_name, method_name)
|
|
484
442
|
table = @discovery.discovered_def_sources[class_name.to_s]
|
|
485
443
|
return nil unless table
|
|
@@ -487,74 +445,56 @@ module Rigor
|
|
|
487
445
|
table[method_name.to_sym]
|
|
488
446
|
end
|
|
489
447
|
|
|
490
|
-
# ADR-24 slice 2 — per-class table mapping a fully
|
|
491
|
-
#
|
|
492
|
-
#
|
|
493
|
-
#
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
# to walk the superclass chain when an implicit-self call
|
|
497
|
-
# does not resolve against the enclosing class's own defs.
|
|
498
|
-
# The as-written name is resolved to a qualified class at
|
|
499
|
-
# walk time against the call's lexical nesting.
|
|
448
|
+
# ADR-24 slice 2 — per-class table mapping a fully qualified user-class name to its superclass name AS WRITTEN
|
|
449
|
+
# at the `class Foo < Bar` declaration (`"Bar"`, possibly a qualified `"A::B"`). Populated by `ScopeIndexer` —
|
|
450
|
+
# per-file plus the cross-file project pre-pass — and consumed by
|
|
451
|
+
# `ExpressionTyper#try_user_method_inference` to walk the superclass chain when an implicit-self call does not
|
|
452
|
+
# resolve against the enclosing class's own defs. The as-written name is resolved to a qualified class at walk
|
|
453
|
+
# time against the call's lexical nesting.
|
|
500
454
|
def superclass_of(class_name)
|
|
501
455
|
record_class_dependency(class_name) if Analysis::DependencyRecorder.active?
|
|
502
456
|
@discovery.discovered_superclasses[class_name.to_s]
|
|
503
457
|
end
|
|
504
458
|
|
|
505
|
-
# ADR-48 — per-class table mapping a fully qualified class name to its
|
|
506
|
-
#
|
|
507
|
-
# `
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
# {Inference::MethodDispatcher::DataFolding} so `Point.new(...)` on a
|
|
511
|
-
# `Singleton[Point]` receiver materialises a precise member instance.
|
|
512
|
-
# Returns nil when the class has no recorded layout.
|
|
459
|
+
# ADR-48 — per-class table mapping a fully qualified class name to its ordered `Data.define` / `Struct.new`
|
|
460
|
+
# member-name list. Populated by `ScopeIndexer` for both the constant-assigned form
|
|
461
|
+
# (`Point = Data.define(:x, :y)`) and the named-subclass form (`class Point < Data.define(:x, :y)`). Consumed
|
|
462
|
+
# by {Inference::MethodDispatcher::DataFolding} so `Point.new(...)` on a `Singleton[Point]` receiver
|
|
463
|
+
# materialises a precise member instance. Returns nil when the class has no recorded layout.
|
|
513
464
|
def data_member_layout(class_name)
|
|
514
465
|
layout = @discovery.data_member_layouts[class_name.to_s]
|
|
515
|
-
# Record the ancestry dependency only on a hit — DataFolding consults
|
|
516
|
-
#
|
|
517
|
-
# ordinary class) must not manufacture a spurious cross-file edge.
|
|
466
|
+
# Record the ancestry dependency only on a hit — DataFolding consults this for every `Singleton[*].new`,
|
|
467
|
+
# and a miss (the common case: an ordinary class) must not manufacture a spurious cross-file edge.
|
|
518
468
|
record_class_dependency(class_name) if layout && Analysis::DependencyRecorder.active?
|
|
519
469
|
layout
|
|
520
470
|
end
|
|
521
471
|
|
|
522
|
-
# ADR-48 Struct follow-up — the `{ members:, keyword_init: }` layout
|
|
523
|
-
#
|
|
524
|
-
# (`Point
|
|
525
|
-
# (`
|
|
526
|
-
# {
|
|
527
|
-
# `Singleton[Point]` receiver materialises a member instance. Returns nil
|
|
528
|
-
# when the class has no recorded struct layout. Mirrors
|
|
529
|
-
# {#data_member_layout}'s dependency-recording contract.
|
|
472
|
+
# ADR-48 Struct follow-up — the `{ members:, keyword_init: }` layout recorded for a `Struct.new(...)`-defined
|
|
473
|
+
# class, in the constant form (`Point = Struct.new(:x, :y)`) and the named-subclass form
|
|
474
|
+
# (`class Point < Struct.new(:x, :y)`). Consumed by {Inference::MethodDispatcher::StructFolding} so
|
|
475
|
+
# `Point.new(...)` on a `Singleton[Point]` receiver materialises a member instance. Returns nil when the class
|
|
476
|
+
# has no recorded struct layout. Mirrors {#data_member_layout}'s dependency-recording contract.
|
|
530
477
|
def struct_member_layout(class_name)
|
|
531
478
|
layout = @discovery.struct_member_layouts[class_name.to_s]
|
|
532
479
|
record_class_dependency(class_name) if layout && Analysis::DependencyRecorder.active?
|
|
533
480
|
layout
|
|
534
481
|
end
|
|
535
482
|
|
|
536
|
-
# ADR-24 slice 2 — per-class/module table mapping a fully
|
|
537
|
-
#
|
|
538
|
-
#
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
# `ExpressionTyper#resolve_user_def_through_ancestors` so an
|
|
542
|
-
# implicit-self call resolves against an included module's
|
|
543
|
-
# `def`s, not just the superclass chain. As-written names
|
|
544
|
-
# are resolved to qualified classes at walk time.
|
|
483
|
+
# ADR-24 slice 2 — per-class/module table mapping a fully qualified user class or module to the list of
|
|
484
|
+
# module names it `include`s / `prepend`s, AS WRITTEN at the mixin call. Populated by `ScopeIndexer` (per-file
|
|
485
|
+
# plus the cross-file pre-pass) and consumed by `ExpressionTyper#resolve_user_def_through_ancestors` so an
|
|
486
|
+
# implicit-self call resolves against an included module's `def`s, not just the superclass chain. As-written
|
|
487
|
+
# names are resolved to qualified classes at walk time.
|
|
545
488
|
def includes_of(class_name)
|
|
546
489
|
record_class_dependency(class_name) if Analysis::DependencyRecorder.active?
|
|
547
490
|
@discovery.discovered_includes[class_name.to_s] || []
|
|
548
491
|
end
|
|
549
492
|
|
|
550
|
-
# Records, for a resolved cross-class ancestry read, every file that
|
|
551
|
-
#
|
|
552
|
-
#
|
|
553
|
-
#
|
|
554
|
-
#
|
|
555
|
-
# when dependency recording is active. No-op when the class is not a
|
|
556
|
-
# project class (core / stdlib / gem names never appear in the source
|
|
557
|
-
# map). Gated by the caller on the recorder being active.
|
|
493
|
+
# Records, for a resolved cross-class ancestry read, every file that declares `class_name` (its declaration /
|
|
494
|
+
# reopening / superclass / include sites). The `discovered_class_sources` table it reads is populated only by
|
|
495
|
+
# the cross-file project pre-pass ({Inference::ScopeIndexer.discovered_def_index_for_paths}) and only when
|
|
496
|
+
# dependency recording is active. No-op when the class is not a project class (core / stdlib / gem names
|
|
497
|
+
# never appear in the source map). Gated by the caller on the recorder being active.
|
|
558
498
|
def record_class_dependency(class_name)
|
|
559
499
|
sites = @discovery.discovered_class_sources[class_name.to_s]
|
|
560
500
|
return if sites.nil?
|
|
@@ -563,15 +503,11 @@ module Rigor
|
|
|
563
503
|
end
|
|
564
504
|
private :record_class_dependency
|
|
565
505
|
|
|
566
|
-
# v0.1.2 — per-class table mapping `method_name (Symbol) →
|
|
567
|
-
#
|
|
568
|
-
# `
|
|
569
|
-
#
|
|
570
|
-
#
|
|
571
|
-
# any post-hoc `private :name, ...` named-argument calls.
|
|
572
|
-
# Consumed by the `def.method-visibility-mismatch` rule
|
|
573
|
-
# so explicit-non-self calls to a private method surface
|
|
574
|
-
# a diagnostic.
|
|
506
|
+
# v0.1.2 — per-class table mapping `method_name (Symbol) → :public | :private | :protected`. Populated by
|
|
507
|
+
# `ScopeIndexer` for every `def` it sees inside a class body, with the visibility taken from the surrounding
|
|
508
|
+
# `private` / `protected` / `public` modifier state plus any post-hoc `private :name, ...` named-argument
|
|
509
|
+
# calls. Consumed by the `def.method-visibility-mismatch` rule so explicit-non-self calls to a private method
|
|
510
|
+
# surface a diagnostic.
|
|
575
511
|
def discovered_method_visibility(class_name, method_name)
|
|
576
512
|
table = @discovery.discovered_method_visibilities[class_name.to_s]
|
|
577
513
|
return nil unless table
|
|
@@ -579,15 +515,11 @@ module Rigor
|
|
|
579
515
|
table[method_name.to_sym]
|
|
580
516
|
end
|
|
581
517
|
|
|
582
|
-
# Closes the "`params[:f] ||= []; params[:f] << x`" precision
|
|
583
|
-
#
|
|
584
|
-
#
|
|
585
|
-
# `
|
|
586
|
-
#
|
|
587
|
-
# `(receiver_kind, receiver_name, literal_key)` lets the
|
|
588
|
-
# ExpressionTyper's `[]` dispatch hand back the narrowed
|
|
589
|
-
# type. Receiver-rebind and `[]=`/mutator invalidation rules
|
|
590
|
-
# are documented at the call sites in
|
|
518
|
+
# Closes the "`params[:f] ||= []; params[:f] << x`" precision gap (ROADMAP § Type-language / engine —
|
|
519
|
+
# indexed-collection narrowing through `Hash[k] ||= default`). After `receiver[key] ||= default`, the next
|
|
520
|
+
# read at `receiver[key]` is known non-nil; recording the post-`||=` type keyed on
|
|
521
|
+
# `(receiver_kind, receiver_name, literal_key)` lets the ExpressionTyper's `[]` dispatch hand back the
|
|
522
|
+
# narrowed type. Receiver-rebind and `[]=`/mutator invalidation rules are documented at the call sites in
|
|
591
523
|
# `Inference::StatementEvaluator`.
|
|
592
524
|
def indexed_narrowing(receiver_kind, receiver_name, key)
|
|
593
525
|
@indexed_narrowings[indexed_key(receiver_kind, receiver_name, key)]
|
|
@@ -615,16 +547,12 @@ module Rigor
|
|
|
615
547
|
rebuild(indexed_narrowings: new_table)
|
|
616
548
|
end
|
|
617
549
|
|
|
618
|
-
# Closes the "stable receiver method-chain narrowing" gap
|
|
619
|
-
#
|
|
620
|
-
#
|
|
621
|
-
#
|
|
622
|
-
# the dominated body's `x.last` reads MUST observe the
|
|
623
|
-
# truthy-narrowed type; the same chain reaching the falsey
|
|
624
|
-
# edge observes the negative narrowing.
|
|
550
|
+
# Closes the "stable receiver method-chain narrowing" gap (ROADMAP § Future cycles / Type-language / engine —
|
|
551
|
+
# "Method-call receiver narrowing across stable receivers"; 2026-05-28 Redmine survey). After
|
|
552
|
+
# `if x.last.is_a?(Array)` the dominated body's `x.last` reads MUST observe the truthy-narrowed type; the same
|
|
553
|
+
# chain reaching the falsey edge observes the negative narrowing.
|
|
625
554
|
#
|
|
626
|
-
# Address shape mirrors {.indexed_narrowing}: stable root
|
|
627
|
-
# variable + no-arg single-hop method name. See
|
|
555
|
+
# Address shape mirrors {.indexed_narrowing}: stable root variable + no-arg single-hop method name. See
|
|
628
556
|
# {ChainKey} for the precise contract.
|
|
629
557
|
def method_chain_narrowing(receiver_kind, receiver_name, method_name)
|
|
630
558
|
@method_chain_narrowings[chain_key(receiver_kind, receiver_name, method_name)]
|
|
@@ -664,23 +592,19 @@ module Rigor
|
|
|
664
592
|
Inference::ExpressionTyper.new(scope: self, tracer: tracer).type_of(node)
|
|
665
593
|
end
|
|
666
594
|
|
|
667
|
-
# Statement-level evaluation: returns the pair `[type, scope']`
|
|
668
|
-
#
|
|
669
|
-
#
|
|
670
|
-
#
|
|
671
|
-
# the catalogue of nodes that thread scope; everything else
|
|
672
|
-
# defers to {#type_of} and returns the receiver scope unchanged.
|
|
595
|
+
# Statement-level evaluation: returns the pair `[type, scope']` where `type` is what the node produces and
|
|
596
|
+
# `scope'` is the scope observable after the node has run. The receiver scope is never mutated. See
|
|
597
|
+
# {Rigor::Inference::StatementEvaluator} for the catalogue of nodes that thread scope; everything else defers
|
|
598
|
+
# to {#type_of} and returns the receiver scope unchanged.
|
|
673
599
|
def evaluate(node, tracer: nil)
|
|
674
600
|
Inference::StatementEvaluator.new(scope: self, tracer: tracer).evaluate(node)
|
|
675
601
|
end
|
|
676
602
|
|
|
677
|
-
# Joins this scope with another at a control-flow merge point. The
|
|
678
|
-
#
|
|
679
|
-
# the
|
|
680
|
-
#
|
|
681
|
-
#
|
|
682
|
-
# nil-injecting half-bound names where the language semantics demand
|
|
683
|
-
# it. The two scopes MUST share the same Environment.
|
|
603
|
+
# Joins this scope with another at a control-flow merge point. The joined scope is bound to every local that
|
|
604
|
+
# BOTH branches bind, with the type widened to the union of both sides. Names bound in only one branch are
|
|
605
|
+
# dropped from the joined scope; the eventual statement-level evaluator (Slice 3 phase 2) is responsible for
|
|
606
|
+
# nil-injecting half-bound names where the language semantics demand it. The two scopes MUST share the same
|
|
607
|
+
# Environment.
|
|
684
608
|
def join(other)
|
|
685
609
|
raise ArgumentError, "join requires a Rigor::Scope, got #{other.class}" unless other.is_a?(Scope)
|
|
686
610
|
|
|
@@ -725,7 +649,9 @@ module Rigor
|
|
|
725
649
|
declaration_sourced: @declaration_sourced,
|
|
726
650
|
source_path: @source_path,
|
|
727
651
|
struct_fold_safe_locals: @struct_fold_safe_locals,
|
|
728
|
-
dynamic_origins: @dynamic_origins
|
|
652
|
+
dynamic_origins: @dynamic_origins,
|
|
653
|
+
local_origins: @local_origins,
|
|
654
|
+
ivar_origins: @ivar_origins
|
|
729
655
|
)
|
|
730
656
|
self.class.new(
|
|
731
657
|
environment: environment, locals: locals,
|
|
@@ -737,16 +663,16 @@ module Rigor
|
|
|
737
663
|
declaration_sourced: declaration_sourced,
|
|
738
664
|
source_path: source_path,
|
|
739
665
|
struct_fold_safe_locals: struct_fold_safe_locals,
|
|
740
|
-
dynamic_origins: dynamic_origins
|
|
666
|
+
dynamic_origins: dynamic_origins,
|
|
667
|
+
local_origins: local_origins,
|
|
668
|
+
ivar_origins: ivar_origins
|
|
741
669
|
)
|
|
742
670
|
end
|
|
743
671
|
|
|
744
672
|
def join_bindings(left, right)
|
|
745
|
-
# Keys present in both, unioned. Iterating `left` and probing
|
|
746
|
-
#
|
|
747
|
-
#
|
|
748
|
-
# the intersection array — this is the control-flow join, run at
|
|
749
|
-
# every branch merge, and was a top allocation site (~75% of
|
|
673
|
+
# Keys present in both, unioned. Iterating `left` and probing `right.key?` yields the same keys in the same
|
|
674
|
+
# order as the prior `(left.keys & right.keys)` while avoiding the two key arrays and the intersection
|
|
675
|
+
# array — this is the control-flow join, run at every branch merge, and was a top allocation site (~75% of
|
|
750
676
|
# `Hash#keys`).
|
|
751
677
|
result = {}
|
|
752
678
|
left.each do |name, ltype|
|
|
@@ -769,16 +695,35 @@ module Rigor
|
|
|
769
695
|
discovery: @discovery,
|
|
770
696
|
indexed_narrowings: join_bindings(@indexed_narrowings, other.indexed_narrowings),
|
|
771
697
|
method_chain_narrowings: join_bindings(@method_chain_narrowings, other.method_chain_narrowings),
|
|
772
|
-
# ADR-58 WD1 — a ref is declaration-sourced after a join only when
|
|
773
|
-
#
|
|
774
|
-
# flow-live
|
|
775
|
-
# merge is flow-live and `possible-nil-receiver` fires as before.
|
|
698
|
+
# ADR-58 WD1 — a ref is declaration-sourced after a join only when BOTH branches agree it is. If either
|
|
699
|
+
# path made the binding flow-live (a method-local nil write / failed-guard narrowing), the merge is
|
|
700
|
+
# flow-live and `possible-nil-receiver` fires as before.
|
|
776
701
|
declaration_sourced: join_declaration_sourced(other),
|
|
777
702
|
source_path: source_path,
|
|
778
|
-
dynamic_origins: @dynamic_origins
|
|
703
|
+
dynamic_origins: @dynamic_origins,
|
|
704
|
+
local_origins: join_origins(@local_origins, other.local_origins),
|
|
705
|
+
ivar_origins: join_origins(@ivar_origins, other.ivar_origins)
|
|
779
706
|
)
|
|
780
707
|
end
|
|
781
708
|
|
|
709
|
+
# ADR-82 WD1 — merge two branches' propagated origins (self wins on a name conflict; advisory metadata, so
|
|
710
|
+
# a disagreement is harmless). Zero-alloc when either side is empty — the common case, keeping the join
|
|
711
|
+
# hot-path cost negligible.
|
|
712
|
+
def join_origins(mine, theirs)
|
|
713
|
+
return mine if mine.equal?(theirs) || theirs.empty?
|
|
714
|
+
return theirs if mine.empty?
|
|
715
|
+
|
|
716
|
+
theirs.merge(mine).freeze
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# ADR-82 WD1 — rebinding drops any propagated origin for the name (the new value's provenance is set
|
|
720
|
+
# afterward by `with_local_origin` when it is a `Dynamic` with a recorded cause). Zero-alloc when the name
|
|
721
|
+
# carries no origin — the overwhelming common case, so this stays off the with_local hot-path budget.
|
|
722
|
+
def drop_origin(origins, name)
|
|
723
|
+
key = name.to_sym
|
|
724
|
+
origins.key?(key) ? origins.reject { |k, _| k == key }.freeze : origins
|
|
725
|
+
end
|
|
726
|
+
|
|
782
727
|
def join_declaration_sourced(other)
|
|
783
728
|
return @declaration_sourced if @declaration_sourced.equal?(other.declaration_sourced)
|
|
784
729
|
return EMPTY_DECLARATION_SOURCED if @declaration_sourced.empty? || other.declaration_sourced.empty?
|