rigortype 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +18 -1
  4. data/lib/rigor/analysis/check_rules/rule_walk.rb +67 -0
  5. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +100 -0
  6. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +226 -0
  7. data/lib/rigor/analysis/check_rules.rb +180 -73
  8. data/lib/rigor/analysis/dependency_recorder.rb +122 -0
  9. data/lib/rigor/analysis/diagnostic.rb +18 -0
  10. data/lib/rigor/analysis/incremental.rb +162 -0
  11. data/lib/rigor/analysis/incremental_session.rb +337 -0
  12. data/lib/rigor/analysis/rule_catalog.rb +48 -0
  13. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +580 -0
  14. data/lib/rigor/analysis/runner/pool_coordinator.rb +569 -0
  15. data/lib/rigor/analysis/runner/project_pre_passes.rb +318 -0
  16. data/lib/rigor/analysis/runner/run_snapshots.rb +46 -0
  17. data/lib/rigor/analysis/runner.rb +477 -1110
  18. data/lib/rigor/analysis/self_call_resolution_recorder.rb +121 -0
  19. data/lib/rigor/analysis/worker_session.rb +47 -8
  20. data/lib/rigor/builtins/static_return_refinements.rb +7 -1
  21. data/lib/rigor/cache/descriptor.rb +50 -49
  22. data/lib/rigor/cache/incremental_snapshot.rb +153 -0
  23. data/lib/rigor/cache/rbs_cache_producer.rb +34 -0
  24. data/lib/rigor/cache/rbs_class_ancestor_table.rb +2 -8
  25. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -8
  26. data/lib/rigor/cache/rbs_constant_table.rb +2 -8
  27. data/lib/rigor/cache/rbs_environment.rb +2 -8
  28. data/lib/rigor/cache/rbs_known_class_names.rb +2 -8
  29. data/lib/rigor/cache/store.rb +145 -14
  30. data/lib/rigor/cli/annotate_command.rb +2 -7
  31. data/lib/rigor/cli/baseline_command.rb +2 -7
  32. data/lib/rigor/cli/check_command.rb +705 -0
  33. data/lib/rigor/cli/ci_detector.rb +94 -0
  34. data/lib/rigor/cli/command.rb +47 -0
  35. data/lib/rigor/cli/coverage_command.rb +3 -23
  36. data/lib/rigor/cli/coverage_renderer.rb +3 -8
  37. data/lib/rigor/cli/diagnostic_formats.rb +345 -0
  38. data/lib/rigor/cli/diff_command.rb +3 -7
  39. data/lib/rigor/cli/explain_command.rb +2 -7
  40. data/lib/rigor/cli/lsp_command.rb +3 -7
  41. data/lib/rigor/cli/mcp_command.rb +3 -7
  42. data/lib/rigor/cli/options.rb +57 -0
  43. data/lib/rigor/cli/plugin_command.rb +3 -7
  44. data/lib/rigor/cli/plugins_command.rb +2 -7
  45. data/lib/rigor/cli/prism_colorizer.rb +10 -3
  46. data/lib/rigor/cli/renderable.rb +26 -0
  47. data/lib/rigor/cli/sig_gen_command.rb +2 -7
  48. data/lib/rigor/cli/skill_command.rb +3 -7
  49. data/lib/rigor/cli/trace_command.rb +143 -0
  50. data/lib/rigor/cli/trace_renderer.rb +310 -0
  51. data/lib/rigor/cli/triage_command.rb +2 -7
  52. data/lib/rigor/cli/type_of_command.rb +5 -38
  53. data/lib/rigor/cli/type_of_renderer.rb +4 -9
  54. data/lib/rigor/cli/type_scan_command.rb +3 -23
  55. data/lib/rigor/cli/type_scan_renderer.rb +4 -9
  56. data/lib/rigor/cli.rb +15 -532
  57. data/lib/rigor/configuration/dependencies.rb +18 -1
  58. data/lib/rigor/configuration/severity_profile.rb +22 -3
  59. data/lib/rigor/configuration.rb +16 -3
  60. data/lib/rigor/environment/rbs_loader.rb +129 -71
  61. data/lib/rigor/environment.rb +1 -1
  62. data/lib/rigor/inference/acceptance.rb +10 -0
  63. data/lib/rigor/inference/block_parameter_binder.rb +1 -2
  64. data/lib/rigor/inference/builtins/array_catalog.rb +2 -5
  65. data/lib/rigor/inference/builtins/comparable_catalog.rb +2 -5
  66. data/lib/rigor/inference/builtins/complex_catalog.rb +2 -5
  67. data/lib/rigor/inference/builtins/date_catalog.rb +2 -5
  68. data/lib/rigor/inference/builtins/encoding_catalog.rb +2 -5
  69. data/lib/rigor/inference/builtins/enumerable_catalog.rb +2 -5
  70. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -5
  71. data/lib/rigor/inference/builtins/hash_catalog.rb +2 -5
  72. data/lib/rigor/inference/builtins/method_catalog.rb +15 -0
  73. data/lib/rigor/inference/builtins/numeric_catalog.rb +21 -93
  74. data/lib/rigor/inference/builtins/pathname_catalog.rb +2 -5
  75. data/lib/rigor/inference/builtins/proc_catalog.rb +2 -5
  76. data/lib/rigor/inference/builtins/random_catalog.rb +2 -5
  77. data/lib/rigor/inference/builtins/range_catalog.rb +2 -5
  78. data/lib/rigor/inference/builtins/rational_catalog.rb +2 -5
  79. data/lib/rigor/inference/builtins/re_catalog.rb +2 -5
  80. data/lib/rigor/inference/builtins/set_catalog.rb +2 -5
  81. data/lib/rigor/inference/builtins/string_catalog.rb +2 -5
  82. data/lib/rigor/inference/builtins/struct_catalog.rb +2 -5
  83. data/lib/rigor/inference/builtins/time_catalog.rb +2 -5
  84. data/lib/rigor/inference/expression_typer.rb +149 -63
  85. data/lib/rigor/inference/flow_tracer.rb +180 -0
  86. data/lib/rigor/inference/macro_block_self_type.rb +10 -11
  87. data/lib/rigor/inference/method_dispatcher/block_folding.rb +5 -1
  88. data/lib/rigor/inference/method_dispatcher/call_context.rb +65 -0
  89. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +11 -10
  90. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +12 -6
  91. data/lib/rigor/inference/method_dispatcher/data_folding.rb +246 -0
  92. data/lib/rigor/inference/method_dispatcher/file_folding.rb +6 -2
  93. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  94. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +4 -1
  95. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +4 -1
  96. data/lib/rigor/inference/method_dispatcher/math_folding.rb +6 -6
  97. data/lib/rigor/inference/method_dispatcher/method_folding.rb +12 -7
  98. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +33 -1
  99. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +23 -13
  100. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +9 -9
  101. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -6
  102. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +120 -9
  103. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +12 -12
  104. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +49 -0
  105. data/lib/rigor/inference/method_dispatcher/time_folding.rb +6 -6
  106. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -9
  107. data/lib/rigor/inference/method_dispatcher.rb +185 -84
  108. data/lib/rigor/inference/narrowing.rb +262 -5
  109. data/lib/rigor/inference/scope_indexer.rb +208 -21
  110. data/lib/rigor/inference/statement_evaluator.rb +110 -48
  111. data/lib/rigor/language_server/buffer_resolution.rb +33 -0
  112. data/lib/rigor/language_server/completion_provider.rb +4 -4
  113. data/lib/rigor/language_server/document_symbol_provider.rb +4 -4
  114. data/lib/rigor/language_server/folding_range_provider.rb +4 -4
  115. data/lib/rigor/language_server/hover_provider.rb +4 -4
  116. data/lib/rigor/language_server/selection_range_provider.rb +4 -4
  117. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  118. data/lib/rigor/plugin/additional_initializer.rb +61 -38
  119. data/lib/rigor/plugin/base.rb +302 -45
  120. data/lib/rigor/plugin/node_rule_walk.rb +147 -0
  121. data/lib/rigor/plugin/registry.rb +281 -15
  122. data/lib/rigor/plugin.rb +1 -0
  123. data/lib/rigor/rbs_extended/conformance_checker.rb +293 -0
  124. data/lib/rigor/rbs_extended.rb +39 -0
  125. data/lib/rigor/scope/discovery_index.rb +58 -0
  126. data/lib/rigor/scope.rb +150 -167
  127. data/lib/rigor/sig_gen/observation_collector.rb +6 -6
  128. data/lib/rigor/source/literals.rb +14 -0
  129. data/lib/rigor/type/acceptance_router.rb +19 -0
  130. data/lib/rigor/type/accepts_result.rb +3 -10
  131. data/lib/rigor/type/app.rb +3 -7
  132. data/lib/rigor/type/bot.rb +2 -3
  133. data/lib/rigor/type/bound_method.rb +5 -12
  134. data/lib/rigor/type/combinator.rb +22 -0
  135. data/lib/rigor/type/constant.rb +2 -3
  136. data/lib/rigor/type/data_class.rb +80 -0
  137. data/lib/rigor/type/data_instance.rb +100 -0
  138. data/lib/rigor/type/difference.rb +5 -10
  139. data/lib/rigor/type/dynamic.rb +5 -10
  140. data/lib/rigor/type/hash_shape.rb +5 -15
  141. data/lib/rigor/type/integer_range.rb +5 -10
  142. data/lib/rigor/type/intersection.rb +5 -10
  143. data/lib/rigor/type/nominal.rb +5 -10
  144. data/lib/rigor/type/refined.rb +5 -10
  145. data/lib/rigor/type/singleton.rb +5 -10
  146. data/lib/rigor/type/top.rb +2 -3
  147. data/lib/rigor/type/tuple.rb +5 -10
  148. data/lib/rigor/type/union.rb +5 -10
  149. data/lib/rigor/type.rb +2 -0
  150. data/lib/rigor/value_semantics.rb +77 -0
  151. data/lib/rigor/version.rb +1 -1
  152. data/lib/rigor.rb +1 -1
  153. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -2
  154. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +2 -4
  155. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +70 -32
  156. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -3
  157. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +15 -21
  158. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +1 -1
  159. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -2
  160. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -2
  161. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +12 -2
  162. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +12 -2
  163. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  164. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +35 -18
  165. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +8 -29
  166. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +17 -1
  167. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +2 -2
  168. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +83 -36
  169. data/sig/rigor/cache.rbs +19 -0
  170. data/sig/rigor/environment.rbs +0 -2
  171. data/sig/rigor/inference.rbs +27 -0
  172. data/sig/rigor/plugin/base.rbs +1 -2
  173. data/sig/rigor/rbs_extended.rbs +2 -0
  174. data/sig/rigor/scope.rbs +42 -25
  175. data/sig/rigor/source.rbs +1 -0
  176. data/sig/rigor/type.rbs +58 -1
  177. data/sig/rigor.rbs +6 -1
  178. data/skills/rigor-ci-setup/SKILL.md +319 -0
  179. metadata +36 -2
  180. data/lib/rigor/cache/rbs_instance_definitions.rb +0 -79
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
4
5
 
5
6
  module Rigor
6
7
  module Type
@@ -79,17 +80,9 @@ module Rigor
79
80
  self.class.new(trinary, mode: mode, reasons: reasons + [reason])
80
81
  end
81
82
 
82
- def ==(other)
83
- other.is_a?(AcceptsResult) &&
84
- trinary == other.trinary &&
85
- mode == other.mode &&
86
- reasons == other.reasons
87
- end
88
- alias eql? ==
83
+ include Rigor::ValueSemantics
89
84
 
90
- def hash
91
- [AcceptsResult, trinary, mode, reasons].hash
92
- end
85
+ value_fields :trinary, :mode, :reasons
93
86
 
94
87
  def inspect
95
88
  "#<Rigor::Type::AcceptsResult #{trinary.inspect} mode=#{mode}>"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
4
5
 
5
6
  module Rigor
6
7
  module Type
@@ -82,14 +83,9 @@ module Rigor
82
83
  Inference::Acceptance.accepts(bound, other, mode: mode)
83
84
  end
84
85
 
85
- def ==(other)
86
- other.is_a?(App) && uri == other.uri && args == other.args && bound == other.bound
87
- end
88
- alias eql? ==
86
+ include Rigor::ValueSemantics
89
87
 
90
- def hash
91
- [App, uri, args, bound].hash
92
- end
88
+ value_fields :uri, :args, :bound
93
89
 
94
90
  def inspect
95
91
  "#<Rigor::Type::App #{describe(:short)} (bound=#{bound.describe(:short)})>"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "acceptance_router"
4
5
 
5
6
  module Rigor
6
7
  module Type
@@ -39,9 +40,7 @@ module Rigor
39
40
  Trinary.no
40
41
  end
41
42
 
42
- def accepts(other, mode: :gradual)
43
- Inference::Acceptance.accepts(self, other, mode: mode)
44
- end
43
+ include Rigor::Type::AcceptanceRouter
45
44
 
46
45
  def ==(other)
47
46
  other.is_a?(Bot)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -56,20 +58,11 @@ module Rigor
56
58
  Trinary.no
57
59
  end
58
60
 
59
- def accepts(other, mode: :gradual)
60
- Inference::Acceptance.accepts(self, other, mode: mode)
61
- end
61
+ include Rigor::Type::AcceptanceRouter
62
62
 
63
- def ==(other)
64
- other.is_a?(BoundMethod) &&
65
- receiver_type == other.receiver_type &&
66
- method_name == other.method_name
67
- end
68
- alias eql? ==
63
+ include Rigor::ValueSemantics
69
64
 
70
- def hash
71
- [BoundMethod, receiver_type, method_name].hash
72
- end
65
+ value_fields :receiver_type, :method_name
73
66
 
74
67
  def inspect
75
68
  "#<Rigor::Type::BoundMethod #{describe(:short)}>"
@@ -9,12 +9,15 @@ require_relative "constant"
9
9
  require_relative "integer_range"
10
10
  require_relative "tuple"
11
11
  require_relative "hash_shape"
12
+ require_relative "data_class"
13
+ require_relative "data_instance"
12
14
  require_relative "union"
13
15
  require_relative "difference"
14
16
  require_relative "refined"
15
17
  require_relative "intersection"
16
18
  require_relative "bound_method"
17
19
  require_relative "../inference/budget_trace"
20
+ require_relative "../inference/flow_tracer"
18
21
 
19
22
  module Rigor
20
23
  module Type
@@ -358,6 +361,21 @@ module Rigor
358
361
  HashShape.new(pairs, **options)
359
362
  end
360
363
 
364
+ # ADR-48 — the class object produced by `Data.define(:x, :y)`.
365
+ # `members` is the ordered Symbol member-name list; `class_name`
366
+ # tags the class when known (the named-subclass form) and is nil
367
+ # for the anonymous `Data.define(...)` result.
368
+ def data_class_of(members:, class_name: nil)
369
+ DataClass.new(members, class_name)
370
+ end
371
+
372
+ # ADR-48 — a `Data.define` value instance. `members` is the ordered
373
+ # member-name -> value-type map; `class_name` tags the instance's
374
+ # class when known.
375
+ def data_instance_of(members:, class_name: nil)
376
+ DataInstance.new(members, class_name)
377
+ end
378
+
361
379
  # Normalized union. Flattens nested Unions, deduplicates structurally
362
380
  # equal members, drops Bot, and collapses 0/1-member results.
363
381
  def union(*types)
@@ -365,6 +383,10 @@ module Rigor
365
383
  if Inference::BudgetTrace.enabled? && result.is_a?(Union)
366
384
  Inference::BudgetTrace.observe(Inference::BudgetTrace::UNION_ARITY, result.members.size)
367
385
  end
386
+ # `rigor trace` — degenerate merges (`1 | 1 → 1`, Dynamic
387
+ # absorption) are recorded too; the collapse itself is the
388
+ # teachable moment.
389
+ Inference::FlowTracer.union(types, result) if Inference::FlowTracer.active? && types.size >= 2
368
390
  result
369
391
  end
370
392
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "date"
4
4
  require_relative "../trinary"
5
+ require_relative "acceptance_router"
5
6
 
6
7
  module Rigor
7
8
  module Type
@@ -111,9 +112,7 @@ module Rigor
111
112
  Trinary.no
112
113
  end
113
114
 
114
- def accepts(other, mode: :gradual)
115
- Inference::Acceptance.accepts(self, other, mode: mode)
116
- end
115
+ include Rigor::Type::AcceptanceRouter
117
116
 
118
117
  def ==(other)
119
118
  other.is_a?(Constant) && value.class == other.value.class && value == other.value
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
6
+
7
+ module Rigor
8
+ module Type
9
+ # The class object produced by `Data.define(:x, :y)` (ADR-48). Models
10
+ # the *class*, not an instance — the value bound to `Point` in
11
+ # `Point = Data.define(:x, :y)` or the anonymous superclass in
12
+ # `class Point < Data.define(:x, :y)`. Parameterised by the ordered
13
+ # member-name list so that `Point.new(...)` can materialise a precise
14
+ # {DataInstance}.
15
+ #
16
+ # `class_name` carries the binding name when known (the named-subclass
17
+ # form, ADR-48 slice 3) and is `nil` for the anonymous result of a bare
18
+ # `Data.define(...)` before it is assigned to a constant. It tags the
19
+ # instances `.new` produces; it does not change the carrier's folding
20
+ # behaviour.
21
+ #
22
+ # Equality and hashing are structural over the member list and the
23
+ # class name. Two distinct `Data.define(:x)` results are equal *as
24
+ # types* — they describe the same shape; the engine distinguishes the
25
+ # constants they are bound to by the binding, not by the carrier.
26
+ #
27
+ # See docs/adr/48-data-struct-value-folding.md.
28
+ class DataClass
29
+ attr_reader :members, :class_name
30
+
31
+ # @param members [Array<Symbol>] ordered member names.
32
+ # @param class_name [String, nil] the bound class name, or nil for
33
+ # the anonymous `Data.define(...)` result.
34
+ def initialize(members, class_name = nil)
35
+ unless members.is_a?(Array) && members.all?(Symbol)
36
+ raise ArgumentError, "members must be an Array of Symbols, got #{members.inspect}"
37
+ end
38
+ unless class_name.nil? || (class_name.is_a?(String) && !class_name.empty?)
39
+ raise ArgumentError, "class_name must be a non-empty String or nil, got #{class_name.inspect}"
40
+ end
41
+
42
+ @members = members.dup.freeze
43
+ @class_name = class_name&.freeze
44
+ freeze
45
+ end
46
+
47
+ def describe(_verbosity = :short)
48
+ return "singleton(#{class_name})" if class_name
49
+
50
+ "Data.define(#{members.map(&:inspect).join(', ')})"
51
+ end
52
+
53
+ def erase_to_rbs
54
+ "singleton(#{class_name || 'Data'})"
55
+ end
56
+
57
+ def top
58
+ Trinary.no
59
+ end
60
+
61
+ def bot
62
+ Trinary.no
63
+ end
64
+
65
+ def dynamic
66
+ Trinary.no
67
+ end
68
+
69
+ include Rigor::Type::AcceptanceRouter
70
+
71
+ include Rigor::ValueSemantics
72
+
73
+ value_fields :members, :class_name
74
+
75
+ def inspect
76
+ "#<Rigor::Type::DataClass #{describe(:short)}>"
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
6
+
7
+ module Rigor
8
+ module Type
9
+ # A `Data.define` value instance (ADR-48) — `Point.new(1, 2)`. Models a
10
+ # closed, total, class-tagged member map (member name -> value type).
11
+ # HashShape-shaped, but nominal: a `DataInstance` tagged `Point` is a
12
+ # different type from one tagged `Line` even with identical members,
13
+ # and it erases to its class nominal rather than an RBS record.
14
+ #
15
+ # `Data` instances are frozen, so the member map is sound for the
16
+ # instance's whole lifetime — the reason `Data` is the first target
17
+ # (a `Struct` instance can be mutated through a setter or `[]=`, which
18
+ # would invalidate the map; that follow-up is deferred — see ADR-48).
19
+ #
20
+ # Member reads fold to the member's type (`Point.new(1, 2).x` ->
21
+ # `Constant[1]`); `[]`, `to_h`, `deconstruct`, `deconstruct_keys`,
22
+ # `members`, and `with` project precisely. Methods this carrier does
23
+ # not fold project to the `Data` nominal (or the tagged class) through
24
+ # {RbsDispatch}'s `receiver_descriptor`, so non-member calls resolve
25
+ # without mis-firing undefined-method.
26
+ #
27
+ # Equality and hashing are structural over the (member -> type) map and
28
+ # the class name.
29
+ #
30
+ # See docs/adr/48-data-struct-value-folding.md.
31
+ class DataInstance
32
+ attr_reader :members, :class_name
33
+
34
+ # @param members [Hash{Symbol => Rigor::Type}] ordered member -> type
35
+ # map. Every declared member is present (Data instances are total).
36
+ # @param class_name [String, nil] the tagging class name, or nil for
37
+ # an instance of an anonymous `Data.define(...)` class.
38
+ def initialize(members, class_name = nil)
39
+ unless members.is_a?(Hash) && members.each_key.all?(Symbol)
40
+ raise ArgumentError, "members must be a Hash with Symbol keys, got #{members.inspect}"
41
+ end
42
+ unless class_name.nil? || (class_name.is_a?(String) && !class_name.empty?)
43
+ raise ArgumentError, "class_name must be a non-empty String or nil, got #{class_name.inspect}"
44
+ end
45
+
46
+ @members = members.dup.freeze
47
+ @class_name = class_name&.freeze
48
+ freeze
49
+ end
50
+
51
+ # @return [Array<Symbol>] ordered member names.
52
+ def member_names
53
+ members.keys
54
+ end
55
+
56
+ # @return [Rigor::Type, nil] the member's value type, or nil when the
57
+ # name is not a declared member.
58
+ def member_type(name)
59
+ members[name]
60
+ end
61
+
62
+ def describe(verbosity = :short)
63
+ rendered = members.map { |name, type| "#{name}: #{type.describe(verbosity)}" }
64
+ "#{class_name || 'Data'}(#{rendered.join(', ')})"
65
+ end
66
+
67
+ # Erases to the tagging class nominal (conservative: the structural
68
+ # members are not RBS-expressible as a class instance). The
69
+ # anonymous case erases to the `Data` supertype.
70
+ def erase_to_rbs
71
+ name = class_name
72
+ return "Data" if name.nil?
73
+
74
+ name
75
+ end
76
+
77
+ def top
78
+ Trinary.no
79
+ end
80
+
81
+ def bot
82
+ Trinary.no
83
+ end
84
+
85
+ def dynamic
86
+ Trinary.no
87
+ end
88
+
89
+ include Rigor::Type::AcceptanceRouter
90
+
91
+ include Rigor::ValueSemantics
92
+
93
+ value_fields :members, :class_name
94
+
95
+ def inspect
96
+ "#<Rigor::Type::DataInstance #{describe(:short)}>"
97
+ end
98
+ end
99
+ end
100
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -67,18 +69,11 @@ module Rigor
67
69
  base.respond_to?(:dynamic) ? base.dynamic : Trinary.no
68
70
  end
69
71
 
70
- def accepts(other, mode: :gradual)
71
- Inference::Acceptance.accepts(self, other, mode: mode)
72
- end
72
+ include Rigor::Type::AcceptanceRouter
73
73
 
74
- def ==(other)
75
- other.is_a?(Difference) && base == other.base && removed == other.removed
76
- end
77
- alias eql? ==
74
+ include Rigor::ValueSemantics
78
75
 
79
- def hash
80
- [Difference, base, removed].hash
81
- end
76
+ value_fields :base, :removed
82
77
 
83
78
  def inspect
84
79
  "#<Rigor::Type::Difference #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -39,18 +41,11 @@ module Rigor
39
41
  Trinary.yes
40
42
  end
41
43
 
42
- def accepts(other, mode: :gradual)
43
- Inference::Acceptance.accepts(self, other, mode: mode)
44
- end
44
+ include Rigor::Type::AcceptanceRouter
45
45
 
46
- def ==(other)
47
- other.is_a?(Dynamic) && static_facet == other.static_facet
48
- end
49
- alias eql? ==
46
+ include Rigor::ValueSemantics
50
47
 
51
- def hash
52
- [Dynamic, static_facet].hash
53
- end
48
+ value_fields :static_facet
54
49
 
55
50
  def inspect
56
51
  "#<Rigor::Type::Dynamic #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -106,23 +108,11 @@ module Rigor
106
108
  Trinary.no
107
109
  end
108
110
 
109
- def accepts(other, mode: :gradual)
110
- Inference::Acceptance.accepts(self, other, mode: mode)
111
- end
111
+ include Rigor::Type::AcceptanceRouter
112
112
 
113
- def ==(other)
114
- other.is_a?(HashShape) &&
115
- pairs == other.pairs &&
116
- required_keys == other.required_keys &&
117
- optional_keys == other.optional_keys &&
118
- read_only_keys == other.read_only_keys &&
119
- extra_keys == other.extra_keys
120
- end
121
- alias eql? ==
113
+ include Rigor::ValueSemantics
122
114
 
123
- def hash
124
- [HashShape, pairs, required_keys, optional_keys, read_only_keys, extra_keys].hash
125
- end
115
+ value_fields :pairs, :required_keys, :optional_keys, :read_only_keys, :extra_keys
126
116
 
127
117
  def inspect
128
118
  "#<Rigor::Type::HashShape #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -113,18 +115,11 @@ module Rigor
113
115
  Trinary.no
114
116
  end
115
117
 
116
- def accepts(other, mode: :gradual)
117
- Inference::Acceptance.accepts(self, other, mode: mode)
118
- end
118
+ include Rigor::Type::AcceptanceRouter
119
119
 
120
- def ==(other)
121
- other.is_a?(IntegerRange) && min == other.min && max == other.max
122
- end
123
- alias eql? ==
120
+ include Rigor::ValueSemantics
124
121
 
125
- def hash
126
- [IntegerRange, min, max].hash
127
- end
122
+ value_fields :min, :max
128
123
 
129
124
  def inspect
130
125
  "#<Rigor::Type::IntegerRange #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -72,18 +74,11 @@ module Rigor
72
74
  Trinary.no
73
75
  end
74
76
 
75
- def accepts(other, mode: :gradual)
76
- Inference::Acceptance.accepts(self, other, mode: mode)
77
- end
77
+ include Rigor::Type::AcceptanceRouter
78
78
 
79
- def ==(other)
80
- other.is_a?(Intersection) && members == other.members
81
- end
82
- alias eql? ==
79
+ include Rigor::ValueSemantics
83
80
 
84
- def hash
85
- [Intersection, members].hash
86
- end
81
+ value_fields :members
87
82
 
88
83
  def inspect
89
84
  "#<Rigor::Type::Intersection #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -62,18 +64,11 @@ module Rigor
62
64
  Trinary.no
63
65
  end
64
66
 
65
- def accepts(other, mode: :gradual)
66
- Inference::Acceptance.accepts(self, other, mode: mode)
67
- end
67
+ include Rigor::Type::AcceptanceRouter
68
68
 
69
- def ==(other)
70
- other.is_a?(Nominal) && class_name == other.class_name && type_args == other.type_args
71
- end
72
- alias eql? ==
69
+ include Rigor::ValueSemantics
73
70
 
74
- def hash
75
- [Nominal, class_name, type_args].hash
76
- end
71
+ value_fields :class_name, :type_args
77
72
 
78
73
  def inspect
79
74
  "#<Rigor::Type::Nominal #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -69,18 +71,11 @@ module Rigor
69
71
  base.respond_to?(:dynamic) ? base.dynamic : Trinary.no
70
72
  end
71
73
 
72
- def accepts(other, mode: :gradual)
73
- Inference::Acceptance.accepts(self, other, mode: mode)
74
- end
74
+ include Rigor::Type::AcceptanceRouter
75
75
 
76
- def ==(other)
77
- other.is_a?(Refined) && base == other.base && predicate_id == other.predicate_id
78
- end
79
- alias eql? ==
76
+ include Rigor::ValueSemantics
80
77
 
81
- def hash
82
- [Refined, base, predicate_id].hash
83
- end
78
+ value_fields :base, :predicate_id
84
79
 
85
80
  def inspect
86
81
  "#<Rigor::Type::Refined #{describe(:short)}>"
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -44,18 +46,11 @@ module Rigor
44
46
  Trinary.no
45
47
  end
46
48
 
47
- def accepts(other, mode: :gradual)
48
- Inference::Acceptance.accepts(self, other, mode: mode)
49
- end
49
+ include Rigor::Type::AcceptanceRouter
50
50
 
51
- def ==(other)
52
- other.is_a?(Singleton) && class_name == other.class_name
53
- end
54
- alias eql? ==
51
+ include Rigor::ValueSemantics
55
52
 
56
- def hash
57
- [Singleton, class_name].hash
58
- end
53
+ value_fields :class_name
59
54
 
60
55
  def inspect
61
56
  "#<Rigor::Type::Singleton #{class_name}>"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "acceptance_router"
4
5
 
5
6
  module Rigor
6
7
  module Type
@@ -36,9 +37,7 @@ module Rigor
36
37
  Trinary.no
37
38
  end
38
39
 
39
- def accepts(other, mode: :gradual)
40
- Inference::Acceptance.accepts(self, other, mode: mode)
41
- end
40
+ include Rigor::Type::AcceptanceRouter
42
41
 
43
42
  def ==(other)
44
43
  other.is_a?(Top)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../trinary"
4
+ require_relative "../value_semantics"
5
+ require_relative "acceptance_router"
4
6
 
5
7
  module Rigor
6
8
  module Type
@@ -63,18 +65,11 @@ module Rigor
63
65
  Trinary.no
64
66
  end
65
67
 
66
- def accepts(other, mode: :gradual)
67
- Inference::Acceptance.accepts(self, other, mode: mode)
68
- end
68
+ include Rigor::Type::AcceptanceRouter
69
69
 
70
- def ==(other)
71
- other.is_a?(Tuple) && elements == other.elements
72
- end
73
- alias eql? ==
70
+ include Rigor::ValueSemantics
74
71
 
75
- def hash
76
- [Tuple, elements].hash
77
- end
72
+ value_fields :elements
78
73
 
79
74
  def inspect
80
75
  "#<Rigor::Type::Tuple #{describe(:short)}>"