rigortype 0.3.6 → 0.3.7
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 +1 -1
- data/data/builtins/ruby_core/date.yml +6 -3
- data/data/builtins/ruby_core/enumerable.yml +1 -0
- data/data/builtins/ruby_core/exception.yml +2 -1
- data/data/builtins/ruby_core/file.yml +28 -14
- data/data/builtins/ruby_core/hash.yml +5 -2
- data/data/builtins/ruby_core/io.yml +33 -16
- data/data/builtins/ruby_core/random.yml +2 -1
- data/data/builtins/ruby_core/re.yml +6 -3
- data/data/builtins/ruby_core/struct.yml +2 -0
- data/data/builtins/ruby_core/time.yml +2 -1
- data/data/gem_overlay/activesupport/core_ext.rbs +409 -11
- data/docs/manual/02-cli-reference.md +16 -7
- data/docs/manual/05-inspecting-types.md +20 -8
- data/docs/manual/07-plugins.md +4 -0
- data/docs/manual/plugins/rigor-actionpack.md +61 -0
- data/docs/manual/plugins/rigor-activerecord.md +74 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
- data/docs/manual/plugins/rigor-sidekiq.md +22 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
- data/lib/rigor/analysis/check_rules.rb +383 -48
- data/lib/rigor/analysis/crash_signature.rb +116 -0
- data/lib/rigor/analysis/dependency_recorder.rb +18 -3
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental.rb +28 -0
- data/lib/rigor/analysis/incremental_session.rb +44 -9
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
- data/lib/rigor/analysis/reachability/graph.rb +14 -5
- data/lib/rigor/analysis/reachability/scan.rb +7 -3
- data/lib/rigor/analysis/result.rb +32 -0
- data/lib/rigor/analysis/run_cache_key.rb +41 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
- data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
- data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
- data/lib/rigor/analysis/runner.rb +147 -10
- data/lib/rigor/analysis/worker_session.rb +10 -1
- data/lib/rigor/bleeding_edge.rb +7 -6
- data/lib/rigor/builtins/hkt_builtins.rb +45 -6
- data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
- data/lib/rigor/builtins/static_return_refinements.rb +20 -1
- data/lib/rigor/cache/descriptor.rb +42 -2
- data/lib/rigor/cache/incremental_snapshot.rb +40 -3
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
- data/lib/rigor/cache/store.rb +10 -1
- data/lib/rigor/cli/coverage_command.rb +23 -18
- data/lib/rigor/cli/coverage_mutation.rb +13 -17
- data/lib/rigor/cli/coverage_scan.rb +47 -6
- data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
- data/lib/rigor/cli/fused_protection_report.rb +23 -2
- data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
- data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
- data/lib/rigor/cli/mutation_protection_report.rb +31 -2
- data/lib/rigor/cli/sig_gen_command.rb +18 -0
- data/lib/rigor/cli/type_of_command.rb +188 -41
- data/lib/rigor/cli/type_of_renderer.rb +72 -3
- data/lib/rigor/cli/type_scan_command.rb +10 -1
- data/lib/rigor/cli.rb +1 -1
- data/lib/rigor/configuration.rb +7 -4
- data/lib/rigor/effects/plugin_facts.rb +1 -1
- data/lib/rigor/effects/scanner.rb +2 -3
- data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
- data/lib/rigor/environment/lockfile_resolver.rb +15 -5
- data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
- data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
- data/lib/rigor/environment/rbs_loader.rb +525 -46
- data/lib/rigor/environment.rb +38 -15
- data/lib/rigor/inference/acceptance.rb +172 -20
- data/lib/rigor/inference/captured_locals.rb +62 -0
- data/lib/rigor/inference/content_join.rb +347 -0
- data/lib/rigor/inference/def_handle.rb +15 -3
- data/lib/rigor/inference/def_node_resolver.rb +62 -3
- data/lib/rigor/inference/expression_typer.rb +1020 -141
- data/lib/rigor/inference/fork_map.rb +6 -1
- data/lib/rigor/inference/hkt_reducer.rb +12 -1
- data/lib/rigor/inference/hkt_registry.rb +46 -0
- data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
- data/lib/rigor/inference/index_write_widening.rb +48 -0
- data/lib/rigor/inference/indexed_narrowing.rb +14 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
- data/lib/rigor/inference/method_dispatcher.rb +155 -14
- data/lib/rigor/inference/method_parameter_binder.rb +3 -3
- data/lib/rigor/inference/mutation_widening.rb +244 -174
- data/lib/rigor/inference/narrowing.rb +331 -27
- data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
- data/lib/rigor/inference/pre_eval_constants.rb +15 -0
- data/lib/rigor/inference/precision_scanner.rb +5 -2
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +89 -39
- data/lib/rigor/inference/scope_indexer.rb +1493 -140
- data/lib/rigor/inference/statement_evaluator.rb +276 -91
- data/lib/rigor/inference/struct_fold_safety.rb +186 -27
- data/lib/rigor/inference/version_guard.rb +229 -0
- data/lib/rigor/language_server/selection_range_provider.rb +1 -1
- data/lib/rigor/plugin/base.rb +3 -2
- data/lib/rigor/plugin/inflector.rb +14 -5
- data/lib/rigor/plugin/io_boundary.rb +107 -4
- data/lib/rigor/plugin/loader.rb +14 -0
- data/lib/rigor/protection/analysis_guard.rb +60 -0
- data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
- data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
- data/lib/rigor/protection/discovery_seed.rb +1 -1
- data/lib/rigor/protection/measurement_integrity.rb +36 -0
- data/lib/rigor/protection/mutation_scanner.rb +61 -6
- data/lib/rigor/protection/mutator.rb +121 -18
- data/lib/rigor/reflection.rb +182 -30
- data/lib/rigor/scope/discovery_index.rb +39 -1
- data/lib/rigor/scope.rb +385 -4
- data/lib/rigor/sig_gen/classification.rb +12 -1
- data/lib/rigor/sig_gen/generator.rb +254 -5
- data/lib/rigor/sig_gen/renderer.rb +1 -6
- data/lib/rigor/sig_gen/writer.rb +3 -0
- data/lib/rigor/source/constant_path.rb +79 -2
- data/lib/rigor/triage/catalogue.rb +1 -1
- data/lib/rigor/type/combinator.rb +10 -0
- data/lib/rigor/type/maybe.rb +47 -0
- data/lib/rigor/type/refined.rb +1 -2
- data/lib/rigor/type/result.rb +53 -0
- data/lib/rigor/type.rb +2 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
- data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
- data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
- data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
- data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
- data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
- data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
- data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
- data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
- data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
- data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
- data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
- data/sig/rigor/analysis/baseline.rbs +1 -1
- data/sig/rigor/environment.rbs +3 -0
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/io_boundary.rbs +2 -0
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +25 -1
- data/sig/rigor/type.rbs +95 -1
- data/sig/rigor.rbs +16 -0
- metadata +69 -3
|
@@ -51,7 +51,7 @@ module Rigor
|
|
|
51
51
|
# a rest positional (`*T`), or a keyword parameter is still unbound and degrades to `Dynamic[Top]`.
|
|
52
52
|
#
|
|
53
53
|
# See docs/adr/4-type-inference-engine.md for the broader plan.
|
|
54
|
-
# rubocop:disable Metrics/ModuleLength
|
|
54
|
+
# rubocop:disable-next Metrics/ModuleLength
|
|
55
55
|
module RbsDispatch
|
|
56
56
|
module_function
|
|
57
57
|
|
|
@@ -144,7 +144,7 @@ module Rigor
|
|
|
144
144
|
)
|
|
145
145
|
end
|
|
146
146
|
|
|
147
|
-
# rubocop:disable Metrics/ClassLength
|
|
147
|
+
# rubocop:disable-next Metrics/ClassLength
|
|
148
148
|
class << self
|
|
149
149
|
private
|
|
150
150
|
|
|
@@ -230,6 +230,13 @@ module Rigor
|
|
|
230
230
|
# still resolve through Method's RBS contract. Routing here keeps reflective Method methods
|
|
231
231
|
# working without forcing the carrier to collapse to a plain Nominal at construction.
|
|
232
232
|
["Method", :instance, []]
|
|
233
|
+
when Type::Refined, Type::Difference
|
|
234
|
+
# #533 — a refinement (`Refined`) or subtraction (`Difference` — `non-empty-string` is
|
|
235
|
+
# `String − ""`) is a precision layer over its base; RBS method lookup erases to the base
|
|
236
|
+
# carrier (`RUBY_VERSION != "1.0"` resolves through `String#!=` instead of declining the
|
|
237
|
+
# whole dispatch to Dynamic). The refinement-aware promotions (`String#upcase` →
|
|
238
|
+
# `uppercase-string`, …) run in their own catalog tier ABOVE this one, so they still win.
|
|
239
|
+
receiver_descriptor(receiver.base)
|
|
233
240
|
when Type::Dynamic
|
|
234
241
|
receiver_descriptor(receiver.static_facet)
|
|
235
242
|
end
|
|
@@ -355,7 +362,7 @@ module Rigor
|
|
|
355
362
|
# fallback returns the caller's type, not Object.
|
|
356
363
|
self_type = self_type_override || resolved_self_type
|
|
357
364
|
|
|
358
|
-
|
|
365
|
+
candidates = OverloadSelector.select_candidates(
|
|
359
366
|
method_definition,
|
|
360
367
|
arg_types: args,
|
|
361
368
|
self_type: self_type,
|
|
@@ -364,17 +371,15 @@ module Rigor
|
|
|
364
371
|
block_required: !block_type.nil?,
|
|
365
372
|
environment: environment
|
|
366
373
|
)
|
|
367
|
-
return nil
|
|
374
|
+
return nil if candidates.empty?
|
|
368
375
|
|
|
369
376
|
call_site = [class_name, method_name, kind]
|
|
370
|
-
record_dispatch_provenance(method_definition,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
instance_type: instance_type,
|
|
377
|
-
type_vars: full_type_vars
|
|
377
|
+
record_dispatch_provenance(method_definition, candidates.first, scope, call_node, call_site)
|
|
378
|
+
join_candidate_returns(
|
|
379
|
+
candidates,
|
|
380
|
+
self_type: self_type, instance_type: instance_type, type_vars: type_vars,
|
|
381
|
+
args: args, block_type: block_type, scope: scope, call_node: call_node, call_site: call_site,
|
|
382
|
+
alias_expander: environment.rbs_loader
|
|
378
383
|
)
|
|
379
384
|
end
|
|
380
385
|
|
|
@@ -400,6 +405,38 @@ module Rigor
|
|
|
400
405
|
# The two method-level type-parameter binding positions, layered in precedence order over the
|
|
401
406
|
# receiver-derived `type_vars`: the block return type first, then the argument positions (which
|
|
402
407
|
# never displace an existing key). See {#compose_arg_type_vars} for the argument envelope.
|
|
408
|
+
# Issue #521 — more than one candidate means a `Dynamic[Top]` argument made the overloads
|
|
409
|
+
# indistinguishable; pinning the first answered a wrong precise type the runtime can contradict
|
|
410
|
+
# (`[true] * n` read as String). The join is the candidates' return union wrapped in `Dynamic`,
|
|
411
|
+
# not the bare union: the untyped argument may satisfy constraints (kwarg shapes, value pins)
|
|
412
|
+
# that exclude arms statically-indistinguishable here, so a bare union licenses the negative
|
|
413
|
+
# rules to fire on arms the runtime never takes — measured immediately as three false positives
|
|
414
|
+
# on this repository's own `lib` (`Integer(v)` joining the `exception: bool` nil arm). The
|
|
415
|
+
# `Dynamic[T]` carrier keeps the candidate set visible without that license (ADR-5).
|
|
416
|
+
# A candidate whose return does not translate leaves the join incomplete — decline (fail-soft
|
|
417
|
+
# to Dynamic downstream) rather than answer a join missing an arm the runtime can take.
|
|
418
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
419
|
+
def join_candidate_returns(candidates, self_type:, instance_type:, type_vars:, args:, block_type:,
|
|
420
|
+
scope:, call_node:, call_site:, alias_expander: nil)
|
|
421
|
+
returns = candidates.map do |method_type|
|
|
422
|
+
full_type_vars = compose_type_vars(method_type, type_vars, args, block_type, scope, call_node, call_site)
|
|
423
|
+
RbsTypeTranslator.translate(
|
|
424
|
+
method_type.type.return_type,
|
|
425
|
+
self_type: self_type,
|
|
426
|
+
instance_type: instance_type,
|
|
427
|
+
type_vars: full_type_vars,
|
|
428
|
+
alias_expander: alias_expander
|
|
429
|
+
)
|
|
430
|
+
end
|
|
431
|
+
return returns.first if returns.size == 1
|
|
432
|
+
return nil if returns.any?(&:nil?)
|
|
433
|
+
|
|
434
|
+
distinct = returns.uniq
|
|
435
|
+
return distinct.first if distinct.size == 1
|
|
436
|
+
|
|
437
|
+
Type::Combinator.dynamic(Type::Combinator.union(*distinct))
|
|
438
|
+
end
|
|
439
|
+
|
|
403
440
|
def compose_type_vars(method_type, type_vars, args, block_type, scope, call_node, call_site)
|
|
404
441
|
vars = compose_block_type_vars(method_type, type_vars, block_type)
|
|
405
442
|
compose_arg_type_vars(method_type, vars, args, scope: scope, call_node: call_node,
|
|
@@ -658,7 +695,8 @@ module Rigor
|
|
|
658
695
|
block,
|
|
659
696
|
self_type: self_type,
|
|
660
697
|
instance_type: instance_type,
|
|
661
|
-
type_vars: type_vars
|
|
698
|
+
type_vars: type_vars,
|
|
699
|
+
alias_expander: environment.rbs_loader
|
|
662
700
|
)
|
|
663
701
|
end
|
|
664
702
|
|
|
@@ -666,7 +704,7 @@ module Rigor
|
|
|
666
704
|
# list; some signatures use `RBS::Types::UntypedFunction` (a `(?)` block) which exposes no
|
|
667
705
|
# parameter types -- we treat it as "no information" and return an empty array so the binder
|
|
668
706
|
# defaults every slot.
|
|
669
|
-
def translate_block_positional_params(block, self_type:, instance_type:, type_vars:)
|
|
707
|
+
def translate_block_positional_params(block, self_type:, instance_type:, type_vars:, alias_expander: nil)
|
|
670
708
|
fun = block.type
|
|
671
709
|
return [] unless fun.respond_to?(:required_positionals)
|
|
672
710
|
|
|
@@ -676,14 +714,13 @@ module Rigor
|
|
|
676
714
|
param.type,
|
|
677
715
|
self_type: self_type,
|
|
678
716
|
instance_type: instance_type,
|
|
679
|
-
type_vars: type_vars
|
|
717
|
+
type_vars: type_vars,
|
|
718
|
+
alias_expander: alias_expander
|
|
680
719
|
)
|
|
681
720
|
end
|
|
682
721
|
end
|
|
683
722
|
end
|
|
684
|
-
# rubocop:enable Metrics/ClassLength
|
|
685
723
|
end
|
|
686
|
-
# rubocop:enable Metrics/ModuleLength
|
|
687
724
|
end
|
|
688
725
|
end
|
|
689
726
|
end
|
|
@@ -48,7 +48,7 @@ module Rigor
|
|
|
48
48
|
#
|
|
49
49
|
# See docs/internal-spec/inference-engine.md (Slice 5 phase 2) and docs/adr/4-type-inference-engine.md
|
|
50
50
|
# for the slice rationale.
|
|
51
|
-
# rubocop:disable Metrics/ClassLength, Metrics/ModuleLength
|
|
51
|
+
# rubocop:disable-next Metrics/ClassLength, Metrics/ModuleLength
|
|
52
52
|
module ShapeDispatch
|
|
53
53
|
module_function
|
|
54
54
|
|
|
@@ -1920,7 +1920,6 @@ module Rigor
|
|
|
1920
1920
|
end
|
|
1921
1921
|
end
|
|
1922
1922
|
end
|
|
1923
|
-
# rubocop:enable Metrics/ClassLength, Metrics/ModuleLength
|
|
1924
1923
|
end
|
|
1925
1924
|
end
|
|
1926
1925
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# `Foo.instance`, where `Foo` includes the stdlib `Singleton` mixin.
|
|
9
|
+
#
|
|
10
|
+
# Ruby grows that class method through a hook — `Singleton.included(klass)` extends the class with
|
|
11
|
+
# `Singleton::SingletonClassMethods` — so nothing static declares it, and the upstream RBS does not close
|
|
12
|
+
# the gap either: `stdlib/singleton/0/singleton.rbs` declares `def self.instance: () -> instance` on the
|
|
13
|
+
# module itself and leaves `module SingletonClassMethods` **empty**. `Singleton.instance` is not a call
|
|
14
|
+
# anyone makes; `Foo.instance` is the entire point of the mixin, and it typed `Dynamic[Top]`.
|
|
15
|
+
#
|
|
16
|
+
# That is expensive out of proportion to its site count, because the value is a whole object: every
|
|
17
|
+
# method called on the returned instance is then dispatched on `Dynamic` too. Mastodon has 365 such
|
|
18
|
+
# calls (`ActivityPub::TagManager.instance`, `FeedManager.instance`, `TagManager.instance`).
|
|
19
|
+
#
|
|
20
|
+
# The rule is exact rather than heuristic — the receiver must be a `Singleton[C]` whose `C` the project
|
|
21
|
+
# pre-pass recorded as including `Singleton` — so a class that does not include the mixin is untouched,
|
|
22
|
+
# and a project that defines its own `Singleton` module is the one case that would misfire. Guarded by
|
|
23
|
+
# {project_singleton_shadow?}: a project-declared `Singleton` class or module wins and the tier declines,
|
|
24
|
+
# because then the name means something else entirely.
|
|
25
|
+
module SingletonMixinDispatch
|
|
26
|
+
MIXIN_NAMES = Ractor.make_shareable(Set["Singleton", "::Singleton"])
|
|
27
|
+
private_constant :MIXIN_NAMES
|
|
28
|
+
|
|
29
|
+
SELECTOR = :instance
|
|
30
|
+
private_constant :SELECTOR
|
|
31
|
+
|
|
32
|
+
module_function
|
|
33
|
+
|
|
34
|
+
# @param context [CallContext]
|
|
35
|
+
# @return [Rigor::Type, nil] `Nominal[C]` for `C.instance`, or nil to decline.
|
|
36
|
+
def try_dispatch(context)
|
|
37
|
+
return nil unless context.method_name == SELECTOR
|
|
38
|
+
return nil unless context.args.empty?
|
|
39
|
+
|
|
40
|
+
receiver = context.receiver
|
|
41
|
+
return nil unless receiver.is_a?(Type::Singleton)
|
|
42
|
+
|
|
43
|
+
scope = context.scope
|
|
44
|
+
return nil if scope.nil?
|
|
45
|
+
return nil unless includes_singleton_mixin?(scope, receiver.class_name)
|
|
46
|
+
return nil if project_singleton_shadow?(scope)
|
|
47
|
+
|
|
48
|
+
Type::Combinator.nominal_of(receiver.class_name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def includes_singleton_mixin?(scope, class_name)
|
|
52
|
+
scope.includes_of(class_name).any? { |name| MIXIN_NAMES.include?(name.to_s) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# True when the project declares its OWN `Singleton` class / module, in which case `include Singleton`
|
|
56
|
+
# names that one and the stdlib rule does not apply.
|
|
57
|
+
def project_singleton_shadow?(scope)
|
|
58
|
+
MIXIN_NAMES.any? { |name| scope.discovery.discovered_classes.key?(name.to_s.delete_prefix("::")) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../../type"
|
|
4
4
|
require_relative "singleton_folding"
|
|
5
|
+
require_relative "struct_materialization"
|
|
5
6
|
require_relative "member_shape_projection"
|
|
6
7
|
|
|
7
8
|
module Rigor
|
|
@@ -41,6 +42,9 @@ module Rigor
|
|
|
41
42
|
# and the reader-redefinition guard are shared with {DataFolding}.
|
|
42
43
|
extend MemberShapeProjection
|
|
43
44
|
|
|
45
|
+
# Issue #595 / #525 — the shared materialisation test; see {StructMaterialization}.
|
|
46
|
+
extend StructMaterialization
|
|
47
|
+
|
|
44
48
|
# @return [Rigor::Type, nil] the folded result, or nil to defer.
|
|
45
49
|
def try_dispatch(context)
|
|
46
50
|
receiver = context.receiver
|
|
@@ -258,16 +262,8 @@ module Rigor
|
|
|
258
262
|
# read) or a FOLD-SAFE stored local (ADR-48 slice 3 — `StructFoldSafety` proved the binding is
|
|
259
263
|
# never mutated / aliased / escaped in its scope).
|
|
260
264
|
def foldable_receiver?(context)
|
|
261
|
-
fresh_receiver?(context) || fold_safe_local_receiver?(context)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
# A fresh receiver is the transient result of a chained call (`Point.new(1, 2).x`,
|
|
265
|
-
# `inst.with(x: 9).y`).
|
|
266
|
-
def fresh_receiver?(context)
|
|
267
|
-
node = context.call_node
|
|
268
|
-
return false if node.nil?
|
|
269
|
-
|
|
270
|
-
node.receiver.is_a?(Prism::CallNode)
|
|
265
|
+
fresh_receiver?(context) || fold_safe_local_receiver?(context) ||
|
|
266
|
+
fold_safe_self_receiver?(context)
|
|
271
267
|
end
|
|
272
268
|
|
|
273
269
|
# A fold-safe stored receiver is a local-variable read whose name the body's fold-safe set (on the
|
|
@@ -282,6 +278,63 @@ module Rigor
|
|
|
282
278
|
scope.struct_fold_safe?(receiver.name)
|
|
283
279
|
end
|
|
284
280
|
|
|
281
|
+
# Issue #525 — the in-body half. A member read written with no receiver (or `self.member`) inside a
|
|
282
|
+
# method the CALLER resolved on a fold-safe struct receiver: `Line.new("a", 2).shout` re-types
|
|
283
|
+
# `shout`'s body with `self_type` = the caller's `StructInstance`, and the implicit-self `text` read
|
|
284
|
+
# arrives here with the right carrier but no receiver node, so neither of the two arms above can see
|
|
285
|
+
# that the caller's receiver was foldable.
|
|
286
|
+
#
|
|
287
|
+
# The caller records that grant as the `:self` sentinel in the body scope's fold-safe set — a
|
|
288
|
+
# `Set[Symbol]` no Ruby local can collide with, since `self` is a keyword. The grant is issued only
|
|
289
|
+
# for a `StructInstance` carrier whose body {Inference::StructFoldSafety.self_fold_safe_body?}
|
|
290
|
+
# cleared, and it is tied HERE to the exact carrier it was issued for: a body scope reached by any
|
|
291
|
+
# other route types its `self` differently, and the read declines rather than fold a map that
|
|
292
|
+
# belongs to a different instance.
|
|
293
|
+
def fold_safe_self_receiver?(context)
|
|
294
|
+
node = context.call_node
|
|
295
|
+
return false if node.nil?
|
|
296
|
+
|
|
297
|
+
receiver_node = node.receiver
|
|
298
|
+
return false unless receiver_node.nil? || receiver_node.is_a?(Prism::SelfNode)
|
|
299
|
+
|
|
300
|
+
scope = context.scope
|
|
301
|
+
return false unless scope&.struct_fold_safe?(:self)
|
|
302
|
+
|
|
303
|
+
same_carrier?(context.receiver, scope.self_type)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Carrier identity for the `:self` grant. `StructInstance` carries structural equality over exactly
|
|
307
|
+
# the two fields that matter here (`value_fields :members, :class_name`), so `==` is both the tighter
|
|
308
|
+
# comparison and allocation-free — no display string is built.
|
|
309
|
+
def same_carrier?(receiver, self_type)
|
|
310
|
+
receiver.is_a?(Type::StructInstance) && receiver == self_type
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# A fresh receiver is the result of a chained call that MATERIALISED the struct — `Point.new(1, 2).x`,
|
|
314
|
+
# `Point[1, 2].x`, `inst.with(x: 9).y`. Not merely a chained call: slices 1-2 read "chained" as
|
|
315
|
+
# "cannot have been mutated between materialisation and the read", and a method that hands back its
|
|
316
|
+
# own receiver falsifies that (issue #595):
|
|
317
|
+
#
|
|
318
|
+
# Line = Struct.new(:text) do
|
|
319
|
+
# def with_text(v) = (self.text = v; self)
|
|
320
|
+
# def dup_self = self
|
|
321
|
+
# end
|
|
322
|
+
# x = Line.new("a"); x.text = "z"
|
|
323
|
+
# x.dup_self.text # the chain returns the SAME, mutated object
|
|
324
|
+
# Line.new("a").with_text("z").text # so does the fluent spelling
|
|
325
|
+
#
|
|
326
|
+
# Both folded the construction-time `"a"` against a runtime `"z"` — a wrong precise value on
|
|
327
|
+
# ordinary fluent-builder Ruby. `infer_user_method_return` types a body under a fixed `self_type`,
|
|
328
|
+
# so the setter never poisons the returned `self`, and the freshness test never asked the callee
|
|
329
|
+
# what it returns. Restricting the test to the shapes this module itself materialises closes the
|
|
330
|
+
# family in one place: any other callee refuses, whatever it does internally.
|
|
331
|
+
def fresh_receiver?(context)
|
|
332
|
+
node = context.call_node
|
|
333
|
+
return false if node.nil?
|
|
334
|
+
|
|
335
|
+
materialization_call?(node.receiver, context.receiver, context.scope)
|
|
336
|
+
end
|
|
337
|
+
|
|
285
338
|
# ADR-48 slice 4 — precise mutated-member re-typing. After a `local.member = v` setter on a
|
|
286
339
|
# fold-safe `StructInstance` local, rebind the local to a `StructInstance` with that member replaced
|
|
287
340
|
# by the assigned type, so a later `local.member` read folds to the assigned value (and a sibling
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../../type"
|
|
6
|
+
require_relative "../../source/constant_path"
|
|
7
|
+
|
|
8
|
+
module Rigor
|
|
9
|
+
module Inference
|
|
10
|
+
module MethodDispatcher
|
|
11
|
+
# Issue #595 / #525 — the ONE answer to "did this receiver expression just BUILD the struct?", shared by
|
|
12
|
+
# the two gates that must not disagree about it: {StructFolding#fresh_receiver?}, which decides whether a
|
|
13
|
+
# direct member read folds, and `ExpressionTyper`'s caller-side `:self`-grant arm, which decides whether a
|
|
14
|
+
# whole method body may fold its receiverless member reads.
|
|
15
|
+
#
|
|
16
|
+
# They had forked answers once — the grant arm was tightened for #591 while the read gate still trusted
|
|
17
|
+
# any chained call — and that is exactly how the composed `x.dup_self.with(indent: 9).shout` shape
|
|
18
|
+
# survived: a correctly whitelisted `.with` fold firing off a receiver the other gate wrongly called
|
|
19
|
+
# fresh. One implementation is the fix for the class of divergence, not just for that shape — and the
|
|
20
|
+
# module takes NO resolver parameter for the same reason: a lookup each consumer supplies is a fork
|
|
21
|
+
# waiting to happen, so the `.with` guard resolves through `Scope` itself (#598 review).
|
|
22
|
+
#
|
|
23
|
+
# Scope is this module's only entry: `StructFolding` `extend`s it, and since `module_function` copies
|
|
24
|
+
# arrive private through `extend`, the predicate stays callable under this module's own name.
|
|
25
|
+
module StructMaterialization
|
|
26
|
+
module_function
|
|
27
|
+
|
|
28
|
+
# Issue #595 / #525 — the ONE materialisation test. Both places that must answer "was this receiver
|
|
29
|
+
# expression's struct newly built?" go through it: this module's direct member-read gate above, and
|
|
30
|
+
# `ExpressionTyper`'s caller-side `:self`-grant arm. They had forked answers once (the grant arm was
|
|
31
|
+
# tightened for #591 while this one still trusted any chained call), which is exactly how the
|
|
32
|
+
# composed `x.dup_self.with(indent: 9).shout` shape survived — a correctly whitelisted `.with` fold
|
|
33
|
+
# firing off a receiver the other gate should never have called fresh.
|
|
34
|
+
#
|
|
35
|
+
#
|
|
36
|
+
# @param node [Prism::Node, nil] the receiver EXPRESSION.
|
|
37
|
+
# @param receiver [Rigor::Type, nil] the carrier the expression produced.
|
|
38
|
+
# @param scope [Rigor::Scope, nil]
|
|
39
|
+
def materialization_call?(node, receiver, scope)
|
|
40
|
+
return false unless node.is_a?(Prism::CallNode)
|
|
41
|
+
|
|
42
|
+
case node.name
|
|
43
|
+
when :new, :[] then struct_class_expression?(node.receiver, scope)
|
|
44
|
+
when :with then !hand_written_with?(receiver, scope)
|
|
45
|
+
else false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# `.with` copies the receiver into a new instance — unless the struct wrote its own, which is free to
|
|
50
|
+
# return `self` and would reopen the very hole above.
|
|
51
|
+
#
|
|
52
|
+
# An ANONYMOUS carrier cannot have one: a `StructInstance` with no class name came from a blockless
|
|
53
|
+
# `Struct.new(…)` (the block form defers in {fold_struct_new} before any instance exists), and a
|
|
54
|
+
# blockless factory defines nothing but the generated accessors. So it is the NAMED carriers that
|
|
55
|
+
# need the lookup — and a named one with no scope to ask refuses, since unproven is not fresh.
|
|
56
|
+
def hand_written_with?(receiver, scope)
|
|
57
|
+
return true unless receiver.is_a?(Type::StructInstance)
|
|
58
|
+
|
|
59
|
+
class_name = receiver.class_name
|
|
60
|
+
return false if class_name.nil?
|
|
61
|
+
return true if scope.nil?
|
|
62
|
+
|
|
63
|
+
# The ANCESTOR walk, not the own-class table: a `with` contributed by an included module is just as
|
|
64
|
+
# free to return `self`. Both consumers reach the identical answer because there is one lookup here
|
|
65
|
+
# rather than a parameter each of them supplies (#598 review).
|
|
66
|
+
!scope.user_def_through_ancestors(class_name, :with).first.nil?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# An expression naming the struct class itself. All three definition forms ADR-48 supports qualify,
|
|
70
|
+
# because each of them really does construct: a constant whose member layout the project side-table
|
|
71
|
+
# recorded (`Point.new(…)`), a local holding a `StructClass` carrier (`c = Struct.new(:x, :y)` then
|
|
72
|
+
# `c.new(…)`), and the inline factory chain (`Struct.new(:x, :y).new(…)`). The local arm reads the
|
|
73
|
+
# binding rather than re-typing the node, so it costs one hash read and cannot re-enter dispatch.
|
|
74
|
+
def struct_class_expression?(node, scope)
|
|
75
|
+
return false if scope.nil?
|
|
76
|
+
|
|
77
|
+
case node
|
|
78
|
+
when Prism::ConstantReadNode, Prism::ConstantPathNode
|
|
79
|
+
name = Source::ConstantPath.qualified_name_or_nil(node)
|
|
80
|
+
!name.nil? && !scope.struct_member_layout(name).nil?
|
|
81
|
+
when Prism::LocalVariableReadNode
|
|
82
|
+
scope.local(node.name).is_a?(Type::StructClass)
|
|
83
|
+
when Prism::CallNode
|
|
84
|
+
inline_struct_factory?(node)
|
|
85
|
+
else false
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# `Struct.new(:a, :b)` / `Data.define(:a, :b)` written inline as the receiver of the `.new` that
|
|
90
|
+
# materialises the instance.
|
|
91
|
+
def inline_struct_factory?(node)
|
|
92
|
+
return false unless %i[new define].include?(node.name)
|
|
93
|
+
|
|
94
|
+
receiver = node.receiver
|
|
95
|
+
case receiver
|
|
96
|
+
when Prism::ConstantReadNode then %i[Struct Data].include?(receiver.name)
|
|
97
|
+
when Prism::ConstantPathNode then receiver.parent.nil? && %i[Struct Data].include?(receiver.name)
|
|
98
|
+
else false
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../type"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Inference
|
|
7
|
+
module MethodDispatcher
|
|
8
|
+
# The last resolution tier for a `Dynamic` receiver: the handful of `BasicObject` / `Object` / `Kernel`
|
|
9
|
+
# methods whose return type does not depend on who the receiver is.
|
|
10
|
+
#
|
|
11
|
+
# `x.nil?` is `bool` whatever `x` is. Until this tier existed it was `Dynamic[Top]`, because every
|
|
12
|
+
# preceding tier needs a receiver it can name and the typer then fell through to the Dynamic-origin
|
|
13
|
+
# propagation. The same held for `is_a?`, `respond_to?`, `!` and the rest of the set below — a
|
|
14
|
+
# measurable share of the opaque expressions on a real codebase, closed by a fixed table rather than
|
|
15
|
+
# by knowing anything about the receiver
|
|
16
|
+
# (`docs/notes/20260831-self-check-type-coverage-audit.md` § Finding 2: +2.27 points of precision on this
|
|
17
|
+
# repository's own `lib`, with zero new diagnostics).
|
|
18
|
+
#
|
|
19
|
+
# **Where it sits.** After every real tier has declined, so a receiver Rigor can name always resolves
|
|
20
|
+
# through RBS first — `"".frozen?` is answered by `String`'s signature, not by this table. It only ever
|
|
21
|
+
# replaces a `Dynamic[Top]` that the engine was about to produce anyway.
|
|
22
|
+
#
|
|
23
|
+
# **What is deliberately NOT here**, each measured rather than argued (same note):
|
|
24
|
+
#
|
|
25
|
+
# - `class` — `Nominal[Class]` erases the singleton. `p.class.dynamic_returns` on a plugin instance is
|
|
26
|
+
# real code in this repository; folding the receiver's class to the bare `Class` nominal produced 13
|
|
27
|
+
# `call.undefined-method` false positives on `lib` alone. An ADR-5 violation bought for +0.14 points.
|
|
28
|
+
# - `==` / `!=` / `eql?` — the same failure at a much larger blast radius. `==` is the most commonly
|
|
29
|
+
# overridden method in Ruby, and an overridden one is free to return anything.
|
|
30
|
+
# - `to_s` — receiver-independent in principle, but enabling it surfaces the RBS
|
|
31
|
+
# `Array#[](Range) -> Array[T]?` optional-return noise on `x.to_s.split("::")[0...-1]` (7
|
|
32
|
+
# `call.possible-nil-receiver` on `lib`). Not this tier's defect, but not this tier's to ship either:
|
|
33
|
+
# it goes in once that noise has an answer.
|
|
34
|
+
# - `freeze` / `dup` / `clone` / `itself` / `tap` — receiver-independent in a different way (they
|
|
35
|
+
# return the receiver), so on a `Dynamic` receiver they gain nothing.
|
|
36
|
+
module UniversalObjectDispatch
|
|
37
|
+
# `bool` as the engine spells it — `Constant[true] | Constant[false]`, the union
|
|
38
|
+
# `RbsTypeTranslator` folds RBS's `bool` to. Spelling it `Nominal[TrueClass] | Nominal[FalseClass]`
|
|
39
|
+
# instead type-checks but is not the same value: it fails to match a `-> bool` declaration and
|
|
40
|
+
# produced 17 spurious `def.return-type-mismatch` warnings when this tier was first prototyped.
|
|
41
|
+
BOOL = Type::Combinator.union(
|
|
42
|
+
Type::Combinator.constant_of(true),
|
|
43
|
+
Type::Combinator.constant_of(false)
|
|
44
|
+
).freeze
|
|
45
|
+
private_constant :BOOL
|
|
46
|
+
|
|
47
|
+
# Selector => return type, for selectors defined on `BasicObject` / `Object` / `Kernel` whose result
|
|
48
|
+
# is a function of the language, not of the receiver's class:
|
|
49
|
+
#
|
|
50
|
+
# - the type predicates (`nil?`, `is_a?` and its aliases, `respond_to?`, `frozen?`) and the identity
|
|
51
|
+
# comparison `equal?`, which `BasicObject` defines as object identity;
|
|
52
|
+
# - `!`, which Ruby's own semantics constrain to `true` / `false`;
|
|
53
|
+
# - `inspect`, whose `Object` contract is a `String` representation;
|
|
54
|
+
# - `hash` and `object_id`, both `Integer` by contract — `hash` because `Hash` requires it.
|
|
55
|
+
RETURN_TYPES = Ractor.make_shareable({
|
|
56
|
+
:nil? => BOOL,
|
|
57
|
+
:is_a? => BOOL,
|
|
58
|
+
:kind_of? => BOOL,
|
|
59
|
+
:instance_of? => BOOL,
|
|
60
|
+
:respond_to? => BOOL,
|
|
61
|
+
:equal? => BOOL,
|
|
62
|
+
:frozen? => BOOL,
|
|
63
|
+
:! => BOOL,
|
|
64
|
+
:inspect => Type::Combinator.nominal_of("String"),
|
|
65
|
+
:hash => Type::Combinator.nominal_of("Integer"),
|
|
66
|
+
:object_id => Type::Combinator.nominal_of("Integer")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
module_function
|
|
70
|
+
|
|
71
|
+
# @param context [CallContext]
|
|
72
|
+
# @return [Rigor::Type, nil] the receiver-independent return type, or nil to decline.
|
|
73
|
+
def try_dispatch(context)
|
|
74
|
+
return nil unless context.receiver.is_a?(Type::Dynamic)
|
|
75
|
+
|
|
76
|
+
RETURN_TYPES[context.method_name]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|