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