low_type 0.5.0 → 0.7.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
- data/lib/errors.rb +3 -0
- data/lib/interfaces/proxy_interface.rb +11 -0
- data/lib/low_type.rb +21 -17
- data/lib/proxies/local_proxy.rb +21 -0
- data/lib/proxies/method_proxy.rb +11 -0
- data/lib/proxies/param_proxy.rb +26 -0
- data/lib/proxies/return_proxy.rb +21 -0
- data/lib/redefiner.rb +14 -15
- data/lib/type_assignment.rb +59 -0
- data/lib/type_expression.rb +13 -5
- data/lib/version.rb +1 -1
- metadata +9 -4
- data/lib/method_proxy.rb +0 -11
- data/lib/param_proxy.rb +0 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d845917e9ed4afc79db302fe7f5a2955a4e299d7bd415e82e2f26af3b14cd25f
|
|
4
|
+
data.tar.gz: f9213b5731f3805ebf1e99efa0ccdd741b955464403842aeaa171d0b41fff146
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6066f8c29073720201a83438c58067ef37d2b02b493f108e31bee624069b68a7049e3a64aebe20da851f7b8e13e7d599c02deb1adbb9050ccd271a895933c6c2
|
|
7
|
+
data.tar.gz: 16c144045ae7666c1ab42610e641d9b4946b14cc9adf22023239a55dbfcbc5a8da714b53c94972a883249008512bcc56a60b465ce9ba9da1908a0151856afa7c
|
data/lib/errors.rb
ADDED
data/lib/low_type.rb
CHANGED
|
@@ -29,19 +29,6 @@ module LowType
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
class << klass
|
|
32
|
-
# Public API.
|
|
33
|
-
def type(expression)
|
|
34
|
-
# TODO: Runtime type expression for the supplied variable.
|
|
35
|
-
end
|
|
36
|
-
alias_method :low_type, :type
|
|
37
|
-
|
|
38
|
-
# Public API.
|
|
39
|
-
def value(expression)
|
|
40
|
-
::LowType.value(expression)
|
|
41
|
-
end
|
|
42
|
-
alias_method :low_value, :value
|
|
43
|
-
|
|
44
|
-
# Internal API.
|
|
45
32
|
def low_methods
|
|
46
33
|
@low_methods ||= {}
|
|
47
34
|
end
|
|
@@ -57,21 +44,38 @@ module LowType
|
|
|
57
44
|
Hash.define_singleton_method('[]', hash_class_method)
|
|
58
45
|
end
|
|
59
46
|
|
|
60
|
-
# Internal API.
|
|
61
47
|
class << self
|
|
48
|
+
# Public API.
|
|
49
|
+
|
|
50
|
+
def config
|
|
51
|
+
config = Struct.new(:type_assignment, :deep_type_check)
|
|
52
|
+
@config ||= config.new(false, false)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def configure
|
|
56
|
+
yield(config)
|
|
57
|
+
|
|
58
|
+
if config.type_assignment
|
|
59
|
+
require_relative 'type_assignment'
|
|
60
|
+
include TypeAssignment
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Internal API.
|
|
65
|
+
|
|
62
66
|
def file_path(klass:)
|
|
63
67
|
caller.find { |callee| callee.end_with?("<class:#{klass}>'") }.split(':').first
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
def type?(
|
|
67
|
-
|
|
70
|
+
def type?(type)
|
|
71
|
+
type.respond_to?(:new) || type == Integer || (type.is_a?(::Hash) && type.keys.first.respond_to?(:new) && type.values.first.respond_to?(:new))
|
|
68
72
|
end
|
|
69
73
|
|
|
70
74
|
def value?(expression)
|
|
71
75
|
!expression.respond_to?(:new) && expression != Integer
|
|
72
76
|
end
|
|
73
77
|
|
|
74
|
-
def value(type)
|
|
78
|
+
def value(type:)
|
|
75
79
|
TypeExpression.new(default_value: ValueExpression.new(value: type))
|
|
76
80
|
end
|
|
77
81
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require_relative '../interfaces/proxy_interface'
|
|
2
|
+
require_relative '../errors'
|
|
3
|
+
|
|
4
|
+
module LowType
|
|
5
|
+
class LocalProxy < ProxyInterface
|
|
6
|
+
attr_reader :type_expression, :name
|
|
7
|
+
|
|
8
|
+
def initialize(type_expression:, name:)
|
|
9
|
+
@type_expression = type_expression
|
|
10
|
+
@name = name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error_type(value:)
|
|
14
|
+
LocalTypeError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def error_message(value:, line:)
|
|
18
|
+
"Invalid variable type #{value.class} in '#{@name.class}' on line #{line}. Valid types: '#{@type_expression.valid_types.join(', ')}'"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require_relative '../interfaces/proxy_interface'
|
|
2
|
+
require_relative '../errors'
|
|
3
|
+
|
|
4
|
+
module LowType
|
|
5
|
+
class ParamProxy < ProxyInterface
|
|
6
|
+
attr_reader :type_expression, :name, :type, :position
|
|
7
|
+
|
|
8
|
+
def initialize(type_expression:, name:, type:, position: nil)
|
|
9
|
+
@type_expression = type_expression
|
|
10
|
+
@name = name
|
|
11
|
+
@type = type
|
|
12
|
+
@position = position
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def error_type(value:)
|
|
16
|
+
return ArgumentError if value.nil?
|
|
17
|
+
ArgumentTypeError
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def error_message(value:, line: nil)
|
|
21
|
+
return "Missing argument for parameter '#{@name}'. Position: #{@position}" if value.nil?
|
|
22
|
+
|
|
23
|
+
"Invalid argument type '#{value.class}' for parameter '#{@name}'. Valid types: '#{@type_expression.valid_types.join(', ')}'"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require_relative '../interfaces/proxy_interface'
|
|
2
|
+
require_relative '../errors'
|
|
3
|
+
|
|
4
|
+
module LowType
|
|
5
|
+
class ReturnProxy < ProxyInterface
|
|
6
|
+
attr_reader :type_expression, :name
|
|
7
|
+
|
|
8
|
+
def initialize(type_expression:, name:)
|
|
9
|
+
@type_expression = type_expression
|
|
10
|
+
@name = name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error_type(value:)
|
|
14
|
+
ReturnTypeError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def error_message(value:, line: nil)
|
|
18
|
+
"Invalid return type '#{value.class}' for method '#{@name}'. Valid types: '#{@type_expression.valid_types.join(', ')}'"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/redefiner.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
require_relative 'method_proxy'
|
|
2
|
-
require_relative 'param_proxy'
|
|
1
|
+
require_relative 'proxies/method_proxy'
|
|
2
|
+
require_relative 'proxies/param_proxy'
|
|
3
|
+
require_relative 'proxies/return_proxy'
|
|
3
4
|
require_relative 'parser'
|
|
4
5
|
require_relative 'type_expression'
|
|
5
6
|
|
|
6
7
|
module LowType
|
|
7
|
-
class ReturnError < StandardError; end
|
|
8
|
-
|
|
9
8
|
class Redefiner
|
|
10
9
|
class << self
|
|
11
10
|
def redefine_methods(method_nodes:, private_start_line:, klass:)
|
|
@@ -13,9 +12,9 @@ module LowType
|
|
|
13
12
|
method_nodes.each do |method_node|
|
|
14
13
|
name = method_node.name
|
|
15
14
|
params = Redefiner.params_with_type_expressions(method_node:)
|
|
16
|
-
|
|
15
|
+
return_proxy = Redefiner.return_proxy(method_node:)
|
|
17
16
|
|
|
18
|
-
klass.low_methods[name] = MethodProxy.new(name:, params:,
|
|
17
|
+
klass.low_methods[name] = MethodProxy.new(name:, params:, return_proxy:)
|
|
19
18
|
|
|
20
19
|
define_method(name) do |*args, **kwargs|
|
|
21
20
|
klass.low_methods[name].params.each do |param_proxy|
|
|
@@ -23,16 +22,16 @@ module LowType
|
|
|
23
22
|
value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
24
23
|
value = param_proxy.type_expression.default_value if value.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
|
|
25
24
|
# Validate argument type.
|
|
26
|
-
param_proxy.type_expression.validate!(value:,
|
|
25
|
+
param_proxy.type_expression.validate!(value:, proxy: param_proxy)
|
|
27
26
|
# Handle value(type) special case.
|
|
28
27
|
value = value.value if value.is_a?(ValueExpression)
|
|
29
28
|
# Redefine argument value.
|
|
30
29
|
param_proxy.position ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
|
|
31
30
|
end
|
|
32
31
|
|
|
33
|
-
if
|
|
32
|
+
if return_proxy
|
|
34
33
|
return_value = super(*args, **kwargs)
|
|
35
|
-
|
|
34
|
+
return_proxy.type_expression.validate!(value: return_value, proxy: klass.low_methods[name].return_proxy)
|
|
36
35
|
return return_value
|
|
37
36
|
end
|
|
38
37
|
|
|
@@ -84,14 +83,14 @@ module LowType
|
|
|
84
83
|
raise ArgumentError, "Incorrect param syntax: #{e.message}"
|
|
85
84
|
end
|
|
86
85
|
|
|
87
|
-
def
|
|
86
|
+
def return_proxy(method_node:)
|
|
88
87
|
return_node = Parser.return_node(method_node:)
|
|
89
88
|
return nil if return_node.nil?
|
|
90
89
|
|
|
91
90
|
expression = eval(return_node.slice).call
|
|
91
|
+
expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
TypeExpression.new(type: expression)
|
|
93
|
+
ReturnProxy.new(type_expression: expression, name: method_node.name)
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
private
|
|
@@ -114,9 +113,9 @@ module LowType
|
|
|
114
113
|
[required_args, required_kwargs]
|
|
115
114
|
end
|
|
116
115
|
|
|
117
|
-
#
|
|
118
|
-
def value(
|
|
119
|
-
|
|
116
|
+
# Value expressions are eval()'d in the context of this module class (the instance doesn't exist yet) so alias API.
|
|
117
|
+
def value(type)
|
|
118
|
+
LowType.value(type:)
|
|
120
119
|
end
|
|
121
120
|
end
|
|
122
121
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative 'proxies/local_proxy'
|
|
2
|
+
require_relative 'type_expression'
|
|
3
|
+
require_relative 'value_expression'
|
|
4
|
+
|
|
5
|
+
module TypeAssignment
|
|
6
|
+
class AssignmentError < StandardError; end
|
|
7
|
+
|
|
8
|
+
def type(type_expression)
|
|
9
|
+
object = type_expression.default_value
|
|
10
|
+
|
|
11
|
+
if !LowType.value?(object)
|
|
12
|
+
raise AssignmentError, "Single-instance objects like #{object} are not supported"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
local_proxy = LowType::LocalProxy.new(type_expression:, name: self)
|
|
16
|
+
object.instance_variable_set('@local_proxy', local_proxy)
|
|
17
|
+
|
|
18
|
+
type_expression.validate!(value: object, proxy: local_proxy, line: caller_locations(1, 1).first.lineno)
|
|
19
|
+
|
|
20
|
+
def object.with_type=(value)
|
|
21
|
+
local_proxy = self.instance_variable_get('@local_proxy')
|
|
22
|
+
type_expression = local_proxy.type_expression
|
|
23
|
+
type_expression.validate!(value:, proxy: local_proxy, line: caller_locations(1, 1).first.lineno)
|
|
24
|
+
|
|
25
|
+
# We can't reassign self in Ruby so we reassign instance variables instead.
|
|
26
|
+
value.instance_variables.each do |variable|
|
|
27
|
+
self.instance_variable_set(variable, value.instance_variable_get(variable))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
return object.value if object.is_a?(ValueExpression)
|
|
34
|
+
|
|
35
|
+
object
|
|
36
|
+
end
|
|
37
|
+
alias_method :low_type, :type
|
|
38
|
+
|
|
39
|
+
def value(type)
|
|
40
|
+
LowType.value(type:)
|
|
41
|
+
end
|
|
42
|
+
alias_method :low_value, :value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module LowType
|
|
46
|
+
class Array < ::Array
|
|
47
|
+
def self.[](type)
|
|
48
|
+
return TypeExpression.new(type: [type]) if LowType.type?(type)
|
|
49
|
+
super
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class Hash < ::Hash
|
|
54
|
+
def self.[](type)
|
|
55
|
+
return TypeExpression.new(type:) if LowType.type?(type)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/type_expression.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require_relative 'proxies/param_proxy'
|
|
2
|
+
|
|
1
3
|
module LowType
|
|
2
4
|
class TypeExpression
|
|
3
5
|
attr_reader :types, :default_value
|
|
@@ -25,22 +27,28 @@ module LowType
|
|
|
25
27
|
@default_value == :LOW_TYPE_UNDEFINED
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def validate!(value:,
|
|
30
|
+
def validate!(value:, proxy:, line: nil)
|
|
29
31
|
if value.nil?
|
|
30
32
|
return true if @default_value.nil?
|
|
31
|
-
raise error_type,
|
|
33
|
+
raise proxy.error_type(value:), proxy.error_message(value:, line:) if required?
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
@types.each do |type|
|
|
35
37
|
return true if LowType.type?(type) && type == value.class
|
|
36
38
|
# TODO: Shallow validation of enumerables could be made deeper with user config.
|
|
37
|
-
return true if type.class == Array && value.class == Array && type.first == value.first.class
|
|
38
|
-
if type.class == Hash && value.class == Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
|
|
39
|
+
return true if type.class == ::Array && value.class == ::Array && type.first == value.first.class
|
|
40
|
+
if type.class == ::Hash && value.class == ::Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
|
|
39
41
|
return true
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
raise
|
|
45
|
+
raise proxy.error_type(value:), proxy.error_message(value:, line:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def valid_types
|
|
49
|
+
types = @types.map { |type| type.inspect.to_s }
|
|
50
|
+
return types + ['nil'] if @default_value.nil?
|
|
51
|
+
types
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: low_type
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -17,11 +17,16 @@ executables: []
|
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
|
+
- lib/errors.rb
|
|
21
|
+
- lib/interfaces/proxy_interface.rb
|
|
20
22
|
- lib/low_type.rb
|
|
21
|
-
- lib/method_proxy.rb
|
|
22
|
-
- lib/param_proxy.rb
|
|
23
23
|
- lib/parser.rb
|
|
24
|
+
- lib/proxies/local_proxy.rb
|
|
25
|
+
- lib/proxies/method_proxy.rb
|
|
26
|
+
- lib/proxies/param_proxy.rb
|
|
27
|
+
- lib/proxies/return_proxy.rb
|
|
24
28
|
- lib/redefiner.rb
|
|
29
|
+
- lib/type_assignment.rb
|
|
25
30
|
- lib/type_expression.rb
|
|
26
31
|
- lib/value_expression.rb
|
|
27
32
|
- lib/version.rb
|
|
@@ -46,5 +51,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
46
51
|
requirements: []
|
|
47
52
|
rubygems_version: 3.7.2
|
|
48
53
|
specification_version: 4
|
|
49
|
-
summary:
|
|
54
|
+
summary: Elegant types in Ruby
|
|
50
55
|
test_files: []
|
data/lib/method_proxy.rb
DELETED
data/lib/param_proxy.rb
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
module LowType
|
|
2
|
-
class ParamProxy
|
|
3
|
-
attr_reader :type_expression, :name, :type, :position
|
|
4
|
-
|
|
5
|
-
def initialize(type_expression:, name:, type:, position: nil)
|
|
6
|
-
@type_expression = type_expression
|
|
7
|
-
@name = name
|
|
8
|
-
@type = type
|
|
9
|
-
@position = position
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|