rigortype 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +154 -33
- data/lib/rigor/analysis/check_rules.rb +10 -18
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +75 -0
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +47 -21
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +32 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference.rb +1 -0
- data/lib/rigor/analysis/diagnostic.rb +0 -2
- data/lib/rigor/analysis/fact_store.rb +26 -6
- data/lib/rigor/analysis/result.rb +11 -3
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +193 -0
- data/lib/rigor/analysis/runner.rb +498 -12
- data/lib/rigor/analysis/worker_session.rb +327 -0
- data/lib/rigor/builtins/imported_refinements.rb +364 -55
- data/lib/rigor/builtins/regex_refinement.rb +17 -12
- data/lib/rigor/cache/descriptor.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +3 -1
- data/lib/rigor/cache/store.rb +39 -6
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +173 -0
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_renderer.rb +1 -1
- data/lib/rigor/cli/type_scan_report.rb +2 -2
- data/lib/rigor/cli.rb +61 -3
- data/lib/rigor/configuration/dependencies.rb +2 -2
- data/lib/rigor/configuration.rb +131 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +198 -0
- data/lib/rigor/environment/class_registry.rb +12 -3
- data/lib/rigor/environment/lockfile_resolver.rb +125 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +126 -0
- data/lib/rigor/environment/rbs_coverage_report.rb +112 -0
- data/lib/rigor/environment/rbs_loader.rb +194 -6
- data/lib/rigor/environment/reflection.rb +152 -0
- data/lib/rigor/environment.rb +109 -6
- data/lib/rigor/flow_contribution/conflict.rb +2 -2
- data/lib/rigor/flow_contribution/element.rb +1 -1
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution/merge_result.rb +1 -1
- data/lib/rigor/flow_contribution/merger.rb +3 -3
- data/lib/rigor/flow_contribution.rb +2 -2
- data/lib/rigor/inference/acceptance.rb +35 -1
- data/lib/rigor/inference/block_parameter_binder.rb +0 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +12 -5
- data/lib/rigor/inference/builtins/numeric_catalog.rb +15 -4
- data/lib/rigor/inference/coverage_scanner.rb +1 -1
- data/lib/rigor/inference/expression_typer.rb +77 -11
- data/lib/rigor/inference/fallback.rb +1 -1
- data/lib/rigor/inference/macro_block_self_type.rb +96 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +3 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +29 -41
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -4
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +135 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +7 -12
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +27 -11
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +46 -44
- data/lib/rigor/inference/method_dispatcher.rb +274 -5
- data/lib/rigor/inference/method_parameter_binder.rb +22 -14
- data/lib/rigor/inference/narrowing.rb +129 -12
- data/lib/rigor/inference/rbs_type_translator.rb +0 -2
- data/lib/rigor/inference/scope_indexer.rb +14 -9
- data/lib/rigor/inference/statement_evaluator.rb +7 -7
- data/lib/rigor/inference/synthetic_method.rb +86 -0
- data/lib/rigor/inference/synthetic_method_index.rb +82 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +521 -0
- data/lib/rigor/plugin/blueprint.rb +60 -0
- data/lib/rigor/plugin/io_boundary.rb +0 -2
- data/lib/rigor/plugin/loader.rb +5 -3
- data/lib/rigor/plugin/macro/block_as_method.rb +131 -0
- data/lib/rigor/plugin/macro/external_file.rb +143 -0
- data/lib/rigor/plugin/macro/heredoc_template.rb +201 -0
- data/lib/rigor/plugin/macro/trait_registry.rb +198 -0
- data/lib/rigor/plugin/macro.rb +31 -0
- data/lib/rigor/plugin/manifest.rb +102 -10
- data/lib/rigor/plugin/registry.rb +43 -2
- data/lib/rigor/plugin/services.rb +1 -1
- data/lib/rigor/plugin/type_node_resolver.rb +52 -0
- data/lib/rigor/plugin.rb +2 -0
- data/lib/rigor/rbs_extended/reporter.rb +91 -0
- data/lib/rigor/rbs_extended.rb +131 -32
- data/lib/rigor/scope.rb +25 -8
- data/lib/rigor/sig_gen/classification.rb +36 -0
- data/lib/rigor/sig_gen/generator.rb +1048 -0
- data/lib/rigor/sig_gen/layout_index.rb +108 -0
- data/lib/rigor/sig_gen/method_candidate.rb +62 -0
- data/lib/rigor/sig_gen/observation_collector.rb +391 -0
- data/lib/rigor/sig_gen/observed_call.rb +62 -0
- data/lib/rigor/sig_gen/path_mapper.rb +116 -0
- data/lib/rigor/sig_gen/renderer.rb +157 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +92 -0
- data/lib/rigor/sig_gen/write_result.rb +48 -0
- data/lib/rigor/sig_gen/writer.rb +530 -0
- data/lib/rigor/sig_gen.rb +25 -0
- data/lib/rigor/trinary.rb +15 -11
- data/lib/rigor/type/bot.rb +6 -3
- data/lib/rigor/type/bound_method.rb +79 -0
- data/lib/rigor/type/combinator.rb +207 -3
- data/lib/rigor/type/constant.rb +13 -0
- data/lib/rigor/type/hash_shape.rb +0 -2
- data/lib/rigor/type/integer_range.rb +7 -7
- data/lib/rigor/type/refined.rb +18 -12
- data/lib/rigor/type/top.rb +4 -3
- data/lib/rigor/type/union.rb +20 -1
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +68 -0
- data/lib/rigor/type_node/identifier.rb +38 -0
- data/lib/rigor/type_node/indexed_access.rb +41 -0
- data/lib/rigor/type_node/integer_literal.rb +29 -0
- data/lib/rigor/type_node/name_scope.rb +52 -0
- data/lib/rigor/type_node/resolver_chain.rb +56 -0
- data/lib/rigor/type_node/string_literal.rb +32 -0
- data/lib/rigor/type_node/symbol_literal.rb +28 -0
- data/lib/rigor/type_node/union.rb +42 -0
- data/lib/rigor/type_node.rb +29 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +2 -0
- data/sig/rigor/analysis/check_rules/always_truthy_condition_collector.rbs +10 -0
- data/sig/rigor/analysis/check_rules/dead_assignment_collector.rbs +10 -0
- data/sig/rigor/analysis/dependency_source_inference/gem_resolver.rbs +25 -0
- data/sig/rigor/analysis/dependency_source_inference/index.rbs +9 -0
- data/sig/rigor/cli/diff_command.rbs +4 -0
- data/sig/rigor/cli/explain_command.rbs +4 -0
- data/sig/rigor/cli/sig_gen_command.rbs +4 -0
- data/sig/rigor/cli/type_scan_command.rbs +3 -0
- data/sig/rigor/environment.rbs +8 -2
- data/sig/rigor/inference/builtins/method_catalog.rbs +4 -0
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +3 -0
- data/sig/rigor/inference/builtins.rbs +2 -0
- data/sig/rigor/plugin/access_denied_error.rbs +3 -0
- data/sig/rigor/plugin/base.rbs +6 -0
- data/sig/rigor/plugin/blueprint.rbs +7 -0
- data/sig/rigor/plugin/fact_store.rbs +11 -0
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/load_error.rbs +6 -0
- data/sig/rigor/plugin/loader.rbs +20 -0
- data/sig/rigor/plugin/manifest.rbs +9 -0
- data/sig/rigor/plugin/registry.rbs +16 -0
- data/sig/rigor/plugin/services.rbs +3 -0
- data/sig/rigor/plugin/trust_policy.rbs +4 -0
- data/sig/rigor/plugin/type_node_resolver.rbs +3 -0
- data/sig/rigor/plugin.rbs +8 -0
- data/sig/rigor/scope.rbs +4 -2
- data/sig/rigor/type.rbs +28 -6
- data/sig/rigor.rbs +35 -2
- metadata +90 -1
|
@@ -57,9 +57,19 @@ module Rigor
|
|
|
57
57
|
# `Scope#with_local` / `#with_fact` / `#with_self_type`
|
|
58
58
|
# propagates it across every derived scope.
|
|
59
59
|
declared_types, discovered_classes = build_declaration_artifacts(root)
|
|
60
|
+
# Merge the indexer's findings on top of whatever the
|
|
61
|
+
# base scope already carries so callers that seed
|
|
62
|
+
# cross-file class knowledge (e.g. the ADR-14
|
|
63
|
+
# `SigGen::ObservationCollector` pre-walking project
|
|
64
|
+
# `lib/` before scanning `spec/`) keep their seeds
|
|
65
|
+
# alongside the per-file declarations the indexer
|
|
66
|
+
# itself discovers. Indexer-found entries win on
|
|
67
|
+
# collision — same-file declarations are the most
|
|
68
|
+
# specific authority.
|
|
69
|
+
merged_classes = default_scope.discovered_classes.merge(discovered_classes)
|
|
60
70
|
seeded_scope = default_scope
|
|
61
71
|
.with_declared_types(declared_types)
|
|
62
|
-
.with_discovered_classes(
|
|
72
|
+
.with_discovered_classes(merged_classes)
|
|
63
73
|
|
|
64
74
|
# Slice 7 phase 2. Pre-pass over every class/module body
|
|
65
75
|
# to collect the per-class ivar accumulator. Seeded after
|
|
@@ -300,7 +310,7 @@ module Rigor
|
|
|
300
310
|
accumulator.freeze
|
|
301
311
|
end
|
|
302
312
|
|
|
303
|
-
def walk_constant_writes(node, qualified_prefix, default_scope, accumulator)
|
|
313
|
+
def walk_constant_writes(node, qualified_prefix, default_scope, accumulator)
|
|
304
314
|
return unless node.is_a?(Prism::Node)
|
|
305
315
|
|
|
306
316
|
case node
|
|
@@ -348,7 +358,7 @@ module Rigor
|
|
|
348
358
|
accumulator.transform_values(&:freeze).freeze
|
|
349
359
|
end
|
|
350
360
|
|
|
351
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
361
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
352
362
|
def walk_methods(node, qualified_prefix, in_singleton_class, accumulator)
|
|
353
363
|
return unless node.is_a?(Prism::Node)
|
|
354
364
|
|
|
@@ -385,7 +395,7 @@ module Rigor
|
|
|
385
395
|
walk_methods(child, qualified_prefix, in_singleton_class, accumulator)
|
|
386
396
|
end
|
|
387
397
|
end
|
|
388
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
398
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
389
399
|
|
|
390
400
|
# v0.1.2 — when a `Const = Data.define(*sym) do ... end`
|
|
391
401
|
# / `Const = Struct.new(*sym) do ... end` constant write
|
|
@@ -430,7 +440,6 @@ module Rigor
|
|
|
430
440
|
accumulator.transform_values(&:freeze).freeze
|
|
431
441
|
end
|
|
432
442
|
|
|
433
|
-
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
434
443
|
def walk_def_nodes(node, qualified_prefix, in_singleton_class, accumulator)
|
|
435
444
|
return unless node.is_a?(Prism::Node)
|
|
436
445
|
|
|
@@ -462,8 +471,6 @@ module Rigor
|
|
|
462
471
|
walk_def_nodes(child, qualified_prefix, in_singleton_class, accumulator)
|
|
463
472
|
end
|
|
464
473
|
end
|
|
465
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
466
|
-
|
|
467
474
|
# v0.0.3 A — sentinel key under which `record_def_node`
|
|
468
475
|
# files DefNodes that live outside any class / module
|
|
469
476
|
# body (top-level helpers, `def`s nested inside DSL
|
|
@@ -646,7 +653,6 @@ module Rigor
|
|
|
646
653
|
|
|
647
654
|
# Builds a map `{class_name => {new_name_sym => old_name_sym}}` by
|
|
648
655
|
# walking the tree for `AliasMethodNode` nodes inside class bodies.
|
|
649
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
650
656
|
def collect_class_alias_map(node, qualified_prefix, accumulator)
|
|
651
657
|
return accumulator unless node.is_a?(Prism::Node)
|
|
652
658
|
|
|
@@ -667,7 +673,6 @@ module Rigor
|
|
|
667
673
|
node.compact_child_nodes.each { |child| collect_class_alias_map(child, qualified_prefix, accumulator) }
|
|
668
674
|
accumulator
|
|
669
675
|
end
|
|
670
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
671
676
|
|
|
672
677
|
def record_alias_map_entry(alias_node, qualified_prefix, accumulator)
|
|
673
678
|
return if qualified_prefix.empty?
|
|
@@ -889,7 +889,7 @@ module Rigor
|
|
|
889
889
|
# or nil otherwise. Centralised so each per-matcher
|
|
890
890
|
# decoder can short-circuit on a non-matching outer
|
|
891
891
|
# call.
|
|
892
|
-
def rspec_expectation_target(call_node)
|
|
892
|
+
def rspec_expectation_target(call_node)
|
|
893
893
|
receiver = call_node.receiver
|
|
894
894
|
return nil unless receiver.is_a?(Prism::CallNode) && receiver.name == :expect
|
|
895
895
|
return nil unless receiver.receiver.nil?
|
|
@@ -967,7 +967,7 @@ module Rigor
|
|
|
967
967
|
method_def = resolve_call_method(call_node, current_scope)
|
|
968
968
|
return current_scope if method_def.nil?
|
|
969
969
|
|
|
970
|
-
contribution = RbsExtended.read_flow_contribution(method_def)
|
|
970
|
+
contribution = RbsExtended.read_flow_contribution(method_def, environment: current_scope.environment)
|
|
971
971
|
return current_scope if contribution.nil?
|
|
972
972
|
|
|
973
973
|
result = Rigor::FlowContribution::Merger.merge([contribution])
|
|
@@ -1034,7 +1034,7 @@ module Rigor
|
|
|
1034
1034
|
end
|
|
1035
1035
|
end
|
|
1036
1036
|
|
|
1037
|
-
def resolve_call_method(call_node, current_scope)
|
|
1037
|
+
def resolve_call_method(call_node, current_scope)
|
|
1038
1038
|
receiver_node = call_node.receiver
|
|
1039
1039
|
receiver_type =
|
|
1040
1040
|
if receiver_node
|
|
@@ -1120,7 +1120,7 @@ module Rigor
|
|
|
1120
1120
|
end
|
|
1121
1121
|
end
|
|
1122
1122
|
|
|
1123
|
-
def lookup_post_return_arg(call_node, method_def, target_name)
|
|
1123
|
+
def lookup_post_return_arg(call_node, method_def, target_name)
|
|
1124
1124
|
# Plugin-source contributions arrive without an
|
|
1125
1125
|
# authoritative method_def (the plugin recognised the
|
|
1126
1126
|
# call shape directly). Parameter-targeting falls back
|
|
@@ -1394,6 +1394,8 @@ module Rigor
|
|
|
1394
1394
|
.with_class_ivars(scope.class_ivars)
|
|
1395
1395
|
.with_class_cvars(scope.class_cvars)
|
|
1396
1396
|
.with_program_globals(scope.program_globals)
|
|
1397
|
+
.with_discovered_methods(scope.discovered_methods)
|
|
1398
|
+
.with_discovered_method_visibilities(scope.discovered_method_visibilities)
|
|
1397
1399
|
end
|
|
1398
1400
|
|
|
1399
1401
|
def singleton_def?(def_node)
|
|
@@ -1501,7 +1503,7 @@ module Rigor
|
|
|
1501
1503
|
EXIT_CALL_NAMES = %i[raise throw exit abort fail].freeze
|
|
1502
1504
|
private_constant :EXIT_CALL_NAMES
|
|
1503
1505
|
|
|
1504
|
-
def branch_unconditionally_exits?(node)
|
|
1506
|
+
def branch_unconditionally_exits?(node)
|
|
1505
1507
|
return false if node.nil?
|
|
1506
1508
|
|
|
1507
1509
|
case node
|
|
@@ -1607,7 +1609,6 @@ module Rigor
|
|
|
1607
1609
|
# Returns an array of `[Symbol, Rigor::Type]` pairs for every
|
|
1608
1610
|
# variable captured by `pattern`. Unrecognised pattern nodes
|
|
1609
1611
|
# contribute no bindings (fail-soft).
|
|
1610
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
1611
1612
|
def collect_in_pattern_bindings(subject, pattern, scope)
|
|
1612
1613
|
case pattern
|
|
1613
1614
|
when Prism::CapturePatternNode
|
|
@@ -1629,7 +1630,6 @@ module Rigor
|
|
|
1629
1630
|
[]
|
|
1630
1631
|
end
|
|
1631
1632
|
end
|
|
1632
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
1633
1633
|
|
|
1634
1634
|
def collect_array_pattern_bindings(pattern, scope)
|
|
1635
1635
|
bindings = [*pattern.requireds, *pattern.posts].flat_map do |elem|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rigor
|
|
4
|
+
module Inference
|
|
5
|
+
# ADR-16 Tier C output — one synthetic method declared by a
|
|
6
|
+
# plugin's `Plugin::Macro::HeredocTemplate` entry, after the
|
|
7
|
+
# pre-pass has interpolated the call-site literal symbol into
|
|
8
|
+
# the template name. Stored in {SyntheticMethodIndex} and
|
|
9
|
+
# consulted by {MethodDispatcher} below the RBS dispatch tier.
|
|
10
|
+
#
|
|
11
|
+
# Per ADR-16 § WD13 (cost-bounded best-effort): the v0.1.x
|
|
12
|
+
# delivery commitment is the floor — method names emit; their
|
|
13
|
+
# return types degrade to `Dynamic[T]` until slice 6
|
|
14
|
+
# (precision promotion) routes the recorded `return_type`
|
|
15
|
+
# string through ADR-13's `Plugin::TypeNodeResolver` chain.
|
|
16
|
+
# The string is preserved so the ceiling slice can resolve it
|
|
17
|
+
# without re-walking.
|
|
18
|
+
#
|
|
19
|
+
# The `provenance` Hash carries debug / `--explain` metadata:
|
|
20
|
+
# plugin id, the template's call shape, and the source
|
|
21
|
+
# location of the originating DSL call. Surfaced through the
|
|
22
|
+
# dispatcher's `macro.tier_c.*` provenance markers.
|
|
23
|
+
class SyntheticMethod
|
|
24
|
+
INSTANCE = :instance
|
|
25
|
+
SINGLETON = :singleton
|
|
26
|
+
VALID_KINDS = [INSTANCE, SINGLETON].freeze
|
|
27
|
+
|
|
28
|
+
attr_reader :class_name, :method_name, :return_type, :kind, :provenance
|
|
29
|
+
|
|
30
|
+
def initialize(class_name:, method_name:, return_type:, kind: INSTANCE, provenance: {})
|
|
31
|
+
validate!(class_name, method_name, return_type, kind, provenance)
|
|
32
|
+
@class_name = class_name.dup.freeze
|
|
33
|
+
@method_name = method_name.to_sym
|
|
34
|
+
@return_type = return_type.dup.freeze
|
|
35
|
+
@kind = kind
|
|
36
|
+
@provenance = provenance.transform_keys(&:to_sym).transform_values do |v|
|
|
37
|
+
v.is_a?(String) ? v.dup.freeze : v
|
|
38
|
+
end.freeze
|
|
39
|
+
freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def instance? = kind == INSTANCE
|
|
43
|
+
def singleton? = kind == SINGLETON
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
{
|
|
47
|
+
"class_name" => class_name,
|
|
48
|
+
"method_name" => method_name.to_s,
|
|
49
|
+
"return_type" => return_type,
|
|
50
|
+
"kind" => kind.to_s,
|
|
51
|
+
"provenance" => provenance.transform_keys(&:to_s)
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ==(other)
|
|
56
|
+
other.is_a?(SyntheticMethod) && to_h == other.to_h
|
|
57
|
+
end
|
|
58
|
+
alias eql? ==
|
|
59
|
+
|
|
60
|
+
def hash
|
|
61
|
+
to_h.hash
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def validate!(class_name, method_name, return_type, kind, provenance)
|
|
67
|
+
unless class_name.is_a?(String) && !class_name.empty?
|
|
68
|
+
raise ArgumentError, "SyntheticMethod#class_name must be non-empty String, got #{class_name.inspect}"
|
|
69
|
+
end
|
|
70
|
+
unless method_name.is_a?(Symbol) || (method_name.is_a?(String) && !method_name.empty?)
|
|
71
|
+
raise ArgumentError, "SyntheticMethod#method_name must be Symbol/non-empty String, got #{method_name.inspect}"
|
|
72
|
+
end
|
|
73
|
+
unless return_type.is_a?(String) && !return_type.empty?
|
|
74
|
+
raise ArgumentError, "SyntheticMethod#return_type must be non-empty String, got #{return_type.inspect}"
|
|
75
|
+
end
|
|
76
|
+
unless VALID_KINDS.include?(kind)
|
|
77
|
+
raise ArgumentError, "SyntheticMethod#kind must be one of #{VALID_KINDS.inspect}, got #{kind.inspect}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return if provenance.is_a?(Hash)
|
|
81
|
+
|
|
82
|
+
raise ArgumentError, "SyntheticMethod#provenance must be a Hash, got #{provenance.inspect}"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "synthetic_method"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
# Frozen, Ractor-shareable lookup table for the synthetic
|
|
8
|
+
# methods emitted by ADR-16 Tier C declarations during a
|
|
9
|
+
# single `Analysis::Runner#run`. Constructed by the pre-pass
|
|
10
|
+
# scanner (see {SyntheticMethodScanner}) and consulted by
|
|
11
|
+
# {MethodDispatcher} below `RbsDispatch.try_dispatch` (per WD13:
|
|
12
|
+
# user-authored RBS overrides substrate synthesis).
|
|
13
|
+
#
|
|
14
|
+
# The index is keyed by `(class_name, method_name, kind)`. A
|
|
15
|
+
# single key may resolve to multiple {SyntheticMethod} records
|
|
16
|
+
# if two plugins emit the same name (e.g. `rigor-dry-struct`
|
|
17
|
+
# and a hypothetical `rigor-dry-struct-extras` both registering
|
|
18
|
+
# the same attribute). Per ADR-16 WD11 / the WD-discussion in
|
|
19
|
+
# `## Open questions` the dispatcher uses first-wins by
|
|
20
|
+
# registration order; this index preserves that order in
|
|
21
|
+
# `lookup`'s return.
|
|
22
|
+
#
|
|
23
|
+
# ## Slice 2b — return-type precision posture
|
|
24
|
+
#
|
|
25
|
+
# The recorded `SyntheticMethod#return_type` is a String
|
|
26
|
+
# (e.g. `"ActiveStorage::Attached::One"`), preserved verbatim
|
|
27
|
+
# from the manifest's emit table. Slice 2b's engine wiring
|
|
28
|
+
# treats every match as returning `Dynamic[T]` per WD13's
|
|
29
|
+
# floor — the recorded string is the input to a later slice's
|
|
30
|
+
# precision promotion via ADR-13's `Plugin::TypeNodeResolver`.
|
|
31
|
+
class SyntheticMethodIndex
|
|
32
|
+
attr_reader :entries
|
|
33
|
+
|
|
34
|
+
def initialize(entries: [])
|
|
35
|
+
unless entries.is_a?(Array) && entries.all?(SyntheticMethod)
|
|
36
|
+
raise ArgumentError,
|
|
37
|
+
"SyntheticMethodIndex#entries must be an Array of SyntheticMethod, got #{entries.inspect}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
@entries = Ractor.make_shareable(entries.dup)
|
|
41
|
+
@by_instance = Ractor.make_shareable(bucket(entries, SyntheticMethod::INSTANCE))
|
|
42
|
+
@by_singleton = Ractor.make_shareable(bucket(entries, SyntheticMethod::SINGLETON))
|
|
43
|
+
freeze
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def empty?
|
|
47
|
+
entries.empty?
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns an Array of matching {SyntheticMethod} records in
|
|
51
|
+
# plugin-registration order. Empty Array when no plugin has
|
|
52
|
+
# declared a Tier C entry that interpolates to this name.
|
|
53
|
+
def lookup_instance(class_name, method_name)
|
|
54
|
+
@by_instance.fetch([class_name, method_name.to_sym], EMPTY_ROW)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def lookup_singleton(class_name, method_name)
|
|
58
|
+
@by_singleton.fetch([class_name, method_name.to_sym], EMPTY_ROW)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_h
|
|
62
|
+
{ "entries" => entries.map(&:to_h) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
EMPTY_ROW = [].freeze
|
|
66
|
+
|
|
67
|
+
def bucket(entries, kind)
|
|
68
|
+
h = {}
|
|
69
|
+
entries.each do |entry|
|
|
70
|
+
next unless entry.kind == kind
|
|
71
|
+
|
|
72
|
+
key = [entry.class_name, entry.method_name]
|
|
73
|
+
(h[key] ||= []) << entry
|
|
74
|
+
end
|
|
75
|
+
h.each_value(&:freeze).freeze
|
|
76
|
+
end
|
|
77
|
+
private :bucket
|
|
78
|
+
|
|
79
|
+
EMPTY = new(entries: []).freeze
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|