Class: Rigor::Type::Refined
- Inherits:
-
Object
- Object
- Rigor::Type::Refined
- Includes:
- AcceptanceRouter, ValueSemantics
- Defined in:
- lib/rigor/type/refined.rb
Overview
‘Refined[base, predicate_id]` — predicate-subset half of the OQ3 refinement-carrier strategy ([ADR-3](docs/adr/3-type-representation.md), Working Decision Option C). Sibling of `Type::Difference`, which carries the point-removal half.
lowercase-string = Refined[Nominal[String], :lowercase]
uppercase-string = Refined[Nominal[String], :uppercase]
numeric-string = Refined[Nominal[String], :numeric]
The carrier wraps a base type and a ‘predicate_id` Symbol drawn from PREDICATES. The recogniser is invoked at constant-fold and acceptance time over a `Constant<base>` value; for non-Constant receivers the carrier is a marker the catalog tier consults to project `String#downcase` / `String#upcase` (etc.) into the matching refinement.
Display routes through CANONICAL_NAMES: registered ‘(base_class_name, predicate_id)` pairs print in their kebab-case spelling (`lowercase-string`); unregistered combinations fall back to the `base & predicate?` operator form per [`type-operators.md`](docs/type-specification/type-operators.md).
Construction MUST go through ‘Type::Combinator.refined` / the per-name factories (`Combinator.lowercase_string`, `Combinator.uppercase_string`, `Combinator.numeric_string`). Direct `.new` is an internal escape hatch for tests and combinator’s own implementation.
Constant Summary collapse
- PREDICATES =
{ lowercase: ->(v) { v.is_a?(String) && v == v.downcase }, not_lowercase: ->(v) { v.is_a?(String) && v != v.downcase }, uppercase: ->(v) { v.is_a?(String) && v == v.upcase }, not_uppercase: ->(v) { v.is_a?(String) && v != v.upcase }, numeric: ->(v) { ruby_numeric_literal?(v) }, not_numeric: ->(v) { v.is_a?(String) && !ruby_numeric_literal?(v) }, decimal_int: ->(v) { v.is_a?(String) && DECIMAL_INT_STRING_PATTERN.match?(v) }, octal_int: ->(v) { v.is_a?(String) && OCTAL_INT_STRING_PATTERN.match?(v) }, hex_int: ->(v) { v.is_a?(String) && HEX_INT_STRING_PATTERN.match?(v) }, # `literal-string` is a flow-tracked predicate, not a value- # level predicate: a String is literal-string when it is # known to come from a source-code literal (or composition # of literals). Every concrete `Constant<String>` is # already literal by construction, so the inspection # recogniser returns true for any String — the property is # really tracked in the flow analysis (interpolation, # concatenation, RBS::Extended `return: literal-string`) # rather than recovered by inspecting an arbitrary string. literal_string: ->(v) { v.is_a?(String) } }.freeze
Instance Attribute Summary collapse
-
#base ⇒ Object
readonly
Returns the value of attribute base.
-
#predicate_id ⇒ Object
readonly
Returns the value of attribute predicate_id.
Class Method Summary collapse
-
.ruby_numeric_literal?(value) ⇒ Boolean
True when ‘value` is a String that is a single, complete Ruby numeric literal.
Instance Method Summary collapse
- #bot ⇒ Object
-
#complement_predicate_id ⇒ Symbol?
The registered complement predicate id, or nil when no pair is registered for this predicate.
- #describe(verbosity = :short) ⇒ Object
- #dynamic ⇒ Object
-
#erase_to_rbs ⇒ Object
Erases to the base nominal: every refinement MUST erase to its base per [‘rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
-
#initialize(base, predicate_id) ⇒ Refined
constructor
A new instance of Refined.
- #inspect ⇒ Object
-
#matches?(value) ⇒ Boolean
Recognises a Ruby value against this carrier’s predicate.
- #top ⇒ Object
Methods included from ValueSemantics
Methods included from AcceptanceRouter
Constructor Details
#initialize(base, predicate_id) ⇒ Refined
Returns a new instance of Refined.
43 44 45 46 47 48 49 |
# File 'lib/rigor/type/refined.rb', line 43 def initialize(base, predicate_id) raise ArgumentError, "predicate_id must be a Symbol" unless predicate_id.is_a?(Symbol) @base = base @predicate_id = predicate_id freeze end |
Instance Attribute Details
#base ⇒ Object (readonly)
Returns the value of attribute base.
41 42 43 |
# File 'lib/rigor/type/refined.rb', line 41 def base @base end |
#predicate_id ⇒ Object (readonly)
Returns the value of attribute predicate_id.
41 42 43 |
# File 'lib/rigor/type/refined.rb', line 41 def predicate_id @predicate_id end |
Class Method Details
.ruby_numeric_literal?(value) ⇒ Boolean
Returns true when ‘value` is a String that is a single, complete Ruby numeric literal. Total over arbitrary input — never raises (Prism reports malformed input through `errors`, it does not throw).
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/rigor/type/refined.rb', line 176 def self.ruby_numeric_literal?(value) return false unless value.is_a?(String) return false if value.empty? # A numeric literal carries no whitespace; reject any # leading / trailing / interior space so the *whole* string # must be the literal (Prism would otherwise accept a # trailing-space `"1 "`). return false if value.match?(/\s/) return false unless value.match?(NUMERIC_LITERAL_PREFIX) result = Prism.parse(value) return false unless result.errors.empty? body = result.value.statements&.body return false unless body && body.size == 1 node = body.first NUMERIC_LITERAL_NODES.any? { |klass| node.is_a?(klass) } end |
Instance Method Details
#complement_predicate_id ⇒ Symbol?
Returns the registered complement predicate id, or nil when no pair is registered for this predicate.
269 270 271 |
# File 'lib/rigor/type/refined.rb', line 269 def complement_predicate_id COMPLEMENT_PAIRS[predicate_id] end |
#describe(verbosity = :short) ⇒ Object
51 52 53 54 55 56 |
# File 'lib/rigor/type/refined.rb', line 51 def describe(verbosity = :short) named = canonical_name return named if named "#{base.describe(verbosity)} & #{predicate_id}?" end |
#dynamic ⇒ Object
72 73 74 |
# File 'lib/rigor/type/refined.rb', line 72 def dynamic base.respond_to?(:dynamic) ? base.dynamic : Trinary.no end |
#erase_to_rbs ⇒ Object
Erases to the base nominal: every refinement MUST erase to its base per [‘rbs-erasure.md`](docs/type-specification/rbs-erasure.md).
60 61 62 |
# File 'lib/rigor/type/refined.rb', line 60 def erase_to_rbs base.erase_to_rbs end |
#inspect ⇒ Object
82 83 84 |
# File 'lib/rigor/type/refined.rb', line 82 def inspect "#<Rigor::Type::Refined #{describe(:short)}>" end |
#matches?(value) ⇒ Boolean
Recognises a Ruby value against this carrier’s predicate. The trinary return is intentional: ‘true` / `false` when the predicate registry decides, `nil` when the predicate is unknown to the registry, so callers (today Inference::Acceptance) can fall through to gradual-mode `:maybe`. rubocop:disable Style/ReturnNilInPredicateMethodDefinition
93 94 95 96 97 98 |
# File 'lib/rigor/type/refined.rb', line 93 def matches?(value) recogniser = PREDICATES[predicate_id] return nil if recogniser.nil? !!recogniser.call(value) end |