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
|
@@ -18,6 +18,7 @@ require_relative "method_dispatcher/data_folding"
|
|
|
18
18
|
require_relative "method_dispatcher/struct_folding"
|
|
19
19
|
require_relative "method_dispatcher/rbs_dispatch"
|
|
20
20
|
require_relative "method_dispatcher/iterator_dispatch"
|
|
21
|
+
require_relative "method_dispatcher/arithmetic_sequence_elements"
|
|
21
22
|
require_relative "method_dispatcher/reduce_folding"
|
|
22
23
|
require_relative "method_dispatcher/block_folding"
|
|
23
24
|
require_relative "method_dispatcher/array_to_h_folding"
|
|
@@ -101,7 +102,7 @@ module Rigor
|
|
|
101
102
|
# the tier commits the buffer only if it goes on to answer. See {#try_composite_receiver}.
|
|
102
103
|
def resolve(receiver_type:, method_name:, arg_types:, # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/ParameterLists
|
|
103
104
|
block_type: nil, environment: nil,
|
|
104
|
-
call_node: nil, scope: nil, plugin_typed_sink: nil)
|
|
105
|
+
call_node: nil, scope: nil, plugin_typed_sink: nil, skip_project_patched: false)
|
|
105
106
|
return nil if receiver_type.nil?
|
|
106
107
|
|
|
107
108
|
# Build the call context once and thread it — unchanged — through every tier
|
|
@@ -205,7 +206,10 @@ module Rigor
|
|
|
205
206
|
# the pre-pass populated `ProjectPatchedMethods` with the `(class, method, kind)` triple;
|
|
206
207
|
# this tier surfaces it as `Dynamic[top]` so the patched call resolves cross-file without
|
|
207
208
|
# `call.undefined-method`.
|
|
208
|
-
patched_result =
|
|
209
|
+
patched_result =
|
|
210
|
+
unless skip_project_patched
|
|
211
|
+
try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
|
|
212
|
+
end
|
|
209
213
|
if patched_result
|
|
210
214
|
scope&.record_dynamic_origin(call_node, DynamicOrigin::EXTERNAL_GEM_WITHOUT_RBS)
|
|
211
215
|
return patched_result
|
|
@@ -614,7 +618,7 @@ module Rigor
|
|
|
614
618
|
# `def to_url; "hello"; end` patched onto `String` now resolves `s.to_url` to
|
|
615
619
|
# `Dynamic[Nominal[String]]` instead of the pre-3a `Dynamic[Top]`. Falls back to
|
|
616
620
|
# `Dynamic[Top]` when the heuristic declined (non-literal tail expression).
|
|
617
|
-
def try_project_patched_method(receiver_type, method_name, environment)
|
|
621
|
+
def try_project_patched_method(receiver_type, method_name, arg_types, block_type, environment)
|
|
618
622
|
registry = environment&.project_patched_methods
|
|
619
623
|
return nil if registry.nil? || registry.empty?
|
|
620
624
|
|
|
@@ -624,9 +628,25 @@ module Rigor
|
|
|
624
628
|
kind = receiver_type.is_a?(Type::Singleton) ? :singleton : :instance
|
|
625
629
|
entry = registry.lookup(class_name: class_name, method_name: method_name, kind: kind)
|
|
626
630
|
return nil if entry.nil?
|
|
627
|
-
return Type::Combinator.
|
|
631
|
+
return Type::Combinator.dynamic(entry.return_type) if entry.return_type
|
|
632
|
+
|
|
633
|
+
aliased = project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
|
|
634
|
+
aliased ? Type::Combinator.dynamic(aliased) : Type::Combinator.untyped
|
|
635
|
+
end
|
|
628
636
|
|
|
629
|
-
|
|
637
|
+
# Issue #1702 — an alias in a `pre_eval:` patch whose old name no earlier patch binding defines
|
|
638
|
+
# (`alias old_plus +` on Integer, or `alias_method :orig_succ, :succ` ahead of the patch's own `def succ`)
|
|
639
|
+
# names the method the class had before the patch, so it answers what that method answers for the same
|
|
640
|
+
# arguments, behind the `Dynamic` every patched method carries. The re-entry skips this tier: a later patch
|
|
641
|
+
# `def` of the old name is not the body the alias copied, and the skip is also what ends an alias cycle. It
|
|
642
|
+
# carries no call node or scope, so it answers a type and records nothing at the call site.
|
|
643
|
+
def project_patched_alias_target(entry, receiver_type, arg_types, block_type, environment)
|
|
644
|
+
return nil if entry.alias_of.nil?
|
|
645
|
+
|
|
646
|
+
resolve(
|
|
647
|
+
receiver_type: receiver_type, method_name: entry.alias_of, arg_types: arg_types,
|
|
648
|
+
block_type: block_type, environment: environment, skip_project_patched: true
|
|
649
|
+
)
|
|
630
650
|
end
|
|
631
651
|
|
|
632
652
|
# ADR-10 slice 2b-ii. Consults the per-run `Analysis::DependencySourceInference::Index`
|
|
@@ -847,7 +867,8 @@ module Rigor
|
|
|
847
867
|
private_constant :STDLIB_SINGLETON_FOLDERS
|
|
848
868
|
|
|
849
869
|
PRECISE_TIERS_TAIL = Ractor.make_shareable([
|
|
850
|
-
MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding
|
|
870
|
+
MethodFolding, ReduceFolding, ArrayToHFolding, HashTransformKeysFolding, BlockFolding,
|
|
871
|
+
ArithmeticSequenceElements
|
|
851
872
|
].freeze)
|
|
852
873
|
private_constant :PRECISE_TIERS_TAIL
|
|
853
874
|
|
|
@@ -1104,14 +1125,23 @@ module Rigor
|
|
|
1104
1125
|
# `instance_of?(self)` later as the rule catalogue grows; for now only `class` is handled.
|
|
1105
1126
|
def try_meta_introspection(receiver_type, method_name, arg_types = [], context = nil)
|
|
1106
1127
|
case method_name
|
|
1107
|
-
when :class then meta_class(receiver_type)
|
|
1128
|
+
when :class then meta_class(receiver_type, context&.environment)
|
|
1108
1129
|
when :new then meta_new(receiver_type, arg_types, context)
|
|
1109
1130
|
end
|
|
1110
1131
|
end
|
|
1111
1132
|
|
|
1112
|
-
def meta_class(receiver_type)
|
|
1133
|
+
def meta_class(receiver_type, environment = nil)
|
|
1113
1134
|
case receiver_type
|
|
1114
|
-
when Type::Nominal
|
|
1135
|
+
when Type::Nominal
|
|
1136
|
+
# An instance of module `M` is an instance of some class that includes `M`, never of `M`
|
|
1137
|
+
# itself: `self.class` in `M`'s instance method is that includer, which no RBS type names.
|
|
1138
|
+
# `Dynamic[Class]` keeps the surface every class shares (`name`, `ancestors`) and answers
|
|
1139
|
+
# nothing for the includer's own singleton methods.
|
|
1140
|
+
if environment&.rbs_module?(receiver_type.class_name)
|
|
1141
|
+
return Type::Combinator.dynamic(Type::Combinator.nominal_of("Class"))
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
Type::Combinator.singleton_of(receiver_type.class_name)
|
|
1115
1145
|
when Type::Constant then constant_metaclass(receiver_type.value)
|
|
1116
1146
|
end
|
|
1117
1147
|
end
|
|
@@ -1555,13 +1585,16 @@ module Rigor
|
|
|
1555
1585
|
# method definition, or selected overload does not provide statically declared block
|
|
1556
1586
|
# parameter types. Callers MUST treat the empty array as "no information"; the binder
|
|
1557
1587
|
# falls back to `Dynamic[Top]` for every parameter slot in that case.
|
|
1588
|
+
#
|
|
1589
|
+
# `call_node` lets the overload selector read the call's keyword arguments as keywords (#1727, #1737), as
|
|
1590
|
+
# the return path does; without it they are read positionally.
|
|
1558
1591
|
def expected_block_param_types(receiver_type:, method_name:, arg_types:, environment: nil,
|
|
1559
|
-
scope: nil)
|
|
1592
|
+
scope: nil, call_node: nil)
|
|
1560
1593
|
return [] if receiver_type.nil?
|
|
1561
1594
|
|
|
1562
1595
|
context = CallContext.build(
|
|
1563
1596
|
receiver: receiver_type, method_name: method_name,
|
|
1564
|
-
args: arg_types, environment: environment, scope: scope
|
|
1597
|
+
args: arg_types, environment: environment, scope: scope, call_node: call_node
|
|
1565
1598
|
)
|
|
1566
1599
|
iterator_result = IteratorDispatch.block_param_types(context)
|
|
1567
1600
|
return iterator_result if iterator_result
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require_relative "../source/literals"
|
|
5
|
+
require_relative "../source/node_children"
|
|
6
|
+
|
|
7
|
+
module Rigor
|
|
8
|
+
module Inference
|
|
9
|
+
# The one home for what a receiverless `module_function` call does to the methods around it
|
|
10
|
+
# ([#1507](https://github.com/rigortype/rigor/issues/1507),
|
|
11
|
+
# [#1197](https://github.com/rigortype/rigor/issues/1197)). Four readers used to compute it in four
|
|
12
|
+
# places. They disagree with each other and, in places, with Ruby. Each entry point below reproduces
|
|
13
|
+
# one reader's answer exactly: this module gives the readings one place to live, and does not yet make
|
|
14
|
+
# them agree. A change to one reading lands here, beside the other three. Examples are #1550's
|
|
15
|
+
# named-form snapshot and the reset a bare `public`, `private` or `protected` makes.
|
|
16
|
+
#
|
|
17
|
+
# - The singleton def-node table (`ScopeIndexer#walk_singleton_body`) reads
|
|
18
|
+
# {.each_singleton_sibling} and {.each_singleton_copy}. A bare call covers the later direct
|
|
19
|
+
# statements of its own body and is never reset.
|
|
20
|
+
# - The deferred ranges (`ScopeIndexer#walk_deferred_body`) read {.prescan_deferred} and
|
|
21
|
+
# {.deferred_module_function?}. A bare call covers every later def by offset, and a call inside
|
|
22
|
+
# control flow or a block counts.
|
|
23
|
+
# - The extends table (`ScopeIndexer#record_extend_call`) reads {.extends_self?}. A bare call covers
|
|
24
|
+
# every instance def of the module, in any order.
|
|
25
|
+
# - sig-gen (`SigGen::Generator#walk_statements`) reads {.each_sig_gen_statement} and
|
|
26
|
+
# {.sig_gen_module_function?}. A bare call covers the later statements of its own statement list and
|
|
27
|
+
# what nests in them.
|
|
28
|
+
#
|
|
29
|
+
# A fifth reader does not come here yet: `rigor-activerecord`'s `ModelDiscoverer#table_name_decorators`
|
|
30
|
+
# (`plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb`) reads a module body's
|
|
31
|
+
# direct statements for its table-name readers. It also accepts `self.module_function`, and there a bare
|
|
32
|
+
# call covers the instance methods written before it as well as after it, while a named call takes only a
|
|
33
|
+
# def written before it. A change to what `module_function` means must decide whether that reader follows.
|
|
34
|
+
#
|
|
35
|
+
# The deferred-ranges prescan asks `ScopeIndexer.receiver_eval_call?` and `ScopeIndexer.def_singleton?`, so
|
|
36
|
+
# `rigor/inference/scope_indexer` must be loaded before it runs. This file does not require it:
|
|
37
|
+
# `scope_indexer.rb` requires this file, and a require back would be circular. The other entry points need
|
|
38
|
+
# only Prism.
|
|
39
|
+
#
|
|
40
|
+
# `spec/rigor/inference/module_function_state_spec.rb` pins each reader's answers, and marks the ones
|
|
41
|
+
# Ruby contradicts with a "flip this when" note.
|
|
42
|
+
module ModuleFunctionState
|
|
43
|
+
module_function
|
|
44
|
+
|
|
45
|
+
# A receiverless `module_function` call, bare or named.
|
|
46
|
+
def call?(node)
|
|
47
|
+
node.name == :module_function && node.receiver.nil?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The toggle form: a `module_function` call with no arguments.
|
|
51
|
+
def bare?(node)
|
|
52
|
+
node.arguments.nil? || node.arguments.arguments.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The singleton def-node table's reading, over one class-ish body's direct statements in source
|
|
56
|
+
# order. `ScopeIndexer#statements_of` builds that list, folding a body-level `rescue`, `else` or
|
|
57
|
+
# `ensure` clause into it. Yields `stmt, module_function_on, named_call` for every statement except a
|
|
58
|
+
# bare call:
|
|
59
|
+
#
|
|
60
|
+
# - `module_function_on` says whether a bare call precedes the statement. The toggle never switches
|
|
61
|
+
# off, although a later bare `public`, `private` or `protected` resets it in Ruby.
|
|
62
|
+
# - `named_call` is true for a named call (`module_function :a` or `module_function def x`), which
|
|
63
|
+
# {.each_singleton_copy} resolves; the caller records nothing else for it.
|
|
64
|
+
#
|
|
65
|
+
# A call nested in control flow or a block is not seen.
|
|
66
|
+
def each_singleton_sibling(statements)
|
|
67
|
+
module_function_on = false
|
|
68
|
+
statements.each do |stmt|
|
|
69
|
+
if stmt.is_a?(Prism::CallNode) && call?(stmt)
|
|
70
|
+
if bare?(stmt)
|
|
71
|
+
module_function_on = true
|
|
72
|
+
else
|
|
73
|
+
yield stmt, module_function_on, true
|
|
74
|
+
end
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
yield stmt, module_function_on, false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The defs a named call copies onto the module's singleton, for the singleton def-node table. Yields
|
|
82
|
+
# `name, def_node` for each Symbol or String argument that names a receiverless def among the body's
|
|
83
|
+
# direct `statements`. The last def of that name wins wherever it sits. A def written after the call
|
|
84
|
+
# is therefore taken, although Ruby copies the one in effect at the call (#1550), and a name defined
|
|
85
|
+
# only after the call resolves, although Ruby raises `NameError` there. A `module_function def x`
|
|
86
|
+
# argument names nothing here.
|
|
87
|
+
def each_singleton_copy(call, statements)
|
|
88
|
+
defs_by_name = statements.each_with_object({}) do |stmt, acc|
|
|
89
|
+
acc[stmt.name] = stmt if stmt.is_a?(Prism::DefNode) && stmt.receiver.nil?
|
|
90
|
+
end
|
|
91
|
+
call.arguments&.arguments&.each do |arg|
|
|
92
|
+
name = symbol_argument_name(arg)
|
|
93
|
+
def_node = name && defs_by_name[name]
|
|
94
|
+
yield name, def_node if def_node
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The deferred-ranges reading, over one class-ish body's subtree. Appends to `offsets` the start of
|
|
99
|
+
# every bare call; each def that starts after one is a module function
|
|
100
|
+
# ({.deferred_module_function?}). Appends to `ranges` one row per named call, in the table's
|
|
101
|
+
# `[start, end, name, kind, owner]` shape:
|
|
102
|
+
#
|
|
103
|
+
# - a `:name` argument gives a `:singleton` row over the call itself, because the copy happens at
|
|
104
|
+
# the call and not at the def;
|
|
105
|
+
# - a `module_function def x` argument gives a `:both` row over the def, or a `:singleton` row for
|
|
106
|
+
# `def self.x`.
|
|
107
|
+
#
|
|
108
|
+
# The prescan looks through control flow and blocks, because a call that ran there still flips the
|
|
109
|
+
# later defs. It stops at a nested class, module, `class <<` or def body, where the call targets
|
|
110
|
+
# another module. It skips an `END` body, which runs after every def, and a `*_eval` / `*_exec`
|
|
111
|
+
# block, whose self is the receiver's module; that block gets its own prescan as a body. Nothing
|
|
112
|
+
# resets it.
|
|
113
|
+
#
|
|
114
|
+
# `qualified_prefix` names the owner, and `in_singleton_class` says whether the body is a
|
|
115
|
+
# `class <<` body; both feed `ScopeIndexer.def_singleton?`.
|
|
116
|
+
def prescan_deferred(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
117
|
+
return unless node.is_a?(Prism::Node)
|
|
118
|
+
return if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode) ||
|
|
119
|
+
node.is_a?(Prism::SingletonClassNode) || node.is_a?(Prism::DefNode) ||
|
|
120
|
+
node.is_a?(Prism::PostExecutionNode)
|
|
121
|
+
|
|
122
|
+
if node.is_a?(Prism::CallNode)
|
|
123
|
+
return prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges) if call?(node)
|
|
124
|
+
if ScopeIndexer.receiver_eval_call?(node)
|
|
125
|
+
return prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
node.rigor_each_child do |child|
|
|
130
|
+
prescan_deferred(child, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Whether the deferred-ranges reading makes a def starting at `start` a module function: a bare call
|
|
135
|
+
# starts before it, at any depth the prescan entered.
|
|
136
|
+
def deferred_module_function?(offsets, start)
|
|
137
|
+
offsets.any? { |offset| offset < start }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The extends table's reading. A bare call makes the module extend itself, so every instance def the
|
|
141
|
+
# module has also answers on its singleton. That holds for defs before and after the call, and
|
|
142
|
+
# whatever a later `public` or `private` says. The over-approximation is deliberate (#526): the
|
|
143
|
+
# extra names only silence `call.undefined-method` on calls that raise. The walk reaches a call in
|
|
144
|
+
# control flow, and one in a `class_eval`-family block under that block's receiver. It does not reach
|
|
145
|
+
# one in an ordinary block, an `END` body or a `class << self` body.
|
|
146
|
+
def extends_self?(call)
|
|
147
|
+
call?(call) && bare?(call)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# sig-gen's reading, over one statement list. Yields every statement except a bare call, with
|
|
151
|
+
# whether a bare call came before it in this list or `active` was already true on entry. The
|
|
152
|
+
# generator passes the enclosing list's answer into an `if`, a block or a `begin`, but not into a
|
|
153
|
+
# nested class, module or `class << self`. A bare call in a nested list covers only that list's
|
|
154
|
+
# later statements. Nothing resets it, and a named call is not a directive at all.
|
|
155
|
+
def each_sig_gen_statement(statements, active)
|
|
156
|
+
statements.each do |stmt|
|
|
157
|
+
if sig_gen_directive?(stmt)
|
|
158
|
+
active = true
|
|
159
|
+
next
|
|
160
|
+
end
|
|
161
|
+
yield stmt, active
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# A bare receiverless call, as sig-gen's statement-level directive.
|
|
166
|
+
def sig_gen_directive?(node)
|
|
167
|
+
node.is_a?(Prism::CallNode) && call?(node) && bare?(node)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Whether sig-gen renders a def as `def self?.name`: an instance-side def under an active directive,
|
|
171
|
+
# in a class or a module alike. `kind` is `:singleton` for `def self.x` and a `class << self` body.
|
|
172
|
+
def sig_gen_module_function?(active, kind)
|
|
173
|
+
active && kind == :instance
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# The prescan's call arm.
|
|
177
|
+
def prescan_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
178
|
+
if bare?(node)
|
|
179
|
+
offsets << node.location.start_offset
|
|
180
|
+
else
|
|
181
|
+
owner = qualified_prefix.empty? ? nil : qualified_prefix.join("::")
|
|
182
|
+
node.arguments&.arguments&.each do |arg|
|
|
183
|
+
if arg.is_a?(Prism::DefNode)
|
|
184
|
+
kind = ScopeIndexer.def_singleton?(arg, qualified_prefix, in_singleton_class) ? :singleton : :both
|
|
185
|
+
ranges << [arg.location.start_offset, arg.location.end_offset, arg.name, kind, owner]
|
|
186
|
+
elsif (name = symbol_argument_name(arg))
|
|
187
|
+
ranges << [node.location.start_offset, node.location.end_offset, name, :singleton, owner]
|
|
188
|
+
else
|
|
189
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
return unless node.block
|
|
194
|
+
|
|
195
|
+
prescan_deferred(node.block, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# The prescan's eval arm: only the call's receiver and arguments keep this module's context.
|
|
199
|
+
def prescan_eval_call(node, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
200
|
+
prescan_deferred(node.receiver, qualified_prefix, in_singleton_class, offsets, ranges) if node.receiver
|
|
201
|
+
node.arguments&.arguments&.each do |arg|
|
|
202
|
+
prescan_deferred(arg, qualified_prefix, in_singleton_class, offsets, ranges)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# The Symbol a `:name` or `"name"` literal argument names, or nil.
|
|
207
|
+
def symbol_argument_name(arg)
|
|
208
|
+
Source::Literals.symbol_or_string(arg)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
private_class_method :prescan_call, :prescan_eval_call, :symbol_argument_name
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
@@ -108,31 +108,36 @@ module Rigor
|
|
|
108
108
|
# `index_targets` maps each `Prism::IndexTargetNode` to the value it stores; {#apply_to}
|
|
109
109
|
# binds nothing for it.
|
|
110
110
|
Result = Data.define(:types, :optimistic, :ivars, :optimistic_ivars, :index_targets) do
|
|
111
|
-
def initialize(types:, optimistic:, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES,
|
|
112
|
-
index_targets: NO_BINDINGS)
|
|
111
|
+
def initialize(types:, optimistic:, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES, index_targets: NO_BINDINGS)
|
|
113
112
|
super
|
|
114
113
|
end
|
|
115
114
|
|
|
116
|
-
# Binds every name into `scope`
|
|
117
|
-
# `Scope#with_local` / `Scope#with_ivar` drop any mark the name carried before. `miss` is what
|
|
118
|
-
# each marked slot answers on a miss (issue #1302): `nil` for every mark the binder makes itself —
|
|
119
|
-
# a short array pads with `nil`, and a `nil` destructures to `nil` — so only a caller whose
|
|
120
|
-
# right-hand side a miss can make something else passes another answer
|
|
121
|
-
# ({OptimisticOrigin.destructuring_miss}).
|
|
115
|
+
# Binds every name into `scope` with its mark ({MultiTargetBinder.apply_bindings}).
|
|
122
116
|
def apply_to(scope, miss: nil)
|
|
123
|
-
|
|
124
|
-
bound = ivars.reduce(bound) { |acc, (name, type)| acc.with_ivar(name, type) }
|
|
125
|
-
bound = optimistic.reduce(bound) do |acc, name|
|
|
126
|
-
acc.with_optimistic_local(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
|
|
127
|
-
end
|
|
128
|
-
optimistic_ivars.reduce(bound) do |acc, name|
|
|
129
|
-
acc.with_optimistic_ivar(name, OptimisticOrigin::IMPLICITLY_RETURNS_NIL, miss: miss)
|
|
130
|
-
end
|
|
117
|
+
MultiTargetBinder.apply_bindings(scope, types, optimistic, ivars:, optimistic_ivars:, miss:)
|
|
131
118
|
end
|
|
132
119
|
end
|
|
133
120
|
|
|
134
121
|
module_function
|
|
135
122
|
|
|
123
|
+
# The one place bindings and their optimistic marks are applied, shared by {Result#apply_to} and
|
|
124
|
+
# `BlockParameterBinder#bind_onto`. Every name is bound into the scope `bound` and the mark recorded
|
|
125
|
+
# after the binding, since `Scope#with_local` / `Scope#with_ivar` drop any mark the name carried
|
|
126
|
+
# before; the answer is the bound scope. `miss` is what each marked slot answers on a miss (issue
|
|
127
|
+
# #1302): `nil` for every mark the binder makes itself — a short array pads with `nil`, and a `nil`
|
|
128
|
+
# destructures to `nil` — so only a caller whose right-hand side a miss can make something else passes
|
|
129
|
+
# another answer ({OptimisticOrigin.destructuring_miss}). It loops with `each`, since `reduce`
|
|
130
|
+
# allocates even over the empty collections most of the four are
|
|
131
|
+
# ([#1504](https://github.com/rigortype/rigor/issues/1504)).
|
|
132
|
+
def apply_bindings(bound, types, optimistic, ivars: NO_BINDINGS, optimistic_ivars: NO_NAMES, miss: nil)
|
|
133
|
+
types.each { |name, type| bound = bound.with_local(name, type) }
|
|
134
|
+
ivars.each { |name, type| bound = bound.with_ivar(name, type).without_ivar_guard(name) }
|
|
135
|
+
origin = OptimisticOrigin::IMPLICITLY_RETURNS_NIL
|
|
136
|
+
optimistic.each { |name| bound = bound.with_optimistic_local(name, origin, miss: miss) }
|
|
137
|
+
optimistic_ivars.each { |name| bound = bound.with_optimistic_ivar(name, origin, miss: miss) }
|
|
138
|
+
bound
|
|
139
|
+
end
|
|
140
|
+
|
|
136
141
|
# @param rhs_type — type of the right-hand side
|
|
137
142
|
# @param scope — the scope the destructure is evaluated in, which answers the `to_ary`
|
|
138
143
|
# question for a class outside the core list; without one only the core list wraps.
|