low_type 1.1.9 → 1.1.10
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/adapters/adapter_loader.rb +2 -5
- data/lib/adapters/sinatra_adapter.rb +33 -61
- data/lib/definitions/evaluator.rb +94 -0
- data/lib/definitions/redefiner.rb +44 -78
- data/lib/definitions/type_accessors.rb +11 -13
- data/lib/expressions/{expressions.rb → expression_helpers.rb} +5 -5
- data/lib/expressions/type_expression.rb +5 -3
- data/lib/expressions/value_expression.rb +3 -1
- data/lib/interfaces/adapter_interface.rb +2 -2
- data/lib/interfaces/{error_interface.rb → error_handling.rb} +3 -14
- data/lib/low_type.rb +35 -24
- data/lib/proxies/local_proxy.rb +8 -4
- data/lib/proxies/param_proxy.rb +7 -19
- data/lib/proxies/return_proxy.rb +8 -13
- data/lib/queries/file_query.rb +7 -1
- data/lib/version.rb +1 -1
- metadata +6 -9
- data/lib/definitions/repository.rb +0 -32
- data/lib/factories/expression_factory.rb +0 -14
- data/lib/factories/proxy_factory.rb +0 -114
- data/lib/proxies/method_proxy.rb +0 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 54e1e267e4ef98dc2364738a169dee20cb6364d9cb597c8f7c46084309b2296f
|
|
4
|
+
data.tar.gz: 1c1b44d5b7ba03b955d039bf62a7898897d2d04230dc89ff58df055de719d37d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 39b512c4ff8c250659bdce0bb1be51c5f3e46e7a8ac30fa607deabdf9c933b7459bb78542211aafebf483dff3ecd8600ebb589dd4b91fb0d410c4dd9206e74fb
|
|
7
|
+
data.tar.gz: 0d4840867f0454b51b82f64807b691c4a4e4ac1380f5de113862ca8964f12fd18a62e0accaa51fad1105b94be403659ed5a80f486b4fc3ad4a1cddaf72abbf59
|
|
@@ -7,14 +7,11 @@ module Low
|
|
|
7
7
|
class Loader
|
|
8
8
|
class << self
|
|
9
9
|
def load(klass:, class_proxy:)
|
|
10
|
-
adaptor = nil
|
|
11
|
-
|
|
12
10
|
ancestors = klass.ancestors.map(&:to_s)
|
|
13
|
-
adaptor = Sinatra.new(klass:, class_proxy:) if ancestors.include?('Sinatra::Base')
|
|
14
11
|
|
|
15
|
-
return
|
|
12
|
+
return unless ancestors.include?('Sinatra::Base')
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
klass.prepend SinatraAdapter.new.module(file_path: class_proxy.file_path)
|
|
18
15
|
end
|
|
19
16
|
end
|
|
20
17
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'prism'
|
|
4
|
+
require 'lowkey'
|
|
4
5
|
|
|
5
|
-
require_relative '../factories/proxy_factory'
|
|
6
6
|
require_relative '../interfaces/adapter_interface'
|
|
7
7
|
require_relative '../proxies/return_proxy'
|
|
8
8
|
require_relative '../types/error_types'
|
|
@@ -10,66 +10,38 @@ require_relative '../types/error_types'
|
|
|
10
10
|
module Low
|
|
11
11
|
module Adapter
|
|
12
12
|
# We don't use https://sinatrarb.com/extensions.html because we need to type check all Ruby methods (not just Sinatra) at a lower level.
|
|
13
|
-
class
|
|
14
|
-
def
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
module Methods
|
|
48
|
-
# Unfortunately overriding invoke() is the best way to validate types for now. Though direct it's also very compute efficient.
|
|
49
|
-
# I originally tried an after filter and it mostly worked but it only had access to Response which isn't the raw return value.
|
|
50
|
-
# I suggest that Sinatra provide a hook that allows us to access the raw return value of a route before it becomes a Response.
|
|
51
|
-
def invoke(&block)
|
|
52
|
-
res = catch(:halt, &block)
|
|
53
|
-
|
|
54
|
-
low_validate!(value: res) if res
|
|
55
|
-
|
|
56
|
-
res = [res] if res.is_a?(Integer) || res.is_a?(String)
|
|
57
|
-
if res.is_a?(::Array) && res.first.is_a?(Integer)
|
|
58
|
-
res = res.dup
|
|
59
|
-
status(res.shift)
|
|
60
|
-
body(res.pop)
|
|
61
|
-
headers(*res)
|
|
62
|
-
elsif res.respond_to? :each
|
|
63
|
-
body res
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
nil # avoid double setting the same response tuple twice
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def low_validate!(value:)
|
|
70
|
-
route = "#{request.request_method} #{request.path}"
|
|
71
|
-
if (method_proxy = self.class.low_methods[route]) && (proxy = method_proxy.return_proxy)
|
|
72
|
-
proxy.type_expression.validate!(value:, proxy:)
|
|
13
|
+
class SinatraAdapter < AdapterInterface
|
|
14
|
+
def module(file_path:) # rubocop:disable Metrics/AbcSize
|
|
15
|
+
Module.new do
|
|
16
|
+
@@file_path = file_path # rubocop:disable Style/ClassVars
|
|
17
|
+
|
|
18
|
+
# Unfortunately overriding invoke() is the best way to validate types for now. Though direct it's also very compute efficient.
|
|
19
|
+
# I originally tried an after filter and it mostly worked but it only had access to Response which isn't the raw return value.
|
|
20
|
+
# I suggest that Sinatra provide a hook that allows us to access the raw return value of a route before it becomes a Response.
|
|
21
|
+
def invoke(&block)
|
|
22
|
+
res = catch(:halt, &block)
|
|
23
|
+
|
|
24
|
+
lowtype_validate!(value: res) if res
|
|
25
|
+
|
|
26
|
+
res = [res] if res.is_a?(Integer) || res.is_a?(String)
|
|
27
|
+
if res.is_a?(::Array) && res.first.is_a?(Integer)
|
|
28
|
+
res = res.dup
|
|
29
|
+
status(res.shift)
|
|
30
|
+
body(res.pop)
|
|
31
|
+
headers(*res)
|
|
32
|
+
elsif res.respond_to? :each
|
|
33
|
+
body res
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
nil # avoid double setting the same response tuple twice
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def lowtype_validate!(value:)
|
|
40
|
+
route = "#{request.request_method} #{request.path}"
|
|
41
|
+
if (method_proxy = Lowkey[@@file_path][self.class.name][route]) && (proxy = method_proxy.return_proxy)
|
|
42
|
+
proxy.expression.validate!(value:, proxy:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
73
45
|
end
|
|
74
46
|
end
|
|
75
47
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'expressions'
|
|
4
|
+
require 'lowkey'
|
|
5
|
+
|
|
6
|
+
require_relative '../expressions/expression_helpers'
|
|
7
|
+
require_relative '../expressions/type_expression'
|
|
8
|
+
require_relative '../syntax/syntax'
|
|
9
|
+
require_relative '../types/complex_types'
|
|
10
|
+
require_relative '../types/status'
|
|
11
|
+
|
|
12
|
+
module Low
|
|
13
|
+
# Evaluate code stored in strings into constants and values.
|
|
14
|
+
# ┌────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌─────────┐
|
|
15
|
+
# │ Lowkey │ │ Proxies │ │ Expressions │ │ LowType │ │ Methods │
|
|
16
|
+
# └────┬───┘ └────┬────┘ └──────┬──────┘ └────┬────┘ └────┬────┘
|
|
17
|
+
# │ │ │ │ │
|
|
18
|
+
# │ Parses AST │ │ │ │
|
|
19
|
+
# ├─────────────►│ │ │ │
|
|
20
|
+
# │ │ │ │ │
|
|
21
|
+
# │ │ Stores │ │ │
|
|
22
|
+
# │ ├────────────────►│ │ │
|
|
23
|
+
# │ │ │ │ │
|
|
24
|
+
# │ │ │ Evaluates <-- YOU ARE HERE. |
|
|
25
|
+
# │ │ │◄────────────────┤ │
|
|
26
|
+
# │ │ │ │ │
|
|
27
|
+
# │ │ │ │ Redefines │
|
|
28
|
+
# │ │ │ ├──────────────►│
|
|
29
|
+
# │ │ │ │ │
|
|
30
|
+
# │ │ │ Validates │ │
|
|
31
|
+
# │ │ │◄┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
|
|
32
|
+
# │ │ │ │ │
|
|
33
|
+
class Evaluator
|
|
34
|
+
include ExpressionHelpers
|
|
35
|
+
include Types
|
|
36
|
+
using LowType::Syntax
|
|
37
|
+
|
|
38
|
+
def instance_evaluate(proxy:)
|
|
39
|
+
# Not a security risk because the code comes from a trusted source; the file that included lowtype.
|
|
40
|
+
eval(proxy.value, binding, proxy.file_path, proxy.start_line) # rubocop:disable Security/Eval
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class << self
|
|
44
|
+
def evaluate(method_proxies:)
|
|
45
|
+
require_relative '../syntax/union_types' if LowType.config.union_type_expressions
|
|
46
|
+
|
|
47
|
+
method_proxies.each_value do |method_proxy|
|
|
48
|
+
evaluate_param_proxy_expressions(method_proxy:)
|
|
49
|
+
evaluate_return_proxy_expression(return_proxy: method_proxy.return_proxy) if method_proxy.return_proxy
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def evaluate_param_proxy_expressions(method_proxy:)
|
|
54
|
+
begin # rubocop:disable Style/RedundantBegin
|
|
55
|
+
method_proxy.tagged_params(:value).each do |param_proxy|
|
|
56
|
+
# TODO: Evaluate in the binding of the class that included LowType if not a type managed by LowType.
|
|
57
|
+
expression = new.instance_evaluate(proxy: param_proxy)
|
|
58
|
+
param_proxy.expression = cast_type_expression(expression:, method_proxy:)
|
|
59
|
+
end
|
|
60
|
+
rescue NameError
|
|
61
|
+
mp = method_proxy
|
|
62
|
+
raise NameError, "Unknown type '#{mp.value}' for #{mp.scope} at #{mp.file_path}:#{mp.start_line}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def evaluate_return_proxy_expression(return_proxy:)
|
|
67
|
+
begin
|
|
68
|
+
expression = new.instance_evaluate(proxy: return_proxy)
|
|
69
|
+
rescue NameError
|
|
70
|
+
rp = return_proxy
|
|
71
|
+
raise NameError, "Unknown return type '#{rp.value}' for #{rp.scope} at #{rp.file_path}:#{rp.start_line}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
|
|
75
|
+
|
|
76
|
+
return_proxy.expression = expression
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def cast_type_expression(expression:, method_proxy:)
|
|
82
|
+
if expression.is_a?(::Expressions::Expression)
|
|
83
|
+
return expression
|
|
84
|
+
elsif expression.instance_of?(Class) && expression.name == 'Low::Dependency'
|
|
85
|
+
return expression.new(provider_key: method_proxy.name)
|
|
86
|
+
elsif TypeQuery.type?(expression)
|
|
87
|
+
return TypeExpression.new(type: expression)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -1,35 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../expressions/value_expression'
|
|
4
|
-
require_relative '../
|
|
5
|
-
require_relative '../proxies/method_proxy'
|
|
6
|
-
require_relative '../queries/type_query'
|
|
7
|
-
require_relative 'repository'
|
|
4
|
+
require_relative '../definitions/evaluator'
|
|
8
5
|
|
|
9
6
|
module Low
|
|
10
7
|
# Redefine methods to have their arguments and return values type checked.
|
|
8
|
+
# ┌────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌─────────┐
|
|
9
|
+
# │ Lowkey │ │ Proxies │ │ Expressions │ │ LowType │ │ Methods │
|
|
10
|
+
# └────┬───┘ └────┬────┘ └──────┬──────┘ └────┬────┘ └────┬────┘
|
|
11
|
+
# │ │ │ │ │
|
|
12
|
+
# │ Parses AST │ │ │ │
|
|
13
|
+
# ├─────────────►│ │ │ │
|
|
14
|
+
# │ │ │ │ │
|
|
15
|
+
# │ │ Stores │ │ │
|
|
16
|
+
# │ ├────────────────►│ │ │
|
|
17
|
+
# │ │ │ │ │
|
|
18
|
+
# │ │ │ Evaluates │ │
|
|
19
|
+
# │ │ │◄────────────────┤ │
|
|
20
|
+
# │ │ │ │ │
|
|
21
|
+
# │ │ │ │ Redefines <-- YOU ARE HERE.
|
|
22
|
+
# │ │ │ ├──────────────►│
|
|
23
|
+
# │ │ │ │ │
|
|
24
|
+
# │ │ │ Validates │ │
|
|
25
|
+
# │ │ │◄┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
|
|
26
|
+
# │ │ │ │ │
|
|
11
27
|
class Redefiner
|
|
12
28
|
class << self
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
# TODO: Pass in "klass" and use it to class_eval/eval methods in the binding of the class that included LowType.
|
|
30
|
+
def redefine(method_proxies:, class_proxy:)
|
|
16
31
|
if LowType.config.type_checking
|
|
17
|
-
typed_methods(method_proxies:, class_proxy
|
|
32
|
+
typed_methods(method_proxies:, class_proxy:)
|
|
18
33
|
else
|
|
19
|
-
untyped_methods(method_proxies:, class_proxy
|
|
34
|
+
untyped_methods(method_proxies:, class_proxy:)
|
|
20
35
|
end
|
|
21
36
|
end
|
|
22
37
|
|
|
23
|
-
def redefinable?(method_proxy:, class_proxy:, klass:)
|
|
24
|
-
method_has_types?(method_proxy:, klass:) && method_within_class_bounds?(method_proxy:, class_proxy:, klass:)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
38
|
def untyped_args(args:, kwargs:, method_proxy:) # rubocop:disable Metrics/AbcSize
|
|
28
|
-
method_proxy.
|
|
39
|
+
method_proxy.params_with_expressions.each do |param_proxy|
|
|
29
40
|
value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
30
41
|
|
|
31
42
|
next unless value.nil?
|
|
32
|
-
raise param_proxy.error_type, param_proxy.error_message(value:) if param_proxy.required?
|
|
43
|
+
raise param_proxy.error_type, param_proxy.error_message(value:) if param_proxy.expression.required?
|
|
33
44
|
|
|
34
45
|
value = param_proxy.expression.default_value # Default value can still be `nil`.
|
|
35
46
|
value = value.value if value.is_a?(ValueExpression)
|
|
@@ -41,96 +52,51 @@ module Low
|
|
|
41
52
|
|
|
42
53
|
private
|
|
43
54
|
|
|
44
|
-
def
|
|
45
|
-
method_nodes.each do |name, method_node|
|
|
46
|
-
begin # rubocop:disable Style/RedundantBegin
|
|
47
|
-
name = method_node.name
|
|
48
|
-
scope = name
|
|
49
|
-
|
|
50
|
-
param_proxies = ProxyFactory.param_proxies(method_node:, file_path:, scope:)
|
|
51
|
-
return_proxy = ProxyFactory.return_proxy(method_node:, name:, file_path:, scope:)
|
|
52
|
-
method_proxy = MethodProxy.new(file_path:, start_line: method_node.start_line, scope:, name:, param_proxies:, return_proxy:)
|
|
53
|
-
|
|
54
|
-
Repository.save(method: method_proxy, klass:)
|
|
55
|
-
# When we can't parse the method's params or return type then skip it.
|
|
56
|
-
rescue SyntaxError
|
|
57
|
-
next
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
Repository.all(klass:)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def typed_methods(method_proxies:, class_proxy:, klass:) # rubocop:disable Metrics
|
|
55
|
+
def typed_methods(method_proxies:, class_proxy:) # rubocop:disable Metrics
|
|
65
56
|
Module.new do
|
|
66
|
-
method_proxies.each do |
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
define_method(name) do |*args, **kwargs|
|
|
71
|
-
# Inlined version of Repository.load() for performance increase.
|
|
72
|
-
method_proxy = instance_of?(Class) ? low_methods[name] : self.class.low_methods[name] || Object.low_methods[name]
|
|
73
|
-
|
|
74
|
-
method_proxy.param_proxies.each do |param_proxy|
|
|
75
|
-
value = param_proxy.position ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
76
|
-
value = param_proxy.expression.default_value if value.nil? && !param_proxy.required?
|
|
57
|
+
method_proxies.values.filter(&:expressions?).each do |method_proxy|
|
|
58
|
+
define_method(method_proxy.name) do |*args, **kwargs|
|
|
59
|
+
method_proxy.params_with_expressions.each do |param_proxy|
|
|
60
|
+
positional = %i[pos_req pos_opt].include?(param_proxy.type)
|
|
77
61
|
|
|
62
|
+
value = positional ? args[param_proxy.position] : kwargs[param_proxy.name]
|
|
63
|
+
value = param_proxy.expression.default_value if value.nil? && !param_proxy.expression.required?
|
|
78
64
|
param_proxy.expression.validate!(value:, proxy: param_proxy)
|
|
79
65
|
value = value.value if value.is_a?(ValueExpression)
|
|
80
|
-
|
|
66
|
+
|
|
67
|
+
positional ? args[param_proxy.position] = value : kwargs[param_proxy.name] = value
|
|
81
68
|
end
|
|
82
69
|
|
|
83
70
|
if (return_proxy = method_proxy.return_proxy)
|
|
84
71
|
return_value = super(*args, **kwargs)
|
|
85
|
-
return_proxy.
|
|
72
|
+
return_proxy.expression.validate!(value: return_value, proxy: return_proxy)
|
|
86
73
|
return return_value
|
|
87
74
|
end
|
|
88
75
|
|
|
89
76
|
super(*args, **kwargs)
|
|
90
77
|
end
|
|
91
78
|
|
|
92
|
-
private name if class_proxy.private_start_line && method_proxy.start_line > class_proxy.private_start_line
|
|
79
|
+
private method_proxy.name if class_proxy.private_start_line && method_proxy.start_line > class_proxy.private_start_line
|
|
93
80
|
end
|
|
94
81
|
end
|
|
95
82
|
end
|
|
96
83
|
|
|
97
|
-
def untyped_methods(method_proxies:, class_proxy
|
|
84
|
+
def untyped_methods(method_proxies:, class_proxy:)
|
|
98
85
|
Module.new do
|
|
99
|
-
method_proxies.each do |
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# You are now in the binding of the includer class (`name` is also available here).
|
|
103
|
-
define_method(name) do |*args, **kwargs|
|
|
86
|
+
method_proxies.values.filter(&:expressions?).each do |method_proxy|
|
|
87
|
+
# You are now in the binding of the includer class.
|
|
88
|
+
define_method(method_proxy.name) do |*args, **kwargs|
|
|
104
89
|
# NOTE: Type checking is currently disabled. See 'config.type_checking'.
|
|
105
|
-
method_proxy =
|
|
90
|
+
method_proxy = Lowkey[class_proxy.file_path][class_proxy.namespace][__method__]
|
|
91
|
+
|
|
106
92
|
args, kwargs = Low::Redefiner.untyped_args(args:, kwargs:, method_proxy:)
|
|
107
93
|
super(*args, **kwargs)
|
|
108
94
|
end
|
|
109
95
|
|
|
110
|
-
private name if class_proxy.private_start_line && method_proxy.start_line > class_proxy.private_start_line
|
|
96
|
+
private method_proxy.name if class_proxy.private_start_line && method_proxy.start_line > class_proxy.private_start_line
|
|
111
97
|
end
|
|
112
98
|
end
|
|
113
99
|
end
|
|
114
|
-
|
|
115
|
-
def method_has_types?(method_proxy:, klass:)
|
|
116
|
-
if method_proxy.param_proxies == [] && method_proxy.return_proxy.nil?
|
|
117
|
-
Low::Repository.delete(name: method_proxy.name, klass:)
|
|
118
|
-
return false
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
true
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def method_within_class_bounds?(method_proxy:, class_proxy:, klass:)
|
|
125
|
-
within_bounds = method_proxy.start_line > class_proxy.start_line && method_proxy.start_line <= class_proxy.end_line
|
|
126
|
-
|
|
127
|
-
unless within_bounds
|
|
128
|
-
Low::Repository.delete(name: method_proxy.name, klass:)
|
|
129
|
-
return false
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
true
|
|
133
|
-
end
|
|
134
100
|
end
|
|
135
101
|
end
|
|
136
102
|
end
|
|
@@ -9,37 +9,35 @@ module Low
|
|
|
9
9
|
def type_reader(named_expressions)
|
|
10
10
|
named_expressions.each do |name, exp|
|
|
11
11
|
last_caller = caller_locations(1, 1).first
|
|
12
|
+
|
|
12
13
|
file_path = last_caller.path
|
|
13
14
|
start_line = last_caller.lineno
|
|
14
15
|
scope = "#{self}##{name}"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@low_methods[name] = MethodProxy.new(file_path:, start_line:, scope:, name:, return_proxy:)
|
|
17
|
+
expression = cast_type_expression(exp)
|
|
18
|
+
proxy = ::Lowkey::ReturnProxy.new(file_path:, start_line:, scope:, name:, expression:)
|
|
20
19
|
|
|
21
20
|
define_method(name) do
|
|
22
|
-
method_proxy = self.class.low_methods[name]
|
|
23
21
|
value = instance_variable_get("@#{name}")
|
|
24
|
-
|
|
22
|
+
expression.validate!(value:, proxy:)
|
|
25
23
|
value
|
|
26
24
|
end
|
|
27
25
|
end
|
|
28
26
|
end
|
|
29
27
|
|
|
30
|
-
def type_writer(named_expressions)
|
|
28
|
+
def type_writer(named_expressions)
|
|
31
29
|
named_expressions.each do |name, expression|
|
|
32
30
|
last_caller = caller_locations(1, 1).first
|
|
31
|
+
|
|
33
32
|
file_path = last_caller.path
|
|
34
33
|
start_line = last_caller.lineno
|
|
35
34
|
scope = "#{self}##{name}"
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
expression = cast_type_expression(expression)
|
|
37
|
+
proxy = ::Lowkey::ParamProxy.new(file_path:, start_line:, scope:, name:, type: :key_req, expression:)
|
|
39
38
|
|
|
40
39
|
define_method("#{name}=") do |value|
|
|
41
|
-
|
|
42
|
-
method_proxy.param_proxies.first.expression.validate!(value:, proxy: method_proxy.param_proxies.first)
|
|
40
|
+
expression.validate!(value:, proxy:)
|
|
43
41
|
instance_variable_set("@#{name}", value)
|
|
44
42
|
end
|
|
45
43
|
end
|
|
@@ -54,10 +52,10 @@ module Low
|
|
|
54
52
|
|
|
55
53
|
private
|
|
56
54
|
|
|
57
|
-
def
|
|
55
|
+
def cast_type_expression(expression)
|
|
58
56
|
if expression.is_a?(::Expressions::Expression)
|
|
59
57
|
expression
|
|
60
|
-
elsif
|
|
58
|
+
elsif TypeQuery.type?(expression)
|
|
61
59
|
TypeExpression.new(type: expression)
|
|
62
60
|
end
|
|
63
61
|
end
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
3
|
+
require_relative 'type_expression'
|
|
4
|
+
require_relative 'value_expression'
|
|
4
5
|
require_relative '../proxies/local_proxy'
|
|
5
|
-
require_relative '../types/error_types'
|
|
6
6
|
|
|
7
7
|
module Low
|
|
8
|
-
module
|
|
8
|
+
module ExpressionHelpers
|
|
9
9
|
def type(type_expression)
|
|
10
10
|
value = type_expression.default_value
|
|
11
11
|
|
|
@@ -20,12 +20,12 @@ module Low
|
|
|
20
20
|
|
|
21
21
|
value
|
|
22
22
|
rescue NoMethodError
|
|
23
|
-
raise ConfigError, "Invalid type expression
|
|
23
|
+
raise ConfigError, "Invalid type expression. Did you add 'using LowType::Syntax'?"
|
|
24
24
|
end
|
|
25
25
|
alias low_type type
|
|
26
26
|
|
|
27
27
|
def value(type)
|
|
28
|
-
|
|
28
|
+
TypeExpression.new(default_value: ValueExpression.new(value: type))
|
|
29
29
|
end
|
|
30
30
|
alias low_value value
|
|
31
31
|
end
|
|
@@ -22,7 +22,7 @@ module Low
|
|
|
22
22
|
@types << type unless type.nil?
|
|
23
23
|
@default_value = default_value
|
|
24
24
|
# TODO: Override per type expression with a config expression.
|
|
25
|
-
@deep_type_check =
|
|
25
|
+
@deep_type_check = nil
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def required?
|
|
@@ -76,7 +76,7 @@ module Low
|
|
|
76
76
|
|
|
77
77
|
# Override Expressions as LowType supports complex types which are implemented as values.
|
|
78
78
|
def value?(expression)
|
|
79
|
-
|
|
79
|
+
TypeQuery.value?(expression) || expression.nil?
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def valid_subtype(subtype:)
|
|
@@ -141,7 +141,9 @@ module Low
|
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
def deep_type_check?
|
|
144
|
-
@deep_type_check
|
|
144
|
+
return @deep_type_check unless @deep_type_check.nil?
|
|
145
|
+
|
|
146
|
+
LowType.config.deep_type_check
|
|
145
147
|
end
|
|
146
148
|
end
|
|
147
149
|
end
|
|
@@ -2,18 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Low
|
|
4
4
|
# Used by proxies to output errors.
|
|
5
|
-
|
|
6
|
-
attr_reader :file_path, :start_line, :scope
|
|
7
|
-
|
|
8
|
-
def initialize(file_path:, start_line:, scope:)
|
|
9
|
-
@file_path = file_path
|
|
10
|
-
@start_line = start_line
|
|
11
|
-
@scope = scope
|
|
12
|
-
|
|
13
|
-
@output_mode = LowType.config.output_mode
|
|
14
|
-
@output_size = LowType.config.output_size
|
|
15
|
-
end
|
|
16
|
-
|
|
5
|
+
module ErrorHandling
|
|
17
6
|
def error_type
|
|
18
7
|
raise NotImplementedError
|
|
19
8
|
end
|
|
@@ -23,12 +12,12 @@ module Low
|
|
|
23
12
|
end
|
|
24
13
|
|
|
25
14
|
def output(value:)
|
|
26
|
-
case
|
|
15
|
+
case LowType.config.output_mode
|
|
27
16
|
when :type
|
|
28
17
|
# TODO: Show full type structure in error output instead of just the type of the supertype.
|
|
29
18
|
value.class
|
|
30
19
|
when :value
|
|
31
|
-
value.inspect[0
|
|
20
|
+
value.inspect[0...LowType.config.output_size]
|
|
32
21
|
else
|
|
33
22
|
'REDACTED'
|
|
34
23
|
end
|
data/lib/low_type.rb
CHANGED
|
@@ -5,38 +5,49 @@ require 'lowkey'
|
|
|
5
5
|
require_relative 'adapters/adapter_loader'
|
|
6
6
|
require_relative 'definitions/redefiner'
|
|
7
7
|
require_relative 'definitions/type_accessors'
|
|
8
|
-
require_relative 'expressions/
|
|
8
|
+
require_relative 'expressions/expression_helpers'
|
|
9
9
|
require_relative 'queries/file_query'
|
|
10
10
|
require_relative 'syntax/syntax'
|
|
11
11
|
require_relative 'types/complex_types'
|
|
12
12
|
|
|
13
|
+
# Architecture:
|
|
14
|
+
# ┌────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────┐ ┌─────────┐
|
|
15
|
+
# │ Lowkey │ │ Proxies │ │ Expressions │ │ LowType │ │ Methods │
|
|
16
|
+
# └────┬───┘ └────┬────┘ └──────┬──────┘ └────┬────┘ └────┬────┘
|
|
17
|
+
# │ │ │ │ │
|
|
18
|
+
# │ Parses AST │ │ │ │
|
|
19
|
+
# ├─────────────►│ │ │ │
|
|
20
|
+
# │ │ │ │ │
|
|
21
|
+
# │ │ Stores │ │ │
|
|
22
|
+
# │ ├────────────────►│ │ │
|
|
23
|
+
# │ │ │ │ │
|
|
24
|
+
# │ │ │ Evaluates │ │
|
|
25
|
+
# │ │ │◄────────────────┤ │
|
|
26
|
+
# │ │ │ │ │
|
|
27
|
+
# │ │ │ │ Redefines │
|
|
28
|
+
# │ │ │ ├──────────────►│
|
|
29
|
+
# │ │ │ │ │
|
|
30
|
+
# │ │ │ Validates │ │
|
|
31
|
+
# │ │ │◄┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┤
|
|
32
|
+
# │ │ │ │ │
|
|
13
33
|
module LowType
|
|
14
|
-
# We do as much as possible on class load rather than on instantiation to be thread-safe and efficient.
|
|
15
|
-
def self.included(klass)
|
|
16
|
-
require_relative 'syntax/union_types' if LowType.config.union_type_expressions
|
|
17
|
-
|
|
18
|
-
class << klass
|
|
19
|
-
def low_methods
|
|
20
|
-
@low_methods ||= {}
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
|
|
34
|
+
# We do as much as possible on class load rather than on object instantiation to be thread-safe and efficient.
|
|
35
|
+
def self.included(klass)
|
|
24
36
|
file_path = Low::FileQuery.file_path(klass:)
|
|
25
|
-
return unless File.exist?(file_path)
|
|
26
|
-
|
|
27
37
|
file_proxy = Lowkey.load(file_path:)
|
|
28
|
-
class_proxy = file_proxy
|
|
38
|
+
class_proxy = file_proxy[klass.name]
|
|
39
|
+
|
|
40
|
+
Low::Evaluator.evaluate(method_proxies: class_proxy.keyed_methods)
|
|
29
41
|
|
|
42
|
+
klass.include Low::ExpressionHelpers
|
|
43
|
+
klass.extend Low::ExpressionHelpers
|
|
30
44
|
klass.extend Low::TypeAccessors
|
|
31
|
-
klass.
|
|
32
|
-
|
|
33
|
-
klass.prepend Low::Redefiner.redefine(
|
|
34
|
-
klass.singleton_class.prepend Low::Redefiner.redefine(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
adapter.process
|
|
38
|
-
klass.prepend Low::Adapter::Methods
|
|
39
|
-
end
|
|
45
|
+
klass.extend Low::Types
|
|
46
|
+
|
|
47
|
+
klass.prepend Low::Redefiner.redefine(method_proxies: class_proxy.instance_methods, class_proxy:)
|
|
48
|
+
klass.singleton_class.prepend Low::Redefiner.redefine(method_proxies: class_proxy.class_methods, class_proxy:)
|
|
49
|
+
|
|
50
|
+
Low::Adapter::Loader.load(klass:, class_proxy:)
|
|
40
51
|
end
|
|
41
52
|
|
|
42
53
|
class << self
|
|
@@ -49,7 +60,7 @@ module LowType
|
|
|
49
60
|
:deep_type_check,
|
|
50
61
|
:union_type_expressions
|
|
51
62
|
)
|
|
52
|
-
@config ||= config.new(true, :error, :type, 100,
|
|
63
|
+
@config ||= config.new(true, :error, :type, 100, true, true)
|
|
53
64
|
end
|
|
54
65
|
|
|
55
66
|
def configure
|
data/lib/proxies/local_proxy.rb
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../interfaces/
|
|
3
|
+
require_relative '../interfaces/error_handling'
|
|
4
4
|
require_relative '../types/error_types'
|
|
5
5
|
|
|
6
6
|
module Low
|
|
7
|
-
class LocalProxy
|
|
8
|
-
|
|
7
|
+
class LocalProxy
|
|
8
|
+
include ErrorHandling
|
|
9
|
+
|
|
10
|
+
attr_reader :type_expression, :name, :file_path, :start_line, :scope
|
|
9
11
|
|
|
10
12
|
def initialize(type_expression:, name:, file_path:, start_line:, scope:)
|
|
11
|
-
|
|
13
|
+
@file_path = file_path
|
|
14
|
+
@start_line = start_line
|
|
15
|
+
@scope = scope
|
|
12
16
|
|
|
13
17
|
@type_expression = type_expression
|
|
14
18
|
@name = name
|
data/lib/proxies/param_proxy.rb
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative '../types/error_types'
|
|
5
|
-
|
|
6
|
-
module Low
|
|
7
|
-
class ParamProxy < ErrorInterface
|
|
8
|
-
attr_reader :expression, :name, :type, :position
|
|
9
|
-
|
|
10
|
-
# TODO: Refactor file path, start line and scope into "meta scope" model.
|
|
11
|
-
def initialize(expression:, name:, type:, file_path:, start_line:, scope:, position: nil) # rubocop:disable Metrics/ParameterLists
|
|
12
|
-
super(file_path:, start_line:, scope:)
|
|
3
|
+
require 'lowkey'
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@type = type
|
|
17
|
-
@position = position
|
|
18
|
-
end
|
|
5
|
+
require_relative '../interfaces/error_handling'
|
|
6
|
+
require_relative '../types/error_types'
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
module ::Lowkey
|
|
9
|
+
class ParamProxy
|
|
10
|
+
include ::Low::ErrorHandling
|
|
23
11
|
|
|
24
12
|
def error_type
|
|
25
|
-
ArgumentTypeError
|
|
13
|
+
::Low::ArgumentTypeError
|
|
26
14
|
end
|
|
27
15
|
|
|
28
16
|
def error_message(value:)
|
data/lib/proxies/return_proxy.rb
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative '../types/error_types'
|
|
5
|
-
|
|
6
|
-
module Low
|
|
7
|
-
class ReturnProxy < ErrorInterface
|
|
8
|
-
attr_reader :type_expression, :name
|
|
3
|
+
require 'lowkey'
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
require_relative '../interfaces/error_handling'
|
|
6
|
+
require_relative '../types/error_types'
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
module ::Lowkey
|
|
9
|
+
class ReturnProxy
|
|
10
|
+
include ::Low::ErrorHandling
|
|
16
11
|
|
|
17
12
|
def error_type
|
|
18
|
-
ReturnTypeError
|
|
13
|
+
::Low::ReturnTypeError
|
|
19
14
|
end
|
|
20
15
|
|
|
21
16
|
def error_message(value:)
|
|
22
|
-
"Invalid return type '#{output(value:)}' for method '#{@name}'. Valid types: '#{@
|
|
17
|
+
"Invalid return type '#{output(value:)}' for method '#{@name}'. Valid types: '#{@expression.valid_types}'"
|
|
23
18
|
end
|
|
24
19
|
end
|
|
25
20
|
end
|
data/lib/queries/file_query.rb
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Low
|
|
4
|
+
class MissingFileError < StandardError; end
|
|
5
|
+
|
|
4
6
|
class FileQuery
|
|
5
7
|
class << self
|
|
6
8
|
def file_path(klass:)
|
|
7
9
|
includer_line = line_from_class(klass:) || line_from_include || ''
|
|
8
|
-
includer_line.split(':').first || ''
|
|
10
|
+
file_path = includer_line.split(':').first || ''
|
|
11
|
+
|
|
12
|
+
return file_path if File.exist?(file_path)
|
|
13
|
+
|
|
14
|
+
raise MissingFileError, "No file found at path '#{file_path}'"
|
|
9
15
|
end
|
|
10
16
|
|
|
11
17
|
private
|
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: 1.1.
|
|
4
|
+
version: 1.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
32
|
+
version: '0.3'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
39
|
+
version: '0.3'
|
|
40
40
|
description: An elegant and simple way to define types in Ruby, only when you need
|
|
41
41
|
them.
|
|
42
42
|
email:
|
|
@@ -47,20 +47,17 @@ extra_rdoc_files: []
|
|
|
47
47
|
files:
|
|
48
48
|
- lib/adapters/adapter_loader.rb
|
|
49
49
|
- lib/adapters/sinatra_adapter.rb
|
|
50
|
+
- lib/definitions/evaluator.rb
|
|
50
51
|
- lib/definitions/redefiner.rb
|
|
51
|
-
- lib/definitions/repository.rb
|
|
52
52
|
- lib/definitions/type_accessors.rb
|
|
53
|
-
- lib/expressions/
|
|
53
|
+
- lib/expressions/expression_helpers.rb
|
|
54
54
|
- lib/expressions/type_expression.rb
|
|
55
55
|
- lib/expressions/value_expression.rb
|
|
56
|
-
- lib/factories/expression_factory.rb
|
|
57
|
-
- lib/factories/proxy_factory.rb
|
|
58
56
|
- lib/factories/type_factory.rb
|
|
59
57
|
- lib/interfaces/adapter_interface.rb
|
|
60
|
-
- lib/interfaces/
|
|
58
|
+
- lib/interfaces/error_handling.rb
|
|
61
59
|
- lib/low_type.rb
|
|
62
60
|
- lib/proxies/local_proxy.rb
|
|
63
|
-
- lib/proxies/method_proxy.rb
|
|
64
61
|
- lib/proxies/param_proxy.rb
|
|
65
62
|
- lib/proxies/return_proxy.rb
|
|
66
63
|
- lib/queries/file_query.rb
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Low
|
|
4
|
-
class Repository
|
|
5
|
-
class << self
|
|
6
|
-
def all(klass:)
|
|
7
|
-
klass.low_methods
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def save(method:, klass:)
|
|
11
|
-
klass.low_methods[method.name] = method
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def delete(name:, klass:)
|
|
15
|
-
klass.low_methods.delete(name)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Redefiner inlines this method in define_method() for better performance. TODO: Test this assumption.
|
|
19
|
-
def load(name:, object:)
|
|
20
|
-
singleton(object:).low_methods[name]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# TODO: export() to RBS
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def singleton(object:)
|
|
28
|
-
object.instance_of?(Class) ? object : object.class || Object
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../expressions/type_expression'
|
|
4
|
-
require_relative '../expressions/value_expression'
|
|
5
|
-
|
|
6
|
-
module Low
|
|
7
|
-
class ExpressionFactory
|
|
8
|
-
class << self
|
|
9
|
-
def type_expression_with_value(type:)
|
|
10
|
-
TypeExpression.new(default_value: ValueExpression.new(value: type))
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'expressions'
|
|
4
|
-
require 'lowkey'
|
|
5
|
-
|
|
6
|
-
require_relative '../expressions/expressions'
|
|
7
|
-
require_relative '../expressions/type_expression'
|
|
8
|
-
require_relative '../proxies/param_proxy'
|
|
9
|
-
require_relative '../proxies/return_proxy'
|
|
10
|
-
require_relative '../syntax/syntax'
|
|
11
|
-
require_relative '../types/complex_types'
|
|
12
|
-
require_relative '../types/status'
|
|
13
|
-
|
|
14
|
-
module Low
|
|
15
|
-
class ProxyFactory
|
|
16
|
-
using ::LowType::Syntax
|
|
17
|
-
|
|
18
|
-
class << self
|
|
19
|
-
include Low::Expressions
|
|
20
|
-
include Low::Types
|
|
21
|
-
|
|
22
|
-
# The evals below aren't a security risk because the code comes from a trusted source; the file itself that did the include.
|
|
23
|
-
def param_proxies(method_node:, file_path:, scope:)
|
|
24
|
-
return [] if method_node.parameters.nil?
|
|
25
|
-
|
|
26
|
-
params_without_block = method_node.parameters.slice.delete_suffix(', &block')
|
|
27
|
-
|
|
28
|
-
ruby_method = eval("-> (#{params_without_block}) {}", binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
|
|
29
|
-
|
|
30
|
-
# Local variable names are prefixed with __lt or __rb where needed to avoid being overridden by method parameters.
|
|
31
|
-
typed_method = <<~RUBY
|
|
32
|
-
-> (#{params_without_block}, __rb_method:, __lt_file:) {
|
|
33
|
-
param_proxies_for_expressions(ruby_method: __rb_method, file_path: __lt_file, start_line: method_node.start_line, scope:, method_binding: binding)
|
|
34
|
-
}
|
|
35
|
-
RUBY
|
|
36
|
-
|
|
37
|
-
required_args, required_kwargs = required_args(ruby_method:)
|
|
38
|
-
|
|
39
|
-
# Called with only required args (as nil) and optional args omitted, to evaluate expressions stored as default values.
|
|
40
|
-
eval(typed_method, binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
|
|
41
|
-
.call(*required_args, **required_kwargs, __rb_method: ruby_method, __lt_file: file_path)
|
|
42
|
-
|
|
43
|
-
# TODO: Unit test this.
|
|
44
|
-
rescue ArgumentError => e
|
|
45
|
-
raise ArgumentError, "Incorrect param syntax: #{e.message}"
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def return_proxy(method_node:, name:, file_path:, scope:)
|
|
49
|
-
return_type = Lowkey::ClassProxy.return_type(method_node:)
|
|
50
|
-
return nil if return_type.nil?
|
|
51
|
-
|
|
52
|
-
start_line = method_node.start_line
|
|
53
|
-
|
|
54
|
-
begin
|
|
55
|
-
# Not a security risk because the code comes from a trusted source; the file that did the include. Does the file trust itself?
|
|
56
|
-
expression = eval(return_type.slice, binding, __FILE__, __LINE__).call # rubocop:disable Security/Eval
|
|
57
|
-
rescue NameError
|
|
58
|
-
raise NameError, "Unknown return type '#{return_type.slice}' for #{scope} at #{file_path}:#{start_line}"
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
expression = TypeExpression.new(type: expression) unless expression.is_a?(TypeExpression)
|
|
62
|
-
|
|
63
|
-
ReturnProxy.new(type_expression: expression, name:, file_path:, start_line:, scope:)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
|
|
68
|
-
def required_args(ruby_method:)
|
|
69
|
-
required_args = []
|
|
70
|
-
required_kwargs = {}
|
|
71
|
-
|
|
72
|
-
ruby_method.parameters.each do |param|
|
|
73
|
-
param_type, param_name = param
|
|
74
|
-
|
|
75
|
-
case param_type
|
|
76
|
-
when :req
|
|
77
|
-
required_args << nil
|
|
78
|
-
when :keyreq
|
|
79
|
-
required_kwargs[param_name] = nil
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
[required_args, required_kwargs]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def param_proxies_for_expressions(ruby_method:, file_path:, start_line:, scope:, method_binding:)
|
|
87
|
-
param_proxies = []
|
|
88
|
-
|
|
89
|
-
ruby_method.parameters.each_with_index do |param, position|
|
|
90
|
-
type, name = param
|
|
91
|
-
|
|
92
|
-
# We don't support splatted *positional and **keyword arguments as by definition they are untyped.
|
|
93
|
-
next if type == :rest
|
|
94
|
-
|
|
95
|
-
position = nil unless %i[opt req].include?(type)
|
|
96
|
-
local_variable = method_binding.local_variable_get(name)
|
|
97
|
-
|
|
98
|
-
expression = nil
|
|
99
|
-
if local_variable.is_a?(::Expressions::Expression)
|
|
100
|
-
expression = local_variable
|
|
101
|
-
elsif local_variable.instance_of?(Class) && local_variable < ::Expressions::Expression
|
|
102
|
-
expression = local_variable.new(provider_key: name)
|
|
103
|
-
elsif ::Low::TypeQuery.type?(local_variable)
|
|
104
|
-
expression = TypeExpression.new(type: local_variable)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
param_proxies << ParamProxy.new(expression:, name:, type:, file_path:, start_line:, scope:, position:) if expression
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
param_proxies
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
end
|
data/lib/proxies/method_proxy.rb
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Low
|
|
4
|
-
class MethodProxy
|
|
5
|
-
attr_reader :file_path, :start_line, :scope, :name, :param_proxies, :return_proxy
|
|
6
|
-
|
|
7
|
-
# TODO: Refactor file path, start line and scope into "meta scope" model.
|
|
8
|
-
def initialize(file_path:, start_line:, scope:, name:, param_proxies: [], return_proxy: nil) # rubocop:disable Metrics/ParameterLists
|
|
9
|
-
@file_path = file_path
|
|
10
|
-
@start_line = start_line
|
|
11
|
-
@scope = scope
|
|
12
|
-
|
|
13
|
-
@name = name
|
|
14
|
-
@param_proxies = param_proxies
|
|
15
|
-
@return_proxy = return_proxy
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|