rigortype 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/15-type-protection-coverage.md +6 -4
- data/lib/rigor/analysis/baseline.rb +56 -96
- data/lib/rigor/analysis/buffer_binding.rb +13 -20
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
- data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
- data/lib/rigor/analysis/check_rules.rb +174 -274
- data/lib/rigor/analysis/dependency_recorder.rb +26 -38
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
- data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
- data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
- data/lib/rigor/analysis/diagnostic.rb +48 -75
- data/lib/rigor/analysis/erb_template_detector.rb +13 -18
- data/lib/rigor/analysis/fact_store.rb +12 -22
- data/lib/rigor/analysis/incremental.rb +47 -63
- data/lib/rigor/analysis/incremental_session.rb +74 -100
- data/lib/rigor/analysis/project_scan.rb +12 -23
- data/lib/rigor/analysis/result.rb +3 -5
- data/lib/rigor/analysis/rule_catalog.rb +67 -95
- data/lib/rigor/analysis/run_stats.rb +38 -61
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
- data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
- data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
- data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
- data/lib/rigor/analysis/runner.rb +220 -333
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
- data/lib/rigor/analysis/worker_session.rb +64 -102
- data/lib/rigor/ast/type_node.rb +6 -8
- data/lib/rigor/ast.rb +6 -10
- data/lib/rigor/bleeding_edge.rb +27 -35
- data/lib/rigor/builtins/hkt_builtins.rb +7 -14
- data/lib/rigor/builtins/imported_refinements.rb +106 -149
- data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
- data/lib/rigor/builtins/regex_refinement.rb +32 -43
- data/lib/rigor/builtins/static_return_refinements.rb +57 -81
- data/lib/rigor/cache/descriptor.rb +58 -94
- data/lib/rigor/cache/incremental_snapshot.rb +32 -48
- data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
- data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
- data/lib/rigor/cache/rbs_constant_table.rb +8 -13
- data/lib/rigor/cache/rbs_descriptor.rb +8 -13
- data/lib/rigor/cache/rbs_environment.rb +11 -19
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
- data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
- data/lib/rigor/cache/store.rb +96 -151
- data/lib/rigor/cli/annotate_command.rb +51 -88
- data/lib/rigor/cli/baseline_command.rb +10 -17
- data/lib/rigor/cli/check_command.rb +121 -199
- data/lib/rigor/cli/check_runner_factory.rb +8 -14
- data/lib/rigor/cli/ci_detector.rb +9 -14
- data/lib/rigor/cli/command.rb +8 -14
- data/lib/rigor/cli/coverage_command.rb +19 -26
- data/lib/rigor/cli/coverage_mutation.rb +14 -19
- data/lib/rigor/cli/coverage_report.rb +0 -2
- data/lib/rigor/cli/coverage_scan.rb +7 -11
- data/lib/rigor/cli/diagnostic_formats.rb +43 -62
- data/lib/rigor/cli/diff_command.rb +10 -20
- data/lib/rigor/cli/docs_command.rb +26 -40
- data/lib/rigor/cli/doctor_command.rb +8 -9
- data/lib/rigor/cli/explain_command.rb +4 -7
- data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
- data/lib/rigor/cli/fused_protection_report.rb +6 -8
- data/lib/rigor/cli/lsp_command.rb +11 -19
- data/lib/rigor/cli/mcp_command.rb +4 -6
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
- data/lib/rigor/cli/mutation_protection_report.rb +6 -9
- data/lib/rigor/cli/options.rb +11 -19
- data/lib/rigor/cli/plugin_command.rb +18 -30
- data/lib/rigor/cli/plugins_command.rb +29 -51
- data/lib/rigor/cli/plugins_renderer.rb +12 -20
- data/lib/rigor/cli/prism_colorizer.rb +13 -19
- data/lib/rigor/cli/protection_renderer.rb +5 -7
- data/lib/rigor/cli/protection_report.rb +27 -17
- data/lib/rigor/cli/renderable.rb +4 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
- data/lib/rigor/cli/sig_gen_command.rb +14 -26
- data/lib/rigor/cli/skill_command.rb +30 -47
- data/lib/rigor/cli/skill_describe.rb +40 -64
- data/lib/rigor/cli/trace_command.rb +9 -16
- data/lib/rigor/cli/trace_renderer.rb +35 -51
- data/lib/rigor/cli/triage_command.rb +6 -10
- data/lib/rigor/cli/triage_renderer.rb +1 -2
- data/lib/rigor/cli/type_of_command.rb +14 -23
- data/lib/rigor/cli/type_of_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_command.rb +6 -10
- data/lib/rigor/cli/type_scan_renderer.rb +3 -5
- data/lib/rigor/cli/type_scan_report.rb +2 -3
- data/lib/rigor/cli/upgrade_command.rb +2 -3
- data/lib/rigor/cli.rb +16 -28
- data/lib/rigor/config_audit.rb +35 -44
- data/lib/rigor/configuration/dependencies.rb +41 -70
- data/lib/rigor/configuration/severity_profile.rb +28 -42
- data/lib/rigor/configuration.rb +150 -241
- data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
- data/lib/rigor/environment/class_registry.rb +19 -26
- data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
- data/lib/rigor/environment/lockfile_resolver.rb +31 -47
- data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
- data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
- data/lib/rigor/environment/rbs_loader.rb +420 -518
- data/lib/rigor/environment/reflection.rb +28 -53
- data/lib/rigor/environment/reporters.rb +13 -24
- data/lib/rigor/environment.rb +176 -291
- data/lib/rigor/flow_contribution/conflict.rb +16 -27
- data/lib/rigor/flow_contribution/element.rb +7 -12
- data/lib/rigor/flow_contribution/fact.rb +33 -57
- data/lib/rigor/flow_contribution/merge_result.rb +6 -9
- data/lib/rigor/flow_contribution/merger.rb +32 -47
- data/lib/rigor/flow_contribution.rb +37 -55
- data/lib/rigor/inference/acceptance.rb +133 -219
- data/lib/rigor/inference/block_parameter_binder.rb +52 -82
- data/lib/rigor/inference/body_fixpoint.rb +22 -30
- data/lib/rigor/inference/budget_trace.rb +43 -63
- data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
- data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
- data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
- data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
- data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
- data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
- data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
- data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
- data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
- data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
- data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
- data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
- data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
- data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
- data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
- data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
- data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
- data/lib/rigor/inference/coverage_scanner.rb +13 -18
- data/lib/rigor/inference/def_return_typer.rb +8 -14
- data/lib/rigor/inference/dynamic_origin.rb +23 -20
- data/lib/rigor/inference/expression_typer.rb +774 -1152
- data/lib/rigor/inference/fallback.rb +8 -12
- data/lib/rigor/inference/fallback_tracer.rb +4 -10
- data/lib/rigor/inference/flow_tracer.rb +25 -36
- data/lib/rigor/inference/hkt_body.rb +45 -68
- data/lib/rigor/inference/hkt_body_parser.rb +33 -52
- data/lib/rigor/inference/hkt_reducer.rb +38 -59
- data/lib/rigor/inference/hkt_registry.rb +50 -76
- data/lib/rigor/inference/indexed_narrowing.rb +55 -81
- data/lib/rigor/inference/macro_block_self_type.rb +21 -34
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
- data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
- data/lib/rigor/inference/method_dispatcher.rb +378 -524
- data/lib/rigor/inference/method_parameter_binder.rb +81 -114
- data/lib/rigor/inference/multi_target_binder.rb +51 -68
- data/lib/rigor/inference/mutation_widening.rb +115 -158
- data/lib/rigor/inference/narrowing.rb +509 -727
- data/lib/rigor/inference/origin_lookup.rb +38 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
- data/lib/rigor/inference/precision_scanner.rb +26 -35
- data/lib/rigor/inference/project_patched_methods.rb +20 -32
- data/lib/rigor/inference/project_patched_scanner.rb +23 -37
- data/lib/rigor/inference/protection_scanner.rb +16 -18
- data/lib/rigor/inference/rbs_type_translator.rb +49 -66
- data/lib/rigor/inference/scope_indexer.rb +473 -817
- data/lib/rigor/inference/statement_evaluator.rb +706 -1111
- data/lib/rigor/inference/struct_fold_safety.rb +34 -48
- data/lib/rigor/inference/synthetic_method.rb +9 -16
- data/lib/rigor/inference/synthetic_method_index.rb +20 -35
- data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
- data/lib/rigor/language_server/buffer_resolution.rb +6 -9
- data/lib/rigor/language_server/buffer_table.rb +11 -18
- data/lib/rigor/language_server/completion_provider.rb +69 -116
- data/lib/rigor/language_server/debouncer.rb +16 -25
- data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
- data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
- data/lib/rigor/language_server/folding_range_provider.rb +11 -19
- data/lib/rigor/language_server/hover_provider.rb +8 -11
- data/lib/rigor/language_server/hover_renderer.rb +56 -93
- data/lib/rigor/language_server/loop.rb +14 -19
- data/lib/rigor/language_server/project_context.rb +46 -74
- data/lib/rigor/language_server/selection_range_provider.rb +9 -12
- data/lib/rigor/language_server/server.rb +55 -83
- data/lib/rigor/language_server/signature_help_provider.rb +29 -46
- data/lib/rigor/language_server/synchronized_writer.rb +4 -7
- data/lib/rigor/language_server/uri.rb +8 -13
- data/lib/rigor/language_server.rb +4 -6
- data/lib/rigor/mcp/loop.rb +2 -3
- data/lib/rigor/mcp/server.rb +4 -7
- data/lib/rigor/mcp.rb +3 -6
- data/lib/rigor/plugin/access_denied_error.rb +5 -8
- data/lib/rigor/plugin/additional_initializer.rb +21 -31
- data/lib/rigor/plugin/base.rb +335 -518
- data/lib/rigor/plugin/blueprint.rb +14 -23
- data/lib/rigor/plugin/box.rb +18 -29
- data/lib/rigor/plugin/fact_store.rb +16 -26
- data/lib/rigor/plugin/inflector.rb +37 -53
- data/lib/rigor/plugin/io_boundary.rb +33 -56
- data/lib/rigor/plugin/isolation.rb +42 -55
- data/lib/rigor/plugin/load_error.rb +10 -15
- data/lib/rigor/plugin/loader.rb +30 -49
- data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
- data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
- data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
- data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
- data/lib/rigor/plugin/macro.rb +10 -15
- data/lib/rigor/plugin/manifest.rb +85 -144
- data/lib/rigor/plugin/node_context.rb +14 -22
- data/lib/rigor/plugin/node_rule_walk.rb +49 -74
- data/lib/rigor/plugin/protocol_contract.rb +25 -39
- data/lib/rigor/plugin/registry.rb +132 -205
- data/lib/rigor/plugin/services.rb +21 -33
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
- data/lib/rigor/plugin/trust_policy.rb +24 -38
- data/lib/rigor/plugin/type_node_resolver.rb +15 -24
- data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
- data/lib/rigor/protection/mutation_scanner.rb +27 -35
- data/lib/rigor/protection/mutator.rb +50 -70
- data/lib/rigor/protection/test_suite_oracle.rb +20 -27
- data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
- data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
- data/lib/rigor/rbs_extended/reporter.rb +24 -40
- data/lib/rigor/rbs_extended.rb +107 -197
- data/lib/rigor/reflection.rb +68 -86
- data/lib/rigor/scope/discovery_index.rb +14 -19
- data/lib/rigor/scope.rb +255 -310
- data/lib/rigor/sig_gen/classification.rb +6 -10
- data/lib/rigor/sig_gen/generator.rb +197 -323
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +12 -17
- data/lib/rigor/sig_gen/observation_collector.rb +38 -70
- data/lib/rigor/sig_gen/observed_call.rb +13 -23
- data/lib/rigor/sig_gen/path_mapper.rb +17 -29
- data/lib/rigor/sig_gen/renderer.rb +7 -13
- data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
- data/lib/rigor/sig_gen/write_result.rb +8 -16
- data/lib/rigor/sig_gen/writer.rb +95 -174
- data/lib/rigor/sig_gen.rb +3 -6
- data/lib/rigor/signature_path_audit.rb +24 -30
- data/lib/rigor/source/constant_path.rb +10 -14
- data/lib/rigor/source/literals.rb +31 -45
- data/lib/rigor/source/node_locator.rb +9 -11
- data/lib/rigor/source/node_walker.rb +9 -13
- data/lib/rigor/source.rb +3 -4
- data/lib/rigor/testing.rb +16 -20
- data/lib/rigor/triage/catalogue.rb +38 -62
- data/lib/rigor/triage.rb +31 -52
- data/lib/rigor/trinary.rb +9 -13
- data/lib/rigor/type/acceptance_router.rb +4 -6
- data/lib/rigor/type/accepts_result.rb +10 -14
- data/lib/rigor/type/app.rb +19 -27
- data/lib/rigor/type/bot.rb +4 -6
- data/lib/rigor/type/bound_method.rb +10 -15
- data/lib/rigor/type/combinator.rb +165 -257
- data/lib/rigor/type/constant.rb +23 -34
- data/lib/rigor/type/data_class.rb +10 -15
- data/lib/rigor/type/data_instance.rb +14 -20
- data/lib/rigor/type/difference.rb +21 -32
- data/lib/rigor/type/dynamic.rb +3 -5
- data/lib/rigor/type/hash_shape.rb +32 -18
- data/lib/rigor/type/integer_range.rb +11 -16
- data/lib/rigor/type/intersection.rb +27 -42
- data/lib/rigor/type/nominal.rb +10 -15
- data/lib/rigor/type/plain_lattice.rb +9 -13
- data/lib/rigor/type/refined.rb +67 -114
- data/lib/rigor/type/singleton.rb +4 -6
- data/lib/rigor/type/struct_class.rb +11 -16
- data/lib/rigor/type/struct_instance.rb +15 -21
- data/lib/rigor/type/tuple.rb +14 -19
- data/lib/rigor/type/union.rb +30 -42
- data/lib/rigor/type_node/generic.rb +14 -26
- data/lib/rigor/type_node/identifier.rb +12 -19
- data/lib/rigor/type_node.rb +3 -12
- data/lib/rigor/value_semantics.rb +16 -21
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
- data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
- data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
- data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
- data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
- data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
- data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
- data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
- data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
- data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
- data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
- data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
- data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
- data/sig/rigor/scope.rbs +6 -0
- metadata +2 -1
data/lib/rigor/cache/store.rb
CHANGED
|
@@ -11,51 +11,38 @@ require_relative "descriptor"
|
|
|
11
11
|
|
|
12
12
|
module Rigor
|
|
13
13
|
module Cache
|
|
14
|
-
# Filesystem-backed cache store. Schema, layout, file format,
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# `#
|
|
18
|
-
# caches, ADR-45), `#stats`, `#evict!`, and `.disk_inventory`,
|
|
19
|
-
# plus the [`Rigor::Cache::Descriptor`](descriptor.rb) value object.
|
|
14
|
+
# Filesystem-backed cache store. Schema, layout, file format, atomicity, and locking are fixed by
|
|
15
|
+
# [ADR-6](../../../docs/adr/6-cache-persistence-backend.md); callers use `#fetch_or_compute`
|
|
16
|
+
# (producer-keyed), `#fetch_or_validate` (record-and-validate for discovered-dep caches, ADR-45), `#stats`,
|
|
17
|
+
# `#evict!`, and `.disk_inventory`, plus the [`Rigor::Cache::Descriptor`](descriptor.rb) value object.
|
|
20
18
|
#
|
|
21
|
-
# Read failures (missing file, bad magic, format-version mismatch,
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# catches accidental corruption (partial writes, FS errors); it is
|
|
26
|
-
# **not** a security boundary, per ADR-2's trusted-gem trust model.
|
|
19
|
+
# Read failures (missing file, bad magic, format-version mismatch, corrupt SHA-256 trailer, un-inflatable
|
|
20
|
+
# or unmarshal-able payload) are silently treated as cache misses; the producer block reruns and the next
|
|
21
|
+
# write replaces the bad entry. The trailing SHA-256 catches accidental corruption (partial writes, FS
|
|
22
|
+
# errors); it is **not** a security boundary, per ADR-2's trusted-gem trust model.
|
|
27
23
|
class Store # rubocop:disable Metrics/ClassLength
|
|
28
|
-
# On-disk byte-layout version. Bumped on incompatible format
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
# silent misses; the `schema_version.txt` marker additionally
|
|
36
|
-
# carries this version, so the first writable run after a bump
|
|
37
|
-
# clears the root and reclaims the unreadable bytes.
|
|
24
|
+
# On-disk byte-layout version. Bumped on incompatible format changes (independent of
|
|
25
|
+
# {Descriptor::SCHEMA_VERSION}, which covers the descriptor schema rather than the byte layout). v2
|
|
26
|
+
# (ADR-54 WD2): the value payload is zlib-deflated on write and inflated on read — Marshal blobs
|
|
27
|
+
# compress to 13–16 % at an inflate cost an order of magnitude below their `Marshal.load`. v1 entries
|
|
28
|
+
# fail the header check and read as silent misses; the `schema_version.txt` marker additionally carries
|
|
29
|
+
# this version, so the first writable run after a bump clears the root and reclaims the unreadable
|
|
30
|
+
# bytes.
|
|
38
31
|
FORMAT_VERSION = 2
|
|
39
32
|
|
|
40
|
-
# Header literal: 5-byte ASCII magic, 1-byte separator, 1-byte
|
|
41
|
-
# format version.
|
|
33
|
+
# Header literal: 5-byte ASCII magic, 1-byte separator, 1-byte format version.
|
|
42
34
|
HEADER = "RIGOR\x00#{FORMAT_VERSION.chr}".b.freeze
|
|
43
35
|
|
|
44
36
|
VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
|
|
45
37
|
|
|
46
38
|
# @param root [String] cache root directory.
|
|
47
|
-
# @param read_only [Boolean] when true, every disk-side
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
# populated so repeated lookups within the same run stay
|
|
55
|
-
# cheap. Used by editor mode so multiple buffer-mode
|
|
56
|
-
# invocations can read from the same cache concurrently
|
|
57
|
-
# without churning it. See
|
|
58
|
-
# `docs/design/20260516-editor-mode.md` § "Cache behaviour".
|
|
39
|
+
# @param read_only [Boolean] when true, every disk-side side-effect is suppressed: `fetch_or_compute`
|
|
40
|
+
# still reads existing entries (hits) and still runs the producer block on miss, but it does NOT
|
|
41
|
+
# write the produced value to disk, does NOT update the `schema_version.txt` marker, and does NOT
|
|
42
|
+
# touch the on-disk root directory. The in-process memo is still populated so repeated lookups within
|
|
43
|
+
# the same run stay cheap. Used by editor mode so multiple buffer-mode invocations can read from the
|
|
44
|
+
# same cache concurrently without churning it. See `docs/design/20260516-editor-mode.md` § "Cache
|
|
45
|
+
# behaviour".
|
|
59
46
|
def initialize(root:, read_only: false, max_bytes: nil)
|
|
60
47
|
@root = root.to_s.dup.freeze
|
|
61
48
|
@read_only = read_only
|
|
@@ -65,40 +52,30 @@ module Rigor
|
|
|
65
52
|
@misses = 0
|
|
66
53
|
@writes = 0
|
|
67
54
|
@by_producer = Hash.new { |h, k| h[k] = { hits: 0, misses: 0, writes: 0 } }
|
|
68
|
-
# Process-level in-memory layer keyed by
|
|
69
|
-
# `(
|
|
70
|
-
# `
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# spec process, the LSP daemon's repeated re-check
|
|
74
|
-
# path, and any other "many runs, same project" loop.
|
|
75
|
-
# Keys are content-derived (descriptor digests), so
|
|
76
|
-
# cross-fixture contamination is impossible.
|
|
55
|
+
# Process-level in-memory layer keyed by `(producer_id, cache_key)`. Avoids the disk read +
|
|
56
|
+
# `Marshal.load` cost (the dominant share of repeated cache-hit calls per stackprof) when many
|
|
57
|
+
# short-lived `Analysis::Runner` instances share one `Store` — the spec process, the LSP daemon's
|
|
58
|
+
# repeated re-check path, and any other "many runs, same project" loop. Keys are content-derived
|
|
59
|
+
# (descriptor digests), so cross-fixture contamination is impossible.
|
|
77
60
|
@memo = {}
|
|
78
|
-
# `Analysis::Runner` walks files concurrently (file-
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
# re-entrant so producer blocks can recursively
|
|
83
|
-
# consult the Store (e.g. one cache layer building on
|
|
84
|
-
# another) without dead-locking.
|
|
61
|
+
# `Analysis::Runner` walks files concurrently (file-level parallelism); the per-file workers share
|
|
62
|
+
# one Store. The monitor guards `@memo` + the counter hashes against concurrent writes. The Monitor
|
|
63
|
+
# is re-entrant so producer blocks can recursively consult the Store (e.g. one cache layer building
|
|
64
|
+
# on another) without dead-locking.
|
|
85
65
|
@monitor = Monitor.new
|
|
86
66
|
end
|
|
87
67
|
|
|
88
68
|
attr_reader :root
|
|
89
69
|
|
|
90
|
-
# @return [Boolean] whether this Store suppresses disk writes
|
|
91
|
-
#
|
|
92
|
-
# unaffected.
|
|
70
|
+
# @return [Boolean] whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
|
|
71
|
+
# Reads are unaffected.
|
|
93
72
|
def read_only?
|
|
94
73
|
@read_only
|
|
95
74
|
end
|
|
96
75
|
|
|
97
|
-
# Returns a frozen snapshot of this Store's per-run hit / miss /
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
# against this specific instance rather than the on-disk cache
|
|
101
|
-
# state. Disk-level state is reported separately by
|
|
76
|
+
# Returns a frozen snapshot of this Store's per-run hit / miss / write counters. The bookkeeping is
|
|
77
|
+
# in-memory only — every new `Store.new` starts at zero — so the counters reflect activity against this
|
|
78
|
+
# specific instance rather than the on-disk cache state. Disk-level state is reported separately by
|
|
102
79
|
# {.disk_inventory}.
|
|
103
80
|
#
|
|
104
81
|
# @return [Hash] `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
|
|
@@ -109,26 +86,19 @@ module Rigor
|
|
|
109
86
|
end
|
|
110
87
|
end
|
|
111
88
|
|
|
112
|
-
# Walks the on-disk cache rooted at `root` and reports a
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# depending on in-process counters (which only reflect the
|
|
116
|
-
# current run).
|
|
89
|
+
# Walks the on-disk cache rooted at `root` and reports a producer-level inventory. Used by
|
|
90
|
+
# `rigor check --cache-stats` to surface cache size and per-producer entry counts without depending on
|
|
91
|
+
# in-process counters (which only reflect the current run).
|
|
117
92
|
#
|
|
118
|
-
# @return [Hash] `{ root:, schema_version:, total_entries:,
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
# marker, `schema_version` is nil and the producer list is
|
|
122
|
-
# empty.
|
|
93
|
+
# @return [Hash] `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
|
|
94
|
+
# bytes: }, ...] }`. When the root does not exist or has no schema-version marker, `schema_version` is
|
|
95
|
+
# nil and the producer list is empty.
|
|
123
96
|
#
|
|
124
|
-
# The `schema_version.txt` marker content. Covers BOTH
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# below the eviction cap forever. Folding the format version
|
|
130
|
-
# into the marker routes the bump through the established
|
|
131
|
-
# clear-the-root path instead.
|
|
97
|
+
# The `schema_version.txt` marker content. Covers BOTH invalidation axes: the descriptor schema and the
|
|
98
|
+
# on-disk byte layout ({FORMAT_VERSION}, ADR-54). A format bump leaves the old entries permanently
|
|
99
|
+
# unreadable (header mismatch → miss) but, alone, would never reclaim their bytes — they can sit below
|
|
100
|
+
# the eviction cap forever. Folding the format version into the marker routes the bump through the
|
|
101
|
+
# established clear-the-root path instead.
|
|
132
102
|
def self.schema_marker_value
|
|
133
103
|
"#{Descriptor::SCHEMA_VERSION}.#{FORMAT_VERSION}"
|
|
134
104
|
end
|
|
@@ -166,31 +136,21 @@ module Rigor
|
|
|
166
136
|
end
|
|
167
137
|
private_class_method :collect_producers
|
|
168
138
|
|
|
169
|
-
# @param producer_id [String] stable cache namespace; only
|
|
170
|
-
#
|
|
171
|
-
# @param
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
#
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
# reads with one strategy and writes with another corrupts
|
|
182
|
-
# its own cache slice.
|
|
183
|
-
# @param deserialize [#call, nil] optional callable that turns
|
|
184
|
-
# bytes back into the producer's value. Defaults to
|
|
185
|
-
# `Marshal.load`. Any exception (`StandardError`) raised by
|
|
186
|
-
# the deserialiser is treated as a cache miss — the entry is
|
|
187
|
-
# considered corrupt, the producer block reruns, and the
|
|
188
|
-
# next write overwrites it. This is consistent with the
|
|
189
|
-
# fault-tolerance contract for the default `Marshal.load`
|
|
139
|
+
# @param producer_id [String] stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
|
|
140
|
+
# @param params [Hash] producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
|
|
141
|
+
# @param descriptor [Rigor::Cache::Descriptor] the invalidation descriptor for the value being cached.
|
|
142
|
+
# @param serialize [#call, nil] optional callable that turns the producer's return value into a binary
|
|
143
|
+
# `String`. Defaults to `Marshal.dump(value).b`. Producers whose return values are not
|
|
144
|
+
# `Marshal`-clean (RBS-native objects with `RBS::Location` members, raw `IO`, …) MUST provide a
|
|
145
|
+
# serialiser. The pair `(serialize, deserialize)` MUST round-trip — a producer that reads with one
|
|
146
|
+
# strategy and writes with another corrupts its own cache slice.
|
|
147
|
+
# @param deserialize [#call, nil] optional callable that turns bytes back into the producer's value.
|
|
148
|
+
# Defaults to `Marshal.load`. Any exception (`StandardError`) raised by the deserialiser is treated as
|
|
149
|
+
# a cache miss — the entry is considered corrupt, the producer block reruns, and the next write
|
|
150
|
+
# overwrites it. This is consistent with the fault-tolerance contract for the default `Marshal.load`
|
|
190
151
|
# path.
|
|
191
152
|
# @yieldreturn the value to cache.
|
|
192
|
-
# @return the cached value (loaded from disk on hit; produced by
|
|
193
|
-
# the block on miss).
|
|
153
|
+
# @return the cached value (loaded from disk on hit; produced by the block on miss).
|
|
194
154
|
def fetch_or_compute(producer_id:, params:, descriptor:,
|
|
195
155
|
serialize: nil, deserialize: nil, &block)
|
|
196
156
|
validate_producer_id!(producer_id)
|
|
@@ -224,19 +184,15 @@ module Rigor
|
|
|
224
184
|
value
|
|
225
185
|
end
|
|
226
186
|
|
|
227
|
-
# ADR-45 — record-and-validate variant. Unlike {fetch_or_compute},
|
|
228
|
-
#
|
|
229
|
-
# known
|
|
230
|
-
#
|
|
231
|
-
#
|
|
232
|
-
# including inputs discovered DURING the computation (e.g. a plugin
|
|
233
|
-
# reading a file mid-analysis). On the next run the stored
|
|
234
|
-
# dependencies are re-validated against the filesystem
|
|
187
|
+
# ADR-45 — record-and-validate variant. Unlike {fetch_or_compute}, which keys the entry on its
|
|
188
|
+
# descriptor (so the inputs MUST be known before running), this keys on `key_descriptor` (the stable
|
|
189
|
+
# inputs known up front) and stores, alongside the value, a `dependency_descriptor` of the files the
|
|
190
|
+
# value actually read — including inputs discovered DURING the computation (e.g. a plugin reading a
|
|
191
|
+
# file mid-analysis). On the next run the stored dependencies are re-validated against the filesystem
|
|
235
192
|
# ({Descriptor#fresh?}); a stale dependency forces a recompute.
|
|
236
193
|
#
|
|
237
|
-
# The block MUST return `[value, dependency_descriptor]`. Disk reads
|
|
238
|
-
#
|
|
239
|
-
# filesystem — but a single run only looks up once.
|
|
194
|
+
# The block MUST return `[value, dependency_descriptor]`. Disk reads are not in-process-memoised —
|
|
195
|
+
# validation always re-checks the filesystem — but a single run only looks up once.
|
|
240
196
|
def fetch_or_validate(producer_id:, key_descriptor:, params: {}, serialize: nil, deserialize: nil)
|
|
241
197
|
validate_producer_id!(producer_id)
|
|
242
198
|
ensure_schema_version!
|
|
@@ -253,9 +209,8 @@ module Rigor
|
|
|
253
209
|
value, dependency_descriptor = block_given? ? yield : [nil, Descriptor.new]
|
|
254
210
|
wrote = false
|
|
255
211
|
unless @read_only
|
|
256
|
-
# A cache write must never break the run. If the value is not
|
|
257
|
-
#
|
|
258
|
-
# return the freshly-computed value — the next run recomputes.
|
|
212
|
+
# A cache write must never break the run. If the value is not Marshal-clean (or any disk error
|
|
213
|
+
# occurs) skip caching and return the freshly-computed value — the next run recomputes.
|
|
259
214
|
begin
|
|
260
215
|
write_entry(path, key_descriptor, [value, dependency_descriptor], serialize: serialize)
|
|
261
216
|
wrote = true
|
|
@@ -270,14 +225,12 @@ module Rigor
|
|
|
270
225
|
value
|
|
271
226
|
end
|
|
272
227
|
|
|
273
|
-
# ADR-6 § "Eviction" — LRU pass over the on-disk cache. No-op when
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# updates the mtime so recently-read entries survive the eviction pass.
|
|
280
|
-
# Any FS error is swallowed — eviction must never break a run.
|
|
228
|
+
# ADR-6 § "Eviction" — LRU pass over the on-disk cache. No-op when `max_bytes:` was not configured or
|
|
229
|
+
# the store is read-only. Walks all `.entry` files, sorts by mtime ascending (oldest = least recently
|
|
230
|
+
# used), and unlinks from the oldest until the total is at or below the cap. Touch-on-disk-read
|
|
231
|
+
# ({read_entry}) is the cross-process LRU signal: every disk hit (not in-process-memo hit) updates the
|
|
232
|
+
# mtime so recently-read entries survive the eviction pass. Any FS error is swallowed — eviction must
|
|
233
|
+
# never break a run.
|
|
281
234
|
def evict!
|
|
282
235
|
return if @max_bytes.nil? || @read_only
|
|
283
236
|
|
|
@@ -324,9 +277,8 @@ module Rigor
|
|
|
324
277
|
File.join(@root, producer_id, key[0, 2], "#{key[2..]}.entry")
|
|
325
278
|
end
|
|
326
279
|
|
|
327
|
-
# Reads and validates one entry file. Any failure (missing,
|
|
328
|
-
#
|
|
329
|
-
# returns nil so the caller treats it as a cache miss.
|
|
280
|
+
# Reads and validates one entry file. Any failure (missing, short, bad magic, bad version, bad
|
|
281
|
+
# checksum, unmarshal-able) returns nil so the caller treats it as a cache miss.
|
|
330
282
|
def read_entry(path, deserialize: nil)
|
|
331
283
|
return nil unless File.file?(path)
|
|
332
284
|
|
|
@@ -344,8 +296,8 @@ module Rigor
|
|
|
344
296
|
Entry.new(descriptor_bytes, value)
|
|
345
297
|
end
|
|
346
298
|
|
|
347
|
-
# Validates the magic + format-version header and the trailing
|
|
348
|
-
#
|
|
299
|
+
# Validates the magic + format-version header and the trailing SHA-256 over everything before the
|
|
300
|
+
# trailer.
|
|
349
301
|
def envelope_valid?(bytes)
|
|
350
302
|
return false if bytes.bytesize < HEADER.bytesize + 32
|
|
351
303
|
return false unless bytes.byteslice(0, HEADER.bytesize) == HEADER
|
|
@@ -354,8 +306,8 @@ module Rigor
|
|
|
354
306
|
Digest::SHA256.digest(bytes.byteslice(0, bytes.bytesize - 32)) == trailer
|
|
355
307
|
end
|
|
356
308
|
|
|
357
|
-
# Splits the body into (descriptor_bytes, value_bytes). Returns
|
|
358
|
-
#
|
|
309
|
+
# Splits the body into (descriptor_bytes, value_bytes). Returns `[nil, nil]` on a malformed varint or
|
|
310
|
+
# length-overrun.
|
|
359
311
|
def parse_body(body)
|
|
360
312
|
offset = 0
|
|
361
313
|
descriptor_len, offset = read_varint(body, offset)
|
|
@@ -374,9 +326,8 @@ module Rigor
|
|
|
374
326
|
LOAD_FAILED = Object.new.freeze
|
|
375
327
|
private_constant :LOAD_FAILED
|
|
376
328
|
|
|
377
|
-
# Inflates the stored value payload (ADR-54 WD2), then hands the
|
|
378
|
-
#
|
|
379
|
-
# stream included — reads as a miss.
|
|
329
|
+
# Inflates the stored value payload (ADR-54 WD2), then hands the raw bytes to the deserialiser. Any
|
|
330
|
+
# failure — corrupt deflate stream included — reads as a miss.
|
|
380
331
|
def safe_load(bytes, deserialize)
|
|
381
332
|
raw = Zlib::Inflate.inflate(bytes)
|
|
382
333
|
if deserialize
|
|
@@ -430,17 +381,13 @@ module Rigor
|
|
|
430
381
|
end
|
|
431
382
|
|
|
432
383
|
def ensure_schema_version!
|
|
433
|
-
# Read-only stores never touch the cache root — no mkdir,
|
|
434
|
-
#
|
|
435
|
-
#
|
|
436
|
-
#
|
|
437
|
-
# are content-keyed, so a write under the new schema
|
|
438
|
-
# never collides with a read under the old). The next
|
|
439
|
-
# writable run will repair the cache.
|
|
384
|
+
# Read-only stores never touch the cache root — no mkdir, no marker write, no destructive clear on
|
|
385
|
+
# schema mismatch. A stale or wrong-schema marker simply yields nothing back (entries read through
|
|
386
|
+
# the version check are content-keyed, so a write under the new schema never collides with a read
|
|
387
|
+
# under the old). The next writable run will repair the cache.
|
|
440
388
|
return if @read_only
|
|
441
|
-
# The marker is process-stable; one check per Store is
|
|
442
|
-
#
|
|
443
|
-
# repeats idempotent work).
|
|
389
|
+
# The marker is process-stable; one check per Store is enough (a benign double-check under a thread
|
|
390
|
+
# race just repeats idempotent work).
|
|
444
391
|
return if @schema_version_ensured
|
|
445
392
|
|
|
446
393
|
@schema_version_ensured = true
|
|
@@ -465,9 +412,8 @@ module Rigor
|
|
|
465
412
|
end
|
|
466
413
|
end
|
|
467
414
|
|
|
468
|
-
# LEB128 unsigned varint encoder/decoder. Lengths fit easily in
|
|
469
|
-
#
|
|
470
|
-
# larger than that in practice.
|
|
415
|
+
# LEB128 unsigned varint encoder/decoder. Lengths fit easily in five bytes (cap at 2^35); the cache
|
|
416
|
+
# layer never writes a value larger than that in practice.
|
|
471
417
|
def write_varint(bytes, value)
|
|
472
418
|
raise ArgumentError, "varint must be non-negative" if value.negative?
|
|
473
419
|
|
|
@@ -482,9 +428,8 @@ module Rigor
|
|
|
482
428
|
end
|
|
483
429
|
end
|
|
484
430
|
|
|
485
|
-
# Updates both atime and mtime of `path` to the current time — the
|
|
486
|
-
#
|
|
487
|
-
# error (read-only mount, deleted file) is silently ignored.
|
|
431
|
+
# Updates both atime and mtime of `path` to the current time — the cross-process LRU signal used by
|
|
432
|
+
# {evict!}. Best-effort: any FS error (read-only mount, deleted file) is silently ignored.
|
|
488
433
|
def touch_for_lru(path)
|
|
489
434
|
now = Time.now
|
|
490
435
|
File.utime(now, now, path)
|
|
@@ -492,8 +437,8 @@ module Rigor
|
|
|
492
437
|
nil
|
|
493
438
|
end
|
|
494
439
|
|
|
495
|
-
# Returns an array of `{ path:, mtime:, bytes: }` hashes for every
|
|
496
|
-
#
|
|
440
|
+
# Returns an array of `{ path:, mtime:, bytes: }` hashes for every `.entry` file under the cache root,
|
|
441
|
+
# skipping unreadable entries.
|
|
497
442
|
def collect_entry_stats
|
|
498
443
|
Dir.glob(File.join(@root, "**", "*.entry")).filter_map do |path|
|
|
499
444
|
stat = File.stat(path)
|
|
@@ -19,37 +19,26 @@ module Rigor
|
|
|
19
19
|
class CLI
|
|
20
20
|
# Executes `rigor annotate FILE`.
|
|
21
21
|
#
|
|
22
|
-
# For every source line the command finds the expression the
|
|
23
|
-
# line
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# header reports its condition). It infers that expression's
|
|
27
|
-
# type and appends a `#=> <type>` comment (the xmpfilter /
|
|
28
|
-
# seeing_is_believing convention).
|
|
22
|
+
# For every source line the command finds the expression the line evaluates to — the last statement that ends on the
|
|
23
|
+
# line (so `1; 2; 3` reports `3`), or, for a line that no statement closes, the widest expression ending there (so
|
|
24
|
+
# the `if nil` header reports its condition). It infers that expression's type and appends a `#=> <type>` comment
|
|
25
|
+
# (the xmpfilter / seeing_is_believing convention).
|
|
29
26
|
#
|
|
30
|
-
# The annotated source is re-parsed with Prism — a sanity gate,
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# (https://github.com/sharkdp/bat) is on PATH it is used for
|
|
34
|
-
# highlighting; otherwise IRB-style highlighting via
|
|
35
|
-
# {PrismColorizer}.
|
|
27
|
+
# The annotated source is re-parsed with Prism — a sanity gate, since the appended text is always a comment — and
|
|
28
|
+
# printed to stdout. When colour is enabled and `bat` (https://github.com/sharkdp/bat) is on PATH it is used for
|
|
29
|
+
# highlighting; otherwise IRB-style highlighting via {PrismColorizer}.
|
|
36
30
|
class AnnotateCommand < Command
|
|
37
31
|
USAGE = "Usage: rigor annotate [options] FILE"
|
|
38
32
|
|
|
39
|
-
# Trailing `#=> …` annotation comment. Matched and stripped
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
# (idempotency across re-runs). The leading `\s` requirement
|
|
44
|
-
# keeps a `#=>` inside a string literal (no preceding
|
|
45
|
-
# whitespace ambiguity aside) from matching mid-expression.
|
|
33
|
+
# Trailing `#=> …` annotation comment. Matched and stripped before re-annotating so re-running is idempotent —
|
|
34
|
+
# this follows xmpfilter's convention of owning the `#=>` marker, and also absorbs the older `#=> dump_type:
|
|
35
|
+
# <type>` spelling (idempotency across re-runs). The leading `\s` requirement keeps a `#=>` inside a string
|
|
36
|
+
# literal (no preceding whitespace ambiguity aside) from matching mid-expression.
|
|
46
37
|
ANNOTATION_PATTERN = /\s+#=>(?:\s.*)?\z/
|
|
47
38
|
|
|
48
|
-
# Arguments for highlighting through `bat`: the annotated
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# matches the PrismColorizer fallback line-for-line; paging
|
|
52
|
-
# stays off because the CLI may itself sit in a pipeline.
|
|
39
|
+
# Arguments for highlighting through `bat`: the annotated text arrives on stdin, so the language must be explicit;
|
|
40
|
+
# `--style=plain` drops the grid/header chrome so the output matches the PrismColorizer fallback line-for-line;
|
|
41
|
+
# paging stays off because the CLI may itself sit in a pipeline.
|
|
53
42
|
BAT_ARGS = %w[--language=ruby --style=plain --paging=never --color=always].freeze
|
|
54
43
|
|
|
55
44
|
# @return [Integer] CLI exit status.
|
|
@@ -71,8 +60,7 @@ module Rigor
|
|
|
71
60
|
private
|
|
72
61
|
|
|
73
62
|
def parse_options
|
|
74
|
-
# Default: colour a tty, unless `NO_COLOR` opts out. An
|
|
75
|
-
# explicit `--color` / `--no-color` overrides both.
|
|
63
|
+
# Default: colour a tty, unless `NO_COLOR` opts out. An explicit `--color` / `--no-color` overrides both.
|
|
76
64
|
options = { config: nil, color: @out.tty? && !no_color_env?, bat: nil, format: :text }
|
|
77
65
|
|
|
78
66
|
parser = OptionParser.new do |opts|
|
|
@@ -96,9 +84,8 @@ module Rigor
|
|
|
96
84
|
options
|
|
97
85
|
end
|
|
98
86
|
|
|
99
|
-
# https://no-color.org — colour output is suppressed by
|
|
100
|
-
#
|
|
101
|
-
# regardless of its value.
|
|
87
|
+
# https://no-color.org — colour output is suppressed by default when `NO_COLOR` is present and not an empty
|
|
88
|
+
# string, regardless of its value.
|
|
102
89
|
def no_color_env?
|
|
103
90
|
value = ENV.fetch("NO_COLOR", nil)
|
|
104
91
|
!value.nil? && !value.empty?
|
|
@@ -106,22 +93,17 @@ module Rigor
|
|
|
106
93
|
|
|
107
94
|
def execute(file, options)
|
|
108
95
|
configuration = Configuration.load(options.fetch(:config))
|
|
109
|
-
# Force UTF-8 (with BOM tolerance) regardless of
|
|
110
|
-
# `
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# `String#lines` calls in `#annotate` raise
|
|
114
|
-
# `invalid byte sequence in US-ASCII`. Ruby source is
|
|
115
|
-
# UTF-8 by convention (the parser's own assumption
|
|
116
|
-
# absent a magic comment).
|
|
96
|
+
# Force UTF-8 (with BOM tolerance) regardless of `Encoding.default_external`. Under a minimal locale the default
|
|
97
|
+
# is US-ASCII; reading multi-byte source under that tag makes the post-parse `String#sub` / `String#lines` calls
|
|
98
|
+
# in `#annotate` raise `invalid byte sequence in US-ASCII`. Ruby source is UTF-8 by convention (the parser's own
|
|
99
|
+
# assumption absent a magic comment).
|
|
117
100
|
source = File.read(file, mode: "r:bom|utf-8")
|
|
118
101
|
parse_result = Prism.parse(source, filepath: file, version: configuration.target_ruby)
|
|
119
102
|
return 1 if parse_errors?(parse_result, file)
|
|
120
103
|
|
|
121
|
-
# `converged_loop_recording` re-records fixpoint-tracked loop
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
# rather than a stale first-iterations constant (`1 | 2`).
|
|
104
|
+
# `converged_loop_recording` re-records fixpoint-tracked loop bodies from their converged (post-writeback)
|
|
105
|
+
# bindings, so a loop-body line annotates the joined widened type (`Integer`) rather than a stale
|
|
106
|
+
# first-iterations constant (`1 | 2`).
|
|
125
107
|
scope_index = Inference::ScopeIndexer.index(
|
|
126
108
|
parse_result.value, default_scope: base_scope(configuration),
|
|
127
109
|
converged_loop_recording: true
|
|
@@ -137,11 +119,10 @@ module Rigor
|
|
|
137
119
|
0
|
|
138
120
|
end
|
|
139
121
|
|
|
140
|
-
# `--format json` — emit the { line_number => type } map directly, the
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
# also shows; keys are 1-based line numbers as strings (JSON object keys).
|
|
122
|
+
# `--format json` — emit the { line_number => type } map directly, the same data the text renderer consumes, so
|
|
123
|
+
# clients (the playground, editors) get structured annotations without reparsing the `#=> <type>` comment grammar.
|
|
124
|
+
# Values are the short type descriptions the text form also shows; keys are 1-based line numbers as strings (JSON
|
|
125
|
+
# object keys).
|
|
145
126
|
def emit_json(line_types)
|
|
146
127
|
annotations = line_types.keys.sort.to_h { |line| [line.to_s, line_types[line].describe(:short)] }
|
|
147
128
|
@out.puts(JSON.generate({ "annotations" => annotations }))
|
|
@@ -165,8 +146,7 @@ module Rigor
|
|
|
165
146
|
true
|
|
166
147
|
end
|
|
167
148
|
|
|
168
|
-
# Appends ` #=> <type>` to every line a type was inferred
|
|
169
|
-
# for, aligning the comment column.
|
|
149
|
+
# Appends ` #=> <type>` to every line a type was inferred for, aligning the comment column.
|
|
170
150
|
def annotate(source, line_types)
|
|
171
151
|
lines = source.lines
|
|
172
152
|
column = annotation_column(lines, line_types)
|
|
@@ -198,10 +178,8 @@ module Rigor
|
|
|
198
178
|
rendered || PrismColorizer.colorize(annotated)
|
|
199
179
|
end
|
|
200
180
|
|
|
201
|
-
# Pipes the annotated source through `bat` and returns its
|
|
202
|
-
#
|
|
203
|
-
# fails (broken install, killed mid-write) — the caller
|
|
204
|
-
# falls back to {PrismColorizer}. An explicit `--bat` with
|
|
181
|
+
# Pipes the annotated source through `bat` and returns its highlighted output, or nil when bat is unavailable or
|
|
182
|
+
# fails (broken install, killed mid-write) — the caller falls back to {PrismColorizer}. An explicit `--bat` with
|
|
205
183
|
# no bat on PATH warns instead of failing silently.
|
|
206
184
|
def render_with_bat(annotated, forced: nil)
|
|
207
185
|
executable = bat_executable
|
|
@@ -233,9 +211,8 @@ module Rigor
|
|
|
233
211
|
end
|
|
234
212
|
end
|
|
235
213
|
|
|
236
|
-
# Walks a parsed program and resolves, per source line, the
|
|
237
|
-
#
|
|
238
|
-
# {AnnotateCommand}.
|
|
214
|
+
# Walks a parsed program and resolves, per source line, the type of the expression the line evaluates to. Used only
|
|
215
|
+
# by {AnnotateCommand}.
|
|
239
216
|
class LineTypeCollector
|
|
240
217
|
def initialize(scope_index)
|
|
241
218
|
@scope_index = scope_index
|
|
@@ -256,16 +233,11 @@ module Rigor
|
|
|
256
233
|
|
|
257
234
|
private
|
|
258
235
|
|
|
259
|
-
# Yields each statement node (a child of any `StatementsNode`
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
# `
|
|
263
|
-
#
|
|
264
|
-
# wins — for `b = if cond then :then else :else end` the
|
|
265
|
-
# line resolves to the assignment's type (the if-expression's
|
|
266
|
-
# union), not the else-branch's inner `:else`. Direct siblings
|
|
267
|
-
# (`1; 2; 3`) are still yielded in source order so the last
|
|
268
|
-
# sibling wins.
|
|
236
|
+
# Yields each statement node (a child of any `StatementsNode` anywhere in the tree) in post-order: nested
|
|
237
|
+
# statements are yielded before the enclosing statement that contains them. `by_line[end_line] = type` overwrites
|
|
238
|
+
# earlier entries, so post-order means the *outermost* statement closing a line wins — for `b = if cond then :then
|
|
239
|
+
# else :else end` the line resolves to the assignment's type (the if-expression's union), not the else-branch's
|
|
240
|
+
# inner `:else`. Direct siblings (`1; 2; 3`) are still yielded in source order so the last sibling wins.
|
|
269
241
|
def each_statement(node, &block)
|
|
270
242
|
return if node.nil?
|
|
271
243
|
|
|
@@ -279,8 +251,7 @@ module Rigor
|
|
|
279
251
|
end
|
|
280
252
|
end
|
|
281
253
|
|
|
282
|
-
# For a line no statement closes (the `if` / block header
|
|
283
|
-
# lines), fall back to the widest expression ending there.
|
|
254
|
+
# For a line no statement closes (the `if` / block header lines), fall back to the widest expression ending there.
|
|
284
255
|
def fill_uncovered_lines(program, by_line)
|
|
285
256
|
widest_per_line(program).each do |line, node|
|
|
286
257
|
next if by_line.key?(line)
|
|
@@ -290,12 +261,10 @@ module Rigor
|
|
|
290
261
|
end
|
|
291
262
|
end
|
|
292
263
|
|
|
293
|
-
# A `do |i|` header line's widest node is its BlockParametersNode —
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
#
|
|
297
|
-
# for multi-param blocks); decline (nil) when any param has no
|
|
298
|
-
# plain name or no recorded binding, leaving the line bare.
|
|
264
|
+
# A `do |i|` header line's widest node is its BlockParametersNode — not an expression, so evaluating it would only
|
|
265
|
+
# echo the `Dynamic[top]` fallback. Annotate the line with the parameters' inferred bindings instead (the single
|
|
266
|
+
# param's type, or a tuple for multi-param blocks); decline (nil) when any param has no plain name or no recorded
|
|
267
|
+
# binding, leaving the line bare.
|
|
299
268
|
def block_params_type(params_node)
|
|
300
269
|
inner = params_node.parameters
|
|
301
270
|
return nil if inner.nil? || inner.requireds.empty?
|
|
@@ -332,26 +301,20 @@ module Rigor
|
|
|
332
301
|
node.compact_child_nodes.each { |child| walk(child, &block) }
|
|
333
302
|
end
|
|
334
303
|
|
|
335
|
-
# Types the node through the flow evaluator (not the bare
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
# `i`'s binding (`Integer`, post-fixpoint) instead of echoing the
|
|
339
|
-
# RHS literal's `1`.
|
|
304
|
+
# Types the node through the flow evaluator (not the bare expression typer) under its recorded entry scope, so
|
|
305
|
+
# flow-only forms type as the engine sees them — `i += 1` dispatches `+` on `i`'s binding (`Integer`,
|
|
306
|
+
# post-fixpoint) instead of echoing the RHS literal's `1`.
|
|
340
307
|
def type_of(node)
|
|
341
308
|
Inference::StatementEvaluator.new(scope: @scope_index[node]).evaluate(node).first
|
|
342
309
|
rescue StandardError
|
|
343
310
|
nil
|
|
344
311
|
end
|
|
345
312
|
|
|
346
|
-
# For every `def`, replace the annotation on the header line
|
|
347
|
-
#
|
|
348
|
-
# return type
|
|
349
|
-
#
|
|
350
|
-
#
|
|
351
|
-
# to the method signature. When the return type cannot be
|
|
352
|
-
# inferred (empty body, scope-lookup miss, or any error
|
|
353
|
-
# under `DefReturnTyper.call`), the entry is deleted so no
|
|
354
|
-
# annotation is shown on that line.
|
|
313
|
+
# For every `def`, replace the annotation on the header line (where the `def` keyword sits) with the method's
|
|
314
|
+
# inferred return type. The default annotation there comes from the parameter list (`name` typing as
|
|
315
|
+
# `Dynamic[top]`), which is noise; the return type is what readers actually want next to the method signature.
|
|
316
|
+
# When the return type cannot be inferred (empty body, scope-lookup miss, or any error under
|
|
317
|
+
# `DefReturnTyper.call`), the entry is deleted so no annotation is shown on that line.
|
|
355
318
|
def override_def_header_lines(program, by_line)
|
|
356
319
|
each_def_node(program) do |def_node|
|
|
357
320
|
line = def_node.location.start_line
|