rigortype 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +18 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +67 -0
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +226 -0
- data/lib/rigor/analysis/check_rules.rb +180 -73
- data/lib/rigor/analysis/dependency_recorder.rb +122 -0
- data/lib/rigor/analysis/diagnostic.rb +18 -0
- data/lib/rigor/analysis/incremental.rb +162 -0
- data/lib/rigor/analysis/incremental_session.rb +337 -0
- data/lib/rigor/analysis/rule_catalog.rb +48 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +580 -0
- data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +318 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
- data/lib/rigor/analysis/runner.rb +477 -1110
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
- data/lib/rigor/analysis/worker_session.rb +47 -8
- data/lib/rigor/builtins/static_return_refinements.rb +7 -1
- data/lib/rigor/cache/descriptor.rb +50 -49
- data/lib/rigor/cache/incremental_snapshot.rb +153 -0
- data/lib/rigor/cache/rbs_cache_producer.rb +34 -0
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
- data/lib/rigor/cache/rbs_constant_table.rb +2 -8
- data/lib/rigor/cache/rbs_environment.rb +2 -8
- data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
- data/lib/rigor/cache/store.rb +145 -14
- data/lib/rigor/cli/annotate_command.rb +2 -7
- data/lib/rigor/cli/baseline_command.rb +2 -7
- data/lib/rigor/cli/check_command.rb +705 -0
- data/lib/rigor/cli/ci_detector.rb +94 -0
- data/lib/rigor/cli/command.rb +47 -0
- data/lib/rigor/cli/coverage_command.rb +3 -23
- data/lib/rigor/cli/coverage_renderer.rb +3 -8
- data/lib/rigor/cli/diagnostic_formats.rb +345 -0
- data/lib/rigor/cli/diff_command.rb +3 -7
- data/lib/rigor/cli/explain_command.rb +2 -7
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mcp_command.rb +3 -7
- data/lib/rigor/cli/options.rb +57 -0
- data/lib/rigor/cli/plugin_command.rb +3 -7
- data/lib/rigor/cli/plugins_command.rb +2 -7
- data/lib/rigor/cli/prism_colorizer.rb +10 -3
- data/lib/rigor/cli/renderable.rb +26 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -7
- data/lib/rigor/cli/skill_command.rb +3 -7
- data/lib/rigor/cli/trace_command.rb +143 -0
- data/lib/rigor/cli/trace_renderer.rb +310 -0
- data/lib/rigor/cli/triage_command.rb +2 -7
- data/lib/rigor/cli/type_of_command.rb +5 -38
- data/lib/rigor/cli/type_of_renderer.rb +4 -9
- data/lib/rigor/cli/type_scan_command.rb +3 -23
- data/lib/rigor/cli/type_scan_renderer.rb +4 -9
- data/lib/rigor/cli.rb +15 -532
- data/lib/rigor/configuration/dependencies.rb +18 -1
- data/lib/rigor/configuration/severity_profile.rb +22 -3
- data/lib/rigor/configuration.rb +16 -3
- data/lib/rigor/environment/rbs_loader.rb +129 -71
- data/lib/rigor/environment.rb +1 -1
- data/lib/rigor/inference/acceptance.rb +10 -0
- data/lib/rigor/inference/block_parameter_binder.rb +1 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
- data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
- data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
- data/lib/rigor/inference/expression_typer.rb +149 -63
- data/lib/rigor/inference/flow_tracer.rb +180 -0
- data/lib/rigor/inference/macro_block_self_type.rb +10 -11
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
- data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +33 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
- data/lib/rigor/inference/method_dispatcher.rb +185 -84
- data/lib/rigor/inference/narrowing.rb +262 -5
- data/lib/rigor/inference/scope_indexer.rb +208 -21
- data/lib/rigor/inference/statement_evaluator.rb +110 -48
- data/lib/rigor/language_server/buffer_resolution.rb +33 -0
- data/lib/rigor/language_server/completion_provider.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
- data/lib/rigor/language_server/folding_range_provider.rb +4 -4
- data/lib/rigor/language_server/hover_provider.rb +4 -4
- data/lib/rigor/language_server/selection_range_provider.rb +4 -4
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/plugin/additional_initializer.rb +61 -38
- data/lib/rigor/plugin/base.rb +302 -45
- data/lib/rigor/plugin/node_rule_walk.rb +147 -0
- data/lib/rigor/plugin/registry.rb +281 -15
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/rbs_extended/conformance_checker.rb +293 -0
- data/lib/rigor/rbs_extended.rb +39 -0
- data/lib/rigor/scope/discovery_index.rb +58 -0
- data/lib/rigor/scope.rb +150 -167
- data/lib/rigor/sig_gen/observation_collector.rb +6 -6
- data/lib/rigor/source/literals.rb +14 -0
- data/lib/rigor/type/acceptance_router.rb +19 -0
- data/lib/rigor/type/accepts_result.rb +3 -10
- data/lib/rigor/type/app.rb +3 -7
- data/lib/rigor/type/bot.rb +2 -3
- data/lib/rigor/type/bound_method.rb +5 -12
- data/lib/rigor/type/combinator.rb +22 -0
- data/lib/rigor/type/constant.rb +2 -3
- data/lib/rigor/type/data_class.rb +80 -0
- data/lib/rigor/type/data_instance.rb +100 -0
- data/lib/rigor/type/difference.rb +5 -10
- data/lib/rigor/type/dynamic.rb +5 -10
- data/lib/rigor/type/hash_shape.rb +5 -15
- data/lib/rigor/type/integer_range.rb +5 -10
- data/lib/rigor/type/intersection.rb +5 -10
- data/lib/rigor/type/nominal.rb +5 -10
- data/lib/rigor/type/refined.rb +5 -10
- data/lib/rigor/type/singleton.rb +5 -10
- data/lib/rigor/type/top.rb +2 -3
- data/lib/rigor/type/tuple.rb +5 -10
- data/lib/rigor/type/union.rb +5 -10
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/value_semantics.rb +77 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +70 -32
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +15 -21
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +35 -18
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +83 -36
- data/sig/rigor/cache.rbs +19 -0
- data/sig/rigor/environment.rbs +0 -2
- data/sig/rigor/inference.rbs +27 -0
- data/sig/rigor/plugin/base.rbs +1 -2
- data/sig/rigor/rbs_extended.rbs +2 -0
- data/sig/rigor/scope.rbs +42 -25
- data/sig/rigor/source.rbs +1 -0
- data/sig/rigor/type.rbs +58 -1
- data/sig/rigor.rbs +6 -1
- data/skills/rigor-ci-setup/SKILL.md +319 -0
- metadata +36 -2
- data/lib/rigor/cache/rbs_instance_definitions.rb +0 -79
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -32,7 +33,10 @@ module Rigor
|
|
|
32
33
|
|
|
33
34
|
# @return [Array<Rigor::Type>, nil] block-param types, or
|
|
34
35
|
# nil to fall through to the next tier.
|
|
35
|
-
def block_param_types(
|
|
36
|
+
def block_param_types(context)
|
|
37
|
+
receiver = context.receiver
|
|
38
|
+
method_name = context.method_name
|
|
39
|
+
args = context.args
|
|
36
40
|
case method_name
|
|
37
41
|
when :times then times_block_params(receiver)
|
|
38
42
|
when :upto then upto_block_params(receiver, args.first)
|
|
@@ -66,7 +70,7 @@ module Rigor
|
|
|
66
70
|
end
|
|
67
71
|
|
|
68
72
|
def class_metaclass_receiver?(type)
|
|
69
|
-
|
|
73
|
+
SingletonFolding.receiver?(type, "Class")
|
|
70
74
|
end
|
|
71
75
|
|
|
72
76
|
def times_block_params(receiver)
|
|
@@ -53,7 +53,10 @@ module Rigor
|
|
|
53
53
|
INTEGER_REFINEMENT_PREDICATES = Set[:decimal_int, :numeric].freeze
|
|
54
54
|
private_constant :INTEGER_REFINEMENT_PREDICATES
|
|
55
55
|
|
|
56
|
-
def try_dispatch(
|
|
56
|
+
def try_dispatch(context)
|
|
57
|
+
receiver = context.receiver
|
|
58
|
+
method_name = context.method_name
|
|
59
|
+
args = context.args
|
|
57
60
|
return nil if receiver.nil?
|
|
58
61
|
return try_array(args) if method_name == :Array
|
|
59
62
|
return try_numeric_constructor(method_name, args) if NUMERIC_CONSTRUCTORS.key?(method_name)
|
|
@@ -80,7 +80,10 @@ module Rigor
|
|
|
80
80
|
:LITERAL_PRESERVING_METHODS, :NON_EMPTY_LITERAL_PRESERVING_METHODS,
|
|
81
81
|
:WIDTH_PADDING_METHODS
|
|
82
82
|
|
|
83
|
-
def try_dispatch(
|
|
83
|
+
def try_dispatch(context)
|
|
84
|
+
receiver = context.receiver
|
|
85
|
+
method_name = context.method_name
|
|
86
|
+
args = context.args
|
|
84
87
|
return fold_array_join(receiver, args) if method_name == :join
|
|
85
88
|
return fold_format(args) if FORMAT_METHODS.include?(method_name)
|
|
86
89
|
return nil unless Type::Combinator.literal_string_compatible?(receiver)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -57,8 +58,11 @@ module Rigor
|
|
|
57
58
|
module_function
|
|
58
59
|
|
|
59
60
|
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
60
|
-
def try_dispatch(
|
|
61
|
-
|
|
61
|
+
def try_dispatch(context)
|
|
62
|
+
receiver = context.receiver
|
|
63
|
+
method_name = context.method_name
|
|
64
|
+
args = context.args
|
|
65
|
+
return nil unless SingletonFolding.receiver?(receiver, "Math")
|
|
62
66
|
|
|
63
67
|
# `log` is variadic (1 or 2 args), so it cannot live in the
|
|
64
68
|
# fixed-arity sets above.
|
|
@@ -70,10 +74,6 @@ module Rigor
|
|
|
70
74
|
nil
|
|
71
75
|
end
|
|
72
76
|
|
|
73
|
-
def dispatch_target?(receiver)
|
|
74
|
-
receiver.is_a?(Type::Singleton) && receiver.class_name == "Math"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
77
|
# Unwraps a numeric `Constant` argument to its Ruby value.
|
|
78
78
|
# Returns nil for any non-`Constant` or non-`Numeric` carrier.
|
|
79
79
|
def numeric_constant(arg)
|
|
@@ -51,7 +51,10 @@ module Rigor
|
|
|
51
51
|
# @param args [Array<Rigor::Type>] caller's argument
|
|
52
52
|
# types in order. Only the single-argument case
|
|
53
53
|
# matches; other arities decline.
|
|
54
|
-
def
|
|
54
|
+
def try_dispatch(context)
|
|
55
|
+
receiver = context.receiver
|
|
56
|
+
method_name = context.method_name
|
|
57
|
+
args = context.args
|
|
55
58
|
return nil unless method_name == :method
|
|
56
59
|
return nil if args.size != 1
|
|
57
60
|
|
|
@@ -72,7 +75,9 @@ module Rigor
|
|
|
72
75
|
# well-defined type (the gradual-safety net mirrors
|
|
73
76
|
# the engine's "BoundMethod erases to `Method`,
|
|
74
77
|
# `Method#call: (*untyped) -> untyped`" RBS fallback).
|
|
75
|
-
def try_backward(
|
|
78
|
+
def try_backward(context)
|
|
79
|
+
receiver = context.receiver
|
|
80
|
+
method_name = context.method_name
|
|
76
81
|
return nil unless receiver.is_a?(Type::BoundMethod)
|
|
77
82
|
return nil unless backward_method?(method_name)
|
|
78
83
|
|
|
@@ -94,11 +99,11 @@ module Rigor
|
|
|
94
99
|
MethodDispatcher.dispatch(
|
|
95
100
|
receiver_type: receiver.receiver_type,
|
|
96
101
|
method_name: receiver.method_name,
|
|
97
|
-
arg_types: args,
|
|
98
|
-
block_type: block_type,
|
|
99
|
-
environment: environment,
|
|
100
|
-
call_node: call_node,
|
|
101
|
-
scope: scope
|
|
102
|
+
arg_types: context.args,
|
|
103
|
+
block_type: context.block_type,
|
|
104
|
+
environment: context.environment,
|
|
105
|
+
call_node: context.call_node,
|
|
106
|
+
scope: context.scope
|
|
102
107
|
) || Type::Combinator.untyped
|
|
103
108
|
end
|
|
104
109
|
# `Method#call` / `Method#()` and `Method#[]` are the
|
|
@@ -32,7 +32,16 @@ module Rigor
|
|
|
32
32
|
# matches, accept the first arity-and-gradual-accept match
|
|
33
33
|
# (the v0.1.1 behaviour). Alias / Interface / Intersection
|
|
34
34
|
# params still reach this pass, so call sites whose only
|
|
35
|
-
# candidate IS an alias-typed overload keep working.
|
|
35
|
+
# candidate IS an alias-typed overload keep working. One
|
|
36
|
+
# exclusion: an `untyped` argument does NOT gradually match
|
|
37
|
+
# a value-pinning param (`nil` / literal types — carriers
|
|
38
|
+
# that admit only specific values). Those overloads carry
|
|
39
|
+
# value-precise returns (`Kernel#Array: (nil) -> []`,
|
|
40
|
+
# `Regexp#=~: (nil) -> nil`) that would otherwise win purely
|
|
41
|
+
# by list position and inject false constants into the flow;
|
|
42
|
+
# they remain selectable when the argument PROVES the value
|
|
43
|
+
# (strict pass) or when no other overload matches (step 4's
|
|
44
|
+
# fallback picks the first overload regardless).
|
|
36
45
|
# 4. If no overload matches at all, fall back to
|
|
37
46
|
# `method_types.first` so existing call sites keep their
|
|
38
47
|
# phase 1 / 2b behavior. This preserves the fail-soft
|
|
@@ -393,9 +402,32 @@ module Rigor
|
|
|
393
402
|
instance_type: instance_type,
|
|
394
403
|
type_vars: type_vars
|
|
395
404
|
)
|
|
405
|
+
# An `untyped` arg gradually accepts against every param,
|
|
406
|
+
# so a value-pinning param would be "matched" with zero
|
|
407
|
+
# evidence and its value-precise return (`(nil) -> []`)
|
|
408
|
+
# would beat broader overloads purely by list position.
|
|
409
|
+
# Decline the pair; only the strict pass (where the arg
|
|
410
|
+
# proves the value) or the final first-overload fallback
|
|
411
|
+
# may select such an overload. (Pass 1 already skips
|
|
412
|
+
# untyped args entirely, so this only engages pass 2.)
|
|
413
|
+
return false if untyped_arg?(arg) && value_pinning?(param_type)
|
|
414
|
+
|
|
396
415
|
result = param_type.accepts(arg, mode: :gradual)
|
|
397
416
|
result.yes? || result.maybe?
|
|
398
417
|
end
|
|
418
|
+
|
|
419
|
+
# A type that admits only specific VALUES rather than a
|
|
420
|
+
# class of values: a `Constant` carrier (RBS `nil` and
|
|
421
|
+
# literal types both translate to one) or a union made up
|
|
422
|
+
# entirely of them (`true | false`, `1 | 2`, `nil?`-style
|
|
423
|
+
# optionals of literals).
|
|
424
|
+
def value_pinning?(type)
|
|
425
|
+
case type
|
|
426
|
+
when Type::Constant then true
|
|
427
|
+
when Type::Union then type.members.all? { |member| value_pinning?(member) }
|
|
428
|
+
else false
|
|
429
|
+
end
|
|
430
|
+
end
|
|
399
431
|
end
|
|
400
432
|
end
|
|
401
433
|
end
|
|
@@ -118,20 +118,20 @@ module Rigor
|
|
|
118
118
|
# `Dynamic[Top]`, which is the false-positive-safe default
|
|
119
119
|
# for the open hierarchies (`< ActionController::Base`, …)
|
|
120
120
|
# the allow-list deliberately excludes.
|
|
121
|
-
def try_dispatch(
|
|
122
|
-
|
|
121
|
+
def try_dispatch(context)
|
|
122
|
+
environment = context.environment
|
|
123
123
|
return nil if environment.nil?
|
|
124
124
|
return nil unless environment.rbs_loader
|
|
125
125
|
|
|
126
126
|
dispatch_for(
|
|
127
|
-
receiver: receiver,
|
|
128
|
-
method_name: method_name,
|
|
129
|
-
args: args,
|
|
127
|
+
receiver: context.receiver,
|
|
128
|
+
method_name: context.method_name,
|
|
129
|
+
args: context.args,
|
|
130
130
|
environment: environment,
|
|
131
|
-
block_type: block_type,
|
|
132
|
-
self_type_override: self_type_override,
|
|
133
|
-
public_only: public_only,
|
|
134
|
-
scope: scope
|
|
131
|
+
block_type: context.block_type,
|
|
132
|
+
self_type_override: context.self_type_override,
|
|
133
|
+
public_only: context.public_only,
|
|
134
|
+
scope: context.scope
|
|
135
135
|
)
|
|
136
136
|
end
|
|
137
137
|
|
|
@@ -157,14 +157,15 @@ module Rigor
|
|
|
157
157
|
# block" from "the block is untyped"; the binder treats both
|
|
158
158
|
# the same way (every parameter defaults to `Dynamic[Top]`).
|
|
159
159
|
# @return [Array<Rigor::Type>] positional block parameter types.
|
|
160
|
-
def block_param_types(
|
|
160
|
+
def block_param_types(context)
|
|
161
|
+
environment = context.environment
|
|
161
162
|
return [] if environment.nil?
|
|
162
163
|
return [] unless environment.rbs_loader
|
|
163
164
|
|
|
164
165
|
probe_block_param_types(
|
|
165
|
-
receiver: receiver,
|
|
166
|
-
method_name: method_name,
|
|
167
|
-
args: args,
|
|
166
|
+
receiver: context.receiver,
|
|
167
|
+
method_name: context.method_name,
|
|
168
|
+
args: context.args,
|
|
168
169
|
environment: environment
|
|
169
170
|
)
|
|
170
171
|
end
|
|
@@ -248,6 +249,15 @@ module Rigor
|
|
|
248
249
|
["Array", :instance, tuple_type_args(receiver)]
|
|
249
250
|
when Type::HashShape
|
|
250
251
|
["Hash", :instance, hash_shape_type_args(receiver)]
|
|
252
|
+
when Type::DataInstance
|
|
253
|
+
# ADR-48 — project a member-instance carrier to its tagging
|
|
254
|
+
# class (or the `Data` supertype) so non-member calls
|
|
255
|
+
# (`inspect`, `==`, `frozen?`, ...) resolve through RBS
|
|
256
|
+
# rather than mis-firing undefined-method. Member reads were
|
|
257
|
+
# already folded by DataFolding above this tier.
|
|
258
|
+
[receiver.class_name || "Data", :instance, []]
|
|
259
|
+
when Type::DataClass
|
|
260
|
+
[receiver.class_name || "Data", :singleton, []]
|
|
251
261
|
when Type::BoundMethod
|
|
252
262
|
# `BoundMethod` is a precision-bearing alias for
|
|
253
263
|
# `Nominal[Method]`: it carries the
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -30,26 +31,25 @@ module Rigor
|
|
|
30
31
|
module_function
|
|
31
32
|
|
|
32
33
|
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
33
|
-
def try_dispatch(
|
|
34
|
-
|
|
34
|
+
def try_dispatch(context)
|
|
35
|
+
receiver = context.receiver
|
|
36
|
+
method_name = context.method_name
|
|
37
|
+
args = context.args
|
|
38
|
+
return nil unless SingletonFolding.receiver?(receiver, "Regexp")
|
|
35
39
|
return fold_escape(args) if REGEXP_ESCAPE_METHODS.include?(method_name)
|
|
36
40
|
return fold_new(args) if method_name == :new
|
|
37
41
|
|
|
38
42
|
nil
|
|
39
43
|
end
|
|
40
44
|
|
|
41
|
-
def dispatch_target?(receiver)
|
|
42
|
-
receiver.is_a?(Type::Singleton) && receiver.class_name == "Regexp"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
45
|
# `Regexp.escape(str)` / `.quote(str)` — one String arg.
|
|
46
46
|
def fold_escape(args)
|
|
47
47
|
return nil unless args.size == 1
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
return nil
|
|
49
|
+
str = SingletonFolding.constant_string(args.first)
|
|
50
|
+
return nil if str.nil?
|
|
51
51
|
|
|
52
|
-
Type::Combinator.constant_of(Regexp.escape(
|
|
52
|
+
Type::Combinator.constant_of(Regexp.escape(str))
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
# `Regexp.new(pattern)` / `Regexp.new(pattern, opts)` — constructs
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -30,8 +31,11 @@ module Rigor
|
|
|
30
31
|
module_function
|
|
31
32
|
|
|
32
33
|
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
33
|
-
def try_dispatch(
|
|
34
|
-
|
|
34
|
+
def try_dispatch(context)
|
|
35
|
+
receiver = context.receiver
|
|
36
|
+
method_name = context.method_name
|
|
37
|
+
args = context.args
|
|
38
|
+
return nil unless SingletonFolding.receiver?(receiver, "Set")
|
|
35
39
|
|
|
36
40
|
case method_name
|
|
37
41
|
when :[] then fold_bracket(args)
|
|
@@ -39,10 +43,6 @@ module Rigor
|
|
|
39
43
|
end
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
def dispatch_target?(receiver)
|
|
43
|
-
receiver.is_a?(Type::Singleton) && receiver.class_name == "Set"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
46
|
# `Set["a", "b", "c"]` — all positional args must be Constant.
|
|
47
47
|
def fold_bracket(args)
|
|
48
48
|
values = args.map do |a|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "call_context"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -91,7 +92,13 @@ module Rigor
|
|
|
91
92
|
values_at: :tuple_values_at,
|
|
92
93
|
:+ => :tuple_concat,
|
|
93
94
|
compact: :tuple_compact,
|
|
94
|
-
take: :tuple_take
|
|
95
|
+
take: :tuple_take,
|
|
96
|
+
drop: :tuple_drop,
|
|
97
|
+
rotate: :tuple_rotate,
|
|
98
|
+
uniq: :tuple_uniq,
|
|
99
|
+
index: :tuple_find_index,
|
|
100
|
+
find_index: :tuple_find_index,
|
|
101
|
+
rindex: :tuple_rindex
|
|
95
102
|
}.freeze
|
|
96
103
|
|
|
97
104
|
HASH_SHAPE_HANDLERS = {
|
|
@@ -122,6 +129,7 @@ module Rigor
|
|
|
122
129
|
values_at: :hash_values_at,
|
|
123
130
|
fetch_values: :hash_fetch_values,
|
|
124
131
|
assoc: :hash_assoc,
|
|
132
|
+
rassoc: :hash_rassoc,
|
|
125
133
|
key: :hash_key,
|
|
126
134
|
has_key?: :hash_has_key?,
|
|
127
135
|
key?: :hash_has_key?,
|
|
@@ -167,7 +175,10 @@ module Rigor
|
|
|
167
175
|
}.freeze
|
|
168
176
|
private_constant :TO_S_BASE_REFINEMENTS
|
|
169
177
|
|
|
170
|
-
def try_dispatch(
|
|
178
|
+
def try_dispatch(context)
|
|
179
|
+
receiver = context.receiver
|
|
180
|
+
method_name = context.method_name
|
|
181
|
+
args = context.args
|
|
171
182
|
args ||= []
|
|
172
183
|
handler = RECEIVER_HANDLERS[receiver.class]
|
|
173
184
|
return nil unless handler
|
|
@@ -545,7 +556,9 @@ module Rigor
|
|
|
545
556
|
# falls through to the next dispatcher tier.
|
|
546
557
|
def dispatch_intersection(intersection, method_name, args)
|
|
547
558
|
results = intersection.members.filter_map do |member|
|
|
548
|
-
ShapeDispatch.try_dispatch(
|
|
559
|
+
ShapeDispatch.try_dispatch(
|
|
560
|
+
CallContext.build(receiver: member, method_name: method_name, args: args)
|
|
561
|
+
)
|
|
549
562
|
end
|
|
550
563
|
|
|
551
564
|
case results.size
|
|
@@ -580,6 +593,10 @@ module Rigor
|
|
|
580
593
|
Type::Combinator.integer_range(min, max)
|
|
581
594
|
end
|
|
582
595
|
|
|
596
|
+
# `first` (no arg) → the first element (or `Constant[nil]` when
|
|
597
|
+
# empty). The `first(n)` arg-form is deliberately left to RBS
|
|
598
|
+
# overload selection (see the overload-selection specs) — folding
|
|
599
|
+
# it here would change that documented `Array[Elem]` contract.
|
|
583
600
|
def tuple_first(tuple, _method_name, args)
|
|
584
601
|
return nil unless args.empty?
|
|
585
602
|
return Type::Combinator.constant_of(nil) if tuple.elements.empty?
|
|
@@ -832,21 +849,95 @@ module Rigor
|
|
|
832
849
|
Type::Combinator.tuple_of(*kept)
|
|
833
850
|
end
|
|
834
851
|
|
|
852
|
+
# `uniq` (no block) → `Tuple` of the first occurrence of each
|
|
853
|
+
# distinct value. Folds only when every element is a `Constant`
|
|
854
|
+
# so value equality is decidable; the block form defers.
|
|
855
|
+
def tuple_uniq(tuple, _method_name, args)
|
|
856
|
+
return nil unless args.empty?
|
|
857
|
+
return nil unless tuple.elements.all?(Type::Constant)
|
|
858
|
+
|
|
859
|
+
seen = []
|
|
860
|
+
kept = tuple.elements.each_with_object([]) do |element, acc|
|
|
861
|
+
next if seen.include?(element.value)
|
|
862
|
+
|
|
863
|
+
seen << element.value
|
|
864
|
+
acc << element
|
|
865
|
+
end
|
|
866
|
+
Type::Combinator.tuple_of(*kept)
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
# `index(obj)` / `find_index(obj)` → `Constant[Integer]` of the
|
|
870
|
+
# first element equal to `obj`, `Constant[nil]` when none match.
|
|
871
|
+
# Folds only for the argument form (the block form defers) when
|
|
872
|
+
# every element AND the argument are `Constant` (decidable
|
|
873
|
+
# equality).
|
|
874
|
+
def tuple_find_index(tuple, _method_name, args)
|
|
875
|
+
constant_index(tuple, args) { |elements, value| elements.index { |e| e.value == value } }
|
|
876
|
+
end
|
|
877
|
+
|
|
878
|
+
# `rindex(obj)` → the LAST matching index, same decidability gate.
|
|
879
|
+
def tuple_rindex(tuple, _method_name, args)
|
|
880
|
+
constant_index(tuple, args) { |elements, value| elements.rindex { |e| e.value == value } }
|
|
881
|
+
end
|
|
882
|
+
|
|
883
|
+
def constant_index(tuple, args)
|
|
884
|
+
return nil unless args.size == 1
|
|
885
|
+
|
|
886
|
+
needle = args.first
|
|
887
|
+
return nil unless needle.is_a?(Type::Constant)
|
|
888
|
+
return nil unless tuple.elements.all?(Type::Constant)
|
|
889
|
+
|
|
890
|
+
Type::Combinator.constant_of(yield(tuple.elements, needle.value))
|
|
891
|
+
end
|
|
892
|
+
|
|
835
893
|
# `tuple.take(n)` — returns the first n elements as a
|
|
836
894
|
# new Tuple. The argument must be a `Constant[Integer]`.
|
|
837
895
|
# n <= 0 returns the empty Tuple; n >= size returns the
|
|
838
896
|
# full receiver.
|
|
839
897
|
def tuple_take(tuple, _method_name, args)
|
|
898
|
+
n = non_negative_count_arg(args)
|
|
899
|
+
return nil if n.nil?
|
|
900
|
+
|
|
901
|
+
Type::Combinator.tuple_of(*tuple.elements.take(n))
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
# `drop(n)` → `Tuple` of every element after the first `n`
|
|
905
|
+
# (mirror of `take`; `n >= size` → empty Tuple).
|
|
906
|
+
def tuple_drop(tuple, _method_name, args)
|
|
907
|
+
n = non_negative_count_arg(args)
|
|
908
|
+
return nil if n.nil?
|
|
909
|
+
|
|
910
|
+
Type::Combinator.tuple_of(*tuple.elements.drop(n))
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
# `rotate` (no arg → 1) / `rotate(n)` → `Tuple` of the elements
|
|
914
|
+
# cyclically shifted left by `n` (`Array#rotate` handles negative
|
|
915
|
+
# and out-of-range `n` by modulo, so any Integer arg folds).
|
|
916
|
+
def tuple_rotate(tuple, _method_name, args)
|
|
917
|
+
count =
|
|
918
|
+
if args.empty?
|
|
919
|
+
1
|
|
920
|
+
else
|
|
921
|
+
arg = args.size == 1 ? args.first : nil
|
|
922
|
+
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
923
|
+
|
|
924
|
+
arg.value
|
|
925
|
+
end
|
|
926
|
+
Type::Combinator.tuple_of(*tuple.elements.rotate(count))
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# Unwraps a single non-negative `Constant[Integer]` count argument
|
|
930
|
+
# (the `take` / `drop` / `first(n)` / `last(n)` shape). Returns the
|
|
931
|
+
# Integer, or nil to defer (wrong arity, non-constant, non-Integer,
|
|
932
|
+
# or negative — `Array#take`/`#drop` raise on negative counts).
|
|
933
|
+
def non_negative_count_arg(args)
|
|
840
934
|
return nil unless args.size == 1
|
|
841
935
|
|
|
842
936
|
arg = args.first
|
|
843
|
-
return nil unless arg.is_a?(Type::Constant)
|
|
844
|
-
return nil
|
|
845
|
-
|
|
846
|
-
n = arg.value
|
|
847
|
-
return Type::Combinator.tuple_of if n <= 0
|
|
937
|
+
return nil unless arg.is_a?(Type::Constant) && arg.value.is_a?(Integer)
|
|
938
|
+
return nil if arg.value.negative?
|
|
848
939
|
|
|
849
|
-
|
|
940
|
+
arg.value
|
|
850
941
|
end
|
|
851
942
|
|
|
852
943
|
# Returns `true` / `false` if every element's truthiness
|
|
@@ -1083,6 +1174,26 @@ module Rigor
|
|
|
1083
1174
|
Type::Combinator.tuple_of(Type::Combinator.constant_of(key), shape.pairs[key])
|
|
1084
1175
|
end
|
|
1085
1176
|
|
|
1177
|
+
# `shape.rassoc(value)` — reverse of `assoc`: returns
|
|
1178
|
+
# `Tuple[Constant[k], V]` for the first key whose VALUE equals
|
|
1179
|
+
# the argument, `Constant[nil]` when none match. Folds when every
|
|
1180
|
+
# value is a `Constant` so equality is decidable (mirrors
|
|
1181
|
+
# `hash_key`, which returns only the key).
|
|
1182
|
+
def hash_rassoc(shape, _method_name, args)
|
|
1183
|
+
return nil unless args.size == 1
|
|
1184
|
+
return nil unless shape.closed?
|
|
1185
|
+
return nil unless shape.optional_keys.empty?
|
|
1186
|
+
return nil unless shape.pairs.values.all?(Type::Constant)
|
|
1187
|
+
|
|
1188
|
+
arg = args.first
|
|
1189
|
+
return nil unless arg.is_a?(Type::Constant)
|
|
1190
|
+
|
|
1191
|
+
pair = shape.pairs.find { |_k, v| v.value == arg.value }
|
|
1192
|
+
return Type::Combinator.constant_of(nil) if pair.nil?
|
|
1193
|
+
|
|
1194
|
+
Type::Combinator.tuple_of(Type::Combinator.constant_of(pair.first), pair.last)
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1086
1197
|
# `shape.key(value)` — reverse lookup. Folds when every
|
|
1087
1198
|
# value is a `Constant` so equality is decidable: returns
|
|
1088
1199
|
# `Constant[k]` for the first matching key, `Constant[nil]`
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "shellwords"
|
|
4
4
|
require_relative "../../type"
|
|
5
|
+
require_relative "singleton_folding"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -64,8 +65,11 @@ module Rigor
|
|
|
64
65
|
module_function
|
|
65
66
|
|
|
66
67
|
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
67
|
-
def try_dispatch(
|
|
68
|
-
|
|
68
|
+
def try_dispatch(context)
|
|
69
|
+
receiver = context.receiver
|
|
70
|
+
method_name = context.method_name
|
|
71
|
+
args = context.args
|
|
72
|
+
return nil unless SingletonFolding.receiver?(receiver, "Shellwords")
|
|
69
73
|
return nil unless SHELLWORDS_ALL_METHODS.include?(method_name)
|
|
70
74
|
|
|
71
75
|
if SHELLWORDS_ESCAPE_METHODS.include?(method_name)
|
|
@@ -77,18 +81,14 @@ module Rigor
|
|
|
77
81
|
end
|
|
78
82
|
end
|
|
79
83
|
|
|
80
|
-
def dispatch_target?(receiver)
|
|
81
|
-
receiver.is_a?(Type::Singleton) && receiver.class_name == "Shellwords"
|
|
82
|
-
end
|
|
83
|
-
|
|
84
84
|
# `Shellwords.escape(str)` / `.shellescape(str)` — one String arg.
|
|
85
85
|
def fold_escape(args)
|
|
86
86
|
return nil unless args.size == 1
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
return nil
|
|
88
|
+
str = SingletonFolding.constant_string(args.first)
|
|
89
|
+
return nil if str.nil?
|
|
90
90
|
|
|
91
|
-
Type::Combinator.constant_of(Shellwords.escape(
|
|
91
|
+
Type::Combinator.constant_of(Shellwords.escape(str))
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
# `Shellwords.split(line)` / `.shellsplit` / `.shellwords` —
|
|
@@ -96,10 +96,10 @@ module Rigor
|
|
|
96
96
|
def fold_split(args)
|
|
97
97
|
return nil unless args.size == 1
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
return nil
|
|
99
|
+
str = SingletonFolding.constant_string(args.first)
|
|
100
|
+
return nil if str.nil?
|
|
101
101
|
|
|
102
|
-
tokens = Shellwords.split(
|
|
102
|
+
tokens = Shellwords.split(str)
|
|
103
103
|
return nil if tokens.size > SHELLWORDS_SPLIT_LIMIT
|
|
104
104
|
|
|
105
105
|
Type::Combinator.tuple_of(*tokens.map { |t| Type::Combinator.constant_of(t) })
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# Shared helpers for the per-singleton folding tiers (CGI, URI,
|
|
9
|
+
# Shellwords, Math, Time, Regexp, Set, File, …).
|
|
10
|
+
#
|
|
11
|
+
# Each of those tiers folds a pure module-/class-function call on
|
|
12
|
+
# `Constant` receivers, and every one of them opens with the same
|
|
13
|
+
# two questions:
|
|
14
|
+
#
|
|
15
|
+
# 1. "Is the receiver the `Singleton[X]` I handle?" — a one-line
|
|
16
|
+
# `is_a?(Type::Singleton) && class_name == "X"` that used to be
|
|
17
|
+
# copied verbatim into nine modules.
|
|
18
|
+
# 2. "Is this argument a foldable `Constant[String]`?" — the
|
|
19
|
+
# `is_a?(Type::Constant) && value.is_a?(String)` unwrap the
|
|
20
|
+
# string-folding tiers (CGI / URI / Shellwords / Regexp) gate
|
|
21
|
+
# on before evaluating the literal.
|
|
22
|
+
#
|
|
23
|
+
# Centralising both here keeps the receiver-shape and
|
|
24
|
+
# constant-unwrap knowledge in one place: if `Type::Singleton` or
|
|
25
|
+
# `Type::Constant` ever changes shape, there is a single edit
|
|
26
|
+
# rather than nine. The per-tier fold bodies stay where they are —
|
|
27
|
+
# only the shared gate moves.
|
|
28
|
+
module SingletonFolding
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
# True when `receiver` is the class/module singleton named
|
|
32
|
+
# `class_name` (e.g. `Singleton[Math]`).
|
|
33
|
+
def receiver?(receiver, class_name)
|
|
34
|
+
receiver.is_a?(Type::Singleton) && receiver.class_name == class_name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The `String` value carried by a `Constant[String]` argument, or
|
|
38
|
+
# `nil` for any other carrier. Callers fold when this is non-nil
|
|
39
|
+
# and decline (return nil, deferring to the RBS tier) otherwise.
|
|
40
|
+
def constant_string(arg)
|
|
41
|
+
return nil unless arg.is_a?(Type::Constant)
|
|
42
|
+
|
|
43
|
+
value = arg.value
|
|
44
|
+
value.is_a?(String) ? value : nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
|
+
require_relative "singleton_folding"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
module Inference
|
|
@@ -33,8 +34,11 @@ module Rigor
|
|
|
33
34
|
module_function
|
|
34
35
|
|
|
35
36
|
# @return [Rigor::Type, nil] folded result, or nil to defer.
|
|
36
|
-
def try_dispatch(
|
|
37
|
-
|
|
37
|
+
def try_dispatch(context)
|
|
38
|
+
receiver = context.receiver
|
|
39
|
+
method_name = context.method_name
|
|
40
|
+
args = context.args
|
|
41
|
+
return nil unless SingletonFolding.receiver?(receiver, "Time")
|
|
38
42
|
return nil unless TIME_UTC_METHODS.include?(method_name)
|
|
39
43
|
return nil unless args.size.between?(1, MAX_TIME_ARITY)
|
|
40
44
|
return nil unless args.all?(Type::Constant)
|
|
@@ -46,10 +50,6 @@ module Rigor
|
|
|
46
50
|
rescue StandardError
|
|
47
51
|
nil
|
|
48
52
|
end
|
|
49
|
-
|
|
50
|
-
def dispatch_target?(receiver)
|
|
51
|
-
receiver.is_a?(Type::Singleton) && receiver.class_name == "Time"
|
|
52
|
-
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|