rigortype 0.4.5 → 0.4.7
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 +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +61 -5
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +192 -27
- data/docs/manual/03-configuration.md +38 -4
- data/docs/manual/04-diagnostics.md +1 -1
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +47 -3
- data/docs/manual/12-caching.md +9 -1
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +49 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +2 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +217 -52
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +15 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +154 -0
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental.rb +37 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +202 -43
- data/lib/rigor/analysis/path_expansion.rb +41 -0
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +7 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
- data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +233 -120
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +38 -33
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +60 -18
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +188 -93
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +27 -21
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +17 -13
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +14 -8
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +60 -4
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +22 -18
- data/lib/rigor/cli/plugins_renderer.rb +34 -8
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
- data/lib/rigor/cli/sig_gen_command.rb +130 -34
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +18 -12
- data/lib/rigor/cli/type_of_project_scope.rb +24 -0
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +17 -6
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +86 -34
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +592 -70
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +136 -32
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +44 -11
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +170 -0
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +204 -12
- data/lib/rigor/inference/definer_resolution.rb +21 -5
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +173 -57
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +110 -1
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +246 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +57 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +109 -29
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +23 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +162 -29
- data/lib/rigor/inference/narrowing.rb +113 -38
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_seed_tables.rb +2 -1
- data/lib/rigor/inference/project_yielder.rb +122 -0
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +614 -146
- data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +3036 -278
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +9 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +98 -4
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +12 -6
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +82 -3
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +26 -11
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +38 -16
- data/lib/rigor/plugin/registry.rb +41 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +96 -2
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +25 -2
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +379 -36
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +668 -84
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +7 -6
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +21 -12
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +13 -16
- data/lib/rigor/type/constant.rb +48 -7
- data/lib/rigor/type/hash_shape.rb +26 -40
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +0 -2
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +10 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +6 -5
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +6 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/scope.rbs +51 -50
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +55 -3
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../reflection"
|
|
4
|
+
require_relative "../rbs_type_translator"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module ScopeIndexer
|
|
9
|
+
# Issue #1406 — an ivar the class's RBS declares, as `@x: T` or through an `attr_reader x: T` /
|
|
10
|
+
# `attr_writer` / `attr_accessor` member (in `sig/`, or inline `attr_reader :x #: T`), reads the declared type
|
|
11
|
+
# in place of the census's join of the writes. The declaration is the author's contract for the field, so it
|
|
12
|
+
# wins over every write the census saw, #541's untyped `attr_writer` write included; whether the writes
|
|
13
|
+
# honour it is ADR-112 WD5's question (#1075), not this one's. It serves
|
|
14
|
+
# checking only: `rigor sig-gen` indexes with `declared_ivar_types: false` and keeps the census's answer.
|
|
15
|
+
#
|
|
16
|
+
# Only an ivar the census already holds is replaced, so a declaration adds no entry the file's writes did not.
|
|
17
|
+
# A declaration is not taken when it is `untyped` (what rbs-inline supplies for a bare `attr_reader :x`, which
|
|
18
|
+
# the type specification reads as a placeholder) or mentions a type variable (`@x: T` in `class Box[T]` is
|
|
19
|
+
# unsolved at a read), and the census entry stands.
|
|
20
|
+
module DeclaredIvarTypes
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def apply!(accumulator, scope)
|
|
24
|
+
environment = scope&.environment
|
|
25
|
+
return if environment.nil?
|
|
26
|
+
|
|
27
|
+
accumulator.each do |class_name, entries|
|
|
28
|
+
next unless class_name.is_a?(String) # a guard: the side-table keys are deleted before this runs
|
|
29
|
+
|
|
30
|
+
declared = declared_ivars(class_name, environment)
|
|
31
|
+
next if declared.empty?
|
|
32
|
+
|
|
33
|
+
accumulator[class_name] = entries.to_h do |ivar, type|
|
|
34
|
+
[ivar, declared.fetch(ivar, type)]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# `{ivar name => Rigor::Type}` for the ivars `class_name`'s RBS instance definition declares with a usable
|
|
40
|
+
# type, or `{}`.
|
|
41
|
+
def declared_ivars(class_name, environment)
|
|
42
|
+
definition = Reflection.instance_definition(class_name, environment: environment)
|
|
43
|
+
return {} if definition.nil?
|
|
44
|
+
|
|
45
|
+
instance = Type::Combinator.nominal_of(class_name)
|
|
46
|
+
definition.instance_variables.each_with_object({}) do |(name, variable), acc|
|
|
47
|
+
type = translate(variable.type, instance, environment)
|
|
48
|
+
acc[name] = type if type
|
|
49
|
+
end
|
|
50
|
+
rescue StandardError # a guard: a definition that fails to build declares nothing
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def translate(rbs_type, instance, environment)
|
|
55
|
+
return nil unless rbs_type.free_variables.empty?
|
|
56
|
+
|
|
57
|
+
type = RbsTypeTranslator.translate(rbs_type, self_type: instance, instance_type: instance,
|
|
58
|
+
alias_expander: environment.rbs_loader)
|
|
59
|
+
type.is_a?(Type::Dynamic) ? nil : type
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../reflection"
|
|
6
|
+
require_relative "../method_parameter_binder"
|
|
7
|
+
require_relative "../global_write_census"
|
|
8
|
+
require_relative "unseen_ivar_writers"
|
|
9
|
+
require_relative "derived_param_value"
|
|
10
|
+
|
|
11
|
+
module Rigor
|
|
12
|
+
module Inference
|
|
13
|
+
module ScopeIndexer
|
|
14
|
+
# Issue #1154 — the class-ivar census types an `@x = x` write whose right-hand side reads a parameter of the
|
|
15
|
+
# enclosing instance `def` as that parameter's DECLARED type: the method's RBS signature, from `sig/` or from
|
|
16
|
+
# an rbs-inline `# @rbs` / `#:` annotation, read through {MethodParameterBinder}, the binder the method body's
|
|
17
|
+
# own entry scope uses. Without it the census typed the rvalue with no local bindings, so a constructor field
|
|
18
|
+
# read `Dynamic[top]` in every sibling method even where `sig/` declared `initialize: (String x) -> void`.
|
|
19
|
+
#
|
|
20
|
+
# The census is flow-free, so a write is credited only where the parameter can hold nothing but its
|
|
21
|
+
# argument. {.collect} answers `{write node => declared type}` (an identity Hash) for the writes that pass
|
|
22
|
+
# every gate, and {EMPTY} otherwise; an absent write keeps the census's ordinary rvalue typing, so a declined
|
|
23
|
+
# case reads exactly what it read before:
|
|
24
|
+
#
|
|
25
|
+
# - the write's value reads at least one parameter, and every local it reads is a parameter with a declared type.
|
|
26
|
+
# A bare read (`@x = x`) records that type; an expression over parameters (`@x = x.dup.freeze`,
|
|
27
|
+
# `@x = Array(x)`, `@x = f ? x : y`, issue #2321) records the value typed with each parameter it reads bound to
|
|
28
|
+
# its declared type, unless {DerivedParamValue} declines it (a parameter that holds `untyped` at any depth or
|
|
29
|
+
# has a default outside its declared type, or a call argument whose type may match a narrower overload). A
|
|
30
|
+
# value that holds a block, a lambda or a nested `def` is not credited: their own parameters are locals the
|
|
31
|
+
# scan does not bind;
|
|
32
|
+
# - the write is not inside a block or a lambda, so a block parameter of the same name and a block that runs
|
|
33
|
+
# under another `self` (`instance_eval`, `Class.new`, `define_method`) never borrow the method's parameter;
|
|
34
|
+
# - the method body never writes the parameter, in any form and at any depth (`x = Integer(x)`, `x ||= …`,
|
|
35
|
+
# a `rescue => x` or pattern target);
|
|
36
|
+
# - the owner is a class, not a module (a module's ivar belongs to whichever object includes it), and its
|
|
37
|
+
# RBS declares the method itself (`defined_in`), so an inherited signature never types a redefinition;
|
|
38
|
+
# - the signature has exactly one overload, whose parameter lists are typed (`(?)` declines) and mention no
|
|
39
|
+
# type variable (a class `Box[T]` or a method `[T]` variable is unsolved at the write);
|
|
40
|
+
# - the class's primary RBS declaration is the project's (`sig/` or rbs-inline), not core, stdlib or a gem's:
|
|
41
|
+
# a project reopening of a library class (`Thread::SizedQueue#initialize`) would otherwise borrow the
|
|
42
|
+
# library's parameter type for its own `def`;
|
|
43
|
+
# - the body neither calls `binding` nor passes code as an argument to an `eval`-family call
|
|
44
|
+
# (`OPAQUE_EVAL_CALLS`), any of which can rewrite the parameter unseen;
|
|
45
|
+
# - nothing outside the class's own census may write the ivar ({UnseenIvarWriters}): no project ancestor, and
|
|
46
|
+
# no other object's code (`o.instance_variable_set(:@x, v)`);
|
|
47
|
+
# - the declared type is not `untyped`, which is what the write already records.
|
|
48
|
+
module DeclaredParamWrites
|
|
49
|
+
EMPTY = {}.compare_by_identity.freeze
|
|
50
|
+
|
|
51
|
+
SCOPE_BARRIERS = [Prism::DefNode, Prism::ClassNode, Prism::ModuleNode, Prism::SingletonClassNode].freeze
|
|
52
|
+
private_constant :SCOPE_BARRIERS
|
|
53
|
+
|
|
54
|
+
BLOCK_NODES = [Prism::BlockNode, Prism::LambdaNode].freeze
|
|
55
|
+
private_constant :BLOCK_NODES
|
|
56
|
+
|
|
57
|
+
OPAQUE_EVAL_CALLS = %i[eval instance_eval class_eval module_eval instance_exec class_exec
|
|
58
|
+
module_exec].to_set.freeze
|
|
59
|
+
private_constant :OPAQUE_EVAL_CALLS
|
|
60
|
+
|
|
61
|
+
WRITER_SCOPE_KEY = :__rigor_declared_param_writer_scope__
|
|
62
|
+
private_constant :WRITER_SCOPE_KEY
|
|
63
|
+
|
|
64
|
+
module_function
|
|
65
|
+
|
|
66
|
+
# Runs the block with `scope` as the one whose project tables the unseen-writer and single-file gates read, in
|
|
67
|
+
# place of the census's own scope. `rigor sig-gen` indexes each file over the project tables less the member
|
|
68
|
+
# layouts; this scope carries all of them, so another file's writer, ancestor or reopening is seen.
|
|
69
|
+
def with_writer_scope(scope)
|
|
70
|
+
return yield if scope.nil?
|
|
71
|
+
|
|
72
|
+
previous = Thread.current[WRITER_SCOPE_KEY]
|
|
73
|
+
Thread.current[WRITER_SCOPE_KEY] = scope
|
|
74
|
+
begin
|
|
75
|
+
yield
|
|
76
|
+
ensure
|
|
77
|
+
Thread.current[WRITER_SCOPE_KEY] = previous
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def writer_scope(scope) = Thread.current[WRITER_SCOPE_KEY] || scope
|
|
82
|
+
|
|
83
|
+
# `{InstanceVariableWriteNode => Rigor::Type}` for `def_node`'s creditable writes, or {EMPTY}. The caller
|
|
84
|
+
# passes only an instance `def`: a singleton body's `@x` is the class object's.
|
|
85
|
+
def collect(def_node, class_name, scope)
|
|
86
|
+
return EMPTY if def_node.body.nil? || def_node.parameters.nil?
|
|
87
|
+
|
|
88
|
+
written = Set.new
|
|
89
|
+
writes = []
|
|
90
|
+
return EMPTY if scan(def_node.body, written, writes, in_block: false) == :opaque
|
|
91
|
+
|
|
92
|
+
reads = unwritten_reads(writes, written)
|
|
93
|
+
return EMPTY if reads.empty?
|
|
94
|
+
|
|
95
|
+
types = declared_types(def_node, class_name, scope)
|
|
96
|
+
writers = writer_scope(scope)
|
|
97
|
+
credited = reads.each_with_object({}.compare_by_identity) do |(write, names), acc|
|
|
98
|
+
next unless names.all? { |name| types.key?(name) }
|
|
99
|
+
|
|
100
|
+
type = value_type(write.value, names, types, def_node, scope)
|
|
101
|
+
acc[write] = type if type && !UnseenIvarWriters.possible?(writers, class_name, write.name)
|
|
102
|
+
end
|
|
103
|
+
credited.empty? ? EMPTY : credited
|
|
104
|
+
rescue StandardError
|
|
105
|
+
EMPTY
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# `{write => names of the locals its value reads}` for each write whose value reads at least one local and no
|
|
109
|
+
# local the body writes.
|
|
110
|
+
def unwritten_reads(writes, written)
|
|
111
|
+
writes.each_with_object({}.compare_by_identity) do |write, acc|
|
|
112
|
+
names = parameter_reads(write.value)
|
|
113
|
+
next if names.nil? || names.empty? || names.any? { |name| written.include?(name) }
|
|
114
|
+
|
|
115
|
+
acc[write] = names
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The names of the locals `value` reads, or nil when it holds a block, a lambda or a scope barrier.
|
|
120
|
+
def parameter_reads(value, names = Set.new)
|
|
121
|
+
return names unless value.is_a?(Prism::Node)
|
|
122
|
+
return nil if BLOCK_NODES.any? { |klass| value.is_a?(klass) }
|
|
123
|
+
return nil if SCOPE_BARRIERS.any? { |klass| value.is_a?(klass) }
|
|
124
|
+
|
|
125
|
+
names << value.name if value.is_a?(Prism::LocalVariableReadNode)
|
|
126
|
+
value.rigor_each_child do |child|
|
|
127
|
+
return nil if parameter_reads(child, names).nil?
|
|
128
|
+
end
|
|
129
|
+
names
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# A bare read takes the declared type itself; an expression over the parameters is typed with each bound to
|
|
133
|
+
# its declared type, unless {DerivedParamValue} declines it.
|
|
134
|
+
def value_type(value, names, types, def_node, scope)
|
|
135
|
+
return types[value.name] if value.is_a?(Prism::LocalVariableReadNode)
|
|
136
|
+
return nil if DerivedParamValue.declined?(names, types, def_node, scope)
|
|
137
|
+
|
|
138
|
+
bound = names.reduce(scope) { |acc, name| acc.with_local(name, types[name]) }
|
|
139
|
+
return nil if DerivedParamValue.overload_sensitive_argument?(value, bound)
|
|
140
|
+
|
|
141
|
+
bound.type_of(value)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Collects the `@x = …` writes outside every block, and the name of every local the body writes in
|
|
147
|
+
# any form at any depth. A read at depth zero outside a block names the method's own local, which is the
|
|
148
|
+
# parameter when the body never writes the name.
|
|
149
|
+
#
|
|
150
|
+
# Answers `:opaque` when the body can rewrite a local through code the scan cannot read: a `binding` call, or
|
|
151
|
+
# an `eval`-family call given an argument ({OPAQUE_EVAL_CALLS}).
|
|
152
|
+
def scan(node, written, writes, in_block:)
|
|
153
|
+
return unless node.is_a?(Prism::Node)
|
|
154
|
+
return if SCOPE_BARRIERS.any? { |klass| node.is_a?(klass) }
|
|
155
|
+
|
|
156
|
+
case node
|
|
157
|
+
when Prism::LocalVariableWriteNode, Prism::LocalVariableOrWriteNode, Prism::LocalVariableAndWriteNode,
|
|
158
|
+
Prism::LocalVariableOperatorWriteNode, Prism::LocalVariableTargetNode
|
|
159
|
+
written << node.name
|
|
160
|
+
when Prism::CallNode
|
|
161
|
+
return :opaque if opaque_call?(node)
|
|
162
|
+
end
|
|
163
|
+
writes << node if !in_block && node.is_a?(Prism::InstanceVariableWriteNode)
|
|
164
|
+
inner = in_block || BLOCK_NODES.any? { |klass| node.is_a?(klass) }
|
|
165
|
+
node.rigor_each_child do |child|
|
|
166
|
+
return :opaque if scan(child, written, writes, in_block: inner) == :opaque
|
|
167
|
+
end
|
|
168
|
+
nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# `binding` hands the frame's locals to code that may write them (`binding.eval("x = 's'")`,
|
|
172
|
+
# `Helper.rw(binding)`); a string passed to an `eval`-family call is code the scan cannot read.
|
|
173
|
+
def opaque_call?(node)
|
|
174
|
+
return true if node.name == :binding
|
|
175
|
+
|
|
176
|
+
OPAQUE_EVAL_CALLS.include?(node.name) && !(node.arguments&.arguments || []).empty?
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The declared, non-`untyped` type of each parameter, or `{}` when a gate declines the method.
|
|
180
|
+
def declared_types(def_node, class_name, scope)
|
|
181
|
+
environment = scope&.environment
|
|
182
|
+
return {} if environment.nil?
|
|
183
|
+
return {} if environment.rbs_module?(class_name)
|
|
184
|
+
return {} unless Reflection.project_declared_class?(class_name, environment: environment)
|
|
185
|
+
|
|
186
|
+
rbs_method = Reflection.instance_method_definition(class_name, def_node.name, environment: environment)
|
|
187
|
+
return {} unless sole_typed_overload?(rbs_method, class_name)
|
|
188
|
+
|
|
189
|
+
binder = MethodParameterBinder.new(environment: environment, class_path: class_name, singleton: false)
|
|
190
|
+
binder.bind(def_node).reject { |_name, type| type.is_a?(Type::Dynamic) }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def sole_typed_overload?(rbs_method, class_name)
|
|
194
|
+
return false if rbs_method.nil? || rbs_method.defined_in.to_s.delete_prefix("::") != class_name
|
|
195
|
+
|
|
196
|
+
method_types = rbs_method.method_types
|
|
197
|
+
return false unless method_types.size == 1
|
|
198
|
+
|
|
199
|
+
function = method_types.first.type
|
|
200
|
+
function.respond_to?(:required_positionals) && function.free_variables.empty?
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../acceptance"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
module ScopeIndexer
|
|
10
|
+
# Issue #2321 — the declines {DeclaredParamWrites} applies to a write whose value is an expression over declared
|
|
11
|
+
# parameters, beyond the ones a bare read (`@x = x`) shares. Each says the expression's type, evaluated with the
|
|
12
|
+
# parameters bound to their declared types, may not be the type the ivar holds at runtime:
|
|
13
|
+
#
|
|
14
|
+
# - a parameter whose declared type holds `untyped` at any depth (`Array[untyped]`, #2325): the answer is built
|
|
15
|
+
# on the part the author left untyped;
|
|
16
|
+
# - an optional parameter whose default value's type is not within its declared type
|
|
17
|
+
# (`def initialize(x = nil)` under `(?Integer x)`): the value may run on the default, which the binding does not
|
|
18
|
+
# carry;
|
|
19
|
+
# - an argument of any call in the value (`a + b`, `a + (b > 0 ? b : 0.5)`, `a[i]`, `Array(x)`, `super(…)`,
|
|
20
|
+
# `yield(…)`), typed under the parameter bindings, whose type is anything but a literal or an instance of a
|
|
21
|
+
# class no RBS class names as its superclass: the engine picks the first overload such an argument matches
|
|
22
|
+
# (`Integer#+` given `Integer | Float` answers `Integer`), not the join of every overload it may reach. A
|
|
23
|
+
# receiver dispatches per member and is not declined. A splat or keyword argument types as neither, so it
|
|
24
|
+
# declines too.
|
|
25
|
+
module DerivedParamValue
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Whether a write over the parameters `names`, declared `types`, is not credited for a parameter it reads.
|
|
29
|
+
def declined?(names, types, def_node, scope)
|
|
30
|
+
names.any? do |name|
|
|
31
|
+
carries_dynamic?(types[name]) || default_outside?(def_node, name, types[name], scope)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Whether a call in `value` takes an argument whose type, under the parameter bindings of `bound`, may match an
|
|
36
|
+
# overload narrower than itself.
|
|
37
|
+
def overload_sensitive_argument?(value, bound)
|
|
38
|
+
return false unless value.is_a?(Prism::Node)
|
|
39
|
+
|
|
40
|
+
if value.is_a?(Prism::CallNode) || value.is_a?(Prism::SuperNode) || value.is_a?(Prism::YieldNode)
|
|
41
|
+
arguments = value.arguments&.arguments || []
|
|
42
|
+
return true if arguments.any? { |argument| overload_sensitive?(bound.type_of(argument), bound) }
|
|
43
|
+
end
|
|
44
|
+
value.rigor_each_child { |child| return true if overload_sensitive_argument?(child, bound) }
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def carries_dynamic?(type)
|
|
49
|
+
case type
|
|
50
|
+
when Type::Dynamic then true
|
|
51
|
+
when Type::Union, Type::Intersection then type.members.any? { |member| carries_dynamic?(member) }
|
|
52
|
+
when Type::Nominal then type.type_args.any? { |arg| carries_dynamic?(arg) }
|
|
53
|
+
when Type::Tuple then type.elements.any? { |element| carries_dynamic?(element) }
|
|
54
|
+
when Type::HashShape then type.pairs.each_value.any? { |pair_value| carries_dynamic?(pair_value) }
|
|
55
|
+
# No RBS parameter spelling yields a refinement over `untyped`; the arm keeps the walk total.
|
|
56
|
+
when Type::Difference, Type::Refined then carries_dynamic?(type.base)
|
|
57
|
+
else false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Whether an argument of this declared type may match an overload narrower than itself.
|
|
62
|
+
def overload_sensitive?(type, scope)
|
|
63
|
+
case type
|
|
64
|
+
when Type::Constant then false
|
|
65
|
+
when Type::Nominal then !leaf_class?(type.class_name, scope&.environment)
|
|
66
|
+
else true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def leaf_class?(class_name, environment)
|
|
71
|
+
return false if environment.nil? || environment.rbs_module?(class_name)
|
|
72
|
+
# A guard: a validated RBS signature names no unknown class, so this answers only for a broken environment.
|
|
73
|
+
return false unless environment.class_known?(class_name)
|
|
74
|
+
|
|
75
|
+
superclasses = environment.rbs_loader&.superclass_names
|
|
76
|
+
return false if superclasses.nil? || superclasses.empty?
|
|
77
|
+
|
|
78
|
+
!superclasses.include?(class_name.delete_prefix("::"))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Whether `name` is an optional parameter of `def_node` whose default value's type is not within `declared`.
|
|
82
|
+
def default_outside?(def_node, name, declared, scope)
|
|
83
|
+
default = default_value(def_node.parameters, name)
|
|
84
|
+
return false if default.nil?
|
|
85
|
+
|
|
86
|
+
default_type = scope.type_of(default)
|
|
87
|
+
return true if carries_dynamic?(default_type)
|
|
88
|
+
|
|
89
|
+
!Acceptance.accepts(declared, default_type).yes? && !empty_collection_within?(declared, default_type)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# An empty `[]` or `{}` default is within any `Array[T]` or `Hash[K, V]` the declaration names, which the
|
|
93
|
+
# acceptance check answers only `maybe` (the empty literal has no element type to compare).
|
|
94
|
+
def empty_collection_within?(declared, default_type)
|
|
95
|
+
collection =
|
|
96
|
+
if default_type.is_a?(Type::Tuple) && default_type.elements.empty? then "Array"
|
|
97
|
+
elsif default_type.is_a?(Type::HashShape) && default_type.pairs.empty? then "Hash"
|
|
98
|
+
end
|
|
99
|
+
return false if collection.nil?
|
|
100
|
+
|
|
101
|
+
members = declared.is_a?(Type::Union) ? declared.members : [declared]
|
|
102
|
+
members.any? { |member| member.is_a?(Type::Nominal) && member.class_name == collection }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def default_value(parameters, name)
|
|
106
|
+
candidates = parameters.optionals + parameters.keywords
|
|
107
|
+
candidates.find { |param| param.respond_to?(:value) && param.name == name }&.value
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "../../type"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module Inference
|
|
8
|
+
module ScopeIndexer
|
|
9
|
+
# Issue #2499 — the accumulator idiom: a heap slot (an instance, class or global variable, or a constant)
|
|
10
|
+
# seeded with an empty literal and filled later, often from a method, a subclass, a mixin or another file
|
|
11
|
+
# the census cannot see. The seed is never proof that the slot is still empty, so the census records it
|
|
12
|
+
# without its emptiness: `""` and `+""` as `String`, `[]` as `Array[untyped]`, `{}` as
|
|
13
|
+
# `Hash[untyped, untyped]`. `String.new`, `Array.new` and `Hash.new` already type as their nominal.
|
|
14
|
+
#
|
|
15
|
+
# Only the empty spellings count. A non-empty literal (`@order = "asc"`, `MODES = %w[a b]`,
|
|
16
|
+
# `@opts = { a: 1 }`) keeps its exact type: such slots are modes and config tables, and an RBS parameter
|
|
17
|
+
# typed `"asc" | "desc"` must still accept them. A local keeps its flow precision; this applies only where
|
|
18
|
+
# the census seeds a heap slot.
|
|
19
|
+
module EmptySeed
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# `type` with its emptiness dropped when `node` is an empty seed literal, otherwise `type` itself.
|
|
23
|
+
def erase(node, type)
|
|
24
|
+
return type unless empty_seed?(node)
|
|
25
|
+
|
|
26
|
+
members = type.is_a?(Type::Union) ? type.members : [type]
|
|
27
|
+
Type::Combinator.union(*members.map { |member| unpinned(member) })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# `{ name => value node }` for each leading target of a multiple assignment whose right-hand side is a literal
|
|
31
|
+
# list (`@a, @b = {}, []`), so each slot is erased from its own element. Empty for any other right-hand side.
|
|
32
|
+
def multi_write_values(node)
|
|
33
|
+
value = node.value
|
|
34
|
+
return EMPTY_VALUES unless value.is_a?(Prism::ArrayNode)
|
|
35
|
+
|
|
36
|
+
elements = value.elements
|
|
37
|
+
node.lefts.each_with_index.with_object({}) do |(target, index), values|
|
|
38
|
+
break values if index >= elements.size || elements[index].is_a?(Prism::SplatNode)
|
|
39
|
+
next unless target.respond_to?(:name)
|
|
40
|
+
|
|
41
|
+
values[target.name] = elements[index]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
EMPTY_VALUES = {}.freeze
|
|
46
|
+
private_constant :EMPTY_VALUES
|
|
47
|
+
|
|
48
|
+
# A chained write (`@a = @b = ""`) stores the inner write's value in the outer slot too, so it is followed.
|
|
49
|
+
def empty_seed?(node)
|
|
50
|
+
case node
|
|
51
|
+
when Prism::StringNode then node.unescaped.empty?
|
|
52
|
+
when Prism::ArrayNode, Prism::HashNode then node.elements.empty?
|
|
53
|
+
when Prism::CallNode then copied_empty_seed?(node)
|
|
54
|
+
when *CHAINED_WRITES then empty_seed?(node.value)
|
|
55
|
+
else false
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
CHAINED_WRITES = [
|
|
60
|
+
Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode, Prism::ClassVariableWriteNode,
|
|
61
|
+
Prism::GlobalVariableWriteNode, Prism::ConstantWriteNode, Prism::ConstantPathWriteNode
|
|
62
|
+
].freeze
|
|
63
|
+
private_constant :CHAINED_WRITES
|
|
64
|
+
|
|
65
|
+
# `+""`, the unfrozen empty String the accumulator idiom spells under `# frozen_string_literal: true`, and a
|
|
66
|
+
# `.dup` of an empty seed (`"".dup`, `[].dup`, `{}.dup`).
|
|
67
|
+
def copied_empty_seed?(node)
|
|
68
|
+
return false unless node.arguments.nil? && node.block.nil?
|
|
69
|
+
|
|
70
|
+
case node.name
|
|
71
|
+
when :+@ then node.receiver.is_a?(Prism::StringNode) && node.receiver.unescaped.empty?
|
|
72
|
+
when :dup then empty_seed?(node.receiver)
|
|
73
|
+
else false
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def unpinned(member)
|
|
78
|
+
case member
|
|
79
|
+
when Type::Constant
|
|
80
|
+
member.value.is_a?(String) && member.value.empty? ? Type::Combinator.nominal_of("String") : member
|
|
81
|
+
when Type::Tuple
|
|
82
|
+
if member.elements.empty?
|
|
83
|
+
Type::Combinator.nominal_of("Array",
|
|
84
|
+
type_args: [Type::Combinator.untyped])
|
|
85
|
+
else
|
|
86
|
+
member
|
|
87
|
+
end
|
|
88
|
+
when Type::HashShape
|
|
89
|
+
return member unless member.pairs.empty?
|
|
90
|
+
|
|
91
|
+
Type::Combinator.nominal_of("Hash", type_args: [Type::Combinator.untyped, Type::Combinator.untyped])
|
|
92
|
+
else member
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
private_class_method :copied_empty_seed?, :unpinned
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../analysis/dependency_recorder"
|
|
4
|
+
require_relative "../../run_scoped_slots"
|
|
5
|
+
require_relative "../../type"
|
|
6
|
+
require_relative "../refined_dispatch/own_method"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
module ScopeIndexer
|
|
11
|
+
# Issues #2079 and #2170 — the instance-side ivar writes the class-ivar census of one file does not walk, joined
|
|
12
|
+
# into the ivars that census already holds for a class `K`:
|
|
13
|
+
#
|
|
14
|
+
# - the writes another file's reopening of `K` makes;
|
|
15
|
+
# - the writes of every project module and superclass on `K`'s instance resolution chain (`include`,
|
|
16
|
+
# `prepend`, transitively, and `<`), from this file's own census of that ancestor where it declares one
|
|
17
|
+
# (`writes`, taken before the read-before-write `nil` is added, so an ancestor's `initialize` exempts nothing
|
|
18
|
+
# and its read-before-write adds nothing, and only for an ivar an instance body of the ancestor writes in this
|
|
19
|
+
# file) and from every other file's entries for it.
|
|
20
|
+
#
|
|
21
|
+
# Another file's write comes from its `[:own_write_type, owner, ivar, value, path]` census entry
|
|
22
|
+
# ({IvarWriterCensus}): a literal joins as its `Constant`, anything else as `Dynamic[top]`. An ivar the census
|
|
23
|
+
# has no entry for gains none, so a read of an ivar only another file writes still reads `Dynamic[top]`. The
|
|
24
|
+
# join only widens, and every joined type is in the seed, so a `nil` it brings is declaration-sourced (ADR-58
|
|
25
|
+
# WD1). A file the scope names no path for (a census built outside the project pre-pass, `rigor sig-gen`'s
|
|
26
|
+
# unseeded scopes) joins nothing.
|
|
27
|
+
#
|
|
28
|
+
# ADR-46: each ivar read files `own_write_type:<owner>#<ivar>` for `K` and each chain owner, which the
|
|
29
|
+
# incremental session fires when a file's entries for the pair appear, vanish or change value; and the chain
|
|
30
|
+
# files its class edges ({Scope::ResolutionChain#record}), so an `include` added or removed re-checks the reader.
|
|
31
|
+
module IvarContributions
|
|
32
|
+
TABLE_KEY = RunScopedSlots.register(:__rigor_ivar_contributions_table__)
|
|
33
|
+
EMPTY = {}.freeze
|
|
34
|
+
private_constant :TABLE_KEY, :EMPTY
|
|
35
|
+
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
def join!(accumulator, writes, scope)
|
|
39
|
+
path = scope.source_path
|
|
40
|
+
return if path.nil?
|
|
41
|
+
|
|
42
|
+
path = canonical(path)
|
|
43
|
+
|
|
44
|
+
table = table(scope.discovered_global_write_census)
|
|
45
|
+
accumulator.each do |class_name, entries|
|
|
46
|
+
next unless class_name.is_a?(String) && !entries.empty?
|
|
47
|
+
|
|
48
|
+
owners = chain_owners(scope, class_name)
|
|
49
|
+
entries.each_key do |ivar|
|
|
50
|
+
joined = contributed(owners, class_name, ivar, table, writes, path)
|
|
51
|
+
entries[ivar] = Type::Combinator.union(entries[ivar], joined) if joined
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# `K` and the project classes and modules on its instance chain, in chain order, with their class edges filed.
|
|
57
|
+
# A module's own census takes no chain: its ancestors' writes land on whatever includes it.
|
|
58
|
+
def chain_owners(scope, class_name)
|
|
59
|
+
return [class_name] if RefinedDispatch::OwnMethod.module_name?(scope, class_name)
|
|
60
|
+
|
|
61
|
+
chain = Scope::ResolutionChain.for(scope, class_name, :instance, :methods)
|
|
62
|
+
# A guard: the class body's own `include` / `<` resolution already files these edges; kept so the read
|
|
63
|
+
# states its own dependency.
|
|
64
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
65
|
+
ancestors = chain.entries.filter_map do |entry|
|
|
66
|
+
# An external entry has no name, so `filter_map` drops it. The side check is a guard: an instance chain
|
|
67
|
+
# holds only instance-side entries.
|
|
68
|
+
entry.name if entry.side == :instance && entry.name != class_name
|
|
69
|
+
end
|
|
70
|
+
[class_name, *ancestors.uniq]
|
|
71
|
+
rescue StandardError
|
|
72
|
+
[class_name]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The union of what the owners contribute to `ivar`, or nil for nothing.
|
|
76
|
+
def contributed(owners, class_name, ivar, table, writes, path)
|
|
77
|
+
types = []
|
|
78
|
+
owners.each do |owner|
|
|
79
|
+
if Analysis::DependencyRecorder.active?
|
|
80
|
+
Analysis::DependencyRecorder.read_name(:own_write_type, "#{owner}##{ivar}")
|
|
81
|
+
end
|
|
82
|
+
by_path = table.dig(owner, ivar) || EMPTY
|
|
83
|
+
# This file's census of an ancestor counts only an ivar an instance body of it writes here: the census
|
|
84
|
+
# also holds a `def self.` write of a module (#1958), which runs on the module object.
|
|
85
|
+
own = writes.dig(owner, ivar) if owner != class_name && by_path.key?(path)
|
|
86
|
+
types << own if own
|
|
87
|
+
by_path.each do |value_path, values|
|
|
88
|
+
next if value_path == path
|
|
89
|
+
|
|
90
|
+
values.each { |value| types << value_type(value) }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
types.empty? ? nil : Type::Combinator.union(*types)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# A String literal joins as `String`, not its `Constant`: a write elsewhere cannot see an in-place mutation of
|
|
97
|
+
# the String (`@s << "abc"`) in this file, in another or in an ancestor, so the value would be pinned too
|
|
98
|
+
# narrowly. Every other literal kind is immutable and joins as its `Constant`.
|
|
99
|
+
def value_type(value)
|
|
100
|
+
return Type::Combinator.untyped unless value.is_a?(Array)
|
|
101
|
+
|
|
102
|
+
literal = value[1]
|
|
103
|
+
literal.is_a?(String) ? Type::Combinator.nominal_of("String") : Type::Combinator.constant_of(literal)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# `path` resolved through symlinks, so a file the pre-pass tagged under one spelling (`/tmp/…`) is the one
|
|
107
|
+
# analysed under another (`/private/tmp/…`); the expanded spelling when it cannot be resolved.
|
|
108
|
+
def canonical(path)
|
|
109
|
+
File.realpath(path)
|
|
110
|
+
rescue SystemCallError
|
|
111
|
+
File.expand_path(path)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# `{ owner => { ivar => { path => [value, ...] } } }` over `census`'s path-tagged `:own_write_type` entries,
|
|
115
|
+
# built once per census and kept in one run-scoped slot keyed by its identity.
|
|
116
|
+
def table(census)
|
|
117
|
+
slot = Thread.current[TABLE_KEY]
|
|
118
|
+
return slot[1] if slot && slot[0].equal?(census)
|
|
119
|
+
|
|
120
|
+
table = {}
|
|
121
|
+
canonical_paths = {}
|
|
122
|
+
census.each do |entry|
|
|
123
|
+
next unless entry.size == 5 && entry[0] == :own_write_type
|
|
124
|
+
|
|
125
|
+
by_path = ((table[entry[1]] ||= {})[entry[2]] ||= {})
|
|
126
|
+
(by_path[canonical_paths[entry[4]] ||= canonical(entry[4])] ||= []) << entry[3]
|
|
127
|
+
end
|
|
128
|
+
Thread.current[TABLE_KEY] = [census, table]
|
|
129
|
+
table
|
|
130
|
+
end
|
|
131
|
+
private_class_method :chain_owners, :contributed, :value_type, :canonical, :table
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|