Class: Rigor::Type::Union
- Inherits:
-
Object
- Object
- Rigor::Type::Union
- Defined in:
- lib/rigor/type/union.rb
Overview
A normalized non-empty union of two or more distinct types. Unions are constructed exclusively through Rigor::Type::Combinator.union, which flattens nested unions, deduplicates structurally-equal members, and collapses single-member or empty results to the appropriate scalar type. Direct calls to .new are an internal contract: callers MUST pass an already-normalized members array.
See docs/type-specification/normalization.md.
Instance Attribute Summary collapse
-
#members ⇒ Object
readonly
Returns the value of attribute members.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
- #accepts(other, mode: :gradual) ⇒ Object
- #bot ⇒ Object
-
#describe(verbosity = :short) ⇒ Object
Display-only adoption of two concise RBS spellings for the union (see docs/type-specification/normalization.md § “Interaction with display” and rbs-compatible-types.md § “Optionals”).
- #dynamic ⇒ Object
-
#erase_to_rbs ⇒ Object
ADR-1 § “RBS round-trip is lossless” + the value-lattice rule ‘untyped | T = untyped` (every `T` is gradually consistent with `untyped`).
- #hash ⇒ Object
-
#initialize(members) ⇒ Union
constructor
A new instance of Union.
- #inspect ⇒ Object
- #top ⇒ Object
Constructor Details
#initialize(members) ⇒ Union
Returns a new instance of Union.
18 19 20 21 22 23 24 25 |
# File 'lib/rigor/type/union.rb', line 18 def initialize(members) unless members.is_a?(Array) && members.size >= 2 raise ArgumentError, "Union requires at least two members; use Combinator.union for normalization" end @members = members.freeze freeze end |
Instance Attribute Details
#members ⇒ Object (readonly)
Returns the value of attribute members.
16 17 18 |
# File 'lib/rigor/type/union.rb', line 16 def members @members end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
93 94 95 |
# File 'lib/rigor/type/union.rb', line 93 def ==(other) other.is_a?(Union) && members == other.members end |
#accepts(other, mode: :gradual) ⇒ Object
89 90 91 |
# File 'lib/rigor/type/union.rb', line 89 def accepts(other, mode: :gradual) Inference::Acceptance.accepts(self, other, mode: mode) end |
#describe(verbosity = :short) ⇒ Object
Display-only adoption of two concise RBS spellings for the union (see docs/type-specification/normalization.md § “Interaction with display” and rbs-compatible-types.md § “Optionals”). Both are purely cosmetic: ‘@members` keeps every carrier verbatim, so the underlying type identity, RBS erasure, and round-trip are unchanged — only the human-facing rendering reads like the RBS the user wrote.
* `true | false` → `bool` (the RBS boolean alias). The
`bool` token leads the rendering, so `false | Foo | true` reads
as `bool | Foo` rather than burying the pair mid-list.
* `T | nil` → `T?` (the RBS optional sugar). Only
applied when exactly one *logical* member remains beside `nil`,
matching the rbs gem's own `to_s`: a multi-member union such as
`Integer | String | nil` stays explicit rather than gaining a
parenthesised `(Integer | String)?`. The two collapses compose,
so `false | true | nil` reads as `bool?`.
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/rigor/type/union.rb', line 43 def describe(verbosity = :short) return "#{optional_inner(verbosity)}?" if optional? if boolean_pair? rest = members.reject { |m| boolean_literal?(m) } ["bool", *rest.map { |m| m.describe(verbosity) }].join(" | ") else members.map { |m| m.describe(verbosity) }.join(" | ") end end |
#dynamic ⇒ Object
85 86 87 |
# File 'lib/rigor/type/union.rb', line 85 def dynamic members.any? { |m| m.respond_to?(:dynamic) && m.dynamic.yes? } ? Trinary.maybe : Trinary.no end |
#erase_to_rbs ⇒ Object
ADR-1 § “RBS round-trip is lossless” + the value-lattice rule ‘untyped | T = untyped` (every `T` is gradually consistent with `untyped`). When any union member erases to `“untyped”`, the whole union erases to `“untyped”` —the RBS surface has no carrier for “Dynamic-origin alongside a static facet”, and the gradual-consistency contract guarantees the substitution is sound at every call site.
Post-erasure dedupe removes ‘String | String` artefacts that arise when two structurally-distinct `Constant` carriers (e.g. `Constant<“Alice”>` / `Constant<“Bob”>`) share an RBS-erased envelope. The members themselves are already structurally deduped at construction by `Type::Combinator.union`, but the post-erase strings can collide.
70 71 72 73 74 75 |
# File 'lib/rigor/type/union.rb', line 70 def erase_to_rbs erased = members.map(&:erase_to_rbs) return "untyped" if erased.include?("untyped") erased.uniq.join(" | ") end |
#hash ⇒ Object
98 99 100 |
# File 'lib/rigor/type/union.rb', line 98 def hash [Union, members].hash end |
#inspect ⇒ Object
102 103 104 |
# File 'lib/rigor/type/union.rb', line 102 def inspect "#<Rigor::Type::Union #{describe(:short)}>" end |