Class: Rigor::Type::HashShape

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/type/hash_shape.rb

Overview

A hash shape with statically known keys. Inhabitants are Ruby ‘Hash` instances whose known entries inhabit the corresponding value types. RBS records correspond to the exact closed subset; Rigor extends that carrier with optional keys, read-only entry views, and an open/closed extra-key policy.

Keys are restricted to Symbol and String values. Exact closed symbol-keyed shapes erase to the RBS record syntax ‘{ a: Integer, ?b: String }`; all other shapes degrade to `Hash[K, V]` or raw `Hash` when no useful bounds are available.

Equality and hashing are structural over the (key -> Rigor::Type) pair set and policy fields. Hash insertion order is preserved by the underlying storage but does NOT affect equality (matching Ruby’s ‘Hash#==`).

See docs/type-specification/rbs-compatible-types.md (records) and docs/type-specification/rigor-extensions.md (hash shape). rubocop:disable Metrics/ClassLength

Constant Summary collapse

ALLOWED_KEY_CLASSES =
[Symbol, String].freeze
EXTRA_KEY_POLICIES =
%i[open closed].freeze
POLICY_KEYWORDS =
%i[required_keys optional_keys read_only_keys extra_keys].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pairs = nil, **keywords) ⇒ HashShape

Returns a new instance of HashShape.

Parameters:

  • pairs (Hash{Symbol|String => Rigor::Type}) (defaults to: nil)

    ordered map of keys to declared types. Keys MUST be Symbol or String; values MUST be Rigor::Type instances. The hash is duped and frozen at construction; callers MUST NOT mutate the input afterwards (mutation does not affect the carrier, but the carrier is a value object).

  • required_keys (Array<Symbol|String>, nil)

    keys that MUST be present. When omitted, every non-optional key is required. When supplied without optional_keys, every remaining known key is treated as optional.

  • optional_keys (Array<Symbol|String>, nil)

    keys that MAY be absent. Optional absence is not a stored nil.

  • read_only_keys (Array<Symbol|String>)

    entries that cannot be written through this shape view.

  • extra_keys (Symbol)

    :closed rejects keys outside pairs; :open permits them.



49
50
51
52
53
54
55
56
# File 'lib/rigor/type/hash_shape.rb', line 49

def initialize(pairs = nil, **keywords)
  pairs, policy = split_constructor_args(pairs, keywords)
  validate_pairs!(pairs)

  @pairs = pairs.dup.freeze
  apply_policy!(policy)
  freeze
end

Instance Attribute Details

#extra_keysObject (readonly)

Returns the value of attribute extra_keys.



31
32
33
# File 'lib/rigor/type/hash_shape.rb', line 31

def extra_keys
  @extra_keys
end

#optional_keysObject (readonly)

Returns the value of attribute optional_keys.



31
32
33
# File 'lib/rigor/type/hash_shape.rb', line 31

def optional_keys
  @optional_keys
end

#pairsObject (readonly)

Returns the value of attribute pairs.



31
32
33
# File 'lib/rigor/type/hash_shape.rb', line 31

def pairs
  @pairs
end

#read_only_keysObject (readonly)

Returns the value of attribute read_only_keys.



31
32
33
# File 'lib/rigor/type/hash_shape.rb', line 31

def read_only_keys
  @read_only_keys
end

#required_keysObject (readonly)

Returns the value of attribute required_keys.



31
32
33
# File 'lib/rigor/type/hash_shape.rb', line 31

def required_keys
  @required_keys
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



114
115
116
117
118
119
120
121
# File 'lib/rigor/type/hash_shape.rb', line 114

def ==(other)
  other.is_a?(HashShape) &&
    pairs == other.pairs &&
    required_keys == other.required_keys &&
    optional_keys == other.optional_keys &&
    read_only_keys == other.read_only_keys &&
    extra_keys == other.extra_keys
end

#accepts(other, mode: :gradual) ⇒ Object



110
111
112
# File 'lib/rigor/type/hash_shape.rb', line 110

def accepts(other, mode: :gradual)
  Inference::Acceptance.accepts(self, other, mode: mode)
end

#botObject



102
103
104
# File 'lib/rigor/type/hash_shape.rb', line 102

def bot
  Trinary.no
end

#closed?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/rigor/type/hash_shape.rb', line 82

def closed?
  extra_keys == :closed
end

#describe(verbosity = :short) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/rigor/type/hash_shape.rb', line 58

def describe(verbosity = :short)
  return "{}" if pairs.empty?

  rendered = pairs.map { |k, v| render_entry(k, v, verbosity) }
  rendered << "..." if open?
  "{ #{rendered.join(', ')} }"
end

#dynamicObject



106
107
108
# File 'lib/rigor/type/hash_shape.rb', line 106

def dynamic
  Trinary.no
end

#erase_to_rbsObject

Erases to the RBS record form ‘{ a: Integer, ?b: String }` for exact closed symbol-keyed shapes. Open shapes and string-keyed closed shapes degrade to a generic Hash bound.



69
70
71
72
73
74
75
76
# File 'lib/rigor/type/hash_shape.rb', line 69

def erase_to_rbs
  return "{}" if pairs.empty? && closed?
  return hash_erasure unless closed?
  return hash_erasure if pairs.each_key.any? { |k| !k.is_a?(Symbol) }

  rendered = pairs.map { |k, v| "#{record_key(k)}: #{v.erase_to_rbs}" }
  "{ #{rendered.join(', ')} }"
end

#hashObject



124
125
126
# File 'lib/rigor/type/hash_shape.rb', line 124

def hash
  [HashShape, pairs, required_keys, optional_keys, read_only_keys, extra_keys].hash
end

#inspectObject



128
129
130
# File 'lib/rigor/type/hash_shape.rb', line 128

def inspect
  "#<Rigor::Type::HashShape #{describe(:short)}>"
end

#open?Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/rigor/type/hash_shape.rb', line 78

def open?
  extra_keys == :open
end

#optional_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/rigor/type/hash_shape.rb', line 90

def optional_key?(key)
  optional_keys.include?(key)
end

#read_only_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/rigor/type/hash_shape.rb', line 94

def read_only_key?(key)
  read_only_keys.include?(key)
end

#required_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


86
87
88
# File 'lib/rigor/type/hash_shape.rb', line 86

def required_key?(key)
  required_keys.include?(key)
end

#topObject



98
99
100
# File 'lib/rigor/type/hash_shape.rb', line 98

def top
  Trinary.no
end