kumi 0.0.16 → 0.0.18

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/golden/cascade_logic/schema.kumi +3 -1
  4. data/lib/kumi/analyzer.rb +8 -11
  5. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +0 -81
  6. data/lib/kumi/core/analyzer/passes/lower_to_ir_pass.rb +0 -36
  7. data/lib/kumi/core/analyzer/passes/toposorter.rb +1 -36
  8. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +8 -191
  9. data/lib/kumi/core/compiler/access_builder.rb +5 -8
  10. data/lib/kumi/version.rb +1 -1
  11. metadata +2 -25
  12. data/BACKLOG.md +0 -34
  13. data/config/functions.yaml +0 -352
  14. data/docs/functions/analyzer_integration.md +0 -199
  15. data/docs/functions/signatures.md +0 -171
  16. data/examples/hash_objects_demo.rb +0 -138
  17. data/lib/kumi/core/analyzer/passes/function_signature_pass.rb +0 -199
  18. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +0 -48
  19. data/lib/kumi/core/functions/dimension.rb +0 -98
  20. data/lib/kumi/core/functions/dtypes.rb +0 -20
  21. data/lib/kumi/core/functions/errors.rb +0 -11
  22. data/lib/kumi/core/functions/kernel_adapter.rb +0 -45
  23. data/lib/kumi/core/functions/loader.rb +0 -119
  24. data/lib/kumi/core/functions/registry_v2.rb +0 -68
  25. data/lib/kumi/core/functions/shape.rb +0 -70
  26. data/lib/kumi/core/functions/signature.rb +0 -122
  27. data/lib/kumi/core/functions/signature_parser.rb +0 -86
  28. data/lib/kumi/core/functions/signature_resolver.rb +0 -272
  29. data/lib/kumi/kernels/ruby/aggregate_core.rb +0 -105
  30. data/lib/kumi/kernels/ruby/datetime_scalar.rb +0 -21
  31. data/lib/kumi/kernels/ruby/mask_scalar.rb +0 -15
  32. data/lib/kumi/kernels/ruby/scalar_core.rb +0 -63
  33. data/lib/kumi/kernels/ruby/string_scalar.rb +0 -19
  34. data/lib/kumi/kernels/ruby/vector_struct.rb +0 -39
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f51be8774c472629e599ce3edd4ab02551edfd52fea8676e2ee93eed5198800
4
- data.tar.gz: 26532cec84e5c59553031dc86d82abff310d2a79c857776e1d793af2b35e00ea
3
+ metadata.gz: f48b3c1d3dc46f7725b1f138fd4eafd2f1c197ed9bdcc0465a26206ed0b3ec56
4
+ data.tar.gz: 758a5ed295dda7ab9ca97f8caa888b502c0485abfb80ad13ccf6e0a0c03a716e
5
5
  SHA512:
6
- metadata.gz: 3ba28da3acbf5cd430902c29e34e5786562e7121f9c3851a2e9db3a04a13fc8b1a5e9729649f3daadb523ea55f2c97a1e4560139b9809aa636eff21e9716abbf
7
- data.tar.gz: 6683358d25786a5e15cba975e785b3178ec0ea13f40a3eeb940e72f25886f5004c499f3e80ddded31fdd23f3981cbe5244db1ab0aec1405b3c52acb68c4b1e60
6
+ metadata.gz: ebdd2e94fe3e3146018fbb4487601b8cbb5c10b1574c3fe7858cc230d8b69f1884d4bab7e8f9f352523754e4a6f1ed39d512659812569248f41584a4c692b341
7
+ data.tar.gz: 3d588dac5c01a4c6773d43383b3f0ed54ea21c43bf6d8aeefbc933518b74d00cd2b5b3b02cac5ce86cf633d930c4733c8a55d851e770063325b4a94d56f64d15
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.0.18] – 2025-09-03
4
+ - Fixed bug missing updated Gemfile.lock
5
+
6
+ ## [0.0.17] – 2025-09-03
7
+
8
+ ### Removed
9
+ - Reverted experimental function registry v2 implementation
10
+ - Cleaned up unused analyzer passes and simplified unsat detector logic
11
+
3
12
  ## [0.0.16] – 2025-08-22
4
13
 
5
14
  ### Performance
@@ -6,9 +6,11 @@ schema do
6
6
 
7
7
  trait :x_positive, input.x > 0
8
8
  trait :y_positive, input.y > 0
9
+
10
+ trait :both_positive, y_positive & x_positive
9
11
 
10
12
  value :status do
11
- on x_positive, y_positive, "both positive"
13
+ on both_positive, "both positive"
12
14
  on x_positive, "x positive"
13
15
  on y_positive, "y positive"
14
16
  base "neither positive"
data/lib/kumi/analyzer.rb CHANGED
@@ -15,17 +15,14 @@ module Kumi
15
15
  Core::Analyzer::Passes::Toposorter, # 8. Creates the final evaluation order, allowing safe cycles.
16
16
  Core::Analyzer::Passes::BroadcastDetector, # 9. Detects which operations should be broadcast over arrays.
17
17
  Core::Analyzer::Passes::TypeInferencerPass, # 10. Infers types for all declarations (uses vectorization metadata).
18
- Core::Analyzer::Passes::TypeConsistencyChecker, # 11. Validates declared vs inferred type consistency.
19
- Core::Analyzer::Passes::FunctionSignaturePass, # 12. Resolves NEP-20 signatures for function calls.
20
- Core::Analyzer::Passes::TypeChecker, # 13. Validates types using inferred information.
21
- Core::Analyzer::Passes::InputAccessPlannerPass, # 14. Plans access strategies for input fields.
22
- Core::Analyzer::Passes::ScopeResolutionPass, # 15. Plans execution scope and lifting needs for declarations.
23
- Core::Analyzer::Passes::JoinReducePlanningPass, # 16. Plans join/reduce operations (Generates IR Structs)
24
- Core::Analyzer::Passes::LowerToIRPass, # 17. Lowers the schema to IR (Generates IR Structs)
25
- Core::Analyzer::Passes::LoadInputCSE, # 18. Eliminates redundant load_input operations
26
- Core::Analyzer::Passes::IRDependencyPass, # 19. Extracts IR-level dependencies for VM execution optimization
27
- Core::Analyzer::Passes::IRExecutionSchedulePass # 20. Builds a precomputed execution schedule.
28
-
18
+ Core::Analyzer::Passes::TypeChecker, # 11. Validates types using inferred information.
19
+ Core::Analyzer::Passes::InputAccessPlannerPass, # 12. Plans access strategies for input fields.
20
+ Core::Analyzer::Passes::ScopeResolutionPass, # 13. Plans execution scope and lifting needs for declarations.
21
+ Core::Analyzer::Passes::JoinReducePlanningPass, # 14. Plans join/reduce operations (Generates IR Structs)
22
+ Core::Analyzer::Passes::LowerToIRPass, # 15. Lowers the schema to IR (Generates IR Structs)
23
+ Core::Analyzer::Passes::LoadInputCSE, # 16. Eliminates redundant load_input operations
24
+ Core::Analyzer::Passes::IRDependencyPass, # 17. Extracts IR-level dependencies for VM execution optimization
25
+ Core::Analyzer::Passes::IRExecutionSchedulePass # 18. Builds a precomputed execution schedule.
29
26
  ].freeze
30
27
 
31
28
  def self.analyze!(schema, passes: DEFAULT_PASSES, **opts)
@@ -656,87 +656,6 @@ module Kumi
656
656
  end
657
657
  end
658
658
 
659
- def extract_dimensional_info_with_context(info, _array_fields, _nested_paths, vectorized_values)
660
- case info[:source]
661
- when :array_field_access, :nested_array_access
662
- # Direct array field access - use the path
663
- source = info[:path]&.first
664
- dimension = info[:path]
665
- [source, dimension]
666
- when :vectorized_declaration
667
- # Reference to another vectorized declaration - look it up
668
- if info[:name] && vectorized_values[info[:name]]
669
- vectorized_info = vectorized_values[info[:name]]
670
- if vectorized_info[:array_source]
671
- # This declaration references an array field, use that source
672
- [vectorized_info[:array_source], [vectorized_info[:array_source]]]
673
- else
674
- # This is a derived vectorized value, try to trace its source
675
- [:vectorized_reference, [:vectorized_reference]]
676
- end
677
- else
678
- [:unknown_declaration, [:unknown_declaration]]
679
- end
680
- else
681
- # Operations and other cases - try to extract from operation args
682
- if info[:operation] && info[:vectorized_args]
683
- # This is an operation result - trace the vectorized arguments
684
- # For now, assume operations inherit the dimension of their first vectorized arg
685
- [:operation_result, [:operation_result]]
686
- else
687
- [:unknown, [:unknown]]
688
- end
689
- end
690
- end
691
-
692
- def extract_dimensional_source(info, _array_fields)
693
- case info[:source]
694
- when :array_field_access
695
- info[:path]&.first
696
- when :nested_array_access
697
- info[:path]&.first
698
- when :vectorized_declaration, :vectorized_value
699
- # Try to extract from the vectorized value info if available
700
- if info[:name] && info.dig(:info, :path)
701
- info[:info][:path].first
702
- else
703
- :vectorized_reference
704
- end
705
- else
706
- # For operations and other cases, try to infer from vectorized args
707
- if info[:vectorized_args]
708
- # This is likely an operation - we should look at its arguments
709
- :operation_result
710
- else
711
- :unknown
712
- end
713
- end
714
- end
715
-
716
- def extract_dimensions(info, _array_fields, _nested_paths)
717
- case info[:source]
718
- when :array_field_access
719
- info[:path]
720
- when :nested_array_access
721
- info[:path]
722
- when :vectorized_declaration, :vectorized_value
723
- # Try to extract from the vectorized value info if available
724
- if info[:name] && info.dig(:info, :path)
725
- info[:info][:path]
726
- else
727
- [:vectorized_reference]
728
- end
729
- else
730
- # For operations, try to infer from the operation context
731
- if info[:vectorized_args]
732
- # This is likely an operation - we should trace its arguments
733
- [:operation_result]
734
- else
735
- [:unknown]
736
- end
737
- end
738
- end
739
-
740
659
  def extract_nested_paths_from_dimensions(dimension, nested_paths)
741
660
  return nil unless dimension.is_a?(Array)
742
661
 
@@ -426,9 +426,6 @@ module Kumi
426
426
  when Syntax::CallExpression
427
427
  entry = Kumi::Registry.entry(expr.fn_name)
428
428
 
429
- # Validate signature metadata from FunctionSignaturePass (read-only assertions)
430
- validate_signature_metadata(expr, entry)
431
-
432
429
  # Constant folding optimization: evaluate expressions with all literal arguments
433
430
  if can_constant_fold?(expr, entry)
434
431
  folded_value = constant_fold(expr, entry)
@@ -888,39 +885,6 @@ module Kumi
888
885
  expr.args.all? { |arg| arg.is_a?(Syntax::Literal) }
889
886
  end
890
887
 
891
- def validate_signature_metadata(expr, entry)
892
- # Get the node index to access signature metadata
893
- node_index = get_state(:node_index, required: false)
894
- return unless node_index
895
-
896
- node_entry = node_index[expr.object_id]
897
- return unless node_entry
898
-
899
- metadata = node_entry[:metadata]
900
- return unless metadata
901
-
902
- # Validate that dropped axes make sense for reduction functions
903
- if entry&.reducer && metadata[:dropped_axes]
904
- dropped_axes = metadata[:dropped_axes]
905
- unless dropped_axes.is_a?(Array)
906
- raise "Invalid dropped_axes metadata for reducer #{expr.fn_name}: expected Array, got #{dropped_axes.class}"
907
- end
908
-
909
- # For reductions, we should have at least one dropped axis (or empty for scalar reductions)
910
- puts " SIGNATURE[#{expr.fn_name}] dropped_axes: #{dropped_axes.inspect}" if ENV["DEBUG_LOWER"]
911
- end
912
-
913
- # Validate join_policy is recognized
914
- if metadata[:join_policy] && !%i[zip product].include?(metadata[:join_policy])
915
- raise "Invalid join_policy for #{expr.fn_name}: #{metadata[:join_policy].inspect}"
916
- end
917
-
918
- # Warn about join_policy when no join op exists yet (future integration point)
919
- return unless metadata[:join_policy] && ENV["DEBUG_LOWER"]
920
-
921
- puts " SIGNATURE[#{expr.fn_name}] join_policy: #{metadata[:join_policy]} (join op not yet implemented)"
922
- end
923
-
924
888
  def constant_fold(expr, entry)
925
889
  literal_values = expr.args.map(&:value)
926
890
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pry"
4
3
  module Kumi
5
4
  module Core
6
5
  module Analyzer
@@ -8,52 +7,18 @@ module Kumi
8
7
  # RESPONSIBILITY: Compute topological ordering of declarations, blocking all cycles
9
8
  # DEPENDENCIES: :dependencies from DependencyResolver, :declarations from NameIndexer
10
9
  # PRODUCES: :evaluation_order - Array of declaration names in evaluation order
11
- # :node_index - Hash mapping object_id to node metadata for later passes
12
10
  # INTERFACE: new(schema, state).run(errors)
13
11
  class Toposorter < PassBase
14
12
  def run(errors)
15
13
  dependency_graph = get_state(:dependencies, required: false) || {}
16
14
  definitions = get_state(:declarations, required: false) || {}
17
15
 
18
- # Create node index for later passes to use
19
- node_index = build_node_index(definitions)
20
16
  order = compute_topological_order(dependency_graph, definitions, errors)
21
-
22
- state.with(:evaluation_order, order).with(:node_index, node_index)
17
+ state.with(:evaluation_order, order)
23
18
  end
24
19
 
25
20
  private
26
21
 
27
- def build_node_index(definitions)
28
- index = {}
29
-
30
- # Walk all declarations and their expressions to index every node
31
- definitions.each_value do |decl|
32
- index_node_recursive(decl, index)
33
- end
34
-
35
- index
36
- end
37
-
38
- def index_node_recursive(node, index)
39
- return unless node
40
-
41
- # Index this node by its object_id
42
- index[node.object_id] = {
43
- node: node,
44
- type: node.class.name.split("::").last,
45
- metadata: {}
46
- }
47
-
48
- # Use the same approach as the visitor pattern - recursively index all children
49
- node.children.each { |child| index_node_recursive(child, index) } if node.respond_to?(:children)
50
-
51
- # Index expression for declaration nodes
52
- return unless node.respond_to?(:expression)
53
-
54
- index_node_recursive(node.expression, index)
55
- end
56
-
57
22
  def compute_topological_order(graph, definitions, errors)
58
23
  temp_marks = Set.new
59
24
  perm_marks = Set.new
@@ -6,7 +6,6 @@ module Kumi
6
6
  module Passes
7
7
  # RESPONSIBILITY: Detect unsatisfiable constraints and analyze cascade mutual exclusion
8
8
  # DEPENDENCIES: :declarations from NameIndexer, :input_metadata from InputCollector
9
- # PRODUCES: :cascades - Hash of cascade mutual exclusion analysis results
10
9
  # INTERFACE: new(schema, state).run(errors)
11
10
  class UnsatDetector < VisitorPass
12
11
  include Syntax
@@ -20,142 +19,31 @@ module Kumi
20
19
  @definitions = definitions
21
20
  @evaluator = ConstantEvaluator.new(definitions)
22
21
 
23
- # First pass: analyze cascade conditions for mutual exclusion
24
- cascades = {}
25
22
  each_decl do |decl|
26
- cascades[decl.name] = analyze_cascade_mutual_exclusion(decl, definitions) if decl.expression.is_a?(CascadeExpression)
27
-
28
- # Store cascade metadata for later passes
29
-
30
- # Second pass: check for unsatisfiable constraints
31
23
  if decl.expression.is_a?(CascadeExpression)
32
- # Special handling for cascade expressions
33
24
  check_cascade_expression(decl, definitions, errors)
34
25
  elsif decl.expression.is_a?(CallExpression) && decl.expression.fn_name == :or
35
- # Check for OR expressions which need special disjunctive handling
36
26
  impossible = check_or_expression(decl.expression, definitions, errors)
37
27
  report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc) if impossible
38
28
  else
39
- # Normal handling for non-cascade expressions
40
29
  atoms = gather_atoms(decl.expression, definitions, Set.new)
41
30
  next if atoms.empty?
42
31
 
43
- # DEBUG: Add detailed logging for hierarchical broadcasting debugging
44
- if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
45
- puts "DEBUG UNSAT: Checking declaration '#{decl.name}' at #{decl.loc}"
46
- puts " Expression: #{decl.expression.inspect}"
47
- puts " Gathered atoms: #{atoms.map(&:inspect)}"
48
- puts " Input meta: #{@input_meta.keys.inspect}" if @input_meta
49
- end
50
-
51
- # Use enhanced solver that can detect cross-variable mathematical constraints
52
- if definitions && !definitions.empty?
53
- result = Kumi::Core::ConstraintRelationshipSolver.unsat?(atoms, definitions, input_meta: @input_meta)
54
- if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
55
- puts " Enhanced solver result: #{result}"
56
- end
57
- else
58
- result = Kumi::Core::AtomUnsatSolver.unsat?(atoms)
59
- if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
60
- puts " Basic solver result: #{result}"
61
- end
62
- end
32
+ result = if definitions && !definitions.empty?
33
+ Kumi::Core::ConstraintRelationshipSolver.unsat?(atoms, definitions, input_meta: @input_meta)
34
+ else
35
+ Kumi::Core::AtomUnsatSolver.unsat?(atoms)
36
+ end
63
37
  impossible = result
64
38
 
65
- if impossible && (ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257"))
66
- puts " -> FLAGGING AS IMPOSSIBLE: #{decl.name}"
67
- end
68
-
69
39
  report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc) if impossible
70
40
  end
71
41
  end
72
- state.with(:cascades, cascades)
73
- end
74
-
75
- private
76
-
77
- def analyze_cascade_mutual_exclusion(decl, definitions)
78
- conditions = []
79
- condition_traits = []
80
-
81
- # Extract all cascade conditions (except base case)
82
- decl.expression.cases[0...-1].each do |when_case|
83
- next unless when_case.condition
84
-
85
- next unless when_case.condition.fn_name == :cascade_and
86
-
87
- when_case.condition.args.each do |arg|
88
- if arg.is_a?(ArrayExpression)
89
- # Handle array elements (for array broadcasting)
90
- arg.elements.each do |element|
91
- next unless element.is_a?(DeclarationReference)
92
-
93
- trait_name = element.name
94
- trait = definitions[trait_name]
95
- if trait
96
- conditions << trait.expression
97
- condition_traits << trait_name
98
- end
99
- end
100
- elsif arg.is_a?(DeclarationReference)
101
- # Handle direct trait references (simple case)
102
- trait_name = arg.name
103
- trait = definitions[trait_name]
104
- if trait
105
- conditions << trait.expression
106
- condition_traits << trait_name
107
- end
108
- end
109
- end
110
- # end
111
- end
112
-
113
- # Check mutual exclusion for all pairs
114
- total_pairs = conditions.size * (conditions.size - 1) / 2
115
- exclusive_pairs = 0
116
-
117
- if conditions.size >= 2
118
- conditions.combination(2).each do |cond1, cond2|
119
- exclusive_pairs += 1 if conditions_mutually_exclusive?(cond1, cond2)
120
- end
121
- end
122
-
123
- all_mutually_exclusive = total_pairs.positive? && (exclusive_pairs == total_pairs)
124
-
125
- {
126
- condition_traits: condition_traits,
127
- condition_count: conditions.size,
128
- all_mutually_exclusive: all_mutually_exclusive,
129
- exclusive_pairs: exclusive_pairs,
130
- total_pairs: total_pairs
131
- }
132
- end
133
-
134
- def conditions_mutually_exclusive?(cond1, cond2)
135
- if cond1.is_a?(CallExpression) && cond1.fn_name == :== &&
136
- cond2.is_a?(CallExpression) && cond2.fn_name == :==
137
-
138
- c1_field, c1_value = cond1.args
139
- c2_field, c2_value = cond2.args
140
-
141
- # Same field, different values = mutually exclusive
142
- return true if same_field?(c1_field, c2_field) && different_values?(c1_value, c2_value)
143
- end
144
42
 
145
- false
43
+ state
146
44
  end
147
45
 
148
- def same_field?(field1, field2)
149
- return false unless field1.is_a?(InputReference) && field2.is_a?(InputReference)
150
-
151
- field1.name == field2.name
152
- end
153
-
154
- def different_values?(val1, val2)
155
- return false unless val1.is_a?(Literal) && val2.is_a?(Literal)
156
-
157
- val1.value != val2.value
158
- end
46
+ private
159
47
 
160
48
  def check_or_expression(or_expr, definitions, _errors)
161
49
  # For OR expressions: A | B is impossible only if BOTH A AND B are impossible
@@ -199,23 +87,16 @@ module Kumi
199
87
  if current.is_a?(CallExpression) && COMPARATORS.include?(current.fn_name)
200
88
  lhs, rhs = current.args
201
89
 
202
- # Check for domain constraint violations before creating atom
203
90
  list << if impossible_constraint?(lhs, rhs, current.fn_name)
204
- # Create a special impossible atom that will always trigger unsat
205
91
  Atom.new(:==, :__impossible__, true)
206
92
  else
207
93
  Atom.new(current.fn_name, term(lhs, defs), term(rhs, defs))
208
94
  end
209
95
  elsif current.is_a?(CallExpression) && current.fn_name == :or
210
- # Special handling for OR expressions - they are disjunctive, not conjunctive
211
- # We should NOT add OR children to the stack as they would be treated as AND
212
- # OR expressions need separate analysis in the main run() method
213
96
  next
214
97
  elsif current.is_a?(CallExpression) && current.fn_name == :cascade_and
215
- # cascade_and takes individual arguments (not wrapped in array)
216
98
  current.args.each { |arg| stack << arg }
217
99
  elsif current.is_a?(ArrayExpression)
218
- # For ArrayExpression, add all elements to the stack
219
100
  current.elements.each { |elem| stack << elem }
220
101
  elsif current.is_a?(DeclarationReference)
221
102
  name = current.name
@@ -225,10 +106,6 @@ module Kumi
225
106
  end
226
107
  end
227
108
 
228
- # Add children to stack for processing
229
- # IMPORTANT: Skip CascadeExpression children to avoid false positives
230
- # Cascades are handled separately by check_cascade_expression() and are disjunctive,
231
- # but gather_atoms() treats all collected atoms as conjunctive
232
109
  current.children.each { |child| stack << child } if current.respond_to?(:children) && !current.is_a?(CascadeExpression)
233
110
  end
234
111
 
@@ -247,32 +124,24 @@ module Kumi
247
124
  end
248
125
 
249
126
  decl.expression.cases.each_with_index do |when_case, index|
250
- # DEBUG: Log each case
251
127
  if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
252
128
  puts " Case #{index}: condition=#{when_case.condition.inspect}"
253
129
  end
254
130
 
255
- # Skip the base case (it's typically a literal true condition)
256
131
  next if when_case.condition.is_a?(Literal) && when_case.condition.value == true
257
132
 
258
- # Skip non-conjunctive conditions (any?, none?) as they are disjunctive
259
133
  next if when_case.condition.is_a?(CallExpression) && %i[any? none?].include?(when_case.condition.fn_name)
260
134
 
261
- # Skip single-trait 'on' branches: trait-level unsat detection covers these
262
135
  if when_case.condition.is_a?(CallExpression) && when_case.condition.fn_name == :cascade_and && (when_case.condition.args.size == 1)
263
- # cascade_and uses individual arguments - skip if only one trait
264
136
  next
265
137
  end
266
138
 
267
- # Gather atoms from this individual condition only
268
139
  condition_atoms = gather_atoms(when_case.condition, definitions, Set.new, [])
269
140
 
270
- # DEBUG: Add detailed logging for hierarchical broadcasting debugging
271
141
  if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
272
142
  puts " Condition atoms: #{condition_atoms.map(&:inspect)}"
273
143
  end
274
144
 
275
- # Use enhanced solver for cascade conditions too
276
145
  if definitions && !definitions.empty?
277
146
  result = Kumi::Core::ConstraintRelationshipSolver.unsat?(condition_atoms, definitions, input_meta: @input_meta)
278
147
  if ENV["DEBUG_UNSAT"] || decl.loc&.to_s&.include?("hierarchical_broadcasting_spec.rb:257")
@@ -287,9 +156,7 @@ module Kumi
287
156
  impossible = result
288
157
  next unless !condition_atoms.empty? && impossible
289
158
 
290
- # For multi-trait on-clauses, report the trait names rather than the value name
291
159
  if when_case.condition.is_a?(CallExpression) && when_case.condition.fn_name == :cascade_and
292
- # cascade_and uses individual arguments
293
160
  trait_bindings = when_case.condition.args
294
161
 
295
162
  if trait_bindings.all?(DeclarationReference)
@@ -314,9 +181,6 @@ module Kumi
314
181
  val = @evaluator.evaluate(node)
315
182
  val == :unknown ? node.name : val
316
183
  when InputElementReference
317
- # For hierarchical paths like input.companies.regions.offices.teams.department,
318
- # create a unique identifier that represents the specific path
319
- # This prevents false positives where different paths are treated as the same :unknown
320
184
  path_identifier = node.path.join(".").to_s
321
185
  path_identifier.to_sym
322
186
  when Literal
@@ -326,51 +190,10 @@ module Kumi
326
190
  end
327
191
  end
328
192
 
329
- def check_domain_constraints(node, definitions, errors)
330
- case node
331
- when InputReference
332
- # Check if InputReference points to a field with domain constraints
333
- field_meta = @input_meta[node.name]
334
- nil unless field_meta&.dig(:domain)
335
-
336
- # For InputReference, the constraint comes from trait conditions
337
- # We don't flag here since the InputReference itself is valid
338
- when DeclarationReference
339
- # Check if this binding evaluates to a value that violates domain constraints
340
- definition = definitions[node.name]
341
- return unless definition
342
-
343
- if definition.expression.is_a?(Literal)
344
- literal_value = definition.expression.value
345
- check_value_against_domains(node.name, literal_value, errors, definition.loc)
346
- end
347
- end
348
- end
349
-
350
- def check_value_against_domains(_var_name, value, _errors, _location)
351
- # Check if this value violates any input domain constraints
352
- @input_meta.each_value do |field_meta|
353
- domain = field_meta[:domain]
354
- next unless domain
355
-
356
- if violates_domain?(value, domain)
357
- # This indicates a constraint that can never be satisfied
358
- # Rather than flagging the cascade, flag the impossible condition
359
- return true
360
- end
361
- end
362
- false
363
- end
364
-
365
193
  def violates_domain?(value, domain)
366
194
  case domain
367
- when Range
368
- !domain.include?(value)
369
- when Array
195
+ when Range, Array
370
196
  !domain.include?(value)
371
- when Proc
372
- # For Proc domains, we can't statically analyze
373
- false
374
197
  else
375
198
  false
376
199
  end
@@ -381,7 +204,6 @@ module Kumi
381
204
  if lhs.is_a?(InputReference) && rhs.is_a?(Literal)
382
205
  return field_literal_impossible?(lhs, rhs, operator)
383
206
  elsif rhs.is_a?(InputReference) && lhs.is_a?(Literal)
384
- # Reverse case: literal compared to field
385
207
  return field_literal_impossible?(rhs, lhs, flip_operator(operator))
386
208
  end
387
209
 
@@ -404,13 +226,10 @@ module Kumi
404
226
 
405
227
  case operator
406
228
  when :==
407
- # field == value where value is not in domain
408
229
  violates_domain?(literal_value, domain)
409
230
  when :!=
410
- # field != value where value is not in domain is always true (not impossible)
411
231
  false
412
232
  else
413
- # For other operators, we'd need more sophisticated analysis
414
233
  false
415
234
  end
416
235
  end
@@ -424,10 +243,8 @@ module Kumi
424
243
 
425
244
  case operator
426
245
  when :==
427
- # binding == value where binding evaluates to different value
428
246
  evaluated_value != literal_value
429
247
  else
430
- # For other operators, we could add more sophisticated checking
431
248
  false
432
249
  end
433
250
  end
@@ -3,17 +3,14 @@ module Kumi
3
3
  module Compiler
4
4
  class AccessBuilder
5
5
  class << self
6
- attr_accessor :strategy
6
+ attr_accessor :yjit
7
7
  end
8
+ self.yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
8
9
 
9
- self.strategy = if defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
10
- :interp
11
- else
12
- :codegen
13
- end
14
-
15
- def self.build(plans)
10
+ def self.build(plans, strategy: nil)
11
+ strategy ||= yjit ? :interp : :codegen
16
12
  accessors = {}
13
+
17
14
  plans.each_value do |variants|
18
15
  variants.each do |plan|
19
16
  accessors[plan.accessor_key] =
data/lib/kumi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kumi
4
- VERSION = "0.0.16"
4
+ VERSION = "0.0.18"
5
5
  end