rigortype 0.4.3 → 0.4.5
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 +5 -5
- data/data/core_overlay/ostruct.rbs +40 -0
- data/docs/handbook/11-sig-gen.md +104 -15
- data/docs/manual/02-cli-reference.md +43 -12
- data/docs/manual/03-configuration.md +20 -7
- data/docs/manual/04-diagnostics.md +56 -3
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +3 -2
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +40 -4
- data/docs/manual/18-removing-dead-code.md +23 -2
- data/docs/manual/plugins/rigor-rails-routes.md +2 -2
- data/docs/manual/plugins/rigor-rspec.md +101 -1
- data/docs/manual/plugins/rigor-shoulda-matchers.md +4 -1
- data/exe/rigor +12 -17
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +111 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +190 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +60 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +13 -4
- data/lib/rigor/analysis/check_rules.rb +274 -51
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +61 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +270 -28
- data/lib/rigor/analysis/path_expansion.rb +3 -1
- data/lib/rigor/analysis/reachability/graph.rb +111 -14
- data/lib/rigor/analysis/reachability/scan.rb +305 -23
- data/lib/rigor/analysis/rule_catalog.rb +22 -4
- data/lib/rigor/analysis/run_cache_key.rb +31 -7
- data/lib/rigor/analysis/run_cache_probe.rb +5 -3
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +48 -12
- data/lib/rigor/analysis/runner/pool_coordinator.rb +179 -31
- data/lib/rigor/analysis/runner/project_pre_passes.rb +29 -4
- data/lib/rigor/analysis/runner.rb +102 -72
- data/lib/rigor/analysis/severity_stamp.rb +45 -1
- data/lib/rigor/analysis/worker_session.rb +69 -2
- data/lib/rigor/cache/descriptor.rb +79 -12
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +39 -5
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +32 -11
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +163 -22
- data/lib/rigor/configuration/dependencies.rb +19 -11
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +29 -3
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +67 -11
- data/lib/rigor/inference/block_call_timing.rb +12 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/captured_locals.rb +67 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/definer_resolution.rb +101 -25
- data/lib/rigor/inference/expression_typer.rb +94 -9
- data/lib/rigor/inference/guard_rebinding.rb +7 -2
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +170 -35
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher.rb +67 -3
- data/lib/rigor/inference/module_function_state.rb +213 -51
- data/lib/rigor/inference/narrowing.rb +235 -75
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +120 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +116 -0
- data/lib/rigor/inference/repeated_or_writes.rb +414 -1
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer.rb +1061 -122
- data/lib/rigor/inference/self_call_ivar_writes.rb +346 -0
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +512 -150
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/lens/anchor.rb +3 -3
- data/lib/rigor/lens/contract_reader.rb +60 -2
- data/lib/rigor/lens/document.rb +4 -3
- data/lib/rigor/lens/file_reader.rb +452 -73
- data/lib/rigor/lens/members.rb +81 -17
- data/lib/rigor/lens/run.rb +73 -4
- data/lib/rigor/lens.rb +2 -1
- data/lib/rigor/mcp/server.rb +63 -22
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/box_probe.rb +154 -7
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/project_environment.rb +6 -3
- data/lib/rigor/protection/discovery_seed.rb +9 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +27 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +56 -8
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +167 -22
- data/lib/rigor/sig_gen/layout_index.rb +40 -1
- data/lib/rigor/sig_gen/method_candidate.rb +8 -0
- data/lib/rigor/sig_gen/observation_collector.rb +137 -11
- data/lib/rigor/sig_gen/path_mapper.rb +18 -2
- data/lib/rigor/sig_gen/renderer.rb +13 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +50 -35
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/constant.rb +10 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +5 -1
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +57 -6
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +1 -1
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +5 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +155 -12
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +10 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +4 -3
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +16 -1
|
@@ -10,6 +10,7 @@ require_relative "../rbs_extended"
|
|
|
10
10
|
require_relative "../analysis/fact_store"
|
|
11
11
|
require_relative "../builtins/regex_refinement"
|
|
12
12
|
require_relative "guard_rebinding"
|
|
13
|
+
require_relative "intervening_ivar_reset"
|
|
13
14
|
require_relative "key_presence_guard"
|
|
14
15
|
require_relative "last_line"
|
|
15
16
|
require_relative "operand_effects"
|
|
@@ -511,7 +512,9 @@ module Rigor
|
|
|
511
512
|
# none of the condition shapes are recognised, both returned scopes equal the input
|
|
512
513
|
# scope. The catalogue mirrors {.case_equality_target_class}: static class/module
|
|
513
514
|
# constants narrow as `is_a?`; integer/float-endpoint ranges narrow to `Numeric`;
|
|
514
|
-
# string-endpoint ranges
|
|
515
|
+
# string-endpoint ranges narrow to `String`; regexp literals narrow as
|
|
516
|
+
# {.regexp_case_equality_type} says; a `nil`, `true` or `false` literal narrows as its
|
|
517
|
+
# class does ({.when_condition_class}).
|
|
515
518
|
def case_when_scopes(subject, conditions, scope)
|
|
516
519
|
# C1 — `case x when /re/` runs `/re/ === x`, which sets the regex match-data globals
|
|
517
520
|
# exactly as a successful `=~` does. Narrow `$~`/`$&`/`$1..$N` on the clause body (the
|
|
@@ -535,6 +538,30 @@ module Rigor
|
|
|
535
538
|
CASE_SUBJECT_KINDS = %i[local ivar global constant].freeze
|
|
536
539
|
private_constant :CASE_SUBJECT_KINDS
|
|
537
540
|
|
|
541
|
+
# Returns `[body_scope, falsey_scope]` for one clause of a `case` with a subject: a `when` clause narrows
|
|
542
|
+
# through {.case_when_scopes}, and an `in` clause only for a bare class pattern ({.bare_class_pattern_node}),
|
|
543
|
+
# which matches exactly when `subject.is_a?(C)`. Every other pattern can fail to match where a class test
|
|
544
|
+
# passes (deconstruction arity, hash keys, a guard), so it narrows neither edge. The pattern's bindings are
|
|
545
|
+
# the caller's to apply.
|
|
546
|
+
def case_clause_scopes(subject, clause, scope)
|
|
547
|
+
return case_when_scopes(subject, clause.conditions, scope) if clause.is_a?(Prism::WhenNode)
|
|
548
|
+
|
|
549
|
+
class_node = bare_class_pattern_node(clause.pattern)
|
|
550
|
+
class_node ? case_when_scopes(subject, [class_node], scope) : [scope, scope]
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# ADR-47 WD3a — the class-constant node of a `in C` / `in C => x` pattern (the only `in` shapes whose match is
|
|
554
|
+
# pure `is_a?`), or nil for any pattern that deconstructs, binds, or matches a value.
|
|
555
|
+
def bare_class_pattern_node(pattern)
|
|
556
|
+
case pattern
|
|
557
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
558
|
+
pattern
|
|
559
|
+
when Prism::CapturePatternNode
|
|
560
|
+
value = pattern.value
|
|
561
|
+
value if value.is_a?(Prism::ConstantReadNode) || value.is_a?(Prism::ConstantPathNode)
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
538
565
|
# When the clause has exactly one `RegularExpressionNode` literal condition, narrow the
|
|
539
566
|
# match-data globals on the body edge (same rule as `analyse_regex_match_predicate`'s
|
|
540
567
|
# truthy edge). With multiple regex conditions (`when /a/, /b/`) the body is reachable
|
|
@@ -1088,12 +1115,17 @@ module Rigor
|
|
|
1088
1115
|
# in {Rigor::Builtins::RegexRefinement::RULES}, the truthy branch narrows further than
|
|
1089
1116
|
# `String` to the matching imported refinement (e.g. `decimal-int-string` for `\d+`).
|
|
1090
1117
|
# Bodies outside the table fall back to the v0.1.0 baseline (plain `String`).
|
|
1118
|
+
#
|
|
1119
|
+
# The match rebinds `$~` and the other match globals on both edges, and this rule binds none of them (#1482),
|
|
1120
|
+
# so each edge forgets what an earlier match bound. The statement evaluator's walk already forgets them in the
|
|
1121
|
+
# scope the match leaves; the per-node scope walk of a conditional it did not enter narrows from the scope
|
|
1122
|
+
# before the match, where the falsey edge of an earlier `=~` had bound `$1` to `nil` (#1495).
|
|
1091
1123
|
def analyse_match_write(node, scope)
|
|
1092
1124
|
string_t = Type::Combinator.nominal_of("String")
|
|
1093
1125
|
nil_t = Type::Combinator.constant_of(nil)
|
|
1094
1126
|
refinements = match_write_capture_refinements(node)
|
|
1095
|
-
truthy = scope
|
|
1096
|
-
falsey =
|
|
1127
|
+
truthy = scope.forget_match_globals
|
|
1128
|
+
falsey = truthy
|
|
1097
1129
|
node.targets.each do |target|
|
|
1098
1130
|
next unless target.is_a?(Prism::LocalVariableTargetNode)
|
|
1099
1131
|
|
|
@@ -1575,23 +1607,59 @@ module Rigor
|
|
|
1575
1607
|
end
|
|
1576
1608
|
|
|
1577
1609
|
# Survey item (b): `/regex/ =~ str` and `str =~ /regex/` bind the regex match-data
|
|
1578
|
-
# globals on each edge
|
|
1610
|
+
# globals on each edge ({#regex_match_edges}), gated on the subject (#1494): an edge is
|
|
1611
|
+
# narrowed only when the receiver proves what the frame holds on it
|
|
1612
|
+
# ({#match_receiver_edges}), and an edge it cannot vouch for keeps the match globals
|
|
1613
|
+
# forgotten. A `String?` subject keeps the match edge only, since `NilClass#=~` answers
|
|
1614
|
+
# nil with the previous match still bound; an untyped one is read the same way.
|
|
1615
|
+
#
|
|
1616
|
+
# Returns nil (no narrowing) when the receiver / argument pair does not resolve to exactly one
|
|
1617
|
+
# regex pattern we can count (see {#regex_match_pattern}).
|
|
1618
|
+
def analyse_regex_match_predicate(node, scope)
|
|
1619
|
+
gated_regex_match_edges(node, scope)
|
|
1620
|
+
end
|
|
1621
|
+
|
|
1622
|
+
# Issue #1379 — `subject !~ pattern` is `Kernel#!~`, which returns `!(subject =~ pattern)`. It runs the same
|
|
1623
|
+
# match in the caller's frame, so its edges are {#analyse_regex_match_predicate}'s swapped: the falsey edge is
|
|
1624
|
+
# the match edge (`$~` a `MatchData`, an unconditional `$N` a `String`, and the whole-receiver refinement), the
|
|
1625
|
+
# truthy edge the no-match edge (`$~` and `$N` nil). The same patterns are recognised and the same ones
|
|
1626
|
+
# declined (a non-regex operand, an ambiguous constant, `//x`), and the receiver gates the edges alike.
|
|
1627
|
+
def analyse_regex_not_match_predicate(node, scope)
|
|
1628
|
+
gated_regex_match_edges(node, scope)&.reverse
|
|
1629
|
+
end
|
|
1630
|
+
|
|
1631
|
+
# `[match_edge, no_match_edge]` of a `=~` / `!~` predicate, each kept only where
|
|
1632
|
+
# {#match_receiver_edges} lets it be; an edge it cannot vouch for is the entry scope with the
|
|
1633
|
+
# match globals forgotten, since the call may still have rebound them. The whole-receiver
|
|
1634
|
+
# refinement is kept only when the local it would narrow is known to be a String on the
|
|
1635
|
+
# match edge ({#refinable_operand?}).
|
|
1636
|
+
def gated_regex_match_edges(node, scope)
|
|
1637
|
+
edges = regex_match_edges(node, scope, refine: refinable_operand?(node, scope))
|
|
1638
|
+
return nil if edges.nil?
|
|
1639
|
+
|
|
1640
|
+
match, no_match = edges
|
|
1641
|
+
forgotten = scope.forget_match_globals
|
|
1642
|
+
case match_receiver_edges(node, scope)
|
|
1643
|
+
when :both then [match, no_match]
|
|
1644
|
+
when :match then [match, forgotten]
|
|
1645
|
+
else [forgotten, forgotten]
|
|
1646
|
+
end
|
|
1647
|
+
end
|
|
1648
|
+
|
|
1649
|
+
# The ungated edges of a match:
|
|
1579
1650
|
#
|
|
1580
|
-
# -
|
|
1651
|
+
# - Match edge (`=~` returned an Integer position — the match succeeded): `$~` to
|
|
1581
1652
|
# `Nominal[MatchData]`; `$&` and `$1..$N` (where N is the number of capture groups in
|
|
1582
1653
|
# the regex source) to `Nominal[String]`. This is the same optimistic-narrowing shape
|
|
1583
1654
|
# the existing `analyse_match_write` uses for named captures inside `if /(?<x>...)/
|
|
1584
1655
|
# =~ str` — optional groups in the regex source (`(\d+)?`) would bind `$N` to `nil`
|
|
1585
1656
|
# at runtime, but the floor here matches the common idiom (required captures) and
|
|
1586
1657
|
# lets `unless /(\d+)/ =~ s; raise; end; $1.to_i` resolve cleanly.
|
|
1587
|
-
# -
|
|
1658
|
+
# - No-match edge (`=~` returned nil): `$~` and every numbered / back-reference
|
|
1588
1659
|
# global bound to `Constant<nil>`.
|
|
1589
1660
|
#
|
|
1590
|
-
# Returns nil (no narrowing) when the receiver / argument pair does not resolve to exactly one
|
|
1591
|
-
# regex pattern we can count (see {#regex_match_pattern}).
|
|
1592
|
-
#
|
|
1593
1661
|
# `refine: false` leaves out the whole-receiver refinement, which names a String.
|
|
1594
|
-
def
|
|
1662
|
+
def regex_match_edges(node, scope, refine:)
|
|
1595
1663
|
return nil if node.arguments.nil?
|
|
1596
1664
|
return nil unless node.arguments.arguments.size == 1
|
|
1597
1665
|
|
|
@@ -1615,90 +1683,113 @@ module Rigor
|
|
|
1615
1683
|
[truthy, falsey]
|
|
1616
1684
|
end
|
|
1617
1685
|
|
|
1618
|
-
#
|
|
1619
|
-
# match
|
|
1620
|
-
# the match edge (`$~` a `MatchData`, an unconditional `$N` a `String`, and the whole-receiver refinement), the
|
|
1621
|
-
# truthy edge the no-match edge (`$~` and `$N` nil). The same patterns are recognised and the same ones
|
|
1622
|
-
# declined (a non-regex operand, an ambiguous constant, `//x`).
|
|
1623
|
-
#
|
|
1624
|
-
# Unlike `=~`'s, an edge here is narrowed only when the receiver proves what the frame holds on it
|
|
1625
|
-
# ({#not_match_receiver_edges}). An edge it cannot vouch for keeps the match globals forgotten, since the call
|
|
1626
|
-
# may still have rebound them. The refinement is kept only when the local it would narrow is known to be a
|
|
1627
|
-
# String on the match edge ({#refinable_operand?}).
|
|
1628
|
-
def analyse_regex_not_match_predicate(node, scope)
|
|
1629
|
-
edges = analyse_regex_match_predicate(node, scope, refine: refinable_operand?(node, scope))
|
|
1630
|
-
return nil if edges.nil?
|
|
1631
|
-
|
|
1632
|
-
match, no_match = edges
|
|
1633
|
-
forgotten = scope.forget_match_globals
|
|
1634
|
-
case not_match_receiver_edges(node, scope)
|
|
1635
|
-
when :both then [no_match, match]
|
|
1636
|
-
when :match then [forgotten, match]
|
|
1637
|
-
else [forgotten, forgotten]
|
|
1638
|
-
end
|
|
1639
|
-
end
|
|
1640
|
-
|
|
1641
|
-
# Which edges of `recv !~ pattern` the receiver lets {#analyse_regex_not_match_predicate} narrow. `Kernel#!~`
|
|
1642
|
-
# dispatches `=~` on the receiver:
|
|
1686
|
+
# Which edges of `recv =~ pattern` / `recv !~ pattern` the receiver lets {#gated_regex_match_edges} narrow:
|
|
1687
|
+
# `:both`, `:match` (the match edge only) or nil. `Kernel#!~` dispatches `=~` on the receiver:
|
|
1643
1688
|
#
|
|
1644
1689
|
# - A Regexp receiver (the pattern on the left) runs `Regexp#=~`, which sets `$~` on both outcomes, a `nil`
|
|
1645
1690
|
# argument included: `:both`.
|
|
1646
|
-
# - `String#=~` and `Symbol#=~` do the same: `:both` when every member of the receiver's type is a String
|
|
1647
|
-
# Symbol.
|
|
1691
|
+
# - `String#=~` and `Symbol#=~` do the same: `:both` when every member of the receiver's type is a String
|
|
1692
|
+
# (a project subclass of String included) or a Symbol.
|
|
1648
1693
|
# - `NilClass#=~` answers nil and leaves `$~` alone, so a nil receiver reaches the no-match edge with the
|
|
1649
1694
|
# previous match still bound; it never reaches the match edge. A `Dynamic` receiver may be nil at run time
|
|
1650
|
-
# whatever its static facet says
|
|
1651
|
-
#
|
|
1652
|
-
# - Any other receiver may define `=~` in Ruby, which binds `$~` in its own frame, not the caller's: nil.
|
|
1695
|
+
# whatever its static facet says. So does a safe-navigation call, which answers nil without matching on a
|
|
1696
|
+
# nil receiver.
|
|
1653
1697
|
#
|
|
1654
|
-
#
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
return
|
|
1698
|
+
# The two operators part over a receiver that is none of those ({#match_operator_edges},
|
|
1699
|
+
# {#not_match_operator_edges}).
|
|
1700
|
+
def match_receiver_edges(node, scope)
|
|
1701
|
+
return nil if node.receiver.nil?
|
|
1702
|
+
|
|
1703
|
+
edges =
|
|
1704
|
+
if regex_operand_pattern(node.receiver, scope).is_a?(RegexMatchPattern)
|
|
1705
|
+
:both
|
|
1706
|
+
else
|
|
1707
|
+
kinds = match_member_kinds(scope.type_of(node.receiver), scope)
|
|
1708
|
+
node.name == :=~ ? match_operator_edges(kinds) : not_match_operator_edges(kinds)
|
|
1709
|
+
end
|
|
1710
|
+
return edges unless node.safe_navigation?
|
|
1711
|
+
|
|
1712
|
+
:match if edges && node.name == :=~
|
|
1713
|
+
end
|
|
1658
1714
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1715
|
+
STRING_MATCH_KINDS = %i[string string_subclass symbol].freeze
|
|
1716
|
+
private_constant :STRING_MATCH_KINDS
|
|
1661
1717
|
|
|
1662
|
-
|
|
1718
|
+
# `recv =~ pattern`'s match edge is the truthy one. Only a member known to define its own `=~` in the
|
|
1719
|
+
# project keeps it out (#1494): that method binds `$~` in its own frame, not the caller's. Any other member
|
|
1720
|
+
# either matches in the caller's frame, answers nil (`NilClass`), or has no `=~` at all (`Integer` since
|
|
1721
|
+
# Ruby 3.2 drops `Object#=~`) and so never reaches the edge; a receiver the analyzer cannot classify keeps the
|
|
1722
|
+
# edge, as every `=~` did before #1494.
|
|
1723
|
+
def match_operator_edges(kinds)
|
|
1724
|
+
return nil if kinds.include?(:custom)
|
|
1725
|
+
return :both if kinds.all? { |kind| STRING_MATCH_KINDS.include?(kind) }
|
|
1726
|
+
|
|
1727
|
+
:match
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
# `recv !~ pattern`'s match edge is the falsey one, which a receiver whose `=~` is not the caller's match
|
|
1731
|
+
# also reaches, so it is kept only when every member is a String, a Symbol, nil, or a `Dynamic` that is
|
|
1732
|
+
# untyped or whose facet holds only those (#1379).
|
|
1733
|
+
def not_match_operator_edges(kinds)
|
|
1734
|
+
return :both if kinds.all? { |kind| STRING_MATCH_KINDS.include?(kind) }
|
|
1735
|
+
|
|
1736
|
+
:match if kinds.all? { |kind| (STRING_MATCH_KINDS + %i[nil dynamic]).include?(kind) }
|
|
1663
1737
|
end
|
|
1664
1738
|
|
|
1665
1739
|
# Whether the local operand {#apply_whole_receiver_refinement} would narrow holds only Strings and nil, which
|
|
1666
1740
|
# the match edge excludes. The refinement names a String, so a member that may be a Symbol — a Symbol, or an
|
|
1667
|
-
# untyped `Dynamic` that
|
|
1668
|
-
# `
|
|
1741
|
+
# untyped `Dynamic` that may be one at run time — keeps it out: `return unless raw =~ /\A\d+\z/` on an untyped
|
|
1742
|
+
# `raw` must not make `raw.to_proc` an undefined method (#1379, #1494).
|
|
1669
1743
|
def refinable_operand?(node, scope)
|
|
1670
1744
|
operand = [node.receiver, *node.arguments&.arguments].find { |o| o.is_a?(Prism::LocalVariableReadNode) }
|
|
1671
1745
|
type = operand && scope.local(operand.name)
|
|
1672
1746
|
return true if type.nil?
|
|
1673
1747
|
|
|
1674
|
-
|
|
1748
|
+
match_member_kinds(type, scope).all? { |kind| %i[string nil].include?(kind) }
|
|
1675
1749
|
end
|
|
1676
1750
|
|
|
1677
|
-
def
|
|
1678
|
-
(type.is_a?(Type::Union) ? type.members : [type]).map { |member|
|
|
1751
|
+
def match_member_kinds(type, scope)
|
|
1752
|
+
(type.is_a?(Type::Union) ? type.members : [type]).map { |member| match_member_kind(member, scope) }
|
|
1679
1753
|
end
|
|
1680
1754
|
|
|
1681
|
-
# `:
|
|
1682
|
-
#
|
|
1683
|
-
#
|
|
1684
|
-
#
|
|
1685
|
-
|
|
1686
|
-
|
|
1755
|
+
# `:custom` for a member whose class the project gives a `=~` of its own (in its ancestry, a reopened
|
|
1756
|
+
# `String` included); `:string` or `:symbol` for a member {.narrow_not_class} proves a String or a Symbol (a
|
|
1757
|
+
# value or refinement of either, or a class the environment orders below it); `:string_subclass` for a
|
|
1758
|
+
# project class whose superclass chain reaches `String`, which the environment cannot order; `:nil`;
|
|
1759
|
+
# `:dynamic` for a `Dynamic` whose facet is untyped or holds only Strings, Symbols and nil; or nil for anything
|
|
1760
|
+
# else.
|
|
1761
|
+
def match_member_kind(member, scope)
|
|
1762
|
+
return match_dynamic_kind(member, scope) if member.is_a?(Type::Dynamic)
|
|
1687
1763
|
return :nil if narrow_nil(member) == member && !member.is_a?(Type::Bot)
|
|
1764
|
+
return :custom if custom_match_operator?(member, scope)
|
|
1688
1765
|
|
|
1689
1766
|
environment = scope.environment
|
|
1690
1767
|
{ "String" => :string, "Symbol" => :symbol }.each do |class_name, kind|
|
|
1691
1768
|
return kind if narrow_not_class(member, class_name, environment: environment, scope: scope).is_a?(Type::Bot)
|
|
1692
1769
|
end
|
|
1693
|
-
|
|
1770
|
+
:string_subclass if project_string_subclass?(member, scope)
|
|
1694
1771
|
end
|
|
1695
1772
|
|
|
1696
|
-
def
|
|
1773
|
+
def match_dynamic_kind(member, scope)
|
|
1697
1774
|
facet = member.static_facet
|
|
1698
1775
|
return :dynamic if facet.is_a?(Type::Top)
|
|
1699
1776
|
|
|
1700
|
-
facet_kinds =
|
|
1701
|
-
:dynamic if facet_kinds.all? { |kind|
|
|
1777
|
+
facet_kinds = match_member_kinds(facet, scope)
|
|
1778
|
+
:dynamic if facet_kinds.all? { |kind| (STRING_MATCH_KINDS + [:nil]).include?(kind) }
|
|
1779
|
+
end
|
|
1780
|
+
|
|
1781
|
+
def custom_match_operator?(member, scope)
|
|
1782
|
+
targets = ProjectMethodOwnership.targets(member)
|
|
1783
|
+
return false if targets.nil?
|
|
1784
|
+
|
|
1785
|
+
targets.any? { |class_name, kind| ProjectMethodOwnership.defines?(class_name, :=~, kind, scope) }
|
|
1786
|
+
end
|
|
1787
|
+
|
|
1788
|
+
def project_string_subclass?(member, scope)
|
|
1789
|
+
return false unless member.is_a?(Type::Nominal) && scope.known_user_class?(member.class_name)
|
|
1790
|
+
|
|
1791
|
+
scope.external_ancestor_name_candidates(member.class_name, mixins: false)
|
|
1792
|
+
.flatten.any? { |name| name.to_s.delete_prefix("::") == "String" }
|
|
1702
1793
|
end
|
|
1703
1794
|
|
|
1704
1795
|
# `str.match?(/\A\d+\z/)` — `String#match?` is a pure boolean predicate whose truthy edge
|
|
@@ -1708,9 +1799,13 @@ module Rigor
|
|
|
1708
1799
|
# passes through unchanged (failing one pattern proves nothing about the string's shape).
|
|
1709
1800
|
# A non-literal / unanchored / `\Z` / line-anchored / `//x` pattern falls through to no
|
|
1710
1801
|
# narrowing.
|
|
1802
|
+
#
|
|
1803
|
+
# The refinement names a String, so it shares `=~`'s gate (#1955): a receiver that may be a Symbol, or an
|
|
1804
|
+
# untyped one, is left as it is ({#refinable_operand?}).
|
|
1711
1805
|
def analyse_whole_regex_match_predicate(node, scope)
|
|
1712
1806
|
return nil unless node.receiver.is_a?(Prism::LocalVariableReadNode)
|
|
1713
1807
|
return nil if node.arguments.nil? || node.arguments.arguments.size != 1
|
|
1808
|
+
return nil unless refinable_operand?(node, scope)
|
|
1714
1809
|
|
|
1715
1810
|
pattern = regex_operand_pattern(node.arguments.arguments.first, scope)
|
|
1716
1811
|
return nil unless pattern.is_a?(RegexMatchPattern)
|
|
@@ -2547,10 +2642,10 @@ module Rigor
|
|
|
2547
2642
|
# iff the endpoints compare it. We conservatively narrow `x` to `Numeric` for
|
|
2548
2643
|
# integer-endpoint ranges and to `String` for string-endpoint ranges; other endpoint
|
|
2549
2644
|
# types fall through.
|
|
2550
|
-
# - **Regexp literal receiver** (`/foo/ === x`): the
|
|
2551
|
-
#
|
|
2552
|
-
# type unchanged because `Regexp#===` returns false for non-Strings AND
|
|
2553
|
-
# that simply do not match.
|
|
2645
|
+
# - **Regexp literal receiver** (`/foo/ === x`): the truthy edge narrows `x` as
|
|
2646
|
+
# {#regexp_case_equality_type} says (its String and Symbol parts, #1955). The falsey edge
|
|
2647
|
+
# keeps the entry type unchanged because `Regexp#===` returns false for non-Strings AND
|
|
2648
|
+
# for Strings that simply do not match.
|
|
2554
2649
|
#
|
|
2555
2650
|
# Anything else — non-local LHS argument, dynamic receiver, custom `===` method — falls
|
|
2556
2651
|
# through to the no-narrowing branch (nil), preserving the entry scope on both edges.
|
|
@@ -2614,6 +2709,9 @@ module Rigor
|
|
|
2614
2709
|
if (class_name = lexical_class_name(receiver, scope))
|
|
2615
2710
|
return class_predicate_scopes(scope, local_name, current, class_name, exact: false)
|
|
2616
2711
|
end
|
|
2712
|
+
if regexp_literal?(receiver)
|
|
2713
|
+
return [scope.with_local(local_name, regexp_case_equality_type(current, scope)), scope]
|
|
2714
|
+
end
|
|
2617
2715
|
|
|
2618
2716
|
target_class = case_equality_target_class(receiver)
|
|
2619
2717
|
return nil if target_class.nil?
|
|
@@ -2626,11 +2724,27 @@ module Rigor
|
|
|
2626
2724
|
]
|
|
2627
2725
|
end
|
|
2628
2726
|
|
|
2727
|
+
# The class a `when` condition narrows the subject to, as `is_a?` would, or nil when the condition is no class
|
|
2728
|
+
# test Rigor reads: a class or module constant, a range literal ({.case_equality_target_class}),
|
|
2729
|
+
# or (#1925) a `nil` / `true` / `false` literal. `nil === x` runs `NilClass#===`, which holds only for `nil`
|
|
2730
|
+
# itself whatever the subject's class defines, and `nil` is `NilClass`'s only instance, so `when nil` is
|
|
2731
|
+
# `when NilClass`; `true` and `false` likewise.
|
|
2732
|
+
def when_condition_class(condition, scope)
|
|
2733
|
+
lexical_class_name(condition, scope) || case_equality_target_class(condition) ||
|
|
2734
|
+
SINGLETON_WHEN_LITERAL_CLASSES[unwrap_parens(condition).class]
|
|
2735
|
+
end
|
|
2736
|
+
|
|
2737
|
+
SINGLETON_WHEN_LITERAL_CLASSES = {
|
|
2738
|
+
Prism::NilNode => "NilClass", Prism::TrueNode => "TrueClass", Prism::FalseNode => "FalseClass"
|
|
2739
|
+
}.freeze
|
|
2740
|
+
private_constant :SINGLETON_WHEN_LITERAL_CLASSES
|
|
2741
|
+
|
|
2629
2742
|
# Maps a case-equality literal receiver to the class whose membership is implied by the
|
|
2630
2743
|
# truthy edge. `Prism::ParenthesesNode` wrappers are transparently unwrapped (`(1..10)
|
|
2631
2744
|
# === x` is parsed with the range inside parentheses). Range literals: integer
|
|
2632
|
-
# endpoints → `Numeric`; string endpoints → `String`. Regexp literals → `String
|
|
2633
|
-
#
|
|
2745
|
+
# endpoints → `Numeric`; string endpoints → `String`. Regexp literals → `String`, read only
|
|
2746
|
+
# as the class a dead `when` arm names: their narrowing is {#regexp_case_equality_type}'s,
|
|
2747
|
+
# which the callers consult first. Other shapes return nil so the caller falls through.
|
|
2634
2748
|
def case_equality_target_class(receiver)
|
|
2635
2749
|
receiver = unwrap_parens(receiver)
|
|
2636
2750
|
case receiver
|
|
@@ -2639,6 +2753,33 @@ module Rigor
|
|
|
2639
2753
|
end
|
|
2640
2754
|
end
|
|
2641
2755
|
|
|
2756
|
+
def regexp_literal?(node)
|
|
2757
|
+
case unwrap_parens(node)
|
|
2758
|
+
when Prism::RegularExpressionNode, Prism::InterpolatedRegularExpressionNode then true
|
|
2759
|
+
else false
|
|
2760
|
+
end
|
|
2761
|
+
end
|
|
2762
|
+
|
|
2763
|
+
# Issue #1955 — the subject on the match edge of `/re/ === x` (and of `case x when /re/`). `Regexp#===`
|
|
2764
|
+
# matches a String, a Symbol by its name, and an object that converts with `to_str`, and answers false for
|
|
2765
|
+
# anything else. Each member narrows to its String and Symbol parts: a `String | nil` subject reads `String`, a
|
|
2766
|
+
# Symbol one stays `Symbol`. The `to_str` conversion is not modelled, so a member that defines `to_str` and is
|
|
2767
|
+
# neither drops out of the edge. A `Dynamic` member is left as it is, since it may be a Symbol at run time. The
|
|
2768
|
+
# no-match edge keeps the entry type: a String that does not match reaches it too.
|
|
2769
|
+
def regexp_case_equality_type(current, scope)
|
|
2770
|
+
members = current.is_a?(Type::Union) ? current.members : [current]
|
|
2771
|
+
Type::Combinator.union(*members.map { |member| regexp_case_equality_member(member, scope) })
|
|
2772
|
+
end
|
|
2773
|
+
|
|
2774
|
+
def regexp_case_equality_member(member, scope)
|
|
2775
|
+
return member if member.is_a?(Type::Dynamic)
|
|
2776
|
+
|
|
2777
|
+
parts = %w[String Symbol].map do |class_name|
|
|
2778
|
+
narrow_class(member, class_name, exact: false, environment: scope.environment, scope: scope)
|
|
2779
|
+
end
|
|
2780
|
+
Type::Combinator.union(*parts)
|
|
2781
|
+
end
|
|
2782
|
+
|
|
2642
2783
|
def unwrap_parens(node)
|
|
2643
2784
|
while node.is_a?(Prism::ParenthesesNode) && node.body.is_a?(Prism::StatementsNode) &&
|
|
2644
2785
|
node.body.body.size == 1
|
|
@@ -2818,15 +2959,15 @@ module Rigor
|
|
|
2818
2959
|
# narrowed type for the subject as if `condition === subject`, and accumulates them.
|
|
2819
2960
|
# The body's narrowed type is the union across all conditions; the falsey type is the
|
|
2820
2961
|
# running result after subtracting every condition's class. Conditions whose shape we
|
|
2821
|
-
# cannot statically classify
|
|
2822
|
-
#
|
|
2823
|
-
# edge is the entry type (because we cannot prove the unknown condition didn't match).
|
|
2962
|
+
# cannot statically classify narrow neither edge: the body reads as
|
|
2963
|
+
# {.case_when_body_type} says, and the falsey edge is the entry type.
|
|
2824
2964
|
#
|
|
2825
2965
|
# `[body type, next-clause type]` of a `when` clause's conditions over the subject type `current`.
|
|
2826
2966
|
def case_when_types(scope, current, conditions)
|
|
2827
2967
|
truthy_members = []
|
|
2828
2968
|
falsey_type = current
|
|
2829
2969
|
fully_narrowable = true
|
|
2970
|
+
unread = false
|
|
2830
2971
|
|
|
2831
2972
|
conditions.each do |condition|
|
|
2832
2973
|
applied = apply_case_when_condition(scope, current, condition, falsey_type)
|
|
@@ -2835,12 +2976,24 @@ module Rigor
|
|
|
2835
2976
|
falsey_type = applied[:falsey]
|
|
2836
2977
|
fully_narrowable &&= applied[:fully_narrowable]
|
|
2837
2978
|
else
|
|
2979
|
+
unread = true
|
|
2838
2980
|
fully_narrowable = false
|
|
2839
2981
|
end
|
|
2840
2982
|
end
|
|
2841
2983
|
|
|
2842
|
-
|
|
2843
|
-
|
|
2984
|
+
[case_when_body_type(current, truthy_members, unread), fully_narrowable ? falsey_type : current]
|
|
2985
|
+
end
|
|
2986
|
+
|
|
2987
|
+
# #1925 — the body type of a `when` clause. A clause whose conditions are all unread keeps the entry type. A
|
|
2988
|
+
# clause mixing read conditions with an unread one reads `Dynamic[top]`: the body may run because the unread
|
|
2989
|
+
# condition matched, so the read conditions' union is unsound (`when nil, ""` is not `nil`), and the entry
|
|
2990
|
+
# type is wider than what reaches the body when the unread condition is itself a test
|
|
2991
|
+
# (`when /a/, ->(v) { !v.nil? }` never enters with `nil`), so reading it would report a call no `nil` reaches.
|
|
2992
|
+
def case_when_body_type(current, truthy_members, unread)
|
|
2993
|
+
return current if truthy_members.empty?
|
|
2994
|
+
return Type::Combinator.untyped if unread
|
|
2995
|
+
|
|
2996
|
+
Type::Combinator.union(*truthy_members)
|
|
2844
2997
|
end
|
|
2845
2998
|
|
|
2846
2999
|
# Per-condition rule. Returns `nil` when the condition shape is not recognised (caller
|
|
@@ -2855,7 +3008,11 @@ module Rigor
|
|
|
2855
3008
|
return integer_literal_when_result(current, int_literal, falsey_acc)
|
|
2856
3009
|
end
|
|
2857
3010
|
|
|
2858
|
-
|
|
3011
|
+
if regexp_literal?(condition)
|
|
3012
|
+
return { truthy: regexp_case_equality_type(current, scope), falsey: falsey_acc, fully_narrowable: true }
|
|
3013
|
+
end
|
|
3014
|
+
|
|
3015
|
+
target = when_condition_class(condition, scope)
|
|
2859
3016
|
return class_when_result(scope, current, target, falsey_acc) if target
|
|
2860
3017
|
|
|
2861
3018
|
nil
|
|
@@ -3677,9 +3834,12 @@ module Rigor
|
|
|
3677
3834
|
# that edge, so a `$_` the earlier operand narrowed is forgotten when the operand may set it
|
|
3678
3835
|
# (`gets && log(items.each { gets })`), before the operand's own edges narrow it again.
|
|
3679
3836
|
# Issue #1429 — likewise a guard's narrowing of a global or constant the operand may rebind
|
|
3680
|
-
# (`$sep && reset_sep && $sep.length`).
|
|
3837
|
+
# (`$sep && reset_sep && $sep.length`). Issue #1963 — and an instance variable's narrowing an implicit-self
|
|
3838
|
+
# or `self.` call in the operand may write ({InterveningIvarReset.after_operand}): `@x.nil? && reset!` does
|
|
3839
|
+
# not leave `@x` `nil` when `reset!` assigns it.
|
|
3681
3840
|
def operand_edges(node, edge_scope)
|
|
3682
3841
|
edge_scope = LastLine.forget_if_set(edge_scope, node)
|
|
3842
|
+
edge_scope = InterveningIvarReset.after_operand(node, edge_scope)
|
|
3683
3843
|
if edge_scope.guard_narrowed? && GuardRebinding.may_rebind?(node, edge_scope)
|
|
3684
3844
|
edge_scope = edge_scope.forget_guard_narrowings
|
|
3685
3845
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "prism"
|
|
4
4
|
|
|
5
5
|
require_relative "scope_indexer"
|
|
6
|
+
require_relative "project_seed_tables"
|
|
6
7
|
require_relative "fork_map"
|
|
7
8
|
require_relative "../source/node_walker"
|
|
8
9
|
|
|
@@ -238,18 +239,10 @@ module Rigor
|
|
|
238
239
|
base.with_discovery(base.discovery.with(**tables))
|
|
239
240
|
end
|
|
240
241
|
|
|
242
|
+
# Issue #1614 — the tables a `check` run seeds, from the one list {ProjectSeedTables} keeps for every path that
|
|
243
|
+
# runs no `check`, so an argument types here as it does in the file `check` analyses.
|
|
241
244
|
def discovery_seed_tables
|
|
242
|
-
|
|
243
|
-
def_index = ScopeIndexer.discovered_def_index_for_paths(@files)
|
|
244
|
-
tables = {}
|
|
245
|
-
tables[:discovered_classes] = classes unless classes.empty?
|
|
246
|
-
DISCOVERY_FIELD.each do |index_key, field|
|
|
247
|
-
table = def_index.fetch(index_key)
|
|
248
|
-
tables[field] = table unless table.empty? && !Scope::DiscoveryIndex.paired?(field)
|
|
249
|
-
end
|
|
250
|
-
# ADR-119 WD1 — the pairs travel together: a pair is dropped only when both halves are empty. A sibling whose
|
|
251
|
-
# member this collector does not seed completes to that member's empty value, which the base scope holds.
|
|
252
|
-
Scope::DiscoveryIndex.compact_pairs(tables.merge(def_index.fetch(:siblings)))
|
|
245
|
+
ProjectSeedTables.for_paths(@files)
|
|
253
246
|
rescue StandardError => e
|
|
254
247
|
# Discovery is best-effort; a malformed corner of the project must not crash the
|
|
255
248
|
# protection scan. Without discovery the collector simply resolves fewer call sites. A broken
|
|
@@ -257,21 +250,6 @@ module Rigor
|
|
|
257
250
|
e.is_a?(DeclarationWalk::ContractError) ? raise : {}
|
|
258
251
|
end
|
|
259
252
|
|
|
260
|
-
DISCOVERY_FIELD = {
|
|
261
|
-
def_nodes: :discovered_def_nodes,
|
|
262
|
-
singleton_def_nodes: :discovered_singleton_def_nodes,
|
|
263
|
-
def_sources: :discovered_def_sources,
|
|
264
|
-
superclasses: :discovered_superclasses,
|
|
265
|
-
header_nestings: :discovered_header_nestings,
|
|
266
|
-
includes: :discovered_includes, unpositioned_mixins: :unpositioned_mixins,
|
|
267
|
-
class_sources: :discovered_class_sources,
|
|
268
|
-
method_visibilities: :discovered_method_visibilities,
|
|
269
|
-
methods: :discovered_methods,
|
|
270
|
-
data_member_layouts: :data_member_layouts,
|
|
271
|
-
struct_member_layouts: :struct_member_layouts, deferred_ranges: :discovered_deferred_ranges
|
|
272
|
-
}.freeze
|
|
273
|
-
private_constant :DISCOVERY_FIELD
|
|
274
|
-
|
|
275
253
|
# Built once over the whole project and inherited by the fork workers via copy-on-write; a frozen Set
|
|
276
254
|
# for O(1) membership in the {#record_call} hot loop.
|
|
277
255
|
def user_method_names(discovery)
|