t-ruby 0.0.35 → 0.0.37
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/README.md +7 -6
- data/lib/t_ruby/benchmark.rb +16 -16
- data/lib/t_ruby/bundler_integration.rb +33 -35
- data/lib/t_ruby/cache.rb +46 -43
- data/lib/t_ruby/cli.rb +29 -5
- data/lib/t_ruby/compiler.rb +106 -52
- data/lib/t_ruby/config.rb +15 -18
- data/lib/t_ruby/constraint_checker.rb +14 -8
- data/lib/t_ruby/declaration_generator.rb +25 -8
- data/lib/t_ruby/doc_generator.rb +33 -33
- data/lib/t_ruby/docs_badge_generator.rb +8 -8
- data/lib/t_ruby/docs_example_verifier.rb +5 -6
- data/lib/t_ruby/error_handler.rb +21 -22
- data/lib/t_ruby/generic_type_parser.rb +3 -3
- data/lib/t_ruby/intersection_type_parser.rb +2 -2
- data/lib/t_ruby/ir.rb +21 -24
- data/lib/t_ruby/lsp_server.rb +128 -138
- data/lib/t_ruby/package_manager.rb +16 -18
- data/lib/t_ruby/parser.rb +7 -7
- data/lib/t_ruby/parser_combinator.rb +22 -22
- data/lib/t_ruby/rbs_generator.rb +2 -4
- data/lib/t_ruby/runtime_validator.rb +41 -37
- data/lib/t_ruby/smt_solver.rb +31 -29
- data/lib/t_ruby/type_alias_registry.rb +1 -0
- data/lib/t_ruby/type_checker.rb +64 -63
- data/lib/t_ruby/type_erasure.rb +2 -4
- data/lib/t_ruby/type_inferencer.rb +22 -26
- data/lib/t_ruby/union_type_parser.rb +3 -3
- data/lib/t_ruby/version.rb +1 -1
- data/lib/t_ruby/version_checker.rb +50 -0
- data/lib/t_ruby/watcher.rb +18 -14
- data/lib/t_ruby.rb +1 -0
- metadata +5 -3
data/lib/t_ruby/compiler.rb
CHANGED
|
@@ -38,29 +38,27 @@ module TRuby
|
|
|
38
38
|
# Transform source to Ruby code
|
|
39
39
|
output = @use_ir ? transform_with_ir(source, parser) : transform_legacy(source, parse_result)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
base_filename = File.basename(input_path, ".trb")
|
|
45
|
-
output_path = File.join(out_dir, base_filename + ".rb")
|
|
41
|
+
# Compute output path (respects preserve_structure setting)
|
|
42
|
+
output_path = compute_output_path(input_path, @config.ruby_dir, ".rb")
|
|
43
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
46
44
|
|
|
47
45
|
File.write(output_path, output)
|
|
48
46
|
|
|
49
47
|
# Generate .rbs file if enabled in config
|
|
50
48
|
if @config.compiler["generate_rbs"]
|
|
51
|
-
|
|
52
|
-
FileUtils.mkdir_p(
|
|
49
|
+
rbs_path = compute_output_path(input_path, @config.rbs_dir, ".rbs")
|
|
50
|
+
FileUtils.mkdir_p(File.dirname(rbs_path))
|
|
53
51
|
if @use_ir && parser.ir_program
|
|
54
|
-
|
|
52
|
+
generate_rbs_from_ir_to_path(rbs_path, parser.ir_program)
|
|
55
53
|
else
|
|
56
|
-
|
|
54
|
+
generate_rbs_file_to_path(rbs_path, parse_result)
|
|
57
55
|
end
|
|
58
56
|
end
|
|
59
57
|
|
|
60
58
|
# Generate .d.trb file if enabled in config (legacy support)
|
|
61
59
|
# TODO: Add compiler.generate_dtrb option in future
|
|
62
60
|
if @config.compiler.key?("generate_dtrb") && @config.compiler["generate_dtrb"]
|
|
63
|
-
generate_dtrb_file(input_path,
|
|
61
|
+
generate_dtrb_file(input_path, @config.ruby_dir)
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
output_path
|
|
@@ -98,19 +96,19 @@ module TRuby
|
|
|
98
96
|
{
|
|
99
97
|
ruby: ruby_output,
|
|
100
98
|
rbs: rbs_output,
|
|
101
|
-
errors: []
|
|
99
|
+
errors: [],
|
|
102
100
|
}
|
|
103
101
|
rescue ParseError => e
|
|
104
102
|
{
|
|
105
103
|
ruby: "",
|
|
106
104
|
rbs: "",
|
|
107
|
-
errors: [e.message]
|
|
105
|
+
errors: [e.message],
|
|
108
106
|
}
|
|
109
107
|
rescue StandardError => e
|
|
110
108
|
{
|
|
111
109
|
ruby: "",
|
|
112
110
|
rbs: "",
|
|
113
|
-
errors: ["Compilation error: #{e.message}"]
|
|
111
|
+
errors: ["Compilation error: #{e.message}"],
|
|
114
112
|
}
|
|
115
113
|
end
|
|
116
114
|
|
|
@@ -161,8 +159,70 @@ module TRuby
|
|
|
161
159
|
@optimizer&.stats
|
|
162
160
|
end
|
|
163
161
|
|
|
162
|
+
# Compute output path for a source file
|
|
163
|
+
# @param input_path [String] path to source file
|
|
164
|
+
# @param output_dir [String] base output directory
|
|
165
|
+
# @param new_extension [String] new file extension (e.g., ".rb", ".rbs")
|
|
166
|
+
# @return [String] computed output path (always preserves directory structure)
|
|
167
|
+
def compute_output_path(input_path, output_dir, new_extension)
|
|
168
|
+
relative = compute_relative_path(input_path)
|
|
169
|
+
base = relative.sub(/\.[^.]+$/, new_extension)
|
|
170
|
+
File.join(output_dir, base)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Compute relative path from source directory
|
|
174
|
+
# @param input_path [String] path to source file
|
|
175
|
+
# @return [String] relative path preserving directory structure
|
|
176
|
+
def compute_relative_path(input_path)
|
|
177
|
+
# Use realpath to resolve symlinks (e.g., /var vs /private/var on macOS)
|
|
178
|
+
absolute_input = resolve_path(input_path)
|
|
179
|
+
source_dirs = @config.source_include
|
|
180
|
+
|
|
181
|
+
# Check if file is inside any source_include directory
|
|
182
|
+
if source_dirs.size > 1
|
|
183
|
+
# Multiple source directories: include the source dir name in output
|
|
184
|
+
# src/models/user.trb → src/models/user.trb
|
|
185
|
+
source_dirs.each do |src_dir|
|
|
186
|
+
absolute_src = resolve_path(src_dir)
|
|
187
|
+
next unless absolute_input.start_with?("#{absolute_src}/")
|
|
188
|
+
|
|
189
|
+
# Return path relative to parent of source dir (includes src dir name)
|
|
190
|
+
parent_of_src = File.dirname(absolute_src)
|
|
191
|
+
return absolute_input.sub("#{parent_of_src}/", "")
|
|
192
|
+
end
|
|
193
|
+
else
|
|
194
|
+
# Single source directory: exclude the source dir name from output
|
|
195
|
+
# src/models/user.trb → models/user.trb
|
|
196
|
+
src_dir = source_dirs.first
|
|
197
|
+
if src_dir
|
|
198
|
+
absolute_src = resolve_path(src_dir)
|
|
199
|
+
if absolute_input.start_with?("#{absolute_src}/")
|
|
200
|
+
return absolute_input.sub("#{absolute_src}/", "")
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# File outside source directories: use path relative to current working directory
|
|
206
|
+
# external/foo.trb → external/foo.trb
|
|
207
|
+
cwd = resolve_path(".")
|
|
208
|
+
if absolute_input.start_with?("#{cwd}/")
|
|
209
|
+
return absolute_input.sub("#{cwd}/", "")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Absolute path from outside cwd: use basename only
|
|
213
|
+
File.basename(input_path)
|
|
214
|
+
end
|
|
215
|
+
|
|
164
216
|
private
|
|
165
217
|
|
|
218
|
+
# Resolve path to absolute path, following symlinks
|
|
219
|
+
# Falls back to expand_path if realpath fails (e.g., file doesn't exist yet)
|
|
220
|
+
def resolve_path(path)
|
|
221
|
+
File.realpath(path)
|
|
222
|
+
rescue Errno::ENOENT
|
|
223
|
+
File.expand_path(path)
|
|
224
|
+
end
|
|
225
|
+
|
|
166
226
|
def setup_declaration_paths
|
|
167
227
|
# Add default declaration paths
|
|
168
228
|
@declaration_loader.add_search_path(@config.out_dir)
|
|
@@ -197,30 +257,29 @@ module TRuby
|
|
|
197
257
|
end
|
|
198
258
|
end
|
|
199
259
|
|
|
200
|
-
# Generate RBS from IR
|
|
201
|
-
def
|
|
260
|
+
# Generate RBS from IR to a specific path
|
|
261
|
+
def generate_rbs_from_ir_to_path(rbs_path, ir_program)
|
|
202
262
|
generator = IR::RBSGenerator.new
|
|
203
263
|
rbs_content = generator.generate(ir_program)
|
|
204
|
-
|
|
205
|
-
rbs_path = File.join(out_dir, base_filename + ".rbs")
|
|
206
264
|
File.write(rbs_path, rbs_content) unless rbs_content.strip.empty?
|
|
207
265
|
end
|
|
208
266
|
|
|
209
|
-
# Legacy RBS generation
|
|
210
|
-
def
|
|
267
|
+
# Legacy RBS generation to a specific path
|
|
268
|
+
def generate_rbs_file_to_path(rbs_path, parse_result)
|
|
211
269
|
generator = RBSGenerator.new
|
|
212
270
|
rbs_content = generator.generate(
|
|
213
271
|
parse_result[:functions] || [],
|
|
214
272
|
parse_result[:type_aliases] || []
|
|
215
273
|
)
|
|
216
|
-
|
|
217
|
-
rbs_path = File.join(out_dir, base_filename + ".rbs")
|
|
218
274
|
File.write(rbs_path, rbs_content) unless rbs_content.empty?
|
|
219
275
|
end
|
|
220
276
|
|
|
221
277
|
def generate_dtrb_file(input_path, out_dir)
|
|
278
|
+
dtrb_path = compute_output_path(input_path, out_dir, DeclarationGenerator::DECLARATION_EXTENSION)
|
|
279
|
+
FileUtils.mkdir_p(File.dirname(dtrb_path))
|
|
280
|
+
|
|
222
281
|
generator = DeclarationGenerator.new
|
|
223
|
-
generator.
|
|
282
|
+
generator.generate_file_to_path(input_path, dtrb_path)
|
|
224
283
|
end
|
|
225
284
|
|
|
226
285
|
# Copy .rb file to output directory and generate .rbs signature
|
|
@@ -229,28 +288,25 @@ module TRuby
|
|
|
229
288
|
raise ArgumentError, "File not found: #{input_path}"
|
|
230
289
|
end
|
|
231
290
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
base_filename = File.basename(input_path, ".rb")
|
|
236
|
-
output_path = File.join(out_dir, base_filename + ".rb")
|
|
291
|
+
# Compute output path (respects preserve_structure setting)
|
|
292
|
+
output_path = compute_output_path(input_path, @config.ruby_dir, ".rb")
|
|
293
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
237
294
|
|
|
238
295
|
# Copy the .rb file to output directory
|
|
239
296
|
FileUtils.cp(input_path, output_path)
|
|
240
297
|
|
|
241
298
|
# Generate .rbs file if enabled in config
|
|
242
299
|
if @config.compiler["generate_rbs"]
|
|
243
|
-
|
|
244
|
-
FileUtils.mkdir_p(
|
|
245
|
-
|
|
300
|
+
rbs_path = compute_output_path(input_path, @config.rbs_dir, ".rbs")
|
|
301
|
+
FileUtils.mkdir_p(File.dirname(rbs_path))
|
|
302
|
+
generate_rbs_from_ruby_to_path(rbs_path, input_path)
|
|
246
303
|
end
|
|
247
304
|
|
|
248
305
|
output_path
|
|
249
306
|
end
|
|
250
307
|
|
|
251
|
-
# Generate RBS from Ruby file using rbs prototype
|
|
252
|
-
def
|
|
253
|
-
rbs_path = File.join(out_dir, base_filename + ".rbs")
|
|
308
|
+
# Generate RBS from Ruby file using rbs prototype to a specific path
|
|
309
|
+
def generate_rbs_from_ruby_to_path(rbs_path, input_path)
|
|
254
310
|
result = `rbs prototype rb #{input_path} 2>/dev/null`
|
|
255
311
|
File.write(rbs_path, result) unless result.strip.empty?
|
|
256
312
|
end
|
|
@@ -273,20 +329,20 @@ module TRuby
|
|
|
273
329
|
result = source.dup
|
|
274
330
|
|
|
275
331
|
# Collect type alias names to remove
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
332
|
+
program.declarations
|
|
333
|
+
.select { |d| d.is_a?(IR::TypeAlias) }
|
|
334
|
+
.map(&:name)
|
|
279
335
|
|
|
280
336
|
# Collect interface names to remove
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
337
|
+
program.declarations
|
|
338
|
+
.select { |d| d.is_a?(IR::Interface) }
|
|
339
|
+
.map(&:name)
|
|
284
340
|
|
|
285
341
|
# Remove type alias definitions
|
|
286
|
-
result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/,
|
|
342
|
+
result = result.gsub(/^\s*type\s+\w+\s*=\s*.+?$\n?/, "")
|
|
287
343
|
|
|
288
344
|
# Remove interface definitions (multi-line)
|
|
289
|
-
result = result.gsub(/^\s*interface\s+\w+.*?^\s*end\s*$/m,
|
|
345
|
+
result = result.gsub(/^\s*interface\s+\w+.*?^\s*end\s*$/m, "")
|
|
290
346
|
|
|
291
347
|
# Remove parameter type annotations using IR info
|
|
292
348
|
# Enhanced: Handle complex types (generics, unions, etc.)
|
|
@@ -296,9 +352,7 @@ module TRuby
|
|
|
296
352
|
result = erase_return_types(result)
|
|
297
353
|
|
|
298
354
|
# Clean up extra blank lines
|
|
299
|
-
result
|
|
300
|
-
|
|
301
|
-
result
|
|
355
|
+
result.gsub(/\n{3,}/, "\n\n")
|
|
302
356
|
end
|
|
303
357
|
|
|
304
358
|
private
|
|
@@ -308,11 +362,11 @@ module TRuby
|
|
|
308
362
|
result = source.dup
|
|
309
363
|
|
|
310
364
|
# Match function definitions and remove type annotations from parameters
|
|
311
|
-
result.gsub!(/^(\s*def\s+\w+\s*\()([^)]+)(\)\s*)(?::\s*[^\n]+)?(\s*$)/) do |
|
|
312
|
-
indent =
|
|
313
|
-
params =
|
|
314
|
-
close_paren =
|
|
315
|
-
ending =
|
|
365
|
+
result.gsub!(/^(\s*def\s+\w+\s*\()([^)]+)(\)\s*)(?::\s*[^\n]+)?(\s*$)/) do |_match|
|
|
366
|
+
indent = ::Regexp.last_match(1)
|
|
367
|
+
params = ::Regexp.last_match(2)
|
|
368
|
+
close_paren = ::Regexp.last_match(3)
|
|
369
|
+
ending = ::Regexp.last_match(4)
|
|
316
370
|
|
|
317
371
|
# Remove type annotations from each parameter
|
|
318
372
|
cleaned_params = remove_param_types(params)
|
|
@@ -340,7 +394,7 @@ module TRuby
|
|
|
340
394
|
depth -= 1
|
|
341
395
|
current += char
|
|
342
396
|
when ","
|
|
343
|
-
if depth
|
|
397
|
+
if depth.zero?
|
|
344
398
|
params << clean_param(current.strip)
|
|
345
399
|
current = ""
|
|
346
400
|
else
|
|
@@ -358,7 +412,7 @@ module TRuby
|
|
|
358
412
|
# Clean a single parameter (remove type annotation)
|
|
359
413
|
def clean_param(param)
|
|
360
414
|
# Match: name: Type or name
|
|
361
|
-
if match = param.match(/^(\w+)\s*:/)
|
|
415
|
+
if (match = param.match(/^(\w+)\s*:/))
|
|
362
416
|
match[1]
|
|
363
417
|
else
|
|
364
418
|
param
|
|
@@ -370,7 +424,7 @@ module TRuby
|
|
|
370
424
|
result = source.dup
|
|
371
425
|
|
|
372
426
|
# Remove return type: ): Type or ): Type<Foo> etc.
|
|
373
|
-
result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |
|
|
427
|
+
result.gsub!(/\)\s*:\s*[^\n]+?(?=\s*$)/m) do |_match|
|
|
374
428
|
")"
|
|
375
429
|
end
|
|
376
430
|
|
data/lib/t_ruby/config.rb
CHANGED
|
@@ -14,13 +14,12 @@ module TRuby
|
|
|
14
14
|
"source" => {
|
|
15
15
|
"include" => ["src"],
|
|
16
16
|
"exclude" => [],
|
|
17
|
-
"extensions" => [".trb"]
|
|
17
|
+
"extensions" => [".trb"],
|
|
18
18
|
},
|
|
19
19
|
"output" => {
|
|
20
20
|
"ruby_dir" => "build",
|
|
21
21
|
"rbs_dir" => nil,
|
|
22
|
-
"
|
|
23
|
-
"clean_before_build" => false
|
|
22
|
+
"clean_before_build" => false,
|
|
24
23
|
},
|
|
25
24
|
"compiler" => {
|
|
26
25
|
"strictness" => "standard",
|
|
@@ -30,15 +29,15 @@ module TRuby
|
|
|
30
29
|
"checks" => {
|
|
31
30
|
"no_implicit_any" => false,
|
|
32
31
|
"no_unused_vars" => false,
|
|
33
|
-
"strict_nil" => false
|
|
34
|
-
}
|
|
32
|
+
"strict_nil" => false,
|
|
33
|
+
},
|
|
35
34
|
},
|
|
36
35
|
"watch" => {
|
|
37
36
|
"paths" => [],
|
|
38
37
|
"debounce" => 100,
|
|
39
38
|
"clear_screen" => false,
|
|
40
|
-
"on_success" => nil
|
|
41
|
-
}
|
|
39
|
+
"on_success" => nil,
|
|
40
|
+
},
|
|
42
41
|
}.freeze
|
|
43
42
|
|
|
44
43
|
# Legacy keys for migration detection
|
|
@@ -72,12 +71,6 @@ module TRuby
|
|
|
72
71
|
@output["rbs_dir"] || ruby_dir
|
|
73
72
|
end
|
|
74
73
|
|
|
75
|
-
# Check if source directory structure should be preserved in output
|
|
76
|
-
# @return [Boolean] true if structure should be preserved
|
|
77
|
-
def preserve_structure?
|
|
78
|
-
@output["preserve_structure"] != false
|
|
79
|
-
end
|
|
80
|
-
|
|
81
74
|
# Check if output directory should be cleaned before build
|
|
82
75
|
# @return [Boolean] true if should clean before build
|
|
83
76
|
def clean_before_build?
|
|
@@ -249,7 +242,7 @@ module TRuby
|
|
|
249
242
|
value = strictness
|
|
250
243
|
return if VALID_STRICTNESS.include?(value)
|
|
251
244
|
|
|
252
|
-
raise ConfigError, "Invalid compiler.strictness: '#{value}'. Must be one of: #{VALID_STRICTNESS.join(
|
|
245
|
+
raise ConfigError, "Invalid compiler.strictness: '#{value}'. Must be one of: #{VALID_STRICTNESS.join(", ")}"
|
|
253
246
|
end
|
|
254
247
|
|
|
255
248
|
def load_raw_config(config_path)
|
|
@@ -308,8 +301,8 @@ module TRuby
|
|
|
308
301
|
result = deep_dup(DEFAULT_CONFIG)
|
|
309
302
|
|
|
310
303
|
# Migrate emit -> compiler.generate_rbs
|
|
311
|
-
if raw_config["emit"]
|
|
312
|
-
result["compiler"]["generate_rbs"] = raw_config["emit"]["rbs"]
|
|
304
|
+
if raw_config["emit"]&.key?("rbs")
|
|
305
|
+
result["compiler"]["generate_rbs"] = raw_config["emit"]["rbs"]
|
|
313
306
|
end
|
|
314
307
|
|
|
315
308
|
# Migrate paths -> source.include and output.ruby_dir
|
|
@@ -344,8 +337,12 @@ module TRuby
|
|
|
344
337
|
end
|
|
345
338
|
|
|
346
339
|
def deep_dup(hash)
|
|
347
|
-
hash.
|
|
348
|
-
|
|
340
|
+
hash.transform_values do |value|
|
|
341
|
+
if value.is_a?(Hash)
|
|
342
|
+
deep_dup(value)
|
|
343
|
+
else
|
|
344
|
+
(value.is_a?(Array) ? value.dup : value)
|
|
345
|
+
end
|
|
349
346
|
end
|
|
350
347
|
end
|
|
351
348
|
|
|
@@ -42,7 +42,7 @@ module TRuby
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def satisfied?(value_type)
|
|
45
|
-
@left_type
|
|
45
|
+
[@left_type, @right_type].include?(value_type)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -62,6 +62,7 @@ module TRuby
|
|
|
62
62
|
return false unless value.is_a?(Numeric)
|
|
63
63
|
return false if @min && value < @min
|
|
64
64
|
return false if @max && value > @max
|
|
65
|
+
|
|
65
66
|
true
|
|
66
67
|
end
|
|
67
68
|
|
|
@@ -78,6 +79,7 @@ module TRuby
|
|
|
78
79
|
return "#{@min}..#{@max}" if @min && @max
|
|
79
80
|
return ">= #{@min}" if @min
|
|
80
81
|
return "<= #{@max}" if @max
|
|
82
|
+
|
|
81
83
|
""
|
|
82
84
|
end
|
|
83
85
|
end
|
|
@@ -94,6 +96,7 @@ module TRuby
|
|
|
94
96
|
|
|
95
97
|
def satisfied?(value)
|
|
96
98
|
return false unless value.is_a?(String)
|
|
99
|
+
|
|
97
100
|
@pattern.match?(value)
|
|
98
101
|
end
|
|
99
102
|
|
|
@@ -145,10 +148,12 @@ module TRuby
|
|
|
145
148
|
|
|
146
149
|
def satisfied?(value)
|
|
147
150
|
return false unless value.respond_to?(:length)
|
|
151
|
+
|
|
148
152
|
len = value.length
|
|
149
153
|
return len == @exact_length if @exact_length
|
|
150
154
|
return false if @min_length && len < @min_length
|
|
151
155
|
return false if @max_length && len > @max_length
|
|
156
|
+
|
|
152
157
|
true
|
|
153
158
|
end
|
|
154
159
|
|
|
@@ -167,6 +172,7 @@ module TRuby
|
|
|
167
172
|
|
|
168
173
|
def build_condition
|
|
169
174
|
return "length == #{@exact_length}" if @exact_length
|
|
175
|
+
|
|
170
176
|
parts = []
|
|
171
177
|
parts << "length >= #{@min_length}" if @min_length
|
|
172
178
|
parts << "length <= #{@max_length}" if @max_length
|
|
@@ -187,7 +193,7 @@ module TRuby
|
|
|
187
193
|
def register(name, base_type:, constraints: [])
|
|
188
194
|
@constraints[name] = {
|
|
189
195
|
base_type: base_type,
|
|
190
|
-
constraints: constraints
|
|
196
|
+
constraints: constraints,
|
|
191
197
|
}
|
|
192
198
|
end
|
|
193
199
|
|
|
@@ -213,7 +219,7 @@ module TRuby
|
|
|
213
219
|
return {
|
|
214
220
|
name: name,
|
|
215
221
|
base_type: base_type,
|
|
216
|
-
constraints: [BoundsConstraint.new(subtype: name, supertype: base_type)]
|
|
222
|
+
constraints: [BoundsConstraint.new(subtype: name, supertype: base_type)],
|
|
217
223
|
}
|
|
218
224
|
end
|
|
219
225
|
|
|
@@ -325,8 +331,8 @@ module TRuby
|
|
|
325
331
|
end
|
|
326
332
|
|
|
327
333
|
# Pattern: /regex/
|
|
328
|
-
if condition.match?(
|
|
329
|
-
match = condition.match(
|
|
334
|
+
if condition.match?(%r{^/(.+)/$})
|
|
335
|
+
match = condition.match(%r{^/(.+)/$})
|
|
330
336
|
constraints << PatternConstraint.new(base_type: base_type, pattern: match[1])
|
|
331
337
|
end
|
|
332
338
|
|
|
@@ -353,7 +359,7 @@ module TRuby
|
|
|
353
359
|
# Predicate: positive?, negative?, empty?, etc.
|
|
354
360
|
if condition.match?(/^(\w+)\?$/)
|
|
355
361
|
match = condition.match(/^(\w+)\?$/)
|
|
356
|
-
constraints << PredicateConstraint.new(base_type: base_type, predicate: "#{match[1]}?"
|
|
362
|
+
constraints << PredicateConstraint.new(base_type: base_type, predicate: :"#{match[1]}?")
|
|
357
363
|
end
|
|
358
364
|
|
|
359
365
|
constraints
|
|
@@ -367,7 +373,7 @@ module TRuby
|
|
|
367
373
|
when "Numeric" then value.is_a?(Numeric)
|
|
368
374
|
when "Array" then value.is_a?(Array)
|
|
369
375
|
when "Hash" then value.is_a?(Hash)
|
|
370
|
-
when "Boolean" then
|
|
376
|
+
when "Boolean" then [true, false].include?(value)
|
|
371
377
|
when "Symbol" then value.is_a?(Symbol)
|
|
372
378
|
else true # Unknown types pass through
|
|
373
379
|
end
|
|
@@ -388,7 +394,7 @@ module TRuby
|
|
|
388
394
|
@types[name] = {
|
|
389
395
|
base_type: base_type,
|
|
390
396
|
constraints: constraints,
|
|
391
|
-
defined_at: caller_locations(1, 1).first
|
|
397
|
+
defined_at: caller_locations(1, 1).first,
|
|
392
398
|
}
|
|
393
399
|
@checker.register(name, base_type: base_type, constraints: constraints)
|
|
394
400
|
end
|
|
@@ -64,6 +64,23 @@ module TRuby
|
|
|
64
64
|
output_path
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
+
# Generate declaration file to a specific output path
|
|
68
|
+
def generate_file_to_path(input_path, output_path)
|
|
69
|
+
unless File.exist?(input_path)
|
|
70
|
+
raise ArgumentError, "File not found: #{input_path}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless input_path.end_with?(".trb")
|
|
74
|
+
raise ArgumentError, "Expected .trb file, got: #{input_path}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
source = File.read(input_path)
|
|
78
|
+
content = generate(source)
|
|
79
|
+
|
|
80
|
+
File.write(output_path, content)
|
|
81
|
+
output_path
|
|
82
|
+
end
|
|
83
|
+
|
|
67
84
|
private
|
|
68
85
|
|
|
69
86
|
def generate_type_alias(type_alias)
|
|
@@ -195,7 +212,7 @@ module TRuby
|
|
|
195
212
|
{
|
|
196
213
|
type_aliases: @type_aliases,
|
|
197
214
|
interfaces: @interfaces,
|
|
198
|
-
functions: @functions
|
|
215
|
+
functions: @functions,
|
|
199
216
|
}
|
|
200
217
|
end
|
|
201
218
|
|
|
@@ -230,13 +247,13 @@ module TRuby
|
|
|
230
247
|
|
|
231
248
|
@search_paths.each do |path|
|
|
232
249
|
full_path = File.join(path, file_name)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
next unless File.exist?(full_path) && !@loaded_files.include?(full_path)
|
|
251
|
+
|
|
252
|
+
parser = DeclarationParser.new
|
|
253
|
+
parser.parse_file(full_path)
|
|
254
|
+
@loaded_declarations.merge(parser)
|
|
255
|
+
@loaded_files.add(full_path)
|
|
256
|
+
return true
|
|
240
257
|
end
|
|
241
258
|
|
|
242
259
|
false
|