rigortype 0.4.7 → 0.4.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 +3 -1
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +29 -0
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- data/docs/handbook/01-getting-started.md +1 -1
- data/docs/handbook/11-sig-gen.md +35 -0
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +307 -91
- data/docs/manual/03-configuration.md +41 -3
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +8 -0
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +15 -6
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +3 -0
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +8 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +13 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -0
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +40 -11
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +8 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +598 -99
- data/lib/rigor/analysis/crash_signature.rb +43 -1
- data/lib/rigor/analysis/dependency_recorder.rb +233 -76
- data/lib/rigor/analysis/incremental.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +279 -37
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +37 -1
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +4 -0
- data/lib/rigor/analysis/runner/declaration_position.rb +7 -7
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +72 -20
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +3 -8
- data/lib/rigor/analysis/runner/pool_coordinator.rb +26 -5
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- data/lib/rigor/analysis/runner.rb +222 -39
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +61 -22
- data/lib/rigor/analysis/worker_session.rb +42 -41
- data/lib/rigor/bleeding_edge.rb +19 -4
- data/lib/rigor/cache/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +66 -7
- data/lib/rigor/cache/rbs_descriptor.rb +37 -2
- data/lib/rigor/cache/rbs_environment.rb +5 -1
- data/lib/rigor/cli/baseline_command.rb +15 -2
- data/lib/rigor/cli/check_command.rb +4 -2
- data/lib/rigor/cli/effects_snapshot_command.rb +8 -1
- data/lib/rigor/cli/lens_command.rb +17 -2
- data/lib/rigor/cli/sig_gen_command.rb +8 -0
- data/lib/rigor/cli/skill_describe.rb +1 -0
- data/lib/rigor/cli.rb +5 -2
- data/lib/rigor/config_audit.rb +32 -18
- data/lib/rigor/configuration/severity_profile.rb +9 -0
- data/lib/rigor/configuration.rb +32 -5
- data/lib/rigor/effects/collector.rb +53 -21
- data/lib/rigor/environment/bundle_sig_discovery.rb +63 -28
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/default_libraries.rb +17 -0
- data/lib/rigor/environment/deferred_signature_plan.rb +104 -6
- data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -0
- data/lib/rigor/environment/rbs_loader.rb +312 -63
- data/lib/rigor/environment.rb +131 -35
- data/lib/rigor/inference/acceptance.rb +50 -15
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_run_count.rb +9 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +56 -15
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- data/lib/rigor/inference/def_node_resolver.rb +32 -14
- data/lib/rigor/inference/def_return_typer.rb +1 -1
- data/lib/rigor/inference/definer_resolution.rb +15 -7
- data/lib/rigor/inference/definers_index.rb +72 -16
- data/lib/rigor/inference/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1459 -287
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +8 -6
- data/lib/rigor/inference/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +57 -8
- data/lib/rigor/inference/ivar_writer_census.rb +106 -11
- data/lib/rigor/inference/key_presence_guard.rb +51 -25
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +63 -15
- data/lib/rigor/inference/match_rebinding/frame.rb +11 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +74 -18
- data/lib/rigor/inference/match_rebinding.rb +21 -8
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +43 -29
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +12 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +409 -67
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +384 -64
- data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
- data/lib/rigor/inference/method_dispatcher.rb +66 -18
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +13 -2
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_widening.rb +44 -4
- data/lib/rigor/inference/narrowing.rb +201 -27
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +133 -8
- data/lib/rigor/inference/origin_lookup.rb +1 -1
- data/lib/rigor/inference/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +28 -10
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +56 -18
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -17
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +76 -22
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +12 -11
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +188 -28
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2079 -183
- data/lib/rigor/inference/self_call_ivar_writes.rb +26 -7
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +41 -6
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +970 -250
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +35 -11
- data/lib/rigor/inference/toplevel_def_census.rb +14 -6
- data/lib/rigor/inference/unknown_store_widening.rb +1 -1
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -10
- data/lib/rigor/lens/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/io_boundary.rb +25 -3
- data/lib/rigor/plugin/manifest.rb +7 -3
- data/lib/rigor/plugin/registry.rb +13 -2
- data/lib/rigor/plugin/trust_policy.rb +46 -0
- data/lib/rigor/protection/mutation_cache.rb +4 -1
- data/lib/rigor/reflection/constant_ancestors.rb +79 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +18 -1
- data/lib/rigor/run_scoped_slots.rb +20 -20
- data/lib/rigor/scope/discovery_index.rb +149 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +900 -55
- data/lib/rigor/scope.rb +307 -34
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +14 -1
- data/lib/rigor/sig_gen/generator.rb +222 -13
- data/lib/rigor/sig_gen/method_candidate.rb +14 -0
- data/lib/rigor/sig_gen/observation_collector.rb +11 -2
- data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +47 -0
- data/lib/rigor/sig_gen/writer.rb +21 -0
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- data/lib/rigor/type/combinator.rb +164 -8
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +2 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +57 -25
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +130 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +49 -13
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +4 -2
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +13 -5
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +29 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +6 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +199 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +3 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +209 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +80 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +1 -6
- data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -3
- data/sig/rigor/cache/descriptor.rbs +14 -0
- data/sig/rigor/environment.rbs +4 -1
- data/sig/rigor/plugin/base.rbs +53 -19
- data/sig/rigor/plugin/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -3
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +22 -3
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- data/skills/rigor-project-init/references/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +5 -1
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +48 -2
- data/lib/rigor/inference/memo_audit.rb +0 -55
|
@@ -12,11 +12,13 @@ require_relative "../type"
|
|
|
12
12
|
require_relative "diagnostic"
|
|
13
13
|
require_relative "dependency_recorder"
|
|
14
14
|
require_relative "check_rules/rule_ids"
|
|
15
|
+
require_relative "check_rules/call_facts"
|
|
15
16
|
require_relative "check_rules/inferred_param_guard"
|
|
16
17
|
require_relative "check_rules/declaration_sourced_guard"
|
|
17
18
|
require_relative "check_rules/published_constant_guard"
|
|
18
19
|
require_relative "check_rules/dead_version_guard_arms"
|
|
19
20
|
require_relative "check_rules/rule_walk"
|
|
21
|
+
require_relative "check_rules/always_raises_shield"
|
|
20
22
|
require_relative "check_rules/always_truthy_condition_collector"
|
|
21
23
|
require_relative "check_rules/unreachable_clause_collector"
|
|
22
24
|
require_relative "check_rules/shadowed_rescue_collector"
|
|
@@ -33,7 +35,8 @@ require_relative "check_rules/rebinding_block_callees"
|
|
|
33
35
|
require_relative "check_rules/rbs_receiver_surface"
|
|
34
36
|
require_relative "check_rules/special_global_setters"
|
|
35
37
|
require_relative "check_rules/ruby_deprecations"
|
|
36
|
-
require_relative "
|
|
38
|
+
require_relative "check_rules/plugin_api_surface"
|
|
39
|
+
require_relative "../typing_state"
|
|
37
40
|
|
|
38
41
|
module Rigor
|
|
39
42
|
module Analysis
|
|
@@ -110,7 +113,8 @@ module Rigor
|
|
|
110
113
|
diagnostics = collectors[:main_pass].results.dup
|
|
111
114
|
diagnostics.concat(self_undefined_method_diagnostics(path, self_call_misses, root, scope_index))
|
|
112
115
|
COLLECTOR_DIAGNOSTIC_BUILDERS.each do |role, builder|
|
|
113
|
-
|
|
116
|
+
collector = OPTIONAL_COLLECTOR_ROLES.include?(role) ? collectors[role] : collectors.fetch(role)
|
|
117
|
+
diagnostics.concat(send(builder, path, collector.results)) if collector
|
|
114
118
|
end
|
|
115
119
|
# ADR-47 WD5 — the dead arm of a decidable version guard (#627) reports nothing: it cannot run on
|
|
116
120
|
# the Ruby being checked with, so a diagnostic there is a false positive. Applied before
|
|
@@ -141,18 +145,25 @@ module Rigor
|
|
|
141
145
|
ivar_writes: :ivar_write_mismatch_diagnostics,
|
|
142
146
|
dead_assignments: :dead_assignment_diagnostics,
|
|
143
147
|
duplicate_hash_keys: :duplicate_hash_key_diagnostics,
|
|
144
|
-
return_in_ensure: :return_in_ensure_diagnostics
|
|
148
|
+
return_in_ensure: :return_in_ensure_diagnostics,
|
|
149
|
+
plugin_api_surface: :plugin_api_surface_diagnostics
|
|
145
150
|
}.freeze
|
|
146
151
|
private_constant :COLLECTOR_DIAGNOSTIC_BUILDERS
|
|
147
152
|
|
|
153
|
+
# The roles {.build_node_collectors} builds only on request: `plugin_api_surface`, when the rule is active
|
|
154
|
+
# ({PluginApiSurface.active?}). {.diagnose} skips an absent optional role; every other role is required, and a
|
|
155
|
+
# missing one raises.
|
|
156
|
+
OPTIONAL_COLLECTOR_ROLES = %i[plugin_api_surface].freeze
|
|
157
|
+
|
|
148
158
|
# The verbatim per-node dispatch of the former inline main pass
|
|
149
159
|
# (`diagnose`'s `Source::NodeWalker.each` `case`), now invoked by
|
|
150
160
|
# {MainPassCollector} on the shared {RuleWalk}. Returns the
|
|
151
161
|
# diagnostics for one node, in the same emission order as before.
|
|
152
|
-
def main_pass_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil
|
|
162
|
+
def main_pass_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil,
|
|
163
|
+
raise_shield = nil)
|
|
153
164
|
case node
|
|
154
165
|
when Prism::CallNode
|
|
155
|
-
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites)
|
|
166
|
+
call_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
156
167
|
when Prism::DefNode
|
|
157
168
|
[
|
|
158
169
|
refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites),
|
|
@@ -174,7 +185,8 @@ module Rigor
|
|
|
174
185
|
# check in the body stands. The refine-body test runs only once a mismatch would report, so a file pays the
|
|
175
186
|
# {LexicalMethodSites} walk only then.
|
|
176
187
|
def refinement_aware_return_type_mismatch(path, node, scope_index, lexical_sites)
|
|
177
|
-
|
|
188
|
+
replaced = lexical_sites&.replaced_singleton_defs(scope_index)
|
|
189
|
+
diagnostic = return_type_mismatch_diagnostic(path, node, scope_index, replaced)
|
|
178
190
|
return diagnostic if diagnostic.nil? || lexical_sites.nil?
|
|
179
191
|
|
|
180
192
|
lexical_sites.refinements.refinement_def?(node) ? nil : diagnostic
|
|
@@ -187,11 +199,17 @@ module Rigor
|
|
|
187
199
|
# traversal, and hand the populated set back to {.diagnose} as
|
|
188
200
|
# `node_collectors:`. The main pass needs `path` because its per-node
|
|
189
201
|
# diagnostics carry it (ADR-53 B3c hosts it on the same walk).
|
|
190
|
-
|
|
202
|
+
#
|
|
203
|
+
# `plugin_api_surface:` adds the `plugin.api-surface` collector, which the runner asks for only when the rule
|
|
204
|
+
# resolves to a reporting severity ({PluginApiSurface.active?}).
|
|
205
|
+
def build_node_collectors(path, scope_index, root = nil, plugin_api_surface: false)
|
|
191
206
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
192
207
|
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
193
|
-
|
|
194
|
-
|
|
208
|
+
raise_shield = root && AlwaysRaisesShield.new(root)
|
|
209
|
+
main_pass = lambda do |node|
|
|
210
|
+
main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
211
|
+
end
|
|
212
|
+
collectors = {
|
|
195
213
|
main_pass: MainPassCollector.new(main_pass),
|
|
196
214
|
void_value_use: VoidValueUseCollector.new(scope_index),
|
|
197
215
|
always_truthy: AlwaysTruthyConditionCollector.new(scope_index),
|
|
@@ -202,6 +220,8 @@ module Rigor
|
|
|
202
220
|
duplicate_hash_keys: DuplicateHashKeyCollector.new(scope_index),
|
|
203
221
|
return_in_ensure: ReturnInEnsureCollector.new(scope_index)
|
|
204
222
|
}
|
|
223
|
+
collectors[:plugin_api_surface] = PluginApiSurface::Collector.new(scope_index) if plugin_api_surface
|
|
224
|
+
collectors
|
|
205
225
|
end
|
|
206
226
|
|
|
207
227
|
# A {RuleWalk::CollectorDriver} over a built-in collector set, for a
|
|
@@ -279,8 +299,11 @@ module Rigor
|
|
|
279
299
|
diagnostics = []
|
|
280
300
|
eval_ranges = receiver_eval_block_ranges(root)
|
|
281
301
|
lexical_sites = LexicalMethodSites.new(root, stamped_refinements(scope_index, root))
|
|
302
|
+
raise_shield = AlwaysRaisesShield.new(root)
|
|
282
303
|
Source::NodeWalker.each(root) do |node|
|
|
283
|
-
diagnostics.concat(
|
|
304
|
+
diagnostics.concat(
|
|
305
|
+
main_pass_node_diagnostics(path, node, scope_index, eval_ranges, lexical_sites, raise_shield)
|
|
306
|
+
)
|
|
284
307
|
end
|
|
285
308
|
diagnostics
|
|
286
309
|
end
|
|
@@ -300,18 +323,22 @@ module Rigor
|
|
|
300
323
|
shadow_verify_node_collectors(path, root, scope_index, collectors)
|
|
301
324
|
end
|
|
302
325
|
|
|
303
|
-
|
|
326
|
+
# Issue #2786 — the rules that type the call's receiver read it from one {CallFacts}, so the receiver chain is
|
|
327
|
+
# typed once per call node rather than once per rule.
|
|
328
|
+
def call_node_diagnostics(path, node, scope_index, eval_ranges = nil, lexical_sites = nil,
|
|
329
|
+
raise_shield = nil)
|
|
330
|
+
facts = CallFacts.new(node, scope_index)
|
|
304
331
|
[
|
|
305
|
-
undefined_method_diagnostic(path, node,
|
|
332
|
+
undefined_method_diagnostic(path, node, facts, lexical_sites),
|
|
306
333
|
unresolved_toplevel_diagnostic(path, node, scope_index, eval_ranges, lexical_sites),
|
|
307
|
-
wrong_arity_diagnostic(path, node,
|
|
308
|
-
argument_type_diagnostic(path, node,
|
|
309
|
-
nil_receiver_diagnostic(path, node,
|
|
334
|
+
wrong_arity_diagnostic(path, node, facts, lexical_sites),
|
|
335
|
+
argument_type_diagnostic(path, node, facts, lexical_sites),
|
|
336
|
+
nil_receiver_diagnostic(path, node, facts, lexical_sites),
|
|
310
337
|
dump_type_diagnostic(path, node, scope_index),
|
|
311
338
|
assert_type_diagnostic(path, node, scope_index),
|
|
312
|
-
always_raises_diagnostic(path, node,
|
|
339
|
+
always_raises_diagnostic(path, node, facts, lexical_sites, raise_shield),
|
|
313
340
|
raise_non_exception_diagnostic(path, node, scope_index),
|
|
314
|
-
visibility_mismatch_diagnostic(path, node,
|
|
341
|
+
visibility_mismatch_diagnostic(path, node, facts, lexical_sites)
|
|
315
342
|
].compact
|
|
316
343
|
end
|
|
317
344
|
|
|
@@ -407,6 +434,11 @@ module Rigor
|
|
|
407
434
|
end
|
|
408
435
|
end
|
|
409
436
|
|
|
437
|
+
# ADR-128 WD6 / ADR-130 slice 2 — `plugin.api-surface`, from its collector's results.
|
|
438
|
+
def plugin_api_surface_diagnostics(path, results)
|
|
439
|
+
PluginApiSurface.diagnostics(path, results)
|
|
440
|
+
end
|
|
441
|
+
|
|
410
442
|
# ADR-100 WD2 — `static.value-use.void`. Its value-context slot inspection rides the shared per-node
|
|
411
443
|
# {RuleWalk} like the other collectors, so the file is not re-traversed for it. Each result is a
|
|
412
444
|
# value-context use of a call whose author-declared `-> void` return the engine recovered to `top`.
|
|
@@ -740,10 +772,10 @@ module Rigor
|
|
|
740
772
|
class << self
|
|
741
773
|
private
|
|
742
774
|
|
|
743
|
-
def undefined_method_diagnostic(path, call_node,
|
|
775
|
+
def undefined_method_diagnostic(path, call_node, facts, lexical_sites = nil) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
744
776
|
return nil if call_node.receiver.nil?
|
|
745
777
|
|
|
746
|
-
scope =
|
|
778
|
+
scope = facts.scope
|
|
747
779
|
return nil if scope.nil?
|
|
748
780
|
|
|
749
781
|
# The exemptions that hold whatever shape the receiver has. Ahead of the receiver-shape
|
|
@@ -776,7 +808,7 @@ module Rigor
|
|
|
776
808
|
# below — preserving that keeps `&.` from newly firing on the
|
|
777
809
|
# non-nil constituent (which, for a cross-file project def, would
|
|
778
810
|
# be a working-code false positive).
|
|
779
|
-
receiver_type = safe_navigation_receiver(call_node,
|
|
811
|
+
receiver_type = safe_navigation_receiver(call_node, facts.receiver_type)
|
|
780
812
|
|
|
781
813
|
# #320 — the private-singleton-object idiom (`class << Merger = Object.new`). The body's methods
|
|
782
814
|
# are recorded on the constant's own name, but the receiver reads back as `Object`, so the
|
|
@@ -973,16 +1005,268 @@ module Rigor
|
|
|
973
1005
|
call_node: call_node)
|
|
974
1006
|
return true if module_mixin_receiver?(receiver_type, scope)
|
|
975
1007
|
return true if mixin_self_class_receiver?(call_node, scope)
|
|
976
|
-
return true if unknown_mixin_includer?(class_name, scope)
|
|
1008
|
+
return true if unknown_mixin_includer?(class_name, scope, kind)
|
|
977
1009
|
return true if object_mixin_may_define?(receiver_type, class_name, call_node.name, scope)
|
|
978
1010
|
return true if ancestry_declares_method?(scope, class_name, call_node.name, kind)
|
|
1011
|
+
return true if open_names_through_ancestors?(scope, class_name, kind)
|
|
1012
|
+
return true if foreign_singleton_through_ancestors?(scope, class_name, call_node.name, kind)
|
|
979
1013
|
|
|
980
1014
|
rbs_ancestor_may_answer?(class_name, call_node.name, scope, kind)
|
|
981
1015
|
end
|
|
982
1016
|
|
|
1017
|
+
# Issue #2793 — the class, or a project class or module on its chain, carries
|
|
1018
|
+
# `Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK`: a macro there defines methods under names its syntax
|
|
1019
|
+
# does not spell (`def_delegators :@x, *NAMES`), so no name is provably missing. A chain cut at its budget
|
|
1020
|
+
# never reaches here: {#ancestry_declares_method?} reads the same chain first and declines on the cut.
|
|
1021
|
+
# A non-literal `define_method` records its own side's twin of the mark instead, read beside it here.
|
|
1022
|
+
OPEN_NAMES_MARKS = {
|
|
1023
|
+
instance: [Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK,
|
|
1024
|
+
Scope::DiscoveryIndex::ENVELOPE_OPEN_INSTANCE_NAMES_MARK].freeze,
|
|
1025
|
+
singleton: [Scope::DiscoveryIndex::ENVELOPE_OPEN_NAMES_MARK,
|
|
1026
|
+
Scope::DiscoveryIndex::ENVELOPE_OPEN_SINGLETON_NAMES_MARK].freeze
|
|
1027
|
+
}.freeze
|
|
1028
|
+
private_constant :OPEN_NAMES_MARKS
|
|
1029
|
+
|
|
1030
|
+
def open_names_through_ancestors?(scope, class_name, kind)
|
|
1031
|
+
side = kind == :singleton ? :singleton : :instance
|
|
1032
|
+
marks = OPEN_NAMES_MARKS.fetch(side)
|
|
1033
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, side, :methods)
|
|
1034
|
+
# A project class with no `def` of its own is held as an external entry (#2094), so an external's
|
|
1035
|
+
# candidate names are asked too.
|
|
1036
|
+
found = chain.search(scope) do |entry|
|
|
1037
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1038
|
+
names.any? do |name|
|
|
1039
|
+
bucket = scope.parameter_envelopes_of(name)
|
|
1040
|
+
marks.any? { |mark| bucket.key?(mark) } ||
|
|
1041
|
+
(side == :instance && (computed_definer_called?(scope, name, bucket) ||
|
|
1042
|
+
hook_block_macro_defines?(scope, name)))
|
|
1043
|
+
end
|
|
1044
|
+
end
|
|
1045
|
+
!found.nil?
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
# Issue #3018 — a class object's singleton method that `def Foo.x`, `Foo.define_singleton_method(:x)` or a
|
|
1049
|
+
# `Foo.singleton_class.class_eval` def defines outside `Foo`'s body records no `:singleton` row, only an
|
|
1050
|
+
# envelope entry. The class-object path reads the definer's own entry
|
|
1051
|
+
# (`Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND`) on the chain's class-object entries, the class and
|
|
1052
|
+
# its superclasses, so a subclass inherits it. An extended module's entry is instance-side: `extend M` adds
|
|
1053
|
+
# `M`'s instance methods, not a `def M.x`. It does not read the opaque `[:singleton, x]` entry beside it, which
|
|
1054
|
+
# a name-rewriting class-body macro (`my_attr :x`) files too without defining `x` on the class object.
|
|
1055
|
+
def foreign_singleton_through_ancestors?(scope, class_name, method_name, kind)
|
|
1056
|
+
return false unless kind == :singleton
|
|
1057
|
+
|
|
1058
|
+
key = [Scope::DiscoveryIndex::ENVELOPE_OBJECT_SINGLETON_KIND, method_name.to_sym]
|
|
1059
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :singleton, :methods)
|
|
1060
|
+
found = chain.search(scope, side: :singleton) do |entry|
|
|
1061
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1062
|
+
names.any? { |name| scope.parameter_envelopes_of(name).key?(key) }
|
|
1063
|
+
end
|
|
1064
|
+
!found.nil?
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Issue #2901 — the body of `name` calls, on `self`, a method its singleton chain resolves to a computed
|
|
1068
|
+
# definer (`Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND`): a class macro of its own, a
|
|
1069
|
+
# superclass's or an extended module's, which runs `define_method` under a name no table holds. The first
|
|
1070
|
+
# entry on that chain carrying the macro's definer entry answers; a closer override that defines nothing is
|
|
1071
|
+
# not looked for, so it leaves the class open.
|
|
1072
|
+
#
|
|
1073
|
+
# Issue #2975 — the definer entry carries its side: the class's own singleton entry answers with a singleton
|
|
1074
|
+
# method (`def self.my_attr`), an extended module's entry with an instance method, so an instance `def my_attr`
|
|
1075
|
+
# of the class itself opens nothing. A macro patched onto `Module` (or `Class`, `Object`, `Kernel`,
|
|
1076
|
+
# `BasicObject`) as an instance method is on every class object's singleton chain past its own classes.
|
|
1077
|
+
def computed_definer_called?(scope, name, bucket)
|
|
1078
|
+
macros = bucket.each_key.filter_map do |key|
|
|
1079
|
+
key.last if key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_BODY_CALL_KIND
|
|
1080
|
+
end
|
|
1081
|
+
return false if macros.empty?
|
|
1082
|
+
|
|
1083
|
+
chain = Scope::ResolutionChain.for(scope, name.to_s, :singleton, :methods)
|
|
1084
|
+
found = macros.any? do |macro|
|
|
1085
|
+
!chain.search(scope) { |entry| macro_entry_defines?(scope, entry, macro) }.nil? ||
|
|
1086
|
+
universal_macro_defines?(scope, macro)
|
|
1087
|
+
end
|
|
1088
|
+
found || hook_extended_macro_defines?(scope, name, macros)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
# Issue #2975 — a module that a class on `name`'s superclass chain includes or prepends directly, and whose
|
|
1092
|
+
# `included` / `prepended` hook extends its includer with a module (`def self.included(base) =
|
|
1093
|
+
# base.extend(ClassMethods)`), puts that module's instance methods on `name`'s singleton chain, which records no
|
|
1094
|
+
# edge for it. Ruby runs the hook for the direct includer only, so a module included through another module does
|
|
1095
|
+
# not count; the chain's levels flatten a class's transitive mixins, so each level class's own mixin list is
|
|
1096
|
+
# read, one hop, never followed further. Each mixin and each extended constant resolves to the first candidate
|
|
1097
|
+
# the project declares, the innermost cref first, whether or not RBS declares the modules; a concern's
|
|
1098
|
+
# `ClassMethods` resolves as `const_get` does instead (#2986).
|
|
1099
|
+
def hook_extended_macro_defines?(scope, name, macros)
|
|
1100
|
+
definers = macro_definers(macros)
|
|
1101
|
+
direct_hook_mixins(scope, name).any? { |mixin, _edges| hook_extension_defines?(scope, mixin, definers) }
|
|
1102
|
+
end
|
|
1103
|
+
|
|
1104
|
+
# Issue #2986 — an ActiveSupport::Concern's `included do … end` block runs in the class body of a class that
|
|
1105
|
+
# includes the concern directly, and its `prepended do … end` block in one that prepends it, so a macro call
|
|
1106
|
+
# in the block is read as that class's own, against the module the concern extends it with. A call in the
|
|
1107
|
+
# concern's own module body is no includer's: Ruby raises there as the module loads.
|
|
1108
|
+
#
|
|
1109
|
+
# Issue #2992 — Ruby runs the block in every direct includer, a module included, so the read is made for each
|
|
1110
|
+
# entry of the receiver's chain (`owner`) against that entry's own mixins, one hop. A concern included into a
|
|
1111
|
+
# concern after its `extend ActiveSupport::Concern` runs no block there: `Concern#append_features` records it as
|
|
1112
|
+
# a dependency, and the outer concern's final includer includes it, on that includer's own edge
|
|
1113
|
+
# ({#with_concern_dependencies}). Only a mixin whose one `include` / `prepend` statement is such a dependency
|
|
1114
|
+
# is left out of `owner`'s own read: one written ahead of the `extend`, or again in a reopening that may load
|
|
1115
|
+
# first, is an ordinary include there.
|
|
1116
|
+
def hook_block_macro_defines?(scope, owner)
|
|
1117
|
+
own_mixin_edges(scope, owner, deferred: true).any? do |mixin, edges|
|
|
1118
|
+
macros = scope.parameter_envelopes_of(mixin).each_key.filter_map do |key|
|
|
1119
|
+
key[2] if key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_HOOK_BODY_CALL_KIND &&
|
|
1120
|
+
edges.include?(Scope::ResolutionChain.effective_hook(scope.discovery, mixin, key[1]))
|
|
1121
|
+
end
|
|
1122
|
+
!macros.empty? && hook_extension_defines?(scope, mixin, macro_definers(macros))
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
def macro_definers(macros)
|
|
1127
|
+
macros.map { |macro| [Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND, :instance, macro] }
|
|
1128
|
+
end
|
|
1129
|
+
|
|
1130
|
+
# Each module a class on `name`'s superclass chain includes or prepends directly, resolved, with the hook
|
|
1131
|
+
# blocks its edges run: `:included` for an `include`, `:prepended` for a `prepend`; a concern's dependencies
|
|
1132
|
+
# follow it on the same edges.
|
|
1133
|
+
def direct_hook_mixins(scope, name)
|
|
1134
|
+
chain = Scope::ResolutionChain.for(scope, name.to_s, :instance, :methods)
|
|
1135
|
+
chain.record(scope) if Analysis::DependencyRecorder.active?
|
|
1136
|
+
chain.level_classes.compact.flat_map { |klass| own_mixin_edges(scope, klass) }
|
|
1137
|
+
end
|
|
1138
|
+
|
|
1139
|
+
# The modules `owner` itself includes or prepends, as {#direct_hook_mixins} gives them. With `deferred:`, a
|
|
1140
|
+
# provable dependency of a concern `owner` is left out ({#hook_block_macro_defines?}).
|
|
1141
|
+
def own_mixin_edges(scope, owner, deferred: false)
|
|
1142
|
+
mixins = scope.includes_of(owner)
|
|
1143
|
+
prepends = scope.prepends_of(owner)
|
|
1144
|
+
deferred_mixins = deferred ? deferred_dependencies(scope, owner, mixins) : NO_DEFERRED
|
|
1145
|
+
mixins.uniq.flat_map do |raw|
|
|
1146
|
+
mixin = declared_constant(scope, owner, raw)
|
|
1147
|
+
next [] unless mixin
|
|
1148
|
+
|
|
1149
|
+
edges = []
|
|
1150
|
+
edges << :prepended if prepends.include?(raw)
|
|
1151
|
+
edges << :included if mixins.count(raw) > prepends.count(raw)
|
|
1152
|
+
next [] if deferred_mixins.include?(raw)
|
|
1153
|
+
|
|
1154
|
+
with_concern_dependencies(scope, mixin).map { |member| [member, edges] }
|
|
1155
|
+
end
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
NO_DEFERRED = [].freeze
|
|
1159
|
+
private_constant :NO_DEFERRED
|
|
1160
|
+
|
|
1161
|
+
# The spellings among `owner`'s mixins that name a concern dependency of `owner`, when `owner` is declared in
|
|
1162
|
+
# one file: a reopening may write the same `include` without the `extend`, and load first.
|
|
1163
|
+
def deferred_dependencies(scope, owner, mixins)
|
|
1164
|
+
sites = scope.discovery.discovered_class_sources[owner.to_s]
|
|
1165
|
+
return NO_DEFERRED if sites.nil? || sites.size > 1
|
|
1166
|
+
|
|
1167
|
+
resolver = Scope::ResolutionChain.resolver_for(scope, :methods)
|
|
1168
|
+
dependencies = Scope::ResolutionChain.concern_dependencies(scope.discovery, resolver, owner)
|
|
1169
|
+
return NO_DEFERRED if dependencies.empty?
|
|
1170
|
+
|
|
1171
|
+
Scope::ResolutionChain.dependency_spellings(scope.discovery, owner).select do |raw|
|
|
1172
|
+
mixins.include?(raw) && dependencies.include?(resolver.resolve_declared(owner, raw))
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
# Issue #2992 — `mixin` and, when it is a concern, the concerns it includes or prepends, at any depth through
|
|
1177
|
+
# concerns ({Scope::ResolutionChain.concern_dependencies}): `Concern#append_features` (or `prepend_features`)
|
|
1178
|
+
# includes (or prepends) each of them into the final includer ahead of `mixin` itself, on the includer's own
|
|
1179
|
+
# edge to `mixin`, whatever edge each was given. A reader files each one's sites as it reads its envelopes.
|
|
1180
|
+
def with_concern_dependencies(scope, mixin)
|
|
1181
|
+
resolver = Scope::ResolutionChain.resolver_for(scope, :methods)
|
|
1182
|
+
[mixin, *Scope::ResolutionChain.concern_dependencies(scope.discovery, resolver, mixin)]
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
def hook_extension_defines?(scope, mixin, definers)
|
|
1186
|
+
hook_extends(scope, mixin).any? do |written, lookup|
|
|
1187
|
+
target = if lookup == :const_get then const_get_constant(scope, mixin, written)
|
|
1188
|
+
else declared_constant(scope, mixin, written)
|
|
1189
|
+
end
|
|
1190
|
+
record_hook_target_edges(scope, mixin, written, lookup) if Analysis::DependencyRecorder.active?
|
|
1191
|
+
target && definers.any? { |definer| scope.parameter_envelopes_of(target).key?(definer) }
|
|
1192
|
+
end
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
# Issue #2992 — the read depends on the extended module's files whether or not it resolved: an empty
|
|
1196
|
+
# `module ClassMethods; end` is no project class yet, and the edit that gives it the definer makes it one. So
|
|
1197
|
+
# every name the lookup tried files its declaring sites, and the constant's last segment its negative class
|
|
1198
|
+
# edge, which a NEW file declaring it fires.
|
|
1199
|
+
def record_hook_target_edges(scope, mixin, written, lookup)
|
|
1200
|
+
candidates = if lookup == :const_get
|
|
1201
|
+
chain = Scope::ResolutionChain.for(scope, mixin, :instance, :methods)
|
|
1202
|
+
chain.entries.flat_map { |entry| entry.external? ? entry.candidates : [entry.name] }
|
|
1203
|
+
.map { |name| "#{name}::#{written}" } << written
|
|
1204
|
+
else
|
|
1205
|
+
own = written.start_with?("::") ? [] : ["#{mixin}::#{written}"]
|
|
1206
|
+
own + scope.ancestor_name_candidates(mixin, written)
|
|
1207
|
+
end
|
|
1208
|
+
candidates.each { |candidate| Scope::ResolutionChain.record_class(scope, candidate.delete_prefix("::")) }
|
|
1209
|
+
Analysis::DependencyRecorder.read_last_segment(:class, written)
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
# The constant `raw`, written in `owner`'s body, as the first candidate the project declares: `Owner::Raw`
|
|
1213
|
+
# first, since `owner` is the innermost cref there, then the enclosing namespaces. Nil when none is declared.
|
|
1214
|
+
def declared_constant(scope, owner, raw)
|
|
1215
|
+
candidates = scope.ancestor_name_candidates(owner, raw)
|
|
1216
|
+
candidates = ["#{owner}::#{raw}", *candidates] unless raw.start_with?("::")
|
|
1217
|
+
candidates.find { |candidate| scope.known_user_class?(candidate) }
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
# Issue #2986 — `owner.const_get(raw)`, as `Concern#append_features` looks `ClassMethods` up: `owner` and its
|
|
1221
|
+
# ancestors, then `Object`, never the namespaces enclosing `owner`. Issue #2997 — a `class_methods` block's
|
|
1222
|
+
# `Name::ClassMethods` counts only for a concern `Name` ({Scope::ResolutionChain.const_get_candidate?}).
|
|
1223
|
+
def const_get_constant(scope, owner, raw)
|
|
1224
|
+
chain = Scope::ResolutionChain.for(scope, owner, :instance, :methods)
|
|
1225
|
+
found = chain.search(scope) do |entry|
|
|
1226
|
+
names = entry.external? ? entry.candidates : [entry.name]
|
|
1227
|
+
names.map { |name| [name, "#{name}::#{raw}"] }.find do |name, candidate|
|
|
1228
|
+
scope.known_user_class?(candidate) &&
|
|
1229
|
+
Scope::ResolutionChain.const_get_candidate?(scope.discovery, name, candidate)
|
|
1230
|
+
end&.last
|
|
1231
|
+
end
|
|
1232
|
+
found || (raw if scope.known_user_class?(raw))
|
|
1233
|
+
end
|
|
1234
|
+
|
|
1235
|
+
# The `[written, lookup]` pairs of a module's hook-extend entries; `lookup` is `:const_get` for a concern's.
|
|
1236
|
+
# Issue #3005 — a gated concern-block entry counts only for a concern `owner`.
|
|
1237
|
+
def hook_extends(scope, owner)
|
|
1238
|
+
scope.parameter_envelopes_of(owner).each_key.filter_map do |key|
|
|
1239
|
+
next unless key.is_a?(Array) && key.first == Scope::DiscoveryIndex::ENVELOPE_HOOK_EXTEND_KIND
|
|
1240
|
+
next unless Scope::ResolutionChain.effective_hook(scope.discovery, owner, key[2])
|
|
1241
|
+
|
|
1242
|
+
key[1, 2]
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
|
|
1246
|
+
# A macro's owner holds the macro's `def`, so it is never an external entry.
|
|
1247
|
+
def macro_entry_defines?(scope, entry, macro)
|
|
1248
|
+
!entry.external? &&
|
|
1249
|
+
scope.parameter_envelopes_of(entry.name).key?([Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND,
|
|
1250
|
+
entry.side, macro])
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# The classes past a class object's own singleton chain, whose instance methods it answers. A module's chain
|
|
1254
|
+
# does not reach `Class`, but a module body calling a `Class` macro raises as it loads, so it is not told apart.
|
|
1255
|
+
UNIVERSAL_MACRO_OWNERS = %w[Class Module Object Kernel BasicObject].freeze
|
|
1256
|
+
private_constant :UNIVERSAL_MACRO_OWNERS
|
|
1257
|
+
|
|
1258
|
+
def universal_macro_defines?(scope, macro)
|
|
1259
|
+
definer = [Scope::DiscoveryIndex::ENVELOPE_COMPUTED_DEFINER_KIND, :instance, macro]
|
|
1260
|
+
UNIVERSAL_MACRO_OWNERS.any? { |owner| scope.parameter_envelopes_of(owner).key?(definer) }
|
|
1261
|
+
end
|
|
1262
|
+
|
|
983
1263
|
# Issue #1722 — a `method_missing`, or a project mixin, on one of the class's RBS ancestors
|
|
984
1264
|
# ({RbsReceiverSurface}). Last for {#ancestry_declares_method?}'s reason: it walks the class graph.
|
|
1265
|
+
# Issue #2792 — a `method_missing` RBS declares on the class or an ancestor, other than the universal one.
|
|
985
1266
|
def rbs_ancestor_may_answer?(class_name, method_name, scope, kind)
|
|
1267
|
+
side = kind == :singleton ? :singleton : :instance
|
|
1268
|
+
return true if RbsReceiverSurface.rbs_missing_hook?(scope, class_name, side)
|
|
1269
|
+
|
|
986
1270
|
if kind == :singleton
|
|
987
1271
|
RbsReceiverSurface.singleton_hook?(scope, class_name) ||
|
|
988
1272
|
RbsReceiverSurface.singleton_mixin_may_answer?(scope, class_name, method_name)
|
|
@@ -1376,13 +1660,19 @@ module Rigor
|
|
|
1376
1660
|
# answers, that never reaches a verdict — depend on `Widget`'s declaring file.
|
|
1377
1661
|
# `dependency_recorder_spec` caught it here exactly as it caught #723's walk.
|
|
1378
1662
|
#
|
|
1379
|
-
#
|
|
1380
|
-
#
|
|
1381
|
-
|
|
1663
|
+
# A constant the environment does not know is read off the receiver's OWN includes only. Issue #2991 — a
|
|
1664
|
+
# mixin no name spells (`include(*MODS)`, `include helper`) opens the receiver when it sits anywhere on its
|
|
1665
|
+
# instance-side chain, its own level or an ancestor's: the chain marks it (`ResolutionChain#unnamed_mixin?`),
|
|
1666
|
+
# and any name may come from the unlisted module (a hook's `base.include M` is read so too). `self.include M`
|
|
1667
|
+
# and a receiverless `send(:include, M)` are recorded as `include M` is (#2995), so the ordinary lookup reads
|
|
1668
|
+
# `M`. A mixin whose ORDER alone is unknown (`include M; prepend M`) has its module recorded, so it opens
|
|
1669
|
+
# nothing. A class-object receiver reads its singleton chain instead ({#singleton_mixin_may_answer?}).
|
|
1670
|
+
def unknown_mixin_includer?(class_name, scope, kind)
|
|
1382
1671
|
includes = scope.includes_of(class_name)
|
|
1383
|
-
return
|
|
1672
|
+
return true if includes.any? { |raw| !known_mixin?(raw, class_name, scope) }
|
|
1673
|
+
return false if kind == :singleton
|
|
1384
1674
|
|
|
1385
|
-
|
|
1675
|
+
Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods).unnamed_mixin?
|
|
1386
1676
|
end
|
|
1387
1677
|
|
|
1388
1678
|
def known_mixin?(raw_name, class_name, scope)
|
|
@@ -1494,8 +1784,8 @@ module Rigor
|
|
|
1494
1784
|
# as a per-instance singleton method and answers the rest through a private `method_missing` (ostruct 0.6), so
|
|
1495
1785
|
# no signature can enumerate its surface. The only other declaration in the environment is json's add-on
|
|
1496
1786
|
# reopening (`json_create` / `as_json` / `to_json`), which made the class read as fully declared and every
|
|
1497
|
-
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing
|
|
1498
|
-
#
|
|
1787
|
+
# field access a `call.undefined-method`. `data/core_overlay/ostruct.rbs` declares that `method_missing`; since
|
|
1788
|
+
# #2792 that alone keeps `call.undefined-method` silent, but the rules below still need the class named. No load
|
|
1499
1789
|
# gate is needed: the core overlay loads into every environment that has RBS at all, so the list is active
|
|
1500
1790
|
# whenever the declaration it protects is. Both sides of the class are open, as with
|
|
1501
1791
|
# {GEM_OVERLAY_OPEN_RECEIVERS}, and an inherited Object / Kernel signature stops deciding `call.wrong-arity`
|
|
@@ -1636,38 +1926,51 @@ module Rigor
|
|
|
1636
1926
|
registry.open_receiver?(class_name)
|
|
1637
1927
|
end
|
|
1638
1928
|
|
|
1639
|
-
# Whether THIS run's RBS environment actually includes
|
|
1640
|
-
#
|
|
1641
|
-
# constant's comment)
|
|
1642
|
-
#
|
|
1643
|
-
#
|
|
1929
|
+
# Whether THIS run's RBS environment actually includes Rigor's OWN partial declaration of
|
|
1930
|
+
# ACTIVESUPPORT — {GEM_OVERLAY_OPEN_RECEIVERS} membership alone must not grant the exemption (see
|
|
1931
|
+
# that constant's comment), and both members of that list are `ActiveSupport` classes, so the gate
|
|
1932
|
+
# asks for the activesupport overlay specifically. Issue #2857 added a SECOND bundled overlay
|
|
1933
|
+
# (`addressable`, eligible under `:rbs_collection` coverage), and a collection project can load it
|
|
1934
|
+
# while the activesupport overlay stays unloaded — keying on "any `data/gem_overlay/` directory"
|
|
1935
|
+
# would then silence `ActiveSupport::Duration#bar`, the ADR-26 false negative this gate exists to
|
|
1936
|
+
# prevent.
|
|
1644
1937
|
#
|
|
1645
|
-
#
|
|
1646
|
-
#
|
|
1647
|
-
#
|
|
1648
|
-
# source lives under that root;
|
|
1649
|
-
# - the bundled plugin twin's own `sig/`, when a project wires it through `signature_paths:` instead
|
|
1650
|
-
# of `plugins:`. Before #672 that route loaded BOTH halves and `Duration` collapsed outright, so
|
|
1651
|
-
# nothing fired and nothing needed protecting; now the overlay stands down and the twin's
|
|
1652
|
-
# declaration is the one that loads — with no manifest behind it, so `open_receiver?` above stays
|
|
1653
|
-
# false and this is the only thing standing between a real `Duration` receiver and a false
|
|
1654
|
-
# `call.undefined-method` on every member the declaration omits.
|
|
1938
|
+
# `RbsLoader#signature_paths` is the same list `Environment.for_project` builds from
|
|
1939
|
+
# `resolved_paths + plugin_sig_paths + gem_sig_paths + collection_paths + overlay_paths`, and
|
|
1940
|
+
# two of its members can carry the activesupport declaration:
|
|
1655
1941
|
#
|
|
1656
|
-
#
|
|
1657
|
-
#
|
|
1658
|
-
#
|
|
1659
|
-
#
|
|
1660
|
-
#
|
|
1942
|
+
# - `data/gem_overlay/activesupport/` itself — a member if and only if `gem_overlay_paths` picked
|
|
1943
|
+
# it (the gem is locked, its coverage source admits the overlay — `:missing` for activesupport —
|
|
1944
|
+
# and its plugin twin is NOT reachable, ADR-72). {RbsLoader.gem_overlay_sig_paths} rebuilds the
|
|
1945
|
+
# exact Pathname `for_project` appended, so a plain `include?` is the test: no other signature
|
|
1946
|
+
# source can occupy that path;
|
|
1947
|
+
# - the bundled activesupport plugin twin's own `sig/`, when a project wires it through
|
|
1948
|
+
# `signature_paths:` instead of `plugins:`. Before #672 that route loaded BOTH halves and
|
|
1949
|
+
# `Duration` collapsed outright, so nothing fired and nothing needed protecting; now the overlay
|
|
1950
|
+
# stands down and the twin's declaration is the one that loads — with no manifest behind it, so
|
|
1951
|
+
# `open_receiver?` above stays false and this is the only thing standing between a real
|
|
1952
|
+
# `Duration` receiver and a false `call.undefined-method` on every member the declaration
|
|
1953
|
+
# omits. {RbsLoader.gem_overlay_twin_signatures_loaded?} enumerates `GEM_OVERLAY_PLUGIN_IDS`'
|
|
1954
|
+
# values, which today is only the activesupport twin; a second overlay twin would need the same
|
|
1955
|
+
# per-gem keying this method now applies to the directory arm.
|
|
1661
1956
|
#
|
|
1662
|
-
#
|
|
1663
|
-
#
|
|
1664
|
-
#
|
|
1957
|
+
# When the PLUGIN itself is loaded the first arm goes false (the overlay stands down) and the
|
|
1958
|
+
# second goes true — its `sig/` rides in as a plugin signature path — but nothing here decides
|
|
1959
|
+
# that case: `open_receiver?`'s manifest-driven check short-circuits above it. The two answers
|
|
1960
|
+
# agree, which is the point; this predicate asks only whether the Rigor-authored activesupport
|
|
1961
|
+
# declaration is in the environment, never how the project asked for it.
|
|
1962
|
+
#
|
|
1963
|
+
# The two arms are asked in that order, over the WHOLE list each time, rather than interleaved
|
|
1964
|
+
# per path: the overlay arm is a Pathname comparison (plus the two `File.directory?` probes
|
|
1965
|
+
# {RbsLoader.gem_overlay_sig_paths} needs to rebuild the path) and the twin arm canonicalises
|
|
1966
|
+
# paths on the filesystem, so an overlay-only run pays the cheaper arm first.
|
|
1665
1967
|
def gem_overlay_loaded?(scope)
|
|
1666
1968
|
loader = scope.environment&.rbs_loader
|
|
1667
1969
|
return false if loader.nil? || !loader.respond_to?(:signature_paths)
|
|
1668
1970
|
|
|
1669
1971
|
paths = loader.signature_paths
|
|
1670
|
-
|
|
1972
|
+
activesupport_overlay = Rigor::Environment::RbsLoader.gem_overlay_sig_paths(["activesupport"])
|
|
1973
|
+
paths.any? { |path| activesupport_overlay.include?(path) } ||
|
|
1671
1974
|
Rigor::Environment::RbsLoader.gem_overlay_twin_signatures_loaded?(paths)
|
|
1672
1975
|
end
|
|
1673
1976
|
|
|
@@ -1750,6 +2053,16 @@ module Rigor
|
|
|
1750
2053
|
method_def
|
|
1751
2054
|
end
|
|
1752
2055
|
|
|
2056
|
+
# Issue #2786 — {#trustworthy_signature} on the call's receiver as {CallFacts} types it, resolved once per
|
|
2057
|
+
# call node: `call.wrong-arity` and `call.argument-type-mismatch` both ask it with that receiver type and its
|
|
2058
|
+
# concrete class.
|
|
2059
|
+
def receiver_signature(facts)
|
|
2060
|
+
facts.receiver_signature do
|
|
2061
|
+
receiver_type = facts.receiver_type
|
|
2062
|
+
trustworthy_signature(receiver_type, concrete_class_name(receiver_type), facts.call_node, facts.scope)
|
|
2063
|
+
end
|
|
2064
|
+
end
|
|
2065
|
+
|
|
1753
2066
|
def unauthoritative_inherited_signature?(class_name, method_def, scope)
|
|
1754
2067
|
return false unless unbounded_receiver_surface?(class_name, scope)
|
|
1755
2068
|
return true unless method_def.respond_to?(:defined_in)
|
|
@@ -1942,18 +2255,18 @@ module Rigor
|
|
|
1942
2255
|
# it returns nil when the call's receiver / RBS coverage /
|
|
1943
2256
|
# call shape disqualifies the rule.
|
|
1944
2257
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1945
|
-
def wrong_arity_diagnostic(path, call_node,
|
|
2258
|
+
def wrong_arity_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
1946
2259
|
return nil if call_node.receiver.nil?
|
|
1947
2260
|
return nil unless plain_positional_call?(call_node)
|
|
1948
2261
|
|
|
1949
|
-
scope =
|
|
2262
|
+
scope = facts.scope
|
|
1950
2263
|
return nil if scope.nil?
|
|
1951
2264
|
|
|
1952
2265
|
# ADR-67 WD6b — an inferred-parameter receiver's type is an open-call-site lower bound; a wrong-arity
|
|
1953
2266
|
# firing against it is an FP by construction. Decline.
|
|
1954
2267
|
return nil if inferred_param_receiver?(call_node, scope)
|
|
1955
2268
|
|
|
1956
|
-
receiver_type =
|
|
2269
|
+
receiver_type = facts.receiver_type
|
|
1957
2270
|
class_name = concrete_class_name(receiver_type)
|
|
1958
2271
|
return nil if class_name.nil?
|
|
1959
2272
|
|
|
@@ -1974,7 +2287,7 @@ module Rigor
|
|
|
1974
2287
|
# false positive. Skip arity-checking the chained position.
|
|
1975
2288
|
return nil if anonymous_struct_new_call?(call_node, class_name, kind)
|
|
1976
2289
|
|
|
1977
|
-
arity_envelope = arity_envelope_for(
|
|
2290
|
+
arity_envelope = arity_envelope_for(facts, class_name, kind)
|
|
1978
2291
|
return nil if arity_envelope.nil?
|
|
1979
2292
|
|
|
1980
2293
|
actual = (call_node.arguments&.arguments || []).size
|
|
@@ -2005,11 +2318,14 @@ module Rigor
|
|
|
2005
2318
|
# declaration. `trustworthy_signature` is the same lookup `argument_type_diagnostic` treats as
|
|
2006
2319
|
# authoritative over a source `def` (see its own comment beside that call), and the two rules must agree
|
|
2007
2320
|
# on whose contract binds, so a declared method is checked against its declaration.
|
|
2008
|
-
def arity_envelope_for(
|
|
2321
|
+
def arity_envelope_for(facts, class_name, kind)
|
|
2322
|
+
receiver_type = facts.receiver_type
|
|
2323
|
+
call_node = facts.call_node
|
|
2324
|
+
scope = facts.scope
|
|
2009
2325
|
if Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
2010
2326
|
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
2011
2327
|
|
|
2012
|
-
method_def =
|
|
2328
|
+
method_def = receiver_signature(facts)
|
|
2013
2329
|
if method_def && !Rigor::RbsExtended.inferred_signature?(method_def)
|
|
2014
2330
|
return nil if undeclared_constructor?(class_name, call_node, kind, method_def)
|
|
2015
2331
|
|
|
@@ -2194,7 +2510,7 @@ module Rigor
|
|
|
2194
2510
|
# and union receivers where every member already
|
|
2195
2511
|
# disqualifies the call (avoid duplicating the
|
|
2196
2512
|
# undefined-method diagnostic).
|
|
2197
|
-
def nil_receiver_diagnostic(path, call_node,
|
|
2513
|
+
def nil_receiver_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
2198
2514
|
return nil if call_node.receiver.nil?
|
|
2199
2515
|
# Safe-navigation calls (`recv&.method`) already
|
|
2200
2516
|
# short-circuit on nil at runtime, so a nil-bearing
|
|
@@ -2208,7 +2524,7 @@ module Rigor
|
|
|
2208
2524
|
# produce false positives we cannot suppress.
|
|
2209
2525
|
return nil unless call_node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
2210
2526
|
|
|
2211
|
-
scope =
|
|
2527
|
+
scope = facts.scope
|
|
2212
2528
|
return nil if scope.nil?
|
|
2213
2529
|
|
|
2214
2530
|
# ADR-58 WD1 — a receiver whose `nil` constituent is purely
|
|
@@ -2234,7 +2550,7 @@ module Rigor
|
|
|
2234
2550
|
# construction. Decline.
|
|
2235
2551
|
return nil if scope.inferred_param?(call_node.receiver.name)
|
|
2236
2552
|
|
|
2237
|
-
receiver_type =
|
|
2553
|
+
receiver_type = facts.receiver_type
|
|
2238
2554
|
return nil unless receiver_type.is_a?(Type::Union)
|
|
2239
2555
|
|
|
2240
2556
|
# The rule only fires when the analyzer has access to
|
|
@@ -2243,7 +2559,7 @@ module Rigor
|
|
|
2243
2559
|
return nil unless Rigor::Reflection.rbs_class_known?("NilClass", scope: scope)
|
|
2244
2560
|
|
|
2245
2561
|
return nil unless nil_bearing_union_witnesses?(receiver_type, call_node.name, scope)
|
|
2246
|
-
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, scope_index, lexical_sites)
|
|
2562
|
+
return nil if nil_receiver_withheld?(call_node, receiver_type, scope, facts.scope_index, lexical_sites)
|
|
2247
2563
|
|
|
2248
2564
|
build_nil_receiver_diagnostic(path, call_node)
|
|
2249
2565
|
end
|
|
@@ -2282,8 +2598,7 @@ module Rigor
|
|
|
2282
2598
|
# nil so the rule bails (silent). Every other receiver (including a
|
|
2283
2599
|
# `T | nil` union, which already has no single concrete class) flows
|
|
2284
2600
|
# through unchanged.
|
|
2285
|
-
def safe_navigation_receiver(call_node,
|
|
2286
|
-
receiver_type = scope.type_of(call_node.receiver)
|
|
2601
|
+
def safe_navigation_receiver(call_node, receiver_type)
|
|
2287
2602
|
return receiver_type unless call_node.safe_navigation?
|
|
2288
2603
|
return receiver_type unless nil_member?(receiver_type)
|
|
2289
2604
|
|
|
@@ -2605,22 +2920,24 @@ module Rigor
|
|
|
2605
2920
|
INTEGER_RAISING_OPERATORS = %i[/ % div modulo divmod].freeze
|
|
2606
2921
|
private_constant :INTEGER_RAISING_OPERATORS
|
|
2607
2922
|
|
|
2608
|
-
def always_raises_diagnostic(path, call_node,
|
|
2609
|
-
return nil unless integer_zero_division?(call_node,
|
|
2923
|
+
def always_raises_diagnostic(path, call_node, facts, lexical_sites = nil, raise_shield = nil)
|
|
2924
|
+
return nil unless integer_zero_division?(call_node, facts)
|
|
2610
2925
|
|
|
2611
2926
|
# ADR-121 WD7 — a refinement of `Integer`'s operator (or one Rigor cannot read) in effect may not raise.
|
|
2612
|
-
scope
|
|
2613
|
-
|
|
2927
|
+
return nil if refined_method_in_effect?("Integer", call_node, facts.scope, :instance, lexical_sites)
|
|
2928
|
+
|
|
2929
|
+
# Issue #3036 — a raise an enclosing `begin` / `def … rescue` catches flows to the handler.
|
|
2930
|
+
return nil if raise_shield&.rescued?(call_node, facts.scope)
|
|
2614
2931
|
|
|
2615
2932
|
build_always_raises_diagnostic(path, call_node)
|
|
2616
2933
|
end
|
|
2617
2934
|
|
|
2618
|
-
def integer_zero_division?(call_node,
|
|
2935
|
+
def integer_zero_division?(call_node, facts)
|
|
2619
2936
|
return false unless raising_call_shape?(call_node)
|
|
2620
2937
|
|
|
2621
|
-
scope =
|
|
2938
|
+
scope = facts.scope
|
|
2622
2939
|
return false if scope.nil?
|
|
2623
|
-
return false unless integer_rooted_for_diagnostic?(
|
|
2940
|
+
return false unless integer_rooted_for_diagnostic?(facts.receiver_type)
|
|
2624
2941
|
|
|
2625
2942
|
arg = single_argument(call_node)
|
|
2626
2943
|
arg && integer_zero_constant?(scope.type_of(arg))
|
|
@@ -3132,17 +3449,17 @@ module Rigor
|
|
|
3132
3449
|
# - Issue #1984 — Ruby hands a private call to `method_missing`, so a `method_missing` or
|
|
3133
3450
|
# `respond_to_missing?` a project entry on the receiver's chain records, on the side the call reaches,
|
|
3134
3451
|
# declines the rule ({#missing_hook_on_chain?}).
|
|
3135
|
-
def visibility_mismatch_diagnostic(path, call_node,
|
|
3452
|
+
def visibility_mismatch_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
3136
3453
|
return nil unless explicit_non_self_receiver?(call_node.receiver)
|
|
3137
3454
|
|
|
3138
|
-
scope =
|
|
3455
|
+
scope = facts.scope
|
|
3139
3456
|
return nil if scope.nil?
|
|
3140
3457
|
|
|
3141
3458
|
# ADR-67 WD6b — an inferred-parameter receiver's nominal type is an open-call-site lower bound; a
|
|
3142
3459
|
# private-method visibility firing against it is an FP by construction. Decline.
|
|
3143
3460
|
return nil if inferred_param_receiver?(call_node, scope)
|
|
3144
3461
|
|
|
3145
|
-
receiver_type =
|
|
3462
|
+
receiver_type = facts.receiver_type
|
|
3146
3463
|
case receiver_type
|
|
3147
3464
|
when Type::Nominal then instance_visibility_mismatch(path, call_node, scope, receiver_type, lexical_sites)
|
|
3148
3465
|
when Type::Singleton then singleton_visibility_mismatch(path, call_node, scope, receiver_type, lexical_sites)
|
|
@@ -3187,7 +3504,14 @@ module Rigor
|
|
|
3187
3504
|
build_visibility_mismatch_diagnostic(path, call_node, "singleton(#{class_name})")
|
|
3188
3505
|
end
|
|
3189
3506
|
|
|
3190
|
-
|
|
3507
|
+
# ADR-129 WD1 — a run-scoped Memo: one `[discovery, names]` slot keyed on the discovery index's identity, the
|
|
3508
|
+
# only input the name set reads.
|
|
3509
|
+
PRIVATE_NAMES_MEMO = TypingState.memo(
|
|
3510
|
+
:check_rules_private_singleton_names,
|
|
3511
|
+
run_scoped: true, audit: "private singleton names",
|
|
3512
|
+
reason: "one [discovery, names] slot keyed on the discovery index's identity; the name " \
|
|
3513
|
+
"set reads only its method-visibility tables"
|
|
3514
|
+
)
|
|
3191
3515
|
private_constant :PRIVATE_NAMES_MEMO
|
|
3192
3516
|
|
|
3193
3517
|
# Issue #1984 — the table-only gate the singleton side asks before it reads a chain, as the instance side asks
|
|
@@ -3198,12 +3522,23 @@ module Rigor
|
|
|
3198
3522
|
def private_singleton_candidate?(scope, name)
|
|
3199
3523
|
DependencyRecorder.read_visibility("*", name) if DependencyRecorder.active?
|
|
3200
3524
|
discovery = scope.discovery
|
|
3201
|
-
|
|
3525
|
+
if Inference::MemoAudit.enabled? && Inference::MemoAudit.suspended?
|
|
3526
|
+
return private_recorded_names(discovery.discovered_method_visibilities).include?(name.to_sym)
|
|
3527
|
+
end
|
|
3528
|
+
|
|
3529
|
+
state = TypingState.current
|
|
3530
|
+
slot = state[PRIVATE_NAMES_MEMO]
|
|
3202
3531
|
unless slot && slot[0].equal?(discovery)
|
|
3203
|
-
|
|
3204
|
-
|
|
3532
|
+
names = Inference::MemoAudit.key_audit("private singleton names") do
|
|
3533
|
+
private_recorded_names(discovery.discovered_method_visibilities)
|
|
3534
|
+
end
|
|
3535
|
+
slot = [discovery, names]
|
|
3536
|
+
state[PRIVATE_NAMES_MEMO] = slot
|
|
3537
|
+
return slot[1].include?(name.to_sym)
|
|
3205
3538
|
end
|
|
3206
|
-
slot[1]
|
|
3539
|
+
Inference::MemoAudit.check("private singleton names", slot[1], name) do
|
|
3540
|
+
private_recorded_names(discovery.discovered_method_visibilities)
|
|
3541
|
+
end.include?(name.to_sym)
|
|
3207
3542
|
end
|
|
3208
3543
|
|
|
3209
3544
|
def private_recorded_names(tables)
|
|
@@ -3512,15 +3847,15 @@ module Rigor
|
|
|
3512
3847
|
COERCE_DISPATCH_METHODS = %i[+ - * / % ** & | ^ << >> < > <= >=].to_set.freeze
|
|
3513
3848
|
private_constant :COERCE_DISPATCH_METHODS
|
|
3514
3849
|
|
|
3515
|
-
def argument_type_diagnostic(path, call_node,
|
|
3850
|
+
def argument_type_diagnostic(path, call_node, facts, lexical_sites = nil)
|
|
3516
3851
|
return nil if call_node.receiver.nil?
|
|
3517
3852
|
return nil if UNIVERSAL_EQUALITY_METHODS.include?(call_node.name)
|
|
3518
3853
|
return nil unless plain_positional_call?(call_node)
|
|
3519
3854
|
|
|
3520
|
-
scope =
|
|
3855
|
+
scope = facts.scope
|
|
3521
3856
|
return nil if scope.nil?
|
|
3522
3857
|
|
|
3523
|
-
receiver_type =
|
|
3858
|
+
receiver_type = facts.receiver_type
|
|
3524
3859
|
class_name = concrete_class_name(receiver_type)
|
|
3525
3860
|
return nil if class_name.nil?
|
|
3526
3861
|
|
|
@@ -3533,11 +3868,11 @@ module Rigor
|
|
|
3533
3868
|
return nil unless Rigor::Reflection.rbs_class_known?(class_name, scope: scope)
|
|
3534
3869
|
return nil unless definition_available?(receiver_type, class_name, scope)
|
|
3535
3870
|
|
|
3536
|
-
method_def =
|
|
3871
|
+
method_def = receiver_signature(facts)
|
|
3537
3872
|
return nil if method_def.nil?
|
|
3538
3873
|
|
|
3539
3874
|
param_overrides = Rigor::RbsExtended.param_type_override_map(method_def, environment: scope.environment)
|
|
3540
|
-
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, scope_index)
|
|
3875
|
+
mismatch = argument_mismatch(method_def.method_types, call_node, scope, param_overrides, facts.scope_index)
|
|
3541
3876
|
return nil if mismatch.nil?
|
|
3542
3877
|
return nil if argument_report_withheld?(receiver_type, class_name, call_node, mismatch, scope, lexical_sites)
|
|
3543
3878
|
|
|
@@ -3685,12 +4020,36 @@ module Rigor
|
|
|
3685
4020
|
# The non-nil channel (ADR-64 WD2/WD3): a single-concrete-class
|
|
3686
4021
|
# argument no overload admits, on a non-coerce method.
|
|
3687
4022
|
def non_nil_arg_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
4023
|
+
return class_object_overload_mismatch(arg, arg_type, params, param_overrides, scope) if
|
|
4024
|
+
arg_type.is_a?(Type::Singleton)
|
|
3688
4025
|
return nil unless single_concrete_arg_class?(arg_type, scope)
|
|
3689
4026
|
return nil if params.any? { |param| param_accepts_arg_class?(param, arg_type, param_overrides, scope) }
|
|
3690
4027
|
|
|
3691
4028
|
{ node: arg, name: nil, expected: overload_param_expected_label(params), actual: arg_type }
|
|
3692
4029
|
end
|
|
3693
4030
|
|
|
4031
|
+
# Issue #2828 — the class-object twin of the non-nil channel. A `singleton(T)` overload the `resolv` overlay
|
|
4032
|
+
# prepends turned `getresources(d, String)`'s one-overload check (`expected singleton(Resolv::DNS::Query)`)
|
|
4033
|
+
# into this multi-overload one, which declined every class object. It fires only when every overload's
|
|
4034
|
+
# parameter at the position is a `singleton(...)` (a `singleton(T)` read through its class bound, see
|
|
4035
|
+
# `SingletonTypeParams.selection_param`) that provably refuses the argument's class; any other parameter
|
|
4036
|
+
# shape admits.
|
|
4037
|
+
def class_object_overload_mismatch(arg, arg_type, params, param_overrides, scope)
|
|
4038
|
+
return nil unless Rigor::Reflection.rbs_class_known?(arg_type.class_name, scope: scope)
|
|
4039
|
+
return nil unless params.all? do |param|
|
|
4040
|
+
!param_overrides[param.name] && param.type.is_a?(RBS::Types::ClassSingleton)
|
|
4041
|
+
end
|
|
4042
|
+
return nil unless params.all? { |param| class_singleton_refuses?(param.type, arg_type, scope) }
|
|
4043
|
+
|
|
4044
|
+
expected = params.map { |param| param.type.to_s.sub("singleton(::", "singleton(") }.uniq.join(" | ")
|
|
4045
|
+
{ node: arg, name: nil, expected: expected, actual: arg_type }
|
|
4046
|
+
end
|
|
4047
|
+
|
|
4048
|
+
def class_singleton_refuses?(rbs_type, arg_type, scope)
|
|
4049
|
+
translated = translate_param_type(rbs_type, scope.environment)
|
|
4050
|
+
translated.is_a?(Type::Singleton) && Inference::Acceptance.accepts(translated, arg_type, mode: :gradual).no?
|
|
4051
|
+
end
|
|
4052
|
+
|
|
3694
4053
|
# The matching positional RBS param across every overload, or nil when
|
|
3695
4054
|
# any overload has no param at `index` (arity divergence — the
|
|
3696
4055
|
# wrong-arity rule's concern, not this one's).
|
|
@@ -3711,6 +4070,7 @@ module Rigor
|
|
|
3711
4070
|
# Load-bearing since rbs 4.1 rewrote core signatures into this form
|
|
3712
4071
|
# (`Array#fetch`'s block overload is `[I < _ToInt, T] (I index) { … }`).
|
|
3713
4072
|
def resolve_param_bounds(param, method_type)
|
|
4073
|
+
param = Inference::MethodDispatcher::SingletonTypeParams.selection_param(param, method_type)
|
|
3714
4074
|
bounded = method_type.type_params.select(&:upper_bound)
|
|
3715
4075
|
return param if bounded.empty?
|
|
3716
4076
|
|
|
@@ -3997,6 +4357,7 @@ module Rigor
|
|
|
3997
4357
|
param_type = param_overrides[param.name] || translate_param_type(param.type, scope.environment)
|
|
3998
4358
|
return nil if param_type.is_a?(Type::Dynamic) || param_type.is_a?(Type::Top)
|
|
3999
4359
|
return nil if arg_type.is_a?(Type::Dynamic) || arg_type.is_a?(Type::Top)
|
|
4360
|
+
return nil if gradual_narrowing_accepted?(arg, arg_type, param_type, scope)
|
|
4000
4361
|
return nil unless argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
4001
4362
|
# Issue #1362 — a value that mixes such a global with something else (`c ? $stdout : StringIO.new`) is judged
|
|
4002
4363
|
# without the declared members the file never writes, as it was before the join.
|
|
@@ -4034,10 +4395,34 @@ module Rigor
|
|
|
4034
4395
|
Inference::Acceptance.accepts(param_type, type, mode: :gradual).no?
|
|
4035
4396
|
end
|
|
4036
4397
|
|
|
4398
|
+
# Issue #2656 — a local a class guard narrowed from a `Dynamic` value
|
|
4399
|
+
# ({DeclarationSourcedGuard.gradual_narrowed?}) is judged by its class, not by the literals the parameter
|
|
4400
|
+
# names: `mode(x) if x.is_a?(String)` with `mode: ("r" | "w")` is accepted, as the gradual value was, while
|
|
4401
|
+
# `num(x) if x.is_a?(String)` with `num: (1 | 2)` still reports, since no String is ever `1` or `2`. The
|
|
4402
|
+
# parameter's value-pinned members are widened to their classes ({Type::Combinator.widen_value_pinned}) and
|
|
4403
|
+
# the narrowed type is judged against that. The overload path ({#non_nil_arg_overload_mismatch}) already
|
|
4404
|
+
# judges by class, so it needs no such reading.
|
|
4405
|
+
#
|
|
4406
|
+
# A literal member of the narrowed type did not come from the gradual value (`x = h[:m] || "r"` narrows to
|
|
4407
|
+
# `"r" | String`), so it is judged against the parameter as written.
|
|
4408
|
+
def gradual_narrowing_accepted?(arg, arg_type, param_type, scope)
|
|
4409
|
+
return false unless DeclarationSourcedGuard.gradual_narrowed?(arg, scope)
|
|
4410
|
+
|
|
4411
|
+
members = arg_type.is_a?(Type::Union) ? arg_type.members : [arg_type]
|
|
4412
|
+
literals, rest = members.partition { |member| member.is_a?(Type::Constant) }
|
|
4413
|
+
if literals.any? { |member| Inference::Acceptance.accepts(param_type, member, mode: :gradual).no? }
|
|
4414
|
+
return false
|
|
4415
|
+
end
|
|
4416
|
+
return true if rest.empty?
|
|
4417
|
+
|
|
4418
|
+
widened = Type::Combinator.widen_value_pinned(param_type)
|
|
4419
|
+
!Inference::Acceptance.accepts(widened, Type::Combinator.union(*rest), mode: :gradual).no?
|
|
4420
|
+
end
|
|
4421
|
+
|
|
4037
4422
|
# The parameter rejects the argument AND the rejection is not a
|
|
4038
4423
|
# withheld declaration-sourced-nil case.
|
|
4039
4424
|
def argument_genuinely_mismatches?(arg, arg_type, param_type, scope)
|
|
4040
|
-
return false unless
|
|
4425
|
+
return false unless rejects_beyond_class_objects?(param_type, arg_type)
|
|
4041
4426
|
|
|
4042
4427
|
# ADR-58 (N2 extension) — the same declaration-sourced-nil-is-not-
|
|
4043
4428
|
# diagnostic-fuel criterion that governs `possible-nil-receiver`
|
|
@@ -4053,6 +4438,31 @@ module Rigor
|
|
|
4053
4438
|
!declaration_sourced_nil_only_mismatch?(arg, arg_type, param_type, scope)
|
|
4054
4439
|
end
|
|
4055
4440
|
|
|
4441
|
+
# Issue #2855 — the parameter rejects the argument, and not only for its `Class` / `Module` members. An
|
|
4442
|
+
# unparameterised `Class` or `Module` nominal ({Acceptance.class_or_module_nominal?}) against a parameter
|
|
4443
|
+
# that admits class objects (`singleton(X)`, alone or in a union) is a class object whose identity inference
|
|
4444
|
+
# lost — a `const_get` result kept by `is_a?(Class)`, an `is_a?(Module)` guard — so it is withheld. The rest
|
|
4445
|
+
# of the argument is still judged: `Class | Integer` against `singleton(X)` reports for its `Integer`.
|
|
4446
|
+
# Acceptance itself keeps answering `no`, so overload selection is unchanged.
|
|
4447
|
+
def rejects_beyond_class_objects?(param_type, arg_type)
|
|
4448
|
+
return false unless Inference::Acceptance.accepts(param_type, arg_type, mode: :gradual).no?
|
|
4449
|
+
return true unless admits_class_objects?(param_type)
|
|
4450
|
+
|
|
4451
|
+
members = arg_type.is_a?(Type::Union) ? arg_type.members : [arg_type]
|
|
4452
|
+
rest = members.reject { |member| Inference::Acceptance.class_or_module_nominal?(member) }
|
|
4453
|
+
return true if rest.size == members.size
|
|
4454
|
+
|
|
4455
|
+
Inference::Acceptance.accepts(param_type, Type::Combinator.union(*rest), mode: :gradual).no?
|
|
4456
|
+
end
|
|
4457
|
+
|
|
4458
|
+
def admits_class_objects?(param_type)
|
|
4459
|
+
case param_type
|
|
4460
|
+
when Type::Singleton then true
|
|
4461
|
+
when Type::Union then param_type.members.any?(Type::Singleton)
|
|
4462
|
+
else false
|
|
4463
|
+
end
|
|
4464
|
+
end
|
|
4465
|
+
|
|
4056
4466
|
# True when `arg` is a declaration-sourced read whose rejection is
|
|
4057
4467
|
# caused solely by its nil constituent: stripping nil from the argument
|
|
4058
4468
|
# type yields a type the parameter accepts (gradual mode). Mirrors the
|
|
@@ -4068,7 +4478,12 @@ module Rigor
|
|
|
4068
4478
|
non_nil = Type::Combinator.union(*arg_type.members.reject { |m| nil_member?(m) })
|
|
4069
4479
|
return false if non_nil.is_a?(Type::Bot)
|
|
4070
4480
|
|
|
4071
|
-
Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual)
|
|
4481
|
+
result = Inference::Acceptance.accepts(param_type, non_nil, mode: :gradual)
|
|
4482
|
+
# Issue #2855 — a `Class?` ivar against `singleton(X)` is withheld as its `Class` alone would be.
|
|
4483
|
+
# Issue #2863 — a `maybe` (a project class the host process has not loaded, so its ancestry is unresolved:
|
|
4484
|
+
# `singleton(Circle)` against `singleton(Shape)`) is not a rejection either: the non-nil part alone would not
|
|
4485
|
+
# fire, so the nil is the only reason and it is withheld.
|
|
4486
|
+
!result.no? || !rejects_beyond_class_objects?(param_type, non_nil)
|
|
4072
4487
|
end
|
|
4073
4488
|
|
|
4074
4489
|
# What this rule needs is that argument N binds to positional
|
|
@@ -4186,7 +4601,7 @@ module Rigor
|
|
|
4186
4601
|
# :no → emit at :error (severity_profile may re-stamp; default `balanced` keeps the
|
|
4187
4602
|
# authored severity).
|
|
4188
4603
|
# :maybe → silent (see {#compare_return}; ADR-8's `strict` promotion is deferred).
|
|
4189
|
-
def return_type_mismatch_diagnostic(path, def_node, scope_index)
|
|
4604
|
+
def return_type_mismatch_diagnostic(path, def_node, scope_index, replaced_defs = nil)
|
|
4190
4605
|
return nil if def_node.body.nil?
|
|
4191
4606
|
|
|
4192
4607
|
last_expr = body_last_expression(def_node.body)
|
|
@@ -4195,7 +4610,7 @@ module Rigor
|
|
|
4195
4610
|
inner_scope = scope_index[last_expr] || scope_index[def_node.body] || scope_index[def_node]
|
|
4196
4611
|
return nil if inner_scope.nil?
|
|
4197
4612
|
|
|
4198
|
-
declared = declared_return_type(def_node, scope_index)
|
|
4613
|
+
declared = declared_return_type(def_node, scope_index, replaced_defs)
|
|
4199
4614
|
return nil if declared.nil?
|
|
4200
4615
|
|
|
4201
4616
|
# Issue #1362 — a body that ends on a builtin global still on its declared seed, or on a local copied from
|
|
@@ -4242,14 +4657,18 @@ module Rigor
|
|
|
4242
4657
|
# `non-empty-array[Integer]`, etc. — are stricter than the underlying RBS class, so a body whose
|
|
4243
4658
|
# inferred type the bare RBS sig would accept may still fail the refinement (e.g. `def name; "";
|
|
4244
4659
|
# end` returns `Constant[""]`, accepted by `String` but rejected by `non-empty-string`).
|
|
4245
|
-
def declared_return_type(def_node, scope_index)
|
|
4660
|
+
def declared_return_type(def_node, scope_index, replaced_defs = nil)
|
|
4246
4661
|
scope = scope_index[def_node]
|
|
4247
4662
|
return nil if scope.nil?
|
|
4248
4663
|
|
|
4249
4664
|
self_type = scope.self_type
|
|
4250
4665
|
return nil unless self_type.respond_to?(:class_name)
|
|
4251
4666
|
|
|
4252
|
-
side = def_node.receiver.nil?
|
|
4667
|
+
side = if def_node.receiver.nil?
|
|
4668
|
+
receiverless_def_side(scope, self_type.class_name, def_node)
|
|
4669
|
+
else
|
|
4670
|
+
receiver_def_side(scope, self_type.class_name, def_node)
|
|
4671
|
+
end
|
|
4253
4672
|
return nil if side.nil?
|
|
4254
4673
|
|
|
4255
4674
|
method_def =
|
|
@@ -4260,6 +4679,7 @@ module Rigor
|
|
|
4260
4679
|
end
|
|
4261
4680
|
return nil if method_def.nil?
|
|
4262
4681
|
return nil unless defined_on?(method_def, self_type.class_name)
|
|
4682
|
+
return nil if replaced_receiver_def?(def_node, replaced_defs)
|
|
4263
4683
|
|
|
4264
4684
|
override = Rigor::RbsExtended.read_return_type_override(method_def, environment: scope.environment)
|
|
4265
4685
|
return override if override
|
|
@@ -4282,6 +4702,30 @@ module Rigor
|
|
|
4282
4702
|
nil
|
|
4283
4703
|
end
|
|
4284
4704
|
|
|
4705
|
+
# Issue #3155 — a singleton def a later certain def replaced ({ReplacedSingletonDefs}). Asked only once the def
|
|
4706
|
+
# has a signature to answer to, so the file walk runs only then.
|
|
4707
|
+
def replaced_receiver_def?(def_node, replaced_defs)
|
|
4708
|
+
!def_node.receiver.nil? && replaced_defs&.replaced?(def_node)
|
|
4709
|
+
end
|
|
4710
|
+
|
|
4711
|
+
# Issue #3155 — `def self.x`, or `def K.x` in `K`'s body, answers to K's singleton signature. A def on another
|
|
4712
|
+
# object (`def String.q`, `def obj.q` in a class body) or `def self.x` in a `class << self` body (a method of
|
|
4713
|
+
# the singleton class's own singleton) answers to none of K's: `nil`, and the caller declines.
|
|
4714
|
+
# {#declared_return_type} also declines, once a signature is found, a singleton def a later certain def of the
|
|
4715
|
+
# name in the same body replaced (`def K.w`; `def self.w`; {ReplacedSingletonDefs}). `def (self).x` reads as
|
|
4716
|
+
# `def self.x`; `K = self; def K.x` is not read.
|
|
4717
|
+
def receiver_def_side(scope, class_name, def_node)
|
|
4718
|
+
receiver = ReplacedSingletonDefs.unparenthesized(def_node.receiver)
|
|
4719
|
+
lexical_self = if receiver.is_a?(Prism::SelfNode)
|
|
4720
|
+
!scope.singleton_class_body?
|
|
4721
|
+
else
|
|
4722
|
+
Inference::ScopeIndexer.def_receiver_targets_lexical_self?(receiver, class_name.split("::"))
|
|
4723
|
+
end
|
|
4724
|
+
return nil unless lexical_self
|
|
4725
|
+
|
|
4726
|
+
:singleton
|
|
4727
|
+
end
|
|
4728
|
+
|
|
4285
4729
|
# The tables hold the live node or one re-resolved from a `DefHandle`, so identity is the name and span.
|
|
4286
4730
|
def same_def_node?(recorded, def_node)
|
|
4287
4731
|
recorded.is_a?(Prism::DefNode) && recorded.name == def_node.name &&
|
|
@@ -4425,8 +4869,10 @@ module Rigor
|
|
|
4425
4869
|
# defined LATER under that name would change the answer; `DefinerResolution` files the negative class
|
|
4426
4870
|
# edge of each external entry it tests.
|
|
4427
4871
|
def overridden_ancestors_start(scope, class_name)
|
|
4428
|
-
|
|
4429
|
-
|
|
4872
|
+
# Issue #2979 — past the entries of the names bound to the same class (`K = Struct.new("Named", …)`):
|
|
4873
|
+
# a `def` there redefines the class's method rather than overriding an ancestor's.
|
|
4874
|
+
chain = Scope::ResolutionChain.for(scope, class_name.to_s, :instance, :methods)
|
|
4875
|
+
chain.ancestors_start(scope, class_name.to_s)
|
|
4430
4876
|
end
|
|
4431
4877
|
|
|
4432
4878
|
# `[defining_class, visibility]` for the nearest user-source ancestor that defines an instance
|
|
@@ -4521,6 +4967,7 @@ module Rigor
|
|
|
4521
4967
|
parent_return = declared_return_union(parent_method, scope.environment, type_vars: parent_type_vars)
|
|
4522
4968
|
return nil if override_return.nil? || parent_return.nil?
|
|
4523
4969
|
return nil if dynamic_top?(parent_return) # untyped / unbound-generic parent contract
|
|
4970
|
+
return nil if void_over_nil?(override_method, parent_return)
|
|
4524
4971
|
|
|
4525
4972
|
return nil unless parent_return.accepts(override_return).no?
|
|
4526
4973
|
|
|
@@ -4529,22 +4976,42 @@ module Rigor
|
|
|
4529
4976
|
)
|
|
4530
4977
|
end
|
|
4531
4978
|
|
|
4979
|
+
# A `-> void` override of a parent return of exactly `nil` (or `NilClass`) is exempt (#3046): both
|
|
4980
|
+
# say "no useful value", and callers cannot use a void return. Keyed on the declared `void` of every
|
|
4981
|
+
# arm (the translator reads `void` as top, so a `-> top` override still fires); any non-nil parent
|
|
4982
|
+
# return still fires.
|
|
4983
|
+
def void_over_nil?(override_method, parent_return)
|
|
4984
|
+
return false unless nil_type?(parent_return)
|
|
4985
|
+
|
|
4986
|
+
override_method.method_types.all? { |mt| mt.type.return_type.is_a?(::RBS::Types::Bases::Void) }
|
|
4987
|
+
end
|
|
4988
|
+
|
|
4989
|
+
# `nil`, or the nominal `NilClass`, whose only value it is.
|
|
4990
|
+
def nil_type?(type)
|
|
4991
|
+
return type.value.nil? if type.is_a?(Type::Constant)
|
|
4992
|
+
|
|
4993
|
+
type.is_a?(Type::Nominal) && type.class_name == "NilClass" && type.type_args.empty?
|
|
4994
|
+
end
|
|
4995
|
+
|
|
4532
4996
|
# `[defining_class, RBS::Definition::Method]` for the nearest project-discovered ancestor whose
|
|
4533
4997
|
# RBS declares `method_name` (not the starting class's own declaration), or nil.
|
|
4534
4998
|
def nearest_ancestor_method_def(scope, class_name, method_name)
|
|
4535
4999
|
from = overridden_ancestors_start(scope, class_name)
|
|
4536
5000
|
return nil if from.nil?
|
|
4537
5001
|
|
|
5002
|
+
passed_owners = []
|
|
4538
5003
|
case Inference::DefinerResolution.resolve(scope, class_name, method_name, :instance,
|
|
4539
5004
|
question: :override, from: from) do |ancestor|
|
|
4540
|
-
|
|
4541
|
-
# The signature text is the answer two worlds compare: an `RBS::Definition::Method` has no
|
|
4542
|
-
# structural equality of its own.
|
|
4543
|
-
method_def.method_types.map(&:to_s) if method_def && !defined_on?(method_def, class_name)
|
|
5005
|
+
override_parent_answer(scope, ancestor, method_name, passed_owners)
|
|
4544
5006
|
end
|
|
4545
|
-
in Inference::DefinerResolution::Known(owner:)
|
|
4546
|
-
|
|
4547
|
-
|
|
5007
|
+
in Inference::DefinerResolution::Known(owner:, answer:)
|
|
5008
|
+
# The nearest declaration must be the one that answers: an ancestor asked past for a project
|
|
5009
|
+
# owner the walk never reached (one the Ruby chain does not hold) leaves the rule silent.
|
|
5010
|
+
unless answer.last == RBS_ONLY_OWNER ||
|
|
5011
|
+
passed_owners.any? { |passed| passed != normalize_class_name(owner) }
|
|
5012
|
+
method_def = safe_instance_method_definition(owner, method_name, scope)
|
|
5013
|
+
method_def && [owner, method_def]
|
|
5014
|
+
end
|
|
4548
5015
|
in Inference::DefinerResolution::UNKNOWN # the case/in contract needs one arm per answer
|
|
4549
5016
|
nil
|
|
4550
5017
|
in Inference::DefinerResolution::ABSENT # rubocop:disable Lint/DuplicateBranch -- see above
|
|
@@ -4552,6 +5019,38 @@ module Rigor
|
|
|
4552
5019
|
end
|
|
4553
5020
|
end
|
|
4554
5021
|
|
|
5022
|
+
# The answer an RBS-only owner gives the override walk: it ends the walk, and the rule stays silent.
|
|
5023
|
+
RBS_ONLY_OWNER = :rbs_only_owner
|
|
5024
|
+
private_constant :RBS_ONLY_OWNER
|
|
5025
|
+
|
|
5026
|
+
# One project ancestor's answer to the override walk. The signature text is the answer two worlds
|
|
5027
|
+
# compare: an `RBS::Definition::Method` has no structural equality of its own. Only an ancestor whose
|
|
5028
|
+
# own RBS declares the method answers with it (#3046). One that inherits the declaration from another
|
|
5029
|
+
# project class or module is asked past, so that owner answers at its own entry; the owner is recorded
|
|
5030
|
+
# in `passed_owners`, which the answering owner must match. One that inherits it from an RBS-only owner
|
|
5031
|
+
# the project chain cannot see ends the walk silent ({RBS_ONLY_OWNER}): that owner is the nearest
|
|
5032
|
+
# declaration, and RBS-only ancestors are #130's.
|
|
5033
|
+
def override_parent_answer(scope, ancestor, method_name, passed_owners)
|
|
5034
|
+
method_def = safe_instance_method_definition(ancestor, method_name, scope)
|
|
5035
|
+
return nil if method_def.nil?
|
|
5036
|
+
return method_def.method_types.map(&:to_s) if declared_by?(method_def, ancestor)
|
|
5037
|
+
|
|
5038
|
+
owner = method_def.implemented_in || method_def.defined_in
|
|
5039
|
+
return RBS_ONLY_OWNER unless owner && scope.discovered_classes.key?(normalize_class_name(owner))
|
|
5040
|
+
|
|
5041
|
+
passed_owners << normalize_class_name(owner)
|
|
5042
|
+
nil
|
|
5043
|
+
end
|
|
5044
|
+
|
|
5045
|
+
# True when `ancestor`'s own RBS declares `method_def`: on its body, or through an interface it
|
|
5046
|
+
# includes itself (RBS records the including class as `implemented_in`).
|
|
5047
|
+
def declared_by?(method_def, ancestor)
|
|
5048
|
+
return true if defined_on?(method_def, ancestor)
|
|
5049
|
+
return false unless method_def.defined_in&.interface?
|
|
5050
|
+
|
|
5051
|
+
normalize_class_name(method_def.implemented_in) == normalize_class_name(ancestor)
|
|
5052
|
+
end
|
|
5053
|
+
|
|
4555
5054
|
def safe_instance_method_definition(class_name, method_name, scope)
|
|
4556
5055
|
Reflection.instance_method_definition(class_name, method_name, scope: scope)
|
|
4557
5056
|
rescue StandardError
|