types 0.4.4 → 0.5.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3785361ab678825d8075ef606e44f2e7515df0037e2c18e13f930709e64a3393
4
- data.tar.gz: c2ac107637b28611d61a29fca64b7a17b7828b4095501cb9bbd2b638aa57e0ca
3
+ metadata.gz: 8fd5cdd213682630df6b1438406870ae7856635ba58d543be3327b0d8d2efe68
4
+ data.tar.gz: f247423d2b76997938605d870fd3b4262bccc71bed5ce53d3bc96e5a97174984
5
5
  SHA512:
6
- metadata.gz: 641614eeb0a0a270a7f29523f7ca733c2fd7bf6a2df2bae494b61d6e9449785b51d29a6fac0c40496682c19abc33865e94c775b9e08707303f5641c7280ef43d
7
- data.tar.gz: faf5130f17a8ec44295157497d20e24df6aa6db5cf7ca193550d53ff0314074e706d2908a79f377e3895ec740a9772abfd7191a151776d4b2278808519c30bc0
6
+ metadata.gz: 7952d06a5940b10e19ada3bd90654628b02417d66e0c7edacb6b4c6a960231b895c48f51f7cbaf15df44aa254f543371be84ca097d05f609577083f571d3ef98
7
+ data.tar.gz: 9e2b0c00186b9d627fccf9215efc4d384cd429a6950c0600fc8dc83971ebad1aeaef19451d6107dd3455d8ad34b25a3afb98f587c5d671ab257933b453e7119b
checksums.yaml.gz.sig CHANGED
Binary file
data/context/usage.md CHANGED
@@ -32,11 +32,11 @@ For more complex types, you can create composite types:
32
32
 
33
33
  ```ruby
34
34
  # Array with specific item type
35
- array_type = Types::Array(Types::Integer)
35
+ array_type = Types::Array.new(Types::Integer)
36
36
  array_type.parse(["1", "2", "3"]) # => [1, 2, 3]
37
37
 
38
38
  # Hash with key and value types
39
- hash_type = Types::Hash(Types::String, Types::Integer)
39
+ hash_type = Types::Hash.new(Types::String, Types::Integer)
40
40
  hash_type.parse("a:1,b:2") # => {"a" => 1, "b" => 2}
41
41
 
42
42
  # Tuple types
@@ -66,8 +66,8 @@ Types.parse("Integer") # => Types::Integer
66
66
  Types.parse("Float") # => Types::Float
67
67
 
68
68
  # Composite types
69
- Types.parse("Array(String)") # => Types::Array(Types::String)
70
- Types.parse("Hash(String, Integer)") # => Types::Hash(Types::String, Types::Integer)
69
+ Types.parse("Array(String)") # => Types::Array.new(Types::String)
70
+ Types.parse("Hash(String, Integer)") # => Types::Hash.new(Types::String, Types::Integer)
71
71
  Types.parse("Tuple(String, Integer)") # => Types::Tuple(Types::String, Types::Integer)
72
72
 
73
73
  # Union types
@@ -127,7 +127,7 @@ Types::Boolean.parse("0") # => false
127
127
  Arrays can parse both string representations and actual arrays:
128
128
 
129
129
  ```ruby
130
- array_type = Types::Array(Types::Integer)
130
+ array_type = Types::Array.new(Types::Integer)
131
131
 
132
132
  # Parse string representation
133
133
  array_type.parse("1,2,3") # => [1, 2, 3]
@@ -142,7 +142,7 @@ array_type.parse(["1", "2", "3"]) # => [1, 2, 3]
142
142
  Hashes can parse string representations:
143
143
 
144
144
  ```ruby
145
- hash_type = Types::Hash(Types::String, Types::Integer)
145
+ hash_type = Types::Hash.new(Types::String, Types::Integer)
146
146
 
147
147
  # Parse string representation
148
148
  hash_type.parse("a:1,b:2,c:3") # => {"a" => 1, "b" => 2, "c" => 3}
@@ -154,7 +154,7 @@ Parsing methods will raise `ArgumentError` for invalid inputs:
154
154
 
155
155
  ```ruby
156
156
  Types::Integer.parse("not_a_number") # => ArgumentError
157
- Types::Array(Types::Integer).parse("invalid") # => ArgumentError
157
+ Types::Array.new(Types::Integer).parse("invalid") # => ArgumentError
158
158
  ```
159
159
 
160
160
  ### Practical Example: Command Line Arguments
@@ -167,7 +167,7 @@ def parse_arguments(arguments)
167
167
  port: Types::Integer.parse(arguments[:port] || "8080"),
168
168
  host: Types::String.parse(arguments[:host] || "localhost"),
169
169
  debug: Types::Boolean.parse(arguments[:debug] || "false"),
170
- tags: Types::Array(Types::String).parse(arguments[:tags] || "")
170
+ tags: Types::Array.new(Types::String).parse(arguments[:tags] || "")
171
171
  }
172
172
 
173
173
  config
data/lib/types/any.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
+ require_relative "parser"
7
+
6
8
  module Types
7
9
  # Represents a union of multiple types. The first type to match the input is used. If no types are specified, this matches any type or value.
8
10
  #
@@ -79,7 +81,7 @@ module Types
79
81
  # Constructs an {Any} type from the given types.
80
82
  # @parameter types [Array(Type)] The types to include in the union.
81
83
  # @returns [Any] a new {Any} type.
82
- def self.Any(*types)
84
+ def PARSER.Any(*types)
83
85
  Any.new(types)
84
86
  end
85
87
  end
data/lib/types/array.rb CHANGED
@@ -4,12 +4,13 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents an array type with a specific item type.
10
11
  #
11
12
  # ```ruby
12
- # type = Types::Array(Types::Integer)
13
+ # type = Types::Array.new(Types::Integer)
13
14
  # type.parse(["1", "2"]) # => [1, 2]
14
15
  # ```
15
16
  class Array
@@ -77,7 +78,7 @@ module Types
77
78
  # Constructs an {Array} type from the given item type.
78
79
  # @parameter item_type [Type] The type of the array elements.
79
80
  # @returns [Array] a new {Array} type.
80
- def self.Array(item_type = Any)
81
+ def PARSER.Array(item_type = Any)
81
82
  Array.new(item_type)
82
83
  end
83
84
  end
data/lib/types/block.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "method"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents a block (Proc) type with argument and return types.
@@ -55,7 +56,7 @@ module Types
55
56
  # @parameter argument_types [Array(Type)] The types of the block arguments.
56
57
  # @parameter returns [Type | Nil] The return type of the block.
57
58
  # @returns [Block] a new {Block} type.
58
- def self.Block(*argument_types, returns: nil)
59
+ def PARSER.Block(*argument_types, returns: nil)
59
60
  Block.new(argument_types, returns)
60
61
  end
61
62
  end
data/lib/types/class.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents a class type, optionally constrained to a base class.
@@ -86,7 +87,7 @@ module Types
86
87
  # Constructs a {Class} type with an optional base class constraint.
87
88
  # @parameter base [Class] The base class constraint.
88
89
  # @returns [Class] a new {Class} type.
89
- def self.Class(base = Object)
90
+ def PARSER.Class(base = Object)
90
91
  Class.new(base)
91
92
  end
92
93
  end
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents an enumerator type with a specific item type.
@@ -67,7 +68,7 @@ module Types
67
68
  # Constructs an {Enumerator} type from the given item type.
68
69
  # @parameter item_type [Type] The type of the enumerator elements.
69
70
  # @returns [Enumerator] a new {Enumerator} type.
70
- def self.Enumerator(item_type = Any)
71
+ def PARSER.Enumerator(item_type = Any)
71
72
  Enumerator.new(item_type)
72
73
  end
73
74
  end
data/lib/types/hash.rb CHANGED
@@ -4,12 +4,13 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents a hash type with key and value types.
10
11
  #
11
12
  # ```ruby
12
- # type = Types::Hash(Types::String, Types::Integer)
13
+ # type = Types::Hash.new(Types::String, Types::Integer)
13
14
  # type.parse({"foo" => "42"}) # => {"foo" => 42}
14
15
  # ```
15
16
  class Hash
@@ -73,7 +74,7 @@ module Types
73
74
  # @parameter key_type [Type] The type of the hash keys.
74
75
  # @parameter value_type [Type] The type of the hash values.
75
76
  # @returns [Hash] a new {Hash} type.
76
- def self.Hash(key_type, value_type)
77
+ def PARSER.Hash(key_type, value_type)
77
78
  Hash.new(key_type, value_type)
78
79
  end
79
80
  end
@@ -4,29 +4,25 @@
4
4
  # Copyright, 2024-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
- require_relative "method"
8
- require_relative "any"
9
7
 
10
8
  module Types
11
- # Represents an interface type, defined by required method names.
9
+ # Represents an RBS interface type.
12
10
  #
13
11
  # ```ruby
14
- # type = Types::Interface(
15
- # foo: Method.new(Any, returns: Any),
16
- # bar: Method.new(Any, returns: Any)
17
- # )
18
- # type.valid?(obj) # => true if obj responds to :foo and :bar
12
+ # type = Types::Interface.new("MyInterface")
13
+ # type.to_rbs # => "_MyInterface"
14
+ # type.to_s # => "Interface(MyInterface)"
19
15
  # ```
20
16
  class Interface
21
17
  include Generic
22
18
 
23
- # @parameter methods [Array(Symbol)] The required method names.
24
- def initialize(methods)
25
- @methods = methods
19
+ # @parameter name [String] The interface name.
20
+ def initialize(name)
21
+ @name = name
26
22
  end
27
23
 
28
- # @returns [Array(Symbol)] The required method names.
29
- attr :methods
24
+ # @returns [String] The interface name.
25
+ attr :name
30
26
 
31
27
  # @returns [Boolean] true if this is a composite type.
32
28
  def composite?
@@ -35,66 +31,37 @@ module Types
35
31
 
36
32
  # @returns [String] the string representation of the interface type.
37
33
  def to_s
38
- buffer = ::String.new
39
-
40
- buffer << "Interface("
41
-
42
- if @methods&.any?
43
- first = true
44
- @methods.each do |name, method|
45
- if first
46
- first = false
47
- else
48
- buffer << ", "
49
- end
50
-
51
- buffer << "#{name}: #{method}"
52
- end
53
- end
54
-
55
- buffer << ")"
56
-
57
- return buffer
34
+ "Interface(#{@name})"
58
35
  end
59
36
 
60
- # Checks if the given instance responds to all required methods.
61
- # @parameter instance [Object] The object to check.
62
- # @returns [Boolean] true if the instance responds to all methods.
63
- def valid?(instance)
64
- @methods.all? do |name, method|
65
- instance.respond_to?(name)
37
+ # @returns [String] the RBS interface representation with underscore prefix on the last component.
38
+ def to_rbs
39
+ if @name.include?("::")
40
+ # For nested interfaces like "MyLibrary::MyInterface", return "MyLibrary::_MyInterface"
41
+ parts = @name.split("::")
42
+ parts[-1] = "_#{parts[-1]}"
43
+ parts.join("::")
44
+ else
45
+ # For simple interfaces like "MyInterface", return "_MyInterface"
46
+ "_#{@name}"
66
47
  end
67
48
  end
68
49
 
69
- # Parses the input as an object and checks if it matches the interface.
70
- # @parameter input [Object] The value to parse.
71
- # @returns [Object] the input if it matches the interface.
72
- # @raises [ArgumentError] if the input does not match the interface.
73
- def parse(input)
74
- case input
75
- when ::String
76
- instance = eval(input)
77
- else
78
- instance = input
79
- end
80
-
81
- if valid?(instance)
82
- return instance
83
- else
84
- raise ArgumentError, "Cannot coerce #{input.inspect} into #{self}!"
85
- end
50
+ # @returns [Boolean] true if other is an Interface with the same name.
51
+ def == other
52
+ other.is_a?(Interface) && @name == other.name
53
+ end
54
+
55
+ # @returns [Integer] hash code based on the name.
56
+ def hash
57
+ @name.hash
86
58
  end
87
59
  end
88
60
 
89
- # Constructs an {Interface} type from the given method names.
90
- # @parameter methods [Array(Symbol)] The required method names.
61
+ # Constructs an {Interface} type from the given name.
62
+ # @parameter name [String, Symbol] The interface name.
91
63
  # @returns [Interface] a new {Interface} type.
92
- def self.Interface(*names, **methods)
93
- # This is a deprecated way to create an interfaces with names only. Ideally, we actually need to know the methods.
94
- names.each do |name|
95
- methods[name.to_sym] = Method.new(Any(), [], Any())
96
- end
97
-
98
- Interface.new(methods)
64
+ def PARSER.Interface(name)
65
+ Interface.new(name.to_s)
99
66
  end
100
67
  end
data/lib/types/lambda.rb CHANGED
@@ -61,7 +61,7 @@ module Types
61
61
  # @parameter argument_types [Array(Type)] The types of the lambda arguments.
62
62
  # @parameter returns [Type | Nil] The return type of the lambda.
63
63
  # @returns [Lambda] a new {Lambda} type.
64
- def self.Lambda(*argument_types, returns: nil)
64
+ def PARSER.Lambda(*argument_types, returns: nil)
65
65
  Lambda.new(argument_types, returns)
66
66
  end
67
67
  end
data/lib/types/method.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
+ require_relative "parser"
7
8
 
8
9
  module Types
9
10
  # Represents a method type attached to a receiver type.
@@ -67,11 +68,11 @@ module Types
67
68
  def parse(input)
68
69
  case input
69
70
  when ::String
70
- receiver_type.instance_method(input)
71
+ receiver_type.resolve.instance_method(input)
71
72
  when ::Proc
72
73
  input
73
74
  else
74
- raise ArgumentError, "Cannot coerce #{input.inpsect} into Method!"
75
+ raise ArgumentError, "Cannot coerce #{input.inspect} into Method!"
75
76
  end
76
77
  end
77
78
  end
@@ -81,7 +82,7 @@ module Types
81
82
  # @parameter argument_types [Array(Type)] The types of the method arguments.
82
83
  # @parameter returns [Type | Nil] The return type of the method.
83
84
  # @returns [Method] a new {Method} type.
84
- def self.Method(receiver_type, *argument_types, returns: nil)
85
+ def PARSER.Method(receiver_type, *argument_types, returns: nil)
85
86
  Method.new(receiver_type, argument_types, returns)
86
87
  end
87
88
  end
data/lib/types/named.rb CHANGED
@@ -53,12 +53,24 @@ module Types
53
53
 
54
54
  # Resolves the named type to the actual Ruby type if it exists.
55
55
  # @returns [Class | Module | Nil] The resolved Ruby type or nil if not found.
56
- def resolve
57
- Object.const_get(@name)
56
+ def resolve(relative_to: Object)
57
+ relative_to.const_get(@name)
58
58
  rescue NameError
59
59
  nil
60
60
  end
61
61
 
62
+ def to_type
63
+ resolve(relative_to: Types)
64
+ end
65
+
66
+ def parse(value)
67
+ if type = self.to_type
68
+ type.parse(value)
69
+ else
70
+ raise ArgumentError, "Cannot parse value #{value.inspect} with unknown type #{@name}!"
71
+ end
72
+ end
73
+
62
74
  # @returns [String] the RBS type string using the name.
63
75
  def to_rbs
64
76
  @name
@@ -100,7 +112,7 @@ module Types
100
112
  # Constructs a {Named} type with the given name.
101
113
  # @parameter name [String] The name of the type.
102
114
  # @returns [Named] a new {Named} type.
103
- def self.Named(name)
115
+ def PARSER.Named(name)
104
116
  Named.new(name)
105
117
  end
106
118
  end
@@ -0,0 +1,42 @@
1
+
2
+ # Parses type signatures, must be defined at the top level for the purpose of name resolution.
3
+ class Types::Parser < BasicObject
4
+ # Handles absolute type paths by creating absolute Named types for unknown types.
5
+ module TOP
6
+ def self.const_missing(name)
7
+ ::Types::Named.new("::#{name}")
8
+ end
9
+ end
10
+
11
+ # Handles missing constants by creating Named types for unknown types.
12
+ # This allows parsing of type signatures with unknown types.
13
+ # @parameter name [Symbol] The name of the missing constant.
14
+ # @returns [Named] A Named type representing the unknown type.
15
+ def self.const_missing(name)
16
+ ::Types::Named.new(name.to_s)
17
+ end
18
+
19
+ def parse(signature)
20
+ # Validate the signature format:
21
+ unless signature.match?(/\A[a-zA-Z0-9\(\):,_|\s]+\z/)
22
+ ::Kernel.raise ::ArgumentError, "Invalid type signature: #{signature.inspect}!"
23
+ end
24
+
25
+ # Replace leading :: with TOP:: to handle absolute type paths
26
+ normalized_signature = signature.gsub(/(?<=\A|\W)::/, "TOP::")
27
+
28
+ binding = ::Kernel.binding
29
+
30
+ return ::Kernel.eval(normalized_signature, binding)
31
+ end
32
+ end
33
+
34
+ module Types
35
+ PARSER = Parser.new
36
+
37
+ self.define_singleton_method(:parse) do |signature|
38
+ PARSER.parse(signature)
39
+ end
40
+
41
+ remove_const :Parser
42
+ end
data/lib/types/tuple.rb CHANGED
@@ -74,7 +74,7 @@ module Types
74
74
  # Constructs a {Tuple} type from the given item types.
75
75
  # @parameter item_types [Array(Type)] The types of the tuple elements.
76
76
  # @returns [Tuple] a new {Tuple} type.
77
- def self.Tuple(*item_types)
77
+ def PARSER.Tuple(*item_types)
78
78
  Tuple.new(item_types)
79
79
  end
80
80
  end
data/lib/types/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # Copyright, 2022-2025, by Samuel Williams.
5
5
 
6
6
  module Types
7
- VERSION = "0.4.4"
7
+ VERSION = "0.5.0"
8
8
  end
data/lib/types.rb CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  require_relative "types/version"
7
7
 
8
+ require_relative "types/parser"
9
+
8
10
  require_relative "types/any"
9
11
  require_relative "types/array"
10
12
  require_relative "types/block"
@@ -27,44 +29,4 @@ require_relative "types/tuple"
27
29
 
28
30
  # @namespace
29
31
  module Types
30
- # The main module for the types library.
31
- #
32
- # Provides parsing and construction of type signatures.
33
- #
34
- # ```ruby
35
- # Types.parse("Array(String)") # => Types::Array(Types::String)
36
- # ```
37
- VALID_SIGNATURE = /\A[a-zA-Z0-9\(\):,_|\s]+\z/
38
-
39
- # Parses a type signature string and returns the corresponding type instance.
40
- # @parameter signature [String] The type signature to parse.
41
- # @returns [Object] The type instance.
42
- # @raises [ArgumentError] if the signature is invalid.
43
- def self.parse(signature)
44
- if signature =~ VALID_SIGNATURE
45
- # Replace leading :: with Top:: to handle absolute type paths
46
- normalized_signature = signature.gsub(/(?<=\A|\W)::/, "TOP::")
47
- eval(normalized_signature, binding)
48
- else
49
- raise ArgumentError, "Invalid type signature: #{signature.inspect}!"
50
- end
51
- end
52
-
53
- # Handles absolute type paths by creating absolute Named types for unknown types.
54
- module TOP
55
- def self.const_missing(name)
56
- Named.new("::#{name}")
57
- end
58
- end
59
-
60
- # Handles missing constants by creating Named types for unknown types.
61
- # This allows parsing of type signatures with unknown types.
62
- # @parameter name [Symbol] The name of the missing constant.
63
- # @returns [Named] A Named type representing the unknown type.
64
- def self.const_missing(name)
65
- Named(name.to_s)
66
- end
67
-
68
- # Bare classes and modules should be convertable to RBS strings.
69
- Module.alias_method(:to_rbs, :to_s)
70
32
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: types
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -62,6 +62,7 @@ files:
62
62
  - lib/types/named.rb
63
63
  - lib/types/nil.rb
64
64
  - lib/types/numeric.rb
65
+ - lib/types/parser.rb
65
66
  - lib/types/string.rb
66
67
  - lib/types/symbol.rb
67
68
  - lib/types/tuple.rb
metadata.gz.sig CHANGED
Binary file