rigortype 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +18 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +67 -0
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +226 -0
- data/lib/rigor/analysis/check_rules.rb +180 -73
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +580 -0
- data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +318 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
- data/lib/rigor/analysis/runner.rb +477 -1110
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/analysis/worker_session.rb +47 -8
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +153 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +34 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +145 -14
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/check_command.rb +705 -0
- data/lib/rigor/cli/ci_detector.rb +94 -0
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diagnostic_formats.rb +345 -0
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +2 -7
- data/lib/rigor/cli/prism_colorizer.rb +10 -3
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/trace_command.rb +143 -0
- data/lib/rigor/cli/trace_renderer.rb +310 -0
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +15 -532
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +16 -3
- data/lib/rigor/environment/rbs_loader.rb +129 -71
- data/lib/rigor/environment.rb +1 -1
- data/lib/rigor/inference/acceptance.rb +10 -0
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +149 -63
- data/lib/rigor/inference/flow_tracer.rb +180 -0
- data/lib/rigor/inference/macro_block_self_type.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +33 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +185 -84
- data/lib/rigor/inference/narrowing.rb +262 -5
- data/lib/rigor/inference/scope_indexer.rb +208 -21
- data/lib/rigor/inference/statement_evaluator.rb +110 -48
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/additional_initializer.rb +61 -38
- data/lib/rigor/plugin/base.rb +302 -45
- data/lib/rigor/plugin/node_rule_walk.rb +147 -0
- data/lib/rigor/plugin/registry.rb +281 -15
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/conformance_checker.rb +293 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope/discovery_index.rb +58 -0
- data/lib/rigor/scope.rb +150 -167
- data/lib/rigor/sig_gen/observation_collector.rb +6 -6
- data/lib/rigor/source/literals.rb +14 -0
- data/lib/rigor/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +22 -0
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +5 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +70 -32
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +15 -21
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +35 -18
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +83 -36
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/environment.rbs +0 -2
- data/sig/rigor/inference.rbs +27 -0
- data/sig/rigor/plugin/base.rbs +1 -2
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +42 -25
- data/sig/rigor/source.rbs +1 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- data/skills/rigor-ci-setup/SKILL.md +319 -0
- metadata +36 -2
- data/lib/rigor/cache/rbs_instance_definitions.rb +0 -79
data/lib/rigor/rbs_extended.rb
CHANGED
|
@@ -608,6 +608,45 @@ module Rigor
|
|
|
608
608
|
ParamOverride.new(param_name: match[:param].to_sym, type: type)
|
|
609
609
|
end
|
|
610
610
|
|
|
611
|
+
# A class- / module-level directive declaring that the
|
|
612
|
+
# annotated class satisfies a named structural interface as
|
|
613
|
+
# part of its public contract (spec:
|
|
614
|
+
# `docs/type-specification/rbs-extended.md` § "Explicit
|
|
615
|
+
# conformance directive"). Unlike the per-method directives
|
|
616
|
+
# above, this attaches to a `class` / `module` declaration and
|
|
617
|
+
# names a single RBS interface (`_RewindableStream`); the
|
|
618
|
+
# right-hand side is therefore an interface name (its last
|
|
619
|
+
# segment begins with `_`), never a refinement payload.
|
|
620
|
+
#
|
|
621
|
+
# This parser only extracts the interface name; the
|
|
622
|
+
# conformance check itself lives in
|
|
623
|
+
# {Rigor::RbsExtended::ConformanceChecker}, which the
|
|
624
|
+
# {Rigor::Analysis::Runner} runs once per project run.
|
|
625
|
+
CONFORMS_TO_DIRECTIVE_PATTERN = /
|
|
626
|
+
\A
|
|
627
|
+
rigor:v1:conforms-to
|
|
628
|
+
\s+
|
|
629
|
+
(?<interface>(?:::)?(?:[A-Z]\w*::)*_[A-Za-z]\w*)
|
|
630
|
+
\s*
|
|
631
|
+
\z
|
|
632
|
+
/x
|
|
633
|
+
private_constant :CONFORMS_TO_DIRECTIVE_PATTERN
|
|
634
|
+
|
|
635
|
+
# Returns the interface name (leading `::` stripped) for a
|
|
636
|
+
# `rigor:v1:conforms-to <Interface>` annotation, or `nil` when
|
|
637
|
+
# the string is not a conforms-to directive (so callers can
|
|
638
|
+
# walk an annotation list without pre-filtering). The name is
|
|
639
|
+
# returned verbatim otherwise — namespace resolution happens at
|
|
640
|
+
# the loader boundary when the interface is built.
|
|
641
|
+
def parse_conforms_to_annotation(string)
|
|
642
|
+
return nil if string.nil?
|
|
643
|
+
|
|
644
|
+
match = CONFORMS_TO_DIRECTIVE_PATTERN.match(string)
|
|
645
|
+
return nil if match.nil?
|
|
646
|
+
|
|
647
|
+
match[:interface].to_s.sub(/\A::/, "")
|
|
648
|
+
end
|
|
649
|
+
|
|
611
650
|
# The shared {Rigor::FlowContribution::Provenance} for every
|
|
612
651
|
# bundle this module produces. `source_family: :rbs_extended`
|
|
613
652
|
# so consumers (today the documentation surface; v0.1.0 the
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
class Scope
|
|
5
|
+
# ADR-53 Track A — the seed-time discovery context every Scope snapshot
|
|
6
|
+
# carries by a single reference. Holds the tables the index-time
|
|
7
|
+
# pre-passes (`Inference::ScopeIndexer` per file, plus the cross-file
|
|
8
|
+
# project pre-pass) populate and that no control-flow transition ever
|
|
9
|
+
# varies: `Scope#==` ignores them and `Scope#join` copies them from the
|
|
10
|
+
# receiver unexamined, which is the membership litmus the ADR fixes.
|
|
11
|
+
#
|
|
12
|
+
# Immutable (`Data` instances are frozen); deriving a seeded index goes
|
|
13
|
+
# through `#with(table_name: table)`. `Scope` exposes each table through
|
|
14
|
+
# its existing reader surface, so engine call sites and plugins are
|
|
15
|
+
# unaffected by the extraction.
|
|
16
|
+
DiscoveryIndex = Data.define(
|
|
17
|
+
:declared_types,
|
|
18
|
+
:class_ivars,
|
|
19
|
+
:class_cvars,
|
|
20
|
+
:program_globals,
|
|
21
|
+
:discovered_classes,
|
|
22
|
+
:in_source_constants,
|
|
23
|
+
:discovered_methods,
|
|
24
|
+
:discovered_def_nodes,
|
|
25
|
+
:discovered_def_sources,
|
|
26
|
+
:discovered_method_visibilities,
|
|
27
|
+
:discovered_superclasses,
|
|
28
|
+
:discovered_includes,
|
|
29
|
+
:discovered_class_sources,
|
|
30
|
+
:data_member_layouts
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
class DiscoveryIndex
|
|
34
|
+
EMPTY_NODE_TABLE = {}.compare_by_identity.freeze
|
|
35
|
+
EMPTY_TABLE = {}.freeze
|
|
36
|
+
private_constant :EMPTY_NODE_TABLE, :EMPTY_TABLE
|
|
37
|
+
|
|
38
|
+
# The shared all-empty index `Scope.empty` (and every scope that never
|
|
39
|
+
# sees a seeding pass) points at — one allocation per process.
|
|
40
|
+
EMPTY = new(
|
|
41
|
+
declared_types: EMPTY_NODE_TABLE,
|
|
42
|
+
class_ivars: EMPTY_TABLE,
|
|
43
|
+
class_cvars: EMPTY_TABLE,
|
|
44
|
+
program_globals: EMPTY_TABLE,
|
|
45
|
+
discovered_classes: EMPTY_TABLE,
|
|
46
|
+
in_source_constants: EMPTY_TABLE,
|
|
47
|
+
discovered_methods: EMPTY_TABLE,
|
|
48
|
+
discovered_def_nodes: EMPTY_TABLE,
|
|
49
|
+
discovered_def_sources: EMPTY_TABLE,
|
|
50
|
+
discovered_method_visibilities: EMPTY_TABLE,
|
|
51
|
+
discovered_superclasses: EMPTY_TABLE,
|
|
52
|
+
discovered_includes: EMPTY_TABLE,
|
|
53
|
+
discovered_class_sources: EMPTY_TABLE,
|
|
54
|
+
data_member_layouts: EMPTY_TABLE
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/rigor/scope.rb
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "type"
|
|
4
4
|
require_relative "environment"
|
|
5
|
+
require_relative "scope/discovery_index"
|
|
5
6
|
require_relative "analysis/fact_store"
|
|
7
|
+
require_relative "analysis/dependency_recorder"
|
|
6
8
|
require_relative "inference/expression_typer"
|
|
9
|
+
require_relative "inference/flow_tracer"
|
|
7
10
|
require_relative "inference/statement_evaluator"
|
|
8
11
|
|
|
9
12
|
module Rigor
|
|
@@ -16,14 +19,36 @@ module Rigor
|
|
|
16
19
|
# See docs/internal-spec/inference-engine.md for the binding contract.
|
|
17
20
|
# rubocop:disable Metrics/ClassLength,Metrics/ParameterLists
|
|
18
21
|
class Scope
|
|
19
|
-
attr_reader :environment, :locals, :fact_store, :self_type,
|
|
22
|
+
attr_reader :environment, :locals, :fact_store, :self_type,
|
|
20
23
|
:ivars, :cvars, :globals,
|
|
21
|
-
:class_ivars, :class_cvars, :program_globals,
|
|
22
|
-
:discovered_classes, :in_source_constants, :discovered_methods,
|
|
23
|
-
:discovered_def_nodes, :discovered_def_sources, :discovered_method_visibilities,
|
|
24
|
-
:discovered_superclasses, :discovered_includes,
|
|
25
24
|
:indexed_narrowings, :method_chain_narrowings,
|
|
26
|
-
:source_path
|
|
25
|
+
:source_path, :discovery
|
|
26
|
+
|
|
27
|
+
# ADR-53 Track A — the seed-time discovery tables live on the
|
|
28
|
+
# {DiscoveryIndex} the scope carries by a single reference; the
|
|
29
|
+
# per-table readers stay on Scope so engine call sites and plugins
|
|
30
|
+
# are unaffected by the extraction. The whole index is swapped in
|
|
31
|
+
# one transition through {#with_discovery}.
|
|
32
|
+
#
|
|
33
|
+
# `declared_types` carries the identity-comparing
|
|
34
|
+
# `Prism::Node => Rigor::Type` declaration overrides
|
|
35
|
+
# `ExpressionTyper#type_of(node)` MUST consult before any other
|
|
36
|
+
# dispatch (a `module Foo` / `class Bar` header types as
|
|
37
|
+
# `Singleton[<qualified path>]` rather than `Dynamic[Top]`).
|
|
38
|
+
def declared_types = @discovery.declared_types
|
|
39
|
+
def class_ivars = @discovery.class_ivars
|
|
40
|
+
def class_cvars = @discovery.class_cvars
|
|
41
|
+
def program_globals = @discovery.program_globals
|
|
42
|
+
def discovered_classes = @discovery.discovered_classes
|
|
43
|
+
def in_source_constants = @discovery.in_source_constants
|
|
44
|
+
def discovered_methods = @discovery.discovered_methods
|
|
45
|
+
def discovered_def_nodes = @discovery.discovered_def_nodes
|
|
46
|
+
def discovered_def_sources = @discovery.discovered_def_sources
|
|
47
|
+
def discovered_method_visibilities = @discovery.discovered_method_visibilities
|
|
48
|
+
def discovered_superclasses = @discovery.discovered_superclasses
|
|
49
|
+
def discovered_includes = @discovery.discovered_includes
|
|
50
|
+
def discovered_class_sources = @discovery.discovered_class_sources
|
|
51
|
+
def data_member_layouts = @discovery.data_member_layouts
|
|
27
52
|
|
|
28
53
|
# Narrowing key for an indexed read `receiver[key]` where both
|
|
29
54
|
# the receiver and the key are stable enough to address. The
|
|
@@ -58,13 +83,10 @@ module Rigor
|
|
|
58
83
|
# multi-hop loses the LoD guarantee).
|
|
59
84
|
ChainKey = Data.define(:receiver_kind, :receiver_name, :method_name)
|
|
60
85
|
|
|
61
|
-
EMPTY_DECLARED_TYPES = {}.compare_by_identity.freeze
|
|
62
86
|
EMPTY_VAR_BINDINGS = {}.freeze
|
|
63
|
-
EMPTY_CLASS_BINDINGS = {}.freeze
|
|
64
87
|
EMPTY_INDEXED_NARROWINGS = {}.freeze
|
|
65
88
|
EMPTY_CHAIN_NARROWINGS = {}.freeze
|
|
66
|
-
private_constant :
|
|
67
|
-
:EMPTY_CLASS_BINDINGS, :EMPTY_INDEXED_NARROWINGS,
|
|
89
|
+
private_constant :EMPTY_VAR_BINDINGS, :EMPTY_INDEXED_NARROWINGS,
|
|
68
90
|
:EMPTY_CHAIN_NARROWINGS
|
|
69
91
|
|
|
70
92
|
class << self
|
|
@@ -78,21 +100,10 @@ module Rigor
|
|
|
78
100
|
environment:, locals:,
|
|
79
101
|
fact_store: Analysis::FactStore.empty,
|
|
80
102
|
self_type: nil,
|
|
81
|
-
declared_types: EMPTY_DECLARED_TYPES,
|
|
82
103
|
ivars: EMPTY_VAR_BINDINGS,
|
|
83
104
|
cvars: EMPTY_VAR_BINDINGS,
|
|
84
105
|
globals: EMPTY_VAR_BINDINGS,
|
|
85
|
-
|
|
86
|
-
class_cvars: EMPTY_CLASS_BINDINGS,
|
|
87
|
-
program_globals: EMPTY_VAR_BINDINGS,
|
|
88
|
-
discovered_classes: EMPTY_VAR_BINDINGS,
|
|
89
|
-
in_source_constants: EMPTY_VAR_BINDINGS,
|
|
90
|
-
discovered_methods: EMPTY_CLASS_BINDINGS,
|
|
91
|
-
discovered_def_nodes: EMPTY_CLASS_BINDINGS,
|
|
92
|
-
discovered_def_sources: EMPTY_CLASS_BINDINGS,
|
|
93
|
-
discovered_method_visibilities: EMPTY_CLASS_BINDINGS,
|
|
94
|
-
discovered_superclasses: EMPTY_CLASS_BINDINGS,
|
|
95
|
-
discovered_includes: EMPTY_CLASS_BINDINGS,
|
|
106
|
+
discovery: DiscoveryIndex::EMPTY,
|
|
96
107
|
indexed_narrowings: EMPTY_INDEXED_NARROWINGS,
|
|
97
108
|
method_chain_narrowings: EMPTY_CHAIN_NARROWINGS,
|
|
98
109
|
source_path: nil
|
|
@@ -101,21 +112,10 @@ module Rigor
|
|
|
101
112
|
@locals = locals
|
|
102
113
|
@fact_store = fact_store
|
|
103
114
|
@self_type = self_type
|
|
104
|
-
@declared_types = declared_types
|
|
105
115
|
@ivars = ivars
|
|
106
116
|
@cvars = cvars
|
|
107
117
|
@globals = globals
|
|
108
|
-
@
|
|
109
|
-
@class_cvars = class_cvars
|
|
110
|
-
@program_globals = program_globals
|
|
111
|
-
@discovered_classes = discovered_classes
|
|
112
|
-
@in_source_constants = in_source_constants
|
|
113
|
-
@discovered_methods = discovered_methods
|
|
114
|
-
@discovered_def_nodes = discovered_def_nodes
|
|
115
|
-
@discovered_def_sources = discovered_def_sources
|
|
116
|
-
@discovered_method_visibilities = discovered_method_visibilities
|
|
117
|
-
@discovered_superclasses = discovered_superclasses
|
|
118
|
-
@discovered_includes = discovered_includes
|
|
118
|
+
@discovery = discovery
|
|
119
119
|
@indexed_narrowings = indexed_narrowings
|
|
120
120
|
@method_chain_narrowings = method_chain_narrowings
|
|
121
121
|
@source_path = source_path
|
|
@@ -127,6 +127,8 @@ module Rigor
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def with_local(name, type)
|
|
130
|
+
# `rigor trace` — the moment a local enters the scope.
|
|
131
|
+
Inference::FlowTracer.bind(name, type) if Inference::FlowTracer.active?
|
|
130
132
|
new_locals = @locals.merge(name.to_sym => type).freeze
|
|
131
133
|
new_fact_store = fact_store.invalidate_target(Analysis::FactStore::Target.local(name))
|
|
132
134
|
# Rebinding `name` invalidates every "after `receiver[key]
|
|
@@ -160,28 +162,19 @@ module Rigor
|
|
|
160
162
|
# ADR-11 per-call-site assertion gating prerequisite. The
|
|
161
163
|
# analyzer's per-file boundary stamps the current source
|
|
162
164
|
# file's path onto the seed scope; nested rebuilds carry
|
|
163
|
-
# the value through so plugin
|
|
164
|
-
# `
|
|
165
|
-
# this call site belong to?" without
|
|
165
|
+
# the value through so plugin rules (`dynamic_return`'s
|
|
166
|
+
# `file_methods:` gate, sigil checks) can resolve "which
|
|
167
|
+
# file does this call site belong to?" without
|
|
168
|
+
# thread-locals.
|
|
166
169
|
def with_source_path(path)
|
|
167
170
|
rebuild(source_path: path)
|
|
168
171
|
end
|
|
169
172
|
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# populated by `ScopeIndexer` for declaration-position
|
|
176
|
-
# nodes (the `constant_path` of `Prism::ModuleNode` and
|
|
177
|
-
# `Prism::ClassNode`) so a `module Foo` / `class Bar`
|
|
178
|
-
# header types as `Singleton[<qualified path>]` instead of
|
|
179
|
-
# falling through to `Dynamic[Top]`. The table is shared
|
|
180
|
-
# by structural reference across every derived scope so
|
|
181
|
-
# `with_local` / `with_fact` / `with_self_type` carry it
|
|
182
|
-
# transparently.
|
|
183
|
-
def with_declared_types(table)
|
|
184
|
-
rebuild(declared_types: table)
|
|
173
|
+
# ADR-53 Track A — swaps the whole discovery index in one transition.
|
|
174
|
+
# The sole seeding path; the per-table writers it replaced are derived
|
|
175
|
+
# off-`Scope` through `scope.discovery.with(table_name: table)`.
|
|
176
|
+
def with_discovery(index)
|
|
177
|
+
rebuild(discovery: index)
|
|
185
178
|
end
|
|
186
179
|
|
|
187
180
|
# Slice 7 phase 1 — instance/class/global variable bindings.
|
|
@@ -237,11 +230,7 @@ module Rigor
|
|
|
237
230
|
def class_ivars_for(class_name)
|
|
238
231
|
return EMPTY_VAR_BINDINGS if class_name.nil?
|
|
239
232
|
|
|
240
|
-
@class_ivars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
def with_class_ivars(table)
|
|
244
|
-
rebuild(class_ivars: table)
|
|
233
|
+
@discovery.class_ivars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
245
234
|
end
|
|
246
235
|
|
|
247
236
|
# Slice 7 phase 6 — class-level cvar accumulator (same shape
|
|
@@ -251,48 +240,7 @@ module Rigor
|
|
|
251
240
|
def class_cvars_for(class_name)
|
|
252
241
|
return EMPTY_VAR_BINDINGS if class_name.nil?
|
|
253
242
|
|
|
254
|
-
@class_cvars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def with_class_cvars(table)
|
|
258
|
-
rebuild(class_cvars: table)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
# Slice 7 phase 6 — program-level globals accumulator.
|
|
262
|
-
# Globals are process-wide in Ruby, so the analyzer carries a
|
|
263
|
-
# single map (`Hash[Symbol, Type]`) keyed by the variable name
|
|
264
|
-
# and seeded into every method body (instance and singleton)
|
|
265
|
-
# plus the top-level program scope. `ScopeIndexer` populates
|
|
266
|
-
# it from a single program-wide pre-pass.
|
|
267
|
-
def with_program_globals(table)
|
|
268
|
-
rebuild(program_globals: table)
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
# Slice 7 phase 7 — in-source class discovery. Maps a
|
|
272
|
-
# qualified class name (e.g. `"Account"`) to its
|
|
273
|
-
# `Type::Singleton` so references to user-defined classes
|
|
274
|
-
# in the analyzed files resolve through
|
|
275
|
-
# `ExpressionTyper#resolve_constant_name` even when no RBS
|
|
276
|
-
# decl exists. Populated once at index time by
|
|
277
|
-
# `ScopeIndexer` from every `Prism::ClassNode` and
|
|
278
|
-
# `Prism::ModuleNode` it walks.
|
|
279
|
-
def with_discovered_classes(table)
|
|
280
|
-
rebuild(discovered_classes: table)
|
|
281
|
-
end
|
|
282
|
-
|
|
283
|
-
# Slice 7 phase 9 — in-source constant-value tracking.
|
|
284
|
-
# Maps a qualified constant name (e.g. `"BUCKETS"` or
|
|
285
|
-
# `"Rigor::Analysis::FactStore::BUCKETS"`) to the type of
|
|
286
|
-
# the rvalue assigned at its `Prism::ConstantWriteNode` /
|
|
287
|
-
# `Prism::ConstantPathWriteNode`. Populated by
|
|
288
|
-
# `ScopeIndexer` once at index time. `ExpressionTyper#resolve_constant_name`
|
|
289
|
-
# consults this map after class lookups so an in-source
|
|
290
|
-
# constant assignment overrides any RBS-declared constant
|
|
291
|
-
# of the same qualified name (matching Ruby's runtime
|
|
292
|
-
# precedence: a constant defined in user code is the
|
|
293
|
-
# authoritative value).
|
|
294
|
-
def with_in_source_constants(table)
|
|
295
|
-
rebuild(in_source_constants: table)
|
|
243
|
+
@discovery.class_cvars[class_name.to_s] || EMPTY_VAR_BINDINGS
|
|
296
244
|
end
|
|
297
245
|
|
|
298
246
|
# Slice 7 phase 12 — in-source method discovery. Maps a
|
|
@@ -305,7 +253,7 @@ module Rigor
|
|
|
305
253
|
# user has defined dynamically, even when no RBS sig
|
|
306
254
|
# describes them.
|
|
307
255
|
def discovered_method?(class_name, method_name, kind)
|
|
308
|
-
table = @discovered_methods[class_name.to_s]
|
|
256
|
+
table = @discovery.discovered_methods[class_name.to_s]
|
|
309
257
|
return false unless table
|
|
310
258
|
|
|
311
259
|
table[method_name.to_sym] == kind
|
|
@@ -325,10 +273,6 @@ module Rigor
|
|
|
325
273
|
@self_type.nil?
|
|
326
274
|
end
|
|
327
275
|
|
|
328
|
-
def with_discovered_methods(table)
|
|
329
|
-
rebuild(discovered_methods: table)
|
|
330
|
-
end
|
|
331
|
-
|
|
332
276
|
# v0.0.2 #5 — per-class table mapping
|
|
333
277
|
# `method_name (Symbol) → Prism::DefNode`. Populated by
|
|
334
278
|
# `ScopeIndexer` alongside `discovered_methods` for
|
|
@@ -339,11 +283,30 @@ module Rigor
|
|
|
339
283
|
# inference when the receiver class is user-defined and
|
|
340
284
|
# has no RBS sig.
|
|
341
285
|
def user_def_for(class_name, method_name)
|
|
342
|
-
table = @discovered_def_nodes[class_name.to_s]
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
286
|
+
table = @discovery.discovered_def_nodes[class_name.to_s]
|
|
287
|
+
node = table && table[method_name.to_sym]
|
|
288
|
+
record_cross_file_method(class_name, method_name, node) if Analysis::DependencyRecorder.active?
|
|
289
|
+
node
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# ADR-46 slice 1 — note the cross-file dependency this resolution
|
|
293
|
+
# creates: the file defining `class_name#method_name` (the consumer's
|
|
294
|
+
# analysis reads its body via `infer_user_method_return`), or, when
|
|
295
|
+
# unresolved, a negative edge so a later definition re-checks the
|
|
296
|
+
# consumer. Gated on the recorder being active — no-op on a normal run.
|
|
297
|
+
def record_cross_file_method(class_name, method_name, node)
|
|
298
|
+
if node
|
|
299
|
+
# ADR-46 slice 4 — pass the symbol so the recorder tracks this as a
|
|
300
|
+
# method-call (symbol-granularity) edge rather than a file-level edge.
|
|
301
|
+
Analysis::DependencyRecorder.read_site(
|
|
302
|
+
@discovery.discovered_def_sources.dig(class_name.to_s, method_name.to_sym),
|
|
303
|
+
"#{class_name}##{method_name}"
|
|
304
|
+
)
|
|
305
|
+
else
|
|
306
|
+
Analysis::DependencyRecorder.read_missing(:method, "#{class_name}##{method_name}")
|
|
307
|
+
end
|
|
346
308
|
end
|
|
309
|
+
private :record_cross_file_method
|
|
347
310
|
|
|
348
311
|
# v0.0.3 A — top-level def lookup for implicit-self
|
|
349
312
|
# calls. Returns the `Prism::DefNode` for a top-level
|
|
@@ -353,15 +316,32 @@ module Rigor
|
|
|
353
316
|
# consumers should treat its presence as an opaque
|
|
354
317
|
# implementation detail and go through this accessor.
|
|
355
318
|
def top_level_def_for(method_name)
|
|
356
|
-
table = @discovered_def_nodes[Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY]
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def
|
|
363
|
-
|
|
319
|
+
table = @discovery.discovered_def_nodes[Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY]
|
|
320
|
+
node = table && table[method_name.to_sym]
|
|
321
|
+
record_cross_file_toplevel(method_name, node) if Analysis::DependencyRecorder.active?
|
|
322
|
+
node
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# ADR-46 slice 3 — a top-level (`def helper` outside any class) call has
|
|
326
|
+
# NO class ancestry to walk, so unlike {#user_def_for} a miss here records
|
|
327
|
+
# no positive ancestry edge that would re-check the consumer when the
|
|
328
|
+
# method later appears. Record the cross-file edge explicitly: the file
|
|
329
|
+
# defining the top-level method (symbol-granularity, so a body / removal
|
|
330
|
+
# edit re-checks the caller), or, on a miss, a negative `toplevel:` edge
|
|
331
|
+
# so a later top-level definition re-checks this consumer (the
|
|
332
|
+
# `call.unresolved-toplevel` stale-diagnostic gap).
|
|
333
|
+
def record_cross_file_toplevel(method_name, node)
|
|
334
|
+
key = Inference::ScopeIndexer::TOP_LEVEL_DEF_KEY
|
|
335
|
+
if node
|
|
336
|
+
Analysis::DependencyRecorder.read_site(
|
|
337
|
+
@discovery.discovered_def_sources.dig(key, method_name.to_sym),
|
|
338
|
+
"#{key}##{method_name}"
|
|
339
|
+
)
|
|
340
|
+
else
|
|
341
|
+
Analysis::DependencyRecorder.read_missing(:toplevel, method_name)
|
|
342
|
+
end
|
|
364
343
|
end
|
|
344
|
+
private :record_cross_file_toplevel
|
|
365
345
|
|
|
366
346
|
# Companion to {#user_def_for}: returns the `"path:line"` where
|
|
367
347
|
# the project defines `class_name#method_name` (instance-side),
|
|
@@ -374,16 +354,12 @@ module Rigor
|
|
|
374
354
|
# can point at `pre_eval:` (ADR-17) instead of reading as a bare
|
|
375
355
|
# unresolved call.
|
|
376
356
|
def user_def_site_for(class_name, method_name)
|
|
377
|
-
table = @discovered_def_sources[class_name.to_s]
|
|
357
|
+
table = @discovery.discovered_def_sources[class_name.to_s]
|
|
378
358
|
return nil unless table
|
|
379
359
|
|
|
380
360
|
table[method_name.to_sym]
|
|
381
361
|
end
|
|
382
362
|
|
|
383
|
-
def with_discovered_def_sources(table)
|
|
384
|
-
rebuild(discovered_def_sources: table)
|
|
385
|
-
end
|
|
386
|
-
|
|
387
363
|
# ADR-24 slice 2 — per-class table mapping a fully
|
|
388
364
|
# qualified user-class name to its superclass name AS
|
|
389
365
|
# WRITTEN at the `class Foo < Bar` declaration (`"Bar"`,
|
|
@@ -395,11 +371,25 @@ module Rigor
|
|
|
395
371
|
# The as-written name is resolved to a qualified class at
|
|
396
372
|
# walk time against the call's lexical nesting.
|
|
397
373
|
def superclass_of(class_name)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
374
|
+
record_class_dependency(class_name) if Analysis::DependencyRecorder.active?
|
|
375
|
+
@discovery.discovered_superclasses[class_name.to_s]
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# ADR-48 — per-class table mapping a fully qualified class name to its
|
|
379
|
+
# ordered `Data.define` / `Struct.new` member-name list. Populated by
|
|
380
|
+
# `ScopeIndexer` for both the constant-assigned form
|
|
381
|
+
# (`Point = Data.define(:x, :y)`) and the named-subclass form
|
|
382
|
+
# (`class Point < Data.define(:x, :y)`). Consumed by
|
|
383
|
+
# {Inference::MethodDispatcher::DataFolding} so `Point.new(...)` on a
|
|
384
|
+
# `Singleton[Point]` receiver materialises a precise member instance.
|
|
385
|
+
# Returns nil when the class has no recorded layout.
|
|
386
|
+
def data_member_layout(class_name)
|
|
387
|
+
layout = @discovery.data_member_layouts[class_name.to_s]
|
|
388
|
+
# Record the ancestry dependency only on a hit — DataFolding consults
|
|
389
|
+
# this for every `Singleton[*].new`, and a miss (the common case: an
|
|
390
|
+
# ordinary class) must not manufacture a spurious cross-file edge.
|
|
391
|
+
record_class_dependency(class_name) if layout && Analysis::DependencyRecorder.active?
|
|
392
|
+
layout
|
|
403
393
|
end
|
|
404
394
|
|
|
405
395
|
# ADR-24 slice 2 — per-class/module table mapping a fully
|
|
@@ -412,12 +402,25 @@ module Rigor
|
|
|
412
402
|
# `def`s, not just the superclass chain. As-written names
|
|
413
403
|
# are resolved to qualified classes at walk time.
|
|
414
404
|
def includes_of(class_name)
|
|
415
|
-
|
|
405
|
+
record_class_dependency(class_name) if Analysis::DependencyRecorder.active?
|
|
406
|
+
@discovery.discovered_includes[class_name.to_s] || []
|
|
416
407
|
end
|
|
417
408
|
|
|
418
|
-
|
|
419
|
-
|
|
409
|
+
# Records, for a resolved cross-class ancestry read, every file that
|
|
410
|
+
# declares `class_name` (its declaration / reopening / superclass /
|
|
411
|
+
# include sites). The `discovered_class_sources` table it reads is
|
|
412
|
+
# populated only by the cross-file project pre-pass
|
|
413
|
+
# ({Inference::ScopeIndexer.discovered_def_index_for_paths}) and only
|
|
414
|
+
# when dependency recording is active. No-op when the class is not a
|
|
415
|
+
# project class (core / stdlib / gem names never appear in the source
|
|
416
|
+
# map). Gated by the caller on the recorder being active.
|
|
417
|
+
def record_class_dependency(class_name)
|
|
418
|
+
sites = @discovery.discovered_class_sources[class_name.to_s]
|
|
419
|
+
return if sites.nil?
|
|
420
|
+
|
|
421
|
+
sites.each { |site| Analysis::DependencyRecorder.read_site(site) }
|
|
420
422
|
end
|
|
423
|
+
private :record_class_dependency
|
|
421
424
|
|
|
422
425
|
# v0.1.2 — per-class table mapping `method_name (Symbol) →
|
|
423
426
|
# :public | :private | :protected`. Populated by
|
|
@@ -429,16 +432,12 @@ module Rigor
|
|
|
429
432
|
# so explicit-non-self calls to a private method surface
|
|
430
433
|
# a diagnostic.
|
|
431
434
|
def discovered_method_visibility(class_name, method_name)
|
|
432
|
-
table = @discovered_method_visibilities[class_name.to_s]
|
|
435
|
+
table = @discovery.discovered_method_visibilities[class_name.to_s]
|
|
433
436
|
return nil unless table
|
|
434
437
|
|
|
435
438
|
table[method_name.to_sym]
|
|
436
439
|
end
|
|
437
440
|
|
|
438
|
-
def with_discovered_method_visibilities(table)
|
|
439
|
-
rebuild(discovered_method_visibilities: table)
|
|
440
|
-
end
|
|
441
|
-
|
|
442
441
|
# Closes the "`params[:f] ||= []; params[:f] << x`" precision
|
|
443
442
|
# gap (ROADMAP § Type-language / engine — indexed-collection
|
|
444
443
|
# narrowing through `Hash[k] ||= default`). After
|
|
@@ -577,14 +576,8 @@ module Rigor
|
|
|
577
576
|
|
|
578
577
|
def rebuild(
|
|
579
578
|
locals: @locals, fact_store: @fact_store, self_type: @self_type,
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
discovered_classes: @discovered_classes, in_source_constants: @in_source_constants,
|
|
583
|
-
discovered_methods: @discovered_methods, discovered_def_nodes: @discovered_def_nodes,
|
|
584
|
-
discovered_def_sources: @discovered_def_sources,
|
|
585
|
-
discovered_method_visibilities: @discovered_method_visibilities,
|
|
586
|
-
discovered_superclasses: @discovered_superclasses,
|
|
587
|
-
discovered_includes: @discovered_includes,
|
|
579
|
+
ivars: @ivars, cvars: @cvars, globals: @globals,
|
|
580
|
+
discovery: @discovery,
|
|
588
581
|
indexed_narrowings: @indexed_narrowings,
|
|
589
582
|
method_chain_narrowings: @method_chain_narrowings,
|
|
590
583
|
source_path: @source_path
|
|
@@ -592,18 +585,8 @@ module Rigor
|
|
|
592
585
|
self.class.new(
|
|
593
586
|
environment: environment, locals: locals,
|
|
594
587
|
fact_store: fact_store, self_type: self_type,
|
|
595
|
-
declared_types: declared_types,
|
|
596
588
|
ivars: ivars, cvars: cvars, globals: globals,
|
|
597
|
-
|
|
598
|
-
program_globals: program_globals,
|
|
599
|
-
discovered_classes: discovered_classes,
|
|
600
|
-
in_source_constants: in_source_constants,
|
|
601
|
-
discovered_methods: discovered_methods,
|
|
602
|
-
discovered_def_nodes: discovered_def_nodes,
|
|
603
|
-
discovered_def_sources: discovered_def_sources,
|
|
604
|
-
discovered_method_visibilities: discovered_method_visibilities,
|
|
605
|
-
discovered_superclasses: discovered_superclasses,
|
|
606
|
-
discovered_includes: discovered_includes,
|
|
589
|
+
discovery: discovery,
|
|
607
590
|
indexed_narrowings: indexed_narrowings,
|
|
608
591
|
method_chain_narrowings: method_chain_narrowings,
|
|
609
592
|
source_path: source_path
|
|
@@ -611,8 +594,19 @@ module Rigor
|
|
|
611
594
|
end
|
|
612
595
|
|
|
613
596
|
def join_bindings(left, right)
|
|
614
|
-
|
|
615
|
-
|
|
597
|
+
# Keys present in both, unioned. Iterating `left` and probing
|
|
598
|
+
# `right.key?` yields the same keys in the same order as the prior
|
|
599
|
+
# `(left.keys & right.keys)` while avoiding the two key arrays and
|
|
600
|
+
# the intersection array — this is the control-flow join, run at
|
|
601
|
+
# every branch merge, and was a top allocation site (~75% of
|
|
602
|
+
# `Hash#keys`).
|
|
603
|
+
result = {}
|
|
604
|
+
left.each do |name, ltype|
|
|
605
|
+
next unless right.key?(name)
|
|
606
|
+
|
|
607
|
+
result[name] = Type::Combinator.union(ltype, right[name])
|
|
608
|
+
end
|
|
609
|
+
result.freeze
|
|
616
610
|
end
|
|
617
611
|
|
|
618
612
|
def build_joined_scope(joined_locals, joined_ivars, joined_cvars, joined_globals, other)
|
|
@@ -621,21 +615,10 @@ module Rigor
|
|
|
621
615
|
locals: joined_locals.freeze,
|
|
622
616
|
fact_store: fact_store.join(other.fact_store),
|
|
623
617
|
self_type: self_type == other.self_type ? self_type : nil,
|
|
624
|
-
declared_types: declared_types,
|
|
625
618
|
ivars: joined_ivars,
|
|
626
619
|
cvars: joined_cvars,
|
|
627
620
|
globals: joined_globals,
|
|
628
|
-
|
|
629
|
-
class_cvars: class_cvars,
|
|
630
|
-
program_globals: program_globals,
|
|
631
|
-
discovered_classes: discovered_classes,
|
|
632
|
-
in_source_constants: in_source_constants,
|
|
633
|
-
discovered_methods: discovered_methods,
|
|
634
|
-
discovered_def_nodes: discovered_def_nodes,
|
|
635
|
-
discovered_def_sources: discovered_def_sources,
|
|
636
|
-
discovered_method_visibilities: discovered_method_visibilities,
|
|
637
|
-
discovered_superclasses: discovered_superclasses,
|
|
638
|
-
discovered_includes: discovered_includes,
|
|
621
|
+
discovery: @discovery,
|
|
639
622
|
indexed_narrowings: join_bindings(@indexed_narrowings, other.indexed_narrowings),
|
|
640
623
|
method_chain_narrowings: join_bindings(@method_chain_narrowings, other.method_chain_narrowings),
|
|
641
624
|
source_path: source_path
|
|
@@ -96,7 +96,8 @@ module Rigor
|
|
|
96
96
|
parse_result = Prism.parse(source, filepath: path, version: @configuration.target_ruby)
|
|
97
97
|
return if parse_result.errors.any?
|
|
98
98
|
|
|
99
|
-
base_scope = Scope.empty(environment: environment)
|
|
99
|
+
base_scope = Scope.empty(environment: environment)
|
|
100
|
+
base_scope = base_scope.with_discovery(base_scope.discovery.with(discovered_classes: discovered_classes))
|
|
100
101
|
scope_index = Inference::ScopeIndexer.index(parse_result.value, default_scope: base_scope)
|
|
101
102
|
bindings = collect_rspec_bindings(parse_result.value, scope_index)
|
|
102
103
|
|
|
@@ -104,11 +105,10 @@ module Rigor
|
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
# Pre-walks `@source_paths` to collect every qualified
|
|
107
|
-
# class / module declaration. The result
|
|
108
|
-
# `
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# `Foo` yet.
|
|
108
|
+
# class / module declaration. The result seeds the
|
|
109
|
+
# `discovered_classes` table on each observe-tree scope so
|
|
110
|
+
# `Foo.new` and `Foo` resolve to the right singleton carrier
|
|
111
|
+
# even when no RBS sig describes `Foo` yet.
|
|
112
112
|
def preindex_source_classes
|
|
113
113
|
accumulator = {}
|
|
114
114
|
resolve_paths(@source_paths).each { |path| harvest_classes_from(path, accumulator) }
|
|
@@ -100,6 +100,20 @@ module Rigor
|
|
|
100
100
|
args.filter_map { |arg| symbol_or_string(arg) }
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
# Whether a node is a literal `Prism::SymbolNode` that names `name`.
|
|
104
|
+
# The key-comparison counterpart to {.symbol_name} — for callers
|
|
105
|
+
# that need a predicate rather than an extraction (hash-key matching
|
|
106
|
+
# in keyword or assoc argument positions, e.g.
|
|
107
|
+
# `el.is_a?(AssocNode) && symbol_named?(el.key, "required")`).
|
|
108
|
+
# Uses `#unescaped` (not `#value`) for round-trip consistency.
|
|
109
|
+
#
|
|
110
|
+
# @param node [Prism::Node, nil]
|
|
111
|
+
# @param name [String]
|
|
112
|
+
# @return [Boolean]
|
|
113
|
+
def symbol_named?(node, name)
|
|
114
|
+
node.is_a?(Prism::SymbolNode) && node.unescaped == name
|
|
115
|
+
end
|
|
116
|
+
|
|
103
117
|
# The literal Symbol/String at positional `index`, or `nil` when the
|
|
104
118
|
# call has no argument list, the index is out of range, or the
|
|
105
119
|
# argument there is not a literal Symbol/String.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Type
|
|
5
|
+
# Routes `accepts` through the engine's acceptance dispatcher.
|
|
6
|
+
#
|
|
7
|
+
# Every carrier's acceptance check is the same fixed forwarding on
|
|
8
|
+
# `self` — `Inference::Acceptance.accepts(self, other, mode: mode)` —
|
|
9
|
+
# so it lived as an identical copy in fourteen carriers. The one
|
|
10
|
+
# exception is `Type::App`, which forwards on its reduced `bound`
|
|
11
|
+
# type rather than `self`; it keeps its own `accepts` and does not
|
|
12
|
+
# include this.
|
|
13
|
+
module AcceptanceRouter
|
|
14
|
+
def accepts(other, mode: :gradual)
|
|
15
|
+
Inference::Acceptance.accepts(self, other, mode: mode)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|