kumi 0.0.7 → 0.0.8

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CLAUDE.md +1 -1
  3. data/README.md +8 -5
  4. data/examples/game_of_life.rb +1 -1
  5. data/examples/static_analysis_errors.rb +7 -7
  6. data/lib/kumi/analyzer.rb +15 -15
  7. data/lib/kumi/compiler.rb +6 -6
  8. data/lib/kumi/core/analyzer/analysis_state.rb +39 -0
  9. data/lib/kumi/core/analyzer/constant_evaluator.rb +59 -0
  10. data/lib/kumi/core/analyzer/passes/broadcast_detector.rb +248 -0
  11. data/lib/kumi/core/analyzer/passes/declaration_validator.rb +45 -0
  12. data/lib/kumi/core/analyzer/passes/dependency_resolver.rb +153 -0
  13. data/lib/kumi/core/analyzer/passes/input_collector.rb +139 -0
  14. data/lib/kumi/core/analyzer/passes/name_indexer.rb +26 -0
  15. data/lib/kumi/core/analyzer/passes/pass_base.rb +52 -0
  16. data/lib/kumi/core/analyzer/passes/semantic_constraint_validator.rb +111 -0
  17. data/lib/kumi/core/analyzer/passes/toposorter.rb +110 -0
  18. data/lib/kumi/core/analyzer/passes/type_checker.rb +162 -0
  19. data/lib/kumi/core/analyzer/passes/type_consistency_checker.rb +48 -0
  20. data/lib/kumi/core/analyzer/passes/type_inferencer.rb +236 -0
  21. data/lib/kumi/core/analyzer/passes/unsat_detector.rb +406 -0
  22. data/lib/kumi/core/analyzer/passes/visitor_pass.rb +44 -0
  23. data/lib/kumi/core/atom_unsat_solver.rb +396 -0
  24. data/lib/kumi/core/compiled_schema.rb +43 -0
  25. data/lib/kumi/core/constraint_relationship_solver.rb +641 -0
  26. data/lib/kumi/core/domain/enum_analyzer.rb +55 -0
  27. data/lib/kumi/core/domain/range_analyzer.rb +85 -0
  28. data/lib/kumi/core/domain/validator.rb +82 -0
  29. data/lib/kumi/core/domain/violation_formatter.rb +42 -0
  30. data/lib/kumi/core/error_reporter.rb +166 -0
  31. data/lib/kumi/core/error_reporting.rb +97 -0
  32. data/lib/kumi/core/errors.rb +120 -0
  33. data/lib/kumi/core/evaluation_wrapper.rb +40 -0
  34. data/lib/kumi/core/explain.rb +295 -0
  35. data/lib/kumi/core/export/deserializer.rb +41 -0
  36. data/lib/kumi/core/export/errors.rb +14 -0
  37. data/lib/kumi/core/export/node_builders.rb +142 -0
  38. data/lib/kumi/core/export/node_registry.rb +54 -0
  39. data/lib/kumi/core/export/node_serializers.rb +158 -0
  40. data/lib/kumi/core/export/serializer.rb +25 -0
  41. data/lib/kumi/core/export.rb +35 -0
  42. data/lib/kumi/core/function_registry/collection_functions.rb +202 -0
  43. data/lib/kumi/core/function_registry/comparison_functions.rb +33 -0
  44. data/lib/kumi/core/function_registry/conditional_functions.rb +38 -0
  45. data/lib/kumi/core/function_registry/function_builder.rb +95 -0
  46. data/lib/kumi/core/function_registry/logical_functions.rb +44 -0
  47. data/lib/kumi/core/function_registry/math_functions.rb +74 -0
  48. data/lib/kumi/core/function_registry/string_functions.rb +57 -0
  49. data/lib/kumi/core/function_registry/type_functions.rb +53 -0
  50. data/lib/kumi/{function_registry.rb → core/function_registry.rb} +28 -36
  51. data/lib/kumi/core/input/type_matcher.rb +97 -0
  52. data/lib/kumi/core/input/validator.rb +51 -0
  53. data/lib/kumi/core/input/violation_creator.rb +52 -0
  54. data/lib/kumi/core/json_schema/generator.rb +65 -0
  55. data/lib/kumi/core/json_schema/validator.rb +27 -0
  56. data/lib/kumi/core/json_schema.rb +16 -0
  57. data/lib/kumi/core/ruby_parser/build_context.rb +27 -0
  58. data/lib/kumi/core/ruby_parser/declaration_reference_proxy.rb +38 -0
  59. data/lib/kumi/core/ruby_parser/dsl.rb +14 -0
  60. data/lib/kumi/core/ruby_parser/dsl_cascade_builder.rb +138 -0
  61. data/lib/kumi/core/ruby_parser/expression_converter.rb +128 -0
  62. data/lib/kumi/core/ruby_parser/guard_rails.rb +45 -0
  63. data/lib/kumi/core/ruby_parser/input_builder.rb +127 -0
  64. data/lib/kumi/core/ruby_parser/input_field_proxy.rb +48 -0
  65. data/lib/kumi/core/ruby_parser/input_proxy.rb +31 -0
  66. data/lib/kumi/core/ruby_parser/nested_input.rb +17 -0
  67. data/lib/kumi/core/ruby_parser/parser.rb +71 -0
  68. data/lib/kumi/core/ruby_parser/schema_builder.rb +175 -0
  69. data/lib/kumi/core/ruby_parser/sugar.rb +263 -0
  70. data/lib/kumi/core/ruby_parser.rb +12 -0
  71. data/lib/kumi/core/schema_instance.rb +111 -0
  72. data/lib/kumi/core/types/builder.rb +23 -0
  73. data/lib/kumi/core/types/compatibility.rb +96 -0
  74. data/lib/kumi/core/types/formatter.rb +26 -0
  75. data/lib/kumi/core/types/inference.rb +42 -0
  76. data/lib/kumi/core/types/normalizer.rb +72 -0
  77. data/lib/kumi/core/types/validator.rb +37 -0
  78. data/lib/kumi/core/types.rb +66 -0
  79. data/lib/kumi/core/vectorization_metadata.rb +110 -0
  80. data/lib/kumi/errors.rb +1 -112
  81. data/lib/kumi/registry.rb +37 -0
  82. data/lib/kumi/schema.rb +5 -5
  83. data/lib/kumi/schema_metadata.rb +3 -3
  84. data/lib/kumi/syntax/array_expression.rb +6 -6
  85. data/lib/kumi/syntax/call_expression.rb +4 -4
  86. data/lib/kumi/syntax/cascade_expression.rb +4 -4
  87. data/lib/kumi/syntax/case_expression.rb +4 -4
  88. data/lib/kumi/syntax/declaration_reference.rb +4 -4
  89. data/lib/kumi/syntax/hash_expression.rb +4 -4
  90. data/lib/kumi/syntax/input_declaration.rb +5 -5
  91. data/lib/kumi/syntax/input_element_reference.rb +5 -5
  92. data/lib/kumi/syntax/input_reference.rb +5 -5
  93. data/lib/kumi/syntax/literal.rb +4 -4
  94. data/lib/kumi/syntax/node.rb +34 -34
  95. data/lib/kumi/syntax/root.rb +6 -6
  96. data/lib/kumi/syntax/trait_declaration.rb +4 -4
  97. data/lib/kumi/syntax/value_declaration.rb +4 -4
  98. data/lib/kumi/version.rb +1 -1
  99. data/migrate_to_core_iterative.rb +938 -0
  100. data/scripts/generate_function_docs.rb +9 -9
  101. metadata +75 -72
  102. data/lib/kumi/analyzer/analysis_state.rb +0 -37
  103. data/lib/kumi/analyzer/constant_evaluator.rb +0 -57
  104. data/lib/kumi/analyzer/passes/broadcast_detector.rb +0 -246
  105. data/lib/kumi/analyzer/passes/declaration_validator.rb +0 -43
  106. data/lib/kumi/analyzer/passes/dependency_resolver.rb +0 -151
  107. data/lib/kumi/analyzer/passes/input_collector.rb +0 -137
  108. data/lib/kumi/analyzer/passes/name_indexer.rb +0 -24
  109. data/lib/kumi/analyzer/passes/pass_base.rb +0 -50
  110. data/lib/kumi/analyzer/passes/semantic_constraint_validator.rb +0 -109
  111. data/lib/kumi/analyzer/passes/toposorter.rb +0 -108
  112. data/lib/kumi/analyzer/passes/type_checker.rb +0 -160
  113. data/lib/kumi/analyzer/passes/type_consistency_checker.rb +0 -46
  114. data/lib/kumi/analyzer/passes/type_inferencer.rb +0 -232
  115. data/lib/kumi/analyzer/passes/unsat_detector.rb +0 -404
  116. data/lib/kumi/analyzer/passes/visitor_pass.rb +0 -42
  117. data/lib/kumi/atom_unsat_solver.rb +0 -394
  118. data/lib/kumi/compiled_schema.rb +0 -41
  119. data/lib/kumi/constraint_relationship_solver.rb +0 -638
  120. data/lib/kumi/domain/enum_analyzer.rb +0 -53
  121. data/lib/kumi/domain/range_analyzer.rb +0 -83
  122. data/lib/kumi/domain/validator.rb +0 -80
  123. data/lib/kumi/domain/violation_formatter.rb +0 -40
  124. data/lib/kumi/error_reporter.rb +0 -164
  125. data/lib/kumi/error_reporting.rb +0 -95
  126. data/lib/kumi/evaluation_wrapper.rb +0 -38
  127. data/lib/kumi/explain.rb +0 -293
  128. data/lib/kumi/export/deserializer.rb +0 -39
  129. data/lib/kumi/export/errors.rb +0 -12
  130. data/lib/kumi/export/node_builders.rb +0 -140
  131. data/lib/kumi/export/node_registry.rb +0 -52
  132. data/lib/kumi/export/node_serializers.rb +0 -156
  133. data/lib/kumi/export/serializer.rb +0 -23
  134. data/lib/kumi/export.rb +0 -33
  135. data/lib/kumi/function_registry/collection_functions.rb +0 -200
  136. data/lib/kumi/function_registry/comparison_functions.rb +0 -31
  137. data/lib/kumi/function_registry/conditional_functions.rb +0 -36
  138. data/lib/kumi/function_registry/function_builder.rb +0 -93
  139. data/lib/kumi/function_registry/logical_functions.rb +0 -42
  140. data/lib/kumi/function_registry/math_functions.rb +0 -72
  141. data/lib/kumi/function_registry/string_functions.rb +0 -54
  142. data/lib/kumi/function_registry/type_functions.rb +0 -51
  143. data/lib/kumi/input/type_matcher.rb +0 -95
  144. data/lib/kumi/input/validator.rb +0 -49
  145. data/lib/kumi/input/violation_creator.rb +0 -50
  146. data/lib/kumi/json_schema/generator.rb +0 -63
  147. data/lib/kumi/json_schema/validator.rb +0 -25
  148. data/lib/kumi/json_schema.rb +0 -14
  149. data/lib/kumi/ruby_parser/build_context.rb +0 -25
  150. data/lib/kumi/ruby_parser/declaration_reference_proxy.rb +0 -36
  151. data/lib/kumi/ruby_parser/dsl.rb +0 -12
  152. data/lib/kumi/ruby_parser/dsl_cascade_builder.rb +0 -136
  153. data/lib/kumi/ruby_parser/expression_converter.rb +0 -126
  154. data/lib/kumi/ruby_parser/guard_rails.rb +0 -43
  155. data/lib/kumi/ruby_parser/input_builder.rb +0 -125
  156. data/lib/kumi/ruby_parser/input_field_proxy.rb +0 -46
  157. data/lib/kumi/ruby_parser/input_proxy.rb +0 -29
  158. data/lib/kumi/ruby_parser/nested_input.rb +0 -15
  159. data/lib/kumi/ruby_parser/parser.rb +0 -69
  160. data/lib/kumi/ruby_parser/schema_builder.rb +0 -173
  161. data/lib/kumi/ruby_parser/sugar.rb +0 -261
  162. data/lib/kumi/ruby_parser.rb +0 -10
  163. data/lib/kumi/schema_instance.rb +0 -109
  164. data/lib/kumi/types/builder.rb +0 -21
  165. data/lib/kumi/types/compatibility.rb +0 -94
  166. data/lib/kumi/types/formatter.rb +0 -24
  167. data/lib/kumi/types/inference.rb +0 -40
  168. data/lib/kumi/types/normalizer.rb +0 -70
  169. data/lib/kumi/types/validator.rb +0 -35
  170. data/lib/kumi/types.rb +0 -64
  171. data/lib/kumi/vectorization_metadata.rb +0 -108
@@ -1,404 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Analyzer
5
- module Passes
6
- # RESPONSIBILITY: Detect unsatisfiable constraints and analyze cascade mutual exclusion
7
- # DEPENDENCIES: :declarations from NameIndexer, :inputs from InputCollector
8
- # PRODUCES: :cascades - Hash of cascade mutual exclusion analysis results
9
- # INTERFACE: new(schema, state).run(errors)
10
- class UnsatDetector < VisitorPass
11
- include Syntax
12
-
13
- COMPARATORS = %i[> < >= <= == !=].freeze
14
- Atom = Kumi::AtomUnsatSolver::Atom
15
-
16
- def run(errors)
17
- definitions = get_state(:declarations)
18
- @input_meta = get_state(:inputs) || {}
19
- @definitions = definitions
20
- @evaluator = ConstantEvaluator.new(definitions)
21
-
22
- # First pass: analyze cascade conditions for mutual exclusion
23
- cascades = {}
24
- each_decl do |decl|
25
- cascades[decl.name] = analyze_cascade_mutual_exclusion(decl, definitions) if decl.expression.is_a?(CascadeExpression)
26
-
27
- # Store cascade metadata for later passes
28
-
29
- # Second pass: check for unsatisfiable constraints
30
- if decl.expression.is_a?(CascadeExpression)
31
- # Special handling for cascade expressions
32
- check_cascade_expression(decl, definitions, errors)
33
- elsif decl.expression.is_a?(CallExpression) && decl.expression.fn_name == :or
34
- # Check for OR expressions which need special disjunctive handling
35
- impossible = check_or_expression(decl.expression, definitions, errors)
36
- report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc) if impossible
37
- else
38
- # Normal handling for non-cascade expressions
39
- atoms = gather_atoms(decl.expression, definitions, Set.new)
40
- next if atoms.empty?
41
-
42
- # Use enhanced solver that can detect cross-variable mathematical constraints
43
- impossible = if definitions && !definitions.empty?
44
- Kumi::ConstraintRelationshipSolver.unsat?(atoms, definitions, input_meta: @input_meta)
45
- else
46
- Kumi::AtomUnsatSolver.unsat?(atoms)
47
- end
48
-
49
- report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc) if impossible
50
- end
51
- end
52
- state.with(:cascades, cascades)
53
- end
54
-
55
- private
56
-
57
- def analyze_cascade_mutual_exclusion(decl, definitions)
58
- conditions = []
59
- condition_traits = []
60
-
61
- # Extract all cascade conditions (except base case)
62
- decl.expression.cases[0...-1].each do |when_case|
63
- next unless when_case.condition
64
-
65
- next unless when_case.condition.fn_name == :all?
66
-
67
- when_case.condition.args.each do |arg|
68
- next unless arg.is_a?(ArrayExpression)
69
-
70
- arg.elements.each do |element|
71
- next unless element.is_a?(DeclarationReference)
72
-
73
- trait_name = element.name
74
- trait = definitions[trait_name]
75
- if trait
76
- conditions << trait.expression
77
- condition_traits << trait_name
78
- end
79
- end
80
- end
81
- # end
82
- end
83
-
84
- # Check mutual exclusion for all pairs
85
- total_pairs = conditions.size * (conditions.size - 1) / 2
86
- exclusive_pairs = 0
87
-
88
- if conditions.size >= 2
89
- conditions.combination(2).each do |cond1, cond2|
90
- exclusive_pairs += 1 if conditions_mutually_exclusive?(cond1, cond2)
91
- end
92
- end
93
-
94
- all_mutually_exclusive = total_pairs.positive? && (exclusive_pairs == total_pairs)
95
-
96
- {
97
- condition_traits: condition_traits,
98
- condition_count: conditions.size,
99
- all_mutually_exclusive: all_mutually_exclusive,
100
- exclusive_pairs: exclusive_pairs,
101
- total_pairs: total_pairs
102
- }
103
- end
104
-
105
- def conditions_mutually_exclusive?(cond1, cond2)
106
- if cond1.is_a?(CallExpression) && cond1.fn_name == :== &&
107
- cond2.is_a?(CallExpression) && cond2.fn_name == :==
108
-
109
- c1_field, c1_value = cond1.args
110
- c2_field, c2_value = cond2.args
111
-
112
- # Same field, different values = mutually exclusive
113
- return true if same_field?(c1_field, c2_field) && different_values?(c1_value, c2_value)
114
- end
115
-
116
- false
117
- end
118
-
119
- def same_field?(field1, field2)
120
- return false unless field1.is_a?(InputReference) && field2.is_a?(InputReference)
121
-
122
- field1.name == field2.name
123
- end
124
-
125
- def different_values?(val1, val2)
126
- return false unless val1.is_a?(Literal) && val2.is_a?(Literal)
127
-
128
- val1.value != val2.value
129
- end
130
-
131
- def check_or_expression(or_expr, definitions, _errors)
132
- # For OR expressions: A | B is impossible only if BOTH A AND B are impossible
133
- # If either side is satisfiable, the OR is satisfiable
134
- left_side, right_side = or_expr.args
135
-
136
- # Check if left side is impossible
137
- left_atoms = gather_atoms(left_side, definitions, Set.new)
138
- left_impossible = if left_atoms.empty?
139
- false
140
- elsif definitions && !definitions.empty?
141
- Kumi::ConstraintRelationshipSolver.unsat?(left_atoms, definitions, input_meta: @input_meta)
142
- else
143
- Kumi::AtomUnsatSolver.unsat?(left_atoms)
144
- end
145
-
146
- # Check if right side is impossible
147
- right_atoms = gather_atoms(right_side, definitions, Set.new)
148
- right_impossible = if right_atoms.empty?
149
- false
150
- elsif definitions && !definitions.empty?
151
- Kumi::ConstraintRelationshipSolver.unsat?(right_atoms, definitions, input_meta: @input_meta)
152
- else
153
- Kumi::AtomUnsatSolver.unsat?(right_atoms)
154
- end
155
-
156
- # OR is impossible only if BOTH sides are impossible
157
- left_impossible && right_impossible
158
- end
159
-
160
- def gather_atoms(node, defs, visited, list = [])
161
- return list unless node
162
-
163
- # Use iterative approach with stack to avoid SystemStackError on deep graphs
164
- stack = [node]
165
-
166
- until stack.empty?
167
- current = stack.pop
168
- next unless current
169
-
170
- if current.is_a?(CallExpression) && COMPARATORS.include?(current.fn_name)
171
- lhs, rhs = current.args
172
-
173
- # Check for domain constraint violations before creating atom
174
- list << if impossible_constraint?(lhs, rhs, current.fn_name)
175
- # Create a special impossible atom that will always trigger unsat
176
- Atom.new(:==, :__impossible__, true)
177
- else
178
- Atom.new(current.fn_name, term(lhs, defs), term(rhs, defs))
179
- end
180
- elsif current.is_a?(CallExpression) && current.fn_name == :or
181
- # Special handling for OR expressions - they are disjunctive, not conjunctive
182
- # We should NOT add OR children to the stack as they would be treated as AND
183
- # OR expressions need separate analysis in the main run() method
184
- next
185
- elsif current.is_a?(CallExpression) && current.fn_name == :all?
186
- # For all? function, add all trait arguments to the stack
187
- current.args.each { |arg| stack << arg }
188
- elsif current.is_a?(ArrayExpression)
189
- # For ArrayExpression, add all elements to the stack
190
- current.elements.each { |elem| stack << elem }
191
- elsif current.is_a?(DeclarationReference)
192
- name = current.name
193
- unless visited.include?(name)
194
- visited << name
195
- stack << defs[name].expression if defs.key?(name)
196
- end
197
- end
198
-
199
- # Add children to stack for processing
200
- # IMPORTANT: Skip CascadeExpression children to avoid false positives
201
- # Cascades are handled separately by check_cascade_expression() and are disjunctive,
202
- # but gather_atoms() treats all collected atoms as conjunctive
203
- current.children.each { |child| stack << child } if current.respond_to?(:children) && !current.is_a?(CascadeExpression)
204
- end
205
-
206
- list
207
- end
208
-
209
- def check_cascade_expression(decl, definitions, errors)
210
- # Analyze each cascade branch condition independently
211
- # This is the correct behavior: each 'on' condition should be checked separately
212
- # since only ONE will be evaluated at runtime (they're mutually exclusive by design)
213
-
214
- decl.expression.cases.each_with_index do |when_case, _index|
215
- # Skip the base case (it's typically a literal true condition)
216
- next if when_case.condition.is_a?(Literal) && when_case.condition.value == true
217
-
218
- # Skip non-conjunctive conditions (any?, none?) as they are disjunctive
219
- next if when_case.condition.is_a?(CallExpression) && %i[any? none?].include?(when_case.condition.fn_name)
220
-
221
- # Skip single-trait 'on' branches: trait-level unsat detection covers these
222
- if when_case.condition.is_a?(CallExpression) && when_case.condition.fn_name == :all?
223
- # Handle both ArrayExpression (old format) and multiple args (new format)
224
- if when_case.condition.args.size == 1 && when_case.condition.args.first.is_a?(ArrayExpression)
225
- list = when_case.condition.args.first
226
- next if list.elements.size == 1
227
- elsif when_case.condition.args.size == 1
228
- # Multiple args format
229
- next
230
- end
231
- end
232
- # Gather atoms from this individual condition only
233
- condition_atoms = gather_atoms(when_case.condition, definitions, Set.new, [])
234
- # DEBUG
235
- # if when_case.condition.is_a?(CallExpression) && [:all?, :any?, :none?].include?(when_case.condition.fn_name)
236
- # puts " Args: #{when_case.condition.args.inspect}"
237
- # puts " Atoms found: #{condition_atoms.inspect}"
238
- # end
239
-
240
- # Only flag if this individual condition is impossible
241
- # if !condition_atoms.empty?
242
- # is_unsat = Kumi::AtomUnsatSolver.unsat?(condition_atoms)
243
- # puts " Is unsat? #{is_unsat}"
244
- # end
245
- # Use enhanced solver for cascade conditions too
246
- impossible = if definitions && !definitions.empty?
247
- Kumi::ConstraintRelationshipSolver.unsat?(condition_atoms, definitions, input_meta: @input_meta)
248
- else
249
- Kumi::AtomUnsatSolver.unsat?(condition_atoms)
250
- end
251
- next unless !condition_atoms.empty? && impossible
252
-
253
- # For multi-trait on-clauses, report the trait names rather than the value name
254
- if when_case.condition.is_a?(CallExpression) && when_case.condition.fn_name == :all?
255
- # Handle both ArrayExpression (old format) and multiple args (new format)
256
- trait_bindings = if when_case.condition.args.size == 1 && when_case.condition.args.first.is_a?(ArrayExpression)
257
- when_case.condition.args.first.elements
258
- else
259
- when_case.condition.args
260
- end
261
-
262
- if trait_bindings.all?(DeclarationReference)
263
- traits = trait_bindings.map(&:name).join(" AND ")
264
- report_error(errors, "conjunction `#{traits}` is impossible", location: decl.loc)
265
- next
266
- end
267
- end
268
- report_error(errors, "conjunction `#{decl.name}` is impossible", location: decl.loc)
269
- end
270
- end
271
-
272
- def term(node, _defs)
273
- case node
274
- when InputReference, DeclarationReference
275
- val = @evaluator.evaluate(node)
276
- val == :unknown ? node.name : val
277
- when Literal
278
- node.value
279
- else
280
- :unknown
281
- end
282
- end
283
-
284
- def check_domain_constraints(node, definitions, errors)
285
- case node
286
- when InputReference
287
- # Check if InputReference points to a field with domain constraints
288
- field_meta = @input_meta[node.name]
289
- nil unless field_meta&.dig(:domain)
290
-
291
- # For InputReference, the constraint comes from trait conditions
292
- # We don't flag here since the InputReference itself is valid
293
- when DeclarationReference
294
- # Check if this binding evaluates to a value that violates domain constraints
295
- definition = definitions[node.name]
296
- return unless definition
297
-
298
- if definition.expression.is_a?(Literal)
299
- literal_value = definition.expression.value
300
- check_value_against_domains(node.name, literal_value, errors, definition.loc)
301
- end
302
- end
303
- end
304
-
305
- def check_value_against_domains(_var_name, value, _errors, _location)
306
- # Check if this value violates any input domain constraints
307
- @input_meta.each_value do |field_meta|
308
- domain = field_meta[:domain]
309
- next unless domain
310
-
311
- if violates_domain?(value, domain)
312
- # This indicates a constraint that can never be satisfied
313
- # Rather than flagging the cascade, flag the impossible condition
314
- return true
315
- end
316
- end
317
- false
318
- end
319
-
320
- def violates_domain?(value, domain)
321
- case domain
322
- when Range
323
- !domain.include?(value)
324
- when Array
325
- !domain.include?(value)
326
- when Proc
327
- # For Proc domains, we can't statically analyze
328
- false
329
- else
330
- false
331
- end
332
- end
333
-
334
- def impossible_constraint?(lhs, rhs, operator)
335
- # Case 1: InputReference compared against value outside its domain
336
- if lhs.is_a?(InputReference) && rhs.is_a?(Literal)
337
- return field_literal_impossible?(lhs, rhs, operator)
338
- elsif rhs.is_a?(InputReference) && lhs.is_a?(Literal)
339
- # Reverse case: literal compared to field
340
- return field_literal_impossible?(rhs, lhs, flip_operator(operator))
341
- end
342
-
343
- # Case 2: DeclarationReference that evaluates to literal compared against impossible value
344
- if lhs.is_a?(DeclarationReference) && rhs.is_a?(Literal)
345
- return binding_literal_impossible?(lhs, rhs, operator)
346
- elsif rhs.is_a?(DeclarationReference) && lhs.is_a?(Literal)
347
- return binding_literal_impossible?(rhs, lhs, flip_operator(operator))
348
- end
349
-
350
- false
351
- end
352
-
353
- def field_literal_impossible?(field_ref, literal, operator)
354
- field_meta = @input_meta[field_ref.name]
355
- return false unless field_meta&.dig(:domain)
356
-
357
- domain = field_meta[:domain]
358
- literal_value = literal.value
359
-
360
- case operator
361
- when :==
362
- # field == value where value is not in domain
363
- violates_domain?(literal_value, domain)
364
- when :!=
365
- # field != value where value is not in domain is always true (not impossible)
366
- false
367
- else
368
- # For other operators, we'd need more sophisticated analysis
369
- false
370
- end
371
- end
372
-
373
- def binding_literal_impossible?(binding, literal, operator)
374
- # Check if binding evaluates to a literal that conflicts with the comparison
375
- evaluated_value = @evaluator.evaluate(binding)
376
- return false if evaluated_value == :unknown
377
-
378
- literal_value = literal.value
379
-
380
- case operator
381
- when :==
382
- # binding == value where binding evaluates to different value
383
- evaluated_value != literal_value
384
- else
385
- # For other operators, we could add more sophisticated checking
386
- false
387
- end
388
- end
389
-
390
- def flip_operator(operator)
391
- case operator
392
- when :> then :<
393
- when :>= then :<=
394
- when :< then :>
395
- when :<= then :>=
396
- when :== then :==
397
- when :!= then :!=
398
- else operator
399
- end
400
- end
401
- end
402
- end
403
- end
404
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kumi
4
- module Analyzer
5
- module Passes
6
- # Base class for analyzer passes that need to traverse the AST using the visitor pattern.
7
- # Inherits the new immutable state interface from PassBase.
8
- class VisitorPass < PassBase
9
- # Visit a node and all its children using depth-first traversal
10
- # @param node [Syntax::Node] The node to visit
11
- # @yield [Syntax::Node] Each node in the traversal
12
- def visit(node, &block)
13
- return unless node
14
-
15
- yield(node)
16
- node.children.each { |child| visit(child, &block) }
17
- end
18
-
19
- protected
20
-
21
- # Helper to visit each declaration's expression tree
22
- # @param errors [Array] Error accumulator
23
- # @yield [Syntax::Node, Syntax::Base] Each node and its containing declaration
24
- def visit_all_expressions(errors)
25
- each_decl do |decl|
26
- visit(decl.expression) { |node| yield(node, decl, errors) }
27
- end
28
- end
29
-
30
- # Helper to visit only specific node types
31
- # @param node_types [Array<Class>] Node types to match
32
- # @param errors [Array] Error accumulator
33
- # @yield [Syntax::Node, Syntax::Base] Matching nodes and their declarations
34
- def visit_nodes_of_type(*node_types, errors:)
35
- visit_all_expressions(errors) do |node, decl, errs|
36
- yield(node, decl, errs) if node_types.any? { |type| node.is_a?(type) }
37
- end
38
- end
39
- end
40
- end
41
- end
42
- end