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
|
@@ -8,72 +8,61 @@ require_relative "rbs_hierarchy"
|
|
|
8
8
|
|
|
9
9
|
module Rigor
|
|
10
10
|
class Environment
|
|
11
|
-
# Loads RBS class declarations and method definitions from disk and
|
|
12
|
-
#
|
|
11
|
+
# Loads RBS class declarations and method definitions from disk and exposes them to the inference engine
|
|
12
|
+
# in a small, stable surface.
|
|
13
13
|
#
|
|
14
|
-
# Slice 4 phase 1 only enabled the RBS *core* signatures shipped with
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `
|
|
18
|
-
#
|
|
19
|
-
# default on `RbsLoader.default` so the core-only fast path stays
|
|
20
|
-
# cheap; project-aware loading is opted into through
|
|
21
|
-
# {Environment.for_project} or by constructing a custom loader.
|
|
14
|
+
# Slice 4 phase 1 only enabled the RBS *core* signatures shipped with the `rbs` gem (`Object`, `Integer`,
|
|
15
|
+
# `String`, `Array`, ...). Phase 2a adds opt-in stdlib library loading (`pathname`, `json`, `tempfile`,
|
|
16
|
+
# ...) and arbitrary-directory signature loading (typically the project's local `sig/` tree). Both are
|
|
17
|
+
# off by default on `RbsLoader.default` so the core-only fast path stays cheap; project-aware loading is
|
|
18
|
+
# opted into through {Environment.for_project} or by constructing a custom loader.
|
|
22
19
|
#
|
|
23
|
-
# The default instance is shared across the process: building the
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# and `RBS::DefinitionBuilder` -- the user-visible API stays purely
|
|
28
|
-
# functional.
|
|
20
|
+
# The default instance is shared across the process: building the core RBS environment costs hundreds of
|
|
21
|
+
# milliseconds and the data is read-only. The shared instance is frozen, but holds a mutable state hash
|
|
22
|
+
# for lazy memoization of the heavy `RBS::Environment` and `RBS::DefinitionBuilder` -- the user-visible
|
|
23
|
+
# API stays purely functional.
|
|
29
24
|
#
|
|
30
25
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
31
26
|
# rubocop:disable Metrics/ClassLength
|
|
32
27
|
class RbsLoader
|
|
33
|
-
# Buffer name stamped on the `module` declarations synthesized by
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# — robust across the marshalled env cache, since the sentinel
|
|
38
|
-
# rides along on each synthetic declaration's location.
|
|
28
|
+
# Buffer name stamped on the `module` declarations synthesized by {.synthesize_missing_namespaces}.
|
|
29
|
+
# Re-read off the built env by {#synthesized_namespaces} so the analysis layer can surface an `:info`
|
|
30
|
+
# diagnostic naming the project's malformed-RBS namespaces — robust across the marshalled env cache,
|
|
31
|
+
# since the sentinel rides along on each synthetic declaration's location.
|
|
39
32
|
SYNTHETIC_NAMESPACE_BUFFER = "(rigor: synthesized namespaces)"
|
|
40
33
|
|
|
41
|
-
# Buffer name stamped on the stub `class` / `module` declarations
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# {#
|
|
45
|
-
# the
|
|
46
|
-
# {#synthesized_type_names} folds them together with the
|
|
47
|
-
# namespace stubs into the set {MethodDispatcher} resolves to
|
|
48
|
-
# `Dynamic[Top]` (no false `call.undefined-method`).
|
|
34
|
+
# Buffer name stamped on the stub `class` / `module` declarations synthesized by
|
|
35
|
+
# {.stub_missing_referenced_types} for types the project's RBS references but no loaded signature
|
|
36
|
+
# declares. {#synthesized_stub_types} reads them back off the built env (so the answer survives the
|
|
37
|
+
# marshalled env cache), and {#synthesized_type_names} folds them together with the namespace stubs
|
|
38
|
+
# into the set {MethodDispatcher} resolves to `Dynamic[Top]` (no false `call.undefined-method`).
|
|
49
39
|
SYNTHETIC_STUB_BUFFER = "(rigor: synthesized stub types)"
|
|
50
40
|
|
|
41
|
+
# Cap on how many quarantined `signature_paths:` files {#warn_about_quarantined_signatures} lists by name
|
|
42
|
+
# before collapsing the tail to "… and N more" — a broken generator can emit many, and a wall of parse
|
|
43
|
+
# errors buries the signal.
|
|
44
|
+
QUARANTINE_WARN_LIMIT = 10
|
|
45
|
+
|
|
51
46
|
class << self
|
|
52
47
|
def default
|
|
53
48
|
@default ||= new.freeze
|
|
54
49
|
end
|
|
55
50
|
|
|
56
|
-
# Used by tests to discard the cached default loader; production
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
# cost of rebuilding it.
|
|
51
|
+
# Used by tests to discard the cached default loader; production code MUST NOT call this. The shared
|
|
52
|
+
# loader holds a several-MB RBS::Environment, so dropping it during a normal run wastes the cost of
|
|
53
|
+
# rebuilding it.
|
|
60
54
|
def reset_default!
|
|
61
55
|
@default = nil
|
|
62
56
|
end
|
|
63
57
|
|
|
64
|
-
# Builds an `RBS::Environment` from explicit `libraries` and
|
|
65
|
-
#
|
|
66
|
-
# {
|
|
67
|
-
#
|
|
68
|
-
# instance-side {#build_env} delegates here so the
|
|
69
|
-
# implementation stays single-rooted.
|
|
58
|
+
# Builds an `RBS::Environment` from explicit `libraries` and `signature_paths`. Stateless surface so
|
|
59
|
+
# the v0.0.9 {Cache::RbsEnvironment} producer can build an env on cache miss without holding a loader
|
|
60
|
+
# instance, and the instance-side {#build_env} delegates here so the implementation stays
|
|
61
|
+
# single-rooted.
|
|
70
62
|
#
|
|
71
|
-
# Vendored gem stubs (`data/vendored_gem_sigs/<gem>/`) are
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# run. The gem stubs are intentionally read-only and
|
|
75
|
-
# appended LAST so user-supplied `signature_paths` win on
|
|
76
|
-
# name conflicts.
|
|
63
|
+
# Vendored gem stubs (`data/vendored_gem_sigs/<gem>/`) are loaded on top of `signature_paths` so the
|
|
64
|
+
# per-gem RBS bundled with Rigor itself is in scope for every analysis run. The gem stubs are
|
|
65
|
+
# intentionally read-only and appended LAST so user-supplied `signature_paths` win on name conflicts.
|
|
77
66
|
def build_env_for(libraries:, signature_paths:, virtual_rbs: [])
|
|
78
67
|
rbs_loader = RBS::EnvironmentLoader.new
|
|
79
68
|
libraries.each do |library|
|
|
@@ -81,59 +70,51 @@ module Rigor
|
|
|
81
70
|
|
|
82
71
|
rbs_loader.add(library: library, version: nil)
|
|
83
72
|
end
|
|
84
|
-
signature_paths.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
# Project `signature_paths:` are loaded per-file by {.add_project_signatures} AFTER `from_loader`,
|
|
74
|
+
# NOT added to the loader here: `RBS::Environment.from_loader` parses every added file all-or-nothing,
|
|
75
|
+
# so one unparseable user `.rbs` raises `RBS::ParsingError` and collapses the WHOLE env to nil (every
|
|
76
|
+
# type-of query then degrades to `Dynamic[top]` — the "sig looks harmful" failure of the 2026-07-06
|
|
77
|
+
# mastodon coverage note). Per-file loading quarantines the broken file instead. Vendored / core-overlay
|
|
78
|
+
# sigs are Rigor-shipped and trusted, so they stay on the loader's fast batch path.
|
|
88
79
|
vendored_gem_sig_paths.each do |path|
|
|
89
80
|
rbs_loader.add(path: path) if path.directory?
|
|
90
81
|
end
|
|
91
|
-
# Rigor-owned core overlay — loaded LAST so an upstream
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# the abstract `Numeric` that Rigor's arithmetic-chain widening
|
|
96
|
-
# produces).
|
|
82
|
+
# Rigor-owned core overlay — loaded LAST so an upstream declaration always wins on conflict; these
|
|
83
|
+
# reopenings only fill genuine holes (e.g. `Numeric#to_f`/`to_i`/`to_r`, which upstream RBS
|
|
84
|
+
# declares on the concrete subclasses but not on the abstract `Numeric` that Rigor's
|
|
85
|
+
# arithmetic-chain widening produces).
|
|
97
86
|
core_overlay_sig_paths.each do |path|
|
|
98
87
|
rbs_loader.add(path: path) if path.directory?
|
|
99
88
|
end
|
|
100
89
|
env = RBS::Environment.from_loader(rbs_loader)
|
|
90
|
+
add_project_signatures(env, signature_paths)
|
|
101
91
|
add_virtual_rbs(env, virtual_rbs)
|
|
102
92
|
synthesize_missing_namespaces(env)
|
|
103
93
|
resolved = env.resolve_type_names
|
|
104
94
|
stub_missing_referenced_types(env, resolved, project_sig_files(signature_paths))
|
|
105
95
|
end
|
|
106
96
|
|
|
107
|
-
# ADR-5 robustness, second tier. A project `signature_paths:`
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
# `
|
|
113
|
-
# `
|
|
114
|
-
# build) that single unresolved reference takes down EVERY
|
|
115
|
-
# method on the class, not just the one signature. Observed on
|
|
116
|
-
# shugo/textbringer: one `DRb::DRbServer` reference left the
|
|
117
|
-
# whole `Textbringer::Commands` module — including its
|
|
118
|
-
# 186-call-site `define_command` DSL — resolving as
|
|
97
|
+
# ADR-5 robustness, second tier. A project `signature_paths:` RBS that *references* a type no loaded
|
|
98
|
+
# signature declares — `def x: () -> DRb::DRbServer` when the `drb` RBS is not available, or a stale
|
|
99
|
+
# reference to its own removed `Textbringer::EditorError` — makes
|
|
100
|
+
# `RBS::DefinitionBuilder#build_instance` raise `NoTypeFoundError`, and (per RBS's all-or-nothing
|
|
101
|
+
# per-class build) that single unresolved reference takes down EVERY method on the class, not just
|
|
102
|
+
# the one signature. Observed on shugo/textbringer: one `DRb::DRbServer` reference left the whole
|
|
103
|
+
# `Textbringer::Commands` module — including its 186-call-site `define_command` DSL — resolving as
|
|
119
104
|
# `Dynamic[Top]`.
|
|
120
105
|
#
|
|
121
|
-
# We synthesize an empty stub for each such referenced-but-
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# resolves such calls to `Dynamic[Top]` instead (the same
|
|
128
|
-
# no-false-positive contract as the dependency-source tier).
|
|
106
|
+
# We synthesize an empty stub for each such referenced-but- undeclared type so the rest of the class
|
|
107
|
+
# builds. A leaf type is stubbed as `class`, its enclosing namespaces as `module`. Stubbed types
|
|
108
|
+
# carry no methods, so a call against a value of a stubbed type would otherwise mis-fire
|
|
109
|
+
# `call.undefined-method`; {MethodDispatcher} consults {#synthesized_type_names} and resolves such
|
|
110
|
+
# calls to `Dynamic[Top]` instead (the same no-false-positive contract as the dependency-source
|
|
111
|
+
# tier).
|
|
129
112
|
#
|
|
130
|
-
# Detection re-uses RBS's own builder (correct by construction):
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# first build error hid, but empty stubs reference nothing, so
|
|
136
|
-
# the fixpoint converges quickly.
|
|
113
|
+
# Detection re-uses RBS's own builder (correct by construction): build every PROJECT class and read
|
|
114
|
+
# the missing name out of the raised error. Bounded to `signature_paths` classes (stdlib / vendored
|
|
115
|
+
# RBS is well-formed) and to {MAX_STUB_PASSES} iterations — a fresh stub can expose a deeper
|
|
116
|
+
# reference the first build error hid, but empty stubs reference nothing, so the fixpoint converges
|
|
117
|
+
# quickly.
|
|
137
118
|
MAX_STUB_PASSES = 5
|
|
138
119
|
|
|
139
120
|
def stub_missing_referenced_types(base_env, resolved, project_files)
|
|
@@ -149,20 +130,15 @@ module Rigor
|
|
|
149
130
|
resolved
|
|
150
131
|
end
|
|
151
132
|
|
|
152
|
-
# Robustness (ADR-5): a project whose RBS declares qualified
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# `
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
# otherwise-usable signature set contribute nothing, synthesize
|
|
162
|
-
# an empty `module` declaration for each undeclared enclosing
|
|
163
|
-
# namespace so the definitions build. We only ever add names
|
|
164
|
-
# that are absent — a genuinely-declared namespace (module or
|
|
165
|
-
# class, here or in a loaded gem) is left untouched.
|
|
133
|
+
# Robustness (ADR-5): a project whose RBS declares qualified names (`class Foo::Bar`) without ever
|
|
134
|
+
# declaring the enclosing namespace (`module Foo`) is invalid by upstream RBS rules —
|
|
135
|
+
# `RBS::DefinitionBuilder#build_instance` raises `NoTypeFoundError: Could not find ::Foo`, which the
|
|
136
|
+
# loader's fail-soft rescue turns into a silent dispatch miss (every method on every such class
|
|
137
|
+
# degrades to `Dynamic[Top]`). This is a common authoring mistake (e.g. shugo/textbringer ships a
|
|
138
|
+
# `sig/` that `rbs validate` itself rejects). Rather than let an otherwise-usable signature set
|
|
139
|
+
# contribute nothing, synthesize an empty `module` declaration for each undeclared enclosing
|
|
140
|
+
# namespace so the definitions build. We only ever add names that are absent — a genuinely-declared
|
|
141
|
+
# namespace (module or class, here or in a loaded gem) is left untouched.
|
|
166
142
|
def synthesize_missing_namespaces(env)
|
|
167
143
|
missing = collect_missing_namespaces(env)
|
|
168
144
|
return if missing.empty?
|
|
@@ -172,16 +148,14 @@ module Rigor
|
|
|
172
148
|
_, directives, decls = ::RBS::Parser.parse_signature(buffer)
|
|
173
149
|
add_parsed_decls(env, buffer, directives, decls)
|
|
174
150
|
rescue ::RBS::BaseError
|
|
175
|
-
# Fail-soft: synthesis is an opportunistic uplift, never a
|
|
176
|
-
#
|
|
177
|
-
# as it was (dispatch misses on the affected classes).
|
|
151
|
+
# Fail-soft: synthesis is an opportunistic uplift, never a hard requirement. A parse failure here
|
|
152
|
+
# just leaves the env as it was (dispatch misses on the affected classes).
|
|
178
153
|
nil
|
|
179
154
|
end
|
|
180
155
|
|
|
181
|
-
# Returns the `::`-stripped names of every enclosing namespace
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
# before `Foo::Bar`.
|
|
156
|
+
# Returns the `::`-stripped names of every enclosing namespace that some declaration references but
|
|
157
|
+
# no declaration defines, shallowest-first so the synthesized source declares `Foo` before
|
|
158
|
+
# `Foo::Bar`.
|
|
185
159
|
def collect_missing_namespaces(env)
|
|
186
160
|
declared = env.class_decls.keys.to_set
|
|
187
161
|
missing = {}
|
|
@@ -196,10 +170,9 @@ module Rigor
|
|
|
196
170
|
missing.sort_by { |_name, depth| depth }.map(&:first)
|
|
197
171
|
end
|
|
198
172
|
|
|
199
|
-
# The absolute paths of every `.rbs` file under the project's
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
# time). Used to scope the referenced-type build sweep.
|
|
173
|
+
# The absolute paths of every `.rbs` file under the project's `signature_paths:` (NOT vendored /
|
|
174
|
+
# stdlib RBS — those are well-formed, so attempting to build them would only waste time). Used to
|
|
175
|
+
# scope the referenced-type build sweep.
|
|
203
176
|
def project_sig_files(signature_paths)
|
|
204
177
|
signature_paths.flat_map do |path|
|
|
205
178
|
path = Pathname(path) unless path.is_a?(Pathname)
|
|
@@ -209,10 +182,57 @@ module Rigor
|
|
|
209
182
|
end.to_set
|
|
210
183
|
end
|
|
211
184
|
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
185
|
+
# Load the project's `signature_paths:` RBS files into `env` ONE FILE AT A TIME, quarantining any file
|
|
186
|
+
# that fails to parse rather than letting it collapse the whole env (the `from_loader` batch parse is
|
|
187
|
+
# all-or-nothing — see {.build_env_for}). A quarantined file's declarations are simply absent, so calls
|
|
188
|
+
# into the types it would have declared read `Dynamic[top]`; the rest of the project's (and all
|
|
189
|
+
# bundled) RBS still loads. The user is told which files were skipped via
|
|
190
|
+
# {RbsLoader#warn_about_quarantined_signatures}, so this is a graceful degrade, not a silent one.
|
|
191
|
+
#
|
|
192
|
+
# Buffer names are the file's absolute path (matching {.project_sig_files}) so {.project_entry?} — which
|
|
193
|
+
# attributes a `class_decls` entry to the project by buffer name — still recognises these declarations.
|
|
194
|
+
# Sorted for a deterministic add order (the env feeds the cache, ADR-54).
|
|
195
|
+
def add_project_signatures(env, signature_paths)
|
|
196
|
+
project_sig_files(signature_paths).sort.each do |file|
|
|
197
|
+
parsed = parse_signature_file(file)
|
|
198
|
+
next if parsed.nil? # quarantined (unparseable) or unreadable — skip so the env survives
|
|
199
|
+
|
|
200
|
+
buffer, directives, decls = parsed
|
|
201
|
+
add_parsed_decls(env, buffer, directives, decls)
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Parse one project `.rbs` into `[buffer, directives, decls]`, or nil when it is unparseable /
|
|
206
|
+
# unreadable. Mirrors `RBS::EnvironmentLoader#each_signature`'s per-file parse so the decls register
|
|
207
|
+
# identically to the loader's batch path.
|
|
208
|
+
def parse_signature_file(file)
|
|
209
|
+
buffer = ::RBS::Buffer.new(name: file, content: File.read(file, encoding: "UTF-8"))
|
|
210
|
+
_buffer, directives, decls = ::RBS::Parser.parse_signature(buffer)
|
|
211
|
+
[buffer, directives, decls]
|
|
212
|
+
rescue ::RBS::ParsingError, Errno::ENOENT, Errno::EISDIR, Errno::EACCES
|
|
213
|
+
nil
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The project `signature_paths:` files that FAIL to parse, as `[absolute_path, first_error_line]` pairs
|
|
217
|
+
# (sorted, deterministic). Detection is independent of {.add_project_signatures} so the warning fires
|
|
218
|
+
# even on a cache hit (where the env was already built with the file quarantined). Cheap: it only
|
|
219
|
+
# re-parses the user's own (usually small) `sig/` set, and returns empty immediately when there is no
|
|
220
|
+
# `signature_paths:`.
|
|
221
|
+
def quarantined_project_signatures(signature_paths)
|
|
222
|
+
project_sig_files(signature_paths).sort.filter_map do |file|
|
|
223
|
+
buffer = ::RBS::Buffer.new(name: file, content: File.read(file, encoding: "UTF-8"))
|
|
224
|
+
::RBS::Parser.parse_signature(buffer)
|
|
225
|
+
nil
|
|
226
|
+
rescue ::RBS::ParsingError => e
|
|
227
|
+
[file, e.message.to_s.lines.first.to_s.strip]
|
|
228
|
+
rescue Errno::ENOENT, Errno::EISDIR, Errno::EACCES
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Builds every project class (instance + singleton side) and returns the `::`-stripped names of the
|
|
234
|
+
# types whose absence raised `NoTypeFoundError`. Only the FIRST missing reference per class surfaces
|
|
235
|
+
# per build, which is why the caller loops.
|
|
216
236
|
def unresolved_referenced_types(env, project_files)
|
|
217
237
|
builder = ::RBS::DefinitionBuilder.new(env: env)
|
|
218
238
|
missing = []
|
|
@@ -225,21 +245,18 @@ module Rigor
|
|
|
225
245
|
name = e.message[/Could not find (\S+)/, 1]
|
|
226
246
|
missing << name.sub(/\A::/, "") if name
|
|
227
247
|
rescue ::RBS::BaseError
|
|
228
|
-
# Other build failures (duplicate decl, mixin cycle, ...)
|
|
229
|
-
#
|
|
248
|
+
# Other build failures (duplicate decl, mixin cycle, ...) are not ours to repair here — leave
|
|
249
|
+
# them fail-soft.
|
|
230
250
|
end
|
|
231
251
|
end
|
|
232
252
|
missing.uniq
|
|
233
253
|
end
|
|
234
254
|
|
|
235
|
-
# Normalises a `class_decls` entry's representative declaration
|
|
236
|
-
#
|
|
237
|
-
# RBS
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# nil when neither accessor is present. Without this guard,
|
|
241
|
-
# `class_decl_paths` crashed under RBS 3.x with
|
|
242
|
-
# `undefined method 'primary_decl'`.
|
|
255
|
+
# Normalises a `class_decls` entry's representative declaration across the gemspec's supported RBS
|
|
256
|
+
# range (`rbs >= 3.0, < 5.0`). RBS 4.x exposes it as `entry.primary_decl` (the AST declaration
|
|
257
|
+
# directly); RBS 3.x exposes `entry.primary` (a wrapper whose `#decl` is the AST declaration).
|
|
258
|
+
# Returns the AST declaration, or nil when neither accessor is present. Without this guard,
|
|
259
|
+
# `class_decl_paths` crashed under RBS 3.x with `undefined method 'primary_decl'`.
|
|
243
260
|
def primary_decl_for(entry)
|
|
244
261
|
if entry.respond_to?(:primary_decl)
|
|
245
262
|
entry.primary_decl
|
|
@@ -249,18 +266,14 @@ module Rigor
|
|
|
249
266
|
end
|
|
250
267
|
end
|
|
251
268
|
|
|
252
|
-
# Appends freshly-parsed declarations to an `RBS::Environment`
|
|
253
|
-
#
|
|
254
|
-
# RBS
|
|
255
|
-
#
|
|
256
|
-
# `
|
|
257
|
-
#
|
|
258
|
-
# `
|
|
259
|
-
#
|
|
260
|
-
# declarations silently vanish on resolve). Without this guard
|
|
261
|
-
# the synthesis paths (`synthesize_missing_namespaces`,
|
|
262
|
-
# `append_stub_declarations`, `add_virtual_rbs`) crashed under
|
|
263
|
-
# RBS 3.x with `uninitialized constant RBS::Source`.
|
|
269
|
+
# Appends freshly-parsed declarations to an `RBS::Environment` across the gemspec's supported RBS
|
|
270
|
+
# range (`rbs >= 3.0, < 5.0`). RBS 4.x wraps the declarations in an `RBS::Source::RBS` and takes them
|
|
271
|
+
# through `env.add_source`; RBS 3.x has neither `RBS::Source` nor `add_source` and instead registers
|
|
272
|
+
# them with `env.add_signature(buffer:, directives:, decls:)` (a bare `env << decl` is NOT enough —
|
|
273
|
+
# it skips the `signatures` table that `resolve_type_names` rebuilds from, so the synthesized
|
|
274
|
+
# declarations silently vanish on resolve). Without this guard the synthesis paths
|
|
275
|
+
# (`synthesize_missing_namespaces`, `append_stub_declarations`, `add_virtual_rbs`) crashed under RBS
|
|
276
|
+
# 3.x with `uninitialized constant RBS::Source`.
|
|
264
277
|
def add_parsed_decls(env, buffer, directives, decls)
|
|
265
278
|
decls ||= []
|
|
266
279
|
directives ||= []
|
|
@@ -273,9 +286,8 @@ module Rigor
|
|
|
273
286
|
end
|
|
274
287
|
end
|
|
275
288
|
|
|
276
|
-
# True when a `class_decls` entry was declared in one of the
|
|
277
|
-
#
|
|
278
|
-
# the sweep skips the bundled stdlib / vendored universe.
|
|
289
|
+
# True when a `class_decls` entry was declared in one of the project's own signature files (by
|
|
290
|
+
# declaration location), so the sweep skips the bundled stdlib / vendored universe.
|
|
279
291
|
def project_entry?(entry, project_files)
|
|
280
292
|
decl = primary_decl_for(entry)
|
|
281
293
|
location = decl&.location
|
|
@@ -285,25 +297,19 @@ module Rigor
|
|
|
285
297
|
project_files.include?(File.expand_path(buffer_name.to_s))
|
|
286
298
|
end
|
|
287
299
|
|
|
288
|
-
# Adds empty stub declarations for the missing referenced types
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
# prefix of another name is declared `module` (it is a
|
|
292
|
-
# namespace); a leaf is declared `class` (referenced types
|
|
300
|
+
# Adds empty stub declarations for the missing referenced types (and any enclosing namespace they
|
|
301
|
+
# need) to the pre-resolve env, tagged with {SYNTHETIC_STUB_BUFFER}. A name that is a prefix of
|
|
302
|
+
# another name is declared `module` (it is a namespace); a leaf is declared `class` (referenced types
|
|
293
303
|
# appear in instance position far more often than as mixins).
|
|
294
304
|
#
|
|
295
|
-
# Names already declared in `base_env` are skipped — exactly the
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
#
|
|
301
|
-
# `
|
|
302
|
-
#
|
|
303
|
-
# malformed `.rbs` must not disproportionately blind the analysis:
|
|
304
|
-
# a subclass sig that references an inherited nested type
|
|
305
|
-
# (`class GitAdapter; def x: () -> GitAdapter::Revision`) was the
|
|
306
|
-
# real-world trigger — see the 2026-07-04 redmine onboarding note.
|
|
305
|
+
# Names already declared in `base_env` are skipped — exactly the `declared.include?` guard
|
|
306
|
+
# {.collect_missing_namespaces} applies. Without it, stubbing a nested reference (`Foo::Bar::Baz`)
|
|
307
|
+
# re-emits its enclosing prefix (`Foo::Bar`) as a `module`, and when that prefix is already a `class`
|
|
308
|
+
# in the project's own `sig/` the kind mismatch makes `resolve_type_names` raise
|
|
309
|
+
# `RBS::DuplicatedDeclarationError`, collapsing the WHOLE env to nil (every type-of query then
|
|
310
|
+
# degrades to `Dynamic[Top]`). One malformed `.rbs` must not disproportionately blind the analysis: a
|
|
311
|
+
# subclass sig that references an inherited nested type (`class GitAdapter; def x: () ->
|
|
312
|
+
# GitAdapter::Revision`) was the real-world trigger — see the 2026-07-04 redmine onboarding note.
|
|
307
313
|
def append_stub_declarations(base_env, missing)
|
|
308
314
|
declared = declared_type_names(base_env)
|
|
309
315
|
names = missing.to_set
|
|
@@ -325,9 +331,8 @@ module Rigor
|
|
|
325
331
|
nil
|
|
326
332
|
end
|
|
327
333
|
|
|
328
|
-
# The `::`-stripped names of every class / module / class-alias
|
|
329
|
-
#
|
|
330
|
-
# never re-declare (and thereby duplicate) a real declaration.
|
|
334
|
+
# The `::`-stripped names of every class / module / class-alias declaration already present in
|
|
335
|
+
# `env`, so the synthesis paths never re-declare (and thereby duplicate) a real declaration.
|
|
331
336
|
def declared_type_names(env)
|
|
332
337
|
names = env.class_decls.keys.map { |n| n.to_s.sub(/\A::/, "") }
|
|
333
338
|
if env.respond_to?(:class_alias_decls)
|
|
@@ -336,15 +341,11 @@ module Rigor
|
|
|
336
341
|
names.to_set
|
|
337
342
|
end
|
|
338
343
|
|
|
339
|
-
# ADR-32 WD4 — merge synthesised-from-source RBS strings
|
|
340
|
-
#
|
|
341
|
-
#
|
|
342
|
-
# is
|
|
343
|
-
#
|
|
344
|
-
# synthesizer-emit path is responsible for catching its
|
|
345
|
-
# own parse errors and returning `nil` rather than
|
|
346
|
-
# garbage; this method assumes its input is parseable
|
|
347
|
-
# and only rescues `RBS::ParsingError` as a fail-soft.
|
|
344
|
+
# ADR-32 WD4 — merge synthesised-from-source RBS strings into the freshly-built environment. Each
|
|
345
|
+
# entry is a `[virtual_filename, rbs_source]` pair. `virtual_filename` is purely for diagnostic
|
|
346
|
+
# provenance (RBS parse errors cite it) — it is not a real file path. Per WD6 the synthesizer-emit
|
|
347
|
+
# path is responsible for catching its own parse errors and returning `nil` rather than garbage; this
|
|
348
|
+
# method assumes its input is parseable and only rescues `RBS::ParsingError` as a fail-soft.
|
|
348
349
|
def add_virtual_rbs(env, virtual_rbs)
|
|
349
350
|
return if virtual_rbs.nil? || virtual_rbs.empty?
|
|
350
351
|
|
|
@@ -355,31 +356,27 @@ module Rigor
|
|
|
355
356
|
_, directives, decls = ::RBS::Parser.parse_signature(buffer)
|
|
356
357
|
add_parsed_decls(env, buffer, directives, decls)
|
|
357
358
|
rescue ::RBS::BaseError
|
|
358
|
-
# WD6 fail-soft: a single broken virtual RBS contribution
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
# in slice 2; here we just skip the entry.
|
|
359
|
+
# WD6 fail-soft: a single broken virtual RBS contribution does not pull the whole env down. The
|
|
360
|
+
# plugin layer records a `source-rbs-synthesis-failed` info diagnostic in slice 2; here we just
|
|
361
|
+
# skip the entry.
|
|
362
362
|
end
|
|
363
363
|
end
|
|
364
364
|
|
|
365
|
-
# Per-gem `data/vendored_gem_sigs/<gem>/` directories that
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
#
|
|
369
|
-
#
|
|
370
|
-
# dominated `call.undefined-method` noise in the real-world
|
|
371
|
-
# survey at `docs/notes/20260515-real-world-rails-survey.md`.
|
|
365
|
+
# Per-gem `data/vendored_gem_sigs/<gem>/` directories that ship with Rigor. Each subdirectory is one
|
|
366
|
+
# gem's RBS surface (the `<gem>.rbs` file is the typical content; `LICENSE.upstream` records
|
|
367
|
+
# provenance). Coverage is deliberately scoped to the native-extension and "everywhere in Rails" gems
|
|
368
|
+
# whose absence dominated `call.undefined-method` noise in the real-world survey at
|
|
369
|
+
# `docs/notes/20260515-real-world-rails-survey.md`.
|
|
372
370
|
VENDORED_GEM_SIGS_ROOT = File.expand_path(
|
|
373
371
|
"../../../data/vendored_gem_sigs",
|
|
374
372
|
__dir__
|
|
375
373
|
).freeze
|
|
376
374
|
private_constant :VENDORED_GEM_SIGS_ROOT
|
|
377
375
|
|
|
378
|
-
# Rigor-owned core-overlay RBS (`data/core_overlay/`). Reopens
|
|
379
|
-
#
|
|
380
|
-
#
|
|
381
|
-
#
|
|
382
|
-
# can digest these files into the env-blob key.
|
|
376
|
+
# Rigor-owned core-overlay RBS (`data/core_overlay/`). Reopens Ruby core classes to add methods
|
|
377
|
+
# upstream `ruby/rbs` omits but which every concrete value answers at runtime — loaded last so
|
|
378
|
+
# upstream always wins on conflict. Public so the cache descriptor can digest these files into the
|
|
379
|
+
# env-blob key.
|
|
383
380
|
CORE_OVERLAY_SIGS_ROOT = File.expand_path(
|
|
384
381
|
"../../../data/core_overlay",
|
|
385
382
|
__dir__
|
|
@@ -391,24 +388,20 @@ module Rigor
|
|
|
391
388
|
[Pathname(CORE_OVERLAY_SIGS_ROOT)]
|
|
392
389
|
end
|
|
393
390
|
|
|
394
|
-
# Rigor-owned per-gem RBS overlays (`data/gem_overlay/<gem>/`),
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
# already-filtered gem-name set here. One directory per gem name
|
|
400
|
-
# keeps the membership check a cheap `File.directory?`.
|
|
391
|
+
# Rigor-owned per-gem RBS overlays (`data/gem_overlay/<gem>/`), ADR-72. Unlike the unconditional
|
|
392
|
+
# `core_overlay`, each gem's overlay is loaded ONLY when that gem is locked in the project's
|
|
393
|
+
# Gemfile.lock but ships no RBS of its own — {Environment.for_project} decides eligibility and passes
|
|
394
|
+
# the already-filtered gem-name set here. One directory per gem name keeps the membership check a
|
|
395
|
+
# cheap `File.directory?`.
|
|
401
396
|
GEM_OVERLAY_SIGS_ROOT = File.expand_path(
|
|
402
397
|
"../../../data/gem_overlay",
|
|
403
398
|
__dir__
|
|
404
399
|
).freeze
|
|
405
400
|
|
|
406
|
-
# @param gem_names [Enumerable<String>] overlay-eligible
|
|
407
|
-
#
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
# each gem that ships one; empty when none match or the
|
|
411
|
-
# overlay root is absent.
|
|
401
|
+
# @param gem_names [Enumerable<String>] overlay-eligible Gemfile.lock gem names (the caller filters
|
|
402
|
+
# to the `:missing`-coverage, no-conflicting-plugin set).
|
|
403
|
+
# @return [Array<Pathname>] the bundled overlay directory for each gem that ships one; empty when
|
|
404
|
+
# none match or the overlay root is absent.
|
|
412
405
|
def gem_overlay_sig_paths(gem_names)
|
|
413
406
|
return [] unless File.directory?(GEM_OVERLAY_SIGS_ROOT)
|
|
414
407
|
|
|
@@ -426,14 +419,10 @@ module Rigor
|
|
|
426
419
|
end
|
|
427
420
|
end
|
|
428
421
|
|
|
429
|
-
# Gem names whose RBS ships under
|
|
430
|
-
# `
|
|
431
|
-
#
|
|
432
|
-
#
|
|
433
|
-
# this set to drop the matching `rbs collection install`
|
|
434
|
-
# directory before it double-declares against the
|
|
435
|
-
# vendored copy — the same hazard `DEFAULT_LIBRARIES`
|
|
436
|
-
# creates for stdlib-extracted gems. See
|
|
422
|
+
# Gem names whose RBS ships under `data/vendored_gem_sigs/<gem>/`. The directory walk is the source
|
|
423
|
+
# of truth (the `README.md` sibling is not a gem and is excluded). Callers building the RBS env use
|
|
424
|
+
# this set to drop the matching `rbs collection install` directory before it double-declares against
|
|
425
|
+
# the vendored copy — the same hazard `DEFAULT_LIBRARIES` creates for stdlib-extracted gems. See
|
|
437
426
|
# `RbsCollectionDiscovery`'s `skip_gem_names:`.
|
|
438
427
|
def vendored_gem_names
|
|
439
428
|
return [] unless File.directory?(VENDORED_GEM_SIGS_ROOT)
|
|
@@ -446,48 +435,32 @@ module Rigor
|
|
|
446
435
|
|
|
447
436
|
attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs
|
|
448
437
|
|
|
449
|
-
# @param libraries [Array<String, Symbol>] stdlib library names to
|
|
450
|
-
#
|
|
451
|
-
#
|
|
452
|
-
# `
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
# `
|
|
458
|
-
#
|
|
459
|
-
#
|
|
460
|
-
# @param
|
|
461
|
-
#
|
|
462
|
-
# `
|
|
463
|
-
#
|
|
464
|
-
# producers. Pass `nil` (the default) to skip caching; the
|
|
465
|
-
# runner threads its own Store through here when enabled.
|
|
466
|
-
# @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 —
|
|
467
|
-
# `[virtual_filename, rbs_source]` pairs synthesised from
|
|
468
|
-
# project source by a plugin's
|
|
469
|
-
# `Manifest#source_rbs_synthesizer`. Merged into the env
|
|
470
|
-
# after `signature_paths:` and the vendored stubs. Pass
|
|
471
|
-
# `[]` (the default) when no synthesizer-emitting plugin
|
|
472
|
-
# is loaded.
|
|
438
|
+
# @param libraries [Array<String, Symbol>] stdlib library names to load on top of core (e.g.,
|
|
439
|
+
# `["pathname", "json"]`). Empty by default. Each entry MUST correspond to a directory under the
|
|
440
|
+
# `rbs` gem's `stdlib/` tree; unknown names are silently dropped on environment build (the underlying
|
|
441
|
+
# `RBS::EnvironmentLoader` raises and we fail-soft).
|
|
442
|
+
# @param signature_paths [Array<String, Pathname>] additional directories of `.rbs` files to load
|
|
443
|
+
# (typically the project's `sig/` tree). Non-existent or non-directory paths are filtered out at
|
|
444
|
+
# build time so the loader stays robust to fixtures and bare repositories.
|
|
445
|
+
# @param cache_store [Rigor::Cache::Store, nil] the persistent cache the loader threads through to
|
|
446
|
+
# `RbsEnvironment`, `RbsKnownClassNames`, `RbsConstantTable`, `RbsClassAncestorTable`, and
|
|
447
|
+
# `RbsClassTypeParamNames` producers. Pass `nil` (the default) to skip caching; the runner threads
|
|
448
|
+
# its own Store through here when enabled.
|
|
449
|
+
# @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 — `[virtual_filename, rbs_source]` pairs
|
|
450
|
+
# synthesised from project source by a plugin's `Manifest#source_rbs_synthesizer`. Merged into the
|
|
451
|
+
# env after `signature_paths:` and the vendored stubs. Pass `[]` (the default) when no
|
|
452
|
+
# synthesizer-emitting plugin is loaded.
|
|
473
453
|
def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [])
|
|
474
454
|
@libraries = libraries.map(&:to_s).freeze
|
|
475
455
|
@signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
|
|
476
456
|
@cache_store = cache_store
|
|
477
457
|
@virtual_rbs = virtual_rbs.map { |name, content| [name.to_s.dup.freeze, content.to_s.dup.freeze].freeze }.freeze
|
|
478
|
-
# Per-loader memoization bucket. Held as a single
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
#
|
|
482
|
-
#
|
|
483
|
-
#
|
|
484
|
-
# `:instance_definitions_table`,
|
|
485
|
-
# `:singleton_definitions_table`. Constructed via
|
|
486
|
-
# `Hash.new` (NOT a `{ ... }` literal) so Rigor's
|
|
487
|
-
# `HashShape` narrowing doesn't infer a fixed key set
|
|
488
|
-
# from the initial state and fold post-initial slot
|
|
489
|
-
# reads (e.g. `@state[:env_loaded]`) to a constant
|
|
490
|
-
# `nil`.
|
|
458
|
+
# Per-loader memoization bucket. Held as a single mutable Hash so the loader instance itself can be
|
|
459
|
+
# `.freeze`d (per ADR-15 reflection-facade contract) without losing the lazy-memo behaviour. Slot
|
|
460
|
+
# names currently consulted: `:env`, `:env_loaded`, `:env_build_warned`, `:builder`, `:reflection`,
|
|
461
|
+
# `:instance_definitions_table`, `:singleton_definitions_table`. Constructed via `Hash.new` (NOT a
|
|
462
|
+
# `{ ... }` literal) so Rigor's `HashShape` narrowing doesn't infer a fixed key set from the initial
|
|
463
|
+
# state and fold post-initial slot reads (e.g. `@state[:env_loaded]`) to a constant `nil`.
|
|
491
464
|
@state = Hash.new # rubocop:disable Style/EmptyLiteral
|
|
492
465
|
@instance_definition_cache = {}
|
|
493
466
|
@singleton_definition_cache = {}
|
|
@@ -495,51 +468,41 @@ module Rigor
|
|
|
495
468
|
@hierarchy = RbsHierarchy.new(self)
|
|
496
469
|
end
|
|
497
470
|
|
|
498
|
-
# The enclosing namespaces {.synthesize_missing_namespaces} had to
|
|
499
|
-
#
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
# collector would). Returns `::`-stripped names, shallowest-first.
|
|
505
|
-
# Empty for a well-formed sig set (the common case) and whenever
|
|
506
|
-
# the env failed to build.
|
|
471
|
+
# The enclosing namespaces {.synthesize_missing_namespaces} had to invent because the project's
|
|
472
|
+
# `signature_paths:` RBS declared qualified names (`class Foo::Bar`) without ever declaring `Foo`.
|
|
473
|
+
# Recovered by scanning the built env for class/module entries whose every declaration originated from
|
|
474
|
+
# the synthetic buffer, so the answer survives the marshalled-env cache (where no build-time collector
|
|
475
|
+
# would). Returns `::`-stripped names, shallowest-first. Empty for a well-formed sig set (the common
|
|
476
|
+
# case) and whenever the env failed to build.
|
|
507
477
|
def synthesized_namespaces
|
|
508
478
|
names_synthesized_in(SYNTHETIC_NAMESPACE_BUFFER)
|
|
509
479
|
end
|
|
510
480
|
|
|
511
|
-
# The referenced-but-undeclared types
|
|
512
|
-
#
|
|
513
|
-
#
|
|
514
|
-
#
|
|
515
|
-
# Recovered off the built env like {#synthesized_namespaces}, so
|
|
516
|
-
# it survives the marshalled-env cache.
|
|
481
|
+
# The referenced-but-undeclared types {.stub_missing_referenced_types} stubbed so the project classes
|
|
482
|
+
# that mention them could build (e.g. an unavailable `DRb::DRbServer`, or a stale
|
|
483
|
+
# `Textbringer::EditorError`). Recovered off the built env like {#synthesized_namespaces}, so it
|
|
484
|
+
# survives the marshalled-env cache.
|
|
517
485
|
def synthesized_stub_types
|
|
518
486
|
names_synthesized_in(SYNTHETIC_STUB_BUFFER)
|
|
519
487
|
end
|
|
520
488
|
|
|
521
|
-
# Every type name Rigor invented to make an otherwise-inert /
|
|
522
|
-
#
|
|
523
|
-
#
|
|
524
|
-
#
|
|
525
|
-
#
|
|
526
|
-
# mis-fires `call.undefined-method`. Memoised; empty (and cheap)
|
|
527
|
-
# for the common well-formed sig set.
|
|
489
|
+
# Every type name Rigor invented to make an otherwise-inert / unbuildable project signature set resolve
|
|
490
|
+
# — both the namespace stubs and the referenced-type stubs. {MethodDispatcher} resolves a call whose
|
|
491
|
+
# receiver is one of these (and that no real signature answered) to `Dynamic[Top]`, so the empty stub
|
|
492
|
+
# never mis-fires `call.undefined-method`. Memoised; empty (and cheap) for the common well-formed sig
|
|
493
|
+
# set.
|
|
528
494
|
def synthesized_type_names
|
|
529
495
|
@state[:synthesized_type_names] ||= (synthesized_namespaces + synthesized_stub_types).to_set
|
|
530
496
|
end
|
|
531
497
|
|
|
532
|
-
# Returns true when an RBS class or module declaration with the given
|
|
533
|
-
#
|
|
534
|
-
# ("Integer" or "::Integer"). Memoized per-name (positive and
|
|
498
|
+
# Returns true when an RBS class or module declaration with the given name is loaded. Accepts
|
|
499
|
+
# unprefixed or top-level-prefixed names ("Integer" or "::Integer"). Memoized per-name (positive and
|
|
535
500
|
# negative results both cache).
|
|
536
501
|
#
|
|
537
|
-
# When `cache_store` is set, the loader fetches the entire set of
|
|
538
|
-
#
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
# persist the result; warm runs (and a separate loader sharing
|
|
542
|
-
# the same Store) skip the env walk entirely.
|
|
502
|
+
# When `cache_store` is set, the loader fetches the entire set of known class / module / alias names
|
|
503
|
+
# once (per process) through {Cache::RbsKnownClassNames.fetch} and answers `class_known?` from the
|
|
504
|
+
# in-memory Set. Cold runs pay a single env walk and persist the result; warm runs (and a separate
|
|
505
|
+
# loader sharing the same Store) skip the env walk entirely.
|
|
543
506
|
def class_known?(name)
|
|
544
507
|
key = name.to_s
|
|
545
508
|
return @class_known_cache[key] if @class_known_cache.key?(key)
|
|
@@ -551,16 +514,14 @@ module Rigor
|
|
|
551
514
|
end
|
|
552
515
|
end
|
|
553
516
|
|
|
554
|
-
# Returns true when the named RBS declaration is a Module
|
|
555
|
-
#
|
|
556
|
-
# `
|
|
557
|
-
# `Nominal[
|
|
558
|
-
#
|
|
559
|
-
# fallback, because every concrete includer of M sees Kernel
|
|
560
|
-
# / Object instance methods as part of its own ancestor chain.
|
|
517
|
+
# Returns true when the named RBS declaration is a Module (`RBS::AST::Declarations::Module`) rather
|
|
518
|
+
# than a Class. The `user_class_fallback_receiver` tier consults this to route
|
|
519
|
+
# `Nominal[M].some_kernel_method` (where M is a module mixin like `PP::ObjectMixin`) through the
|
|
520
|
+
# `Nominal[Object]` fallback, because every concrete includer of M sees Kernel / Object instance
|
|
521
|
+
# methods as part of its own ancestor chain.
|
|
561
522
|
#
|
|
562
|
-
# Returns false for classes, for unknown names, and when the
|
|
563
|
-
#
|
|
523
|
+
# Returns false for classes, for unknown names, and when the RBS environment failed to build
|
|
524
|
+
# (fail-soft).
|
|
564
525
|
def rbs_module?(name)
|
|
565
526
|
return false if env.nil?
|
|
566
527
|
|
|
@@ -573,10 +534,9 @@ module Rigor
|
|
|
573
534
|
false
|
|
574
535
|
end
|
|
575
536
|
|
|
576
|
-
# Yields every known class / module / alias name (top-level
|
|
577
|
-
#
|
|
578
|
-
#
|
|
579
|
-
# it never recurses back through {#class_known?}.
|
|
537
|
+
# Yields every known class / module / alias name (top-level prefixed) currently loaded into the
|
|
538
|
+
# environment. The cache producer that materialises the known-name set uses this so it never recurses
|
|
539
|
+
# back through {#class_known?}.
|
|
580
540
|
def each_known_class_name
|
|
581
541
|
return enum_for(:each_known_class_name) unless block_given?
|
|
582
542
|
return if env.nil?
|
|
@@ -584,22 +544,16 @@ module Rigor
|
|
|
584
544
|
env.class_decls.each_key { |rbs_name| yield rbs_name.to_s }
|
|
585
545
|
env.class_alias_decls.each_key { |rbs_name| yield rbs_name.to_s }
|
|
586
546
|
rescue ::RBS::BaseError
|
|
587
|
-
# fail-soft: a broken RBS environment yields no names.
|
|
588
|
-
#
|
|
589
|
-
#
|
|
590
|
-
# must surface so they don't hide silently the way the
|
|
591
|
-
# v0.0.9 cache `Cache::Descriptor` regression did.
|
|
547
|
+
# fail-soft: a broken RBS environment yields no names. Analyzer-internal errors (NameError,
|
|
548
|
+
# NoMethodError, LoadError) are NOT swallowed — those are bugs and must surface so they don't hide
|
|
549
|
+
# silently the way the v0.0.9 cache `Cache::Descriptor` regression did.
|
|
592
550
|
end
|
|
593
551
|
|
|
594
|
-
# ADR-20 slice 2e — iterates over every `%a{...}`
|
|
595
|
-
#
|
|
596
|
-
#
|
|
597
|
-
# `
|
|
598
|
-
#
|
|
599
|
-
# find `rigor:v1:hkt_register` / `rigor:v1:hkt_define`
|
|
600
|
-
# directives in user-authored overlays and merge them
|
|
601
|
-
# into the per-`Environment` HKT registry. Yields nothing
|
|
602
|
-
# when the env failed to build (fail-soft, same shape as
|
|
552
|
+
# ADR-20 slice 2e — iterates over every `%a{...}` annotation attached to a class- or module-level
|
|
553
|
+
# declaration in the loaded RBS environment, yielding `(annotation_string, source_location)` pairs.
|
|
554
|
+
# Used by {Rigor::Inference::HktRegistry.scan_rbs_loader} to find `rigor:v1:hkt_register` /
|
|
555
|
+
# `rigor:v1:hkt_define` directives in user-authored overlays and merge them into the per-`Environment`
|
|
556
|
+
# HKT registry. Yields nothing when the env failed to build (fail-soft, same shape as
|
|
603
557
|
# {#each_known_class_name}).
|
|
604
558
|
def each_class_decl_annotation
|
|
605
559
|
return enum_for(:each_class_decl_annotation) unless block_given?
|
|
@@ -613,20 +567,15 @@ module Rigor
|
|
|
613
567
|
end
|
|
614
568
|
end
|
|
615
569
|
rescue ::RBS::BaseError, ::Ractor::IsolationError
|
|
616
|
-
# fail-soft: matches each_known_class_name's policy.
|
|
617
|
-
# Ractor
|
|
618
|
-
#
|
|
619
|
-
# ADR-15's full deep-freeze migration completes — the
|
|
620
|
-
# worker falls back to the base (builtins-only)
|
|
621
|
-
# registry rather than crashing.
|
|
570
|
+
# fail-soft: matches each_known_class_name's policy. Ractor::IsolationError surfaces when the scan
|
|
571
|
+
# is invoked from a non-main Ractor pool worker before ADR-15's full deep-freeze migration completes
|
|
572
|
+
# — the worker falls back to the base (builtins-only) registry rather than crashing.
|
|
622
573
|
end
|
|
623
574
|
|
|
624
|
-
# Like {#each_class_decl_annotation}, but also yields the
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
# `rigor:v1:conforms-to` directive back to the class it
|
|
629
|
-
# annotates. Same fail-soft policy as the un-named variant.
|
|
575
|
+
# Like {#each_class_decl_annotation}, but also yields the owning class / module's RBS name as the
|
|
576
|
+
# first block argument: `(class_name, annotation_string, location)`. Used by
|
|
577
|
+
# {Rigor::RbsExtended::ConformanceChecker} to resolve a `rigor:v1:conforms-to` directive back to the
|
|
578
|
+
# class it annotates. Same fail-soft policy as the un-named variant.
|
|
630
579
|
def each_class_decl_annotation_with_name
|
|
631
580
|
return enum_for(:each_class_decl_annotation_with_name) unless block_given?
|
|
632
581
|
return if env.nil?
|
|
@@ -642,16 +591,12 @@ module Rigor
|
|
|
642
591
|
# fail-soft: see #each_class_decl_annotation.
|
|
643
592
|
end
|
|
644
593
|
|
|
645
|
-
# Returns a frozen `Hash<String, String>` mapping each loaded
|
|
646
|
-
#
|
|
647
|
-
#
|
|
648
|
-
#
|
|
649
|
-
#
|
|
650
|
-
#
|
|
651
|
-
# core, stdlib libraries, gem-bundled RBS). Each value is a
|
|
652
|
-
# frozen `String` so the whole result is `Ractor.shareable?`
|
|
653
|
-
# — the Phase 4b worker pool ships a snapshot back to the
|
|
654
|
-
# coordinator on the first `:prepare` message.
|
|
594
|
+
# Returns a frozen `Hash<String, String>` mapping each loaded class / module name (top-level prefixed)
|
|
595
|
+
# to the file path of its FIRST declaration's RBS source. Used by {Rigor::Analysis::RunStats} to
|
|
596
|
+
# attribute the type universe between "project sig/" (paths under the configured `signature_paths`)
|
|
597
|
+
# and "bundled" (everything else — RBS core, stdlib libraries, gem-bundled RBS). Each value is a frozen
|
|
598
|
+
# `String` so the whole result is `Ractor.shareable?` — the Phase 4b worker pool ships a snapshot back
|
|
599
|
+
# to the coordinator on the first `:prepare` message.
|
|
655
600
|
def class_decl_paths
|
|
656
601
|
return {}.freeze if env.nil?
|
|
657
602
|
|
|
@@ -674,17 +619,14 @@ module Rigor
|
|
|
674
619
|
{}.freeze
|
|
675
620
|
end
|
|
676
621
|
|
|
677
|
-
# @return [RBS::Definition, nil] the resolved instance definition
|
|
678
|
-
#
|
|
679
|
-
#
|
|
680
|
-
# we fail-soft and return nil so the caller can fall back).
|
|
622
|
+
# @return [RBS::Definition, nil] the resolved instance definition for `class_name`, or nil when the
|
|
623
|
+
# class is unknown or its definition cannot be built (RBS may raise on broken hierarchies; we
|
|
624
|
+
# fail-soft and return nil so the caller can fall back).
|
|
681
625
|
#
|
|
682
|
-
# Built on demand from the (possibly cache-loaded) env; the
|
|
683
|
-
#
|
|
684
|
-
#
|
|
685
|
-
#
|
|
686
|
-
# measurably slower (and allocation-heavier) than rebuilding
|
|
687
|
-
# the ones a run actually touches.
|
|
626
|
+
# Built on demand from the (possibly cache-loaded) env; the in-memory `@instance_definition_cache`
|
|
627
|
+
# keeps the per-process short-circuit. ADR-54 WD1 retired the definitions disk blob: given a cached
|
|
628
|
+
# env, `Marshal.load`-ing every definition was measurably slower (and allocation-heavier) than
|
|
629
|
+
# rebuilding the ones a run actually touches.
|
|
688
630
|
def instance_definition(class_name)
|
|
689
631
|
key = class_name.to_s
|
|
690
632
|
return @instance_definition_cache[key] if @instance_definition_cache.key?(key)
|
|
@@ -700,13 +642,10 @@ module Rigor
|
|
|
700
642
|
definition.methods[method_name.to_sym]
|
|
701
643
|
end
|
|
702
644
|
|
|
703
|
-
# @return [Array<Symbol>, nil] every instance-method name on
|
|
704
|
-
#
|
|
705
|
-
#
|
|
706
|
-
#
|
|
707
|
-
# "no methods" apart from "unknown class". Used by the
|
|
708
|
-
# `rigor:v1:conforms-to` presence check
|
|
709
|
-
# ({Rigor::RbsExtended::ConformanceChecker}).
|
|
645
|
+
# @return [Array<Symbol>, nil] every instance-method name on `class_name` — own, inherited, and
|
|
646
|
+
# included — as resolved by `RBS::DefinitionBuilder`. Returns `nil` (NOT `[]`) when the class
|
|
647
|
+
# definition cannot be built so callers can tell "no methods" apart from "unknown class". Used by the
|
|
648
|
+
# `rigor:v1:conforms-to` presence check ({Rigor::RbsExtended::ConformanceChecker}).
|
|
710
649
|
def instance_method_names(class_name)
|
|
711
650
|
definition = instance_definition(class_name)
|
|
712
651
|
return nil unless definition
|
|
@@ -714,12 +653,10 @@ module Rigor
|
|
|
714
653
|
definition.methods.keys
|
|
715
654
|
end
|
|
716
655
|
|
|
717
|
-
# @return [RBS::Definition, nil] the built definition for the RBS
|
|
718
|
-
#
|
|
719
|
-
#
|
|
720
|
-
#
|
|
721
|
-
# (a typo, or the defining library / sig set is not on the load
|
|
722
|
-
# path). Fail-soft on RBS build errors.
|
|
656
|
+
# @return [RBS::Definition, nil] the built definition for the RBS interface `interface_name`
|
|
657
|
+
# (`_RewindableStream`), whose `.methods` are the required members (including interface-ancestor
|
|
658
|
+
# members). Returns `nil` when the name does not resolve to a loaded interface (a typo, or the
|
|
659
|
+
# defining library / sig set is not on the load path). Fail-soft on RBS build errors.
|
|
723
660
|
def interface_definition(interface_name)
|
|
724
661
|
rbs_name = parse_type_name(interface_name)
|
|
725
662
|
return nil unless rbs_name
|
|
@@ -731,24 +668,20 @@ module Rigor
|
|
|
731
668
|
nil
|
|
732
669
|
end
|
|
733
670
|
|
|
734
|
-
# @return [Array<Symbol>, nil] every method name required by the RBS
|
|
735
|
-
#
|
|
736
|
-
# accessor over {#interface_definition} for the presence check.
|
|
671
|
+
# @return [Array<Symbol>, nil] every method name required by the RBS interface `interface_name`, or nil
|
|
672
|
+
# when it does not resolve. Thin accessor over {#interface_definition} for the presence check.
|
|
737
673
|
def interface_method_names(interface_name)
|
|
738
674
|
interface_definition(interface_name)&.methods&.keys
|
|
739
675
|
end
|
|
740
676
|
|
|
741
|
-
# @param rbs_alias [RBS::Types::Alias] a type-alias reference (`string`,
|
|
742
|
-
#
|
|
743
|
-
# @return [RBS::Types::t, nil] the alias's aliased type one level out,
|
|
744
|
-
#
|
|
745
|
-
#
|
|
746
|
-
# ::
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
# does not reject `nil`. `expand_alias2` handles the (rarer) generic
|
|
750
|
-
# case — a `range[T]` param previously fell back to "admits", which
|
|
751
|
-
# suppressed e.g. `MatchData#[](nil)`.
|
|
677
|
+
# @param rbs_alias [RBS::Types::Alias] a type-alias reference (`string`, `int`, `range[int?]`, …)
|
|
678
|
+
# appearing in a method signature.
|
|
679
|
+
# @return [RBS::Types::t, nil] the alias's aliased type one level out, with type arguments substituted
|
|
680
|
+
# for a generic alias (`string` → `::String | ::_ToStr`; `range[int?]` → `::Range[int?] |
|
|
681
|
+
# ::_Range[int?]`), or nil for an unresolved name. Lets a caller see through the alias that
|
|
682
|
+
# {Inference::RbsTypeTranslator} otherwise degrades to `untyped`, which is why an interface/alias
|
|
683
|
+
# parameter does not reject `nil`. `expand_alias2` handles the (rarer) generic case — a `range[T]`
|
|
684
|
+
# param previously fell back to "admits", which suppressed e.g. `MatchData#[](nil)`.
|
|
752
685
|
def expand_type_alias(rbs_alias)
|
|
753
686
|
return nil if env.nil?
|
|
754
687
|
|
|
@@ -761,15 +694,13 @@ module Rigor
|
|
|
761
694
|
nil
|
|
762
695
|
end
|
|
763
696
|
|
|
764
|
-
# @return [RBS::Definition, nil] the resolved singleton (class
|
|
765
|
-
#
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
# Returns nil for unknown names and on RBS build errors (fail-soft).
|
|
697
|
+
# @return [RBS::Definition, nil] the resolved singleton (class object) definition for `class_name`. The
|
|
698
|
+
# methods on this definition are the *class methods* of `class_name`, including those inherited from
|
|
699
|
+
# `Class` and `Module` for class types. Returns nil for unknown names and on RBS build errors
|
|
700
|
+
# (fail-soft).
|
|
769
701
|
#
|
|
770
|
-
# Built on demand from the env with a per-process memo; the
|
|
771
|
-
#
|
|
772
|
-
# WD1).
|
|
702
|
+
# Built on demand from the env with a per-process memo; the same on-demand discipline as
|
|
703
|
+
# {#instance_definition} (ADR-54 WD1).
|
|
773
704
|
def singleton_definition(class_name)
|
|
774
705
|
key = class_name.to_s
|
|
775
706
|
return @singleton_definition_cache[key] if @singleton_definition_cache.key?(key)
|
|
@@ -777,11 +708,10 @@ module Rigor
|
|
|
777
708
|
@singleton_definition_cache[key] = build_singleton_definition(class_name)
|
|
778
709
|
end
|
|
779
710
|
|
|
780
|
-
# @return [RBS::Definition::Method, nil] the class method on
|
|
781
|
-
# `class_name
|
|
782
|
-
#
|
|
783
|
-
#
|
|
784
|
-
# method_name: :new)` returns Class#new for any class type.
|
|
711
|
+
# @return [RBS::Definition::Method, nil] the class method on `class_name`. For example,
|
|
712
|
+
# `singleton_method(class_name: "Integer", method_name: :sqrt)` returns the definition for
|
|
713
|
+
# `Integer.sqrt`, while `singleton_method(class_name: "Foo", method_name: :new)` returns Class#new
|
|
714
|
+
# for any class type.
|
|
785
715
|
def singleton_method(class_name:, method_name:)
|
|
786
716
|
definition = singleton_definition(class_name)
|
|
787
717
|
return nil unless definition
|
|
@@ -789,25 +719,19 @@ module Rigor
|
|
|
789
719
|
definition.methods[method_name.to_sym]
|
|
790
720
|
end
|
|
791
721
|
|
|
792
|
-
# Slice 4 phase 2d. Returns the class's declared type-parameter
|
|
793
|
-
#
|
|
794
|
-
# `
|
|
795
|
-
#
|
|
796
|
-
# instance definition is the canonical source because singleton
|
|
797
|
-
# methods (e.g., `Array.new`) parameterize over the same `Elem`
|
|
798
|
-
# as instance methods.
|
|
722
|
+
# Slice 4 phase 2d. Returns the class's declared type-parameter names as Symbols (e.g., `[:Elem]` for
|
|
723
|
+
# `Array`, `[:K, :V]` for `Hash`). Used by the dispatcher to build the substitution map from receiver
|
|
724
|
+
# `type_args` into the method's return type. The instance definition is the canonical source because
|
|
725
|
+
# singleton methods (e.g., `Array.new`) parameterize over the same `Elem` as instance methods.
|
|
799
726
|
#
|
|
800
|
-
# Returns an empty array for non-generic classes and for unknown
|
|
801
|
-
#
|
|
802
|
-
# `
|
|
803
|
-
# not the AST `TypeParam` object (those live on the AST level).
|
|
727
|
+
# Returns an empty array for non-generic classes and for unknown names (the loader stays fail-soft).
|
|
728
|
+
# NOTE: in the `rbs` gem, `RBS::Definition#type_params` returns `Array<Symbol>` directly, not the AST
|
|
729
|
+
# `TypeParam` object (those live on the AST level).
|
|
804
730
|
#
|
|
805
|
-
# When `cache_store` is set, the loader fetches the entire
|
|
806
|
-
#
|
|
807
|
-
#
|
|
808
|
-
#
|
|
809
|
-
# it; warm runs (and a separate loader sharing the same Store)
|
|
810
|
-
# skip the env walk entirely.
|
|
731
|
+
# When `cache_store` is set, the loader fetches the entire type-parameter-name table once (per
|
|
732
|
+
# process) through {Cache::RbsClassTypeParamNames.fetch} and answers point lookups from it. Cold runs
|
|
733
|
+
# build the table once and persist it; warm runs (and a separate loader sharing the same Store) skip
|
|
734
|
+
# the env walk entirely.
|
|
811
735
|
def class_type_param_names(class_name)
|
|
812
736
|
if cache_store
|
|
813
737
|
key = class_name.to_s.delete_prefix("::")
|
|
@@ -824,11 +748,9 @@ module Rigor
|
|
|
824
748
|
@hierarchy.class_ordering(lhs, rhs)
|
|
825
749
|
end
|
|
826
750
|
|
|
827
|
-
# @return [Array<String>] every RBS-declared constant name
|
|
828
|
-
#
|
|
829
|
-
#
|
|
830
|
-
# materialises the constant-type table; ordinary callers
|
|
831
|
-
# should keep using {#constant_type} for point lookups.
|
|
751
|
+
# @return [Array<String>] every RBS-declared constant name (top-level prefixed, e.g., `"::Math::PI"`)
|
|
752
|
+
# currently loaded into the environment. Used by the cache producer that materialises the
|
|
753
|
+
# constant-type table; ordinary callers should keep using {#constant_type} for point lookups.
|
|
832
754
|
def constant_names
|
|
833
755
|
return [] if env.nil?
|
|
834
756
|
|
|
@@ -837,11 +759,9 @@ module Rigor
|
|
|
837
759
|
[]
|
|
838
760
|
end
|
|
839
761
|
|
|
840
|
-
# Yields `(name, entry)` for every RBS constant declaration
|
|
841
|
-
#
|
|
842
|
-
#
|
|
843
|
-
# going back through {#constant_type} (which would recurse
|
|
844
|
-
# back into the cache when `cache_store` is set).
|
|
762
|
+
# Yields `(name, entry)` for every RBS constant declaration currently loaded into the environment. The
|
|
763
|
+
# cache producer uses this to materialise the constant-type table without going back through
|
|
764
|
+
# {#constant_type} (which would recurse back into the cache when `cache_store` is set).
|
|
845
765
|
def each_constant_decl
|
|
846
766
|
return enum_for(:each_constant_decl) unless block_given?
|
|
847
767
|
return if env.nil?
|
|
@@ -853,22 +773,16 @@ module Rigor
|
|
|
853
773
|
# fail-soft: a broken RBS environment yields no entries.
|
|
854
774
|
end
|
|
855
775
|
|
|
856
|
-
# Slice A constant-value lookup. Returns the translated
|
|
857
|
-
# `
|
|
858
|
-
#
|
|
859
|
-
#
|
|
860
|
-
#
|
|
861
|
-
# value as authoritative when present and as "unknown" when
|
|
862
|
-
# nil; the loader does NOT consult the class declarations
|
|
863
|
-
# here — class objects are still resolved through
|
|
864
|
-
# {#class_known?} and `Environment#singleton_for_name`.
|
|
776
|
+
# Slice A constant-value lookup. Returns the translated `Rigor::Type` for a non-class constant
|
|
777
|
+
# declaration (`BUCKETS: Array[Symbol]`, `DEFAULT_PATH: String`, ...) or `nil` when no constant entry
|
|
778
|
+
# exists for `name` in the loaded RBS environment. Callers MUST treat the return value as authoritative
|
|
779
|
+
# when present and as "unknown" when nil; the loader does NOT consult the class declarations here —
|
|
780
|
+
# class objects are still resolved through {#class_known?} and `Environment#singleton_for_name`.
|
|
865
781
|
#
|
|
866
|
-
# When `cache_store` is set, the loader fetches the entire
|
|
867
|
-
#
|
|
868
|
-
#
|
|
869
|
-
#
|
|
870
|
-
# write the result to disk; warm runs skip the translation
|
|
871
|
-
# entirely and pay only a `Marshal.load` of the table.
|
|
782
|
+
# When `cache_store` is set, the loader fetches the entire translated constant table once (per
|
|
783
|
+
# process) through {Cache::RbsConstantTable.fetch} and answers point lookups from it. Cold runs pay
|
|
784
|
+
# the translation cost up-front and write the result to disk; warm runs skip the translation entirely
|
|
785
|
+
# and pay only a `Marshal.load` of the table.
|
|
872
786
|
def constant_type(name)
|
|
873
787
|
rbs_name = parse_type_name(name)
|
|
874
788
|
return nil unless rbs_name
|
|
@@ -882,27 +796,18 @@ module Rigor
|
|
|
882
796
|
nil
|
|
883
797
|
end
|
|
884
798
|
|
|
885
|
-
# ADR-15 Phase 4b.x — eagerly drives every cached
|
|
886
|
-
#
|
|
887
|
-
#
|
|
888
|
-
#
|
|
889
|
-
#
|
|
890
|
-
#
|
|
891
|
-
#
|
|
892
|
-
#
|
|
893
|
-
# (`RBS::EnvironmentLoader::DEFAULT_CORE_ROOT`,
|
|
894
|
-
# `RBS::Repository::DEFAULT_STDLIB_ROOT`,
|
|
895
|
-
# `Gem::Requirement::DefaultRequirement`, …) and trips
|
|
896
|
-
# `Ractor::IsolationError`. Pre-warming on the main
|
|
897
|
-
# Ractor — env blob loaded, derived tables built — keeps
|
|
898
|
-
# workers off that chain (`RBS::DefinitionBuilder` over
|
|
899
|
-
# an already-built env does not touch it).
|
|
799
|
+
# ADR-15 Phase 4b.x — eagerly drives every cached producer (plus the eager definitions tables, computed
|
|
800
|
+
# from the cached env since ADR-54 WD1) so a subsequent worker Ractor can serve all of its RBS queries
|
|
801
|
+
# without ever calling `RBS::EnvironmentLoader.new`. The loader path that calls
|
|
802
|
+
# `EnvironmentLoader.new` transitively reads a chain of non-`Ractor.shareable?` module constants
|
|
803
|
+
# (`RBS::EnvironmentLoader::DEFAULT_CORE_ROOT`, `RBS::Repository::DEFAULT_STDLIB_ROOT`,
|
|
804
|
+
# `Gem::Requirement::DefaultRequirement`, …) and trips `Ractor::IsolationError`. Pre-warming on the
|
|
805
|
+
# main Ractor — env blob loaded, derived tables built — keeps workers off that chain
|
|
806
|
+
# (`RBS::DefinitionBuilder` over an already-built env does not touch it).
|
|
900
807
|
#
|
|
901
|
-
# No-op when `cache_store` is nil — without a Store the
|
|
902
|
-
#
|
|
903
|
-
#
|
|
904
|
-
# enabled. Returns `self` so the call chains cleanly
|
|
905
|
-
# from the `Runner` pre-spawn hook.
|
|
808
|
+
# No-op when `cache_store` is nil — without a Store the worker has no choice but to build env via the
|
|
809
|
+
# loader, so the caller MUST ensure pool mode runs with caching enabled. Returns `self` so the call
|
|
810
|
+
# chains cleanly from the `Runner` pre-spawn hook.
|
|
906
811
|
def prewarm
|
|
907
812
|
return self if cache_store.nil?
|
|
908
813
|
|
|
@@ -916,13 +821,10 @@ module Rigor
|
|
|
916
821
|
self
|
|
917
822
|
end
|
|
918
823
|
|
|
919
|
-
# ADR-54 WD4 — the shared cache descriptor for every RBS-derived
|
|
920
|
-
#
|
|
921
|
-
#
|
|
922
|
-
#
|
|
923
|
-
# memoised per loader (on `@state`, alongside `:env` — the env
|
|
924
|
-
# itself is loader-lifetime-memoised, so this adds no new
|
|
925
|
-
# staleness class).
|
|
824
|
+
# ADR-54 WD4 — the shared cache descriptor for every RBS-derived producer consulting this loader.
|
|
825
|
+
# Building it digests every `.rbs` file under `signature_paths` + the vendored gem sigs, and the
|
|
826
|
+
# result is identical across producers, so one build is memoised per loader (on `@state`, alongside
|
|
827
|
+
# `:env` — the env itself is loader-lifetime-memoised, so this adds no new staleness class).
|
|
926
828
|
def rbs_cache_descriptor
|
|
927
829
|
@state[:rbs_cache_descriptor] ||= begin
|
|
928
830
|
require_relative "../cache/rbs_descriptor"
|
|
@@ -930,19 +832,14 @@ module Rigor
|
|
|
930
832
|
end
|
|
931
833
|
end
|
|
932
834
|
|
|
933
|
-
# ADR-15 Phase 2b — return the loader's read-only
|
|
934
|
-
#
|
|
935
|
-
#
|
|
936
|
-
# access; the loader memoises so repeated calls return
|
|
937
|
-
# the same instance.
|
|
835
|
+
# ADR-15 Phase 2b — return the loader's read-only query surface as a frozen, `Ractor.shareable?`
|
|
836
|
+
# {Reflection} value object. Built lazily on first access; the loader memoises so repeated calls
|
|
837
|
+
# return the same instance.
|
|
938
838
|
#
|
|
939
|
-
# The Reflection consumes the loader's already-warmed
|
|
940
|
-
#
|
|
941
|
-
#
|
|
942
|
-
#
|
|
943
|
-
# and never re-consults the loader — making it safe to
|
|
944
|
-
# share across Ractors while the loader stays per-
|
|
945
|
-
# process / per-Ractor for write-path operations.
|
|
839
|
+
# The Reflection consumes the loader's already-warmed cache producers (or, when no `cache_store` is
|
|
840
|
+
# set, eagerly walks the env). Once constructed, the Reflection carries the derived tables
|
|
841
|
+
# independently and never re-consults the loader — making it safe to share across Ractors while the
|
|
842
|
+
# loader stays per- process / per-Ractor for write-path operations.
|
|
946
843
|
def reflection
|
|
947
844
|
@state[:reflection] ||= begin
|
|
948
845
|
require_relative "reflection"
|
|
@@ -959,11 +856,10 @@ module Rigor
|
|
|
959
856
|
|
|
960
857
|
private
|
|
961
858
|
|
|
962
|
-
# The `::`-stripped names of every class/module entry whose
|
|
963
|
-
#
|
|
964
|
-
#
|
|
965
|
-
#
|
|
966
|
-
# env cache; shallowest-first. Empty when the env failed to build.
|
|
859
|
+
# The `::`-stripped names of every class/module entry whose declarations ALL originated from the given
|
|
860
|
+
# sentinel buffer — i.e. names Rigor synthesized, not names the project declared. Reads off the built
|
|
861
|
+
# env so the answer survives the marshalled env cache; shallowest-first. Empty when the env failed to
|
|
862
|
+
# build.
|
|
967
863
|
def names_synthesized_in(buffer_name)
|
|
968
864
|
e = env
|
|
969
865
|
return [] if e.nil?
|
|
@@ -978,13 +874,11 @@ module Rigor
|
|
|
978
874
|
names.sort_by { |name| name.count("::") }
|
|
979
875
|
end
|
|
980
876
|
|
|
981
|
-
# Collects the AST declaration nodes behind a `class_decls`
|
|
982
|
-
#
|
|
983
|
-
# RBS
|
|
984
|
-
#
|
|
985
|
-
#
|
|
986
|
-
# The single-`decl` shape is handled defensively so the loader
|
|
987
|
-
# survives an rbs-gem minor bump.
|
|
877
|
+
# Collects the AST declaration nodes behind a `class_decls` entry across the supported RBS range (`rbs
|
|
878
|
+
# >= 3.0, < 5.0`). RBS 4's `ModuleEntry` / `ClassEntry` expose `each_decl` yielding bare AST
|
|
879
|
+
# declarations; RBS 3.x exposes `decls`, an array of `MultiEntry::D` wrappers whose `#decl` is the AST
|
|
880
|
+
# declaration. The single-`decl` shape is handled defensively so the loader survives an rbs-gem minor
|
|
881
|
+
# bump.
|
|
988
882
|
def entry_declarations(entry)
|
|
989
883
|
if entry.respond_to?(:each_decl)
|
|
990
884
|
[].tap { |acc| entry.each_decl { |decl| acc << decl } }
|
|
@@ -997,9 +891,8 @@ module Rigor
|
|
|
997
891
|
end
|
|
998
892
|
end
|
|
999
893
|
|
|
1000
|
-
# True when an AST declaration was emitted into `buffer_name`
|
|
1001
|
-
#
|
|
1002
|
-
# buffer name on its location.
|
|
894
|
+
# True when an AST declaration was emitted into `buffer_name` (one of the synthetic-source sentinels)
|
|
895
|
+
# — identified by the buffer name on its location.
|
|
1003
896
|
def synthetic_decl?(decl, buffer_name)
|
|
1004
897
|
location = decl.respond_to?(:location) ? decl.location : nil
|
|
1005
898
|
location&.buffer&.name.to_s == buffer_name
|
|
@@ -1026,23 +919,19 @@ module Rigor
|
|
|
1026
919
|
end
|
|
1027
920
|
end
|
|
1028
921
|
|
|
1029
|
-
# ADR-15 Phase 2b — the `Reflection` build path
|
|
1030
|
-
#
|
|
1031
|
-
#
|
|
1032
|
-
#
|
|
1033
|
-
# producer's `.fetch` when a store IS available, and
|
|
1034
|
-
# falls back to the producer's `.compute` otherwise.
|
|
922
|
+
# ADR-15 Phase 2b — the `Reflection` build path consumes these tables even when `cache_store` is nil
|
|
923
|
+
# (e.g. tests that build a `Reflection` without a persistent cache). The helper routes through the
|
|
924
|
+
# producer's `.fetch` when a store IS available, and falls back to the producer's `.compute`
|
|
925
|
+
# otherwise.
|
|
1035
926
|
def fetch_or_compute_producer(producer)
|
|
1036
927
|
return producer.fetch(loader: self, store: cache_store) if cache_store
|
|
1037
928
|
|
|
1038
929
|
producer.send(:compute, self)
|
|
1039
930
|
end
|
|
1040
931
|
|
|
1041
|
-
# ADR-15 Phase 2b — `Hash<String, Array<String>>` of
|
|
1042
|
-
#
|
|
1043
|
-
#
|
|
1044
|
-
# `cache_store` is set; falls back to the producer's
|
|
1045
|
-
# `compute` otherwise. Used by {#reflection}.
|
|
932
|
+
# ADR-15 Phase 2b — `Hash<String, Array<String>>` of normalised ancestor chains per class. Consumes
|
|
933
|
+
# the existing `RbsClassAncestorTable` producer when `cache_store` is set; falls back to the
|
|
934
|
+
# producer's `compute` otherwise. Used by {#reflection}.
|
|
1046
935
|
def ancestor_names_table
|
|
1047
936
|
@ancestor_names_table ||= begin
|
|
1048
937
|
require_relative "../cache/rbs_class_ancestor_table"
|
|
@@ -1069,27 +958,50 @@ module Rigor
|
|
|
1069
958
|
translated unless translated.is_a?(Type::Bot)
|
|
1070
959
|
end
|
|
1071
960
|
|
|
1072
|
-
# The RBS environment for this loader. Memoised both on
|
|
1073
|
-
#
|
|
1074
|
-
#
|
|
1075
|
-
#
|
|
1076
|
-
#
|
|
1077
|
-
#
|
|
1078
|
-
# whole sig set per AST node touched during analysis,
|
|
1079
|
-
# multiplying per-file analysis cost by ~100x. Failures
|
|
1080
|
-
# short-circuit to `nil` here and are surfaced to the user
|
|
1081
|
-
# via `warn_about_env_build_failure_once` so the broken
|
|
1082
|
-
# `signature_paths:` entry is identifiable.
|
|
961
|
+
# The RBS environment for this loader. Memoised both on success AND on failure: when the env build
|
|
962
|
+
# raises (typically `RBS::DuplicatedDeclarationError` because a `signature_paths:` entry redeclares a
|
|
963
|
+
# constant or class already shipped by stdlib RBS), retrying on every subsequent `env` call would
|
|
964
|
+
# re-parse and re-resolve the whole sig set per AST node touched during analysis, multiplying per-file
|
|
965
|
+
# analysis cost by ~100x. Failures short-circuit to `nil` here and are surfaced to the user via
|
|
966
|
+
# `warn_about_env_build_failure_once` so the broken `signature_paths:` entry is identifiable.
|
|
1083
967
|
def env
|
|
1084
968
|
return @state[:env] if @state[:env_loaded]
|
|
1085
969
|
|
|
1086
970
|
@state[:env_loaded] = true
|
|
1087
971
|
@state[:env] = cache_store ? cached_env : build_env
|
|
972
|
+
warn_about_quarantined_signatures
|
|
973
|
+
@state[:env]
|
|
1088
974
|
rescue ::RBS::BaseError => e
|
|
1089
975
|
warn_about_env_build_failure_once(e)
|
|
1090
976
|
@state[:env] = nil
|
|
1091
977
|
end
|
|
1092
978
|
|
|
979
|
+
# Tell the user, once per run, which of their `signature_paths:` `.rbs` files were skipped because they
|
|
980
|
+
# do not parse ({RbsLoader.add_project_signatures} quarantines them so the rest of the env survives). A
|
|
981
|
+
# skipped file's types are absent, so calls into them read `Dynamic[top]` — silently, without this. This
|
|
982
|
+
# is the visibility half of the fix: a shrinking diagnostic count must never be mistaken for a clean run
|
|
983
|
+
# when it actually means "your sig/ stopped loading". No-op when `signature_paths:` is empty (the cost is
|
|
984
|
+
# then a single empty-set check) or every file parses.
|
|
985
|
+
def warn_about_quarantined_signatures
|
|
986
|
+
return if @state[:quarantine_warned]
|
|
987
|
+
|
|
988
|
+
quarantined = self.class.quarantined_project_signatures(@signature_paths)
|
|
989
|
+
return if quarantined.empty?
|
|
990
|
+
|
|
991
|
+
@state[:quarantine_warned] = true
|
|
992
|
+
listed = quarantined.first(QUARANTINE_WARN_LIMIT)
|
|
993
|
+
more = quarantined.size - listed.size
|
|
994
|
+
lines = listed.map { |_path, first_line| " - #{first_line}" }
|
|
995
|
+
lines << " … and #{more} more" if more.positive?
|
|
996
|
+
warn(
|
|
997
|
+
"rigor: skipped #{quarantined.size} unparseable RBS file(s) under `signature_paths:`.\n " \
|
|
998
|
+
"They were QUARANTINED so the rest of your RBS env still loads, but the types they\n " \
|
|
999
|
+
"declare are absent — calls into them read `Dynamic[top]`, so coverage and diagnostics\n " \
|
|
1000
|
+
"are reduced. Fix the parse error(s) to restore that coverage:\n" \
|
|
1001
|
+
"#{lines.join("\n")}"
|
|
1002
|
+
)
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1093
1005
|
def warn_about_env_build_failure_once(error)
|
|
1094
1006
|
return if @state[:env_build_warned]
|
|
1095
1007
|
|
|
@@ -1111,15 +1023,12 @@ module Rigor
|
|
|
1111
1023
|
Cache::RbsEnvironment.fetch(loader: self, store: cache_store)
|
|
1112
1024
|
end
|
|
1113
1025
|
|
|
1114
|
-
# Full `Hash<String, RBS::Definition>` tables for the
|
|
1115
|
-
#
|
|
1116
|
-
#
|
|
1117
|
-
# from
|
|
1118
|
-
#
|
|
1119
|
-
#
|
|
1120
|
-
# the eager-table cost is now a compute, not a load. Keys stay
|
|
1121
|
-
# in `RBS::TypeName#to_s` form (top-level prefixed `"::Hash"`)
|
|
1122
|
-
# — the shape {Environment::Reflection} documents.
|
|
1026
|
+
# Full `Hash<String, RBS::Definition>` tables for the {#prewarm} / {#reflection} consumers (ADR-15
|
|
1027
|
+
# Phase 2b/4b.x), which need every definition materialised up front. Built from the (cached) env via
|
|
1028
|
+
# `RBS::DefinitionBuilder` — ADR-54 WD1 retired the disk blobs these used to `Marshal.load` (building
|
|
1029
|
+
# all definitions from a cached env is faster), so the eager-table cost is now a compute, not a load.
|
|
1030
|
+
# Keys stay in `RBS::TypeName#to_s` form (top-level prefixed `"::Hash"`) — the shape
|
|
1031
|
+
# {Environment::Reflection} documents.
|
|
1123
1032
|
def instance_definitions_table
|
|
1124
1033
|
@state[:instance_definitions_table] ||= build_definitions_table do |name|
|
|
1125
1034
|
build_instance_definition(name)
|
|
@@ -1179,15 +1088,12 @@ module Rigor
|
|
|
1179
1088
|
nil
|
|
1180
1089
|
end
|
|
1181
1090
|
|
|
1182
|
-
# Resolve an RBS class/module ALIAS to its canonical declared name.
|
|
1183
|
-
#
|
|
1184
|
-
# `
|
|
1185
|
-
#
|
|
1186
|
-
#
|
|
1187
|
-
#
|
|
1188
|
-
# resolution) before the `class_decls` guard fixes dispatch AND the
|
|
1189
|
-
# `call.undefined-method` existence check on them. A non-alias name, or
|
|
1190
|
-
# one that does not normalise, is returned unchanged.
|
|
1091
|
+
# Resolve an RBS class/module ALIAS to its canonical declared name. `class Mutex = Thread::Mutex`
|
|
1092
|
+
# lives only in `class_alias_decls`, so `class_known?` reports it (it checks that table) but the
|
|
1093
|
+
# definition builder — which only knows `class_decls` — could not enumerate its methods, leaving alias
|
|
1094
|
+
# classes (`Mutex`, and any `X = Y`) with no resolvable method surface. Normalising via the env (RBS's
|
|
1095
|
+
# own alias resolution) before the `class_decls` guard fixes dispatch AND the `call.undefined-method`
|
|
1096
|
+
# existence check on them. A non-alias name, or one that does not normalise, is returned unchanged.
|
|
1191
1097
|
def canonical_module_name(rbs_name)
|
|
1192
1098
|
return rbs_name unless env.class_alias_decls.key?(rbs_name)
|
|
1193
1099
|
|
|
@@ -1196,14 +1102,12 @@ module Rigor
|
|
|
1196
1102
|
rbs_name
|
|
1197
1103
|
end
|
|
1198
1104
|
|
|
1199
|
-
# Memoised on `@state` (the per-loader store also holding `:env` /
|
|
1200
|
-
#
|
|
1201
|
-
#
|
|
1202
|
-
#
|
|
1203
|
-
#
|
|
1204
|
-
#
|
|
1205
|
-
# on nearly every call-site dispatch, so this was a top allocation
|
|
1206
|
-
# site; caching the immutable result (nil included) removes it.
|
|
1105
|
+
# Memoised on `@state` (the per-loader store also holding `:env` / `:builder`): `RBS::TypeName.parse`
|
|
1106
|
+
# is a pure, deterministic function of the normalised string, and the `RBS::TypeName` it returns is a
|
|
1107
|
+
# frozen value object safe to share across callers (every consumer only reads it —
|
|
1108
|
+
# `env.class_decls.key?` / `builder.build_*`). The same handful of class names are parsed on nearly
|
|
1109
|
+
# every call-site dispatch, so this was a top allocation site; caching the immutable result (nil
|
|
1110
|
+
# included) removes it.
|
|
1207
1111
|
def parse_type_name(name)
|
|
1208
1112
|
s = name.to_s
|
|
1209
1113
|
return nil if s.empty?
|
|
@@ -1225,10 +1129,8 @@ module Rigor
|
|
|
1225
1129
|
return false unless rbs_name
|
|
1226
1130
|
return false if env.nil?
|
|
1227
1131
|
|
|
1228
|
-
# `RBS::Environment#class_decls` after `resolve_type_names`
|
|
1229
|
-
#
|
|
1230
|
-
# them under one map post-resolution. Aliases live in their
|
|
1231
|
-
# own table.
|
|
1132
|
+
# `RBS::Environment#class_decls` after `resolve_type_names` holds entries for both classes AND
|
|
1133
|
+
# modules; the gem unifies them under one map post-resolution. Aliases live in their own table.
|
|
1232
1134
|
env.class_decls.key?(rbs_name) || env.class_alias_decls.key?(rbs_name)
|
|
1233
1135
|
rescue ::RBS::BaseError
|
|
1234
1136
|
false
|