low_type 0.2.2 → 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
- data/lib/low_type.rb +28 -5
- data/lib/parser.rb +9 -0
- data/lib/redefiner.rb +55 -30
- data/lib/type_expression.rb +17 -7
- data/lib/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: efc284f18360245938c679ce476f0588ea31560da0b75a1076d080d8d687e07c
|
|
4
|
+
data.tar.gz: 7839a82ee075a6436203aaf991be31267a180e9b35f94a4a444365506f4700d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b2260394d54e93340450d2d4cfccd93e3d462f91c39fec4778205c52699590f4467639ac847cf9faa697b5e468308ee1d917c077670b30a4e9d41ca3413bc7b7
|
|
7
|
+
data.tar.gz: 521ee18436fac4d04caa4a2787e93b65367ccbe0fdd69eee7b7479316e2d7642e0a99628fedc8fc93996c28f2620a91d51407143c4ec1baa02040a0478d5a4e8
|
data/lib/low_type.rb
CHANGED
|
@@ -6,9 +6,30 @@ require_relative 'type_expression'
|
|
|
6
6
|
module LowType
|
|
7
7
|
# We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
|
|
8
8
|
def self.included(klass)
|
|
9
|
+
|
|
10
|
+
# Array[] class method returns a type expression only for the duration of this "included" hook.
|
|
11
|
+
array_class_method = Array.method('[]').unbind
|
|
12
|
+
Array.define_singleton_method('[]') do |expression|
|
|
13
|
+
TypeExpression.new(type: [(expression)])
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Hash[] class method returns a type expression only for the duration of this "included" hook.
|
|
17
|
+
hash_class_method = Hash.method('[]').unbind
|
|
18
|
+
Hash.define_singleton_method('[]') do |expression|
|
|
19
|
+
# Support Pry which uses Hash[].
|
|
20
|
+
unless LowType.type?(expression)
|
|
21
|
+
Hash.define_singleton_method('[]', hash_class_method)
|
|
22
|
+
result = Hash[expression]
|
|
23
|
+
Hash.method('[]').unbind
|
|
24
|
+
return result
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
TypeExpression.new(type: expression)
|
|
28
|
+
end
|
|
29
|
+
|
|
9
30
|
class << klass
|
|
10
|
-
def
|
|
11
|
-
@
|
|
31
|
+
def low_methods
|
|
32
|
+
@low_methods ||= {}
|
|
12
33
|
end
|
|
13
34
|
|
|
14
35
|
def type(expression)
|
|
@@ -24,8 +45,12 @@ module LowType
|
|
|
24
45
|
|
|
25
46
|
parser = Parser.new(file_path: LowType.file_path(klass:))
|
|
26
47
|
private_start_line = parser.private_start_line
|
|
48
|
+
|
|
27
49
|
klass.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.instance_methods, private_start_line:, klass:)
|
|
28
50
|
klass.singleton_class.prepend LowType::Redefiner.redefine_methods(method_nodes: parser.class_methods, private_start_line:, klass:)
|
|
51
|
+
ensure
|
|
52
|
+
Array.define_singleton_method('[]', array_class_method)
|
|
53
|
+
Hash.define_singleton_method('[]', hash_class_method)
|
|
29
54
|
end
|
|
30
55
|
|
|
31
56
|
class << self
|
|
@@ -34,7 +59,7 @@ module LowType
|
|
|
34
59
|
end
|
|
35
60
|
|
|
36
61
|
def type?(expression)
|
|
37
|
-
expression.respond_to?(:new) || expression == Integer
|
|
62
|
+
expression.respond_to?(:new) || expression == Integer || (expression.is_a?(Hash) && expression.keys.first.respond_to?(:new) && expression.values.first.respond_to?(:new))
|
|
38
63
|
end
|
|
39
64
|
|
|
40
65
|
def value?(expression)
|
|
@@ -44,6 +69,4 @@ module LowType
|
|
|
44
69
|
|
|
45
70
|
class ValueExpression; end
|
|
46
71
|
class Boolean; end # TrueClass or FalseClass
|
|
47
|
-
class KeyValue; end # KeyValue[String => Hash]
|
|
48
|
-
class MixedTypes; end # MixedTypes[String | Integer]
|
|
49
72
|
end
|
data/lib/parser.rb
CHANGED
|
@@ -18,6 +18,15 @@ module LowType
|
|
|
18
18
|
@class_methods = method_visitor.class_methods
|
|
19
19
|
@private_start_line = method_visitor.private_start_line
|
|
20
20
|
end
|
|
21
|
+
|
|
22
|
+
def self.return_node(method_node:)
|
|
23
|
+
# Only a lambda defined immediately after a method's parameters is considered a return type expression.
|
|
24
|
+
node = method_node.compact_child_nodes.find { |node| node.is_a?(Prism::StatementsNode) }.body.first
|
|
25
|
+
|
|
26
|
+
return node if node.is_a?(Prism::LambdaNode)
|
|
27
|
+
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
21
30
|
end
|
|
22
31
|
|
|
23
32
|
class MethodVisitor < Prism::Visitor
|
data/lib/redefiner.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require_relative 'method_proxy'
|
|
1
2
|
require_relative 'param_proxy'
|
|
2
3
|
require_relative 'parser'
|
|
3
4
|
require_relative 'type_expression'
|
|
@@ -8,54 +9,56 @@ module LowType
|
|
|
8
9
|
def redefine_methods(method_nodes:, private_start_line:, klass:)
|
|
9
10
|
Module.new do
|
|
10
11
|
method_nodes.each do |method_node|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
name = method_node.name
|
|
13
|
+
params = Redefiner.params_with_type_expressions(method_node:)
|
|
14
|
+
return_expression = Redefiner.return_type_expression(method_node:)
|
|
15
|
+
|
|
16
|
+
klass.low_methods[name] = MethodProxy.new(name:, params:, return_expression:)
|
|
17
|
+
|
|
18
|
+
define_method(name) do |*args, **kwargs|
|
|
19
|
+
klass.low_methods[name].params.each do |param_proxy|
|
|
20
|
+
value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
21
|
+
value = param_proxy.type_expression.default_value if value.nil? && param_proxy.type_expression.default_value != :LOW_TYPE_UNDEFINED
|
|
22
|
+
param_proxy.type_expression.validate!(value:, name: param_proxy.name)
|
|
23
|
+
param_proxy.position ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
|
|
24
|
+
end
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
param_proxy.type_expression.validate!(arg:, name: param_proxy.name)
|
|
24
|
-
param_proxy.position ? args[param_proxy.position] = arg : kwargs[param_proxy.name] = arg
|
|
26
|
+
if return_expression
|
|
27
|
+
return_value = super(*args, **kwargs)
|
|
28
|
+
return_expression.validate!(value: return_value, name:)
|
|
29
|
+
return return_value
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
super(*args, **kwargs)
|
|
28
33
|
end
|
|
29
34
|
|
|
30
35
|
if private_start_line && method_node.start_line > private_start_line
|
|
31
|
-
private
|
|
36
|
+
private name
|
|
32
37
|
end
|
|
33
38
|
end
|
|
34
39
|
end
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
def return_type_expression(method_node:)
|
|
43
|
+
return_node = Parser.return_node(method_node:)
|
|
44
|
+
return nil if return_node.nil?
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
param_type, param_name = param
|
|
43
|
-
|
|
44
|
-
case param_type
|
|
45
|
-
when :req
|
|
46
|
-
required_args << nil
|
|
47
|
-
when :keyreq
|
|
48
|
-
required_kwargs[param_name] = nil
|
|
49
|
-
end
|
|
50
|
-
end
|
|
46
|
+
expression = eval(return_node.slice).call
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
return expression if expression.class == TypeExpression
|
|
49
|
+
TypeExpression.new(type: expression)
|
|
53
50
|
end
|
|
54
51
|
|
|
55
|
-
def
|
|
52
|
+
def params_with_type_expressions(method_node:)
|
|
53
|
+
return [] if method_node.parameters.nil?
|
|
54
|
+
|
|
55
|
+
params = method_node.parameters.slice
|
|
56
|
+
proxy_method = eval("-> (#{params}) {}")
|
|
57
|
+
required_args, required_kwargs = Redefiner.required_args(proxy_method:)
|
|
58
|
+
|
|
56
59
|
typed_method = eval(
|
|
57
60
|
<<~RUBY
|
|
58
|
-
-> (#{
|
|
61
|
+
-> (#{params}) {
|
|
59
62
|
param_proxies = []
|
|
60
63
|
|
|
61
64
|
proxy_method.parameters.each_with_index do |param, position|
|
|
@@ -78,6 +81,28 @@ module LowType
|
|
|
78
81
|
|
|
79
82
|
# Call method with only its required args to evaluate type expressions (which are stored as default values).
|
|
80
83
|
typed_method.call(*required_args, **required_kwargs)
|
|
84
|
+
|
|
85
|
+
# TODO: Write spec for this.
|
|
86
|
+
rescue ArgumentError => e
|
|
87
|
+
raise ArgumentError, "Incorrect param syntax"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def required_args(proxy_method:)
|
|
91
|
+
required_args = []
|
|
92
|
+
required_kwargs = {}
|
|
93
|
+
|
|
94
|
+
proxy_method.parameters.each do |param|
|
|
95
|
+
param_type, param_name = param
|
|
96
|
+
|
|
97
|
+
case param_type
|
|
98
|
+
when :req
|
|
99
|
+
required_args << nil
|
|
100
|
+
when :keyreq
|
|
101
|
+
required_kwargs[param_name] = nil
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
[required_args, required_kwargs]
|
|
81
106
|
end
|
|
82
107
|
end
|
|
83
108
|
end
|
data/lib/type_expression.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module LowType
|
|
2
2
|
class TypeExpression
|
|
3
|
-
attr_reader :
|
|
3
|
+
attr_reader :types, :default_value
|
|
4
4
|
|
|
5
|
-
def initialize(type:)
|
|
5
|
+
def initialize(type:, default_value: :LOW_TYPE_UNDEFINED)
|
|
6
6
|
@types = [type]
|
|
7
|
-
@default_value =
|
|
7
|
+
@default_value = default_value
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def |(expression)
|
|
@@ -24,12 +24,22 @@ module LowType
|
|
|
24
24
|
@default_value == :LOW_TYPE_UNDEFINED
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def validate!(
|
|
28
|
-
if
|
|
29
|
-
|
|
27
|
+
def validate!(value:, name:)
|
|
28
|
+
if value.nil?
|
|
29
|
+
return true if @default_value.nil?
|
|
30
|
+
raise ArgumentError, "Missing required argument of type '#{@types.join(', ')}' for '#{name}'" if required?
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
@types.each do |type|
|
|
34
|
+
return true if LowType.type?(type) && type == value.class
|
|
35
|
+
# TODO: Shallow validation of enumerables could be made deeper with user config.
|
|
36
|
+
return true if type.class == Array && value.class == Array && type.first == value.first.class
|
|
37
|
+
if type.class == Hash && value.class == Hash && type.keys[0] == value.keys[0].class && type.values[0] == value.values[0].class
|
|
38
|
+
return true
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
raise TypeError, "Invalid type '#{value.class}' for '#{name}'"
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
end
|
data/lib/version.rb
CHANGED