types 0.3.0 → 0.4.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/types/any.rb +17 -6
- data/lib/types/array.rb +6 -0
- data/lib/types/boolean.rb +7 -1
- data/lib/types/class.rb +17 -2
- data/lib/types/decimal.rb +11 -2
- data/lib/types/float.rb +6 -0
- data/lib/types/hash.rb +6 -0
- data/lib/types/integer.rb +6 -0
- data/lib/types/method.rb +1 -1
- data/lib/types/named.rb +106 -0
- data/lib/types/nil.rb +6 -0
- data/lib/types/string.rb +6 -0
- data/lib/types/symbol.rb +6 -0
- data/lib/types/tuple.rb +7 -1
- data/lib/types/version.rb +2 -2
- data/lib/types.rb +21 -3
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21a7be2b61d63c443516b9dcd51088fd9febe5b4a75361d99ccf0c3348e15261
|
4
|
+
data.tar.gz: 76ad7f162e268407e20a17cb2cf89701675a9672ed352ba27c4746d9bdabd3a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5172ce882b220e0555aea69d66568b53968a3f07139596a00864c72cadfc9a729ccabb4fa34bad26ff8b356e4b2eb52e2596540777fd80d2b9c3fcc4a49b297f
|
7
|
+
data.tar.gz: dad9326a9a932c28d413dfe30c6b19451d2f6c5e8e2f302ec8b4d4b2a469a9b9e7a52bad8571bb809e8d0e39f934f97e0635aae7c09b6e845020b2d7a47a570b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/types/any.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
# Copyright, 2022-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
module Types
|
7
|
-
# Represents a union of multiple types. The first type to match the input is used.
|
7
|
+
# 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
8
|
#
|
9
9
|
# ```ruby
|
10
10
|
# type = Types::Any(Types::String, Types::Integer)
|
@@ -15,23 +15,29 @@ module Types
|
|
15
15
|
def initialize(types)
|
16
16
|
@types = types
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
# @returns [Any] a new {Any} with the other type appended.
|
20
20
|
# @parameter other [Type] The type instance to append.
|
21
21
|
def | other
|
22
22
|
self.class.new([*@types, other])
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
# @returns [Boolean] true if any of the listed types is composite.
|
26
26
|
def composite?
|
27
|
-
@types.any?
|
27
|
+
@types.any?(&:composite?)
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Parses the input using the listed types in order, returning the first one that succeeds.
|
31
31
|
# @parameter input [String] the input to parse.
|
32
32
|
# @returns [Object] the parsed value.
|
33
33
|
# @raises [ArgumentError] if no type can parse the input.
|
34
34
|
def parse(input)
|
35
|
+
# If there are no types, we can just return the input.
|
36
|
+
return input if @types.empty?
|
37
|
+
|
38
|
+
# We need to track the last error, because we want to raise the last error that occurred, if any:
|
39
|
+
last_error = nil
|
40
|
+
|
35
41
|
@types.each do |type|
|
36
42
|
return type.parse(input)
|
37
43
|
rescue => error
|
@@ -59,9 +65,14 @@ module Types
|
|
59
65
|
end
|
60
66
|
end
|
61
67
|
|
68
|
+
# Returns the RBS type string for the union of the listed types. If there are no types, it returns `untyped`.
|
62
69
|
# @returns [String] the RBS type string, e.g. `String | Integer`.
|
63
70
|
def to_rbs
|
64
|
-
@types.
|
71
|
+
if @types.empty?
|
72
|
+
"untyped"
|
73
|
+
else
|
74
|
+
@types.map(&:to_rbs).join(" | ")
|
75
|
+
end
|
65
76
|
end
|
66
77
|
end
|
67
78
|
|
data/lib/types/array.rb
CHANGED
data/lib/types/boolean.rb
CHANGED
@@ -29,10 +29,16 @@ module Types
|
|
29
29
|
raise ArgumentError, "Cannot coerce #{input.inspect} into Boolean!"
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
# @returns [String] the RBS type string, e.g. `bool`.
|
34
34
|
def self.to_rbs
|
35
35
|
"bool"
|
36
36
|
end
|
37
|
+
|
38
|
+
# Resolves to nil since Ruby doesn't have a single Boolean class.
|
39
|
+
# @returns [nil] Returns nil as there's no single Ruby Boolean class.
|
40
|
+
def self.resolve
|
41
|
+
nil
|
42
|
+
end
|
37
43
|
end
|
38
44
|
end
|
data/lib/types/class.rb
CHANGED
@@ -29,15 +29,24 @@ module Types
|
|
29
29
|
true
|
30
30
|
end
|
31
31
|
|
32
|
+
# Resolve the base class if possible.
|
33
|
+
# @returns [Class] the resolved base class.
|
34
|
+
def resolve
|
35
|
+
Object.const_get(@base.to_s)
|
36
|
+
rescue NameError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
32
40
|
# Parses the input as a class, optionally checking the base class constraint.
|
33
41
|
# @parameter input [String] The class name to parse.
|
34
42
|
# @returns [Class] the parsed class.
|
35
43
|
# @raises [ArgumentError] if the class is not a subclass of the base.
|
36
44
|
def parse(input)
|
37
45
|
klass = Object.const_get(input)
|
46
|
+
base = self.resolve
|
38
47
|
|
39
|
-
if
|
40
|
-
raise ArgumentError, "Class #{klass} is not a subclass of #{
|
48
|
+
if base and !klass.ancestors.include?(base)
|
49
|
+
raise ArgumentError, "Class #{klass} is not a subclass of #{base}!"
|
41
50
|
end
|
42
51
|
|
43
52
|
return klass
|
@@ -66,6 +75,12 @@ module Types
|
|
66
75
|
|
67
76
|
return klass
|
68
77
|
end
|
78
|
+
|
79
|
+
# Resolves to the actual Ruby Class class.
|
80
|
+
# @returns [Class] The Class class.
|
81
|
+
def self.resolve
|
82
|
+
::Class
|
83
|
+
end
|
69
84
|
end
|
70
85
|
|
71
86
|
# Constructs a {Class} type with an optional base class constraint.
|
data/lib/types/decimal.rb
CHANGED
@@ -23,10 +23,19 @@ module Types
|
|
23
23
|
def self.parse(input)
|
24
24
|
case input
|
25
25
|
when ::Float
|
26
|
-
BigDecimal(input, ::Float::DIG+1)
|
26
|
+
Kernel.BigDecimal(input, ::Float::DIG+1)
|
27
27
|
else
|
28
|
-
BigDecimal(input)
|
28
|
+
Kernel.BigDecimal(input)
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
# Resolves to the actual Ruby BigDecimal class.
|
33
|
+
# @returns [Class] The BigDecimal class.
|
34
|
+
def self.resolve
|
35
|
+
::BigDecimal
|
36
|
+
end
|
31
37
|
end
|
38
|
+
|
39
|
+
# Alias for the Decimal type.
|
40
|
+
BigDecimal = Decimal
|
32
41
|
end
|
data/lib/types/float.rb
CHANGED
data/lib/types/hash.rb
CHANGED
data/lib/types/integer.rb
CHANGED
data/lib/types/method.rb
CHANGED
@@ -57,7 +57,7 @@ module Types
|
|
57
57
|
|
58
58
|
# @returns [String] the RBS type string, e.g. `Method[Receiver, (Args) -> Return]`.
|
59
59
|
def to_rbs
|
60
|
-
argument_types = @argument_types.map
|
60
|
+
argument_types = @argument_types.map(&:to_rbs).join(", ")
|
61
61
|
return_type = @return_type ? @return_type.to_rbs : "void"
|
62
62
|
|
63
63
|
return "Method[#{@receiver_type}, (#{argument_types}) -> #{return_type}]"
|
data/lib/types/named.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "generic"
|
7
|
+
|
8
|
+
module Types
|
9
|
+
# Represents a named type that may not be defined yet.
|
10
|
+
#
|
11
|
+
# ```ruby
|
12
|
+
# type = Types::Named("CustomType")
|
13
|
+
# type.parse(value) # => value (pass-through)
|
14
|
+
# ```
|
15
|
+
class Named < Module
|
16
|
+
include Generic
|
17
|
+
|
18
|
+
# Initialize with a type name.
|
19
|
+
# @parameter name [String] The name of the type.
|
20
|
+
def initialize(name)
|
21
|
+
@name = name
|
22
|
+
end
|
23
|
+
|
24
|
+
# @returns [String] The name of the type.
|
25
|
+
attr :name
|
26
|
+
|
27
|
+
# @returns [Boolean] whether the type is absolute.
|
28
|
+
def absolute?
|
29
|
+
@name.start_with?("::")
|
30
|
+
end
|
31
|
+
|
32
|
+
# @returns [Boolean] whether the type is relative.
|
33
|
+
def relative?
|
34
|
+
!absolute?
|
35
|
+
end
|
36
|
+
|
37
|
+
# Parses the input by passing it through unchanged.
|
38
|
+
# @parameter input [Object] The value to parse.
|
39
|
+
# @returns [Object] The input value unchanged.
|
40
|
+
def parse(input)
|
41
|
+
if resolved = self.resolve
|
42
|
+
if resolved.respond_to?(:load)
|
43
|
+
return resolved.load(input)
|
44
|
+
elsif resolved.respond_to?(:parse)
|
45
|
+
return resolved.parse(input)
|
46
|
+
else
|
47
|
+
raise ArgumentError, "Type #{@name} does not implement .load or .parse!"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
raise ArgumentError, "Unknown type: #{@name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Resolves the named type to the actual Ruby type if it exists.
|
55
|
+
# @returns [Class | Module | Nil] The resolved Ruby type or nil if not found.
|
56
|
+
def resolve
|
57
|
+
Object.const_get(@name)
|
58
|
+
rescue NameError
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# @returns [String] the RBS type string using the name.
|
63
|
+
def to_rbs
|
64
|
+
@name
|
65
|
+
end
|
66
|
+
|
67
|
+
# @returns [String] the string representation of the named type.
|
68
|
+
def to_s
|
69
|
+
@name
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
"<#{self.class} #{@name}>"
|
74
|
+
end
|
75
|
+
|
76
|
+
# @returns [Boolean] true if other is a Named type with the same name.
|
77
|
+
def == other
|
78
|
+
other.is_a?(Named) && @name == other.name
|
79
|
+
end
|
80
|
+
|
81
|
+
# @returns [Integer] hash code based on the name.
|
82
|
+
def hash
|
83
|
+
@name.hash
|
84
|
+
end
|
85
|
+
|
86
|
+
# @returns [Boolean] whether this type is composite.
|
87
|
+
def composite?
|
88
|
+
false
|
89
|
+
end
|
90
|
+
|
91
|
+
# Handles missing constants by creating nested Named types.
|
92
|
+
# This allows parsing of nested type signatures like Foo::Bar.
|
93
|
+
# @parameter name [Symbol] The name of the missing constant.
|
94
|
+
# @returns [Named] A Named type representing the nested unknown type.
|
95
|
+
def const_missing(name)
|
96
|
+
Named.new("#{@name}::#{name}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Constructs a {Named} type with the given name.
|
101
|
+
# @parameter name [String] The name of the type.
|
102
|
+
# @returns [Named] a new {Named} type.
|
103
|
+
def self.Named(name)
|
104
|
+
Named.new(name)
|
105
|
+
end
|
106
|
+
end
|
data/lib/types/nil.rb
CHANGED
data/lib/types/string.rb
CHANGED
data/lib/types/symbol.rb
CHANGED
data/lib/types/tuple.rb
CHANGED
@@ -44,6 +44,12 @@ module Types
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
# Resolves to the actual Ruby Array class, since there is no direct support for tuples in Ruby.
|
48
|
+
# @returns [Class] The Array class.
|
49
|
+
def resolve
|
50
|
+
::Array
|
51
|
+
end
|
52
|
+
|
47
53
|
# @returns [String] the string representation of the tuple type.
|
48
54
|
def to_s
|
49
55
|
"Tuple(#{@item_types.join(', ')})"
|
@@ -51,7 +57,7 @@ module Types
|
|
51
57
|
|
52
58
|
# @returns [String] the RBS type string, e.g. `[String, Integer]`.
|
53
59
|
def to_rbs
|
54
|
-
"[#{@item_types.map
|
60
|
+
"[#{@item_types.map(&:to_rbs).join(', ')}]"
|
55
61
|
end
|
56
62
|
|
57
63
|
private
|
data/lib/types/version.rb
CHANGED
data/lib/types.rb
CHANGED
@@ -17,6 +17,7 @@ require_relative "types/integer"
|
|
17
17
|
require_relative "types/interface"
|
18
18
|
require_relative "types/lambda"
|
19
19
|
require_relative "types/method"
|
20
|
+
require_relative "types/named"
|
20
21
|
require_relative "types/nil"
|
21
22
|
require_relative "types/numeric"
|
22
23
|
require_relative "types/string"
|
@@ -32,17 +33,34 @@ module Types
|
|
32
33
|
# ```ruby
|
33
34
|
# Types.parse("Array(String)") # => Types::Array(Types::String)
|
34
35
|
# ```
|
35
|
-
VALID_SIGNATURE = /\A[a-zA-
|
36
|
-
|
36
|
+
VALID_SIGNATURE = /\A[a-zA-Z0-9\(\):,_|\s]+\z/
|
37
|
+
|
37
38
|
# Parses a type signature string and returns the corresponding type instance.
|
38
39
|
# @parameter signature [String] The type signature to parse.
|
39
40
|
# @returns [Object] The type instance.
|
40
41
|
# @raises [ArgumentError] if the signature is invalid.
|
41
42
|
def self.parse(signature)
|
42
43
|
if signature =~ VALID_SIGNATURE
|
43
|
-
|
44
|
+
# Replace leading :: with Top:: to handle absolute type paths
|
45
|
+
normalized_signature = signature.gsub(/(?<=\A|\W)::/, "TOP::")
|
46
|
+
eval(normalized_signature, binding)
|
44
47
|
else
|
45
48
|
raise ArgumentError, "Invalid type signature: #{signature.inspect}!"
|
46
49
|
end
|
47
50
|
end
|
51
|
+
|
52
|
+
# Handles absolute type paths by creating absolute Named types for unknown types.
|
53
|
+
module TOP
|
54
|
+
def self.const_missing(name)
|
55
|
+
Named.new("::#{name}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Handles missing constants by creating Named types for unknown types.
|
60
|
+
# This allows parsing of type signatures with unknown types.
|
61
|
+
# @parameter name [Symbol] The name of the missing constant.
|
62
|
+
# @returns [Named] A Named type representing the unknown type.
|
63
|
+
def self.const_missing(name)
|
64
|
+
Named(name.to_s)
|
65
|
+
end
|
48
66
|
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
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- lib/types/interface.rb
|
59
59
|
- lib/types/lambda.rb
|
60
60
|
- lib/types/method.rb
|
61
|
+
- lib/types/named.rb
|
61
62
|
- lib/types/nil.rb
|
62
63
|
- lib/types/numeric.rb
|
63
64
|
- lib/types/string.rb
|
metadata.gz.sig
CHANGED
Binary file
|