t-ruby 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +221 -0
- data/bin/trc +6 -0
- data/lib/t_ruby/benchmark.rb +592 -0
- data/lib/t_ruby/bundler_integration.rb +569 -0
- data/lib/t_ruby/cache.rb +774 -0
- data/lib/t_ruby/cli.rb +106 -0
- data/lib/t_ruby/compiler.rb +299 -0
- data/lib/t_ruby/config.rb +53 -0
- data/lib/t_ruby/constraint_checker.rb +441 -0
- data/lib/t_ruby/declaration_generator.rb +298 -0
- data/lib/t_ruby/doc_generator.rb +474 -0
- data/lib/t_ruby/error_handler.rb +132 -0
- data/lib/t_ruby/generic_type_parser.rb +68 -0
- data/lib/t_ruby/intersection_type_parser.rb +30 -0
- data/lib/t_ruby/ir.rb +1301 -0
- data/lib/t_ruby/lsp_server.rb +994 -0
- data/lib/t_ruby/package_manager.rb +735 -0
- data/lib/t_ruby/parser.rb +245 -0
- data/lib/t_ruby/parser_combinator.rb +942 -0
- data/lib/t_ruby/rbs_generator.rb +71 -0
- data/lib/t_ruby/runtime_validator.rb +367 -0
- data/lib/t_ruby/smt_solver.rb +1076 -0
- data/lib/t_ruby/type_alias_registry.rb +102 -0
- data/lib/t_ruby/type_checker.rb +770 -0
- data/lib/t_ruby/type_erasure.rb +26 -0
- data/lib/t_ruby/type_inferencer.rb +580 -0
- data/lib/t_ruby/union_type_parser.rb +38 -0
- data/lib/t_ruby/version.rb +5 -0
- data/lib/t_ruby/watcher.rb +320 -0
- data/lib/t_ruby.rb +42 -0
- metadata +87 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
class RBSGenerator
|
|
5
|
+
def initialize
|
|
6
|
+
# RBS generation configuration
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def generate(functions, type_aliases)
|
|
10
|
+
lines = []
|
|
11
|
+
|
|
12
|
+
# Add type aliases
|
|
13
|
+
type_aliases.each do |type_alias|
|
|
14
|
+
lines << generate_type_alias(type_alias)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
lines << "" if type_aliases.any? && functions.any?
|
|
18
|
+
|
|
19
|
+
# Add function signatures
|
|
20
|
+
functions.each do |func|
|
|
21
|
+
lines << generate_function_signature(func)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
lines.compact.join("\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def generate_type_aliases(aliases)
|
|
28
|
+
aliases.map { |alias_def| generate_type_alias(alias_def) }.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def generate_type_alias(type_alias)
|
|
32
|
+
name = type_alias[:name]
|
|
33
|
+
definition = type_alias[:definition]
|
|
34
|
+
|
|
35
|
+
"type #{name} = #{definition}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def generate_function_signature(func)
|
|
39
|
+
name = func[:name]
|
|
40
|
+
params = func[:params] || []
|
|
41
|
+
return_type = func[:return_type]
|
|
42
|
+
|
|
43
|
+
param_str = format_parameters(params)
|
|
44
|
+
return_str = format_return_type(return_type)
|
|
45
|
+
|
|
46
|
+
"def #{name}: (#{param_str}) -> #{return_str}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def format_parameters(params)
|
|
52
|
+
return if params.empty?
|
|
53
|
+
|
|
54
|
+
param_strs = params.map do |param|
|
|
55
|
+
param_name = param[:name]
|
|
56
|
+
param_type = param[:type] || "Object"
|
|
57
|
+
|
|
58
|
+
"#{param_name}: #{param_type}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
param_strs.join(", ")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def format_return_type(return_type)
|
|
65
|
+
return "void" if return_type == "void"
|
|
66
|
+
return "nil" if return_type.nil?
|
|
67
|
+
|
|
68
|
+
return_type
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Configuration for runtime validation
|
|
5
|
+
class ValidationConfig
|
|
6
|
+
attr_accessor :validate_all, :validate_public_only, :raise_on_error
|
|
7
|
+
attr_accessor :log_violations, :strict_mode
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@validate_all = true
|
|
11
|
+
@validate_public_only = false
|
|
12
|
+
@raise_on_error = true
|
|
13
|
+
@log_violations = false
|
|
14
|
+
@strict_mode = false
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Generates runtime type validation code
|
|
19
|
+
class RuntimeValidator
|
|
20
|
+
attr_reader :config
|
|
21
|
+
|
|
22
|
+
# Type mappings for runtime checks
|
|
23
|
+
TYPE_CHECKS = {
|
|
24
|
+
"String" => ".is_a?(String)",
|
|
25
|
+
"Integer" => ".is_a?(Integer)",
|
|
26
|
+
"Float" => ".is_a?(Float)",
|
|
27
|
+
"Numeric" => ".is_a?(Numeric)",
|
|
28
|
+
"Boolean" => " == true || %s == false",
|
|
29
|
+
"Symbol" => ".is_a?(Symbol)",
|
|
30
|
+
"Array" => ".is_a?(Array)",
|
|
31
|
+
"Hash" => ".is_a?(Hash)",
|
|
32
|
+
"nil" => ".nil?",
|
|
33
|
+
"Object" => ".is_a?(Object)",
|
|
34
|
+
"Class" => ".is_a?(Class)",
|
|
35
|
+
"Module" => ".is_a?(Module)",
|
|
36
|
+
"Proc" => ".is_a?(Proc)",
|
|
37
|
+
"Regexp" => ".is_a?(Regexp)",
|
|
38
|
+
"Range" => ".is_a?(Range)",
|
|
39
|
+
"Time" => ".is_a?(Time)",
|
|
40
|
+
"Date" => ".is_a?(Date)",
|
|
41
|
+
"IO" => ".is_a?(IO)",
|
|
42
|
+
"File" => ".is_a?(File)"
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
def initialize(config = nil)
|
|
46
|
+
@config = config || ValidationConfig.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Generate validation code for a function
|
|
50
|
+
def generate_function_validation(function_info)
|
|
51
|
+
validations = []
|
|
52
|
+
|
|
53
|
+
# Parameter validations
|
|
54
|
+
function_info[:params].each do |param|
|
|
55
|
+
next unless param[:type]
|
|
56
|
+
|
|
57
|
+
validation = generate_param_validation(param[:name], param[:type])
|
|
58
|
+
validations << validation if validation
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Return type validation (if specified)
|
|
62
|
+
if function_info[:return_type]
|
|
63
|
+
return_validation = generate_return_validation(function_info[:return_type])
|
|
64
|
+
validations << return_validation if return_validation
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
validations
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Generate validation for a single parameter
|
|
71
|
+
def generate_param_validation(param_name, type_annotation)
|
|
72
|
+
check_code = generate_type_check(param_name, type_annotation)
|
|
73
|
+
return nil unless check_code
|
|
74
|
+
|
|
75
|
+
error_message = "TypeError: #{param_name} must be #{type_annotation}, got \#{#{param_name}.class}"
|
|
76
|
+
|
|
77
|
+
if @config.raise_on_error
|
|
78
|
+
"raise #{error_message.inspect.gsub('\#{', '#{')} unless #{check_code}"
|
|
79
|
+
else
|
|
80
|
+
"warn #{error_message.inspect.gsub('\#{', '#{')} unless #{check_code}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Generate type check expression
|
|
85
|
+
def generate_type_check(var_name, type_annotation)
|
|
86
|
+
# Handle nil type
|
|
87
|
+
return "#{var_name}.nil?" if type_annotation == "nil"
|
|
88
|
+
|
|
89
|
+
# Handle union types
|
|
90
|
+
if type_annotation.include?("|")
|
|
91
|
+
return generate_union_check(var_name, type_annotation)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Handle generic types
|
|
95
|
+
if type_annotation.include?("<")
|
|
96
|
+
return generate_generic_check(var_name, type_annotation)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Handle intersection types
|
|
100
|
+
if type_annotation.include?("&")
|
|
101
|
+
return generate_intersection_check(var_name, type_annotation)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Handle optional types (ending with ?)
|
|
105
|
+
if type_annotation.end_with?("?")
|
|
106
|
+
base_type = type_annotation[0..-2]
|
|
107
|
+
base_check = generate_simple_type_check(var_name, base_type)
|
|
108
|
+
return "(#{var_name}.nil? || #{base_check})"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Simple type check
|
|
112
|
+
generate_simple_type_check(var_name, type_annotation)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Generate simple type check
|
|
116
|
+
def generate_simple_type_check(var_name, type_name)
|
|
117
|
+
if type_name == "Boolean"
|
|
118
|
+
"(#{var_name} == true || #{var_name} == false)"
|
|
119
|
+
elsif TYPE_CHECKS.key?(type_name)
|
|
120
|
+
"#{var_name}#{TYPE_CHECKS[type_name]}"
|
|
121
|
+
else
|
|
122
|
+
# Custom type - use is_a? or respond_to?
|
|
123
|
+
"#{var_name}.is_a?(#{type_name})"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Generate union type check
|
|
128
|
+
def generate_union_check(var_name, union_type)
|
|
129
|
+
types = union_type.split("|").map(&:strip)
|
|
130
|
+
checks = types.map { |t| generate_type_check(var_name, t) }
|
|
131
|
+
"(#{checks.join(' || ')})"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Generate generic type check
|
|
135
|
+
def generate_generic_check(var_name, generic_type)
|
|
136
|
+
match = generic_type.match(/^(\w+)<(.+)>$/)
|
|
137
|
+
return nil unless match
|
|
138
|
+
|
|
139
|
+
container_type = match[1]
|
|
140
|
+
element_type = match[2]
|
|
141
|
+
|
|
142
|
+
case container_type
|
|
143
|
+
when "Array"
|
|
144
|
+
"#{var_name}.is_a?(Array) && #{var_name}.all? { |_e| #{generate_type_check('_e', element_type)} }"
|
|
145
|
+
when "Hash"
|
|
146
|
+
if element_type.include?(",")
|
|
147
|
+
key_type, value_type = element_type.split(",").map(&:strip)
|
|
148
|
+
"#{var_name}.is_a?(Hash) && #{var_name}.all? { |_k, _v| #{generate_type_check('_k', key_type)} && #{generate_type_check('_v', value_type)} }"
|
|
149
|
+
else
|
|
150
|
+
"#{var_name}.is_a?(Hash)"
|
|
151
|
+
end
|
|
152
|
+
when "Set"
|
|
153
|
+
"#{var_name}.is_a?(Set) && #{var_name}.all? { |_e| #{generate_type_check('_e', element_type)} }"
|
|
154
|
+
else
|
|
155
|
+
# Generic with unknown container - just check container type
|
|
156
|
+
"#{var_name}.is_a?(#{container_type})"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Generate intersection type check
|
|
161
|
+
def generate_intersection_check(var_name, intersection_type)
|
|
162
|
+
types = intersection_type.split("&").map(&:strip)
|
|
163
|
+
checks = types.map { |t| generate_type_check(var_name, t) }
|
|
164
|
+
"(#{checks.join(' && ')})"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Generate return value validation wrapper
|
|
168
|
+
def generate_return_validation(return_type)
|
|
169
|
+
{
|
|
170
|
+
type: :return,
|
|
171
|
+
check: generate_type_check("_result", return_type),
|
|
172
|
+
return_type: return_type
|
|
173
|
+
}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Transform source code to include runtime validations
|
|
177
|
+
def transform(source, parse_result)
|
|
178
|
+
lines = source.split("\n")
|
|
179
|
+
output_lines = []
|
|
180
|
+
in_function = false
|
|
181
|
+
current_function = nil
|
|
182
|
+
function_indent = 0
|
|
183
|
+
|
|
184
|
+
parse_result[:functions].each do |func|
|
|
185
|
+
@function_validations ||= {}
|
|
186
|
+
@function_validations[func[:name]] = generate_function_validation(func)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
lines.each_with_index do |line, idx|
|
|
190
|
+
# Check for function definition
|
|
191
|
+
if line.match?(/^\s*def\s+(\w+)/)
|
|
192
|
+
match = line.match(/^(\s*)def\s+(\w+)/)
|
|
193
|
+
function_indent = match[1].length
|
|
194
|
+
function_name = match[2]
|
|
195
|
+
|
|
196
|
+
# Add validation code after function definition
|
|
197
|
+
output_lines << line
|
|
198
|
+
|
|
199
|
+
if @function_validations && @function_validations[function_name]
|
|
200
|
+
validations = @function_validations[function_name]
|
|
201
|
+
param_validations = validations.select { |v| v.is_a?(String) }
|
|
202
|
+
|
|
203
|
+
param_validations.each do |validation|
|
|
204
|
+
output_lines << "#{' ' * (function_indent + 2)}#{validation}"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
in_function = true
|
|
209
|
+
current_function = function_name
|
|
210
|
+
elsif in_function && line.match?(/^\s*end\s*$/)
|
|
211
|
+
# End of function
|
|
212
|
+
in_function = false
|
|
213
|
+
current_function = nil
|
|
214
|
+
output_lines << line
|
|
215
|
+
else
|
|
216
|
+
output_lines << line
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
output_lines.join("\n")
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Generate a validation module that can be included
|
|
224
|
+
def generate_validation_module(functions)
|
|
225
|
+
module_code = <<~RUBY
|
|
226
|
+
# frozen_string_literal: true
|
|
227
|
+
# Auto-generated runtime type validation module
|
|
228
|
+
|
|
229
|
+
module TRubyValidation
|
|
230
|
+
class TypeError < StandardError; end
|
|
231
|
+
|
|
232
|
+
def self.validate_type(value, expected_type, param_name = "value")
|
|
233
|
+
RUBY
|
|
234
|
+
|
|
235
|
+
module_code += <<~RUBY
|
|
236
|
+
case expected_type
|
|
237
|
+
when "String"
|
|
238
|
+
raise TypeError, "\#{param_name} must be String, got \#{value.class}" unless value.is_a?(String)
|
|
239
|
+
when "Integer"
|
|
240
|
+
raise TypeError, "\#{param_name} must be Integer, got \#{value.class}" unless value.is_a?(Integer)
|
|
241
|
+
when "Float"
|
|
242
|
+
raise TypeError, "\#{param_name} must be Float, got \#{value.class}" unless value.is_a?(Float)
|
|
243
|
+
when "Boolean"
|
|
244
|
+
raise TypeError, "\#{param_name} must be Boolean, got \#{value.class}" unless value == true || value == false
|
|
245
|
+
when "Symbol"
|
|
246
|
+
raise TypeError, "\#{param_name} must be Symbol, got \#{value.class}" unless value.is_a?(Symbol)
|
|
247
|
+
when "Array"
|
|
248
|
+
raise TypeError, "\#{param_name} must be Array, got \#{value.class}" unless value.is_a?(Array)
|
|
249
|
+
when "Hash"
|
|
250
|
+
raise TypeError, "\#{param_name} must be Hash, got \#{value.class}" unless value.is_a?(Hash)
|
|
251
|
+
when "nil"
|
|
252
|
+
raise TypeError, "\#{param_name} must be nil, got \#{value.class}" unless value.nil?
|
|
253
|
+
else
|
|
254
|
+
# Custom type check
|
|
255
|
+
begin
|
|
256
|
+
type_class = Object.const_get(expected_type)
|
|
257
|
+
raise TypeError, "\#{param_name} must be \#{expected_type}, got \#{value.class}" unless value.is_a?(type_class)
|
|
258
|
+
rescue NameError
|
|
259
|
+
# Unknown type, skip validation
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
true
|
|
263
|
+
end
|
|
264
|
+
RUBY
|
|
265
|
+
|
|
266
|
+
# Generate validation methods for each function
|
|
267
|
+
functions.each do |func|
|
|
268
|
+
next if func[:params].empty? && !func[:return_type]
|
|
269
|
+
|
|
270
|
+
method_name = "validate_#{func[:name]}_params"
|
|
271
|
+
param_list = func[:params].map { |p| p[:name] }.join(", ")
|
|
272
|
+
|
|
273
|
+
module_code += "\n def self.#{method_name}(#{param_list})\n"
|
|
274
|
+
|
|
275
|
+
func[:params].each do |param|
|
|
276
|
+
next unless param[:type]
|
|
277
|
+
module_code += " validate_type(#{param[:name]}, #{param[:type].inspect}, #{param[:name].inspect})\n"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
module_code += " true\n end\n"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
module_code += "end\n"
|
|
284
|
+
module_code
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Check if validation should be applied based on config
|
|
288
|
+
def should_validate?(visibility)
|
|
289
|
+
return true if @config.validate_all
|
|
290
|
+
return visibility == :public if @config.validate_public_only
|
|
291
|
+
false
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Runtime type error
|
|
296
|
+
class RuntimeTypeError < StandardError
|
|
297
|
+
attr_reader :expected_type, :actual_type, :value, :location
|
|
298
|
+
|
|
299
|
+
def initialize(message, expected_type: nil, actual_type: nil, value: nil, location: nil)
|
|
300
|
+
super(message)
|
|
301
|
+
@expected_type = expected_type
|
|
302
|
+
@actual_type = actual_type
|
|
303
|
+
@value = value
|
|
304
|
+
@location = location
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Mixin for adding runtime validation to classes
|
|
309
|
+
module RuntimeTypeChecks
|
|
310
|
+
def self.included(base)
|
|
311
|
+
base.extend(ClassMethods)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
module ClassMethods
|
|
315
|
+
def validate_types!
|
|
316
|
+
@_validate_types = true
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def skip_type_validation!
|
|
320
|
+
@_validate_types = false
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def type_validation_enabled?
|
|
324
|
+
@_validate_types != false
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private
|
|
329
|
+
|
|
330
|
+
def validate_param(value, expected_type, param_name)
|
|
331
|
+
return true unless self.class.type_validation_enabled?
|
|
332
|
+
|
|
333
|
+
validator = RuntimeValidator.new
|
|
334
|
+
check_code = validator.generate_type_check("value", expected_type)
|
|
335
|
+
|
|
336
|
+
# Evaluate the check
|
|
337
|
+
unless eval(check_code)
|
|
338
|
+
raise RuntimeTypeError.new(
|
|
339
|
+
"#{param_name} must be #{expected_type}, got #{value.class}",
|
|
340
|
+
expected_type: expected_type,
|
|
341
|
+
actual_type: value.class.to_s,
|
|
342
|
+
value: value
|
|
343
|
+
)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
true
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def validate_return(value, expected_type)
|
|
350
|
+
return value unless self.class.type_validation_enabled?
|
|
351
|
+
|
|
352
|
+
validator = RuntimeValidator.new
|
|
353
|
+
check_code = validator.generate_type_check("value", expected_type)
|
|
354
|
+
|
|
355
|
+
unless eval(check_code)
|
|
356
|
+
raise RuntimeTypeError.new(
|
|
357
|
+
"Return value must be #{expected_type}, got #{value.class}",
|
|
358
|
+
expected_type: expected_type,
|
|
359
|
+
actual_type: value.class.to_s,
|
|
360
|
+
value: value
|
|
361
|
+
)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
value
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|