rigortype 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@ require "prism"
4
4
 
5
5
  require_relative "../scope"
6
6
  require_relative "../type"
7
+ require_relative "narrowing"
7
8
  require_relative "statement_evaluator"
8
9
 
9
10
  module Rigor
@@ -475,16 +476,9 @@ module Rigor
475
476
 
476
477
  case node
477
478
  when Prism::ModuleNode, Prism::ClassNode
478
- name = qualified_name_for(node.constant_path)
479
- if name
480
- full = (qualified_prefix + [name]).join("::")
481
- singleton = Type::Combinator.singleton_of(full)
482
- identity_table[node.constant_path] = singleton
483
- discovered[full] = singleton
484
- child_prefix = qualified_prefix + [name]
485
- record_declarations(node.body, child_prefix, identity_table, discovered) if node.body
486
- return
487
- end
479
+ return if record_class_or_module?(node, qualified_prefix, identity_table, discovered)
480
+ when Prism::ConstantWriteNode
481
+ return if record_data_define_constant?(node, qualified_prefix, identity_table, discovered)
488
482
  end
489
483
 
490
484
  node.compact_child_nodes.each do |child|
@@ -492,6 +486,60 @@ module Rigor
492
486
  end
493
487
  end
494
488
 
489
+ def record_class_or_module?(node, qualified_prefix, identity_table, discovered)
490
+ name = qualified_name_for(node.constant_path)
491
+ return false unless name
492
+
493
+ full = (qualified_prefix + [name]).join("::")
494
+ singleton = Type::Combinator.singleton_of(full)
495
+ identity_table[node.constant_path] = singleton
496
+ discovered[full] = singleton
497
+ child_prefix = qualified_prefix + [name]
498
+ record_declarations(node.body, child_prefix, identity_table, discovered) if node.body
499
+ true
500
+ end
501
+
502
+ # Recognises `Const = Data.define(*Symbol) [do ... end]` and registers
503
+ # `Const` (qualified by the surrounding class/module path) as a
504
+ # discovered class. `Const.new(...)` then resolves to a fresh
505
+ # `Nominal[Const]` via `meta_new`, instead of the un-narrowed
506
+ # `Dynamic[top]` returned by the default `Class#new` envelope.
507
+ #
508
+ # The Data.define block body, if present, is recursed into so any
509
+ # nested class/module declarations in the override block (rare but
510
+ # legal) still feed the discovered table.
511
+ def record_data_define_constant?(node, qualified_prefix, identity_table, discovered)
512
+ return false unless data_define_call?(node.value)
513
+
514
+ full = (qualified_prefix + [node.name.to_s]).join("::")
515
+ discovered[full] = Type::Combinator.singleton_of(full)
516
+ record_declarations(node.value, qualified_prefix, identity_table, discovered)
517
+ true
518
+ end
519
+
520
+ # Recognises `Data.define(*Symbol)` and `Data.define(*Symbol) do ... end`
521
+ # at constant-write rvalue position. The receiver MUST be the bare
522
+ # `Data` constant (or `::Data`); other receivers (a local variable, a
523
+ # method call return) are rejected because their identity is not
524
+ # statically known.
525
+ def data_define_call?(node)
526
+ return false unless node.is_a?(Prism::CallNode)
527
+ return false unless node.name == :define
528
+ return false unless data_constant_receiver?(node.receiver)
529
+
530
+ args = node.arguments&.arguments || []
531
+ args.all?(Prism::SymbolNode)
532
+ end
533
+
534
+ def data_constant_receiver?(node)
535
+ case node
536
+ when Prism::ConstantReadNode
537
+ node.name == :Data
538
+ when Prism::ConstantPathNode
539
+ node.parent.nil? && node.name == :Data
540
+ end
541
+ end
542
+
495
543
  def qualified_name_for(constant_path_node)
496
544
  case constant_path_node
497
545
  when Prism::ConstantReadNode
@@ -515,6 +563,13 @@ module Rigor
515
563
  # Prism node the StatementEvaluator did not visit (i.e. expression-
516
564
  # interior nodes like the receiver/args of a CallNode). Those
517
565
  # nodes inherit their nearest recorded ancestor's scope.
566
+ #
567
+ # `IfNode` / `UnlessNode` are special-cased: the truthy and falsey
568
+ # branches each get their predicate's narrowed scope before
569
+ # recursing. This handles expression-position conditionals
570
+ # (e.g. `cache[k] = if cond; t; else; e; end` and conditionals
571
+ # nested as call arguments) which are typed by ExpressionTyper
572
+ # without going through `eval_if`'s narrowing path.
518
573
  def propagate(node, table, parent_scope)
519
574
  return unless node.is_a?(Prism::Node)
520
575
 
@@ -526,7 +581,28 @@ module Rigor
526
581
  parent_scope
527
582
  end
528
583
 
529
- node.compact_child_nodes.each { |child| propagate(child, table, current_scope) }
584
+ case node
585
+ when Prism::IfNode
586
+ propagate_if_branches(node, table, current_scope)
587
+ when Prism::UnlessNode
588
+ propagate_unless_branches(node, table, current_scope)
589
+ else
590
+ node.compact_child_nodes.each { |child| propagate(child, table, current_scope) }
591
+ end
592
+ end
593
+
594
+ def propagate_if_branches(node, table, current_scope)
595
+ truthy_scope, falsey_scope = Narrowing.predicate_scopes(node.predicate, current_scope)
596
+ propagate(node.predicate, table, current_scope) if node.predicate
597
+ propagate(node.statements, table, truthy_scope) if node.statements
598
+ propagate(node.subsequent, table, falsey_scope) if node.subsequent
599
+ end
600
+
601
+ def propagate_unless_branches(node, table, current_scope)
602
+ truthy_scope, falsey_scope = Narrowing.predicate_scopes(node.predicate, current_scope)
603
+ propagate(node.predicate, table, current_scope) if node.predicate
604
+ propagate(node.statements, table, falsey_scope) if node.statements
605
+ propagate(node.else_clause, table, truthy_scope) if node.else_clause
530
606
  end
531
607
  end
532
608
  # rubocop:enable Metrics/ModuleLength
@@ -894,7 +894,11 @@ module Rigor
894
894
  end
895
895
 
896
896
  def narrow_for_assert_effect(current_type, effect, environment)
897
- return effect.refinement_type if effect.refinement?
897
+ if effect.refinement?
898
+ return Narrowing.narrow_not_refinement(current_type, effect.refinement_type) if effect.negative?
899
+
900
+ return effect.refinement_type
901
+ end
898
902
 
899
903
  if effect.negative?
900
904
  Narrowing.narrow_not_class(current_type, effect.class_name, exact: false, environment: environment)
@@ -124,8 +124,8 @@ module Rigor
124
124
  \s+
125
125
  (?<target>self|[a-z_][a-zA-Z0-9_]*)
126
126
  \s+is\s+
127
+ (?<negation>~?)
127
128
  (?:
128
- (?<negation>~?)
129
129
  (?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
130
130
  |
131
131
  (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?)
@@ -181,8 +181,8 @@ module Rigor
181
181
  \s+
182
182
  (?<target>self|[a-z_][a-zA-Z0-9_]*)
183
183
  \s+is\s+
184
+ (?<negation>~?)
184
185
  (?:
185
- (?<negation>~?)
186
186
  (?<class_name>(?:::)?[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)
187
187
  |
188
188
  (?<refinement>[a-z][a-z0-9-]*(?:[\[<][^\]>]*[\]>])?)
@@ -231,14 +231,19 @@ module Rigor
231
231
  # nil, `negative` reflects the optional `~` prefix.
232
232
  # - Refinement arm matched: `class_name` is nil,
233
233
  # `refinement_type` is the resolved `Rigor::Type`,
234
- # `negative` is `false` (refinement-form directives do
235
- # not support `~` negation in v0.0.4).
234
+ # `negative` reflects the `~` prefix. v0.0.5 supports
235
+ # refinement-form negation for the `Difference[base,
236
+ # Constant]` shape (the narrowing tier computes the
237
+ # complement decomposition); other refinement carriers
238
+ # under negation fall back to the conservative
239
+ # "current_type unchanged" answer.
236
240
  # - Refinement payload unparseable: returns
237
241
  # `[nil, nil, false]` so callers can drop the directive
238
242
  # silently (fail-soft policy).
239
243
  def resolve_directive_rhs(match)
244
+ negative = match[:negation].to_s == "~"
240
245
  class_capture = match[:class_name]
241
- return [class_capture.to_s.sub(/\A::/, ""), nil, match[:negation].to_s == "~"] if class_capture
246
+ return [class_capture.to_s.sub(/\A::/, ""), nil, negative] if class_capture
242
247
 
243
248
  refinement_capture = match[:refinement]
244
249
  return [nil, nil, false] if refinement_capture.nil?
@@ -246,7 +251,7 @@ module Rigor
246
251
  type = Builtins::ImportedRefinements.parse(refinement_capture)
247
252
  return [nil, nil, false] if type.nil?
248
253
 
249
- [nil, type, false]
254
+ [nil, type, negative]
250
255
  end
251
256
 
252
257
  def target_fields(target)
@@ -65,11 +65,13 @@ module Rigor
65
65
  # `:neg_infinity`). Use this in arithmetic comparisons; never compare
66
66
  # `:neg_infinity` directly with an `Integer`.
67
67
  def lower
68
- min == NEG_INFINITY ? -Float::INFINITY : min
68
+ m = min
69
+ m.is_a?(Symbol) ? -Float::INFINITY : m
69
70
  end
70
71
 
71
72
  def upper
72
- max == POS_INFINITY ? Float::INFINITY : max
73
+ m = max
74
+ m.is_a?(Symbol) ? Float::INFINITY : m
73
75
  end
74
76
 
75
77
  ALIAS_NAMES = {
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.0.4"
4
+ VERSION = "0.0.5"
5
5
  end
@@ -38,15 +38,13 @@ module Rigor
38
38
 
39
39
  def initialize: (?libraries: Array[String], ?signature_paths: Array[String | _ToPath]) -> void
40
40
  def class_known?: (String | Symbol name) -> bool
41
- def instance_definition: (String | Symbol class_name) -> untyped
42
- def instance_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped
43
- def singleton_definition: (String | Symbol class_name) -> untyped
44
- def singleton_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped
41
+ def instance_definition: (String | Symbol class_name) -> untyped?
42
+ def instance_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped?
43
+ def singleton_definition: (String | Symbol class_name) -> untyped?
44
+ def singleton_method: (class_name: String | Symbol, method_name: String | Symbol) -> untyped?
45
45
  def class_type_param_names: (String | Symbol class_name) -> Array[Symbol]
46
46
  def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
47
47
  def constant_type: (String name) -> Type::t?
48
- def instance_definition: (String | Symbol class_name) -> untyped?
49
- def singleton_definition: (String | Symbol class_name) -> untyped?
50
48
  end
51
49
 
52
50
  class RbsHierarchy
@@ -85,6 +85,7 @@ module Rigor
85
85
  def self?.narrow_not_equal: (Type::t type, untyped literal) -> Type::t
86
86
  def self?.narrow_class: (Type::t type, String class_name, ?exact: bool, ?environment: Environment) -> Type::t
87
87
  def self?.narrow_not_class: (Type::t type, String class_name, ?exact: bool, ?environment: Environment) -> Type::t
88
+ def self?.narrow_not_refinement: (Type::t current_type, Type::t refinement_type) -> Type::t
88
89
  def self?.predicate_scopes: (untyped node, Scope scope) -> [Scope, Scope]
89
90
  def self?.case_when_scopes: (untyped subject, Array[untyped] conditions, Scope scope) -> [Scope, Scope]
90
91
  def self?.analyse: (untyped node, Scope scope) -> untyped
@@ -131,7 +132,7 @@ module Rigor
131
132
  module ScopeIndexer
132
133
  def self?.index: (untyped root, default_scope: Scope) -> Hash[untyped, Scope]
133
134
  def self?.build_declaration_overrides: (untyped root) -> Hash[untyped, Type::t]
134
- def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] table) -> void
135
+ def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] identity_table, Hash[String, Type::t] discovered) -> void
135
136
  def self?.qualified_name_for: (untyped constant_path_node) -> String?
136
137
  def self?.render_constant_path: (untyped node) -> String
137
138
  def self?.propagate: (untyped node, Hash[untyped, Scope] table, Scope parent_scope) -> void
data/sig/rigor/type.rbs CHANGED
@@ -8,9 +8,9 @@ module Rigor
8
8
  def self.instance: () -> Top
9
9
  def describe: (?Symbol verbosity) -> String
10
10
  def erase_to_rbs: () -> String
11
- def top: () -> Top
12
- def bot: () -> Bot
13
- def dynamic: () -> Dynamic
11
+ def top: () -> Trinary
12
+ def bot: () -> Trinary
13
+ def dynamic: () -> Trinary
14
14
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
15
15
  def ==: (untyped other) -> bool
16
16
  def hash: () -> Integer
@@ -21,9 +21,9 @@ module Rigor
21
21
  def self.instance: () -> Bot
22
22
  def describe: (?Symbol verbosity) -> String
23
23
  def erase_to_rbs: () -> String
24
- def top: () -> Top
25
- def bot: () -> Bot
26
- def dynamic: () -> Dynamic
24
+ def top: () -> Trinary
25
+ def bot: () -> Trinary
26
+ def dynamic: () -> Trinary
27
27
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
28
28
  def ==: (untyped other) -> bool
29
29
  def hash: () -> Integer
@@ -35,9 +35,9 @@ module Rigor
35
35
  def initialize: (Type::t static_facet) -> void
36
36
  def describe: (?Symbol verbosity) -> String
37
37
  def erase_to_rbs: () -> String
38
- def top: () -> Top
39
- def bot: () -> Bot
40
- def dynamic: () -> Dynamic
38
+ def top: () -> Trinary
39
+ def bot: () -> Trinary
40
+ def dynamic: () -> Trinary
41
41
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
42
42
  def ==: (untyped other) -> bool
43
43
  def hash: () -> Integer
@@ -49,9 +49,9 @@ module Rigor
49
49
  def initialize: (untyped value) -> void
50
50
  def describe: (?Symbol verbosity) -> String
51
51
  def erase_to_rbs: () -> String
52
- def top: () -> Top
53
- def bot: () -> Bot
54
- def dynamic: () -> Dynamic
52
+ def top: () -> Trinary
53
+ def bot: () -> Trinary
54
+ def dynamic: () -> Trinary
55
55
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
56
56
  def ==: (untyped other) -> bool
57
57
  def hash: () -> Integer
@@ -68,14 +68,14 @@ module Rigor
68
68
  def finite?: () -> bool
69
69
  def cardinality: () -> (Integer | Float)
70
70
  def covers?: (untyped value) -> bool
71
- def lower: () -> Numeric
72
- def upper: () -> Numeric
71
+ def lower: () -> (Integer | Float)
72
+ def upper: () -> (Integer | Float)
73
73
  def describe: (?Symbol verbosity) -> String
74
74
  def generic_description: () -> String
75
75
  def erase_to_rbs: () -> String
76
- def top: () -> Top
77
- def bot: () -> Bot
78
- def dynamic: () -> Dynamic
76
+ def top: () -> Trinary
77
+ def bot: () -> Trinary
78
+ def dynamic: () -> Trinary
79
79
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
80
80
  def ==: (untyped other) -> bool
81
81
  def hash: () -> Integer
@@ -88,9 +88,9 @@ module Rigor
88
88
  def initialize: (String class_name, ?Array[Type::t] type_args) -> void
89
89
  def describe: (?Symbol verbosity) -> String
90
90
  def erase_to_rbs: () -> String
91
- def top: () -> Top
92
- def bot: () -> Bot
93
- def dynamic: () -> Dynamic
91
+ def top: () -> Trinary
92
+ def bot: () -> Trinary
93
+ def dynamic: () -> Trinary
94
94
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
95
95
  def ==: (untyped other) -> bool
96
96
  def hash: () -> Integer
@@ -102,9 +102,9 @@ module Rigor
102
102
  def initialize: (String class_name) -> void
103
103
  def describe: (?Symbol verbosity) -> String
104
104
  def erase_to_rbs: () -> String
105
- def top: () -> Top
106
- def bot: () -> Bot
107
- def dynamic: () -> Dynamic
105
+ def top: () -> Trinary
106
+ def bot: () -> Trinary
107
+ def dynamic: () -> Trinary
108
108
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
109
109
  def ==: (untyped other) -> bool
110
110
  def hash: () -> Integer
@@ -116,9 +116,9 @@ module Rigor
116
116
  def initialize: (Array[Type::t] members) -> void
117
117
  def describe: (?Symbol verbosity) -> String
118
118
  def erase_to_rbs: () -> String
119
- def top: () -> Top
120
- def bot: () -> Bot
121
- def dynamic: () -> Dynamic
119
+ def top: () -> Trinary
120
+ def bot: () -> Trinary
121
+ def dynamic: () -> Trinary
122
122
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
123
123
  def ==: (untyped other) -> bool
124
124
  def hash: () -> Integer
@@ -131,9 +131,9 @@ module Rigor
131
131
  def initialize: (Type::t base, Type::t removed) -> void
132
132
  def describe: (?Symbol verbosity) -> String
133
133
  def erase_to_rbs: () -> String
134
- def top: () -> Top
135
- def bot: () -> Bot
136
- def dynamic: () -> Dynamic
134
+ def top: () -> Trinary
135
+ def bot: () -> Trinary
136
+ def dynamic: () -> Trinary
137
137
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
138
138
  def ==: (untyped other) -> bool
139
139
  def hash: () -> Integer
@@ -146,9 +146,9 @@ module Rigor
146
146
  def initialize: (Type::t base, Symbol predicate_id) -> void
147
147
  def describe: (?Symbol verbosity) -> String
148
148
  def erase_to_rbs: () -> String
149
- def top: () -> Top
150
- def bot: () -> Bot
151
- def dynamic: () -> Dynamic
149
+ def top: () -> Trinary
150
+ def bot: () -> Trinary
151
+ def dynamic: () -> Trinary
152
152
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
153
153
  def matches?: (untyped value) -> bool?
154
154
  def ==: (untyped other) -> bool
@@ -161,9 +161,9 @@ module Rigor
161
161
  def initialize: (Array[Type::t] members) -> void
162
162
  def describe: (?Symbol verbosity) -> String
163
163
  def erase_to_rbs: () -> String
164
- def top: () -> Top
165
- def bot: () -> Bot
166
- def dynamic: () -> Dynamic
164
+ def top: () -> Trinary
165
+ def bot: () -> Trinary
166
+ def dynamic: () -> Trinary
167
167
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
168
168
  def ==: (untyped other) -> bool
169
169
  def hash: () -> Integer
@@ -175,9 +175,9 @@ module Rigor
175
175
  def initialize: (Array[Type::t] elements) -> void
176
176
  def describe: (?Symbol verbosity) -> String
177
177
  def erase_to_rbs: () -> String
178
- def top: () -> Top
179
- def bot: () -> Bot
180
- def dynamic: () -> Dynamic
178
+ def top: () -> Trinary
179
+ def bot: () -> Trinary
180
+ def dynamic: () -> Trinary
181
181
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
182
182
  def ==: (untyped other) -> bool
183
183
  def hash: () -> Integer
@@ -198,9 +198,9 @@ module Rigor
198
198
  def required_key?: (untyped key) -> bool
199
199
  def optional_key?: (untyped key) -> bool
200
200
  def read_only_key?: (untyped key) -> bool
201
- def top: () -> Top
202
- def bot: () -> Bot
203
- def dynamic: () -> Dynamic
201
+ def top: () -> Trinary
202
+ def bot: () -> Trinary
203
+ def dynamic: () -> Trinary
204
204
  def accepts: (Type::t other, ?mode: accepts_mode) -> AcceptsResult
205
205
  def ==: (untyped other) -> bool
206
206
  def hash: () -> Integer
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -161,11 +161,16 @@ files:
161
161
  - LICENSE
162
162
  - README.md
163
163
  - data/builtins/ruby_core/array.yml
164
+ - data/builtins/ruby_core/comparable.yml
165
+ - data/builtins/ruby_core/complex.yml
166
+ - data/builtins/ruby_core/date.yml
167
+ - data/builtins/ruby_core/enumerable.yml
164
168
  - data/builtins/ruby_core/file.yml
165
169
  - data/builtins/ruby_core/hash.yml
166
170
  - data/builtins/ruby_core/io.yml
167
171
  - data/builtins/ruby_core/numeric.yml
168
172
  - data/builtins/ruby_core/range.yml
173
+ - data/builtins/ruby_core/rational.yml
169
174
  - data/builtins/ruby_core/set.yml
170
175
  - data/builtins/ruby_core/string.yml
171
176
  - data/builtins/ruby_core/time.yml
@@ -193,10 +198,15 @@ files:
193
198
  - lib/rigor/inference/acceptance.rb
194
199
  - lib/rigor/inference/block_parameter_binder.rb
195
200
  - lib/rigor/inference/builtins/array_catalog.rb
201
+ - lib/rigor/inference/builtins/comparable_catalog.rb
202
+ - lib/rigor/inference/builtins/complex_catalog.rb
203
+ - lib/rigor/inference/builtins/date_catalog.rb
204
+ - lib/rigor/inference/builtins/enumerable_catalog.rb
196
205
  - lib/rigor/inference/builtins/hash_catalog.rb
197
206
  - lib/rigor/inference/builtins/method_catalog.rb
198
207
  - lib/rigor/inference/builtins/numeric_catalog.rb
199
208
  - lib/rigor/inference/builtins/range_catalog.rb
209
+ - lib/rigor/inference/builtins/rational_catalog.rb
200
210
  - lib/rigor/inference/builtins/set_catalog.rb
201
211
  - lib/rigor/inference/builtins/string_catalog.rb
202
212
  - lib/rigor/inference/builtins/time_catalog.rb
@@ -209,6 +219,7 @@ files:
209
219
  - lib/rigor/inference/method_dispatcher/constant_folding.rb
210
220
  - lib/rigor/inference/method_dispatcher/file_folding.rb
211
221
  - lib/rigor/inference/method_dispatcher/iterator_dispatch.rb
222
+ - lib/rigor/inference/method_dispatcher/kernel_dispatch.rb
212
223
  - lib/rigor/inference/method_dispatcher/overload_selector.rb
213
224
  - lib/rigor/inference/method_dispatcher/rbs_dispatch.rb
214
225
  - lib/rigor/inference/method_dispatcher/shape_dispatch.rb