rigortype 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/data/builtins/ruby_core/io.yml +1 -0
- data/data/builtins/ruby_core/proc.yml +4 -0
- data/data/builtins/ruby_core/random.yml +4 -0
- data/data/builtins/ruby_core/struct.yml +3 -0
- data/data/core_overlay/env.rbs +34 -0
- data/data/core_overlay/gc.rbs +50 -0
- data/data/core_overlay/hash.rbs +45 -0
- data/data/core_overlay/integer.rbs +31 -0
- data/data/core_overlay/io_buffer.rbs +27 -0
- data/data/core_overlay/kernel.rbs +40 -0
- data/data/core_overlay/match_data.rbs +28 -0
- data/data/core_overlay/module.rbs +51 -0
- data/data/core_overlay/object_space.rbs +29 -0
- data/data/core_overlay/proc.rbs +45 -0
- data/data/core_overlay/range.rbs +40 -0
- data/data/core_overlay/source_range.rbs +78 -0
- data/data/core_overlay/string.rbs +86 -0
- data/data/vendored_gem_sigs/prime/integer-extension.rbs +41 -0
- data/data/vendored_gem_sigs/prime/prime.rbs +372 -0
- data/docs/handbook/01-getting-started.md +3 -3
- data/docs/handbook/06-classes.md +3 -1
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/11-sig-gen.md +6 -0
- data/docs/handbook/appendix-liskov.md +5 -0
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +2 -2
- data/docs/manual/03-configuration.md +6 -1
- data/docs/manual/04-diagnostics.md +2 -1
- data/docs/manual/06-baseline.md +2 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/18-removing-dead-code.md +32 -2
- data/docs/manual/plugins/README.md +6 -1
- data/docs/manual/plugins/rigor-activerecord.md +21 -0
- data/docs/manual/plugins/rigor-alba.md +60 -0
- data/docs/manual/plugins/rigor-minitest.md +22 -4
- data/docs/manual/plugins/rigor-typelizer.md +71 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +27 -3
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +21 -92
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +275 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +4 -0
- data/lib/rigor/analysis/check_rules/source_arity.rb +184 -61
- data/lib/rigor/analysis/check_rules.rb +407 -144
- data/lib/rigor/analysis/dependency_recorder.rb +215 -18
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +8 -1
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +21 -0
- data/lib/rigor/analysis/dependency_source_inference/index.rb +36 -11
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +196 -27
- data/lib/rigor/analysis/erb_template_detector.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +29 -9
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +291 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +173 -0
- data/lib/rigor/analysis/incremental_session.rb +287 -50
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/reachability/graph.rb +123 -23
- data/lib/rigor/analysis/reachability/scan.rb +290 -36
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -1
- data/lib/rigor/analysis/rule_catalog.rb +43 -0
- data/lib/rigor/analysis/run_cache_key.rb +43 -9
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +88 -6
- data/lib/rigor/analysis/runner/pool_coordinator.rb +52 -13
- data/lib/rigor/analysis/runner/project_pre_passes.rb +21 -13
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +371 -49
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_paths.rb +6 -4
- data/lib/rigor/analysis/template_units.rb +15 -5
- data/lib/rigor/analysis/worker_session.rb +11 -7
- data/lib/rigor/cache/descriptor.rb +40 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +92 -8
- data/lib/rigor/cache/store.rb +63 -3
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/cli/annotate_command.rb +5 -0
- data/lib/rigor/cli/baseline_command.rb +7 -18
- data/lib/rigor/cli/check_command.rb +85 -14
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/trace_command.rb +5 -0
- data/lib/rigor/cli/type_of_command.rb +5 -3
- data/lib/rigor/cli/unused_command.rb +16 -10
- data/lib/rigor/cli.rb +11 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +11 -0
- data/lib/rigor/configuration.rb +52 -7
- data/lib/rigor/effects/plugin_facts.rb +6 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/gated_signature_guard.rb +43 -0
- data/lib/rigor/environment/gated_signature_plan.rb +339 -0
- data/lib/rigor/environment/lockfile_resolver.rb +10 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_coverage_report.rb +1 -1
- data/lib/rigor/environment/rbs_loader.rb +284 -15
- data/lib/rigor/environment/required_features.rb +206 -0
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +34 -73
- data/lib/rigor/inference/acceptance.rb +28 -3
- data/lib/rigor/inference/block_parameter_binder.rb +29 -13
- data/lib/rigor/inference/builtins/cruby_definers.rb +61 -0
- data/lib/rigor/inference/builtins/method_catalog.rb +3 -0
- data/lib/rigor/inference/captured_locals.rb +7 -1
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +26 -3
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/expression_typer.rb +351 -61
- data/lib/rigor/inference/external_ancestor_resolution.rb +13 -16
- data/lib/rigor/inference/global_write_census.rb +7 -4
- data/lib/rigor/inference/guard_rebinding.rb +54 -13
- data/lib/rigor/inference/in_effect_refinements/proc_literals.rb +83 -0
- data/lib/rigor/inference/in_effect_refinements/refine_defs.rb +54 -0
- data/lib/rigor/inference/in_effect_refinements/scope_reads.rb +282 -0
- data/lib/rigor/inference/in_effect_refinements/walk.rb +276 -0
- data/lib/rigor/inference/in_effect_refinements.rb +256 -0
- data/lib/rigor/inference/key_presence_guard/scan.rb +281 -0
- data/lib/rigor/inference/key_presence_guard.rb +303 -0
- data/lib/rigor/inference/macro_block_self_type.rb +73 -14
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/arithmetic_sequence_elements.rb +73 -0
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +68 -0
- data/lib/rigor/inference/method_dispatcher/integer_step_block_params.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -0
- data/lib/rigor/inference/method_dispatcher/keyword_arguments.rb +267 -0
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -1
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +122 -105
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +305 -54
- data/lib/rigor/inference/method_dispatcher/splat_arity.rb +71 -0
- data/lib/rigor/inference/method_dispatcher.rb +44 -11
- data/lib/rigor/inference/module_function_state.rb +214 -0
- data/lib/rigor/inference/multi_target_binder.rb +21 -16
- data/lib/rigor/inference/narrowing.rb +166 -22
- data/lib/rigor/inference/object_mixins.rb +180 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +10 -9
- data/lib/rigor/inference/project_patched_methods.rb +10 -5
- data/lib/rigor/inference/project_patched_scanner.rb +71 -3
- data/lib/rigor/inference/refine_census.rb +239 -0
- data/lib/rigor/inference/refine_self.rb +317 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +403 -0
- data/lib/rigor/inference/refined_dispatch.rb +272 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +143 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +2129 -354
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +149 -32
- data/lib/rigor/inference/string_mutation.rb +9 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +2 -1
- data/lib/rigor/inference/version_guard.rb +19 -10
- data/lib/rigor/language_server/project_context.rb +31 -0
- data/lib/rigor/language_server/server.rb +18 -0
- data/lib/rigor/plugin/base.rb +28 -8
- data/lib/rigor/plugin/inflector.rb +1 -1
- data/lib/rigor/plugin/io_boundary.rb +124 -7
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +51 -8
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/project_environment.rb +29 -6
- data/lib/rigor/protection/discovery_seed.rb +18 -9
- data/lib/rigor/protection/mutation_cache.rb +4 -4
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/reflection/constant_ancestors.rb +211 -46
- data/lib/rigor/reflection/constant_path.rb +33 -7
- data/lib/rigor/reflection.rb +41 -10
- data/lib/rigor/scope/discovery_index.rb +258 -3
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +1032 -0
- data/lib/rigor/scope.rb +480 -328
- data/lib/rigor/sig_gen/alias_index.rb +1 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +2 -2
- data/lib/rigor/sig_gen/generator.rb +77 -21
- data/lib/rigor/sig_gen/method_candidate.rb +2 -2
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +12 -0
- data/lib/rigor/source/alias_names.rb +52 -0
- data/lib/rigor/source/literals.rb +3 -2
- data/lib/rigor/source/symbol_name.rb +20 -0
- data/lib/rigor/type/combinator.rb +59 -3
- data/lib/rigor/type/float_range.rb +4 -1
- data/lib/rigor/type/nominal.rb +6 -1
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-ac-library-rb/lib/rigor/plugin/ac_library_rb.rb +28 -0
- data/plugins/rigor-ac-library-rb/lib/rigor-ac-library-rb.rb +3 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/convolution.rbs +7 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/data_structures.rbs +160 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/graph.rbs +94 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/math.rbs +42 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/modint.rbs +71 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/object.rbs +37 -0
- data/plugins/rigor-ac-library-rb/sig/ac_library_rb/segtree.rbs +83 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +2 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +100 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +94 -10
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +43 -2
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +3 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +149 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +74 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +104 -0
- data/plugins/rigor-alba/lib/rigor/plugin/alba.rb +85 -0
- data/plugins/rigor-alba/lib/rigor-alba.rb +3 -0
- data/plugins/rigor-alba/sig/alba.rbs +4 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +2 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +29 -12
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +13 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +130 -21
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +6 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +4 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +31 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +102 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +87 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +76 -0
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +73 -0
- data/plugins/rigor-typelizer/lib/rigor-typelizer.rb +3 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +1 -1
- data/sig/rigor/environment.rbs +2 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/scope.rbs +69 -4
- data/sig/rigor/source.rbs +4 -0
- data/sig/rigor.rbs +3 -1
- data/skills/rigor-project-init/SKILL.md +37 -11
- data/skills/rigor-project-init/references/02-configure.md +7 -2
- metadata +90 -1
|
@@ -30,10 +30,11 @@ module Rigor
|
|
|
30
30
|
#
|
|
31
31
|
# There is deliberately NO memo here. The dispatch consumer this module is being extracted FOR does
|
|
32
32
|
# not exist yet, and a memo keyed on the tables this slice happens to name would be keyed on less
|
|
33
|
-
# than the walk reads —
|
|
34
|
-
# `discovered_methods`
|
|
35
|
-
# `discovered_header_nestings
|
|
36
|
-
# that
|
|
33
|
+
# than the walk reads — the ancestor chain resolves names through `known_user_class?`
|
|
34
|
+
# (`discovered_def_nodes`, `discovered_methods`) and `ancestor_name_candidates`
|
|
35
|
+
# (`discovered_header_nestings`). The chain itself is memoised per discovery index
|
|
36
|
+
# ({Scope::ResolutionChain}); the slice that brings the hot path brings this module's memo, keyed on
|
|
37
|
+
# what that consumer actually reads.
|
|
37
38
|
module ExternalAncestorResolution
|
|
38
39
|
# The owner a name must PRECEDE for its declaration to win an MRO. See {declared_before_object?}.
|
|
39
40
|
OBJECT_OWNER = "Object"
|
|
@@ -67,13 +68,12 @@ module Rigor
|
|
|
67
68
|
# `include Enumerable` so that slice 2's measurement stays its own, and the narrowing belongs to
|
|
68
69
|
# the CALLER rather than to this module: the implicit-self veto must keep walking both edges,
|
|
69
70
|
# because Ruby reaches an included module's methods too.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
record_dependencies: true, mixins: true)
|
|
71
|
+
def resolve(class_name, method_name, kind = :instance, scope:, environment: nil, record_dependencies: true,
|
|
72
|
+
mixins: true)
|
|
73
73
|
return nil if class_name.nil? || scope.nil?
|
|
74
74
|
return nil unless kind == :instance
|
|
75
75
|
|
|
76
|
-
compute(class_name, method_name, scope, environment,
|
|
76
|
+
compute(class_name, method_name, scope, environment, record_dependencies, mixins)
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# The RBS method definition for `class_name`, or nil for a class the environment does not know, a
|
|
@@ -133,7 +133,7 @@ module Rigor
|
|
|
133
133
|
[]
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
def compute(class_name, method_name, scope, environment,
|
|
136
|
+
def compute(class_name, method_name, scope, environment, record_dependencies, mixins)
|
|
137
137
|
kind = :instance
|
|
138
138
|
own = method_definition(class_name, method_name, kind, scope: scope, environment: environment)
|
|
139
139
|
if own
|
|
@@ -144,7 +144,7 @@ module Rigor
|
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
groups = ancestor_candidate_groups(scope, class_name,
|
|
147
|
+
groups = ancestor_candidate_groups(scope, class_name, record_dependencies, mixins)
|
|
148
148
|
farther_ancestors = {}
|
|
149
149
|
groups.each_with_index do |candidates, index|
|
|
150
150
|
answer = first_known_candidate_answer(candidates, method_name, kind, scope, environment)
|
|
@@ -244,14 +244,11 @@ module Rigor
|
|
|
244
244
|
# The walk, with its ADR-46 reads attached or detached. `withhold` returns `[result, read_set]`
|
|
245
245
|
# and the read set is dropped: a caller that suppresses is saying these reads are not a dependency
|
|
246
246
|
# of its answer, not that they should be replayed somewhere else.
|
|
247
|
-
def ancestor_candidate_groups(scope, class_name,
|
|
248
|
-
|
|
249
|
-
if record_dependencies
|
|
250
|
-
return scope.external_ancestor_name_candidates(class_name, name_memo: memo, mixins: mixins)
|
|
251
|
-
end
|
|
247
|
+
def ancestor_candidate_groups(scope, class_name, record_dependencies, mixins)
|
|
248
|
+
return scope.external_ancestor_name_candidates(class_name, mixins: mixins) if record_dependencies
|
|
252
249
|
|
|
253
250
|
Analysis::DependencyRecorder.withhold do
|
|
254
|
-
scope.external_ancestor_name_candidates(class_name,
|
|
251
|
+
scope.external_ancestor_name_candidates(class_name, mixins: mixins)
|
|
255
252
|
end.first
|
|
256
253
|
end
|
|
257
254
|
private_class_method :ancestor_candidate_groups
|
|
@@ -82,7 +82,7 @@ module Rigor
|
|
|
82
82
|
|
|
83
83
|
# A standalone walk, for a file no existing descent visits (a `pre_eval:` entry).
|
|
84
84
|
def scan(root)
|
|
85
|
-
collector = Collector.new
|
|
85
|
+
collector = Collector.new(InEffectRefinements.new(root))
|
|
86
86
|
walk(root, collector, true)
|
|
87
87
|
collector.census.freeze
|
|
88
88
|
end
|
|
@@ -98,13 +98,16 @@ module Rigor
|
|
|
98
98
|
|
|
99
99
|
# Accumulates one file's census, fed each node of a pre-order descent. `top_level` says whether the node sits
|
|
100
100
|
# outside every `class` / `module` body; it is the host walk's to track. `refine` blocks are remembered by
|
|
101
|
-
# offset as they are met, so a node inside one is known without the host walk tracking it.
|
|
101
|
+
# offset as they are met, so a node inside one is known without the host walk tracking it. `in_effect` is the
|
|
102
|
+
# file's {InEffectRefinements}: a `refine` it places where `self` is a class is the class's own method, so its
|
|
103
|
+
# block is not a refinement (issue #1689).
|
|
102
104
|
class Collector
|
|
103
105
|
attr_reader :census
|
|
104
106
|
|
|
105
|
-
def initialize
|
|
107
|
+
def initialize(in_effect)
|
|
106
108
|
@census = Set.new
|
|
107
109
|
@refine_ranges = []
|
|
110
|
+
@in_effect = in_effect
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
# A node the collector cannot read must not fail the analysis of its file, nor of every file the pre-pass
|
|
@@ -223,7 +226,7 @@ module Rigor
|
|
|
223
226
|
def self_receiver?(node) = node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
|
|
224
227
|
|
|
225
228
|
def remember_refine(node)
|
|
226
|
-
return unless
|
|
229
|
+
return unless ScopeIndexer.refine_call?(node) && !@in_effect.class_body_refine?(node)
|
|
227
230
|
|
|
228
231
|
location = node.block.location
|
|
229
232
|
@refine_ranges << (location.start_offset...location.end_offset)
|
|
@@ -33,12 +33,19 @@ module Rigor
|
|
|
33
33
|
# The accepted gap is implicit conversion: a core method that calls back into a project method the program
|
|
34
34
|
# does not spell (`puts obj` runs `obj.to_s`, `hash[obj]` runs `obj.hash`, `a.sort` runs `<=>`) is read as the
|
|
35
35
|
# core method alone.
|
|
36
|
+
#
|
|
37
|
+
# Issue #1446 — a class guard's narrowing of an instance variable is restored at the same code, since code that
|
|
38
|
+
# may rebind a global may reach `self` and rebind the variable too, and at two more: `instance_variable_set` and
|
|
39
|
+
# `remove_instance_variable` on any receiver, and a write to the narrowed variable in code the scan reads (a
|
|
40
|
+
# block that may run again, a loop's next pass).
|
|
36
41
|
module GuardRebinding
|
|
37
42
|
# Calls that run code chosen by name or by a String.
|
|
38
43
|
CODE_RUNNING_NAMES = Set[
|
|
39
44
|
:send, :__send__, :public_send, :eval, :require, :require_relative, :load,
|
|
40
45
|
# These rebind a constant on any receiver (`Object.const_set(:SEP, nil)`).
|
|
41
|
-
:const_set, :remove_const
|
|
46
|
+
:const_set, :remove_const,
|
|
47
|
+
# These rebind an instance variable of a receiver that may be `self` (#1446).
|
|
48
|
+
:instance_variable_set, :remove_instance_variable
|
|
42
49
|
].freeze
|
|
43
50
|
# These run their literal block, which the scan reads as any block, or a String of code, which it cannot read.
|
|
44
51
|
BLOCK_OR_CODE_NAMES = Set[
|
|
@@ -109,10 +116,11 @@ module Rigor
|
|
|
109
116
|
(call_node.block.is_a?(Prism::BlockArgumentNode) && may_rebind?(call_node.block, scope))
|
|
110
117
|
end
|
|
111
118
|
|
|
112
|
-
# True when running `node` may rebind one: it writes a global
|
|
113
|
-
# call, spelled or implicit ({.implicit_call_may_rebind?}), whose
|
|
114
|
-
# lambda literal run nothing where they are written. Each node is
|
|
115
|
-
# reached as one of its children, so a nested block chain costs its
|
|
119
|
+
# True when running `node` may rebind one: it writes a global, a constant or an instance variable a class guard
|
|
120
|
+
# narrowed, yields, calls `super`, or holds a call, spelled or implicit ({.implicit_call_may_rebind?}), whose
|
|
121
|
+
# method may run foreign code. A `def` and a lambda literal run nothing where they are written. Each node is
|
|
122
|
+
# visited once: a call's literal block is reached as one of its children, so a nested block chain costs its
|
|
123
|
+
# size, not its depth's power.
|
|
116
124
|
#
|
|
117
125
|
# A local the scanned code writes has no binding in `scope` yet, so the scan reads it as the code writes it
|
|
118
126
|
# ({ScanScope.with_scanned_locals}): `copy = $sep; copy.length` is a `String` call. A literal block's parameters
|
|
@@ -126,6 +134,7 @@ module Rigor
|
|
|
126
134
|
def scan(node, scope)
|
|
127
135
|
return false unless node.is_a?(Prism::Node)
|
|
128
136
|
return true if REBINDING_NODES.include?(node.class)
|
|
137
|
+
return true if guarded_ivar_write?(node, scope)
|
|
129
138
|
return false if node.is_a?(Prism::DefNode) || node.is_a?(Prism::LambdaNode)
|
|
130
139
|
return scan_call(node, scope) if node.is_a?(Prism::CallNode)
|
|
131
140
|
return true if IMPLICIT_CALL_NODES.include?(node.class) && implicit_call_may_rebind?(node, scope)
|
|
@@ -135,6 +144,18 @@ module Rigor
|
|
|
135
144
|
found
|
|
136
145
|
end
|
|
137
146
|
|
|
147
|
+
# Issue #1446 — true when `node` writes an instance variable a class guard narrowed
|
|
148
|
+
# ({Scope#guard_narrowed_ivar?}). A write to any other instance variable rebinds nothing a guard recorded.
|
|
149
|
+
def guarded_ivar_write?(node, scope)
|
|
150
|
+
case node
|
|
151
|
+
when Prism::InstanceVariableWriteNode, Prism::InstanceVariableOrWriteNode, Prism::InstanceVariableAndWriteNode,
|
|
152
|
+
Prism::InstanceVariableOperatorWriteNode, Prism::InstanceVariableTargetNode
|
|
153
|
+
scope.guard_narrowed_ivar?(node.name)
|
|
154
|
+
else
|
|
155
|
+
false
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
138
159
|
def scan_call(node, scope)
|
|
139
160
|
return true if call_runs_foreign_code?(node, scope)
|
|
140
161
|
return true if scan(node.receiver, scope) || scan(node.arguments, scope)
|
|
@@ -156,7 +177,9 @@ module Rigor
|
|
|
156
177
|
def implicit_call_may_rebind?(node, scope)
|
|
157
178
|
kind = VARIABLE_OPERATOR_WRITES[node.class]
|
|
158
179
|
return type_method_foreign?(variable_type(kind, node.name, scope), node.binary_operator, scope) if kind
|
|
159
|
-
|
|
180
|
+
if node.is_a?(Prism::ForNode)
|
|
181
|
+
return type_method_foreign?(ScanScope.dispatch_type(node.collection, scope), :each, scope)
|
|
182
|
+
end
|
|
160
183
|
|
|
161
184
|
compound_write_foreign?(node, scope)
|
|
162
185
|
rescue StandardError
|
|
@@ -174,7 +197,7 @@ module Rigor
|
|
|
174
197
|
end
|
|
175
198
|
|
|
176
199
|
def compound_receiver_type(node, scope)
|
|
177
|
-
return
|
|
200
|
+
return ScanScope.dispatch_type(node.receiver, scope) if node.receiver
|
|
178
201
|
|
|
179
202
|
scope.self_type || Type::Combinator.nominal_of("Object")
|
|
180
203
|
end
|
|
@@ -264,7 +287,7 @@ module Rigor
|
|
|
264
287
|
return ProjectMethodOwnership.targets(self_type)
|
|
265
288
|
end
|
|
266
289
|
|
|
267
|
-
ProjectMethodOwnership.targets(
|
|
290
|
+
ProjectMethodOwnership.targets(ScanScope.dispatch_type(receiver, scope))
|
|
268
291
|
end
|
|
269
292
|
|
|
270
293
|
def foreign_target?(class_name, method_name, kind, scope)
|
|
@@ -300,8 +323,8 @@ module Rigor
|
|
|
300
323
|
owner = definition.respond_to?(:defined_in) ? definition.defined_in : nil
|
|
301
324
|
owner&.to_s&.delete_prefix("::")
|
|
302
325
|
end
|
|
303
|
-
private_class_method :scan, :scan_call, :receiver_targets, :foreign_target?,
|
|
304
|
-
:method_owner, :deferred_block_call?,
|
|
326
|
+
private_class_method :scan, :guarded_ivar_write?, :scan_call, :receiver_targets, :foreign_target?,
|
|
327
|
+
:universal_delegate_foreign?, :method_owner, :deferred_block_call?,
|
|
305
328
|
:compound_write_foreign?,
|
|
306
329
|
:compound_receiver_type, :compound_accessors, :compound_read_type, :variable_type,
|
|
307
330
|
:type_method_foreign?
|
|
@@ -376,18 +399,36 @@ module Rigor
|
|
|
376
399
|
bindings.reduce(scope) { |acc, (name, type)| acc.with_local(name, type) }
|
|
377
400
|
end
|
|
378
401
|
|
|
379
|
-
# What the method `call_node` calls yields its block, by position, or `[]` when that cannot be read.
|
|
402
|
+
# What the method `call_node` calls yields its block, by position, or `[]` when that cannot be read. A receiver
|
|
403
|
+
# a class guard left `bot` yields what the guarded class does ({.dispatch_type}, #1446).
|
|
380
404
|
def yielded_types(call_node, scope)
|
|
381
|
-
receiver = call_node.receiver ?
|
|
405
|
+
receiver = call_node.receiver ? dispatch_type(call_node.receiver, scope) : scope.self_type
|
|
382
406
|
arguments = call_node.arguments&.arguments || []
|
|
383
407
|
MethodDispatcher.expected_block_param_types(
|
|
384
408
|
receiver_type: receiver, method_name: call_node.name, environment: scope.environment, scope: scope,
|
|
385
|
-
arg_types: arguments.map { |argument| scope.type_of(argument) }
|
|
409
|
+
arg_types: arguments.map { |argument| scope.type_of(argument) }, call_node: call_node
|
|
386
410
|
)
|
|
387
411
|
rescue StandardError
|
|
388
412
|
[]
|
|
389
413
|
end
|
|
390
414
|
|
|
415
|
+
# Issue #1446 — the type a call on the receiver `node` dispatches on: its type in `scope`, or, where a class
|
|
416
|
+
# guard disjoint from the receiver's binding left it `bot` (`return unless @io.is_a?(StringIO)` on an `IO`),
|
|
417
|
+
# the classes the guard named ({Narrowing.bot_guard_classes_of}), since the value that passes the guard is one
|
|
418
|
+
# of them. So `@io.rewind` reads as `StringIO#rewind` and keeps the narrowing, while `@cb.call` past
|
|
419
|
+
# `@cb.is_a?(Proc)` reads as a call on a code object, and a method a project subclass or reopening defines on
|
|
420
|
+
# the class still counts. A `bot` no class guard produced stays `bot`, which no target resolves, so a call on
|
|
421
|
+
# it counts as an unresolved callee.
|
|
422
|
+
def dispatch_type(node, scope)
|
|
423
|
+
type = scope.type_of(node)
|
|
424
|
+
return type unless type.is_a?(Type::Bot)
|
|
425
|
+
|
|
426
|
+
classes = Narrowing.bot_guard_classes_of(node, scope)
|
|
427
|
+
return type if classes.nil?
|
|
428
|
+
|
|
429
|
+
Type::Combinator.union(*classes.map { |name| Type::Combinator.nominal_of(name) })
|
|
430
|
+
end
|
|
431
|
+
|
|
391
432
|
# The names a block's parameter list declares: every kind of parameter, a destructured one's parts included,
|
|
392
433
|
# and a `;`-block-local. A default value is not read.
|
|
393
434
|
def collect_parameter_names(node, names)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
class InEffectRefinements
|
|
8
|
+
# Issue #1666 — the Proc literals whose body a `.refined` receiver call applies to, recognised from syntax.
|
|
9
|
+
module ProcLiterals
|
|
10
|
+
# The `Kernel` calls whose literal block is the Proc they return.
|
|
11
|
+
BARE_PROC_CALLS = %i[proc lambda].freeze
|
|
12
|
+
private_constant :BARE_PROC_CALLS
|
|
13
|
+
|
|
14
|
+
# Copies that keep a refined Proc's refinements (`test_refined_preserved_by_dup`): a chain reads through them.
|
|
15
|
+
COPY_CALLS = %i[dup clone].freeze
|
|
16
|
+
private_constant :COPY_CALLS
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# `[literal, refined calls]` for the outermost `.refined` call `node`: the Proc literal the chain starts from
|
|
21
|
+
# ({.refinable}, or nil), and every `.refined` call of the chain, innermost (first called) first. The chain
|
|
22
|
+
# reads through parentheses and through `dup` / `clone`.
|
|
23
|
+
def refined_chain(node)
|
|
24
|
+
calls = [node]
|
|
25
|
+
receiver = unwrap(node.receiver)
|
|
26
|
+
loop do
|
|
27
|
+
if refined_call?(receiver)
|
|
28
|
+
calls << receiver
|
|
29
|
+
elsif !copy_call?(receiver)
|
|
30
|
+
break
|
|
31
|
+
end
|
|
32
|
+
receiver = unwrap(receiver.receiver)
|
|
33
|
+
end
|
|
34
|
+
[refinable(receiver), calls.reverse]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def refined_call?(node) = node.is_a?(Prism::CallNode) && node.name == :refined
|
|
38
|
+
|
|
39
|
+
def copy_call?(node)
|
|
40
|
+
node.is_a?(Prism::CallNode) && COPY_CALLS.include?(node.name) && node.arguments.nil? && node.block.nil? &&
|
|
41
|
+
!node.receiver.nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# `( expr )` with one statement reads as `expr`.
|
|
45
|
+
def unwrap(node)
|
|
46
|
+
while node.is_a?(Prism::ParenthesesNode) && node.body.is_a?(Prism::StatementsNode) &&
|
|
47
|
+
node.body.body.size == 1
|
|
48
|
+
node = node.body.body.first
|
|
49
|
+
end
|
|
50
|
+
node
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# The node whose span is the body `node.refined(…)` applies to: `node` itself for a lambda literal, or the
|
|
54
|
+
# literal block of a `proc` / `lambda` call on no receiver or on `Kernel`, or of `Proc.new`; nil for anything
|
|
55
|
+
# else. A Proc held in a variable, or one passed as `&blk`, is not one: which block it holds is not syntax.
|
|
56
|
+
def refinable(node)
|
|
57
|
+
case node
|
|
58
|
+
when Prism::LambdaNode then node
|
|
59
|
+
when Prism::CallNode
|
|
60
|
+
block = node.block
|
|
61
|
+
return nil unless block.is_a?(Prism::BlockNode)
|
|
62
|
+
|
|
63
|
+
block if bare_proc_call?(node) || proc_new_call?(node)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def bare_proc_call?(node)
|
|
68
|
+
BARE_PROC_CALLS.include?(node.name) && (node.receiver.nil? || top_constant?(node.receiver, :Kernel))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def proc_new_call?(node)
|
|
72
|
+
node.name == :new && top_constant?(node.receiver, :Proc)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# `Name` or `::Name`.
|
|
76
|
+
def top_constant?(node, name)
|
|
77
|
+
(node.is_a?(Prism::ConstantReadNode) || (node.is_a?(Prism::ConstantPathNode) && node.parent.nil?)) &&
|
|
78
|
+
node.name == name
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
class InEffectRefinements
|
|
6
|
+
# Issue #1664 — the `def`s one file's refine bodies define, keyed by refining module, refined class (each name
|
|
7
|
+
# the `refine` argument's spelling can denote) and method, with the `Module.nesting` each is written in. The
|
|
8
|
+
# last `def` of a name wins, as Ruby's method table keeps it.
|
|
9
|
+
class RefineDefs
|
|
10
|
+
# ADR-121 WD7 — what {#lookup} answers for a name whose last definer has no body (`define_method`, `attr_*`,
|
|
11
|
+
# `undef`): the search for a body ends there, so the typed arm answers `Dynamic[top]` rather than another
|
|
12
|
+
# file's `def`.
|
|
13
|
+
BODILESS = :bodiless
|
|
14
|
+
# Issue #1749 — what {#lookup} answers for a name whose refinement the walk cannot attribute: one from a
|
|
15
|
+
# `refine` that runs inside a method (it exists once the method has run, replacing what a refine body wrote for
|
|
16
|
+
# the name, or adding nothing if it never runs, and `using` sees it only if it runs first), or from an eval on
|
|
17
|
+
# a constant that several names denote (it runs on one of them, which another file decides, and replaces or is
|
|
18
|
+
# replaced by a `def` of the name in a refine body of the module, whichever runs first). A `def` of the name
|
|
19
|
+
# in a module-body refine, in this file or another, cannot say which one answers, so the name is never typed
|
|
20
|
+
# from any body: the lookup ends there across every file that declares the module, and unlike {BODILESS} a
|
|
21
|
+
# later record does not replace it.
|
|
22
|
+
UNDECIDED = :undecided
|
|
23
|
+
|
|
24
|
+
attr_reader :nestings
|
|
25
|
+
|
|
26
|
+
def initialize
|
|
27
|
+
@nodes = {}
|
|
28
|
+
@nestings = {}.compare_by_identity
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# `owners` are the refining modules (several where an eval on a constant may run on any of its names), each
|
|
32
|
+
# filed under every class of `targets`. `name` is the method `def_node` answers for: its own name, or an
|
|
33
|
+
# alias's (ADR-121 WD7). A nil `def_node` is a later definer with no body (`define_method`, `attr_*`, `undef`),
|
|
34
|
+
# which replaces the earlier `def`. A name whose refinement the walk cannot attribute keeps no body and is
|
|
35
|
+
# {UNDECIDED}: several owners (Ruby runs the body on one of them only), or `created`, true for a `refine` that
|
|
36
|
+
# runs inside a method.
|
|
37
|
+
def record(owners, targets, name, def_node, nesting, created: false)
|
|
38
|
+
undecided = created || owners.size > 1
|
|
39
|
+
def_node = nil if undecided
|
|
40
|
+
owners.each do |owner|
|
|
41
|
+
by_class = (@nodes[owner] ||= {})
|
|
42
|
+
targets.each do |class_name|
|
|
43
|
+
by_name = (by_class[class_name] ||= {})
|
|
44
|
+
by_name[name] = undecided ? UNDECIDED : (def_node || BODILESS) unless by_name[name] == UNDECIDED
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
@nestings[def_node] = nesting if def_node
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def lookup(module_name, class_name, method_name) = @nodes.dig(module_name, class_name, method_name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|