rigortype 0.2.6 → 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 +4 -3
- data/docs/manual/02-cli-reference.md +2 -1
- data/docs/manual/04-diagnostics.md +7 -4
- data/docs/manual/06-baseline.md +5 -2
- data/docs/manual/08-skills.md +21 -0
- 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 +45 -20
- 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 +70 -36
- 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 +433 -503
- 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 -1115
- 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 +208 -309
- data/lib/rigor/sig_gen/layout_index.rb +12 -20
- data/lib/rigor/sig_gen/method_candidate.rb +15 -15
- 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 +141 -173
- 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 +107 -83
- 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 -236
- 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 +20 -36
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +23 -30
- 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 +182 -328
- 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
- data/skills/rigor-ask/SKILL.md +21 -1
- data/skills/rigor-baseline-reduce/SKILL.md +16 -0
- data/skills/rigor-ci-setup/SKILL.md +96 -249
- data/skills/rigor-doctor/SKILL.md +39 -49
- data/skills/rigor-doctor/references/01-checks.md +52 -0
- data/skills/rigor-editor-setup/SKILL.md +14 -0
- data/skills/rigor-mcp-setup/SKILL.md +14 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
- data/skills/rigor-plugin-author/SKILL.md +16 -0
- data/skills/rigor-plugin-review/SKILL.md +174 -0
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
- data/skills/rigor-plugin-tune/SKILL.md +21 -2
- data/skills/rigor-project-init/SKILL.md +16 -0
- data/skills/rigor-protection-uplift/SKILL.md +15 -0
- data/skills/rigor-rbs-setup/SKILL.md +15 -0
- data/skills/rigor-upgrade/SKILL.md +16 -0
- metadata +8 -4
|
@@ -14,46 +14,33 @@ require_relative "sorbet/sigil_detector"
|
|
|
14
14
|
|
|
15
15
|
module Rigor
|
|
16
16
|
module Plugin
|
|
17
|
-
# rigor-sorbet — ingests Sorbet `sig { ... }` blocks as
|
|
18
|
-
#
|
|
17
|
+
# rigor-sorbet — ingests Sorbet `sig { ... }` blocks as method-signature contributions to Rigor's
|
|
18
|
+
# analyzer.
|
|
19
19
|
#
|
|
20
20
|
# ADR-11 slice 1 — first deliverable. Recognises:
|
|
21
21
|
#
|
|
22
|
-
# - `sig { params(x: Integer).returns(String) }` above a
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# - `class Foo` / `module Foo::Bar` / `class << self`
|
|
29
|
-
# nesting; `def self.foo` is recognised as a singleton
|
|
30
|
-
# method.
|
|
22
|
+
# - `sig { params(x: Integer).returns(String) }` above a `def foo(x)` definition, contributing the
|
|
23
|
+
# parsed return type at every call site.
|
|
24
|
+
# - The `void` terminus and the `abstract` / `override` / `overridable` / `final` modifiers (recorded
|
|
25
|
+
# on the {MethodSignature} for slice ≥2).
|
|
26
|
+
# - `class Foo` / `module Foo::Bar` / `class << self` nesting; `def self.foo` is recognised as a
|
|
27
|
+
# singleton method.
|
|
31
28
|
#
|
|
32
|
-
# The {TypeTranslator} table documents coverage. Most of
|
|
33
|
-
#
|
|
34
|
-
# `T::Struct` subclasses, `T.attached_class`, etc.) degrade
|
|
35
|
-
# silently to `Dynamic[top]`.
|
|
29
|
+
# The {TypeTranslator} table documents coverage. Most of Sorbet's vocabulary translates; remaining
|
|
30
|
+
# gaps (`T.proc`, `T::Struct` subclasses, `T.attached_class`, etc.) degrade silently to `Dynamic[top]`.
|
|
36
31
|
#
|
|
37
|
-
# Architecture: per-run `Catalog` is built lazily on first
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# `
|
|
45
|
-
# `(class_name, method_name, kind)`. When a key collides
|
|
46
|
-
# across files, the last-walked sig wins (ordering is
|
|
47
|
-
# platform-dependent: `Dir.glob` returns directory entries
|
|
48
|
-
# in filesystem order). Sorbet's full shim-override
|
|
49
|
-
# semantics — `sorbet/rbi/shims/` overriding
|
|
50
|
-
# `sorbet/rbi/gems/` — lands in a later slice once the
|
|
51
|
-
# catalog gains per-source provenance.
|
|
32
|
+
# Architecture: per-run `Catalog` is built lazily on first access by walking every configured `paths:`
|
|
33
|
+
# entry's `.rb` files plus every `rbi_paths:` entry's `.rbi` files (slice 4) via the plugin's
|
|
34
|
+
# `IoBoundary`. The catalog is frozen after the first build and consulted by the `dynamic_return` rule
|
|
35
|
+
# at every gated call site. RBI files share the catalog with project-source sigs — both produce
|
|
36
|
+
# `MethodSignature` entries keyed by `(class_name, method_name, kind)`. When a key collides across
|
|
37
|
+
# files, the last-walked sig wins (ordering is platform-dependent: `Dir.glob` returns directory
|
|
38
|
+
# entries in filesystem order). Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
|
|
39
|
+
# `sorbet/rbi/gems/` — lands in a later slice once the catalog gains per-source provenance.
|
|
52
40
|
#
|
|
53
|
-
# The plugin emits `plugin.sorbet.parse-error` warnings for
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# not followed by a def) but never aborts a run.
|
|
41
|
+
# The plugin emits `plugin.sorbet.parse-error` warnings for malformed sig blocks (no block / empty
|
|
42
|
+
# block / no `returns` or `void` terminus / two consecutive sigs / sig not followed by a def) but
|
|
43
|
+
# never aborts a run.
|
|
57
44
|
#
|
|
58
45
|
# ## Configuration
|
|
59
46
|
#
|
|
@@ -63,27 +50,23 @@ module Rigor
|
|
|
63
50
|
# paths: ["lib", "app"] # directories to scan for `.rb` sigs; defaults to `paths:`
|
|
64
51
|
# rbi_paths: ["sorbet/rbi"] # directories to scan for `.rbi` files; default shown
|
|
65
52
|
#
|
|
66
|
-
# The `paths:` config key narrows the plugin's `.rb` walk;
|
|
67
|
-
#
|
|
68
|
-
# `
|
|
69
|
-
#
|
|
70
|
-
# layout (`gems/`, `annotations/`, `dsl/`, `shims/`). Set
|
|
71
|
-
# to `[]` to opt out of RBI loading entirely.
|
|
53
|
+
# The `paths:` config key narrows the plugin's `.rb` walk; omit it to inherit the project-wide
|
|
54
|
+
# `paths:` value. The `rbi_paths:` key controls where Sorbet's RBI tree is read from — defaults to
|
|
55
|
+
# `sorbet/rbi/` per Tapioca's standard layout (`gems/`, `annotations/`, `dsl/`, `shims/`). Set to `[]`
|
|
56
|
+
# to opt out of RBI loading entirely.
|
|
72
57
|
class Sorbet < Rigor::Plugin::Base
|
|
73
58
|
manifest(
|
|
74
59
|
id: "sorbet",
|
|
75
60
|
version: "0.1.0",
|
|
76
61
|
description: "Ingests Sorbet `sig` blocks as method-signature contributions.",
|
|
77
62
|
config_schema: {
|
|
78
|
-
# `paths` keeps a `fetch`-with-default in `init` because its
|
|
79
|
-
#
|
|
80
|
-
# static literal the manifest can declare.
|
|
63
|
+
# `paths` keeps a `fetch`-with-default in `init` because its default is dynamic (the project's
|
|
64
|
+
# configured `paths`), not a static literal the manifest can declare.
|
|
81
65
|
"paths" => :array,
|
|
82
|
-
# Default RBI directory tree. Matches the layout `tapioca init`
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# picked up as a side effect of recursing into the parent root.
|
|
66
|
+
# Default RBI directory tree. Matches the layout `tapioca init` generates — see Sorbet's
|
|
67
|
+
# `rbi.md`. Slice 4 walks every `.rbi` file under these roots recursively; the four standard
|
|
68
|
+
# Tapioca subdirectories (`gems` / `annotations` / `dsl` / `shims`) are picked up as a side
|
|
69
|
+
# effect of recursing into the parent root.
|
|
87
70
|
"rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
|
|
88
71
|
"enforce_sigil" => { kind: :boolean, default: true }
|
|
89
72
|
}
|
|
@@ -93,98 +76,94 @@ module Rigor
|
|
|
93
76
|
@services = services
|
|
94
77
|
@configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
|
|
95
78
|
@rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
|
|
96
|
-
# Schema default `true` — only files marked `# typed: true` /
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
# sigil (current behaviour pre-this-config).
|
|
79
|
+
# Schema default `true` — only files marked `# typed: true` / `:strict` / `:strong` contribute
|
|
80
|
+
# their sigs. Set to `false` to record every file's sigs regardless of sigil (current behaviour
|
|
81
|
+
# pre-this-config).
|
|
100
82
|
@enforce_sigil = config.fetch("enforce_sigil")
|
|
101
|
-
# Per-call-site assertion gating: `@sigil_by_path` (built
|
|
102
|
-
#
|
|
103
|
-
# `
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
# gate is open everywhere. Missing-sigil paths (synthetic
|
|
107
|
-
# fixtures, out-of-tree call sites) default to enforced —
|
|
108
|
-
# failing-open suits spec ergonomics better than
|
|
109
|
-
# failing-closed. See `assertion_enforced_here?`.
|
|
83
|
+
# Per-call-site assertion gating: `@sigil_by_path` (built during catalog harvest) is consulted so
|
|
84
|
+
# `T.let` / `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only fire in files Sorbet itself
|
|
85
|
+
# would enforce (`:true` / `:strict` / `:strong`). With `enforce_sigil: false` the gate is open
|
|
86
|
+
# everywhere. Missing-sigil paths (synthetic fixtures, out-of-tree call sites) default to enforced
|
|
87
|
+
# — failing-open suits spec ergonomics better than failing-closed. See `assertion_enforced_here?`.
|
|
110
88
|
@sigil_by_path = {}
|
|
111
89
|
@catalog = nil
|
|
112
90
|
@parse_errors_by_path = {}
|
|
113
91
|
@catalog_built = false
|
|
114
|
-
# ADR-11 slice 6 — Prism nodes for `T.absurd` calls
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# warnings. Hash is keyed on the Prism node's
|
|
120
|
-
# `object_id` because the runner only parses each file
|
|
121
|
-
# once per run, so identity is stable across the two
|
|
122
|
-
# plugin hooks.
|
|
92
|
+
# ADR-11 slice 6 — Prism nodes for `T.absurd` calls we observed in the `dynamic_return` rule to be
|
|
93
|
+
# *reachable* (i.e., their discriminant didn't narrow to `bot`). `diagnostics_for_file` walks the
|
|
94
|
+
# per-file AST and surfaces these as `plugin.sorbet.absurd-reachable` warnings. Hash is keyed on
|
|
95
|
+
# the Prism node's `object_id` because the runner only parses each file once per run, so identity
|
|
96
|
+
# is stable across the two plugin hooks.
|
|
123
97
|
@reachable_absurd_nodes = {}.compare_by_identity
|
|
124
|
-
# ADR-11 light follow-up — `T.reveal_type` calls
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# Mirrors the absurd-node compare-by-identity hash;
|
|
128
|
-
# `diagnostics_for_file` surfaces each entry as a
|
|
98
|
+
# ADR-11 light follow-up — `T.reveal_type` calls observed in the `dynamic_return` rule, paired
|
|
99
|
+
# with the display string for the inferred type at the call site. Mirrors the absurd-node
|
|
100
|
+
# compare-by-identity hash; `diagnostics_for_file` surfaces each entry as a
|
|
129
101
|
# `plugin.sorbet.reveal-type` `:info` diagnostic.
|
|
130
102
|
@reveal_type_calls = {}.compare_by_identity
|
|
131
|
-
# T.bind / T.assert_type! priority slice 1 —
|
|
132
|
-
# `
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
# `diagnostics_for_file` walks the file AST for
|
|
137
|
-
# `T.assert_type!` calls and surfaces matching entries
|
|
138
|
-
# as `plugin.sorbet.assert-type-mismatch` `:error`
|
|
139
|
-
# diagnostics.
|
|
103
|
+
# T.bind / T.assert_type! priority slice 1 — `T.assert_type!` calls observed in the
|
|
104
|
+
# `dynamic_return` rule whose static subtype check FAILED, paired with the inferred + asserted
|
|
105
|
+
# type display strings. Same compare-by-identity discipline. `diagnostics_for_file` walks the
|
|
106
|
+
# file AST for `T.assert_type!` calls and surfaces matching entries as
|
|
107
|
+
# `plugin.sorbet.assert-type-mismatch` `:error` diagnostics.
|
|
140
108
|
@assert_type_mismatches = {}.compare_by_identity
|
|
141
109
|
end
|
|
142
110
|
|
|
143
111
|
def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
|
|
144
112
|
ensure_catalog
|
|
145
|
-
# The catalog records errors under the canonicalised
|
|
146
|
-
#
|
|
147
|
-
# symlink-bearing form here. Look up under both so the
|
|
148
|
-
# match is symlink-agnostic.
|
|
113
|
+
# The catalog records errors under the canonicalised (realpath-resolved) form; the runner may pass
|
|
114
|
+
# the symlink-bearing form here. Look up under both so the match is symlink-agnostic.
|
|
149
115
|
errors = @parse_errors_by_path[path] || @parse_errors_by_path[canonicalize(path)] || []
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
# set is only known at run time (the catalog's `def` names come
|
|
160
|
-
# from the lazy catalog build), so it is declared as a
|
|
161
|
-
# callable: the engine `instance_exec`s it once per run on
|
|
162
|
-
# first dispatch (always after `#prepare`) and memoises the
|
|
163
|
-
# resolved Symbol Set. The gate is a safe over-approximation —
|
|
164
|
-
# a project method merely *named* `cast` or `find` passes it
|
|
165
|
-
# and is declined by the block's own `T.`-receiver / catalog
|
|
166
|
-
# checks.
|
|
116
|
+
errors.map { |error| parse_error_diagnostic(path, error) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# ADR-52 slice 4 — per-call return-type path via the method-name-gated `dynamic_return` DSL. The
|
|
120
|
+
# recognised name set is only known at run time (the catalog's `def` names come from the lazy
|
|
121
|
+
# catalog build), so it is declared as a callable: the engine `instance_exec`s it once per run on
|
|
122
|
+
# first dispatch (always after `#prepare`) and memoises the resolved Symbol Set. The gate is a safe
|
|
123
|
+
# over-approximation — a project method merely *named* `cast` or `find` passes it and is declined by
|
|
124
|
+
# the block's own `T.`-receiver / catalog checks.
|
|
167
125
|
dynamic_return methods: -> { recognised_method_names } do |call_node, scope|
|
|
168
126
|
contribution_return_type(call_node, scope)
|
|
169
127
|
end
|
|
170
128
|
|
|
171
|
-
# ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact,
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
# `dynamic_return` rule above; the block re-checks the `T.`
|
|
176
|
-
# receiver via the recogniser, so an unrelated `bind` call
|
|
177
|
-
# contributes nothing.
|
|
129
|
+
# ADR-52 slice 4 — `T.bind(self, T)`'s self-narrowing fact, contributed via the method-gated
|
|
130
|
+
# `narrowing_facts` DSL. The statement evaluator consults this path for narrowing facts. The
|
|
131
|
+
# return-type half (`Constant[nil]`) flows through the `dynamic_return` rule above; the block
|
|
132
|
+
# re-checks the `T.` receiver via the recogniser, so an unrelated `bind` call contributes nothing.
|
|
178
133
|
narrowing_facts methods: [:bind] do |call_node, scope|
|
|
179
134
|
bind_post_return_facts(call_node, scope)
|
|
180
135
|
end
|
|
181
136
|
|
|
137
|
+
# ADR-37 — the three per-call diagnostics ride the engine-owned walk instead of three hand-rolled
|
|
138
|
+
# `walk_for_*` recursions. Each candidate `T.` call is *recorded by object identity* during the
|
|
139
|
+
# inference pass (the `dynamic_return` / `narrowing_facts` rules above call `record_*`), so by the
|
|
140
|
+
# time these node rules fire in the diagnostics phase the sets are populated; the membership
|
|
141
|
+
# `delete` both gates the emission and pops the entry so a re-run cannot double-fire. The recorded
|
|
142
|
+
# set is the gate — no per-node `AbsurdRecognizer` / name check is needed here.
|
|
143
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
144
|
+
next [] unless @reachable_absurd_nodes.delete(node)
|
|
145
|
+
|
|
146
|
+
[absurd_diagnostic(path, node)]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
150
|
+
display = @reveal_type_calls.delete(node)
|
|
151
|
+
next [] if display.nil?
|
|
152
|
+
|
|
153
|
+
[reveal_type_diagnostic(path, node, display)]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
node_rule Prism::CallNode do |node, _scope, path|
|
|
157
|
+
recorded = @assert_type_mismatches.delete(node)
|
|
158
|
+
next [] if recorded.nil?
|
|
159
|
+
|
|
160
|
+
[assert_type_mismatch_diagnostic(path, node, *recorded)]
|
|
161
|
+
end
|
|
162
|
+
|
|
182
163
|
private
|
|
183
164
|
|
|
184
|
-
# Run-time method-name gate for the `dynamic_return` rule
|
|
185
|
-
# (
|
|
186
|
-
# (`T.let` / `T.cast` / …), `T.absurd`, and every method name
|
|
187
|
-
# the catalog carries a sig for.
|
|
165
|
+
# Run-time method-name gate for the `dynamic_return` rule (ADR-52 slice 4): the static assertion
|
|
166
|
+
# vocabulary (`T.let` / `T.cast` / …), `T.absurd`, and every method name the catalog carries a sig for.
|
|
188
167
|
def recognised_method_names
|
|
189
168
|
ensure_catalog
|
|
190
169
|
names = AssertionRecognizer::SORBET_ASSERTIONS.dup
|
|
@@ -193,54 +172,39 @@ module Rigor
|
|
|
193
172
|
names
|
|
194
173
|
end
|
|
195
174
|
|
|
196
|
-
# Main contribution body for the `dynamic_return` rule.
|
|
197
|
-
#
|
|
198
|
-
# Resolves the receiver in three passes:
|
|
175
|
+
# Main contribution body for the `dynamic_return` rule. Returns the bare `Rigor::Type` the contract
|
|
176
|
+
# expects. Resolves the receiver in three passes:
|
|
199
177
|
#
|
|
200
|
-
# 1. Constant receiver (`User.find(...)`) → singleton-side
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
# 3. Implicit-self (receiver-less inside a method body) →
|
|
206
|
-
# current-class lookup via `implicit_self_lookup`.
|
|
178
|
+
# 1. Constant receiver (`User.find(...)`) → singleton-side catalog lookup.
|
|
179
|
+
# 2. Nominal receiver-type (`user.name` where `user`'s inferred type is `Nominal["User"]`) →
|
|
180
|
+
# instance-side catalog lookup.
|
|
181
|
+
# 3. Implicit-self (receiver-less inside a method body) → current-class lookup via
|
|
182
|
+
# `implicit_self_lookup`.
|
|
207
183
|
def contribution_return_type(call_node, scope)
|
|
208
184
|
return nil unless call_node.is_a?(Prism::CallNode)
|
|
209
185
|
|
|
210
|
-
# ADR-11 slice 6 — `T.absurd(x)` exhaustiveness. Always
|
|
211
|
-
#
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
#
|
|
215
|
-
#
|
|
216
|
-
# only `return_type` survives the merge). When the
|
|
217
|
-
# discriminant *isn't* narrowed to `bot` at this scope, also
|
|
218
|
-
# records the call node so `diagnostics_for_file` can surface
|
|
219
|
-
# a `plugin.sorbet.absurd-reachable` warning.
|
|
186
|
+
# ADR-11 slice 6 — `T.absurd(x)` exhaustiveness. Always contributes a `bot` return (matches
|
|
187
|
+
# Sorbet's runtime behaviour: `T.absurd` raises, so its value type is `bot` and the engine's flow
|
|
188
|
+
# analysis treats code after it as unreachable; the legacy contribution's `exceptional: :raises`
|
|
189
|
+
# slot was never consumed on the dispatcher path — only `return_type` survives the merge). When
|
|
190
|
+
# the discriminant *isn't* narrowed to `bot` at this scope, also records the call node so
|
|
191
|
+
# `diagnostics_for_file` can surface a `plugin.sorbet.absurd-reachable` warning.
|
|
220
192
|
if AbsurdRecognizer.absurd_call?(call_node)
|
|
221
193
|
@reachable_absurd_nodes[call_node] = true unless AbsurdRecognizer.exhaustive?(call_node, scope)
|
|
222
194
|
return Rigor::Type::Combinator.bot
|
|
223
195
|
end
|
|
224
196
|
|
|
225
|
-
# ADR-11 slice 2 — `T.let` / `T.cast` / `T.must` /
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# follow-up extends the recogniser to `T.must_because`
|
|
231
|
-
# (alias of `T.must`) and `T.reveal_type` (passes the
|
|
232
|
-
# type through; the human-facing diagnostic is recorded
|
|
233
|
-
# here for `diagnostics_for_file` to emit).
|
|
197
|
+
# ADR-11 slice 2 — `T.let` / `T.cast` / `T.must` / `T.unsafe` are checked first because they're
|
|
198
|
+
# cheaper to recognise (no catalog walk required) and they win over any cataloged signature: the
|
|
199
|
+
# user explicitly asserted the type at the call site. The light follow-up extends the recogniser
|
|
200
|
+
# to `T.must_because` (alias of `T.must`) and `T.reveal_type` (passes the type through; the
|
|
201
|
+
# human-facing diagnostic is recorded here for `diagnostics_for_file` to emit).
|
|
234
202
|
#
|
|
235
|
-
# Per-call-site sigil gating: with `enforce_sigil: true`
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# through the next tier as if the wrapper weren't
|
|
241
|
-
# there. The catalog tier already gates by sigil at
|
|
242
|
-
# harvest time; this closes the matching gap for
|
|
243
|
-
# caller-side recognition.
|
|
203
|
+
# Per-call-site sigil gating: with `enforce_sigil: true` (default), assertions only fire in files
|
|
204
|
+
# Sorbet itself would enforce. Files at `# typed: false` (or sigil-less, which Sorbet treats as
|
|
205
|
+
# `:false`) skip the assertion path entirely so the dispatcher continues through the next tier as
|
|
206
|
+
# if the wrapper weren't there. The catalog tier already gates by sigil at harvest time; this
|
|
207
|
+
# closes the matching gap for caller-side recognition.
|
|
244
208
|
ensure_catalog
|
|
245
209
|
if assertion_enforced_here?(scope)
|
|
246
210
|
assertion = AssertionRecognizer.recognize(
|
|
@@ -258,10 +222,9 @@ module Rigor
|
|
|
258
222
|
lookup_signature(call_node, scope)&.return_type
|
|
259
223
|
end
|
|
260
224
|
|
|
261
|
-
# The `narrowing_facts` body for `T.bind` — same sigil gate as
|
|
262
|
-
# the
|
|
263
|
-
#
|
|
264
|
-
# narrows `scope.self_type` for the rest of the block).
|
|
225
|
+
# The `narrowing_facts` body for `T.bind` — same sigil gate as the return-type path, then the
|
|
226
|
+
# recogniser's `post_return_facts` (the `Fact(target_kind: :self)` that narrows `scope.self_type`
|
|
227
|
+
# for the rest of the block).
|
|
265
228
|
def bind_post_return_facts(call_node, scope)
|
|
266
229
|
return nil unless call_node.is_a?(Prism::CallNode)
|
|
267
230
|
|
|
@@ -274,24 +237,16 @@ module Rigor
|
|
|
274
237
|
contribution&.post_return_facts
|
|
275
238
|
end
|
|
276
239
|
|
|
277
|
-
# Per-call-site assertion gating (ADR-11). With
|
|
278
|
-
# `enforce_sigil:
|
|
279
|
-
# `
|
|
280
|
-
# sigil must reach `:true` / `:strict` / `:strong` for
|
|
281
|
-
# assertions to fire. Three honest fallbacks:
|
|
240
|
+
# Per-call-site assertion gating (ADR-11). With `enforce_sigil: false` the gate is fully open. With
|
|
241
|
+
# `enforce_sigil: true` (default), the caller file's sigil must reach `:true` / `:strict` /
|
|
242
|
+
# `:strong` for assertions to fire. Three honest fallbacks:
|
|
282
243
|
#
|
|
283
|
-
# - `scope.source_path` is nil — synthetic call sites
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
289
|
-
# (out-of-tree call site, or a path the
|
|
290
|
-
# `configured_paths` config excluded). Sorbet itself
|
|
291
|
-
# has no opinion on such files; default to enforced
|
|
292
|
-
# so the recogniser still fires.
|
|
293
|
-
# - the path IS in `@sigil_by_path` but at `:false` /
|
|
294
|
-
# `:ignore` — gate closes.
|
|
244
|
+
# - `scope.source_path` is nil — synthetic call sites (specs, virtual-node fixtures) have no file
|
|
245
|
+
# context. Default to enforced so existing recogniser tests keep working.
|
|
246
|
+
# - the path is canonicalised to a form not in `@sigil_by_path` — the harvest never saw this file
|
|
247
|
+
# (out-of-tree call site, or a path the `configured_paths` config excluded). Sorbet itself has no
|
|
248
|
+
# opinion on such files; default to enforced so the recogniser still fires.
|
|
249
|
+
# - the path IS in `@sigil_by_path` but at `:false` / `:ignore` — gate closes.
|
|
295
250
|
def assertion_enforced_here?(scope)
|
|
296
251
|
return true unless @enforce_sigil
|
|
297
252
|
|
|
@@ -311,8 +266,8 @@ module Rigor
|
|
|
311
266
|
return nil if method_name.nil?
|
|
312
267
|
|
|
313
268
|
if (singleton_target = constant_receiver_name(receiver))
|
|
314
|
-
# `Post.find(...)` — direct singleton method, or
|
|
315
|
-
#
|
|
269
|
+
# `Post.find(...)` — direct singleton method, or `extend M` lifting `M#find` to the extending
|
|
270
|
+
# class.
|
|
316
271
|
chain_lookup(singleton_target, method_name, anchor_kind: :singleton, mixin_kind: :extend)
|
|
317
272
|
elsif receiver
|
|
318
273
|
instance_chain_lookup(receiver, method_name, scope)
|
|
@@ -322,16 +277,13 @@ module Rigor
|
|
|
322
277
|
end
|
|
323
278
|
|
|
324
279
|
# ADR-11 slice 2 — implicit-self calls.
|
|
325
|
-
# A receiver-less call inside a method body resolves against the
|
|
326
|
-
#
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
# `T.untyped` opt-out (the dispatcher's plugin tier had already
|
|
333
|
-
# run and declined). Anything else (toplevel / Dynamic / DSL
|
|
334
|
-
# self) contributes nothing and the dispatcher continues.
|
|
280
|
+
# A receiver-less call inside a method body resolves against the engine's own `scope.self_type`:
|
|
281
|
+
# `Nominal[Foo]` inside an instance method (instance-side lookup), `Singleton[Foo]` inside a `def
|
|
282
|
+
# self.x` body (singleton-side lookup, `extend` mixins). Without this, an enforced sig on a sibling
|
|
283
|
+
# method was invisible to in-class calls — the engine's body-inference tiers then re-typed the
|
|
284
|
+
# sibling's body, overriding an explicit `T.untyped` opt-out (the dispatcher's plugin tier had
|
|
285
|
+
# already run and declined). Anything else (toplevel / Dynamic / DSL self) contributes nothing and
|
|
286
|
+
# the dispatcher continues.
|
|
335
287
|
def implicit_self_lookup(method_name, scope)
|
|
336
288
|
self_type = scope&.self_type
|
|
337
289
|
case self_type
|
|
@@ -352,9 +304,8 @@ module Rigor
|
|
|
352
304
|
|
|
353
305
|
chain_lookup(receiver_type.class_name, method_name, anchor_kind: :instance, mixin_kind: :include)
|
|
354
306
|
rescue StandardError
|
|
355
|
-
# `scope.type_of` can raise on unrecognised synthetic
|
|
356
|
-
#
|
|
357
|
-
# bubbling the failure into the dispatcher.
|
|
307
|
+
# `scope.type_of` can raise on unrecognised synthetic nodes; degrade to "no contribution" rather
|
|
308
|
+
# than bubbling the failure into the dispatcher.
|
|
358
309
|
nil
|
|
359
310
|
end
|
|
360
311
|
|
|
@@ -362,15 +313,13 @@ module Rigor
|
|
|
362
313
|
#
|
|
363
314
|
# For instance-side calls (`post.body`):
|
|
364
315
|
# - `anchor_kind: :instance` (try `Post#body` first)
|
|
365
|
-
# - `mixin_kind: :include` (then walk Post's `include`d
|
|
366
|
-
# modules and try `Foo#body` on each)
|
|
316
|
+
# - `mixin_kind: :include` (then walk Post's `include`d modules and try `Foo#body` on each)
|
|
367
317
|
#
|
|
368
318
|
# For singleton-side calls (`Post.find`):
|
|
369
319
|
# - `anchor_kind: :singleton` (try `Post.find` first)
|
|
370
|
-
# - `mixin_kind: :extend` (then walk Post's `extend`ed
|
|
371
|
-
#
|
|
372
|
-
#
|
|
373
|
-
# class's SINGLETON methods, matching Ruby's MRO).
|
|
320
|
+
# - `mixin_kind: :extend` (then walk Post's `extend`ed modules and try `Foo#find` *as :instance* —
|
|
321
|
+
# `extend Foo` lifts Foo's INSTANCE methods to the extending class's SINGLETON methods, matching
|
|
322
|
+
# Ruby's MRO).
|
|
374
323
|
def chain_lookup(class_name, method_name, anchor_kind:, mixin_kind:)
|
|
375
324
|
each_class_form(class_name).each do |form|
|
|
376
325
|
sig = @catalog.lookup(class_name: form, method_name: method_name, kind: anchor_kind)
|
|
@@ -388,8 +337,7 @@ module Rigor
|
|
|
388
337
|
sig = @catalog.lookup(class_name: form, method_name: method_name, kind: :instance)
|
|
389
338
|
return sig if sig
|
|
390
339
|
|
|
391
|
-
# Transitive: an `include` inside the mixed-in
|
|
392
|
-
# module is also inherited by the host class.
|
|
340
|
+
# Transitive: an `include` inside the mixed-in module is also inherited by the host class.
|
|
393
341
|
mixin_modules_for(form, :include).each do |inner|
|
|
394
342
|
queue << inner unless visited.include?(inner)
|
|
395
343
|
end
|
|
@@ -399,20 +347,16 @@ module Rigor
|
|
|
399
347
|
nil
|
|
400
348
|
end
|
|
401
349
|
|
|
402
|
-
# `Post` and `::Post` are routinely confused at the catalog
|
|
403
|
-
#
|
|
404
|
-
# often writes the rooted form). Try both at every lookup.
|
|
350
|
+
# `Post` and `::Post` are routinely confused at the catalog boundary (the walker records the lexical
|
|
351
|
+
# name; user code often writes the rooted form). Try both at every lookup.
|
|
405
352
|
def each_class_form(class_name)
|
|
406
353
|
[class_name, "::#{class_name}"]
|
|
407
354
|
end
|
|
408
355
|
|
|
409
|
-
# Resolution forms for a mixed-in module name. Tapioca's
|
|
410
|
-
#
|
|
411
|
-
# (`
|
|
412
|
-
#
|
|
413
|
-
# (`module GeneratedAttributeMethods; ...; end` outside any
|
|
414
|
-
# class); explicit rooting (`::GeneratedAttributeMethods`)
|
|
415
|
-
# is occasionally seen. Try all three.
|
|
356
|
+
# Resolution forms for a mixed-in module name. Tapioca's generated DSL RBIs use the nested form
|
|
357
|
+
# (`class Post; module GeneratedAttributeMethods; ...; end`); hand-written shims often use the
|
|
358
|
+
# top-level form (`module GeneratedAttributeMethods; ...; end` outside any class); explicit rooting
|
|
359
|
+
# (`::GeneratedAttributeMethods`) is occasionally seen. Try all three.
|
|
416
360
|
def forms_for_mixin(host_class, mixin_name)
|
|
417
361
|
if mixin_name.start_with?("::")
|
|
418
362
|
[mixin_name, mixin_name.delete_prefix("::")]
|
|
@@ -461,8 +405,7 @@ module Rigor
|
|
|
461
405
|
|
|
462
406
|
# @param root [String] directory or single file.
|
|
463
407
|
# @param catalog [Catalog]
|
|
464
|
-
# @param extensions [Array<String>] file extensions to
|
|
465
|
-
# accept (e.g. `[".rb"]` for project source,
|
|
408
|
+
# @param extensions [Array<String>] file extensions to accept (e.g. `[".rb"]` for project source,
|
|
466
409
|
# `[".rbi"]` for Sorbet RBI tree).
|
|
467
410
|
def harvest_path(root, catalog, extensions:)
|
|
468
411
|
absolute = canonicalize(root)
|
|
@@ -473,18 +416,15 @@ module Rigor
|
|
|
473
416
|
end
|
|
474
417
|
end
|
|
475
418
|
elsif File.file?(absolute) && extensions.any? { |ext| absolute.end_with?(ext) }
|
|
476
|
-
# `paths:` may list individual files (the demos do
|
|
477
|
-
# this); walk them directly rather than skipping.
|
|
419
|
+
# `paths:` may list individual files (the demos do this); walk them directly rather than skipping.
|
|
478
420
|
harvest_file(absolute, catalog)
|
|
479
421
|
end
|
|
480
422
|
end
|
|
481
423
|
|
|
482
|
-
# Canonicalises a path through `File.realpath` so it
|
|
483
|
-
#
|
|
484
|
-
#
|
|
485
|
-
#
|
|
486
|
-
# `/private/tmp` etc.). Falls back to `File.expand_path`
|
|
487
|
-
# when realpath fails (e.g. the path no longer exists).
|
|
424
|
+
# Canonicalises a path through `File.realpath` so it matches the form `Plugin::TrustPolicy#allow_read?`
|
|
425
|
+
# sees (the runner builds the policy's roots from `Dir.pwd`, which has symlinks resolved on macOS —
|
|
426
|
+
# `/tmp` → `/private/tmp` etc.). Falls back to `File.expand_path` when realpath fails (e.g. the path
|
|
427
|
+
# no longer exists).
|
|
488
428
|
def canonicalize(path)
|
|
489
429
|
expanded = File.expand_path(path)
|
|
490
430
|
File.exist?(expanded) ? File.realpath(expanded) : expanded
|
|
@@ -496,31 +436,23 @@ module Rigor
|
|
|
496
436
|
contents = io_boundary.read_file(path)
|
|
497
437
|
return if contents.nil?
|
|
498
438
|
|
|
499
|
-
# ADR-11 slice 5 — honour Sorbet's `# typed: ignore`
|
|
500
|
-
# magic comment by skipping the file entirely.
|
|
439
|
+
# ADR-11 slice 5 — honour Sorbet's `# typed: ignore` magic comment by skipping the file entirely.
|
|
501
440
|
level = SigilDetector.detect(contents)
|
|
502
|
-
# Per-call-site assertion gating consults this map at
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
# reports its level to the gate (the gate then chooses
|
|
506
|
-
# to suppress assertions there too — `ignore` is
|
|
507
|
-
# stricter than `false`).
|
|
441
|
+
# Per-call-site assertion gating consults this map at recognition time. Recorded BEFORE the
|
|
442
|
+
# ignored short-circuit so a `# typed: ignore` file still reports its level to the gate (the gate
|
|
443
|
+
# then chooses to suppress assertions there too — `ignore` is stricter than `false`).
|
|
508
444
|
@sigil_by_path[path] = level
|
|
509
445
|
return if SigilDetector.ignored?(level)
|
|
510
446
|
|
|
511
447
|
result = Prism.parse(contents)
|
|
512
448
|
return unless result.errors.empty?
|
|
513
449
|
|
|
514
|
-
# `enforce_sigil` follow-up — when on (default), files
|
|
515
|
-
#
|
|
516
|
-
#
|
|
517
|
-
#
|
|
518
|
-
#
|
|
519
|
-
#
|
|
520
|
-
# that for sig contributions. Assertion recognisers
|
|
521
|
-
# (`T.let` / `T.cast` / `T.must` / `T.bind` /
|
|
522
|
-
# `T.assert_type!`) stay live regardless of sigil — the
|
|
523
|
-
# user wrote those deliberately.
|
|
450
|
+
# `enforce_sigil` follow-up — when on (default), files at `:false` (or sigil-less, which Sorbet
|
|
451
|
+
# treats as `:false`) are STILL walked so parse-error diagnostics surface, but sigs flow into a
|
|
452
|
+
# discardable catalog rather than the per-run one. Sorbet itself doesn't enforce types at `#
|
|
453
|
+
# typed: false`, and Rigor mirrors that for sig contributions. Assertion recognisers (`T.let` /
|
|
454
|
+
# `T.cast` / `T.must` / `T.bind` / `T.assert_type!`) stay live regardless of sigil — the user
|
|
455
|
+
# wrote those deliberately.
|
|
524
456
|
sig_catalog = if @enforce_sigil && !SigilDetector.enforced?(level)
|
|
525
457
|
Catalog.new
|
|
526
458
|
else
|
|
@@ -530,37 +462,13 @@ module Rigor
|
|
|
530
462
|
errors = CatalogWalker.walk(root: result.value, catalog: sig_catalog, path: path)
|
|
531
463
|
@parse_errors_by_path[path] = errors unless errors.empty?
|
|
532
464
|
rescue Plugin::AccessDeniedError, Errno::ENOENT
|
|
533
|
-
# Skip files outside the trusted read scope or that
|
|
534
|
-
#
|
|
535
|
-
# no output for them.
|
|
465
|
+
# Skip files outside the trusted read scope or that vanished between glob and read; the plugin
|
|
466
|
+
# produces no output for them.
|
|
536
467
|
nil
|
|
537
468
|
end
|
|
538
469
|
|
|
539
|
-
#
|
|
540
|
-
#
|
|
541
|
-
# warning for any whose object identity matches
|
|
542
|
-
# `@reachable_absurd_nodes` (populated during the engine's
|
|
543
|
-
# earlier pass through the `dynamic_return` rule). Pops
|
|
544
|
-
# matched entries so a duplicate run doesn't double-emit.
|
|
545
|
-
def absurd_reachable_diagnostics(path, root)
|
|
546
|
-
return [] if @reachable_absurd_nodes.empty?
|
|
547
|
-
|
|
548
|
-
diagnostics = []
|
|
549
|
-
walk_for_absurd(root) do |call_node|
|
|
550
|
-
next unless @reachable_absurd_nodes.delete(call_node)
|
|
551
|
-
|
|
552
|
-
diagnostics << absurd_diagnostic(path, call_node)
|
|
553
|
-
end
|
|
554
|
-
diagnostics
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
def walk_for_absurd(node, &)
|
|
558
|
-
return unless node.is_a?(Prism::Node)
|
|
559
|
-
|
|
560
|
-
yield node if node.is_a?(Prism::CallNode) && AbsurdRecognizer.absurd_call?(node)
|
|
561
|
-
node.compact_child_nodes.each { |child| walk_for_absurd(child, &) }
|
|
562
|
-
end
|
|
563
|
-
|
|
470
|
+
# Emits a `plugin.sorbet.absurd-reachable` warning for the `T.absurd(x)` call recorded in
|
|
471
|
+
# `@reachable_absurd_nodes` during inference; the node rule above does the identity match and pop.
|
|
564
472
|
def absurd_diagnostic(path, call_node)
|
|
565
473
|
Rigor::Analysis::Diagnostic.from_node(
|
|
566
474
|
call_node,
|
|
@@ -573,47 +481,21 @@ module Rigor
|
|
|
573
481
|
)
|
|
574
482
|
end
|
|
575
483
|
|
|
576
|
-
# ADR-11 light follow-up — `T.reveal_type(expr)` records
|
|
577
|
-
#
|
|
578
|
-
#
|
|
579
|
-
#
|
|
580
|
-
# inferred type for downstream chaining; this hash carries
|
|
581
|
-
# the *display* string that the diagnostic shows.
|
|
484
|
+
# ADR-11 light follow-up — `T.reveal_type(expr)` records the inferred type at recogniser time so the
|
|
485
|
+
# per-file diagnostic hook can surface the human-facing message. The reveal call's contribution
|
|
486
|
+
# already preserved the inferred type for downstream chaining; this hash carries the *display*
|
|
487
|
+
# string that the diagnostic shows.
|
|
582
488
|
def record_reveal_type_call(call_node, return_type)
|
|
583
489
|
@reveal_type_calls[call_node] = display_for_type(return_type)
|
|
584
490
|
end
|
|
585
491
|
|
|
586
492
|
def display_for_type(type)
|
|
587
|
-
# `Type#describe` is the human-facing display contract
|
|
588
|
-
# used by `rigor type-of`'s text renderer.
|
|
493
|
+
# `Type#describe` is the human-facing display contract used by `rigor type-of`'s text renderer.
|
|
589
494
|
return "untyped" if type.nil?
|
|
590
495
|
|
|
591
496
|
type.respond_to?(:describe) ? type.describe : type.inspect
|
|
592
497
|
end
|
|
593
498
|
|
|
594
|
-
def reveal_type_diagnostics(path, root)
|
|
595
|
-
return [] if @reveal_type_calls.empty?
|
|
596
|
-
|
|
597
|
-
diagnostics = []
|
|
598
|
-
walk_for_reveal_type(root) do |call_node|
|
|
599
|
-
display = @reveal_type_calls.delete(call_node)
|
|
600
|
-
next if display.nil?
|
|
601
|
-
|
|
602
|
-
diagnostics << reveal_type_diagnostic(path, call_node, display)
|
|
603
|
-
end
|
|
604
|
-
diagnostics
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
def walk_for_reveal_type(node, &)
|
|
608
|
-
return unless node.is_a?(Prism::Node)
|
|
609
|
-
|
|
610
|
-
if node.is_a?(Prism::CallNode) && node.name == :reveal_type &&
|
|
611
|
-
TypeTranslator.sorbet_t_namespaced?(node.receiver)
|
|
612
|
-
yield node
|
|
613
|
-
end
|
|
614
|
-
node.compact_child_nodes.each { |child| walk_for_reveal_type(child, &) }
|
|
615
|
-
end
|
|
616
|
-
|
|
617
499
|
def reveal_type_diagnostic(path, call_node, display)
|
|
618
500
|
Rigor::Analysis::Diagnostic.from_node(
|
|
619
501
|
call_node,
|
|
@@ -624,16 +506,11 @@ module Rigor
|
|
|
624
506
|
)
|
|
625
507
|
end
|
|
626
508
|
|
|
627
|
-
# T.bind / T.assert_type! priority slice 1 — runs the
|
|
628
|
-
#
|
|
629
|
-
#
|
|
630
|
-
#
|
|
631
|
-
#
|
|
632
|
-
# `Dynamic[top]` inferred type
|
|
633
|
-
# silences the check; a definite `:no` records for
|
|
634
|
-
# diagnostic emission; `:maybe` (uncertain) is treated as
|
|
635
|
-
# "trust the user" and silenced — the runtime check is
|
|
636
|
-
# there for those cases.
|
|
509
|
+
# T.bind / T.assert_type! priority slice 1 — runs the static subtype check at recogniser time and
|
|
510
|
+
# records the call only when the inferred type is *provably incompatible* with the asserted type.
|
|
511
|
+
# Gradual consistency rules (`Type#accepts` mode `:gradual`): a `Dynamic[top]` inferred type
|
|
512
|
+
# silences the check; a definite `:no` records for diagnostic emission; `:maybe` (uncertain) is
|
|
513
|
+
# treated as "trust the user" and silenced — the runtime check is there for those cases.
|
|
637
514
|
def record_assert_type_check(call_node, scope)
|
|
638
515
|
check = AssertionRecognizer.assert_type_check(call_node, scope)
|
|
639
516
|
return if check.nil?
|
|
@@ -647,29 +524,6 @@ module Rigor
|
|
|
647
524
|
@assert_type_mismatches[call_node] = [display_for_type(inferred), display_for_type(asserted)]
|
|
648
525
|
end
|
|
649
526
|
|
|
650
|
-
def assert_type_mismatch_diagnostics(path, root)
|
|
651
|
-
return [] if @assert_type_mismatches.empty?
|
|
652
|
-
|
|
653
|
-
diagnostics = []
|
|
654
|
-
walk_for_assert_type(root) do |call_node|
|
|
655
|
-
recorded = @assert_type_mismatches.delete(call_node)
|
|
656
|
-
next if recorded.nil?
|
|
657
|
-
|
|
658
|
-
diagnostics << assert_type_mismatch_diagnostic(path, call_node, *recorded)
|
|
659
|
-
end
|
|
660
|
-
diagnostics
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
def walk_for_assert_type(node, &)
|
|
664
|
-
return unless node.is_a?(Prism::Node)
|
|
665
|
-
|
|
666
|
-
if node.is_a?(Prism::CallNode) && node.name == :assert_type! &&
|
|
667
|
-
TypeTranslator.sorbet_t_namespaced?(node.receiver)
|
|
668
|
-
yield node
|
|
669
|
-
end
|
|
670
|
-
node.compact_child_nodes.each { |child| walk_for_assert_type(child, &) }
|
|
671
|
-
end
|
|
672
|
-
|
|
673
527
|
def assert_type_mismatch_diagnostic(path, call_node, inferred_display, asserted_display)
|
|
674
528
|
Rigor::Analysis::Diagnostic.from_node(
|
|
675
529
|
call_node,
|