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,441 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TRuby
|
|
4
|
+
# Represents a type constraint
|
|
5
|
+
class Constraint
|
|
6
|
+
attr_reader :type, :condition, :message
|
|
7
|
+
|
|
8
|
+
def initialize(type:, condition:, message: nil)
|
|
9
|
+
@type = type
|
|
10
|
+
@condition = condition
|
|
11
|
+
@message = message
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
"#{@type} where #{@condition}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Bounds constraint: T <: BaseType (T is subtype of BaseType)
|
|
20
|
+
class BoundsConstraint < Constraint
|
|
21
|
+
attr_reader :subtype, :supertype
|
|
22
|
+
|
|
23
|
+
def initialize(subtype:, supertype:)
|
|
24
|
+
@subtype = subtype
|
|
25
|
+
@supertype = supertype
|
|
26
|
+
super(type: :bounds, condition: "#{subtype} <: #{supertype}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def satisfied?(type_hierarchy)
|
|
30
|
+
type_hierarchy.subtype_of?(@subtype, @supertype)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Equality constraint: T == SpecificType
|
|
35
|
+
class EqualityConstraint < Constraint
|
|
36
|
+
attr_reader :left_type, :right_type
|
|
37
|
+
|
|
38
|
+
def initialize(left_type:, right_type:)
|
|
39
|
+
@left_type = left_type
|
|
40
|
+
@right_type = right_type
|
|
41
|
+
super(type: :equality, condition: "#{left_type} == #{right_type}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def satisfied?(value_type)
|
|
45
|
+
@left_type == value_type || @right_type == value_type
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Numeric range constraint: Integer where min..max
|
|
50
|
+
class NumericRangeConstraint < Constraint
|
|
51
|
+
attr_reader :base_type, :min, :max
|
|
52
|
+
|
|
53
|
+
def initialize(base_type:, min: nil, max: nil)
|
|
54
|
+
@base_type = base_type
|
|
55
|
+
@min = min
|
|
56
|
+
@max = max
|
|
57
|
+
range_str = build_range_string
|
|
58
|
+
super(type: :numeric_range, condition: range_str)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def satisfied?(value)
|
|
62
|
+
return false unless value.is_a?(Numeric)
|
|
63
|
+
return false if @min && value < @min
|
|
64
|
+
return false if @max && value > @max
|
|
65
|
+
true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def validation_code(var_name)
|
|
69
|
+
conditions = []
|
|
70
|
+
conditions << "#{var_name} >= #{@min}" if @min
|
|
71
|
+
conditions << "#{var_name} <= #{@max}" if @max
|
|
72
|
+
conditions.join(" && ")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def build_range_string
|
|
78
|
+
return "#{@min}..#{@max}" if @min && @max
|
|
79
|
+
return ">= #{@min}" if @min
|
|
80
|
+
return "<= #{@max}" if @max
|
|
81
|
+
""
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Pattern constraint for strings: String where /pattern/
|
|
86
|
+
class PatternConstraint < Constraint
|
|
87
|
+
attr_reader :base_type, :pattern
|
|
88
|
+
|
|
89
|
+
def initialize(base_type:, pattern:)
|
|
90
|
+
@base_type = base_type
|
|
91
|
+
@pattern = pattern.is_a?(Regexp) ? pattern : Regexp.new(pattern)
|
|
92
|
+
super(type: :pattern, condition: "=~ #{@pattern.inspect}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def satisfied?(value)
|
|
96
|
+
return false unless value.is_a?(String)
|
|
97
|
+
@pattern.match?(value)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def validation_code(var_name)
|
|
101
|
+
"#{@pattern.inspect}.match?(#{var_name})"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Predicate constraint: Type where predicate_method
|
|
106
|
+
class PredicateConstraint < Constraint
|
|
107
|
+
attr_reader :base_type, :predicate
|
|
108
|
+
|
|
109
|
+
def initialize(base_type:, predicate:)
|
|
110
|
+
@base_type = base_type
|
|
111
|
+
@predicate = predicate
|
|
112
|
+
super(type: :predicate, condition: predicate.to_s)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def satisfied?(value)
|
|
116
|
+
if @predicate.is_a?(Proc)
|
|
117
|
+
@predicate.call(value)
|
|
118
|
+
elsif @predicate.is_a?(Symbol)
|
|
119
|
+
value.respond_to?(@predicate) && value.send(@predicate)
|
|
120
|
+
else
|
|
121
|
+
false
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def validation_code(var_name)
|
|
126
|
+
if @predicate.is_a?(Symbol)
|
|
127
|
+
"#{var_name}.#{@predicate}"
|
|
128
|
+
else
|
|
129
|
+
"true" # Proc constraints require runtime evaluation
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Length constraint for strings/arrays: Type where length condition
|
|
135
|
+
class LengthConstraint < Constraint
|
|
136
|
+
attr_reader :base_type, :min_length, :max_length, :exact_length
|
|
137
|
+
|
|
138
|
+
def initialize(base_type:, min_length: nil, max_length: nil, exact_length: nil)
|
|
139
|
+
@base_type = base_type
|
|
140
|
+
@min_length = min_length
|
|
141
|
+
@max_length = max_length
|
|
142
|
+
@exact_length = exact_length
|
|
143
|
+
super(type: :length, condition: build_condition)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def satisfied?(value)
|
|
147
|
+
return false unless value.respond_to?(:length)
|
|
148
|
+
len = value.length
|
|
149
|
+
return len == @exact_length if @exact_length
|
|
150
|
+
return false if @min_length && len < @min_length
|
|
151
|
+
return false if @max_length && len > @max_length
|
|
152
|
+
true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def validation_code(var_name)
|
|
156
|
+
conditions = []
|
|
157
|
+
if @exact_length
|
|
158
|
+
conditions << "#{var_name}.length == #{@exact_length}"
|
|
159
|
+
else
|
|
160
|
+
conditions << "#{var_name}.length >= #{@min_length}" if @min_length
|
|
161
|
+
conditions << "#{var_name}.length <= #{@max_length}" if @max_length
|
|
162
|
+
end
|
|
163
|
+
conditions.join(" && ")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def build_condition
|
|
169
|
+
return "length == #{@exact_length}" if @exact_length
|
|
170
|
+
parts = []
|
|
171
|
+
parts << "length >= #{@min_length}" if @min_length
|
|
172
|
+
parts << "length <= #{@max_length}" if @max_length
|
|
173
|
+
parts.join(", ")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Main constraint checker class
|
|
178
|
+
class ConstraintChecker
|
|
179
|
+
attr_reader :constraints, :errors
|
|
180
|
+
|
|
181
|
+
def initialize
|
|
182
|
+
@constraints = {}
|
|
183
|
+
@errors = []
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Register a constrained type
|
|
187
|
+
def register(name, base_type:, constraints: [])
|
|
188
|
+
@constraints[name] = {
|
|
189
|
+
base_type: base_type,
|
|
190
|
+
constraints: constraints
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Parse constraint syntax from source
|
|
195
|
+
def parse_constraint(definition)
|
|
196
|
+
# Match: type Name <: BaseType where condition
|
|
197
|
+
if definition.match?(/^(\w+)\s*<:\s*(\w+)\s*where\s*(.+)$/)
|
|
198
|
+
match = definition.match(/^(\w+)\s*<:\s*(\w+)\s*where\s*(.+)$/)
|
|
199
|
+
name = match[1]
|
|
200
|
+
base_type = match[2]
|
|
201
|
+
condition = match[3].strip
|
|
202
|
+
|
|
203
|
+
constraints = parse_condition(base_type, condition)
|
|
204
|
+
return { name: name, base_type: base_type, constraints: constraints }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Match: type Name <: BaseType (bounds only)
|
|
208
|
+
if definition.match?(/^(\w+)\s*<:\s*(\w+)\s*$/)
|
|
209
|
+
match = definition.match(/^(\w+)\s*<:\s*(\w+)\s*$/)
|
|
210
|
+
name = match[1]
|
|
211
|
+
base_type = match[2]
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
name: name,
|
|
215
|
+
base_type: base_type,
|
|
216
|
+
constraints: [BoundsConstraint.new(subtype: name, supertype: base_type)]
|
|
217
|
+
}
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Match: type Name = BaseType where condition
|
|
221
|
+
if definition.match?(/^(\w+)\s*=\s*(\w+)\s*where\s*(.+)$/)
|
|
222
|
+
match = definition.match(/^(\w+)\s*=\s*(\w+)\s*where\s*(.+)$/)
|
|
223
|
+
name = match[1]
|
|
224
|
+
base_type = match[2]
|
|
225
|
+
condition = match[3].strip
|
|
226
|
+
|
|
227
|
+
constraints = parse_condition(base_type, condition)
|
|
228
|
+
return { name: name, base_type: base_type, constraints: constraints }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
nil
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Validate a value against a constrained type
|
|
235
|
+
def validate(type_name, value)
|
|
236
|
+
@errors = []
|
|
237
|
+
|
|
238
|
+
unless @constraints.key?(type_name)
|
|
239
|
+
@errors << "Unknown constrained type: #{type_name}"
|
|
240
|
+
return false
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
type_info = @constraints[type_name]
|
|
244
|
+
|
|
245
|
+
# Check base type
|
|
246
|
+
unless matches_base_type?(value, type_info[:base_type])
|
|
247
|
+
@errors << "Value #{value.inspect} does not match base type #{type_info[:base_type]}"
|
|
248
|
+
return false
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Check all constraints
|
|
252
|
+
type_info[:constraints].each do |constraint|
|
|
253
|
+
unless constraint.satisfied?(value)
|
|
254
|
+
@errors << "Constraint violation: #{constraint.condition}"
|
|
255
|
+
return false
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
true
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Generate validation code for runtime checking
|
|
263
|
+
def generate_validation_code(type_name, var_name)
|
|
264
|
+
return nil unless @constraints.key?(type_name)
|
|
265
|
+
|
|
266
|
+
type_info = @constraints[type_name]
|
|
267
|
+
conditions = []
|
|
268
|
+
|
|
269
|
+
# Base type check
|
|
270
|
+
case type_info[:base_type]
|
|
271
|
+
when "Integer"
|
|
272
|
+
conditions << "#{var_name}.is_a?(Integer)"
|
|
273
|
+
when "String"
|
|
274
|
+
conditions << "#{var_name}.is_a?(String)"
|
|
275
|
+
when "Float"
|
|
276
|
+
conditions << "#{var_name}.is_a?(Float)"
|
|
277
|
+
when "Numeric"
|
|
278
|
+
conditions << "#{var_name}.is_a?(Numeric)"
|
|
279
|
+
when "Array"
|
|
280
|
+
conditions << "#{var_name}.is_a?(Array)"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Constraint checks
|
|
284
|
+
type_info[:constraints].each do |constraint|
|
|
285
|
+
if constraint.respond_to?(:validation_code)
|
|
286
|
+
code = constraint.validation_code(var_name)
|
|
287
|
+
conditions << code unless code.empty?
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
conditions.join(" && ")
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
private
|
|
295
|
+
|
|
296
|
+
def parse_condition(base_type, condition)
|
|
297
|
+
constraints = []
|
|
298
|
+
|
|
299
|
+
# Numeric comparison: > N, < N, >= N, <= N
|
|
300
|
+
if condition.match?(/^([<>]=?)\s*(\d+(?:\.\d+)?)$/)
|
|
301
|
+
match = condition.match(/^([<>]=?)\s*(\d+(?:\.\d+)?)$/)
|
|
302
|
+
op = match[1]
|
|
303
|
+
value = match[2].include?(".") ? match[2].to_f : match[2].to_i
|
|
304
|
+
|
|
305
|
+
case op
|
|
306
|
+
when ">"
|
|
307
|
+
constraints << NumericRangeConstraint.new(base_type: base_type, min: value + 1)
|
|
308
|
+
when ">="
|
|
309
|
+
constraints << NumericRangeConstraint.new(base_type: base_type, min: value)
|
|
310
|
+
when "<"
|
|
311
|
+
constraints << NumericRangeConstraint.new(base_type: base_type, max: value - 1)
|
|
312
|
+
when "<="
|
|
313
|
+
constraints << NumericRangeConstraint.new(base_type: base_type, max: value)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Range: min..max
|
|
318
|
+
if condition.match?(/^(\d+)\.\.(\d+)$/)
|
|
319
|
+
match = condition.match(/^(\d+)\.\.(\d+)$/)
|
|
320
|
+
constraints << NumericRangeConstraint.new(
|
|
321
|
+
base_type: base_type,
|
|
322
|
+
min: match[1].to_i,
|
|
323
|
+
max: match[2].to_i
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Pattern: /regex/
|
|
328
|
+
if condition.match?(/^\/(.+)\/$/)
|
|
329
|
+
match = condition.match(/^\/(.+)\/$/)
|
|
330
|
+
constraints << PatternConstraint.new(base_type: base_type, pattern: match[1])
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Length constraint: length > N, length == N, etc.
|
|
334
|
+
if condition.match?(/^length\s*([<>=]+)\s*(\d+)$/)
|
|
335
|
+
match = condition.match(/^length\s*([<>=]+)\s*(\d+)$/)
|
|
336
|
+
op = match[1]
|
|
337
|
+
value = match[2].to_i
|
|
338
|
+
|
|
339
|
+
case op
|
|
340
|
+
when "=="
|
|
341
|
+
constraints << LengthConstraint.new(base_type: base_type, exact_length: value)
|
|
342
|
+
when ">="
|
|
343
|
+
constraints << LengthConstraint.new(base_type: base_type, min_length: value)
|
|
344
|
+
when "<="
|
|
345
|
+
constraints << LengthConstraint.new(base_type: base_type, max_length: value)
|
|
346
|
+
when ">"
|
|
347
|
+
constraints << LengthConstraint.new(base_type: base_type, min_length: value + 1)
|
|
348
|
+
when "<"
|
|
349
|
+
constraints << LengthConstraint.new(base_type: base_type, max_length: value - 1)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Predicate: positive?, negative?, empty?, etc.
|
|
354
|
+
if condition.match?(/^(\w+)\?$/)
|
|
355
|
+
match = condition.match(/^(\w+)\?$/)
|
|
356
|
+
constraints << PredicateConstraint.new(base_type: base_type, predicate: "#{match[1]}?".to_sym)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
constraints
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def matches_base_type?(value, type_name)
|
|
363
|
+
case type_name
|
|
364
|
+
when "Integer" then value.is_a?(Integer)
|
|
365
|
+
when "String" then value.is_a?(String)
|
|
366
|
+
when "Float" then value.is_a?(Float)
|
|
367
|
+
when "Numeric" then value.is_a?(Numeric)
|
|
368
|
+
when "Array" then value.is_a?(Array)
|
|
369
|
+
when "Hash" then value.is_a?(Hash)
|
|
370
|
+
when "Boolean" then value == true || value == false
|
|
371
|
+
when "Symbol" then value.is_a?(Symbol)
|
|
372
|
+
else true # Unknown types pass through
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Constrained type registry for managing type constraints
|
|
378
|
+
class ConstrainedTypeRegistry
|
|
379
|
+
attr_reader :types
|
|
380
|
+
|
|
381
|
+
def initialize
|
|
382
|
+
@types = {}
|
|
383
|
+
@checker = ConstraintChecker.new
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Register a constrained type from parsed definition
|
|
387
|
+
def register(name, base_type:, constraints: [])
|
|
388
|
+
@types[name] = {
|
|
389
|
+
base_type: base_type,
|
|
390
|
+
constraints: constraints,
|
|
391
|
+
defined_at: caller_locations(1, 1).first
|
|
392
|
+
}
|
|
393
|
+
@checker.register(name, base_type: base_type, constraints: constraints)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Parse and register from source line
|
|
397
|
+
def register_from_source(line)
|
|
398
|
+
result = @checker.parse_constraint(line)
|
|
399
|
+
return false unless result
|
|
400
|
+
|
|
401
|
+
register(result[:name], base_type: result[:base_type], constraints: result[:constraints])
|
|
402
|
+
true
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Check if a type is registered
|
|
406
|
+
def registered?(name)
|
|
407
|
+
@types.key?(name)
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# Get type info
|
|
411
|
+
def get(name)
|
|
412
|
+
@types[name]
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# Validate value against type
|
|
416
|
+
def validate(type_name, value)
|
|
417
|
+
@checker.validate(type_name, value)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Get validation errors
|
|
421
|
+
def errors
|
|
422
|
+
@checker.errors
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Generate validation code
|
|
426
|
+
def validation_code(type_name, var_name)
|
|
427
|
+
@checker.generate_validation_code(type_name, var_name)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# List all registered types
|
|
431
|
+
def list
|
|
432
|
+
@types.keys
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
# Clear all registrations
|
|
436
|
+
def clear
|
|
437
|
+
@types.clear
|
|
438
|
+
@checker = ConstraintChecker.new
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|