kumi 0.0.29 → 0.0.30

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -1
  3. data/CHANGELOG.md +12 -0
  4. data/README.md +5 -6
  5. data/docs/DEVELOPMENT.md +23 -1
  6. data/docs/GOLDEN_QUICK_START.md +141 -0
  7. data/docs/GOLDEN_TESTS.md +240 -0
  8. data/golden/array_element/expected/schema_ruby.rb +1 -1
  9. data/golden/array_index/expected/schema_ruby.rb +1 -1
  10. data/golden/array_operations/expected/schema_ruby.rb +1 -1
  11. data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
  12. data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
  13. data/golden/decimal_explicit/expected/schema_ruby.rb +1 -1
  14. data/golden/element_arrays/expected/schema_ruby.rb +1 -1
  15. data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
  16. data/golden/function_overload/expected/schema_ruby.rb +1 -1
  17. data/golden/game_of_life/expected/schema_ruby.rb +1 -1
  18. data/golden/hash_keys/expected/schema_ruby.rb +1 -1
  19. data/golden/hash_value/expected/schema_ruby.rb +1 -1
  20. data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
  21. data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
  22. data/golden/input_reference/expected/schema_ruby.rb +1 -1
  23. data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
  24. data/golden/let_inline/expected/schema_ruby.rb +1 -1
  25. data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
  26. data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
  27. data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
  28. data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
  29. data/golden/nested_hash/expected/schema_ruby.rb +1 -1
  30. data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
  31. data/golden/roll/expected/schema_ruby.rb +1 -1
  32. data/golden/shift/expected/schema_ruby.rb +1 -1
  33. data/golden/shift_2d/expected/schema_ruby.rb +1 -1
  34. data/golden/simple_math/expected/schema_ruby.rb +1 -1
  35. data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
  36. data/golden/tuples/expected/schema_ruby.rb +1 -1
  37. data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
  38. data/golden/us_tax_2024/expected/schema_ruby.rb +1 -1
  39. data/golden/with_constants/expected/schema_ruby.rb +1 -1
  40. data/lib/kumi/analyzer.rb +39 -79
  41. data/lib/kumi/core/analyzer/analysis_state.rb +2 -0
  42. data/lib/kumi/core/analyzer/constant_evaluator.rb +0 -2
  43. data/lib/kumi/core/analyzer/execution_phase.rb +24 -0
  44. data/lib/kumi/core/analyzer/execution_result.rb +38 -0
  45. data/lib/kumi/core/analyzer/pass_failure.rb +26 -0
  46. data/lib/kumi/core/analyzer/pass_manager.rb +136 -0
  47. data/lib/kumi/core/analyzer/passes/codegen/js/declaration_emitter.rb +1 -1
  48. data/lib/kumi/core/analyzer/passes/codegen/js_pass.rb +1 -1
  49. data/lib/kumi/core/analyzer/passes/codegen/ruby/declaration_emitter.rb +1 -1
  50. data/lib/kumi/core/analyzer/passes/codegen/ruby_pass.rb +1 -1
  51. data/lib/kumi/core/analyzer/passes/constant_folding_pass.rb +1 -1
  52. data/lib/kumi/core/analyzer/passes/formal_constraint_propagator.rb +13 -31
  53. data/lib/kumi/core/analyzer/passes/input_access_planner_pass.rb +1 -1
  54. data/lib/kumi/core/analyzer/passes/input_collector.rb +1 -1
  55. data/lib/kumi/core/analyzer/passes/input_form_schema_pass.rb +1 -1
  56. data/lib/kumi/core/analyzer/passes/lir/loop_invariant_code_motion_pass.rb +1 -1
  57. data/lib/kumi/core/analyzer/passes/lir/stencil_emitter.rb +0 -2
  58. data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +5 -1
  59. data/lib/kumi/core/analyzer/passes/output_schema_pass.rb +2 -1
  60. data/lib/kumi/core/analyzer/passes/snast_pass.rb +1 -1
  61. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -10
  62. data/lib/kumi/core/compiler/access_planner_v2.rb +1 -2
  63. data/lib/kumi/core/functions/overload_resolver.rb +15 -19
  64. data/lib/kumi/core/functions/type_categories.rb +3 -3
  65. data/lib/kumi/core/functions/type_error_reporter.rb +1 -3
  66. data/lib/kumi/core/input/type_matcher.rb +1 -1
  67. data/lib/kumi/core/types/normalizer.rb +8 -10
  68. data/lib/kumi/core/types/validator.rb +1 -1
  69. data/lib/kumi/core/types/value_objects.rb +5 -2
  70. data/lib/kumi/core/types.rb +2 -4
  71. data/lib/kumi/dev/codegen.rb +1 -1
  72. data/lib/kumi/dev/golden/generator.rb +14 -10
  73. data/lib/kumi/dev/golden/reporter.rb +5 -5
  74. data/lib/kumi/dev/golden/representation.rb +1 -3
  75. data/lib/kumi/dev/golden/result.rb +1 -1
  76. data/lib/kumi/dev/golden/runtime_test.rb +0 -2
  77. data/lib/kumi/dev/golden/suite.rb +20 -4
  78. data/lib/kumi/dev/golden/value_normalizer.rb +1 -3
  79. data/lib/kumi/doc_generator/formatters/json.rb +11 -11
  80. data/lib/kumi/doc_generator/formatters/markdown.rb +35 -37
  81. data/lib/kumi/doc_generator/loader.rb +8 -6
  82. data/lib/kumi/doc_generator/merger.rb +12 -12
  83. data/lib/kumi/frontends/text.rb +4 -6
  84. data/lib/kumi/registry_v2/loader.rb +32 -33
  85. data/lib/kumi/schema.rb +2 -2
  86. data/lib/kumi/version.rb +1 -1
  87. metadata +13 -14
  88. data/debug_ordering.rb +0 -52
  89. data/examples/deep_schema_compilation_and_evaluation_benchmark.rb +0 -106
  90. data/examples/federal_tax_calculator_2024.rb +0 -115
  91. data/examples/game_of_life.rb +0 -95
  92. data/examples/simple_rpg_game.rb +0 -1000
  93. data/examples/static_analysis_errors.rb +0 -178
  94. data/examples/wide_schema_compilation_and_evaluation_benchmark.rb +0 -80
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_fbbd9524e89735c5dbaf290442b7ae9b51c1aeedcbe116ad99c5af17605a99ee
2
+ module Kumi::Compiled::KUMI_a1445291f04923fe3d936c1514d8bf48488a469a9328b03080feb08b70093276
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_cb5c8bdee5947708dc24300b2c2b672056453e6f137563d7bf848a40bdc4e534
2
+ module Kumi::Compiled::KUMI_d17fb0388f7014e59c7e9a892d540e54a949f21eb86a9e8e8a396bc3488d6f35
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_21390de8a42e8b9e82e444c05c8030a3d5285d0077573acab1c303e27d5436fb
2
+ module Kumi::Compiled::KUMI_10a8c4eef85aad586921c958580baafbe11b5bd9ca21b75e678a47f7dd65daab
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_d729bd10302a56f8a23981643e21bb67803d5850bc61ac80e268c592fdfb37d7
2
+ module Kumi::Compiled::KUMI_b5eff95b28c57576e4613ae2a1fa9784e744318ef6565131ada24c407fec28e7
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_03a2e9a0500bceafc56945b155f90c33cc1a23713c1b16b22908a76f6cc0d8c3
2
+ module Kumi::Compiled::KUMI_d972b45a945bf808f0713ddc12e431b18405a54a9d90bfa262d81a86e42b46ca
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_f7ef5fcd350fe14b8092390d8fc3031fa401de46e5e5a7c5944436336aae60c5
2
+ module Kumi::Compiled::KUMI_2eed3f0a8d77c4064660318dbbae64a67ba86a5d882fba6f60715e64d9677ba2
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_b29542da718e63a254f2e4a844b7515afaafc01da8055fa5e9c661f1ba347beb
2
+ module Kumi::Compiled::KUMI_4d2cbd3183abfbb3252e36a42fbc36925bef07ceb2aaa302e586496362f48566
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_6b3029186d9b3c61fe69d9ceb9fc1d33177f973d83d0fb228ab8b2088fcb7cf2
2
+ module Kumi::Compiled::KUMI_f570bd1470fbdb4fe98673449f7d2106c6eeb0a11dbd0d656e2d19485860c206
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
@@ -1,5 +1,5 @@
1
1
  # Autogenerated by Kumi Codegen
2
- module Kumi::Compiled::KUMI_7e3d95df01d361f5adc0bf6b1990b0d5cccfba056716e6870c570d205196a924
2
+ module Kumi::Compiled::KUMI_e200083314e8307addf1cf2056b27031a7256e95b7cde0d0bc3865b4aef4ead2
3
3
  def self.from(input_data = nil)
4
4
  instance = Object.new
5
5
  instance.extend(self)
data/lib/kumi/analyzer.rb CHANGED
@@ -51,7 +51,7 @@ module Kumi
51
51
  def self.analyze!(schema, passes: DEFAULT_PASSES, registry: nil, **opts)
52
52
  errors = []
53
53
  schema_digest = schema.digest
54
- stop_after = Core::Analyzer::Checkpoint.stop_after
54
+ Core::Analyzer::Checkpoint.stop_after
55
55
 
56
56
  registry ||= Kumi::RegistryV2.load
57
57
  state = Core::Analyzer::AnalysisState.new(opts).with(:registry, registry).with(:schema_digest, schema_digest)
@@ -61,7 +61,7 @@ module Kumi
61
61
  state, stopped = run_analysis_passes(schema, HIR_TO_LIR_PASSES, state, errors)
62
62
  return create_analysis_result(state) if stopped
63
63
 
64
- state, stopped = run_analysis_passes(schema, RUBY_TARGET_PASSES, state, errors)
64
+ state, = run_analysis_passes(schema, RUBY_TARGET_PASSES, state, errors)
65
65
 
66
66
  handle_analysis_errors(errors) unless errors.empty?
67
67
  create_analysis_result(state)
@@ -71,91 +71,51 @@ module Kumi
71
71
  # Resume from a saved state if configured
72
72
  state = Core::Analyzer::Checkpoint.load_initial_state(state)
73
73
 
74
+ # Prepare options for PassManager
74
75
  debug_on = Core::Analyzer::Debug.enabled?
75
76
  resume_at = Core::Analyzer::Checkpoint.resume_at
76
77
  stop_after = Core::Analyzer::Checkpoint.stop_after
77
- skipping = !!resume_at
78
- stopped = false
79
-
80
- passes.each_with_index do |pass_class, idx|
81
- raise handle_analysis_errors(errors) if !errors.empty? && (ERROR_THRESHOLD_PASS == pass_class)
82
-
83
- pass_name = pass_class.name.split("::").last
84
78
 
85
- if skipping
86
- skipping = false if pass_name == resume_at
87
- next if skipping
88
- end
89
-
90
- Core::Analyzer::Checkpoint.entering(pass_name:, idx:, state:)
91
-
92
- before = state.to_h if debug_on
93
- Core::Analyzer::Debug.reset_log(pass: pass_name) if debug_on
94
-
95
- t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
96
- pass_instance = pass_class.new(schema, state)
97
- begin
98
- state = Dev::Profiler.phase("analyzer.pass", pass: pass_name) do
99
- pass_instance.run(errors)
100
- end
101
- rescue StandardError => e
102
- # TODO: - GREATLY improve this, need to capture the context of the error
103
- # and the pass that failed and line number if relevant
104
- message = "Error in Analysis Pass(#{pass_name}): #{e.message}"
105
- errors << Core::ErrorReporter.create_error(message, location: nil, type: :semantic, backtrace: e.backtrace)
106
-
107
- if debug_on
108
- logs = Core::Analyzer::Debug.drain_log
109
- elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2)
110
-
111
- Core::Analyzer::Debug.emit(
112
- pass: pass_name,
113
- diff: {},
114
- elapsed_ms: elapsed_ms,
115
- logs: logs + [{ level: :error, id: :exception, message: e.message, error_class: e.class.name }]
116
- )
117
- end
118
-
119
- raise
120
- end
121
- unless state.is_a? Kumi::Core::Analyzer::AnalysisState
122
- raise "Pass #{pass_name} returned a '#{state.class}', expected 'AnalysisState'"
123
- end
79
+ # Filter passes based on checkpoint resume point
80
+ filtered_passes = if resume_at
81
+ passes.each_with_index do |pass_class, idx|
82
+ pass_name = pass_class.name.split("::").last
83
+ if pass_name == resume_at
84
+ break passes[idx..]
85
+ end
86
+ end.flatten.compact
87
+ else
88
+ passes
89
+ end
90
+
91
+ # Check for error threshold pass
92
+ if !errors.empty? && filtered_passes.include?(ERROR_THRESHOLD_PASS)
93
+ raise handle_analysis_errors(errors)
94
+ end
124
95
 
125
- elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2)
126
-
127
- if debug_on
128
- after = state.to_h
129
-
130
- # Optional immutability guard
131
- if ENV["KUMI_DEBUG_REQUIRE_FROZEN"] == "1"
132
- (after || {}).each do |k, v|
133
- if v.nil? || v.is_a?(Numeric) || v.is_a?(Symbol) || v.is_a?(TrueClass) || v.is_a?(FalseClass) || (v.is_a?(String) && v.frozen?)
134
- next
135
- end
136
- raise "State[#{k}] not frozen: #{v.class}" unless v.frozen?
137
- end
138
- end
139
-
140
- diff = Core::Analyzer::Debug.diff_state(before, after)
141
- logs = Core::Analyzer::Debug.drain_log
142
-
143
- Core::Analyzer::Debug.emit(
144
- pass: pass_name,
145
- diff: diff,
146
- elapsed_ms: elapsed_ms,
147
- logs: logs
96
+ # Use PassManager for orchestration
97
+ manager = Core::Analyzer::PassManager.new(filtered_passes)
98
+ options = {
99
+ checkpoint_enabled: true,
100
+ debug_enabled: debug_on,
101
+ profiling_enabled: true,
102
+ stop_after: stop_after
103
+ }
104
+
105
+ result = manager.run(schema, state, errors, options)
106
+
107
+ # Convert PassFailure errors back to ErrorEntry for consistency
108
+ if result.failed?
109
+ result.errors.each do |pass_failure|
110
+ errors << Core::ErrorReporter.create_error(
111
+ pass_failure.message,
112
+ location: pass_failure.location,
113
+ type: :semantic
148
114
  )
149
115
  end
150
-
151
- Core::Analyzer::Checkpoint.leaving(pass_name:, idx:, state:)
152
-
153
- if stop_after && pass_name == stop_after
154
- stopped = true
155
- break
156
- end
157
116
  end
158
- [state, stopped]
117
+
118
+ [result.final_state, result.stopped || false]
159
119
  end
160
120
 
161
121
  def self.handle_analysis_errors(errors)
@@ -19,6 +19,8 @@ module Kumi
19
19
  @data.key?(key)
20
20
  end
21
21
 
22
+ alias has_key? key?
23
+
22
24
  # Get all keys (same as hash)
23
25
  def keys
24
26
  @data.keys
@@ -44,8 +44,6 @@ module Kumi
44
44
  when NAST::Tuple
45
45
  values = node.args.map { |arg| resolve_constant_value(arg) }
46
46
  values.all? ? values : nil
47
- else
48
- nil
49
47
  end
50
48
  end
51
49
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ class ExecutionPhase
7
+ attr_reader :pass_class, :index
8
+
9
+ def initialize(pass_class:, index:)
10
+ @pass_class = pass_class
11
+ @index = index
12
+ end
13
+
14
+ def pass_name
15
+ @pass_class.name.split("::").last
16
+ end
17
+
18
+ def to_s
19
+ "Phase #{index}: #{pass_name}"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ class ExecutionResult
7
+ attr_reader :final_state, :errors, :failed_at_phase, :stopped
8
+
9
+ def initialize(final_state:, errors: [], failed_at_phase: nil, stopped: false)
10
+ @final_state = final_state
11
+ @errors = errors
12
+ @failed_at_phase = failed_at_phase
13
+ @stopped = stopped
14
+ end
15
+
16
+ def self.success(final_state:, stopped: false)
17
+ new(final_state: final_state, errors: [], failed_at_phase: nil, stopped: stopped)
18
+ end
19
+
20
+ def self.failure(final_state:, errors:, failed_at_phase:)
21
+ new(final_state: final_state, errors: errors, failed_at_phase: failed_at_phase, stopped: false)
22
+ end
23
+
24
+ def succeeded?
25
+ errors.empty?
26
+ end
27
+
28
+ def failed?
29
+ !succeeded?
30
+ end
31
+
32
+ def error_count
33
+ errors.size
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ class PassFailure
7
+ attr_reader :message, :phase, :pass_name, :location
8
+
9
+ def initialize(message:, phase:, pass_name:, location:)
10
+ @message = message
11
+ @phase = phase
12
+ @pass_name = pass_name
13
+ @location = location
14
+ end
15
+
16
+ def to_s
17
+ if location
18
+ "#{pass_name} (phase #{phase}) at #{location}: #{message}"
19
+ else
20
+ "#{pass_name} (phase #{phase}): #{message}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kumi
4
+ module Core
5
+ module Analyzer
6
+ class PassManager
7
+ attr_reader :passes, :errors
8
+
9
+ def initialize(passes)
10
+ @passes = passes
11
+ @errors = []
12
+ end
13
+
14
+ def run(syntax_tree, initial_state = nil, errors = [], options = {})
15
+ state = initial_state || AnalysisState.new
16
+
17
+ passes.each_with_index do |pass_class, phase_index|
18
+ pass_name = pass_class.name.split("::").last
19
+
20
+ # Checkpoint support
21
+ Checkpoint.entering(pass_name:, idx: phase_index, state:) if options[:checkpoint_enabled]
22
+
23
+ # Debug support
24
+ debug_on = options[:debug_enabled]
25
+ before = state.to_h if debug_on
26
+ Debug.reset_log(pass: pass_name) if debug_on
27
+
28
+ # Performance profiling support
29
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) if options[:profiling_enabled]
30
+
31
+ begin
32
+ pass_instance = pass_class.new(syntax_tree, state)
33
+
34
+ if options[:profiling_enabled]
35
+ state = Dev::Profiler.phase("analyzer.pass", pass: pass_name) do
36
+ pass_instance.run(errors)
37
+ end
38
+ else
39
+ state = pass_instance.run(errors)
40
+ end
41
+ rescue StandardError => e
42
+ # Capture exception context
43
+ message = "Error in Analysis Pass(#{pass_name}): #{e.message}"
44
+ error_obj = ErrorReporter.create_error(message, location: nil, type: :semantic, backtrace: e.backtrace)
45
+ errors << error_obj
46
+
47
+ if debug_on
48
+ logs = Debug.drain_log
49
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2) if options[:profiling_enabled]
50
+ Debug.emit(
51
+ pass: pass_name,
52
+ diff: {},
53
+ elapsed_ms: elapsed_ms || 0,
54
+ logs: logs + [{ level: :error, id: :exception, message: e.message, error_class: e.class.name }]
55
+ )
56
+ end
57
+
58
+ # Return failure result instead of raising - let caller decide what to do
59
+ phase = ExecutionPhase.new(pass_class: pass_class, index: phase_index)
60
+ converted_error = PassFailure.new(
61
+ message: error_obj.message,
62
+ phase: phase_index,
63
+ pass_name: phase.pass_name,
64
+ location: error_obj.location
65
+ )
66
+ return ExecutionResult.failure(
67
+ final_state: state,
68
+ errors: [converted_error],
69
+ failed_at_phase: phase_index
70
+ )
71
+ end
72
+
73
+ # Type checking (PassManager enforces AnalysisState)
74
+ unless state.is_a?(AnalysisState)
75
+ raise "Pass #{pass_name} returned #{state.class}, expected AnalysisState"
76
+ end
77
+
78
+ # Debug logging with state diff
79
+ if debug_on
80
+ after = state.to_h
81
+
82
+ # Optional immutability guard
83
+ if ENV["KUMI_DEBUG_REQUIRE_FROZEN"] == "1"
84
+ (after || {}).each do |k, v|
85
+ if v.nil? || v.is_a?(Numeric) || v.is_a?(Symbol) || v.is_a?(TrueClass) || v.is_a?(FalseClass) || (v.is_a?(String) && v.frozen?)
86
+ next
87
+ end
88
+ raise "State[#{k}] not frozen: #{v.class}" unless v.frozen?
89
+ end
90
+ end
91
+
92
+ diff = Debug.diff_state(before, after)
93
+ logs = Debug.drain_log
94
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(2) if options[:profiling_enabled]
95
+
96
+ Debug.emit(
97
+ pass: pass_name,
98
+ diff: diff,
99
+ elapsed_ms: elapsed_ms || 0,
100
+ logs: logs
101
+ )
102
+ end
103
+
104
+ # Checkpoint support
105
+ Checkpoint.leaving(pass_name:, idx: phase_index, state:) if options[:checkpoint_enabled]
106
+
107
+ # Handle errors
108
+ if !errors.empty?
109
+ phase = ExecutionPhase.new(pass_class: pass_class, index: phase_index)
110
+ converted_errors = errors.map do |error|
111
+ PassFailure.new(
112
+ message: error.message,
113
+ phase: phase_index,
114
+ pass_name: phase.pass_name,
115
+ location: error.respond_to?(:location) ? error.location : nil
116
+ )
117
+ end
118
+ return ExecutionResult.failure(
119
+ final_state: state,
120
+ errors: converted_errors,
121
+ failed_at_phase: phase_index
122
+ )
123
+ end
124
+
125
+ # Check stop_after
126
+ if options[:stop_after] && pass_name == options[:stop_after]
127
+ return ExecutionResult.success(final_state: state, stopped: true)
128
+ end
129
+ end
130
+
131
+ ExecutionResult.success(final_state: state)
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -73,7 +73,7 @@ module Kumi
73
73
  write "let #{container} = [];"
74
74
  end
75
75
 
76
- def emit_loopend(ins, i)
76
+ def emit_loopend(_ins, i)
77
77
  start_index = find_loop_start_for_end(i)
78
78
  is_yield_container_loop = @stack.length < @yield_depth && loop_contain_yield?(start_index)
79
79
 
@@ -12,7 +12,7 @@ module Kumi
12
12
  schema_digest = get_state(:schema_digest)
13
13
  hints = get_state(:hints)
14
14
 
15
- decls = decls.reject { |n, v| hints[n][:inline] }
15
+ decls = decls.reject { |n, _v| hints[n][:inline] }
16
16
  emitter = Codegen::Js::Emitter.new(manifest["kernels"], manifest["bindings"])
17
17
  src = emitter.emit(decls, schema_digest: schema_digest)
18
18
 
@@ -85,7 +85,7 @@ module Kumi
85
85
  write "#{container} = []"
86
86
  end
87
87
 
88
- def emit_loopend(ins, i)
88
+ def emit_loopend(_ins, i)
89
89
  is_yield_container_loop = @stack.length < @yield_depth && loop_contain_yield?(find_loop_start_for_end(i))
90
90
 
91
91
  if is_yield_container_loop
@@ -18,7 +18,7 @@ module Kumi
18
18
  # The codegen pass no longer needs direct access to the registry
19
19
  emitter = Codegen::Ruby::Emitter.new(manifest["kernels"], manifest["bindings"])
20
20
 
21
- decls = decls.reject { |n, v| hints[n][:inline] }
21
+ decls = decls.reject { |n, _v| hints[n][:inline] }
22
22
  src = emitter.emit(decls, schema_digest:)
23
23
 
24
24
  files = { "codegen.rb" => src }
@@ -7,7 +7,7 @@ module Kumi
7
7
  class ConstantFoldingPass < PassBase
8
8
  NAST = Kumi::Core::NAST
9
9
 
10
- def run(errors)
10
+ def run(_errors)
11
11
  nast_module = get_state(:nast_module, required: true)
12
12
  order = get_state(:evaluation_order, required: true)
13
13
  @registry = get_state(:registry, required: true)
@@ -25,8 +25,6 @@ module Kumi
25
25
  propagate_equality_forward(constraint, operation_spec, operand_map)
26
26
  when :range
27
27
  propagate_range_forward(constraint, operation_spec, operand_map)
28
- else
29
- nil
30
28
  end
31
29
  end
32
30
 
@@ -37,8 +35,6 @@ module Kumi
37
35
  propagate_equality_reverse(constraint, operation_spec, operand_map)
38
36
  when :range
39
37
  propagate_range_reverse(constraint, operation_spec, operand_map)
40
- else
41
- nil
42
38
  end
43
39
  end
44
40
 
@@ -47,7 +43,7 @@ module Kumi
47
43
  # FORWARD PROPAGATION: Compute output value from input constraints
48
44
  def propagate_equality_forward(constraint, operation_spec, operand_map)
49
45
  result_var = operand_map[:result]
50
- input_var = constraint[:variable]
46
+ constraint[:variable]
51
47
  input_value = constraint[:value]
52
48
 
53
49
  case operation_spec.id
@@ -75,8 +71,6 @@ module Kumi
75
71
  output_value = input_value - other_operand
76
72
  { variable: result_var, op: :==, value: output_value }
77
73
 
78
- else
79
- nil
80
74
  end
81
75
  end
82
76
 
@@ -122,14 +116,12 @@ module Kumi
122
116
  output_max = input_max - other
123
117
  { variable: result_var, op: :range, min: output_min, max: output_max }
124
118
 
125
- else
126
- nil
127
119
  end
128
120
  end
129
121
 
130
122
  # REVERSE PROPAGATION: Derive input equality from output equality
131
123
  def propagate_equality_reverse(constraint, operation_spec, operand_map)
132
- result_var = constraint[:variable]
124
+ constraint[:variable]
133
125
  result_value = constraint[:value]
134
126
  left_var = operand_map[:left_operand]
135
127
  right_var = operand_map[:right_operand]
@@ -141,20 +133,18 @@ module Kumi
141
133
  { variable: left_var, op: :==, value: result_value - right_var }
142
134
  elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
143
135
  { variable: right_var, op: :==, value: result_value - left_var }
144
- else
145
- nil
146
136
  end
147
137
 
148
138
  when "core.mul"
149
139
  # result == V, result = x * C => x == V / C (if C != 0)
150
140
  if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
151
141
  return nil unless (result_value % right_var).zero?
142
+
152
143
  { variable: left_var, op: :==, value: result_value / right_var }
153
144
  elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
154
145
  return nil unless (result_value % left_var).zero?
146
+
155
147
  { variable: right_var, op: :==, value: result_value / left_var }
156
- else
157
- nil
158
148
  end
159
149
 
160
150
  when "core.sub"
@@ -163,12 +153,8 @@ module Kumi
163
153
  { variable: left_var, op: :==, value: result_value + right_var }
164
154
  elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
165
155
  { variable: right_var, op: :==, value: left_var - result_value }
166
- else
167
- nil
168
156
  end
169
157
 
170
- else
171
- nil
172
158
  end
173
159
  end
174
160
 
@@ -191,17 +177,15 @@ module Kumi
191
177
  when "core.mul"
192
178
  # result in [min, max], result = x * C => x in [min/C, max/C] (depends on sign)
193
179
  if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
194
- if right_var > 0
195
- return { variable: left_var, op: :range, min: result_min / right_var, max: result_max / right_var }
196
- else
197
- return { variable: left_var, op: :range, min: result_max / right_var, max: result_min / right_var }
198
- end
180
+ return { variable: left_var, op: :range, min: result_min / right_var, max: result_max / right_var } if right_var > 0
181
+
182
+ return { variable: left_var, op: :range, min: result_max / right_var, max: result_min / right_var }
183
+
199
184
  elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
200
- if left_var > 0
201
- return { variable: right_var, op: :range, min: result_min / left_var, max: result_max / left_var }
202
- else
203
- return { variable: right_var, op: :range, min: result_max / left_var, max: result_min / left_var }
204
- end
185
+ return { variable: right_var, op: :range, min: result_min / left_var, max: result_max / left_var } if left_var > 0
186
+
187
+ return { variable: right_var, op: :range, min: result_max / left_var, max: result_min / left_var }
188
+
205
189
  end
206
190
 
207
191
  when "core.sub"
@@ -216,7 +200,7 @@ module Kumi
216
200
  nil
217
201
  end
218
202
 
219
- def get_other_operand_value(constraint, operand_map, operation)
203
+ def get_other_operand_value(constraint, operand_map, _operation)
220
204
  input_var = constraint[:variable]
221
205
  left_var = operand_map[:left_operand] || operand_map.values[0]
222
206
  right_var = operand_map[:right_operand] || operand_map.values[1]
@@ -225,8 +209,6 @@ module Kumi
225
209
  right_var
226
210
  elsif input_var == right_var && left_var.is_a?(Numeric)
227
211
  left_var
228
- else
229
- nil
230
212
  end
231
213
  end
232
214
  end
@@ -5,7 +5,7 @@ module Kumi
5
5
  module Analyzer
6
6
  module Passes
7
7
  class InputAccessPlannerPass < PassBase
8
- def run(errors)
8
+ def run(_errors)
9
9
  input_metadata = get_state(:input_metadata)
10
10
 
11
11
  options = {
@@ -108,7 +108,7 @@ module Kumi
108
108
  when Kumi::Core::Types::ArrayType
109
109
  :array
110
110
  when Kumi::Core::Types::TupleType
111
- :array # Tuples behave like arrays for input access
111
+ :array # Tuples behave like arrays for input access
112
112
  when :array, Kumi::Core::Types::ScalarType
113
113
  # Check if it's a hash scalar or :hash symbol
114
114
  if t.is_a?(Kumi::Core::Types::ScalarType) && t.kind == :hash