kumi 0.0.24 → 0.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +70 -71
- data/data/functions/agg/boolean.yaml +6 -2
- data/data/functions/agg/numeric.yaml +32 -16
- data/data/functions/agg/string.yaml +4 -3
- data/data/functions/core/arithmetic.yaml +62 -14
- data/data/functions/core/boolean.yaml +12 -6
- data/data/functions/core/comparison.yaml +25 -13
- data/data/functions/core/constructor.yaml +16 -8
- data/data/functions/core/select.yaml +3 -1
- data/data/functions/core/stencil.yaml +14 -5
- data/data/functions/core/string.yaml +9 -4
- data/data/kernels/ruby/agg/numeric.yaml +1 -1
- data/docs/UNSAT_DETECTION.md +83 -0
- data/golden/array_element/expected/nast.txt +1 -1
- data/golden/array_element/expected/schema_ruby.rb +1 -1
- data/golden/array_index/expected/nast.txt +7 -7
- data/golden/array_index/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/nast.txt +2 -2
- data/golden/array_operations/expected/schema_ruby.rb +1 -1
- data/golden/array_operations/expected/snast.txt +3 -3
- data/golden/cascade_logic/expected/lir_02_inlined.txt +8 -8
- data/golden/cascade_logic/expected/schema_ruby.rb +1 -1
- data/golden/cascade_logic/expected/snast.txt +2 -2
- data/golden/chained_fusion/expected/lir_02_inlined.txt +36 -36
- data/golden/chained_fusion/expected/lir_03_cse.txt +23 -23
- data/golden/chained_fusion/expected/lir_04_1_loop_fusion.txt +25 -25
- data/golden/chained_fusion/expected/lir_04_loop_invcm.txt +23 -23
- data/golden/chained_fusion/expected/lir_06_const_prop.txt +23 -23
- data/golden/chained_fusion/expected/nast.txt +2 -2
- data/golden/chained_fusion/expected/schema_javascript.mjs +23 -23
- data/golden/chained_fusion/expected/schema_ruby.rb +28 -28
- data/golden/element_arrays/expected/nast.txt +2 -2
- data/golden/element_arrays/expected/schema_ruby.rb +1 -1
- data/golden/element_arrays/expected/snast.txt +1 -1
- data/golden/empty_and_null_inputs/expected/lir_02_inlined.txt +18 -18
- data/golden/empty_and_null_inputs/expected/lir_03_cse.txt +17 -17
- data/golden/empty_and_null_inputs/expected/lir_04_1_loop_fusion.txt +17 -17
- data/golden/empty_and_null_inputs/expected/lir_04_loop_invcm.txt +17 -17
- data/golden/empty_and_null_inputs/expected/lir_06_const_prop.txt +17 -17
- data/golden/empty_and_null_inputs/expected/nast.txt +3 -3
- data/golden/empty_and_null_inputs/expected/schema_javascript.mjs +13 -13
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +18 -18
- data/golden/function_overload/expected/ast.txt +29 -0
- data/golden/function_overload/expected/input_plan.txt +4 -0
- data/golden/function_overload/expected/lir_00_unoptimized.txt +18 -0
- data/golden/function_overload/expected/lir_01_hoist_scalar_references.txt +18 -0
- data/golden/function_overload/expected/lir_02_inlined.txt +20 -0
- data/golden/function_overload/expected/lir_03_cse.txt +20 -0
- data/golden/function_overload/expected/lir_04_1_loop_fusion.txt +20 -0
- data/golden/function_overload/expected/lir_04_loop_invcm.txt +20 -0
- data/golden/function_overload/expected/lir_06_const_prop.txt +20 -0
- data/golden/function_overload/expected/nast.txt +22 -0
- data/golden/function_overload/expected/schema_javascript.mjs +12 -0
- data/golden/function_overload/expected/schema_ruby.rb +39 -0
- data/golden/function_overload/expected/snast.txt +22 -0
- data/golden/function_overload/input.json +8 -0
- data/golden/function_overload/schema.kumi +19 -0
- data/golden/game_of_life/expected/lir_00_unoptimized.txt +4 -4
- data/golden/game_of_life/expected/lir_01_hoist_scalar_references.txt +4 -4
- data/golden/game_of_life/expected/lir_02_inlined.txt +1294 -1294
- data/golden/game_of_life/expected/lir_03_cse.txt +403 -399
- data/golden/game_of_life/expected/lir_04_1_loop_fusion.txt +403 -399
- data/golden/game_of_life/expected/lir_04_loop_invcm.txt +403 -399
- data/golden/game_of_life/expected/lir_06_const_prop.txt +403 -399
- data/golden/game_of_life/expected/nast.txt +4 -4
- data/golden/game_of_life/expected/schema_javascript.mjs +87 -85
- data/golden/game_of_life/expected/schema_ruby.rb +88 -86
- data/golden/game_of_life/expected/snast.txt +10 -10
- data/golden/hash_keys/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/nast.txt +1 -1
- data/golden/hash_value/expected/schema_ruby.rb +1 -1
- data/golden/hash_value/expected/snast.txt +1 -1
- data/golden/hierarchical_complex/expected/lir_02_inlined.txt +15 -15
- data/golden/hierarchical_complex/expected/lir_03_cse.txt +1 -1
- data/golden/hierarchical_complex/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/hierarchical_complex/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/hierarchical_complex/expected/lir_06_const_prop.txt +1 -1
- data/golden/hierarchical_complex/expected/nast.txt +3 -3
- data/golden/hierarchical_complex/expected/schema_javascript.mjs +1 -1
- data/golden/hierarchical_complex/expected/schema_ruby.rb +2 -2
- data/golden/hierarchical_complex/expected/snast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/nast.txt +3 -3
- data/golden/inline_rename_scope_leak/expected/schema_ruby.rb +1 -1
- data/golden/input_reference/expected/nast.txt +2 -2
- data/golden/input_reference/expected/schema_ruby.rb +1 -1
- data/golden/interleaved_fusion/expected/lir_02_inlined.txt +35 -35
- data/golden/interleaved_fusion/expected/lir_03_cse.txt +26 -26
- data/golden/interleaved_fusion/expected/lir_04_1_loop_fusion.txt +27 -26
- data/golden/interleaved_fusion/expected/lir_04_loop_invcm.txt +26 -26
- data/golden/interleaved_fusion/expected/lir_06_const_prop.txt +26 -26
- data/golden/interleaved_fusion/expected/nast.txt +2 -2
- data/golden/interleaved_fusion/expected/schema_javascript.mjs +23 -23
- data/golden/interleaved_fusion/expected/schema_ruby.rb +29 -29
- data/golden/let_inline/expected/nast.txt +4 -4
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/loop_fusion/expected/lir_02_inlined.txt +17 -17
- data/golden/loop_fusion/expected/lir_03_cse.txt +14 -14
- data/golden/loop_fusion/expected/lir_04_1_loop_fusion.txt +14 -14
- data/golden/loop_fusion/expected/lir_04_loop_invcm.txt +14 -14
- data/golden/loop_fusion/expected/lir_06_const_prop.txt +14 -14
- data/golden/loop_fusion/expected/nast.txt +1 -1
- data/golden/loop_fusion/expected/schema_javascript.mjs +12 -12
- data/golden/loop_fusion/expected/schema_ruby.rb +16 -16
- data/golden/min_reduce_scope/expected/nast.txt +3 -3
- data/golden/min_reduce_scope/expected/schema_ruby.rb +1 -1
- data/golden/min_reduce_scope/expected/snast.txt +1 -1
- data/golden/mixed_dimensions/expected/lir_02_inlined.txt +5 -5
- data/golden/mixed_dimensions/expected/lir_03_cse.txt +5 -5
- data/golden/mixed_dimensions/expected/lir_04_1_loop_fusion.txt +5 -5
- data/golden/mixed_dimensions/expected/lir_04_loop_invcm.txt +5 -5
- data/golden/mixed_dimensions/expected/lir_06_const_prop.txt +5 -5
- data/golden/mixed_dimensions/expected/nast.txt +2 -2
- data/golden/mixed_dimensions/expected/schema_javascript.mjs +3 -3
- data/golden/mixed_dimensions/expected/schema_ruby.rb +6 -6
- data/golden/multirank_hoisting/expected/lir_02_inlined.txt +48 -48
- data/golden/multirank_hoisting/expected/lir_03_cse.txt +35 -35
- data/golden/multirank_hoisting/expected/lir_04_1_loop_fusion.txt +35 -35
- data/golden/multirank_hoisting/expected/lir_04_loop_invcm.txt +35 -35
- data/golden/multirank_hoisting/expected/lir_06_const_prop.txt +35 -35
- data/golden/multirank_hoisting/expected/nast.txt +7 -7
- data/golden/multirank_hoisting/expected/schema_javascript.mjs +34 -34
- data/golden/multirank_hoisting/expected/schema_ruby.rb +36 -36
- data/golden/nested_hash/expected/nast.txt +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/reduction_broadcast/expected/lir_02_inlined.txt +30 -30
- data/golden/reduction_broadcast/expected/lir_03_cse.txt +22 -22
- data/golden/reduction_broadcast/expected/lir_04_1_loop_fusion.txt +22 -22
- data/golden/reduction_broadcast/expected/lir_04_loop_invcm.txt +22 -22
- data/golden/reduction_broadcast/expected/lir_06_const_prop.txt +22 -22
- data/golden/reduction_broadcast/expected/nast.txt +3 -3
- data/golden/reduction_broadcast/expected/schema_javascript.mjs +18 -18
- data/golden/reduction_broadcast/expected/schema_ruby.rb +23 -23
- data/golden/reduction_broadcast/expected/snast.txt +1 -1
- data/golden/roll/expected/schema_ruby.rb +1 -1
- data/golden/shift/expected/schema_ruby.rb +1 -1
- data/golden/shift_2d/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/lir_00_unoptimized.txt +1 -1
- data/golden/simple_math/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/simple_math/expected/lir_02_inlined.txt +1 -1
- data/golden/simple_math/expected/lir_03_cse.txt +1 -1
- data/golden/simple_math/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/simple_math/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/simple_math/expected/lir_06_const_prop.txt +1 -1
- data/golden/simple_math/expected/nast.txt +5 -5
- data/golden/simple_math/expected/schema_ruby.rb +1 -1
- data/golden/simple_math/expected/snast.txt +2 -2
- data/golden/streaming_basics/expected/lir_02_inlined.txt +25 -25
- data/golden/streaming_basics/expected/lir_03_cse.txt +13 -13
- data/golden/streaming_basics/expected/lir_04_1_loop_fusion.txt +13 -13
- data/golden/streaming_basics/expected/lir_04_loop_invcm.txt +13 -13
- data/golden/streaming_basics/expected/lir_06_const_prop.txt +13 -13
- data/golden/streaming_basics/expected/nast.txt +8 -8
- data/golden/streaming_basics/expected/schema_javascript.mjs +13 -13
- data/golden/streaming_basics/expected/schema_ruby.rb +14 -14
- data/golden/streaming_basics/expected/snast.txt +1 -1
- data/golden/tuples/expected/lir_00_unoptimized.txt +5 -5
- data/golden/tuples/expected/lir_01_hoist_scalar_references.txt +5 -5
- data/golden/tuples/expected/lir_02_inlined.txt +5 -5
- data/golden/tuples/expected/lir_03_cse.txt +5 -5
- data/golden/tuples/expected/lir_04_1_loop_fusion.txt +5 -5
- data/golden/tuples/expected/lir_04_loop_invcm.txt +5 -5
- data/golden/tuples/expected/lir_06_const_prop.txt +5 -5
- data/golden/tuples/expected/nast.txt +4 -4
- data/golden/tuples/expected/schema_ruby.rb +1 -1
- data/golden/tuples/expected/snast.txt +6 -6
- data/golden/tuples_and_arrays/expected/lir_00_unoptimized.txt +1 -1
- data/golden/tuples_and_arrays/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/tuples_and_arrays/expected/lir_02_inlined.txt +17 -17
- data/golden/tuples_and_arrays/expected/lir_03_cse.txt +13 -13
- data/golden/tuples_and_arrays/expected/lir_04_1_loop_fusion.txt +13 -13
- data/golden/tuples_and_arrays/expected/lir_04_loop_invcm.txt +13 -13
- data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +13 -13
- data/golden/tuples_and_arrays/expected/nast.txt +3 -3
- data/golden/tuples_and_arrays/expected/schema_javascript.mjs +13 -13
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +14 -14
- data/golden/tuples_and_arrays/expected/snast.txt +2 -2
- data/golden/us_tax_2024/expected/ast.txt +63 -670
- data/golden/us_tax_2024/expected/input_plan.txt +8 -45
- data/golden/us_tax_2024/expected/lir_00_unoptimized.txt +253 -863
- data/golden/us_tax_2024/expected/lir_01_hoist_scalar_references.txt +253 -863
- data/golden/us_tax_2024/expected/lir_02_inlined.txt +1215 -5139
- data/golden/us_tax_2024/expected/lir_03_cse.txt +587 -2460
- data/golden/us_tax_2024/expected/lir_04_1_loop_fusion.txt +632 -2480
- data/golden/us_tax_2024/expected/lir_04_loop_invcm.txt +587 -2400
- data/golden/us_tax_2024/expected/lir_06_const_prop.txt +587 -2400
- data/golden/us_tax_2024/expected/nast.txt +123 -826
- data/golden/us_tax_2024/expected/schema_javascript.mjs +127 -581
- data/golden/us_tax_2024/expected/schema_ruby.rb +135 -610
- data/golden/us_tax_2024/expected/snast.txt +155 -858
- data/golden/us_tax_2024/expected.json +120 -1
- data/golden/us_tax_2024/input.json +18 -9
- data/golden/us_tax_2024/schema.kumi +48 -178
- data/golden/with_constants/expected/lir_00_unoptimized.txt +1 -1
- data/golden/with_constants/expected/lir_01_hoist_scalar_references.txt +1 -1
- data/golden/with_constants/expected/lir_02_inlined.txt +1 -1
- data/golden/with_constants/expected/lir_03_cse.txt +1 -1
- data/golden/with_constants/expected/lir_04_1_loop_fusion.txt +1 -1
- data/golden/with_constants/expected/lir_04_loop_invcm.txt +1 -1
- data/golden/with_constants/expected/lir_06_const_prop.txt +1 -1
- data/golden/with_constants/expected/nast.txt +2 -2
- data/golden/with_constants/expected/schema_ruby.rb +1 -1
- data/golden/with_constants/expected/snast.txt +2 -2
- data/lib/kumi/analyzer.rb +12 -12
- data/lib/kumi/core/analyzer/passes/formal_constraint_propagator.rb +236 -0
- data/lib/kumi/core/analyzer/passes/input_collector.rb +22 -4
- data/lib/kumi/core/analyzer/passes/lir/inline_declarations_pass.rb +118 -74
- data/lib/kumi/core/analyzer/passes/nast_dimensional_analyzer_pass.rb +64 -18
- data/lib/kumi/core/analyzer/passes/normalize_to_nast_pass.rb +9 -4
- data/lib/kumi/core/analyzer/passes/snast_pass.rb +3 -1
- data/lib/kumi/core/analyzer/passes/unsat_detector.rb +172 -198
- data/lib/kumi/core/error_reporter.rb +36 -1
- data/lib/kumi/core/errors.rb +33 -1
- data/lib/kumi/core/functions/function_spec.rb +5 -4
- data/lib/kumi/core/functions/loader.rb +17 -1
- data/lib/kumi/core/functions/overload_resolver.rb +164 -0
- data/lib/kumi/core/functions/type_error_reporter.rb +118 -0
- data/lib/kumi/core/functions/type_rules.rb +155 -35
- data/lib/kumi/core/types/inference.rb +29 -22
- data/lib/kumi/core/types/normalizer.rb +29 -45
- data/lib/kumi/core/types/validator.rb +16 -27
- data/lib/kumi/core/types/value_objects.rb +116 -0
- data/lib/kumi/core/types.rb +45 -37
- data/lib/kumi/registry_v2/loader.rb +90 -0
- data/lib/kumi/registry_v2.rb +18 -1
- data/lib/kumi/version.rb +1 -1
- metadata +21 -7
- data/lib/kumi/core/analyzer/unsat_constant_evaluator.rb +0 -59
- data/lib/kumi/core/atom_unsat_solver.rb +0 -396
- data/lib/kumi/core/constraint_relationship_solver.rb +0 -641
- data/lib/kumi/core/types/builder.rb +0 -23
- data/lib/kumi/core/types/compatibility.rb +0 -96
- data/lib/kumi/core/types/formatter.rb +0 -26
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Analyzer
|
6
|
+
module Passes
|
7
|
+
# RESPONSIBILITY: Propagate constraints forward and backward through operations
|
8
|
+
# DEPENDENCIES: :registry (function registry), constraint metadata from function specs
|
9
|
+
# INTERFACE: propagate_forward(constraint), propagate_reverse_through_operation(...)
|
10
|
+
#
|
11
|
+
# Implements formal constraint propagation rules based on function semantics.
|
12
|
+
# Forward: x == 5, y = x + 10 => y == 15
|
13
|
+
# Reverse: y == 15, y = x + 10 => x == 5
|
14
|
+
class FormalConstraintPropagator
|
15
|
+
def initialize(schema, state)
|
16
|
+
@schema = schema
|
17
|
+
@state = state
|
18
|
+
@registry = state[:registry]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Forward propagate a constraint through a single operation
|
22
|
+
def propagate_forward_through_operation(constraint, operation_spec, operand_map)
|
23
|
+
case constraint[:op]
|
24
|
+
when :==
|
25
|
+
propagate_equality_forward(constraint, operation_spec, operand_map)
|
26
|
+
when :range
|
27
|
+
propagate_range_forward(constraint, operation_spec, operand_map)
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reverse propagate: derive input constraints from output constraints
|
34
|
+
def propagate_reverse_through_operation(constraint, operation_spec, operand_map)
|
35
|
+
case constraint[:op]
|
36
|
+
when :==
|
37
|
+
propagate_equality_reverse(constraint, operation_spec, operand_map)
|
38
|
+
when :range
|
39
|
+
propagate_range_reverse(constraint, operation_spec, operand_map)
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# FORWARD PROPAGATION: Compute output value from input constraints
|
48
|
+
def propagate_equality_forward(constraint, operation_spec, operand_map)
|
49
|
+
result_var = operand_map[:result]
|
50
|
+
input_var = constraint[:variable]
|
51
|
+
input_value = constraint[:value]
|
52
|
+
|
53
|
+
case operation_spec.id
|
54
|
+
when "core.add"
|
55
|
+
# x == V, result = x + C => result == V + C
|
56
|
+
other_operand = get_other_operand_value(constraint, operand_map, "add")
|
57
|
+
return nil unless other_operand.is_a?(Numeric)
|
58
|
+
|
59
|
+
output_value = input_value + other_operand
|
60
|
+
{ variable: result_var, op: :==, value: output_value }
|
61
|
+
|
62
|
+
when "core.mul"
|
63
|
+
# x == V, result = x * C => result == V * C
|
64
|
+
other_operand = get_other_operand_value(constraint, operand_map, "mul")
|
65
|
+
return nil unless other_operand.is_a?(Numeric)
|
66
|
+
|
67
|
+
output_value = input_value * other_operand
|
68
|
+
{ variable: result_var, op: :==, value: output_value }
|
69
|
+
|
70
|
+
when "core.sub"
|
71
|
+
# x == V, result = x - C => result == V - C
|
72
|
+
other_operand = get_other_operand_value(constraint, operand_map, "sub")
|
73
|
+
return nil unless other_operand.is_a?(Numeric)
|
74
|
+
|
75
|
+
output_value = input_value - other_operand
|
76
|
+
{ variable: result_var, op: :==, value: output_value }
|
77
|
+
|
78
|
+
else
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# FORWARD PROPAGATION: Compute output range from input range
|
84
|
+
def propagate_range_forward(constraint, operation_spec, operand_map)
|
85
|
+
result_var = operand_map[:result]
|
86
|
+
input_min = constraint[:min]
|
87
|
+
input_max = constraint[:max]
|
88
|
+
|
89
|
+
case operation_spec.id
|
90
|
+
when "core.add"
|
91
|
+
# x in [min, max], result = x + C => result in [min + C, max + C]
|
92
|
+
other = get_other_operand_value(constraint, operand_map, "add")
|
93
|
+
return nil unless other.is_a?(Numeric)
|
94
|
+
|
95
|
+
output_min = input_min + other
|
96
|
+
output_max = input_max + other
|
97
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
98
|
+
|
99
|
+
when "core.mul"
|
100
|
+
# x in [min, max], result = x * C => depends on sign of C
|
101
|
+
other = get_other_operand_value(constraint, operand_map, "mul")
|
102
|
+
return nil unless other.is_a?(Numeric)
|
103
|
+
|
104
|
+
if other > 0
|
105
|
+
output_min = input_min * other
|
106
|
+
output_max = input_max * other
|
107
|
+
elsif other < 0
|
108
|
+
output_min = input_max * other
|
109
|
+
output_max = input_min * other
|
110
|
+
else
|
111
|
+
output_min = 0
|
112
|
+
output_max = 0
|
113
|
+
end
|
114
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
115
|
+
|
116
|
+
when "core.sub"
|
117
|
+
# x in [min, max], result = x - C => result in [min - C, max - C]
|
118
|
+
other = get_other_operand_value(constraint, operand_map, "sub")
|
119
|
+
return nil unless other.is_a?(Numeric)
|
120
|
+
|
121
|
+
output_min = input_min - other
|
122
|
+
output_max = input_max - other
|
123
|
+
{ variable: result_var, op: :range, min: output_min, max: output_max }
|
124
|
+
|
125
|
+
else
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# REVERSE PROPAGATION: Derive input equality from output equality
|
131
|
+
def propagate_equality_reverse(constraint, operation_spec, operand_map)
|
132
|
+
result_var = constraint[:variable]
|
133
|
+
result_value = constraint[:value]
|
134
|
+
left_var = operand_map[:left_operand]
|
135
|
+
right_var = operand_map[:right_operand]
|
136
|
+
|
137
|
+
case operation_spec.id
|
138
|
+
when "core.add"
|
139
|
+
# result == V, result = x + C => x == V - C
|
140
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
141
|
+
{ variable: left_var, op: :==, value: result_value - right_var }
|
142
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
143
|
+
{ variable: right_var, op: :==, value: result_value - left_var }
|
144
|
+
else
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
when "core.mul"
|
149
|
+
# result == V, result = x * C => x == V / C (if C != 0)
|
150
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric) && right_var != 0
|
151
|
+
return nil unless (result_value % right_var).zero?
|
152
|
+
{ variable: left_var, op: :==, value: result_value / right_var }
|
153
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric) && left_var != 0
|
154
|
+
return nil unless (result_value % left_var).zero?
|
155
|
+
{ variable: right_var, op: :==, value: result_value / left_var }
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
|
160
|
+
when "core.sub"
|
161
|
+
# result == V, result = x - C => x == V + C
|
162
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
163
|
+
{ variable: left_var, op: :==, value: result_value + right_var }
|
164
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
165
|
+
{ variable: right_var, op: :==, value: left_var - result_value }
|
166
|
+
else
|
167
|
+
nil
|
168
|
+
end
|
169
|
+
|
170
|
+
else
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# REVERSE PROPAGATION: Derive input range from output range
|
176
|
+
def propagate_range_reverse(constraint, operation_spec, operand_map)
|
177
|
+
result_min = constraint[:min]
|
178
|
+
result_max = constraint[:max]
|
179
|
+
left_var = operand_map[:left_operand]
|
180
|
+
right_var = operand_map[:right_operand]
|
181
|
+
|
182
|
+
case operation_spec.id
|
183
|
+
when "core.add"
|
184
|
+
# result in [min, max], result = x + C => x in [min - C, max - C]
|
185
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
186
|
+
return { variable: left_var, op: :range, min: result_min - right_var, max: result_max - right_var }
|
187
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
188
|
+
return { variable: right_var, op: :range, min: result_min - left_var, max: result_max - left_var }
|
189
|
+
end
|
190
|
+
|
191
|
+
when "core.mul"
|
192
|
+
# result in [min, max], result = x * C => x in [min/C, max/C] (depends on sign)
|
193
|
+
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
|
199
|
+
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
|
205
|
+
end
|
206
|
+
|
207
|
+
when "core.sub"
|
208
|
+
# result in [min, max], result = x - C => x in [min + C, max + C]
|
209
|
+
if left_var.is_a?(Symbol) && right_var.is_a?(Numeric)
|
210
|
+
return { variable: left_var, op: :range, min: result_min + right_var, max: result_max + right_var }
|
211
|
+
elsif right_var.is_a?(Symbol) && left_var.is_a?(Numeric)
|
212
|
+
return { variable: right_var, op: :range, min: left_var - result_max, max: left_var - result_min }
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
nil
|
217
|
+
end
|
218
|
+
|
219
|
+
def get_other_operand_value(constraint, operand_map, operation)
|
220
|
+
input_var = constraint[:variable]
|
221
|
+
left_var = operand_map[:left_operand] || operand_map.values[0]
|
222
|
+
right_var = operand_map[:right_operand] || operand_map.values[1]
|
223
|
+
|
224
|
+
if input_var == left_var && right_var.is_a?(Numeric)
|
225
|
+
right_var
|
226
|
+
elsif input_var == right_var && left_var.is_a?(Numeric)
|
227
|
+
left_var
|
228
|
+
else
|
229
|
+
nil
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
@@ -103,10 +103,28 @@ module Kumi
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def kind_from_type(t)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
# Handle both symbols (legacy) and Type objects (new)
|
107
|
+
case t
|
108
|
+
when Kumi::Core::Types::ArrayType
|
109
|
+
:array
|
110
|
+
when Kumi::Core::Types::TupleType
|
111
|
+
:array # Tuples behave like arrays for input access
|
112
|
+
when :array, Kumi::Core::Types::ScalarType
|
113
|
+
# Check if it's a hash scalar or :hash symbol
|
114
|
+
if t.is_a?(Kumi::Core::Types::ScalarType) && t.kind == :hash
|
115
|
+
:hash
|
116
|
+
elsif t == :hash
|
117
|
+
:hash
|
118
|
+
elsif t == :array
|
119
|
+
:array
|
120
|
+
else
|
121
|
+
:scalar
|
122
|
+
end
|
123
|
+
when :hash
|
124
|
+
:hash
|
125
|
+
else
|
126
|
+
:scalar
|
127
|
+
end
|
110
128
|
end
|
111
129
|
end
|
112
130
|
end
|
@@ -41,22 +41,27 @@ module Kumi
|
|
41
41
|
[fused, changed]
|
42
42
|
end
|
43
43
|
|
44
|
+
# ---------------- core ----------------
|
45
|
+
|
46
|
+
Hoist = Struct.new(:ops, :target_depth, keyword_init: true)
|
47
|
+
|
44
48
|
def inline_top_level_decl(ops)
|
45
49
|
env = Env.new
|
46
50
|
reg_map = {}
|
47
51
|
rename_map = {}
|
48
|
-
processed,
|
49
|
-
raise "Orphaned code was hoisted to top level" unless hoisted.empty?
|
52
|
+
processed, hoist_pkgs = process_and_hoist_block(ops, env, reg_map, rename_map)
|
50
53
|
|
51
|
-
|
52
|
-
|
54
|
+
top_emit, bubble = hoist_pkgs.partition { |p| p.target_depth == 0 }
|
55
|
+
raise "Orphaned code hoist with target depth(s): #{bubble.map(&:target_depth).uniq.inspect}" unless bubble.empty?
|
53
56
|
|
54
|
-
|
57
|
+
top_emit.flat_map(&:ops) + processed
|
58
|
+
end
|
55
59
|
|
56
|
-
# returns [processed_ops,
|
60
|
+
# returns [processed_ops, hoist_pkgs]
|
61
|
+
# returns [processed_ops, hoist_pkgs]
|
57
62
|
def process_and_hoist_block(block_ops, env, reg_map, rename_map)
|
58
63
|
out = []
|
59
|
-
|
64
|
+
hoisted_pkgs = []
|
60
65
|
i = 0
|
61
66
|
while i < block_ops.length
|
62
67
|
ins = block_ops[i]
|
@@ -67,110 +72,144 @@ module Kumi
|
|
67
72
|
|
68
73
|
env.push(ins)
|
69
74
|
child_rename = {}
|
70
|
-
processed_body,
|
75
|
+
processed_body, child_hoists =
|
71
76
|
process_and_hoist_block(loop_body, env, reg_map, child_rename)
|
77
|
+
|
78
|
+
depth_here = env.axes.length
|
79
|
+
child_el = ins.attributes[:as_element]
|
80
|
+
child_ix = ins.attributes[:as_index]
|
81
|
+
|
82
|
+
# Partition hoists: those that belong *inside* this loop vs bubble upward
|
83
|
+
inside_pkgs, bubble_pkgs = child_hoists.partition { |p| p.target_depth == depth_here }
|
84
|
+
|
85
|
+
# Renames: never let aliases to this loop's el/idx escape upward
|
86
|
+
safe_pairs = child_rename.reject { |_, v| v == child_el || v == child_ix }
|
72
87
|
env.pop
|
73
88
|
|
74
|
-
|
75
|
-
rename_map.merge!(
|
89
|
+
# Merge only safe renames into outer scope
|
90
|
+
rename_map.merge!(safe_pairs)
|
91
|
+
|
92
|
+
# Emit loop shell
|
93
|
+
out << rewrite(ins, reg_map, rename_map)
|
94
|
+
|
95
|
+
# Local view inside loop: apply both local and outer renames, with local taking precedence
|
96
|
+
local_map = child_rename.merge(rename_map)
|
97
|
+
|
98
|
+
# Emit hoists that belong at this depth *inside* the loop, before the body
|
99
|
+
inside_ops = inside_pkgs.flat_map(&:ops)
|
100
|
+
out.concat(rewrite_block(inside_ops, local_map))
|
76
101
|
|
77
|
-
|
78
|
-
out.concat(processed_body)
|
102
|
+
# Emit rewritten body
|
103
|
+
out.concat(rewrite_block(processed_body, local_map))
|
104
|
+
|
105
|
+
# Close loop
|
79
106
|
out << rewrite(block_ops[end_idx], reg_map, rename_map)
|
80
107
|
|
108
|
+
# Bubble remaining hoists to outer scopes
|
109
|
+
hoisted_pkgs.concat(bubble_pkgs)
|
110
|
+
|
111
|
+
# Extra safety: ensure no aliases to this loop's el/idx remain in outer map
|
112
|
+
rename_map.delete_if { |_, v| v == child_el || v == child_ix }
|
113
|
+
|
81
114
|
i = end_idx
|
82
115
|
|
83
116
|
when :LoadDeclaration
|
84
|
-
inline_ops,
|
85
|
-
handle_load_declaration(ins, env, reg_map, rename_map)
|
117
|
+
inline_ops, new_pkgs = handle_load_declaration(ins, env, reg_map, rename_map)
|
86
118
|
out.concat(inline_ops)
|
87
|
-
|
119
|
+
hoisted_pkgs.concat(new_pkgs)
|
88
120
|
|
89
121
|
else
|
90
122
|
out << rewrite(ins, reg_map, rename_map)
|
91
123
|
end
|
92
124
|
i += 1
|
93
125
|
end
|
94
|
-
[out,
|
126
|
+
[out, hoisted_pkgs]
|
95
127
|
end
|
96
128
|
|
97
|
-
# returns [inline_ops,
|
98
|
-
def handle_load_declaration(
|
99
|
-
|
100
|
-
|
101
|
-
callee = call_ins.immediates.first.value.to_sym
|
102
|
-
raise "LoadDeclaration callee #{callee} not found" unless @ops_by_decl.key?(callee)
|
129
|
+
# returns [inline_ops, hoist_pkgs]
|
130
|
+
def handle_load_declaration(ins, env, _reg_map, rename_map)
|
131
|
+
callee = ins.immediates.first.value.to_sym
|
103
132
|
|
104
|
-
|
105
|
-
|
133
|
+
# axes presence and agreement with callee gamma
|
134
|
+
decl_axes = ins.attributes.fetch(:axes) { raise "LoadDeclaration missing :axes for #{callee}" }
|
135
|
+
gamma_axes = @gamma.fetch(callee).axes
|
136
|
+
raise "axes mismatch for #{callee}: decl=#{decl_axes.inspect} gamma=#{gamma_axes.inspect}" unless decl_axes == gamma_axes
|
106
137
|
|
107
|
-
body, yield_reg,
|
108
|
-
|
138
|
+
body, yield_reg, callee_regs = inline_callee_core(callee)
|
139
|
+
remap = remap_axes(callee_regs, env)
|
109
140
|
|
141
|
+
# per-callsite freshening
|
110
142
|
local_reg_map = {}
|
111
|
-
_acc, fresh_ops = freshen(body, local_reg_map, pre_map:
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
" hoisted defs size: #{defs_hoisted.size}"
|
131
|
-
].join("\n")
|
132
|
-
raise msg
|
143
|
+
_acc, fresh_ops = freshen(body, local_reg_map, pre_map: remap)
|
144
|
+
|
145
|
+
# recursively process nested calls
|
146
|
+
processed_inline, nested_pkgs = process_and_hoist_block(fresh_ops, env, {}, rename_map)
|
147
|
+
|
148
|
+
# compute yielded register mapping, then resolve through any renames created by nested inlines
|
149
|
+
mapped_yield =
|
150
|
+
local_reg_map[yield_reg] || remap[yield_reg] ||
|
151
|
+
(raise "inliner: yielded reg #{yield_reg} not produced in inlined body for #{callee}")
|
152
|
+
resolved_yield = resolve_rename(mapped_yield, rename_map)
|
153
|
+
|
154
|
+
# sanity: resolved_yield must be definable at site
|
155
|
+
emitted_defs = processed_inline.map(&:result_register).compact +
|
156
|
+
nested_pkgs.flat_map { |p| p.ops }.map(&:result_register).compact
|
157
|
+
unless emitted_defs.include?(resolved_yield) || env.ambient_regs.include?(resolved_yield)
|
158
|
+
raise "inliner: mapped yield #{resolved_yield} has no def in emitted ops for #{callee}\n" \
|
159
|
+
"original yield: #{yield_reg}\n" \
|
160
|
+
"inline defs size: #{processed_inline.count { |x| x.result_register }}\n" \
|
161
|
+
"nested hoist defs size: #{nested_pkgs.flat_map { |p| p.ops }.count { |x| x.result_register }}"
|
133
162
|
end
|
134
163
|
|
135
|
-
|
164
|
+
# final rename for call site result uses the resolved register
|
165
|
+
rename_map[ins.result_register] = resolved_yield
|
166
|
+
|
167
|
+
# decide placement by depth
|
168
|
+
site_depth = env.axes.length
|
169
|
+
callee_depth = decl_axes.length
|
170
|
+
|
171
|
+
if callee_depth < site_depth
|
172
|
+
forb = forbidden_ambient_after(callee_depth, env)
|
173
|
+
used = uses_of(processed_inline)
|
174
|
+
bad = used & forb
|
175
|
+
unless bad.empty?
|
176
|
+
raise "scope error: would hoist ops using deeper-axis regs #{bad.inspect} " \
|
177
|
+
"(callee_depth=#{callee_depth}, site_depth=#{site_depth})"
|
178
|
+
end
|
179
|
+
pkgs = nested_pkgs + [Hoist.new(ops: processed_inline, target_depth: callee_depth)]
|
180
|
+
[[], pkgs]
|
181
|
+
|
182
|
+
elsif callee_depth == site_depth
|
183
|
+
emit, bubble = nested_pkgs.partition { |p| p.target_depth == site_depth }
|
184
|
+
[(emit.flat_map(&:ops) + processed_inline), bubble]
|
136
185
|
|
137
|
-
if prefix?(decl_axes, site_axes) && decl_axes.length < site_axes.length
|
138
|
-
[[], hoisted_inner + processed_inner] # hoist
|
139
|
-
elsif decl_axes == site_axes
|
140
|
-
[hoisted_inner + processed_inner, []] # inline in place
|
141
186
|
else
|
142
|
-
[[rewrite(
|
187
|
+
[[rewrite(ins, {}, rename_map)], []]
|
143
188
|
end
|
144
189
|
end
|
145
190
|
|
146
191
|
# ---------------- helpers ----------------
|
192
|
+
def rewrite_block(ops, rename)
|
193
|
+
# Ensure late-added renames apply to a block we built earlier.
|
194
|
+
ops.map { |ins| rewrite(ins, {}, rename) }
|
195
|
+
end
|
147
196
|
|
148
|
-
def resolve_rename(reg, rename
|
197
|
+
def resolve_rename(reg, rename)
|
149
198
|
seen = {}
|
150
199
|
cur = reg
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
raise "inliner: rename cycle at #{cur}" if seen[nxt]
|
155
|
-
|
156
|
-
seen[nxt] = true
|
157
|
-
cur = nxt
|
200
|
+
while (n = rename[cur]) && !seen[n]
|
201
|
+
seen[cur] = true
|
202
|
+
cur = n
|
158
203
|
end
|
159
204
|
cur
|
160
205
|
end
|
161
206
|
|
162
|
-
def
|
163
|
-
ops.
|
207
|
+
def uses_of(ops)
|
208
|
+
ops.flat_map { |x| Array(x.inputs) }.compact
|
164
209
|
end
|
165
210
|
|
166
|
-
def
|
167
|
-
|
168
|
-
gamma_axes = Array(@gamma.fetch(callee)&.axes || [])
|
169
|
-
ax = attr_axes.nil? ? gamma_axes : Array(attr_axes)
|
170
|
-
raise "LoadDeclaration missing :axes" if ax.nil?
|
171
|
-
raise "inliner: non-array axes for #{callee}: #{ax.inspect}" unless ax.is_a?(Array)
|
172
|
-
|
173
|
-
ax
|
211
|
+
def forbidden_ambient_after(depth, env)
|
212
|
+
env.frames_after(depth).flat_map { |f| [f[:el], f[:idx]] }
|
174
213
|
end
|
175
214
|
|
176
215
|
def find_matching_loop_end(ops, start_index)
|
@@ -195,6 +234,7 @@ module Kumi
|
|
195
234
|
class Env
|
196
235
|
def initialize = @frames = []
|
197
236
|
def axes = @frames.map { _1[:axis] }
|
237
|
+
def ambient_regs = @frames.flat_map { |f| [f[:el], f[:idx]] }
|
198
238
|
|
199
239
|
def push(loop_ins)
|
200
240
|
@frames << {
|
@@ -210,6 +250,10 @@ module Kumi
|
|
210
250
|
@frames.reverse.find { _1[:axis] == axis } ||
|
211
251
|
raise("no element for axis=#{axis.inspect}")
|
212
252
|
end
|
253
|
+
|
254
|
+
def frames_after(depth)
|
255
|
+
@frames[depth..] || []
|
256
|
+
end
|
213
257
|
end
|
214
258
|
|
215
259
|
def detect_all_gammas(ops_by_decl)
|
@@ -240,10 +284,10 @@ module Kumi
|
|
240
284
|
end
|
241
285
|
|
242
286
|
def inline_callee_core(callee_name)
|
243
|
-
ops
|
287
|
+
ops = Array(@ops_by_decl.fetch(callee_name)[:operations])
|
244
288
|
info = @gamma.fetch(callee_name)
|
245
289
|
axes = info.axes
|
246
|
-
k
|
290
|
+
k = axes.length
|
247
291
|
|
248
292
|
yi = ops.rindex { |x| x.opcode == :Yield } or raise "callee #{callee_name} has no Yield"
|
249
293
|
yielded_reg = Array(ops[yi].inputs).first
|
@@ -68,18 +68,61 @@ module Kumi
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def analyze_call_expression(call, errors)
|
71
|
-
|
72
|
-
|
71
|
+
# Step 1: Analyze arguments to get their types and scopes
|
73
72
|
arg_metadata = call.args.map { |arg| analyze_expression(arg, errors) }
|
74
73
|
arg_types = arg_metadata.map { |m| m[:type] }
|
75
74
|
arg_scopes = arg_metadata.map { |m| m[:scope] }
|
76
75
|
|
77
|
-
|
76
|
+
# Ensure all arg_types are Type objects (defensive programming)
|
77
|
+
arg_types = arg_types.map do |t|
|
78
|
+
case t
|
79
|
+
when Types::Type
|
80
|
+
t
|
81
|
+
when :array
|
82
|
+
# :array is actually an ArrayType marker, not a scalar kind
|
83
|
+
Types.array(Types.scalar(:any))
|
84
|
+
when :hash
|
85
|
+
Types.scalar(:hash)
|
86
|
+
when Symbol
|
87
|
+
# Try to normalize as scalar kind
|
88
|
+
Types.normalize(t)
|
89
|
+
else
|
90
|
+
# Already a Type object or unknown format
|
91
|
+
t
|
92
|
+
end
|
93
|
+
end
|
78
94
|
|
79
|
-
|
80
|
-
|
95
|
+
debug " Call #{call.fn}: arg_scopes=#{arg_scopes.inspect}, arg_types=#{arg_types.inspect}"
|
96
|
+
|
97
|
+
# Step 2: Resolve function using type-aware overload resolution
|
98
|
+
begin
|
99
|
+
resolved_fn_id = @registry.resolve_function_with_types(call.fn.to_s, arg_types)
|
100
|
+
function_spec = @registry.function(resolved_fn_id)
|
101
|
+
debug " Resolved '#{call.fn}' with types #{arg_types.inspect} to #{resolved_fn_id}"
|
102
|
+
rescue Core::Functions::OverloadResolver::ResolutionError => e
|
103
|
+
# Type-aware overload resolution failed - report with location
|
104
|
+
report_type_error(
|
105
|
+
errors,
|
106
|
+
e.message,
|
107
|
+
location: call.loc,
|
108
|
+
context: {
|
109
|
+
function: call.fn.to_s,
|
110
|
+
arg_types: arg_types
|
111
|
+
}
|
112
|
+
)
|
113
|
+
raise Kumi::Core::Errors::TypeError, e.message
|
114
|
+
rescue StandardError => e
|
115
|
+
# Other function resolution errors
|
116
|
+
report_semantic_error(
|
117
|
+
errors,
|
118
|
+
"Function resolution error for '#{call.fn}': #{e.message}",
|
119
|
+
location: call.loc,
|
120
|
+
context: { function: call.fn.to_s }
|
121
|
+
)
|
122
|
+
raise Kumi::Core::Errors::SemanticError, e.message
|
81
123
|
end
|
82
124
|
|
125
|
+
# Step 3: Compute result type
|
83
126
|
named_types =
|
84
127
|
if function_spec.params.size == arg_types.size
|
85
128
|
Hash[function_spec.param_names.zip(arg_types)]
|
@@ -89,11 +132,17 @@ module Kumi
|
|
89
132
|
|
90
133
|
begin
|
91
134
|
result_type = function_spec.dtype_rule.call(named_types)
|
92
|
-
rescue StandardError
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
135
|
+
rescue StandardError => e
|
136
|
+
report_type_error(
|
137
|
+
errors,
|
138
|
+
"Type rule evaluation failed for #{function_spec.id}: #{e.message}",
|
139
|
+
location: call.loc,
|
140
|
+
context: {
|
141
|
+
function: function_spec.id,
|
142
|
+
arg_types: arg_types
|
143
|
+
}
|
144
|
+
)
|
145
|
+
raise Kumi::Core::Errors::TypeError, "Type rule failed for #{function_spec.id}: #{e.message}"
|
97
146
|
end
|
98
147
|
|
99
148
|
over_collection = arg_types.size == 1 && Types.collection?(arg_types[0])
|
@@ -120,11 +169,8 @@ module Kumi
|
|
120
169
|
element_scopes = elems.map { |m| m[:scope] }
|
121
170
|
result_scope = lub_by_prefix(element_scopes)
|
122
171
|
|
123
|
-
|
124
|
-
|
125
|
-
else
|
126
|
-
"tuple<#{element_types.join(', ')}>"
|
127
|
-
end
|
172
|
+
# Create TupleType from element Types
|
173
|
+
result_type = Types.tuple(element_types)
|
128
174
|
|
129
175
|
@metadata_table[node_id(node)] = {
|
130
176
|
parameter_names: [],
|
@@ -143,7 +189,7 @@ module Kumi
|
|
143
189
|
fields = node.pairs.map { |e| analyze_expression(e, errors) }
|
144
190
|
fields_scopes = fields.map { |m| m[:scope] }
|
145
191
|
scope = lub_by_prefix(fields_scopes)
|
146
|
-
dtype = :hash
|
192
|
+
dtype = Types.scalar(:hash)
|
147
193
|
|
148
194
|
@metadata_table[node_id(node)] = {
|
149
195
|
type: dtype,
|
@@ -153,7 +199,7 @@ module Kumi
|
|
153
199
|
|
154
200
|
def analyze_pair(node, errors)
|
155
201
|
value_node = analyze_expression(node.value, errors)
|
156
|
-
dtype = :pair
|
202
|
+
dtype = Types.scalar(:pair)
|
157
203
|
|
158
204
|
@metadata_table[node_id(node)] = {
|
159
205
|
type: dtype,
|
@@ -182,7 +228,7 @@ module Kumi
|
|
182
228
|
def analyze_index_ref(node, _errors)
|
183
229
|
meta = @input_table.find { _1.path_fqn == node.input_fqn } or raise "Index plan found: #{n.name.inspect}"
|
184
230
|
axes = Array(meta[:axes])
|
185
|
-
type = :integer
|
231
|
+
type = Types.scalar(:integer)
|
186
232
|
|
187
233
|
debug " IndexRef #{node.name}: input_fqn=#{node.input_fqn}, axes=#{axes.inspect}"
|
188
234
|
|