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
|
@@ -34,21 +34,18 @@ module Rigor
|
|
|
34
34
|
module Inference
|
|
35
35
|
# Coordinates method dispatch for the inference engine.
|
|
36
36
|
#
|
|
37
|
-
# Given `(receiver_type, method_name, arg_types, block_type, environment)`,
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# and decide whether to record a `FallbackTracer` event.
|
|
37
|
+
# Given `(receiver_type, method_name, arg_types, block_type, environment)`, the dispatcher
|
|
38
|
+
# returns the inferred result type or `nil` when no rule matches. `nil` is a deliberately
|
|
39
|
+
# blunt "I don't know" signal: callers (today only `ExpressionTyper`) own the fail-soft
|
|
40
|
+
# fallback and decide whether to record a `FallbackTracer` event.
|
|
42
41
|
#
|
|
43
|
-
# Tier order is documented inline in `resolve`; the precise-tier
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# so a precise per-position/per-key answer wins over the projected
|
|
42
|
+
# Tier order is documented inline in `resolve`; the precise-tier group is built from
|
|
43
|
+
# `PRECISE_TIERS_HEAD`, `STDLIB_SINGLETON_FOLDERS`, and `PRECISE_TIERS_TAIL`. `ShapeDispatch`
|
|
44
|
+
# runs above {RbsDispatch} so a precise per-position/per-key answer wins over the projected
|
|
47
45
|
# `Array#[]`/`Hash#fetch` RBS answer.
|
|
48
46
|
#
|
|
49
|
-
# The `block_type:` and plugin contribution (`dynamic_return`) tiers
|
|
50
|
-
#
|
|
51
|
-
# call sites pass through `dispatch`/`resolve` unchanged.
|
|
47
|
+
# The `block_type:` and plugin contribution (`dynamic_return`) tiers landed in Slice 6 phase C
|
|
48
|
+
# and v0.1.1 Track 2 respectively; all call sites pass through `dispatch`/`resolve` unchanged.
|
|
52
49
|
module MethodDispatcher # rubocop:disable Metrics/ModuleLength
|
|
53
50
|
module_function
|
|
54
51
|
|
|
@@ -73,8 +70,8 @@ module Rigor
|
|
|
73
70
|
block_type: block_type, environment: environment,
|
|
74
71
|
call_node: call_node, scope: scope
|
|
75
72
|
)
|
|
76
|
-
# `rigor trace` — record the dispatch outcome (resolved type, or
|
|
77
|
-
#
|
|
73
|
+
# `rigor trace` — record the dispatch outcome (resolved type, or the fail-soft `nil` the
|
|
74
|
+
# caller widens to `Dynamic[Top]`).
|
|
78
75
|
if FlowTracer.active?
|
|
79
76
|
FlowTracer.dispatch(
|
|
80
77
|
receiver: receiver_type, method_name: method_name, args: arg_types,
|
|
@@ -89,11 +86,9 @@ module Rigor
|
|
|
89
86
|
call_node: nil, scope: nil)
|
|
90
87
|
return nil if receiver_type.nil?
|
|
91
88
|
|
|
92
|
-
# Build the call context once and thread it — unchanged —
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# positional locals below; only the tier modules consume the
|
|
96
|
-
# context object.
|
|
89
|
+
# Build the call context once and thread it — unchanged — through every tier
|
|
90
|
+
# (`_DispatchTier#try_dispatch`). The dispatcher's own private fallback tiers still read
|
|
91
|
+
# the positional locals below; only the tier modules consume the context object.
|
|
97
92
|
context = CallContext.build(
|
|
98
93
|
receiver: receiver_type, method_name: method_name, args: arg_types,
|
|
99
94
|
block_type: block_type, environment: environment,
|
|
@@ -106,14 +101,12 @@ module Rigor
|
|
|
106
101
|
precise = dispatch_precise_tiers(context)
|
|
107
102
|
return precise if precise
|
|
108
103
|
|
|
109
|
-
# v0.1.1 Track 2 slice 7 — plugin return-type contribution
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# are supplied — the dispatcher's own internal callers
|
|
116
|
-
# (per-element block fold, etc.) skip this tier.
|
|
104
|
+
# v0.1.1 Track 2 slice 7 — plugin return-type contribution tier. Sits ahead of
|
|
105
|
+
# `RbsDispatch` so a plugin that understands a domain-specific dispatch (e.g. an
|
|
106
|
+
# `ActiveRecord::Base.find` returning `Nominal[<resolved model>]`) wins over the
|
|
107
|
+
# RBS-projected envelope. Only consults the registry when both `call_node` and `scope`
|
|
108
|
+
# are supplied — the dispatcher's own internal callers (per-element block fold, etc.)
|
|
109
|
+
# skip this tier.
|
|
117
110
|
plugin_result = try_plugin_contribution(call_node, scope, receiver_type)
|
|
118
111
|
if plugin_result
|
|
119
112
|
if plugin_result.is_a?(Type::Dynamic)
|
|
@@ -122,29 +115,22 @@ module Rigor
|
|
|
122
115
|
return plugin_result
|
|
123
116
|
end
|
|
124
117
|
|
|
125
|
-
# ADR-20 slice 3 — Rigor-bundled HKT-builtin return-
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
# populates this tier lives in
|
|
133
|
-
# `Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES`;
|
|
134
|
-
# plugin-supplied per-method overrides are out of
|
|
135
|
-
# scope for slice 3 and continue to flow through the
|
|
118
|
+
# ADR-20 slice 3 — Rigor-bundled HKT-builtin return-type tier. Sits ABOVE
|
|
119
|
+
# `RbsDispatch.try_dispatch` so the handful of stdlib methods whose upstream RBS
|
|
120
|
+
# signature is `untyped` but whose runtime shape Rigor models via a Lightweight HKT
|
|
121
|
+
# (`json::value`, eventually `dry_monads::result`, …) get the reduced type instead of
|
|
122
|
+
# `Dynamic[Top]`. The table that populates this tier lives in
|
|
123
|
+
# `Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES`; plugin-supplied per-method
|
|
124
|
+
# overrides are out of scope for slice 3 and continue to flow through the
|
|
136
125
|
# `try_plugin_contribution` tier above.
|
|
137
126
|
hkt_builtin_result = try_hkt_builtin_return(receiver_type, method_name, arg_types, environment)
|
|
138
127
|
return hkt_builtin_result if hkt_builtin_result
|
|
139
128
|
|
|
140
|
-
# Rigor-bundled static refinement tier. Sits between HKT
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# without modifying the vendored `ruby/rbs` submodule.
|
|
146
|
-
# The override table lives in
|
|
147
|
-
# `Rigor::Builtins::StaticReturnRefinements::OVERRIDES`.
|
|
129
|
+
# Rigor-bundled static refinement tier. Sits between HKT and RBS so stdlib methods whose
|
|
130
|
+
# upstream RBS is broader than the documented behaviour (e.g. `Kernel#__dir__` declared
|
|
131
|
+
# `() -> String?` when the documented return is `non-empty-string | nil`) get the
|
|
132
|
+
# tightened type without modifying the vendored `ruby/rbs` submodule. The override table
|
|
133
|
+
# lives in `Rigor::Builtins::StaticReturnRefinements::OVERRIDES`.
|
|
148
134
|
static_refinement = try_static_refinement(receiver_type, method_name, arg_types)
|
|
149
135
|
return static_refinement if static_refinement
|
|
150
136
|
|
|
@@ -157,121 +143,106 @@ module Rigor
|
|
|
157
143
|
return rbs_result
|
|
158
144
|
end
|
|
159
145
|
|
|
160
|
-
# ADR-16 Tier B / Tier C — synthetic-method tier. Sits
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
# types for Tier B emissions; Tier C emissions still return
|
|
167
|
-
# `Dynamic[T]` at this tier (slice 6b is the Tier C
|
|
168
|
-
# promotion via ADR-13's resolver chain).
|
|
146
|
+
# ADR-16 Tier B / Tier C — synthetic-method tier. Sits BELOW RBS dispatch (per WD13:
|
|
147
|
+
# user-authored RBS overrides substrate synthesis) and ABOVE the dependency-source
|
|
148
|
+
# inference tier so a plugin's declared emit table beats the generic gem-source fallback
|
|
149
|
+
# for the same class. Slice 6a-TierB (origin_module dispatch) lands precise return types
|
|
150
|
+
# for Tier B emissions; Tier C emissions still return `Dynamic[T]` at this tier (slice 6b
|
|
151
|
+
# is the Tier C promotion via ADR-13's resolver chain).
|
|
169
152
|
synthetic_result = try_synthetic_method(
|
|
170
153
|
receiver_type, method_name, arg_types, block_type, environment
|
|
171
154
|
)
|
|
172
155
|
return synthetic_result if synthetic_result
|
|
173
156
|
|
|
174
|
-
# ADR-17 slice 2 — project-side patched-method tier.
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
# `
|
|
180
|
-
# `
|
|
181
|
-
# populated `ProjectPatchedMethods` with the `(class,
|
|
182
|
-
# method, kind)` triple; this tier surfaces it as
|
|
183
|
-
# `Dynamic[top]` so the patched call resolves
|
|
184
|
-
# cross-file without `call.undefined-method`.
|
|
157
|
+
# ADR-17 slice 2 — project-side patched-method tier. Sits BELOW the substrate / plugin
|
|
158
|
+
# tiers and ABOVE dependency-source inference per ADR-17 § "Inference contract". When
|
|
159
|
+
# the user's `pre_eval:` list named a file that re-opens a class (e.g.,
|
|
160
|
+
# `lib/core_ext/string_extensions.rb` declaring `class String; def to_url; end; end`),
|
|
161
|
+
# the pre-pass populated `ProjectPatchedMethods` with the `(class, method, kind)` triple;
|
|
162
|
+
# this tier surfaces it as `Dynamic[top]` so the patched call resolves cross-file without
|
|
163
|
+
# `call.undefined-method`.
|
|
185
164
|
patched_result = try_project_patched_method(receiver_type, method_name, environment)
|
|
186
165
|
if patched_result
|
|
187
166
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
188
167
|
return patched_result
|
|
189
168
|
end
|
|
190
169
|
|
|
191
|
-
# ADR-10 slice 2b-ii — dependency-source inference tier.
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# `Dynamic[top]` — slice 2b-ii deliberately stops at the
|
|
198
|
-
# dynamic-origin envelope; per-method return-type
|
|
199
|
-
# precision is queued for a later slice.
|
|
170
|
+
# ADR-10 slice 2b-ii — dependency-source inference tier. Sits BELOW RBS dispatch (RBS /
|
|
171
|
+
# RBS::Inline / generated stubs / plugin contracts always win) and ABOVE the user-class
|
|
172
|
+
# fallback so a method defined in an opt-in gem stops emitting `call.undefined-method`
|
|
173
|
+
# even when no signature contract resolves. Returns `Dynamic[top]` — slice 2b-ii
|
|
174
|
+
# deliberately stops at the dynamic-origin envelope; per-method return-type precision is
|
|
175
|
+
# queued for a later slice.
|
|
200
176
|
dep_source_result = try_dependency_source(receiver_type, method_name, environment)
|
|
201
177
|
if dep_source_result
|
|
202
178
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
203
179
|
return dep_source_result
|
|
204
180
|
end
|
|
205
181
|
|
|
206
|
-
# v0.1.3 — discovered-method dispatch tier. When the
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
# `def`, `define_method`, and `alias_method`; the
|
|
214
|
-
# `discovered_method?` consult here closes the
|
|
215
|
-
# fail-soft-event hot spot on implicit-self calls
|
|
216
|
-
# (`sibling_private(...)`) inside `lib/rigor/`'s own
|
|
182
|
+
# v0.1.3 — discovered-method dispatch tier. When the receiver class has no RBS BUT
|
|
183
|
+
# scope_indexer recorded `def method_name` for that class (or singleton), the call
|
|
184
|
+
# dispatches to `Dynamic[top]` rather than falling through to the user-class fallback.
|
|
185
|
+
# Sits below RBS / dependency-source so authoritative signatures still win. The
|
|
186
|
+
# scope-indexer-built table records every project-side `def`, `define_method`, and
|
|
187
|
+
# `alias_method`; the `discovered_method?` consult here closes the fail-soft-event hot
|
|
188
|
+
# spot on implicit-self calls (`sibling_private(...)`) inside `lib/rigor/`'s own
|
|
217
189
|
# internals (analyser private helpers don't have RBS).
|
|
218
190
|
discovered_result = try_discovered_method(receiver_type, method_name, scope)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
#
|
|
230
|
-
#
|
|
191
|
+
if discovered_result
|
|
192
|
+
# ADR-82 WD2/WD3 — the call resolved to a discovered user method but its return could not be
|
|
193
|
+
# inferred (bare `def`, untyped-parameter chain). Record it as an inference gap, not the
|
|
194
|
+
# generic `unsupported_syntax`, so `coverage --protection` routes it to ADR-58/67.
|
|
195
|
+
if discovered_result.is_a?(Type::Dynamic)
|
|
196
|
+
scope&.record_dynamic_origin(call_node, DynamicOrigin::INFERRED_RETURN_UNTYPED)
|
|
197
|
+
end
|
|
198
|
+
return discovered_result
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# ADR-5 robustness — synthesized-stub-type tier. When the receiver is a type Rigor
|
|
202
|
+
# invented to make an otherwise-unbuildable project signature resolve (a
|
|
203
|
+
# missing-namespace module, or a stub for a referenced-but-undeclared type like an
|
|
204
|
+
# unavailable `DRb::DRbServer`), the stub carries no methods, so an unresolved call
|
|
205
|
+
# against it would otherwise mis-fire `call.undefined-method`. Resolve it to
|
|
206
|
+
# `Dynamic[Top]` instead — the same no-false-positive contract as the dependency-source
|
|
207
|
+
# tier. Sits below every real resolution tier so a genuine signature always wins.
|
|
231
208
|
stub_result = try_synthesized_stub_type(receiver_type, environment)
|
|
232
209
|
return stub_result if stub_result
|
|
233
210
|
|
|
234
|
-
# Slice 7 phase 10 — user-class ancestor fallback. When
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# receivers. This resolves Kernel intrinsics
|
|
241
|
-
# (`require`, `raise`, `puts`, ...) and Module/Class
|
|
242
|
-
# introspection (`attr_reader`, `private`, ...) on
|
|
243
|
-
# user classes without requiring the user to author
|
|
211
|
+
# Slice 7 phase 10 — user-class ancestor fallback. When the receiver is `Nominal[T]` or
|
|
212
|
+
# `Singleton[T]` for a class not in the RBS environment (typically a user-defined class),
|
|
213
|
+
# retry the dispatch against the implicit ancestor: `Nominal[Object]` for instance
|
|
214
|
+
# receivers and `Singleton[Object]` for singleton receivers. This resolves Kernel
|
|
215
|
+
# intrinsics (`require`, `raise`, `puts`, ...) and Module/Class introspection
|
|
216
|
+
# (`attr_reader`, `private`, ...) on user classes without requiring the user to author
|
|
244
217
|
# their own RBS.
|
|
245
|
-
try_user_class_fallback(receiver_type, environment, call_node, context)
|
|
218
|
+
fallback_result = try_user_class_fallback(receiver_type, environment, call_node, context)
|
|
219
|
+
# ADR-82 WD2/WD3 — a user-class receiver whose call resolved to the lenient ancestor fallback
|
|
220
|
+
# with no inferable return. Same inference-gap provenance as the discovered-method tier, so a
|
|
221
|
+
# downstream dispatch on this value is labeled honestly rather than `unsupported_syntax`.
|
|
222
|
+
if fallback_result.is_a?(Type::Dynamic)
|
|
223
|
+
scope&.record_dynamic_origin(call_node, DynamicOrigin::INFERRED_RETURN_UNTYPED)
|
|
224
|
+
end
|
|
225
|
+
fallback_result
|
|
246
226
|
end
|
|
247
227
|
|
|
248
|
-
# v0.1.3 — discovered-method dispatch tier. `scope` carries
|
|
249
|
-
#
|
|
250
|
-
# `
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
# — the dispatcher cannot infer a more precise return type
|
|
255
|
-
# from the bare `def` shape, but the call site stops being a
|
|
256
|
-
# fail-soft hot spot.
|
|
228
|
+
# v0.1.3 — discovered-method dispatch tier. `scope` carries the `discovered_methods` table
|
|
229
|
+
# built once per program by `ScopeIndexer` (a `Hash[String, Hash[Symbol, :instance |
|
|
230
|
+
# :singleton]]`). When the receiver names a discovered class AND the requested method is
|
|
231
|
+
# recorded for that class's appropriate kind, return `Type::Combinator.untyped` — the
|
|
232
|
+
# dispatcher cannot infer a more precise return type from the bare `def` shape, but the
|
|
233
|
+
# call site stops being a fail-soft hot spot.
|
|
257
234
|
#
|
|
258
|
-
# Returns `nil` when scope / receiver class is unavailable,
|
|
259
|
-
#
|
|
260
|
-
# `
|
|
261
|
-
#
|
|
262
|
-
# `ExpressionTyper#try_user_method_inference` tier can
|
|
263
|
-
# re-type the body for a precise return type rather than
|
|
264
|
-
# collapsing to `Dynamic[top]` here).
|
|
235
|
+
# Returns `nil` when scope / receiver class is unavailable, when the method is not in the
|
|
236
|
+
# discovered table, OR when `discovered_def_nodes` carries a re-typable body for the method
|
|
237
|
+
# (so the downstream `ExpressionTyper#try_user_method_inference` tier can re-type the body
|
|
238
|
+
# for a precise return type rather than collapsing to `Dynamic[top]` here).
|
|
265
239
|
#
|
|
266
|
-
# The tier does NOT gate on `rbs_class_known?`. RBS dispatch
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
# through to the user-class fallback would mistakenly fire
|
|
273
|
-
# `call.undefined-method`. Honoring the discovered table
|
|
274
|
-
# here keeps the sibling-private call resolution working
|
|
240
|
+
# The tier does NOT gate on `rbs_class_known?`. RBS dispatch already had its turn upstream
|
|
241
|
+
# and returned `nil` (otherwise we wouldn't be here). When RBS knows the class but the
|
|
242
|
+
# particular method is missing from the sig — common for internal helpers and for
|
|
243
|
+
# auto-generated stubs that emit `class X` without enumerating every method — falling
|
|
244
|
+
# through to the user-class fallback would mistakenly fire `call.undefined-method`.
|
|
245
|
+
# Honoring the discovered table here keeps the sibling-private call resolution working
|
|
275
246
|
# under partial RBS coverage.
|
|
276
247
|
def try_discovered_method(receiver_type, method_name, scope)
|
|
277
248
|
return nil if scope.nil?
|
|
@@ -279,23 +250,20 @@ module Rigor
|
|
|
279
250
|
class_name, kind = discovered_method_lookup(receiver_type)
|
|
280
251
|
return nil if class_name.nil?
|
|
281
252
|
return nil unless scope.discovered_method?(class_name, method_name, kind)
|
|
282
|
-
# Decline when a re-typable body is recorded for the method, so the
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
# call resolution, ADR-57 follow-up).
|
|
253
|
+
# Decline when a re-typable body is recorded for the method, so the downstream
|
|
254
|
+
# `ExpressionTyper` inference tier can fold a precise return instead of collapsing to
|
|
255
|
+
# `Dynamic[top]` here — instance bodies via `user_def_for`, singleton bodies (`def
|
|
256
|
+
# self.x` / `module_function`) via `singleton_def_for` (module-singleton call
|
|
257
|
+
# resolution, ADR-57 follow-up).
|
|
288
258
|
return nil if kind == :instance && scope.user_def_for(class_name, method_name)
|
|
289
259
|
return nil if kind == :singleton && scope.singleton_def_for(class_name, method_name)
|
|
290
260
|
|
|
291
261
|
Type::Combinator.untyped
|
|
292
262
|
end
|
|
293
263
|
|
|
294
|
-
# Resolves the `(class_name, kind)` pair scope_indexer keys
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
# methods on X. Other carriers return `[nil, nil]` so the
|
|
298
|
-
# tier declines.
|
|
264
|
+
# Resolves the `(class_name, kind)` pair scope_indexer keys its `discovered_methods` table
|
|
265
|
+
# on. `Nominal[X]` looks up instance methods on X; `Singleton[X]` looks up singleton
|
|
266
|
+
# methods on X. Other carriers return `[nil, nil]` so the tier declines.
|
|
299
267
|
def discovered_method_lookup(receiver_type)
|
|
300
268
|
case receiver_type
|
|
301
269
|
when Type::Nominal then [receiver_type.class_name, :instance]
|
|
@@ -304,13 +272,11 @@ module Rigor
|
|
|
304
272
|
end
|
|
305
273
|
end
|
|
306
274
|
|
|
307
|
-
# ADR-5 robustness — returns `Dynamic[Top]` when the receiver is
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
# honest answer for a type Rigor invented is "unknown shape",
|
|
313
|
-
# i.e. `Dynamic[Top]`. Returns nil (declines) for any real type.
|
|
275
|
+
# ADR-5 robustness — returns `Dynamic[Top]` when the receiver is an instance or singleton
|
|
276
|
+
# of a type Rigor synthesized (a missing-namespace module or a referenced-type stub). The
|
|
277
|
+
# stub has no methods, so the call would otherwise reach the user-class fallback and
|
|
278
|
+
# surface `call.undefined-method`; the honest answer for a type Rigor invented is "unknown
|
|
279
|
+
# shape", i.e. `Dynamic[Top]`. Returns nil (declines) for any real type.
|
|
314
280
|
def try_synthesized_stub_type(receiver_type, environment)
|
|
315
281
|
return nil if environment.nil?
|
|
316
282
|
|
|
@@ -329,13 +295,11 @@ module Rigor
|
|
|
329
295
|
Type::Combinator.untyped
|
|
330
296
|
end
|
|
331
297
|
|
|
332
|
-
# ADR-20 slice 3 — looks up the receiver / method pair
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
# succeeds; returns `nil` otherwise so the dispatcher
|
|
338
|
-
# falls through to RBS.
|
|
298
|
+
# ADR-20 slice 3 — looks up the receiver / method pair in
|
|
299
|
+
# {Rigor::Builtins::HktBuiltins::METHOD_RETURN_OVERRIDES} and returns the reduced HKT type.
|
|
300
|
+
# Only fires when the receiver is a {Rigor::Type::Singleton} (the `JSON.parse` shape) and
|
|
301
|
+
# the registry-backed reduction succeeds; returns `nil` otherwise so the dispatcher falls
|
|
302
|
+
# through to RBS.
|
|
339
303
|
def try_hkt_builtin_return(receiver_type, method_name, arg_types, environment)
|
|
340
304
|
return nil if environment.nil?
|
|
341
305
|
return nil unless receiver_type.is_a?(Type::Singleton)
|
|
@@ -349,23 +313,17 @@ module Rigor
|
|
|
349
313
|
)
|
|
350
314
|
end
|
|
351
315
|
|
|
352
|
-
# Consults the Rigor-bundled static refinement table for a
|
|
353
|
-
#
|
|
354
|
-
#
|
|
355
|
-
#
|
|
356
|
-
# Nominal[
|
|
357
|
-
# the owner. Explicit `Kernel.__dir__` (receiver_type =
|
|
358
|
-
# Singleton[Kernel]) and instance-side calls
|
|
359
|
-
# (receiver_type = Nominal[Klass]) share the `:both` row.
|
|
316
|
+
# Consults the Rigor-bundled static refinement table for a (owner-class, method-name,
|
|
317
|
+
# kind) entry. Kernel methods are mixed into every non-BasicObject class, so an
|
|
318
|
+
# implicit-self `__dir__` call (receiver_type = Nominal[ClassName]) is matched by looking
|
|
319
|
+
# up Kernel as the owner. Explicit `Kernel.__dir__` (receiver_type = Singleton[Kernel]) and
|
|
320
|
+
# instance-side calls (receiver_type = Nominal[Klass]) share the `:both` row.
|
|
360
321
|
#
|
|
361
|
-
# The receiver-side ancestor check is intentionally cheap:
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
# would also alter the runtime answer; users with that
|
|
367
|
-
# configuration opt out via a `signature_paths` overlay
|
|
368
|
-
# declaring their own return type.
|
|
322
|
+
# The receiver-side ancestor check is intentionally cheap: any non-BasicObject Nominal /
|
|
323
|
+
# Singleton matches every Kernel-owned override. BasicObject explicitly excludes Kernel and
|
|
324
|
+
# is therefore rejected. The narrow risk of a user-defined `def __dir__` shadowing Kernel's
|
|
325
|
+
# method would also alter the runtime answer; users with that configuration opt out via a
|
|
326
|
+
# `signature_paths` overlay declaring their own return type.
|
|
369
327
|
def try_static_refinement(receiver_type, method_name, arg_types)
|
|
370
328
|
candidates = Rigor::Builtins::StaticReturnRefinements.owners_for(method_name)
|
|
371
329
|
return nil if candidates.empty?
|
|
@@ -382,11 +340,10 @@ module Rigor
|
|
|
382
340
|
)
|
|
383
341
|
end
|
|
384
342
|
|
|
385
|
-
# Picks the most specific override owner the receiver
|
|
386
|
-
#
|
|
387
|
-
#
|
|
388
|
-
#
|
|
389
|
-
# — those carriers go through their own narrower tiers).
|
|
343
|
+
# Picks the most specific override owner the receiver honours. For Kernel-owned overrides
|
|
344
|
+
# the receiver simply needs to be a real-class Nominal / Singleton (i.e. not BasicObject
|
|
345
|
+
# and not a Dynamic / Constant / shape carrier — those carriers go through their own
|
|
346
|
+
# narrower tiers).
|
|
390
347
|
def static_refinement_owner_for(receiver_type, candidates)
|
|
391
348
|
receiver_class = static_refinement_class_for(receiver_type)
|
|
392
349
|
return nil unless receiver_class
|
|
@@ -403,19 +360,14 @@ module Rigor
|
|
|
403
360
|
end
|
|
404
361
|
end
|
|
405
362
|
|
|
406
|
-
# ADR-2 § "Flow Contribution Bundle" / v0.1.1 Track 2
|
|
407
|
-
#
|
|
408
|
-
# `
|
|
409
|
-
# `
|
|
410
|
-
# `FlowContribution::Merger`, and returns the merged
|
|
411
|
-
# `return_type` slot (or nil when no plugin contributed a
|
|
412
|
-
# return type).
|
|
363
|
+
# ADR-2 § "Flow Contribution Bundle" / v0.1.1 Track 2 slice 7; ADR-52 WD3 — consults each
|
|
364
|
+
# loaded plugin's gated `dynamic_return` rules, wraps the contributed types as
|
|
365
|
+
# `FlowContribution` bundles, merges them through `FlowContribution::Merger`, and returns
|
|
366
|
+
# the merged `return_type` slot (or nil when no plugin contributed a return type).
|
|
413
367
|
#
|
|
414
|
-
# Plugins whose hook raises have their contribution
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
# ADR-2 § "Plugin Trust and I/O Policy") is owned by
|
|
418
|
-
# `Analysis::Runner#plugin_emitted_diagnostics`.
|
|
368
|
+
# Plugins whose hook raises have their contribution silently dropped for this call so the
|
|
369
|
+
# dispatch chain keeps moving — the run-level diagnostic envelope (per ADR-2 § "Plugin
|
|
370
|
+
# Trust and I/O Policy") is owned by `Analysis::Runner#plugin_emitted_diagnostics`.
|
|
419
371
|
def try_plugin_contribution(call_node, scope, receiver_type)
|
|
420
372
|
return nil if call_node.nil? || scope.nil?
|
|
421
373
|
|
|
@@ -428,27 +380,20 @@ module Rigor
|
|
|
428
380
|
FlowContribution::Merger.merge(contributions).return_type
|
|
429
381
|
end
|
|
430
382
|
|
|
431
|
-
# ADR-16 synthetic-method tier. Slice 2b shipped the floor —
|
|
432
|
-
#
|
|
433
|
-
#
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
#
|
|
438
|
-
#
|
|
439
|
-
# `
|
|
440
|
-
#
|
|
441
|
-
#
|
|
442
|
-
#
|
|
443
|
-
# `
|
|
444
|
-
#
|
|
445
|
-
# (when the class is in RBS). Unparameterised class names
|
|
446
|
-
# only — parameterised forms (`Array[String]`,
|
|
447
|
-
# `Hash[K, V]`) and plugin-supplied utility-type names
|
|
448
|
-
# (`Pick<T, K>`) require routing through the full ADR-13
|
|
449
|
-
# `Plugin::TypeNodeResolver` chain, which slice 6 does
|
|
450
|
-
# not yet wire in (the resolver chain is consulted only
|
|
451
|
-
# for `%a{rigor:v1:…}` payloads as of ADR-13 slice 3).
|
|
383
|
+
# ADR-16 synthetic-method tier. Slice 2b shipped the floor — a match short-circuits at the
|
|
384
|
+
# right precedence (above dep-source / discovered / user-class-fallback; below RBS) and
|
|
385
|
+
# returns `Dynamic[T]`. Slice 6 (precision promotion):
|
|
386
|
+
# - Tier B path (slice 6a, `provenance[:origin_module]` recorded by the slice-3b scanner):
|
|
387
|
+
# redispatch on `Nominal[origin_module]` via `RbsDispatch` so the module's authored RBS
|
|
388
|
+
# return type wins. Devise's `valid_password?` returns `bool`, not `Dynamic[T]`.
|
|
389
|
+
# - Tier C path (slice 6b, plain `return_type:` string from the manifest's emit table):
|
|
390
|
+
# look up `environment.nominal_for_name(return_type)` so `attribute :avatar,
|
|
391
|
+
# Types::String` emits a synthetic reader returning
|
|
392
|
+
# `Nominal[ActiveStorage::Attached::One]` (when the class is in RBS). Unparameterised
|
|
393
|
+
# class names only — parameterised forms (`Array[String]`, `Hash[K, V]`) and
|
|
394
|
+
# plugin-supplied utility-type names (`Pick<T, K>`) require routing through the full
|
|
395
|
+
# ADR-13 `Plugin::TypeNodeResolver` chain, which slice 6 does not yet wire in (the
|
|
396
|
+
# resolver chain is consulted only for `%a{rigor:v1:…}` payloads as of ADR-13 slice 3).
|
|
452
397
|
def try_synthetic_method(receiver_type, method_name, arg_types, block_type, environment)
|
|
453
398
|
index = environment&.synthetic_method_index
|
|
454
399
|
return nil if index.nil? || index.empty?
|
|
@@ -466,12 +411,10 @@ module Rigor
|
|
|
466
411
|
promoted || Type::Combinator.untyped
|
|
467
412
|
end
|
|
468
413
|
|
|
469
|
-
# First non-nil promotion wins. Tier B (origin_module) and
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
# entries and leaves it absent for Tier C, so the two
|
|
474
|
-
# paths self-route per match.
|
|
414
|
+
# First non-nil promotion wins. Tier B (origin_module) and Tier C (return_type nominal
|
|
415
|
+
# lookup) are tried in the same registration-order pass per WD11 first-wins — the slice-3b
|
|
416
|
+
# scanner sets `origin_module` for Tier B entries and leaves it absent for Tier C, so the
|
|
417
|
+
# two paths self-route per match.
|
|
475
418
|
def promote_synthetic_match(matches, method_name, arg_types, block_type, environment)
|
|
476
419
|
return nil if environment.nil?
|
|
477
420
|
|
|
@@ -484,11 +427,10 @@ module Rigor
|
|
|
484
427
|
nil
|
|
485
428
|
end
|
|
486
429
|
|
|
487
|
-
# Slice 6a-TierB. For Tier B emissions (origin_module
|
|
488
|
-
#
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
# entry or when the origin_module is not in the RBS env.
|
|
430
|
+
# Slice 6a-TierB. For Tier B emissions (origin_module recorded in provenance), redispatch
|
|
431
|
+
# the call on the included module's `Nominal[...]` type via `RbsDispatch`. Returns nil when
|
|
432
|
+
# the SyntheticMethod is not a Tier B entry or when the origin_module is not in the RBS
|
|
433
|
+
# env.
|
|
492
434
|
def promote_via_origin_module(synthetic, method_name, arg_types, block_type, environment)
|
|
493
435
|
module_name = synthetic.provenance[:origin_module]
|
|
494
436
|
return nil unless module_name
|
|
@@ -502,13 +444,11 @@ module Rigor
|
|
|
502
444
|
)
|
|
503
445
|
end
|
|
504
446
|
|
|
505
|
-
# Slice 6b-TierC. For Tier C emissions, look up the
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
#
|
|
510
|
-
# (RBS-style absent return). Falls back to nil when the
|
|
511
|
-
# class is not in the env — caller then returns Dynamic[T].
|
|
447
|
+
# Slice 6b-TierC. For Tier C emissions, look up the manifest-declared `return_type:` string
|
|
448
|
+
# via `environment.nominal_for_name`. Skips the placeholder `"untyped"` (Tier B's
|
|
449
|
+
# record-but-do-not-resolve marker from the slice-3b scanner) and the `"void"` keyword
|
|
450
|
+
# (RBS-style absent return). Falls back to nil when the class is not in the env — caller
|
|
451
|
+
# then returns Dynamic[T].
|
|
512
452
|
TIER_C_PLACEHOLDER_RETURNS = %w[untyped void].freeze
|
|
513
453
|
private_constant :TIER_C_PLACEHOLDER_RETURNS
|
|
514
454
|
|
|
@@ -525,15 +465,12 @@ module Rigor
|
|
|
525
465
|
end
|
|
526
466
|
end
|
|
527
467
|
|
|
528
|
-
# ADR-17 slice 2 — project-side patched-method tier.
|
|
529
|
-
#
|
|
530
|
-
# `
|
|
531
|
-
# `
|
|
532
|
-
#
|
|
533
|
-
#
|
|
534
|
-
# `Dynamic[Nominal[String]]` instead of the pre-3a
|
|
535
|
-
# `Dynamic[Top]`. Falls back to `Dynamic[Top]` when the
|
|
536
|
-
# heuristic declined (non-literal tail expression).
|
|
468
|
+
# ADR-17 slice 2 — project-side patched-method tier. Slice 3a uses the registry's
|
|
469
|
+
# heuristic-extracted `return_type` (populated via the same
|
|
470
|
+
# `Analysis::DependencySourceInference::ReturnTypeHeuristic` the ADR-10 walker uses): a
|
|
471
|
+
# `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
|
|
472
|
+
# `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
|
|
473
|
+
# `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
|
|
537
474
|
def try_project_patched_method(receiver_type, method_name, environment)
|
|
538
475
|
registry = environment&.project_patched_methods
|
|
539
476
|
return nil if registry.nil? || registry.empty?
|
|
@@ -549,16 +486,12 @@ module Rigor
|
|
|
549
486
|
Type::Combinator.dynamic(entry.return_type)
|
|
550
487
|
end
|
|
551
488
|
|
|
552
|
-
# ADR-10 slice 2b-ii. Consults the per-run
|
|
553
|
-
# `
|
|
554
|
-
#
|
|
555
|
-
#
|
|
556
|
-
#
|
|
557
|
-
#
|
|
558
|
-
# "Inference contract": gem-source-inferred shapes never
|
|
559
|
-
# publish as ground-truth `T`). Returns `nil` when the
|
|
560
|
-
# environment carries no index, the index has no entry, or
|
|
561
|
-
# the receiver has no nominal class to look up.
|
|
489
|
+
# ADR-10 slice 2b-ii. Consults the per-run `Analysis::DependencySourceInference::Index`
|
|
490
|
+
# carried by the environment for `(class_name, method_name)` observations harvested from
|
|
491
|
+
# opt-in gems' `roots:`. On a hit, returns `Combinator.untyped` so the call site carries
|
|
492
|
+
# the `Dynamic[top]` provenance (per ADR-10's "Inference contract": gem-source-inferred
|
|
493
|
+
# shapes never publish as ground-truth `T`). Returns `nil` when the environment carries no
|
|
494
|
+
# index, the index has no entry, or the receiver has no nominal class to look up.
|
|
562
495
|
def try_dependency_source(receiver_type, method_name, environment)
|
|
563
496
|
index = environment&.dependency_source_index
|
|
564
497
|
return nil if index.nil? || index.empty?
|
|
@@ -566,53 +499,40 @@ module Rigor
|
|
|
566
499
|
class_name = dep_source_class_name(receiver_type)
|
|
567
500
|
return nil if class_name.nil?
|
|
568
501
|
|
|
569
|
-
# ADR-10 5a — per-receiver plugin veto. When a
|
|
570
|
-
#
|
|
571
|
-
#
|
|
572
|
-
#
|
|
573
|
-
#
|
|
574
|
-
# authoritative source for that type; gem-source
|
|
575
|
-
# inference must not contribute behind their backs.
|
|
502
|
+
# ADR-10 5a — per-receiver plugin veto. When a registered plugin declares
|
|
503
|
+
# `manifest(owns_receivers: [<class>])` AND the call's receiver IS that class (or a
|
|
504
|
+
# subclass), decline and let plugins handle the call. Plugins that own a receiver are
|
|
505
|
+
# the authoritative source for that type; gem-source inference must not contribute
|
|
506
|
+
# behind their backs.
|
|
576
507
|
return nil if plugin_owns_receiver?(class_name, environment)
|
|
577
508
|
|
|
578
509
|
contribution = index.contribution_for(class_name: class_name, method_name: method_name)
|
|
579
510
|
return dependency_source_return_type(contribution) if contribution
|
|
580
511
|
|
|
581
|
-
# ADR-10 5b — β budget semantics. On a catalog miss,
|
|
582
|
-
#
|
|
583
|
-
#
|
|
584
|
-
#
|
|
585
|
-
# the
|
|
586
|
-
# would otherwise emit `call.undefined-method` for
|
|
587
|
-
# methods Rigor's catalog couldn't reach because the
|
|
588
|
-
# walker hit its cap.
|
|
512
|
+
# ADR-10 5b — β budget semantics. On a catalog miss, if the receiver class belongs to a
|
|
513
|
+
# budget-exceeded gem AND the user opted into `:dependency_silence`, return
|
|
514
|
+
# `Dynamic[top]` rather than falling through to the user-class fallback. The user-class
|
|
515
|
+
# fallback would otherwise emit `call.undefined-method` for methods Rigor's catalog
|
|
516
|
+
# couldn't reach because the walker hit its cap.
|
|
589
517
|
budget_silence_result(class_name, index, environment)
|
|
590
518
|
end
|
|
591
519
|
|
|
592
|
-
# ADR-10 slice 5c — record a
|
|
593
|
-
# `
|
|
594
|
-
#
|
|
595
|
-
#
|
|
596
|
-
#
|
|
597
|
-
# The dispatcher still returns the RBS answer (per
|
|
598
|
-
# ADR-10's tier order: authoritative-source wins), but
|
|
599
|
-
# the reporter accumulates the crossing for end-of-run
|
|
600
|
-
# audit diagnostics.
|
|
520
|
+
# ADR-10 slice 5c — record a `dynamic.dependency-source.boundary-cross` event when RBS
|
|
521
|
+
# dispatch resolves a call AND the receiver class belongs to a `mode: :full` opt-in gem
|
|
522
|
+
# whose Walker also catalogued the same `(class_name, method_name)`. The dispatcher still
|
|
523
|
+
# returns the RBS answer (per ADR-10's tier order: authoritative-source wins), but the
|
|
524
|
+
# reporter accumulates the crossing for end-of-run audit diagnostics.
|
|
601
525
|
#
|
|
602
526
|
# Five honest fall-throughs keep the gate narrow:
|
|
603
527
|
#
|
|
604
|
-
# - environment / index / reporter missing — slice 5c
|
|
605
|
-
#
|
|
606
|
-
# - receiver
|
|
607
|
-
#
|
|
608
|
-
# -
|
|
609
|
-
#
|
|
610
|
-
#
|
|
611
|
-
#
|
|
612
|
-
# only RBS knows about it; nothing to cross.
|
|
613
|
-
# - the RBS-side result is itself `Dynamic[Top]` — the
|
|
614
|
-
# "agreement" is trivially `untyped ≈ untyped`, no
|
|
615
|
-
# meaningful divergence to flag.
|
|
528
|
+
# - environment / index / reporter missing — slice 5c needs all three.
|
|
529
|
+
# - receiver has no nominal class name (Dynamic-only carriers) — nothing to look up.
|
|
530
|
+
# - receiver class doesn't belong to a `mode: :full` gem — the user didn't opt this gem
|
|
531
|
+
# into the distinct dispatch path.
|
|
532
|
+
# - the gem-source catalog has no entry for the method — only RBS knows about it; nothing
|
|
533
|
+
# to cross.
|
|
534
|
+
# - the RBS-side result is itself `Dynamic[Top]` — the "agreement" is trivially `untyped ≈
|
|
535
|
+
# untyped`, no meaningful divergence to flag.
|
|
616
536
|
def record_boundary_cross_if_applicable(receiver_type, method_name, rbs_result, environment)
|
|
617
537
|
class_name = boundary_cross_class_name(receiver_type, environment, rbs_result)
|
|
618
538
|
return if class_name.nil?
|
|
@@ -628,23 +548,20 @@ module Rigor
|
|
|
628
548
|
)
|
|
629
549
|
end
|
|
630
550
|
|
|
631
|
-
# Maps a {DependencySourceInference::Walker::CatalogEntry}
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
# `Dynamic[
|
|
635
|
-
# otherwise fall back to the pre-heuristic `Dynamic[top]`.
|
|
551
|
+
# Maps a {DependencySourceInference::Walker::CatalogEntry} to the Type the dispatcher
|
|
552
|
+
# returns at the call site. When the heuristic recovered a static facet, wrap it in
|
|
553
|
+
# `Dynamic[T]` per ADR-10's gem-boundary contract; otherwise fall back to the
|
|
554
|
+
# pre-heuristic `Dynamic[top]`.
|
|
636
555
|
def dependency_source_return_type(contribution)
|
|
637
556
|
return Type::Combinator.untyped if contribution.return_type.nil?
|
|
638
557
|
|
|
639
558
|
Type::Combinator.dynamic(contribution.return_type)
|
|
640
559
|
end
|
|
641
560
|
|
|
642
|
-
# Composite preflight for {#record_boundary_cross_if_applicable}.
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
#
|
|
646
|
-
# result is not the trivial `Dynamic[Top]` envelope). Returns
|
|
647
|
-
# `nil` to short-circuit otherwise.
|
|
561
|
+
# Composite preflight for {#record_boundary_cross_if_applicable}. Returns the receiver
|
|
562
|
+
# class name only when every prerequisite for emitting the diagnostic is satisfied
|
|
563
|
+
# (environment carries an index + reporter, receiver is a nominal carrier, RBS-side result
|
|
564
|
+
# is not the trivial `Dynamic[Top]` envelope). Returns `nil` to short-circuit otherwise.
|
|
648
565
|
def boundary_cross_class_name(receiver_type, environment, rbs_result)
|
|
649
566
|
return nil if environment.nil?
|
|
650
567
|
return nil if environment.dependency_source_index.nil?
|
|
@@ -675,9 +592,8 @@ module Rigor
|
|
|
675
592
|
Type::Combinator.untyped
|
|
676
593
|
end
|
|
677
594
|
|
|
678
|
-
# ADR-52 WD1 — the per-dispatch plugins × owns_receivers ×
|
|
679
|
-
#
|
|
680
|
-
# table: the union is built once per registry (almost always
|
|
595
|
+
# ADR-52 WD1 — the per-dispatch plugins × owns_receivers × `class_ordering` walk moved into
|
|
596
|
+
# the compiled contribution table: the union is built once per registry (almost always
|
|
681
597
|
# empty → O(1) false) and per-class verdicts memoise per run.
|
|
682
598
|
def plugin_owns_receiver?(class_name, environment)
|
|
683
599
|
registry = environment&.plugin_registry
|
|
@@ -692,55 +608,48 @@ module Rigor
|
|
|
692
608
|
end
|
|
693
609
|
end
|
|
694
610
|
|
|
695
|
-
# ADR-37 slice 2 / ADR-52 WD3 — gathers each plugin's return-type
|
|
696
|
-
#
|
|
697
|
-
#
|
|
698
|
-
# ungated `flow_contribution_for` escape valve was deleted once its
|
|
611
|
+
# ADR-37 slice 2 / ADR-52 WD3 — gathers each plugin's return-type contribution from the
|
|
612
|
+
# gated `dynamic_return` DSL, wrapped as a return-only `FlowContribution` for the shared
|
|
613
|
+
# merger. (The legacy ungated `flow_contribution_for` escape valve was deleted once its
|
|
699
614
|
# five users migrated.)
|
|
700
615
|
EMPTY_CONTRIBUTIONS = [].freeze
|
|
701
616
|
private_constant :EMPTY_CONTRIBUTIONS
|
|
702
617
|
|
|
703
|
-
# Collects every plugin's flow / dynamic-return contribution for one
|
|
704
|
-
#
|
|
705
|
-
#
|
|
618
|
+
# Collects every plugin's flow / dynamic-return contribution for one call site. Two
|
|
619
|
+
# prunings keep this off the hot path on plugin-heavy projects (it was the #1 allocation
|
|
620
|
+
# site and a top CPU cost):
|
|
706
621
|
#
|
|
707
|
-
# 1. Only the plugins that *structurally* implement a per-call path
|
|
708
|
-
#
|
|
709
|
-
#
|
|
710
|
-
#
|
|
711
|
-
#
|
|
712
|
-
#
|
|
713
|
-
#
|
|
714
|
-
#
|
|
715
|
-
#
|
|
716
|
-
#
|
|
717
|
-
#
|
|
718
|
-
#
|
|
719
|
-
#
|
|
720
|
-
#
|
|
721
|
-
# whose `dynamic_return` rules are all `methods:`-gated on other
|
|
722
|
-
# names. A pruned consultation could only have returned nil, so
|
|
723
|
-
# contribution order and content are unchanged.
|
|
622
|
+
# 1. Only the plugins that *structurally* implement a per-call path are visited —
|
|
623
|
+
# `registry.contribution_index.for_method_dispatch` is the registry-ordered subset
|
|
624
|
+
# declaring a `dynamic_return`. Iterating the subset in registry order, and gating each
|
|
625
|
+
# path by membership, yields the exact same contributions in the same order as visiting
|
|
626
|
+
# every plugin would (a skipped plugin's call returns nil/[] anyway). The
|
|
627
|
+
# receiver-class ancestry match still happens per dispatch inside `dynamic_return_type`.
|
|
628
|
+
# 2. Contributions accumulate lazily — allocate only when one actually appears, and share a
|
|
629
|
+
# frozen empty array otherwise. The caller treats the result as read-only (`.empty?` /
|
|
630
|
+
# `Merger.merge`).
|
|
631
|
+
# 3. ADR-52 WD1 — method-name gates compiled at registry build. The global gate makes the
|
|
632
|
+
# common "no plugin cares about this call" case a single Set probe; the per-plugin gate
|
|
633
|
+
# skips a plugin whose `dynamic_return` rules are all `methods:`-gated on other names. A
|
|
634
|
+
# pruned consultation could only have returned nil, so contribution order and content
|
|
635
|
+
# are unchanged.
|
|
724
636
|
def collect_plugin_contributions(registry, call_node, scope, receiver_type)
|
|
725
637
|
index = registry.contribution_index
|
|
726
638
|
relevant = index.for_method_dispatch
|
|
727
639
|
return EMPTY_CONTRIBUTIONS if relevant.empty?
|
|
728
640
|
|
|
729
|
-
# `call_node` is not always a CallNode — the `&:symbol` block
|
|
730
|
-
#
|
|
731
|
-
#
|
|
732
|
-
#
|
|
733
|
-
#
|
|
734
|
-
# flipping `select(&:p)`-style calls onto their no-block
|
|
735
|
-
# Enumerator overloads (caught by the GitLab corpus gate).
|
|
641
|
+
# `call_node` is not always a CallNode — the `&:symbol` block path dispatches with the
|
|
642
|
+
# `Prism::BlockArgumentNode` itself (`ExpressionTyper#symbol_block_return_type`). A bare
|
|
643
|
+
# `.name` here raised, and the raise was silently absorbed by `block_return_type_for`'s
|
|
644
|
+
# rescue, nil-ing the block type and flipping `select(&:p)`-style calls onto their
|
|
645
|
+
# no-block Enumerator overloads (caught by the GitLab corpus gate).
|
|
736
646
|
name = call_node.respond_to?(:name) ? call_node.name : nil
|
|
737
647
|
return EMPTY_CONTRIBUTIONS unless index.dispatch_candidate?(name)
|
|
738
648
|
|
|
739
649
|
collect_gated_contributions(index, relevant, name, call_node, scope, receiver_type)
|
|
740
650
|
end
|
|
741
651
|
|
|
742
|
-
# The post-gate walk, in registry order — the same order the
|
|
743
|
-
# ungated walk used.
|
|
652
|
+
# The post-gate walk, in registry order — the same order the ungated walk used.
|
|
744
653
|
def collect_gated_contributions(index, relevant, name, call_node, scope, receiver_type)
|
|
745
654
|
result = nil
|
|
746
655
|
relevant.each do |plugin|
|
|
@@ -754,39 +663,31 @@ module Rigor
|
|
|
754
663
|
result || EMPTY_CONTRIBUTIONS
|
|
755
664
|
end
|
|
756
665
|
|
|
757
|
-
# Runs the precision tiers (constant fold, shape dispatch,
|
|
758
|
-
#
|
|
759
|
-
#
|
|
760
|
-
#
|
|
761
|
-
# returns nil so the next tier can try. The RBS tier sits
|
|
762
|
-
# below this chain and is invoked by the outer `dispatch`
|
|
666
|
+
# Runs the precision tiers (constant fold, shape dispatch, file-path fold, block fold) in
|
|
667
|
+
# order and returns the first non-nil answer. Each tier owns its own receiver/argument
|
|
668
|
+
# shape checks; a tier that does not recognise the receiver returns nil so the next tier
|
|
669
|
+
# can try. The RBS tier sits below this chain and is invoked by the outer `dispatch`
|
|
763
670
|
# method.
|
|
764
671
|
#
|
|
765
|
-
# The precise-tier folders, consulted in order via the uniform
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
#
|
|
769
|
-
#
|
|
770
|
-
#
|
|
771
|
-
#
|
|
772
|
-
# filter the common cases first. Adding a precise tier is a
|
|
773
|
-
# one-line append here rather than another link in a hand-written
|
|
774
|
-
# `||` ladder.
|
|
672
|
+
# The precise-tier folders, consulted in order via the uniform `_DispatchTier` interface
|
|
673
|
+
# (`try_dispatch(CallContext) -> Type?`). Order is significant: ConstantFolding's
|
|
674
|
+
# exact-value folds win first, the eight stdlib singleton folders sit in the middle (each
|
|
675
|
+
# gates on a distinct `Singleton` receiver, so their relative order is immaterial), and
|
|
676
|
+
# BlockFolding runs last because its rules only apply to block-taking calls — the cheaper
|
|
677
|
+
# arity folds above it filter the common cases first. Adding a precise tier is a one-line
|
|
678
|
+
# append here rather than another link in a hand-written `||` ladder.
|
|
775
679
|
PRECISE_TIERS_HEAD = Ractor.make_shareable([
|
|
776
680
|
ConstantFolding, LiteralStringFolding, ShapeDispatch
|
|
777
681
|
].freeze)
|
|
778
682
|
private_constant :PRECISE_TIERS_HEAD
|
|
779
683
|
|
|
780
|
-
# ADR-53 re-review follow-up (gate-by-held-key applied to the
|
|
781
|
-
#
|
|
782
|
-
#
|
|
783
|
-
#
|
|
784
|
-
#
|
|
785
|
-
#
|
|
786
|
-
#
|
|
787
|
-
# the receiver is not a `Singleton` (the overwhelmingly common
|
|
788
|
-
# case). The table sits where the eight sat in the old flat list:
|
|
789
|
-
# after ShapeDispatch, before KernelDispatch.
|
|
684
|
+
# ADR-53 re-review follow-up (gate-by-held-key applied to the built-in tiers): the eight
|
|
685
|
+
# stdlib singleton folders are mutually exclusive — each fires only on `Singleton[<its
|
|
686
|
+
# class>]`, the first check in every `try_dispatch` — so at most one can match a given
|
|
687
|
+
# receiver and their relative trial order was never observable. Compiling them into a
|
|
688
|
+
# class-name table turns eight no-op trials per call into one Hash read, skipped entirely
|
|
689
|
+
# when the receiver is not a `Singleton` (the overwhelmingly common case). The table sits
|
|
690
|
+
# where the eight sat in the old flat list: after ShapeDispatch, before KernelDispatch.
|
|
790
691
|
STDLIB_SINGLETON_FOLDERS = Ractor.make_shareable({
|
|
791
692
|
"File" => FileFolding,
|
|
792
693
|
"Shellwords" => ShellwordsFolding,
|
|
@@ -805,20 +706,18 @@ module Rigor
|
|
|
805
706
|
private_constant :PRECISE_TIERS_TAIL
|
|
806
707
|
|
|
807
708
|
def dispatch_precise_tiers(context)
|
|
808
|
-
# ADR-48 — Data value folding runs ahead of meta-introspection:
|
|
809
|
-
#
|
|
810
|
-
#
|
|
811
|
-
#
|
|
812
|
-
#
|
|
813
|
-
# member layout), so it never shadows meta's Array/Set/Range lifts.
|
|
709
|
+
# ADR-48 — Data value folding runs ahead of meta-introspection: `meta_new` intercepts
|
|
710
|
+
# every `Singleton[*].new` (returning `Nominal`), which would mask a `Data` class's
|
|
711
|
+
# precise instance. The tier only fires on Data receivers (a `Data.define`, a
|
|
712
|
+
# `DataClass`/`DataInstance`, or a `Singleton` with a recorded member layout), so it
|
|
713
|
+
# never shadows meta's Array/Set/Range lifts.
|
|
814
714
|
data_result = DataFolding.try_dispatch(context)
|
|
815
715
|
return data_result if data_result
|
|
816
716
|
|
|
817
|
-
# ADR-48 Struct follow-up — runs in the same band and for the same
|
|
818
|
-
#
|
|
819
|
-
# `
|
|
820
|
-
#
|
|
821
|
-
# Only fires on Struct receivers, so it never shadows meta's lifts.
|
|
717
|
+
# ADR-48 Struct follow-up — runs in the same band and for the same reason as DataFolding:
|
|
718
|
+
# `meta_new` would otherwise intercept every `Singleton[*].new` (the old
|
|
719
|
+
# `struct_new_lift` produced a bare `Singleton[Struct]`), masking a Struct class's
|
|
720
|
+
# precise instance. Only fires on Struct receivers, so it never shadows meta's lifts.
|
|
822
721
|
struct_result = StructFolding.try_dispatch(context)
|
|
823
722
|
return struct_result if struct_result
|
|
824
723
|
|
|
@@ -849,24 +748,19 @@ module Rigor
|
|
|
849
748
|
fallback_receiver = user_class_fallback_receiver(receiver_type, environment)
|
|
850
749
|
return nil if fallback_receiver.nil?
|
|
851
750
|
|
|
852
|
-
# Preserve the ORIGINAL receiver type as the `self`
|
|
853
|
-
#
|
|
854
|
-
#
|
|
855
|
-
#
|
|
856
|
-
# `
|
|
857
|
-
# `Bundler::URI::Generic` instance method types `base`
|
|
858
|
-
# as `Object` because `Bundler::URI::Generic` is not in
|
|
859
|
-
# RBS and the fallback's `self` resolves to Object.
|
|
751
|
+
# Preserve the ORIGINAL receiver type as the `self` substitution so `Kernel#dup: () ->
|
|
752
|
+
# self` and other `self`-returning methods route through Object's RBS while still
|
|
753
|
+
# returning the caller's type rather than `Object`. Without this, `base = self.dup`
|
|
754
|
+
# inside a `Bundler::URI::Generic` instance method types `base` as `Object` because
|
|
755
|
+
# `Bundler::URI::Generic` is not in RBS and the fallback's `self` resolves to Object.
|
|
860
756
|
#
|
|
861
|
-
# `public_only:` — when the call has an EXPLICIT, non-`self`
|
|
862
|
-
#
|
|
863
|
-
# `
|
|
864
|
-
#
|
|
865
|
-
#
|
|
866
|
-
#
|
|
867
|
-
#
|
|
868
|
-
# self / `self.`-receiver calls (`puts`, `raise`, `require`)
|
|
869
|
-
# keep resolving — those are the fallback's intended targets.
|
|
757
|
+
# `public_only:` — when the call has an EXPLICIT, non-`self` receiver
|
|
758
|
+
# (`Favourite.select(...)`), suppress the private `Object`/`Kernel`/`Class` methods the
|
|
759
|
+
# fallback would otherwise resolve. Ruby raises `NoMethodError` for a private method
|
|
760
|
+
# called with an explicit receiver, so resolving `Favourite.select` to the private
|
|
761
|
+
# `Kernel#select` (`-> Array[String]`) is a confidently-wrong type. Implicit-self /
|
|
762
|
+
# `self.`-receiver calls (`puts`, `raise`, `require`) keep resolving — those are the
|
|
763
|
+
# fallback's intended targets.
|
|
870
764
|
RbsDispatch.try_dispatch(
|
|
871
765
|
context.with(
|
|
872
766
|
receiver: fallback_receiver,
|
|
@@ -877,13 +771,11 @@ module Rigor
|
|
|
877
771
|
)
|
|
878
772
|
end
|
|
879
773
|
|
|
880
|
-
# True when the call node carries an explicit receiver that is
|
|
881
|
-
#
|
|
882
|
-
#
|
|
883
|
-
#
|
|
884
|
-
#
|
|
885
|
-
# may legally reach a private method in modern Ruby), and false
|
|
886
|
-
# when no `call_node` is supplied (internal dispatcher callers).
|
|
774
|
+
# True when the call node carries an explicit receiver that is not the literal `self`.
|
|
775
|
+
# Such a call cannot legally dispatch to a private method, so the user-class fallback must
|
|
776
|
+
# skip private signatures rather than return a confidently-wrong type. Returns false for
|
|
777
|
+
# implicit-self calls and `self.`-receiver calls (both may legally reach a private method
|
|
778
|
+
# in modern Ruby), and false when no `call_node` is supplied (internal dispatcher callers).
|
|
887
779
|
def explicit_non_self_receiver?(call_node)
|
|
888
780
|
return false if call_node.nil?
|
|
889
781
|
return false unless call_node.respond_to?(:receiver)
|
|
@@ -897,13 +789,11 @@ module Rigor
|
|
|
897
789
|
def user_class_fallback_receiver(receiver_type, environment)
|
|
898
790
|
case receiver_type
|
|
899
791
|
when Type::Nominal
|
|
900
|
-
# Modules: even when RBS knows the module, an instance
|
|
901
|
-
#
|
|
902
|
-
#
|
|
903
|
-
#
|
|
904
|
-
#
|
|
905
|
-
# `self.respond_to?` / `self.class` resolve cleanly when
|
|
906
|
-
# the module itself does not declare them.
|
|
792
|
+
# Modules: even when RBS knows the module, an instance method on a mixin-only module
|
|
793
|
+
# (e.g. `PP::ObjectMixin`) observes Kernel / Object methods through every concrete
|
|
794
|
+
# includer's ancestor chain. Route through the `Nominal[Object]` fallback so
|
|
795
|
+
# `self.inspect` / `self.respond_to?` / `self.class` resolve cleanly when the module
|
|
796
|
+
# itself does not declare them.
|
|
907
797
|
known = Rigor::Reflection.rbs_class_known?(receiver_type.class_name, environment: environment)
|
|
908
798
|
return environment.nominal_for_name("Object") if !known || environment.rbs_module?(receiver_type.class_name)
|
|
909
799
|
|
|
@@ -915,18 +805,13 @@ module Rigor
|
|
|
915
805
|
end
|
|
916
806
|
end
|
|
917
807
|
|
|
918
|
-
# Slice 7 phase 8 — meta-introspection shortcuts. The
|
|
919
|
-
#
|
|
920
|
-
#
|
|
921
|
-
#
|
|
922
|
-
# (
|
|
923
|
-
#
|
|
924
|
-
#
|
|
925
|
-
# (deliberate; calling `.class` on a class object yields
|
|
926
|
-
# `Class`, the metaclass). We also special-case `is_a?`-
|
|
927
|
-
# adjacent calls and the trivial `instance_of?(self)`
|
|
928
|
-
# later as the rule catalogue grows; for now only `class`
|
|
929
|
-
# is handled.
|
|
808
|
+
# Slice 7 phase 8 — meta-introspection shortcuts. The default `Object#class` RBS return
|
|
809
|
+
# type is `Class`, but for a receiver of known nominal identity we can do better:
|
|
810
|
+
# `instance_of(Foo).class` is `Singleton[Foo]` (the class object itself), which downstream
|
|
811
|
+
# dispatch uses to resolve `self.class.some_class_method`. The same logic answers
|
|
812
|
+
# `Foo.class` as `Singleton[Class]` (deliberate; calling `.class` on a class object yields
|
|
813
|
+
# `Class`, the metaclass). We also special-case `is_a?`-adjacent calls and the trivial
|
|
814
|
+
# `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.
|
|
930
815
|
def try_meta_introspection(receiver_type, method_name, arg_types = [])
|
|
931
816
|
case method_name
|
|
932
817
|
when :class then meta_class(receiver_type)
|
|
@@ -941,18 +826,15 @@ module Rigor
|
|
|
941
826
|
end
|
|
942
827
|
end
|
|
943
828
|
|
|
944
|
-
# `Singleton[Foo].new` returns `Nominal[Foo]` (a fresh
|
|
945
|
-
#
|
|
946
|
-
#
|
|
947
|
-
#
|
|
948
|
-
# `ScanAccumulator.new` types as `Nominal[ScanAccumulator]`
|
|
949
|
-
# rather than `Class`.
|
|
829
|
+
# `Singleton[Foo].new` returns `Nominal[Foo]` (a fresh instance), regardless of whether Foo
|
|
830
|
+
# is in RBS. This short-circuits the Class.new generic-`instance` plumbing for user
|
|
831
|
+
# classes, so a discovered-class `ScanAccumulator.new` types as
|
|
832
|
+
# `Nominal[ScanAccumulator]` rather than `Class`.
|
|
950
833
|
#
|
|
951
|
-
# v0.0.7 — for the curated set of immutable scalar-shaped
|
|
952
|
-
#
|
|
953
|
-
#
|
|
954
|
-
#
|
|
955
|
-
# through the standard catalog tier.
|
|
834
|
+
# v0.0.7 — for the curated set of immutable scalar-shaped classes that
|
|
835
|
+
# `Type::Constant::SCALAR_CLASSES` accepts (today: `Pathname`), `.new(Constant<…>)` lifts
|
|
836
|
+
# to a `Constant<…>` carrier so downstream method calls fold through the standard catalog
|
|
837
|
+
# tier.
|
|
956
838
|
def meta_new(receiver_type, arg_types = [])
|
|
957
839
|
return nil unless receiver_type.is_a?(Type::Singleton)
|
|
958
840
|
|
|
@@ -986,25 +868,21 @@ module Rigor
|
|
|
986
868
|
Type::Combinator.nominal_of(receiver_type.class_name)
|
|
987
869
|
end
|
|
988
870
|
|
|
989
|
-
# `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass*
|
|
990
|
-
#
|
|
991
|
-
#
|
|
992
|
-
#
|
|
993
|
-
#
|
|
994
|
-
#
|
|
995
|
-
#
|
|
996
|
-
# so the trailing `.new` dispatches instead of firing a spurious
|
|
997
|
-
# `undefined method 'new' for Struct`.
|
|
871
|
+
# `Struct.new(:a, :b)` synthesises an anonymous Struct *subclass* (a class object), not a
|
|
872
|
+
# Struct *instance* — so the chained idiom `Struct.new(:a, :b).new(1, 2)` must resolve
|
|
873
|
+
# `.new` again on a class-like carrier. The constant-bound form (`S = Struct.new(:a);
|
|
874
|
+
# S.new(1)`) already records `Singleton[S]` via `ScopeIndexer#record_meta_new_constant?`;
|
|
875
|
+
# this lift gives the *chained* (anonymous) position the same class-like carrier so the
|
|
876
|
+
# trailing `.new` dispatches instead of firing a spurious `undefined method 'new' for
|
|
877
|
+
# Struct`.
|
|
998
878
|
#
|
|
999
|
-
# The disambiguation mirrors `ScopeIndexer#struct_new_call?`: a
|
|
1000
|
-
#
|
|
1001
|
-
#
|
|
1002
|
-
#
|
|
1003
|
-
#
|
|
1004
|
-
#
|
|
1005
|
-
#
|
|
1006
|
-
# the narrower `.new`-dispatch-only fix and contributes no member
|
|
1007
|
-
# layout, so `instance.a` stays at its RBS/Dynamic type.
|
|
879
|
+
# The disambiguation mirrors `ScopeIndexer#struct_new_call?`: a call whose positionals are
|
|
880
|
+
# all `Constant<Symbol>` literals is a member-list class definition → `Singleton[Struct]`.
|
|
881
|
+
# The following `AnonStruct.new(1, 2)` carries non-symbol args, so it falls through this
|
|
882
|
+
# gate to `Nominal[Struct]` (a fresh instance) via the `meta_new` tail. ADR-48 deferred full
|
|
883
|
+
# Struct *value* folding (member-reader precision) on mutability grounds; this is the
|
|
884
|
+
# narrower `.new`-dispatch-only fix and contributes no member layout, so `instance.a` stays
|
|
885
|
+
# at its RBS/Dynamic type.
|
|
1008
886
|
def struct_new_lift(class_name, arg_types)
|
|
1009
887
|
return nil unless class_name == "Struct"
|
|
1010
888
|
|
|
@@ -1015,15 +893,12 @@ module Rigor
|
|
|
1015
893
|
Type::Combinator.singleton_of("Struct")
|
|
1016
894
|
end
|
|
1017
895
|
|
|
1018
|
-
# `Class.new` and `Class.new(Parent)` create a brand-new
|
|
1019
|
-
#
|
|
1020
|
-
# the parent's
|
|
1021
|
-
#
|
|
1022
|
-
#
|
|
1023
|
-
#
|
|
1024
|
-
# `singleton(Object)`. Anything else (dynamic parent, more
|
|
1025
|
-
# than one positional, …) falls back to `Nominal[Class]` via
|
|
1026
|
-
# the surrounding `meta_new` tail.
|
|
896
|
+
# `Class.new` and `Class.new(Parent)` create a brand-new anonymous class. Statically that
|
|
897
|
+
# class is representable as the parent's singleton type — its singleton-method surface is
|
|
898
|
+
# the parent's (plus whatever the block defines, which we do not statically track here),
|
|
899
|
+
# so `Singleton[Parent]` lets downstream `klass.some_class_method` resolve. No parent →
|
|
900
|
+
# `singleton(Object)`. Anything else (dynamic parent, more than one positional, …) falls
|
|
901
|
+
# back to `Nominal[Class]` via the surrounding `meta_new` tail.
|
|
1027
902
|
def class_new_lift(class_name, arg_types)
|
|
1028
903
|
return nil unless class_name == "Class"
|
|
1029
904
|
return Type::Combinator.singleton_of("Object") if arg_types.empty?
|
|
@@ -1035,20 +910,15 @@ module Rigor
|
|
|
1035
910
|
nil
|
|
1036
911
|
end
|
|
1037
912
|
|
|
1038
|
-
# ADR-15 Phase 4b.x — `Ractor.make_shareable` on both the
|
|
1039
|
-
#
|
|
1040
|
-
#
|
|
1041
|
-
#
|
|
1042
|
-
#
|
|
1043
|
-
#
|
|
1044
|
-
#
|
|
1045
|
-
#
|
|
1046
|
-
#
|
|
1047
|
-
# `call.argument-type-mismatch` (sequential's
|
|
1048
|
-
# `argument_type_diagnostic` short-circuits on Constant<Pathname>
|
|
1049
|
-
# because Pathname is not in its CONSTANT_CLASSES table; pool's
|
|
1050
|
-
# Nominal[Pathname] doesn't short-circuit). Surfaced on GitLab
|
|
1051
|
-
# FOSS via `lib/gitlab/mail_room.rb:17`.
|
|
913
|
+
# ADR-15 Phase 4b.x — `Ractor.make_shareable` on both the outer Hash and each lambda value.
|
|
914
|
+
# A plain `.freeze` leaves the Procs unshareable; reading `CONSTANT_CONSTRUCTORS[class]`
|
|
915
|
+
# from a worker Ractor would raise `Ractor::IsolationError`, which the `rescue
|
|
916
|
+
# StandardError` in `constant_constructor_lift` silently swallows — `meta_new` then falls
|
|
917
|
+
# back to `Nominal[Pathname]` in pool mode while sequential builds the `Constant<Pathname>`
|
|
918
|
+
# lift. The divergence surfaces downstream as a spurious `call.argument-type-mismatch`
|
|
919
|
+
# (sequential's `argument_type_diagnostic` short-circuits on Constant<Pathname> because
|
|
920
|
+
# Pathname is not in its CONSTANT_CLASSES table; pool's Nominal[Pathname] doesn't
|
|
921
|
+
# short-circuit). Surfaced on GitLab FOSS via `lib/gitlab/mail_room.rb:17`.
|
|
1052
922
|
CONSTANT_CONSTRUCTORS = Ractor.make_shareable({
|
|
1053
923
|
"Pathname" => Ractor.make_shareable(lambda { |arg|
|
|
1054
924
|
Pathname.new(arg)
|
|
@@ -1070,11 +940,10 @@ module Rigor
|
|
|
1070
940
|
nil
|
|
1071
941
|
end
|
|
1072
942
|
|
|
1073
|
-
# `Array.new(n, value)` and `Array.new(n)` (no value, default
|
|
1074
|
-
# `
|
|
1075
|
-
#
|
|
1076
|
-
#
|
|
1077
|
-
# carrier; oversize calls fall back to `Nominal[Array]`.
|
|
943
|
+
# `Array.new(n, value)` and `Array.new(n)` (no value, default `nil`) lift to a per-position
|
|
944
|
+
# `Tuple[…]` when `n` is a small `Constant<Integer>`. Cap at `ARRAY_NEW_TUPLE_LIMIT` (16)
|
|
945
|
+
# so a `Array.new(1_000_000)` does not balloon the carrier; oversize calls fall back to
|
|
946
|
+
# `Nominal[Array]`.
|
|
1078
947
|
ARRAY_NEW_TUPLE_LIMIT = 16
|
|
1079
948
|
private_constant :ARRAY_NEW_TUPLE_LIMIT
|
|
1080
949
|
|
|
@@ -1101,22 +970,18 @@ module Rigor
|
|
|
1101
970
|
type
|
|
1102
971
|
end
|
|
1103
972
|
|
|
1104
|
-
# `Hash.new(default)` — lifts the default value's type into the
|
|
1105
|
-
#
|
|
1106
|
-
#
|
|
1107
|
-
#
|
|
1108
|
-
#
|
|
1109
|
-
#
|
|
1110
|
-
#
|
|
1111
|
-
# (`0`) is widened to its nominal (`Integer`): the hash's values
|
|
1112
|
-
# mutate over its lifetime, so pinning the parameter to the
|
|
1113
|
-
# literal would be unsound for the aggregate.
|
|
973
|
+
# `Hash.new(default)` — lifts the default value's type into the Hash's value parameter so
|
|
974
|
+
# a subsequent `h[k]` read surfaces the default type rather than `Dynamic[top]`. The
|
|
975
|
+
# common counter idiom `h = Hash.new(0); h[k] += 1` then types the read as `Integer`. The
|
|
976
|
+
# key parameter is left `untyped` (the default carrier imposes no key constraint), so
|
|
977
|
+
# reads of any key resolve through the value parameter. A value-pinned `Constant` default
|
|
978
|
+
# (`0`) is widened to its nominal (`Integer`): the hash's values mutate over its lifetime,
|
|
979
|
+
# so pinning the parameter to the literal would be unsound for the aggregate.
|
|
1114
980
|
#
|
|
1115
|
-
# Only the single-argument default form folds. The zero-arg
|
|
1116
|
-
#
|
|
1117
|
-
#
|
|
1118
|
-
#
|
|
1119
|
-
# leaving the read as today's behaviour is precision-additive.
|
|
981
|
+
# Only the single-argument default form folds. The zero-arg (`Hash.new`) and the block
|
|
982
|
+
# form (`Hash.new { |h, k| … }`) keep the bare `Nominal[Hash]` answer — the block's value
|
|
983
|
+
# type is not available at this `:new` dispatch site, and conservatively leaving the read
|
|
984
|
+
# as today's behaviour is precision-additive.
|
|
1120
985
|
def hash_new_lift(class_name, arg_types)
|
|
1121
986
|
return nil unless class_name == "Hash"
|
|
1122
987
|
return nil unless arg_types.size == 1
|
|
@@ -1128,13 +993,11 @@ module Rigor
|
|
|
1128
993
|
Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.untyped, value])
|
|
1129
994
|
end
|
|
1130
995
|
|
|
1131
|
-
# `Range.new(b, e)` / `Range.new(b, e, excl)` — folds to
|
|
1132
|
-
# `Constant[
|
|
1133
|
-
#
|
|
1134
|
-
#
|
|
1135
|
-
#
|
|
1136
|
-
# methods (`to_a`, `first`, `last`) are not defined for them;
|
|
1137
|
-
# the RBS tier answers `Nominal[Range]` for those forms.
|
|
996
|
+
# `Range.new(b, e)` / `Range.new(b, e, excl)` — folds to `Constant[Range]` when both
|
|
997
|
+
# endpoints are `Constant[Integer]` or both are `Constant[String]`, and the optional third
|
|
998
|
+
# argument is a `Constant[true/false]`. Nil endpoints (beginless / endless ranges) are not
|
|
999
|
+
# folded because the useful instance methods (`to_a`, `first`, `last`) are not defined for
|
|
1000
|
+
# them; the RBS tier answers `Nominal[Range]` for those forms.
|
|
1138
1001
|
def range_new_lift(class_name, arg_types)
|
|
1139
1002
|
return nil unless class_name == "Range"
|
|
1140
1003
|
return nil if arg_types.size < 2 || arg_types.size > 3
|
|
@@ -1159,9 +1022,9 @@ module Rigor
|
|
|
1159
1022
|
nil
|
|
1160
1023
|
end
|
|
1161
1024
|
|
|
1162
|
-
# Resolves the optional `exclude_end` argument for `range_new_lift`.
|
|
1163
|
-
#
|
|
1164
|
-
#
|
|
1025
|
+
# Resolves the optional `exclude_end` argument for `range_new_lift`. Returns `false` (no
|
|
1026
|
+
# arg), `true`/`false` (Constant[bool] arg), or `nil` to signal "decline" (wrong type /
|
|
1027
|
+
# wrong value class).
|
|
1165
1028
|
def range_new_excl(excl_type)
|
|
1166
1029
|
case excl_type
|
|
1167
1030
|
when nil then false
|
|
@@ -1170,11 +1033,10 @@ module Rigor
|
|
|
1170
1033
|
end
|
|
1171
1034
|
end
|
|
1172
1035
|
|
|
1173
|
-
# `Set.new` / `Set.new(tuple_of_constants)` — folds to `Constant[Set]`
|
|
1174
|
-
#
|
|
1175
|
-
#
|
|
1176
|
-
#
|
|
1177
|
-
# reach it before the RBS tier answers `Nominal[Set]`.
|
|
1036
|
+
# `Set.new` / `Set.new(tuple_of_constants)` — folds to `Constant[Set]` when zero arguments
|
|
1037
|
+
# are given or the single argument is a `Tuple` whose every element is a `Constant[T]`.
|
|
1038
|
+
# Mirrors `SetFolding#fold_new` but lives here so the `:new` path in
|
|
1039
|
+
# `try_meta_introspection` can reach it before the RBS tier answers `Nominal[Set]`.
|
|
1178
1040
|
def set_new_lift(class_name, arg_types)
|
|
1179
1041
|
return nil unless class_name == "Set"
|
|
1180
1042
|
return Type::Combinator.constant_of(::Set.new) if arg_types.empty?
|
|
@@ -1190,10 +1052,9 @@ module Rigor
|
|
|
1190
1052
|
nil
|
|
1191
1053
|
end
|
|
1192
1054
|
|
|
1193
|
-
# `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to
|
|
1194
|
-
#
|
|
1195
|
-
#
|
|
1196
|
-
# in `try_meta_introspection` can reach it.
|
|
1055
|
+
# `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — folds to `Constant[Regexp]` when
|
|
1056
|
+
# the pattern is a `Constant[String]`. Mirrors `RegexpFolding#fold_new` but lives here so
|
|
1057
|
+
# the `:new` path in `try_meta_introspection` can reach it.
|
|
1197
1058
|
def regexp_new_lift(class_name, arg_types)
|
|
1198
1059
|
return nil unless class_name == "Regexp"
|
|
1199
1060
|
return nil if arg_types.empty? || arg_types.size > 2
|
|
@@ -1215,15 +1076,12 @@ module Rigor
|
|
|
1215
1076
|
nil
|
|
1216
1077
|
end
|
|
1217
1078
|
|
|
1218
|
-
# `Date.new(y, m, d)` / `DateTime.new(y, m, d, h, min, s, off)`
|
|
1219
|
-
#
|
|
1220
|
-
#
|
|
1221
|
-
# `DateTime` offset
|
|
1222
|
-
# `Date.
|
|
1223
|
-
#
|
|
1224
|
-
# `Date.today` / `Date.parse` are not constructors here — they
|
|
1225
|
-
# are non-deterministic / string-dependent and stay
|
|
1226
|
-
# `Nominal[Date]`.
|
|
1079
|
+
# `Date.new(y, m, d)` / `DateTime.new(y, m, d, h, min, s, off)` — folds to `Constant[Date]`
|
|
1080
|
+
# / `Constant[DateTime]` when every argument is a `Constant` carrying an Integer (or, for
|
|
1081
|
+
# the `DateTime` offset / `start` slots, a Rational or String). `Date.new` carries no
|
|
1082
|
+
# timezone, and `DateTime.new`'s offset defaults to UTC, so the literal is
|
|
1083
|
+
# machine-independent. `Date.today` / `Date.parse` are not constructors here — they are
|
|
1084
|
+
# non-deterministic / string-dependent and stay `Nominal[Date]`.
|
|
1227
1085
|
DATE_NEW_CLASSES = %w[Date DateTime].freeze
|
|
1228
1086
|
private_constant :DATE_NEW_CLASSES
|
|
1229
1087
|
|
|
@@ -1256,18 +1114,14 @@ module Rigor
|
|
|
1256
1114
|
nil
|
|
1257
1115
|
end
|
|
1258
1116
|
|
|
1259
|
-
# Returns the positional block parameter types declared by the
|
|
1260
|
-
#
|
|
1261
|
-
#
|
|
1262
|
-
# handler to bind block parameter names before evaluating the
|
|
1263
|
-
# block body.
|
|
1117
|
+
# Returns the positional block parameter types declared by the receiving method's selected
|
|
1118
|
+
# RBS overload, translated into `Rigor::Type`. Used by the StatementEvaluator's CallNode
|
|
1119
|
+
# handler to bind block parameter names before evaluating the block body.
|
|
1264
1120
|
#
|
|
1265
|
-
# The probe is best-effort: it returns an empty array whenever
|
|
1266
|
-
#
|
|
1267
|
-
#
|
|
1268
|
-
#
|
|
1269
|
-
# the binder falls back to `Dynamic[Top]` for every parameter
|
|
1270
|
-
# slot in that case.
|
|
1121
|
+
# The probe is best-effort: it returns an empty array whenever the receiver, environment,
|
|
1122
|
+
# method definition, or selected overload does not provide statically declared block
|
|
1123
|
+
# parameter types. Callers MUST treat the empty array as "no information"; the binder
|
|
1124
|
+
# falls back to `Dynamic[Top]` for every parameter slot in that case.
|
|
1271
1125
|
#
|
|
1272
1126
|
# @param receiver_type [Rigor::Type, nil]
|
|
1273
1127
|
# @param method_name [Symbol]
|