rigortype 0.1.18 → 0.2.0
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 +159 -224
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +25 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +32 -23
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +54 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +151 -23
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +24 -15
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +9 -3
- data/lib/rigor/analysis/check_rules.rb +756 -132
- data/lib/rigor/analysis/dependency_source_inference/index.rb +4 -7
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -18
- data/lib/rigor/analysis/dependency_source_inference.rb +3 -12
- data/lib/rigor/analysis/diagnostic.rb +8 -0
- data/lib/rigor/analysis/fact_store.rb +5 -4
- data/lib/rigor/analysis/rule_catalog.rb +153 -6
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +19 -18
- data/lib/rigor/analysis/runner/project_pre_passes.rb +13 -9
- data/lib/rigor/analysis/runner.rb +75 -27
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +3 -4
- data/lib/rigor/analysis/worker_session.rb +31 -25
- data/lib/rigor/bleeding_edge.rb +123 -0
- data/lib/rigor/builtins/predefined_constant_refinements.rb +151 -0
- data/lib/rigor/cache/descriptor.rb +86 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +5 -3
- data/lib/rigor/cli/annotate_command.rb +122 -16
- data/lib/rigor/cli/baseline_command.rb +4 -3
- data/lib/rigor/cli/check_command.rb +118 -16
- data/lib/rigor/cli/coverage_command.rb +148 -16
- data/lib/rigor/cli/coverage_scan.rb +57 -0
- data/lib/rigor/cli/explain_command.rb +2 -0
- data/lib/rigor/cli/lsp_command.rb +3 -7
- data/lib/rigor/cli/mutation_protection_renderer.rb +63 -0
- data/lib/rigor/cli/mutation_protection_report.rb +73 -0
- data/lib/rigor/cli/options.rb +9 -0
- data/lib/rigor/cli/plugins_command.rb +4 -5
- data/lib/rigor/cli/plugins_renderer.rb +0 -2
- data/lib/rigor/cli/protection_renderer.rb +63 -0
- data/lib/rigor/cli/protection_report.rb +68 -0
- data/lib/rigor/cli/show_bleedingedge_command.rb +114 -0
- data/lib/rigor/cli/sig_gen_command.rb +2 -1
- data/lib/rigor/cli/trace_command.rb +2 -1
- data/lib/rigor/cli/triage_command.rb +8 -4
- data/lib/rigor/cli/triage_renderer.rb +15 -1
- data/lib/rigor/cli/type_of_command.rb +1 -1
- data/lib/rigor/cli/type_scan_command.rb +2 -1
- data/lib/rigor/cli.rb +12 -3
- data/lib/rigor/configuration/dependencies.rb +2 -4
- data/lib/rigor/configuration/severity_profile.rb +13 -1
- data/lib/rigor/configuration.rb +100 -6
- data/lib/rigor/environment/bundle_sig_discovery.rb +61 -13
- data/lib/rigor/environment/class_registry.rb +4 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +43 -0
- data/lib/rigor/environment/lockfile_resolver.rb +1 -1
- data/lib/rigor/environment/rbs_collection_discovery.rb +1 -2
- data/lib/rigor/environment/rbs_coverage_report.rb +2 -1
- data/lib/rigor/environment/rbs_loader.rb +74 -5
- data/lib/rigor/environment.rb +17 -7
- data/lib/rigor/flow_contribution/fact.rb +1 -1
- data/lib/rigor/flow_contribution.rb +3 -5
- data/lib/rigor/inference/acceptance.rb +17 -9
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +89 -0
- data/lib/rigor/inference/budget_trace.rb +29 -2
- data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/method_catalog.rb +19 -0
- data/lib/rigor/inference/builtins/string_catalog.rb +9 -1
- data/lib/rigor/inference/expression_typer.rb +1072 -71
- data/lib/rigor/inference/hkt_body.rb +8 -11
- data/lib/rigor/inference/hkt_body_parser.rb +10 -12
- data/lib/rigor/inference/hkt_registry.rb +10 -11
- data/lib/rigor/inference/macro_block_self_type.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +60 -0
- data/lib/rigor/inference/method_dispatcher/call_context.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +210 -35
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +9 -73
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -7
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +10 -16
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +25 -13
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +93 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +1 -3
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +24 -22
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +281 -0
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +71 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +237 -24
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +303 -0
- data/lib/rigor/inference/method_dispatcher.rb +112 -49
- data/lib/rigor/inference/method_parameter_binder.rb +56 -2
- data/lib/rigor/inference/multi_target_binder.rb +46 -3
- data/lib/rigor/inference/mutation_widening.rb +147 -11
- data/lib/rigor/inference/narrowing.rb +284 -53
- data/lib/rigor/inference/parameter_inference_collector.rb +367 -0
- data/lib/rigor/inference/project_patched_methods.rb +4 -7
- data/lib/rigor/inference/project_patched_scanner.rb +2 -13
- data/lib/rigor/inference/protection_scanner.rb +86 -0
- data/lib/rigor/inference/scope_indexer.rb +821 -76
- data/lib/rigor/inference/statement_evaluator.rb +1179 -102
- data/lib/rigor/inference/struct_fold_safety.rb +181 -0
- data/lib/rigor/inference/synthetic_method.rb +7 -7
- data/lib/rigor/inference/synthetic_method_scanner.rb +1 -1
- data/lib/rigor/language_server/completion_provider.rb +6 -12
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +3 -3
- data/lib/rigor/language_server/hover_provider.rb +2 -3
- data/lib/rigor/language_server/hover_renderer.rb +2 -11
- data/lib/rigor/language_server/server.rb +9 -17
- data/lib/rigor/language_server.rb +4 -5
- data/lib/rigor/plugin/base.rb +245 -87
- data/lib/rigor/plugin/macro/block_as_method.rb +25 -25
- data/lib/rigor/plugin/macro/heredoc_template.rb +4 -7
- data/lib/rigor/plugin/macro/nested_class_template.rb +9 -7
- data/lib/rigor/plugin/macro/trait_registry.rb +3 -6
- data/lib/rigor/plugin/macro.rb +6 -8
- data/lib/rigor/plugin/manifest.rb +49 -90
- data/lib/rigor/plugin/node_rule_walk.rb +59 -14
- data/lib/rigor/plugin/registry.rb +18 -18
- data/lib/rigor/plugin/type_node_resolver.rb +6 -8
- data/lib/rigor/protection/mutation_scanner.rb +120 -0
- data/lib/rigor/protection/mutator.rb +246 -0
- data/lib/rigor/rbs_extended.rb +24 -36
- data/lib/rigor/reflection.rb +4 -7
- data/lib/rigor/scope/discovery_index.rb +16 -2
- data/lib/rigor/scope.rb +185 -16
- data/lib/rigor/sig_gen/generator.rb +8 -0
- data/lib/rigor/sig_gen/observed_call.rb +3 -3
- data/lib/rigor/sig_gen/writer.rb +40 -2
- data/lib/rigor/source/constant_path.rb +62 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/triage/catalogue.rb +4 -19
- data/lib/rigor/triage.rb +69 -1
- data/lib/rigor/type/bound_method.rb +2 -11
- data/lib/rigor/type/combinator.rb +45 -3
- data/lib/rigor/type/constant.rb +2 -11
- data/lib/rigor/type/data_class.rb +2 -11
- data/lib/rigor/type/data_instance.rb +2 -11
- data/lib/rigor/type/hash_shape.rb +2 -11
- data/lib/rigor/type/integer_range.rb +2 -11
- data/lib/rigor/type/intersection.rb +2 -11
- data/lib/rigor/type/nominal.rb +2 -11
- data/lib/rigor/type/plain_lattice.rb +37 -0
- data/lib/rigor/type/refined.rb +72 -13
- data/lib/rigor/type/singleton.rb +2 -11
- data/lib/rigor/type/struct_class.rb +75 -0
- data/lib/rigor/type/struct_instance.rb +93 -0
- data/lib/rigor/type/tuple.rb +5 -15
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +3 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +16 -32
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +5 -13
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -32
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +11 -17
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +34 -100
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +3 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +13 -30
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +26 -27
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +5 -7
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +9 -8
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +9 -11
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +8 -9
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +13 -12
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +3 -4
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +8 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +9 -11
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +7 -8
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +18 -49
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +12 -13
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +15 -23
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +4 -4
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +3 -3
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +10 -21
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +2 -4
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +27 -11
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +22 -35
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +4 -6
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +12 -18
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +16 -23
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +0 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -4
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +21 -27
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +0 -1
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +3 -23
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +5 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +8 -21
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +2 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +7 -11
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +6 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +5 -15
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +52 -40
- data/sig/rigor/analysis/fact_store.rbs +3 -0
- data/sig/rigor/inference/builtins/method_catalog.rbs +1 -1
- data/sig/rigor/plugin/base.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +1 -2
- data/sig/rigor/scope.rbs +18 -1
- data/sig/rigor/type.rbs +37 -1
- data/sig/rigor.rbs +1 -1
- data/skills/rigor-baseline-reduce/references/01-classify.md +27 -0
- data/skills/rigor-plugin-author/SKILL.md +6 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +22 -17
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +18 -1
- metadata +25 -2
- data/lib/rigor/plugin/macro/external_file.rb +0 -143
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "singleton_folding"
|
|
5
|
+
require_relative "member_shape_projection"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
module Inference
|
|
@@ -30,6 +31,10 @@ module Rigor
|
|
|
30
31
|
module DataFolding
|
|
31
32
|
module_function
|
|
32
33
|
|
|
34
|
+
# The `[]` / `to_h` / `deconstruct` / `members` / `with` projections
|
|
35
|
+
# and the reader-redefinition guard are shared with {StructFolding}.
|
|
36
|
+
extend MemberShapeProjection
|
|
37
|
+
|
|
33
38
|
# @return [Rigor::Type, nil] the folded result, or nil to defer.
|
|
34
39
|
def try_dispatch(context)
|
|
35
40
|
receiver = context.receiver
|
|
@@ -165,81 +170,12 @@ module Rigor
|
|
|
165
170
|
when :deconstruct then instance_deconstruct(instance)
|
|
166
171
|
when :deconstruct_keys then instance_deconstruct_keys(instance, args)
|
|
167
172
|
when :members then instance_members(instance)
|
|
168
|
-
when :with
|
|
173
|
+
when :with
|
|
174
|
+
instance_with(instance, args) do |members, class_name|
|
|
175
|
+
Type::Combinator.data_instance_of(members: members, class_name: class_name)
|
|
176
|
+
end
|
|
169
177
|
end
|
|
170
178
|
end
|
|
171
|
-
|
|
172
|
-
# A `Data.define` class body (the `class Point < Data.define(:x);
|
|
173
|
-
# def x; …; end; end` subclass body, or a `Const = Data.define(:x) do
|
|
174
|
-
# def x; …; end; end` block) can redefine a member's synthesised
|
|
175
|
-
# reader. When it does, `inst.x` runs that `def`, NOT the member, so
|
|
176
|
-
# folding the read to the member type would be unsound (a downstream
|
|
177
|
-
# FP). Both named forms register the override as a real `def` node
|
|
178
|
-
# under the class name, so an entry in the project def-node table is
|
|
179
|
-
# the discriminator (the synthesised reader has no def node). The
|
|
180
|
-
# value accessors `[]` / `to_h` / `deconstruct` bypass the reader and
|
|
181
|
-
# stay foldable, so this gate is on the bare member read only.
|
|
182
|
-
def reader_overridden?(instance, method_name, scope)
|
|
183
|
-
class_name = instance.class_name
|
|
184
|
-
return false if class_name.nil? || scope.nil?
|
|
185
|
-
|
|
186
|
-
!scope.user_def_for(class_name, method_name).nil?
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def instance_index(instance, args)
|
|
190
|
-
return nil unless args.size == 1
|
|
191
|
-
|
|
192
|
-
arg = args.first
|
|
193
|
-
return nil unless arg.is_a?(Type::Constant)
|
|
194
|
-
|
|
195
|
-
key = arg.value
|
|
196
|
-
case key
|
|
197
|
-
when Symbol
|
|
198
|
-
instance.members[key]
|
|
199
|
-
when Integer
|
|
200
|
-
values = instance.members.values
|
|
201
|
-
idx = key.negative? ? key + values.size : key
|
|
202
|
-
values[idx] if idx && idx >= 0 && idx < values.size
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def instance_to_h(instance)
|
|
207
|
-
Type::Combinator.hash_shape_of(instance.members.dup)
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def instance_deconstruct(instance)
|
|
211
|
-
Type::Combinator.tuple_of(*instance.members.values)
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# `deconstruct_keys(nil)` / `deconstruct_keys([:x])` both yield a
|
|
215
|
-
# subset of the member map; the conservative, always-correct answer
|
|
216
|
-
# is the full closed member shape.
|
|
217
|
-
def instance_deconstruct_keys(instance, args)
|
|
218
|
-
return nil unless args.size <= 1
|
|
219
|
-
|
|
220
|
-
Type::Combinator.hash_shape_of(instance.members.dup)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def instance_members(instance)
|
|
224
|
-
Type::Combinator.tuple_of(*instance.member_names.map { |name| Type::Combinator.constant_of(name) })
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# `Data#with(x: 9)` returns a new frozen copy with the named members
|
|
228
|
-
# overridden. Only a closed keyword `HashShape` whose keys are a
|
|
229
|
-
# subset of the members folds; anything else defers (RBS resolves
|
|
230
|
-
# `with` to `self`, returning the unchanged instance type).
|
|
231
|
-
def instance_with(instance, args)
|
|
232
|
-
return instance if args.empty?
|
|
233
|
-
return nil unless args.size == 1
|
|
234
|
-
|
|
235
|
-
shape = args.first
|
|
236
|
-
return nil unless shape.is_a?(Type::HashShape) && shape.closed?
|
|
237
|
-
return nil unless shape.optional_keys.empty?
|
|
238
|
-
return nil unless shape.pairs.keys.all? { |key| instance.members.key?(key) }
|
|
239
|
-
|
|
240
|
-
merged = instance.members.merge(shape.pairs)
|
|
241
|
-
Type::Combinator.data_instance_of(members: merged, class_name: instance.class_name)
|
|
242
|
-
end
|
|
243
179
|
end
|
|
244
180
|
end
|
|
245
181
|
end
|
|
@@ -54,11 +54,10 @@ module Rigor
|
|
|
54
54
|
# *correctness-preservingly* proved" excludes Constants whose
|
|
55
55
|
# value is host-specific.
|
|
56
56
|
module FileFolding
|
|
57
|
-
# File class methods
|
|
58
|
-
#
|
|
59
|
-
# platform-sensitive
|
|
60
|
-
#
|
|
61
|
-
# opt-in flag for any of them to fire.
|
|
57
|
+
# File class methods the analyzer can fold when the opt-in
|
|
58
|
+
# flag is set. Currently identical to PLATFORM_DEPENDENT_METHODS
|
|
59
|
+
# — separated for a future non-platform-sensitive tier that
|
|
60
|
+
# can fold without the opt-in flag.
|
|
62
61
|
FILE_PURE_CLASS_METHODS = Set[
|
|
63
62
|
:basename,
|
|
64
63
|
:dirname,
|
|
@@ -72,8 +71,8 @@ module Rigor
|
|
|
72
71
|
# Methods whose result depends on host directory-separator
|
|
73
72
|
# semantics (`/` on POSIX, `/` AND `\` on Windows, drive
|
|
74
73
|
# letters, UNC paths). Folding these would bake the
|
|
75
|
-
# analyzer-host's platform into the inferred type. The opt-
|
|
76
|
-
#
|
|
74
|
+
# analyzer-host's platform into the inferred type. The opt-in
|
|
75
|
+
# flag below controls whether to do it anyway.
|
|
77
76
|
PLATFORM_DEPENDENT_METHODS = Set[
|
|
78
77
|
:basename, :dirname, :extname, :join, :split, :absolute_path?
|
|
79
78
|
].freeze
|
|
@@ -175,24 +175,18 @@ module Rigor
|
|
|
175
175
|
type.is_a?(Type::Constant) && type.value.is_a?(Symbol)
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
-
# Element-yielding Enumerable methods covered as a
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
# `Tuple[K, V]` pair rather than the projected
|
|
178
|
+
# Element-yielding Enumerable methods covered as a placeholder.
|
|
179
|
+
# RBS already binds the block parameter correctly for plain
|
|
180
|
+
# `Array[T]` / `Set[T]` / `Range[T]` receivers via generic
|
|
181
|
+
# substitution; this tier exists so Tuple- and HashShape-shaped
|
|
182
|
+
# receivers reach the block body with the precise per-position
|
|
183
|
+
# element union / `Tuple[K, V]` pair rather than the projected
|
|
185
184
|
# `Array[union]` / `Hash[K, V]` widening.
|
|
186
185
|
#
|
|
187
|
-
# NOTE (
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
# after PHPStan's extension API (ADR-2). The placeholders
|
|
192
|
-
# below stay until the plugin surface is in place; once it
|
|
193
|
-
# ships, this dispatcher loses these arms and the
|
|
194
|
-
# equivalent rules move into a built-in plugin loaded at
|
|
195
|
-
# boot.
|
|
186
|
+
# NOTE: `Plugin::NodeRuleWalk` (ADR-52 WD4) is now in place as
|
|
187
|
+
# the intended migration target for these Enumerable projections.
|
|
188
|
+
# The four methods (group_by, partition, each_slice, each_cons)
|
|
189
|
+
# remain here pending that migration.
|
|
196
190
|
def single_element_block_params(receiver)
|
|
197
191
|
element = element_type_of(receiver)
|
|
198
192
|
return nil if element.nil?
|
|
@@ -43,14 +43,21 @@ module Rigor
|
|
|
43
43
|
private_constant :NUMERIC_CONSTRUCTORS
|
|
44
44
|
|
|
45
45
|
# `Kernel#Integer(s)` predicate-aware refinement set
|
|
46
|
-
# (v0.1.1 Track 1 slice 2b).
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
|
|
46
|
+
# (v0.1.1 Track 1 slice 2b). `decimal-int-string` is the
|
|
47
|
+
# only string refinement whose every inhabitant `Integer(s)`
|
|
48
|
+
# parses without remainder, so the result is a plain
|
|
49
|
+
# `Integer` — but NOT `non-negative-int`: the predicate
|
|
50
|
+
# `/\A-?\d+\z/` admits a leading sign, so `"-7"` is a valid
|
|
51
|
+
# decimal-int-string and `Integer("-7") == -7 < 0`. The
|
|
52
|
+
# narrowing is total (every inhabitant parses) but not `>= 0`,
|
|
53
|
+
# so it lands on `universal_int`. `numeric-string` is
|
|
54
|
+
# deliberately NOT in this set at all: since it was widened to
|
|
55
|
+
# the full Ruby numeric-literal grammar (floats, hex, rational,
|
|
56
|
+
# imaginary, signs), `Integer(numeric_string)` would raise for
|
|
57
|
+
# a `"1.5"` / `"2i"` inhabitant — not even total — so it falls
|
|
58
|
+
# through to RBS `Integer`. The `Integer(s, base)` overload is
|
|
59
|
+
# left for a later slice.
|
|
60
|
+
INTEGER_REFINEMENT_PREDICATES = Set[:decimal_int].freeze
|
|
54
61
|
private_constant :INTEGER_REFINEMENT_PREDICATES
|
|
55
62
|
|
|
56
63
|
def try_dispatch(context)
|
|
@@ -70,7 +77,7 @@ module Rigor
|
|
|
70
77
|
# paths, tried in order:
|
|
71
78
|
#
|
|
72
79
|
# 1. A `Refined[String, predicate]` argument whose predicate
|
|
73
|
-
# is a
|
|
80
|
+
# is a total-parse carrier narrows to `universal_int`
|
|
74
81
|
# (see {try_integer_from_refinement}).
|
|
75
82
|
# 2. A `Constant` String or Numeric argument — optionally
|
|
76
83
|
# with a `Constant[Integer]` base — runs the actual
|
|
@@ -120,9 +127,14 @@ module Rigor
|
|
|
120
127
|
# `Kernel#Integer(s)` over a `Refined[String, predicate]`
|
|
121
128
|
# whose predicate is in {INTEGER_REFINEMENT_PREDICATES}.
|
|
122
129
|
# Mirrors the `String#to_i` projection in `ShapeDispatch`
|
|
123
|
-
# (v0.1.1 slice 2a) — the result is
|
|
124
|
-
# `non-negative-int
|
|
125
|
-
# so the
|
|
130
|
+
# (v0.1.1 slice 2a) — the result is `universal_int`, NOT
|
|
131
|
+
# `non-negative-int`: a decimal-int-string admits a leading
|
|
132
|
+
# sign (`"-7"`), so the parsed Integer can be negative. The
|
|
133
|
+
# carrier stays an `IntegerRange` (rather than declining to
|
|
134
|
+
# the RBS `Nominal[Integer]`) so downstream range narrowing
|
|
135
|
+
# still has a range to intersect. Returns nil for any other
|
|
136
|
+
# arg shape so the RBS tier handles the generic `Integer(arg)`
|
|
137
|
+
# case.
|
|
126
138
|
def try_integer_from_refinement(args)
|
|
127
139
|
return nil unless args.size == 1
|
|
128
140
|
|
|
@@ -133,7 +145,7 @@ module Rigor
|
|
|
133
145
|
return nil unless base.is_a?(Type::Nominal) && base.class_name == "String"
|
|
134
146
|
return nil unless INTEGER_REFINEMENT_PREDICATES.include?(arg.predicate_id)
|
|
135
147
|
|
|
136
|
-
Type::Combinator.
|
|
148
|
+
Type::Combinator.universal_int
|
|
137
149
|
end
|
|
138
150
|
|
|
139
151
|
def try_array(args)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The member-shape projections shared by {DataFolding} and
|
|
9
|
+
# {StructFolding}. A `DataInstance` and a `StructInstance` expose the
|
|
10
|
+
# same surface — an ordered `members` map, `member_names`, and a
|
|
11
|
+
# `class_name` — so the value projections off that surface
|
|
12
|
+
# (`[]` / `to_h` / `deconstruct` / `deconstruct_keys` / `members` /
|
|
13
|
+
# `with`) and the reader-redefinition guard are identical between the
|
|
14
|
+
# two folders. Only `#with`'s carrier constructor differs
|
|
15
|
+
# (`data_instance_of` vs `struct_instance_of`), so it takes a block
|
|
16
|
+
# that builds the new instance from the merged member map.
|
|
17
|
+
#
|
|
18
|
+
# Both folders `extend` this module so the projections resolve as
|
|
19
|
+
# their own module functions (matching their `module_function` style).
|
|
20
|
+
module MemberShapeProjection
|
|
21
|
+
# A Data/Struct subclass body can redefine a member's synthesised
|
|
22
|
+
# reader (`def x`); when it does, `inst.x` runs that `def`, not the
|
|
23
|
+
# member, so folding the read would be unsound. A real `def` node
|
|
24
|
+
# under the class name is the discriminator (the synthesised reader
|
|
25
|
+
# has none), so an entry in the project def-node table gates the
|
|
26
|
+
# bare member read off.
|
|
27
|
+
def reader_overridden?(instance, method_name, scope)
|
|
28
|
+
class_name = instance.class_name
|
|
29
|
+
return false if class_name.nil? || scope.nil?
|
|
30
|
+
|
|
31
|
+
!scope.user_def_for(class_name, method_name).nil?
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def instance_index(instance, args)
|
|
35
|
+
return nil unless args.size == 1
|
|
36
|
+
|
|
37
|
+
arg = args.first
|
|
38
|
+
return nil unless arg.is_a?(Type::Constant)
|
|
39
|
+
|
|
40
|
+
key = arg.value
|
|
41
|
+
case key
|
|
42
|
+
when Symbol
|
|
43
|
+
instance.members[key]
|
|
44
|
+
when Integer
|
|
45
|
+
values = instance.members.values
|
|
46
|
+
idx = key.negative? ? key + values.size : key
|
|
47
|
+
values[idx] if idx && idx >= 0 && idx < values.size
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def instance_to_h(instance)
|
|
52
|
+
Type::Combinator.hash_shape_of(instance.members.dup)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def instance_deconstruct(instance)
|
|
56
|
+
Type::Combinator.tuple_of(*instance.members.values)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# `deconstruct_keys(nil)` / `deconstruct_keys([:x])` both yield a
|
|
60
|
+
# subset of the member map; the conservative, always-correct answer
|
|
61
|
+
# is the full closed member shape.
|
|
62
|
+
def instance_deconstruct_keys(instance, args)
|
|
63
|
+
return nil unless args.size <= 1
|
|
64
|
+
|
|
65
|
+
Type::Combinator.hash_shape_of(instance.members.dup)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def instance_members(instance)
|
|
69
|
+
Type::Combinator.tuple_of(*instance.member_names.map { |name| Type::Combinator.constant_of(name) })
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# `#with(x: 9)` returns a new copy with the named members
|
|
73
|
+
# overridden. Only a closed keyword `HashShape` whose keys are a
|
|
74
|
+
# subset of the members folds; anything else defers (RBS resolves
|
|
75
|
+
# `with` to `self`, returning the unchanged instance type). The
|
|
76
|
+
# carrier constructor differs per folder, so the caller supplies it
|
|
77
|
+
# as a block taking the merged member map and the class name.
|
|
78
|
+
def instance_with(instance, args)
|
|
79
|
+
return instance if args.empty?
|
|
80
|
+
return nil unless args.size == 1
|
|
81
|
+
|
|
82
|
+
shape = args.first
|
|
83
|
+
return nil unless shape.is_a?(Type::HashShape) && shape.closed?
|
|
84
|
+
return nil unless shape.optional_keys.empty?
|
|
85
|
+
return nil unless shape.pairs.keys.all? { |key| instance.members.key?(key) }
|
|
86
|
+
|
|
87
|
+
merged = instance.members.merge(shape.pairs)
|
|
88
|
+
yield(merged, instance.class_name)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -25,9 +25,7 @@ module Rigor
|
|
|
25
25
|
# so without this preference, an alias-typed overload like
|
|
26
26
|
# `Array#[](::int) -> Elem` would beat the strict
|
|
27
27
|
# `Array#[](Range) -> Array[Elem]?` overload for a Range
|
|
28
|
-
# argument.
|
|
29
|
-
# "Interface-strictness on overload selection" item in
|
|
30
|
-
# `docs/ROADMAP.md`.)
|
|
28
|
+
# argument.
|
|
31
29
|
# 3. **Pass 2 — gradual fall-back.** If no fully strict overload
|
|
32
30
|
# matches, accept the first arity-and-gradual-accept match
|
|
33
31
|
# (the v0.1.1 behaviour). Alias / Interface / Intersection
|
|
@@ -45,8 +45,9 @@ module Rigor
|
|
|
45
45
|
# machinery works without duplication: `Tuple[Integer, String]`
|
|
46
46
|
# dispatches as `Array[Integer | String]`, and
|
|
47
47
|
# `HashShape{a: Integer}` dispatches as `Hash[Symbol, Integer]`.
|
|
48
|
-
# Tuple
|
|
49
|
-
# member)
|
|
48
|
+
# Tuple/HashShape element precision (e.g., `tuple[0]` returning
|
|
49
|
+
# the precise member) is handled by the preceding `ShapeDispatch`
|
|
50
|
+
# tier.
|
|
50
51
|
#
|
|
51
52
|
# Remaining limitations:
|
|
52
53
|
#
|
|
@@ -107,17 +108,11 @@ module Rigor
|
|
|
107
108
|
# @return [Rigor::Type, nil] inferred return type, or `nil`
|
|
108
109
|
# when no rule resolves (no class name, no method, dispatch
|
|
109
110
|
# on a Top/Dynamic[Top] receiver, etc.).
|
|
110
|
-
# @param scope [Rigor::Scope, nil] when supplied, enables
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
# that ancestor's RBS. `nil` (the default for every caller
|
|
116
|
-
# that does not thread a scope) keeps the legacy behaviour —
|
|
117
|
-
# such an inherited call stays unresolved and degrades to
|
|
118
|
-
# `Dynamic[Top]`, which is the false-positive-safe default
|
|
119
|
-
# for the open hierarchies (`< ActionController::Base`, …)
|
|
120
|
-
# the allow-list deliberately excludes.
|
|
111
|
+
# @param scope [Rigor::Scope, nil] when supplied, enables ADR-43
|
|
112
|
+
# RBS-complete-ancestor resolution against
|
|
113
|
+
# `ALLOWED_RBS_COMPLETE_ANCESTORS`. `nil` keeps inherited calls
|
|
114
|
+
# unresolved (`Dynamic[Top]`) — the FP-safe default for open
|
|
115
|
+
# hierarchies (`< ActionController::Base`, …).
|
|
121
116
|
def try_dispatch(context)
|
|
122
117
|
environment = context.environment
|
|
123
118
|
return nil if environment.nil?
|
|
@@ -249,15 +244,8 @@ module Rigor
|
|
|
249
244
|
["Array", :instance, tuple_type_args(receiver)]
|
|
250
245
|
when Type::HashShape
|
|
251
246
|
["Hash", :instance, hash_shape_type_args(receiver)]
|
|
252
|
-
when Type::DataInstance
|
|
253
|
-
|
|
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, []]
|
|
247
|
+
when Type::DataInstance, Type::DataClass, Type::StructInstance, Type::StructClass
|
|
248
|
+
member_carrier_descriptor(receiver)
|
|
261
249
|
when Type::BoundMethod
|
|
262
250
|
# `BoundMethod` is a precision-bearing alias for
|
|
263
251
|
# `Nominal[Method]`: it carries the
|
|
@@ -275,6 +263,20 @@ module Rigor
|
|
|
275
263
|
end
|
|
276
264
|
end
|
|
277
265
|
|
|
266
|
+
# ADR-48 — project a `Data`/`Struct` member carrier to its tagging
|
|
267
|
+
# class (or the `Data`/`Struct` supertype) so non-member calls
|
|
268
|
+
# (`inspect`, `==`, `frozen?`, ...) resolve through RBS rather than
|
|
269
|
+
# mis-firing undefined-method. Precise member reads were already
|
|
270
|
+
# folded by DataFolding / StructFolding above this tier.
|
|
271
|
+
def member_carrier_descriptor(receiver)
|
|
272
|
+
case receiver
|
|
273
|
+
when Type::DataInstance then [receiver.class_name || "Data", :instance, []]
|
|
274
|
+
when Type::DataClass then [receiver.class_name || "Data", :singleton, []]
|
|
275
|
+
when Type::StructInstance then [receiver.class_name || "Struct", :instance, []]
|
|
276
|
+
when Type::StructClass then [receiver.class_name || "Struct", :singleton, []]
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
278
280
|
def tuple_type_args(tuple)
|
|
279
281
|
return [] if tuple.elements.empty?
|
|
280
282
|
|