kumi 0.0.25 → 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 +9 -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/schema_ruby.rb +1 -1
- data/golden/cascade_logic/expected/snast.txt +2 -2
- data/golden/chained_fusion/expected/nast.txt +2 -2
- data/golden/chained_fusion/expected/schema_ruby.rb +1 -1
- 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/nast.txt +3 -3
- data/golden/empty_and_null_inputs/expected/schema_ruby.rb +1 -1
- 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 +16 -16
- data/golden/game_of_life/expected/lir_03_cse.txt +20 -16
- data/golden/game_of_life/expected/lir_04_1_loop_fusion.txt +20 -16
- data/golden/game_of_life/expected/lir_04_loop_invcm.txt +20 -16
- data/golden/game_of_life/expected/lir_06_const_prop.txt +20 -16
- data/golden/game_of_life/expected/nast.txt +4 -4
- data/golden/game_of_life/expected/schema_javascript.mjs +4 -2
- data/golden/game_of_life/expected/schema_ruby.rb +5 -3
- 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/nast.txt +3 -3
- data/golden/hierarchical_complex/expected/schema_ruby.rb +1 -1
- 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/nast.txt +2 -2
- data/golden/interleaved_fusion/expected/schema_ruby.rb +1 -1
- data/golden/let_inline/expected/nast.txt +4 -4
- data/golden/let_inline/expected/schema_ruby.rb +1 -1
- data/golden/loop_fusion/expected/nast.txt +1 -1
- data/golden/loop_fusion/expected/schema_ruby.rb +1 -1
- 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/nast.txt +2 -2
- data/golden/mixed_dimensions/expected/schema_ruby.rb +1 -1
- data/golden/multirank_hoisting/expected/nast.txt +7 -7
- data/golden/multirank_hoisting/expected/schema_ruby.rb +1 -1
- data/golden/nested_hash/expected/nast.txt +1 -1
- data/golden/nested_hash/expected/schema_ruby.rb +1 -1
- data/golden/reduction_broadcast/expected/nast.txt +3 -3
- data/golden/reduction_broadcast/expected/schema_ruby.rb +1 -1
- 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/nast.txt +8 -8
- data/golden/streaming_basics/expected/schema_ruby.rb +1 -1
- 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 +2 -2
- data/golden/tuples_and_arrays/expected/lir_03_cse.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_04_1_loop_fusion.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_04_loop_invcm.txt +2 -2
- data/golden/tuples_and_arrays/expected/lir_06_const_prop.txt +2 -2
- data/golden/tuples_and_arrays/expected/nast.txt +3 -3
- data/golden/tuples_and_arrays/expected/schema_ruby.rb +1 -1
- 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 -2460
- data/golden/us_tax_2024/expected/lir_06_const_prop.txt +587 -2460
- 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/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,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Functions
|
6
|
+
# OverloadResolver handles type-aware function overload resolution
|
7
|
+
# Given a function alias/id and argument types, finds the best matching function
|
8
|
+
#
|
9
|
+
# Responsibilities:
|
10
|
+
# - Track all function overloads per alias
|
11
|
+
# - Match argument types against parameter constraints
|
12
|
+
# - Provide clear error messages when resolution fails
|
13
|
+
class OverloadResolver
|
14
|
+
def initialize(functions_by_id)
|
15
|
+
@functions = functions_by_id # "core.mul" => Function
|
16
|
+
@by_id = functions_by_id # Direct lookup
|
17
|
+
@alias_overloads = build_alias_overloads(functions_by_id)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Resolve a function alias or ID to a specific function ID based on argument types
|
21
|
+
#
|
22
|
+
# @param alias_or_id [String, Symbol] Function alias or full function ID
|
23
|
+
# @param arg_types [Array<Symbol>] Inferred types of arguments
|
24
|
+
# @return [String] The resolved function_id
|
25
|
+
# @raise [ResolutionError] If function cannot be resolved
|
26
|
+
def resolve(alias_or_id, arg_types)
|
27
|
+
s = alias_or_id.to_s
|
28
|
+
|
29
|
+
# If it's already a full function ID, validate and return it
|
30
|
+
if @functions.key?(s)
|
31
|
+
validate_arity!(s, arg_types)
|
32
|
+
return s
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get all candidate overloads for this alias
|
36
|
+
candidates = @alias_overloads[s]
|
37
|
+
raise ResolutionError, "unknown function #{alias_or_id}" if candidates.nil?
|
38
|
+
|
39
|
+
# Single overload - use it directly
|
40
|
+
if candidates.size == 1
|
41
|
+
validate_arity!(candidates.first, arg_types)
|
42
|
+
return candidates.first
|
43
|
+
end
|
44
|
+
|
45
|
+
# Multiple overloads - find best match by type constraints (prefer exact matches)
|
46
|
+
candidates_with_scores = candidates.map do |fn_id|
|
47
|
+
fn = @functions[fn_id]
|
48
|
+
score = match_score(fn.params, arg_types)
|
49
|
+
[fn_id, score]
|
50
|
+
end
|
51
|
+
|
52
|
+
best_match, score = candidates_with_scores.max_by { |_, s| s }
|
53
|
+
|
54
|
+
if score > 0
|
55
|
+
return best_match
|
56
|
+
end
|
57
|
+
|
58
|
+
# No match found - provide helpful error
|
59
|
+
available = candidates.map { |id| @functions[id].id }.join(", ")
|
60
|
+
raise ResolutionError,
|
61
|
+
"no overload of '#{alias_or_id}' matches argument types #{arg_types.inspect}. " \
|
62
|
+
"Available overloads: #{available}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get function object by ID (already resolved)
|
66
|
+
def function(id)
|
67
|
+
@functions.fetch(id) do
|
68
|
+
raise ResolutionError, "unknown function #{id}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Check if a function exists
|
73
|
+
def exists?(id)
|
74
|
+
@functions.key?(id.to_s)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def build_alias_overloads(functions)
|
80
|
+
# Maps each alias to an array of all function_ids that have that alias
|
81
|
+
functions.values.each_with_object({}) do |func, acc|
|
82
|
+
func.aliases.each do |al|
|
83
|
+
acc[al] ||= []
|
84
|
+
acc[al] << func.id
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def params_match?(params, arg_types)
|
90
|
+
# Check arity first
|
91
|
+
return false if params.size != arg_types.size
|
92
|
+
|
93
|
+
# Check each parameter constraint
|
94
|
+
params.zip(arg_types).all? do |param, arg_type|
|
95
|
+
param_dtype = param["dtype"]
|
96
|
+
param_dtype.nil? || type_compatible?(param_dtype, arg_type)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def match_score(params, arg_types)
|
101
|
+
# Returns match quality: higher is better
|
102
|
+
# 0 = no match, 1 = matches with unconstrained params, 2 = exact match
|
103
|
+
return 0 unless params_match?(params, arg_types)
|
104
|
+
|
105
|
+
# Count exact constraint matches (all arg_types are Type objects now)
|
106
|
+
exact_matches = params.zip(arg_types).count do |param, arg_type|
|
107
|
+
param_dtype = param["dtype"]
|
108
|
+
score_type_object_match(param_dtype, arg_type)
|
109
|
+
end
|
110
|
+
|
111
|
+
exact_matches
|
112
|
+
end
|
113
|
+
|
114
|
+
def score_type_object_match(param_dtype, type_obj)
|
115
|
+
case param_dtype&.to_s
|
116
|
+
when "string"
|
117
|
+
type_obj.is_a?(Kumi::Core::Types::ScalarType) && type_obj.kind == :string
|
118
|
+
when "array"
|
119
|
+
type_obj.is_a?(Kumi::Core::Types::ArrayType)
|
120
|
+
when "integer"
|
121
|
+
type_obj.is_a?(Kumi::Core::Types::ScalarType) && type_obj.kind == :integer
|
122
|
+
when "float"
|
123
|
+
type_obj.is_a?(Kumi::Core::Types::ScalarType) && type_obj.kind == :float
|
124
|
+
when "hash"
|
125
|
+
type_obj.is_a?(Kumi::Core::Types::ScalarType) && type_obj.kind == :hash
|
126
|
+
else
|
127
|
+
false
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def type_compatible?(param_dtype_str, arg_type)
|
132
|
+
raise ArgumentError, "arg_type must be a Type object, got #{arg_type.inspect}" unless arg_type.is_a?(Kumi::Core::Types::Type)
|
133
|
+
|
134
|
+
case param_dtype_str
|
135
|
+
when "string"
|
136
|
+
arg_type.is_a?(Kumi::Core::Types::ScalarType) && arg_type.kind == :string
|
137
|
+
when "array"
|
138
|
+
arg_type.is_a?(Kumi::Core::Types::ArrayType)
|
139
|
+
when "integer"
|
140
|
+
arg_type.is_a?(Kumi::Core::Types::ScalarType) && arg_type.kind == :integer
|
141
|
+
when "float"
|
142
|
+
arg_type.is_a?(Kumi::Core::Types::ScalarType) && arg_type.kind == :float
|
143
|
+
when "hash"
|
144
|
+
arg_type.is_a?(Kumi::Core::Types::ScalarType) && arg_type.kind == :hash
|
145
|
+
else
|
146
|
+
# No constraint, any type matches
|
147
|
+
true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def validate_arity!(fn_id, arg_types)
|
152
|
+
fn = @functions[fn_id]
|
153
|
+
return if fn.params.size == arg_types.size
|
154
|
+
|
155
|
+
raise ResolutionError,
|
156
|
+
"function #{fn_id} expects #{fn.params.size} arguments, got #{arg_types.size}"
|
157
|
+
end
|
158
|
+
|
159
|
+
# Custom error for function resolution failures
|
160
|
+
class ResolutionError < StandardError; end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Kumi
|
4
|
+
module Core
|
5
|
+
module Functions
|
6
|
+
# TypeErrorReporter provides typed error reporting for function resolution and type checking
|
7
|
+
# Ensures all type errors have proper location information for better diagnostics
|
8
|
+
module TypeErrorReporter
|
9
|
+
# Report function overload resolution failure with proper location
|
10
|
+
#
|
11
|
+
# @param errors [Array] Error accumulator
|
12
|
+
# @param alias_or_id [String, Symbol] Function alias or ID that couldn't be resolved
|
13
|
+
# @param arg_types [Array<Symbol>] Argument types that didn't match any overload
|
14
|
+
# @param available_overloads [Array<String>] Available function overload IDs
|
15
|
+
# @param location [Syntax::Location, nil] Where the error occurred
|
16
|
+
def self.report_overload_resolution_error(errors, alias_or_id, arg_types, available_overloads, location)
|
17
|
+
message = format_overload_error(alias_or_id, arg_types, available_overloads)
|
18
|
+
|
19
|
+
error = Core::ErrorReporter.create_error(
|
20
|
+
message,
|
21
|
+
location: location,
|
22
|
+
type: :type,
|
23
|
+
context: {
|
24
|
+
alias: alias_or_id.to_s,
|
25
|
+
arg_types: arg_types,
|
26
|
+
candidates: available_overloads
|
27
|
+
}
|
28
|
+
)
|
29
|
+
|
30
|
+
errors << error
|
31
|
+
error
|
32
|
+
end
|
33
|
+
|
34
|
+
# Report arity mismatch (wrong number of arguments)
|
35
|
+
#
|
36
|
+
# @param errors [Array] Error accumulator
|
37
|
+
# @param fn_id [String] Full function ID
|
38
|
+
# @param expected [Integer] Expected number of arguments
|
39
|
+
# @param actual [Integer] Actual number of arguments provided
|
40
|
+
# @param location [Syntax::Location, nil] Where the error occurred
|
41
|
+
def self.report_arity_mismatch(errors, fn_id, expected, actual, location)
|
42
|
+
message = "function '#{fn_id}' expects #{expected} argument(s), got #{actual}"
|
43
|
+
|
44
|
+
error = Core::ErrorReporter.create_error(
|
45
|
+
message,
|
46
|
+
location: location,
|
47
|
+
type: :type,
|
48
|
+
context: {
|
49
|
+
function: fn_id.to_s,
|
50
|
+
expected: expected,
|
51
|
+
actual: actual
|
52
|
+
}
|
53
|
+
)
|
54
|
+
|
55
|
+
errors << error
|
56
|
+
error
|
57
|
+
end
|
58
|
+
|
59
|
+
# Report type constraint violation (parameter type doesn't match argument type)
|
60
|
+
#
|
61
|
+
# @param errors [Array] Error accumulator
|
62
|
+
# @param fn_id [String] Full function ID
|
63
|
+
# @param param_name [String] Parameter name
|
64
|
+
# @param expected_type [String] Expected type constraint
|
65
|
+
# @param actual_type [Symbol] Actual argument type
|
66
|
+
# @param location [Syntax::Location, nil] Where the error occurred
|
67
|
+
def self.report_type_constraint_violation(errors, fn_id, param_name, expected_type, actual_type, location)
|
68
|
+
message = "function '#{fn_id}' parameter '#{param_name}' expects type #{expected_type.inspect}, " \
|
69
|
+
"got #{actual_type.inspect}"
|
70
|
+
|
71
|
+
error = Core::ErrorReporter.create_error(
|
72
|
+
message,
|
73
|
+
location: location,
|
74
|
+
type: :type,
|
75
|
+
context: {
|
76
|
+
function: fn_id.to_s,
|
77
|
+
parameter: param_name.to_s,
|
78
|
+
expected_type: expected_type.to_s,
|
79
|
+
actual_type: actual_type.to_s
|
80
|
+
}
|
81
|
+
)
|
82
|
+
|
83
|
+
errors << error
|
84
|
+
error
|
85
|
+
end
|
86
|
+
|
87
|
+
# Report unknown function
|
88
|
+
#
|
89
|
+
# @param errors [Array] Error accumulator
|
90
|
+
# @param alias_or_id [String, Symbol] Function name/alias that doesn't exist
|
91
|
+
# @param location [Syntax::Location, nil] Where the error occurred
|
92
|
+
def self.report_unknown_function(errors, alias_or_id, location)
|
93
|
+
message = "unknown function '#{alias_or_id}'"
|
94
|
+
|
95
|
+
error = Core::ErrorReporter.create_error(
|
96
|
+
message,
|
97
|
+
location: location,
|
98
|
+
type: :semantic,
|
99
|
+
context: { function: alias_or_id.to_s }
|
100
|
+
)
|
101
|
+
|
102
|
+
errors << error
|
103
|
+
error
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def self.format_overload_error(alias_or_id, arg_types, available_overloads)
|
109
|
+
arg_types_str = arg_types.map(&:inspect).join(", ")
|
110
|
+
available_str = available_overloads.map { |id| "'#{id}'" }.join(", ")
|
111
|
+
|
112
|
+
"no overload of '#{alias_or_id}' matches argument types (#{arg_types_str}). " \
|
113
|
+
"Available overloads: #{available_str}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -6,17 +6,62 @@ module Kumi
|
|
6
6
|
module TypeRules
|
7
7
|
module_function
|
8
8
|
|
9
|
+
# Convert Type objects or symbols to Type objects
|
10
|
+
def to_type_object(type_input)
|
11
|
+
return type_input if type_input.is_a?(Kumi::Core::Types::Type)
|
12
|
+
|
13
|
+
# Convert symbol/string to Type object
|
14
|
+
case type_input
|
15
|
+
when :string
|
16
|
+
Kumi::Core::Types.scalar(:string)
|
17
|
+
when :integer
|
18
|
+
Kumi::Core::Types.scalar(:integer)
|
19
|
+
when :float
|
20
|
+
Kumi::Core::Types.scalar(:float)
|
21
|
+
when :boolean
|
22
|
+
Kumi::Core::Types.scalar(:boolean)
|
23
|
+
when :hash
|
24
|
+
Kumi::Core::Types.scalar(:hash)
|
25
|
+
when String
|
26
|
+
# Handle string type representations like "array<integer>" or "tuple<float, integer>"
|
27
|
+
parse_string_type(type_input)
|
28
|
+
else
|
29
|
+
# For any other type representation, normalize first
|
30
|
+
normalized = Kumi::Core::Types.normalize(type_input)
|
31
|
+
to_type_object(normalized)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_string_type(str_type)
|
36
|
+
# Handle array types: "array<integer>"
|
37
|
+
if (m = /\Aarray<(.+)>\z/.match(str_type))
|
38
|
+
element_str = m[1]
|
39
|
+
element_type = to_type_object(element_str.to_sym)
|
40
|
+
return Kumi::Core::Types.array(element_type)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Handle tuple types: "tuple<integer, float>"
|
44
|
+
if (m = /\Atuple<(.+)>\z/.match(str_type))
|
45
|
+
element_strs = m[1].split(",").map(&:strip)
|
46
|
+
element_types = element_strs.map { |s| to_type_object(s.to_sym) }
|
47
|
+
return Kumi::Core::Types.tuple(element_types)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Try as symbol
|
51
|
+
to_type_object(str_type.to_sym)
|
52
|
+
end
|
53
|
+
|
9
54
|
def normalize_type_symbol(type_symbol)
|
10
55
|
Kumi::Core::Types.normalize(type_symbol)
|
11
56
|
end
|
12
57
|
|
13
|
-
#
|
58
|
+
# Type promotion for NAST analysis - returns Type objects
|
14
59
|
def promote_types(*input_types)
|
15
|
-
|
16
|
-
return :float if
|
17
|
-
return :integer if
|
60
|
+
types = input_types.flatten.compact.uniq
|
61
|
+
return Kumi::Core::Types.scalar(:float) if types.any? { |t| float_type?(t) }
|
62
|
+
return Kumi::Core::Types.scalar(:integer) if types.any? { |t| integer_type?(t) }
|
18
63
|
|
19
|
-
|
64
|
+
to_type_object(types.first)
|
20
65
|
end
|
21
66
|
|
22
67
|
def common_type(element_types)
|
@@ -26,81 +71,156 @@ module Kumi
|
|
26
71
|
def unify_types(type1, type2)
|
27
72
|
return type1 if type1 == type2
|
28
73
|
|
29
|
-
promote_types(type1, type2)
|
74
|
+
promote_types(type1, type2)
|
30
75
|
end
|
31
76
|
|
32
|
-
def same_type_as(
|
33
|
-
|
77
|
+
def same_type_as(reference_type)
|
78
|
+
to_type_object(reference_type)
|
34
79
|
rescue StandardError
|
35
|
-
|
36
|
-
# raise
|
37
|
-
# TODO CHECK HOW HANDLE THIS
|
80
|
+
to_type_object(reference_type)
|
38
81
|
end
|
39
82
|
|
40
83
|
def array_type(element_type)
|
41
|
-
|
84
|
+
element_obj = to_type_object(element_type)
|
85
|
+
Kumi::Core::Types.array(element_obj)
|
42
86
|
end
|
43
87
|
|
44
88
|
def tuple_type(*element_types)
|
45
|
-
|
89
|
+
element_objs = element_types.map { |t| to_type_object(t) }
|
90
|
+
Kumi::Core::Types.tuple(element_objs)
|
46
91
|
end
|
47
92
|
|
48
|
-
#
|
93
|
+
# Extract element type from collection Type objects
|
49
94
|
def element_type_of(collection_type)
|
50
|
-
|
51
|
-
|
52
|
-
|
95
|
+
type_obj = to_type_object(collection_type)
|
96
|
+
|
97
|
+
case type_obj
|
98
|
+
when Kumi::Core::Types::ArrayType
|
99
|
+
type_obj.element_type
|
100
|
+
when Kumi::Core::Types::TupleType
|
101
|
+
# Promote all element types to common type
|
102
|
+
promote_types(*type_obj.element_types)
|
103
|
+
else
|
104
|
+
type_obj
|
53
105
|
end
|
106
|
+
end
|
54
107
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
108
|
+
# --- Typed Rule Builders (Direct Type Construction) ---
|
109
|
+
|
110
|
+
# Build rule: return the type of a specific parameter
|
111
|
+
def build_same_as(param_name)
|
112
|
+
->(named) { same_type_as(named.fetch(param_name)) }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Build rule: promote types of multiple parameters
|
116
|
+
def build_promote(*param_names)
|
117
|
+
->(named) { promote_types(*param_names.map { |k| named.fetch(k) }) }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Build rule: extract element type from a collection parameter
|
121
|
+
def build_element_of(param_name)
|
122
|
+
->(named) { element_type_of(named.fetch(param_name)) }
|
123
|
+
end
|
124
|
+
|
125
|
+
# Build rule: unify types of two parameters
|
126
|
+
def build_unify(param_name1, param_name2)
|
127
|
+
->(named) { unify_types(named.fetch(param_name1), named.fetch(param_name2)) }
|
128
|
+
end
|
129
|
+
|
130
|
+
# Build rule: common type among array elements
|
131
|
+
def build_common_type(param_name)
|
132
|
+
->(named) { common_type(named.fetch(param_name)) }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Build rule: array of a specific element type
|
136
|
+
def build_array(element_type_or_param_name)
|
137
|
+
# Check if it's a known scalar kind or Type object
|
138
|
+
if element_type_or_param_name.is_a?(Kumi::Core::Types::Type)
|
139
|
+
# Type object - use directly
|
140
|
+
type_obj = element_type_or_param_name
|
141
|
+
->(_) { Kumi::Core::Types.array(type_obj) }
|
142
|
+
elsif element_type_or_param_name.is_a?(Symbol) && Kumi::Core::Types::Validator.valid_kind?(element_type_or_param_name)
|
143
|
+
# Known scalar kind - create Type and wrap
|
144
|
+
type_obj = to_type_object(element_type_or_param_name)
|
145
|
+
->(_) { Kumi::Core::Types.array(type_obj) }
|
146
|
+
else
|
147
|
+
# Treat as parameter name reference
|
148
|
+
->(named) { array_type(named.fetch(element_type_or_param_name)) }
|
60
149
|
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Build rule: tuple of specific element types
|
153
|
+
def build_tuple(*element_types_or_param_names)
|
154
|
+
# If single symbol and NOT a known scalar kind, treat as parameter reference
|
155
|
+
if element_types_or_param_names.size == 1 && element_types_or_param_names[0].is_a?(Symbol)
|
156
|
+
sym = element_types_or_param_names[0]
|
157
|
+
unless Kumi::Core::Types::Validator.valid_kind?(sym)
|
158
|
+
# Not a known kind - treat as parameter name (holds array of types)
|
159
|
+
return ->(named) { tuple_type(*named.fetch(sym)) }
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Interpret as explicit types
|
164
|
+
type_objs = element_types_or_param_names.map { |t| to_type_object(t) }
|
165
|
+
->(_) { Kumi::Core::Types.tuple(type_objs) }
|
166
|
+
end
|
61
167
|
|
62
|
-
|
168
|
+
# Build rule: constant scalar type
|
169
|
+
def build_scalar(kind)
|
170
|
+
->(_) { to_type_object(kind) }
|
63
171
|
end
|
64
172
|
|
65
|
-
# Compile dtype rule string into callable
|
173
|
+
# --- Compile dtype rule string into callable (backward compatible) ---
|
66
174
|
def compile_dtype_rule(rule_string, _parameter_names)
|
67
175
|
rule = rule_string.to_s.strip
|
68
176
|
|
69
|
-
# --- NEW: Handle the "element_of" rule ---
|
70
177
|
if (m = /\Aelement_of\((.+)\)\z/.match(rule))
|
71
178
|
key = m[1].strip.to_sym
|
72
|
-
return
|
179
|
+
return build_element_of(key)
|
73
180
|
end
|
74
181
|
|
75
|
-
# Handle existing function-based rules
|
76
182
|
if (m = /\Apromote\((.+)\)\z/.match(rule))
|
77
183
|
keys = m[1].split(",").map { |s| s.strip.to_sym }
|
78
|
-
return
|
184
|
+
return build_promote(*keys)
|
79
185
|
end
|
186
|
+
|
80
187
|
if (m = /\Asame_as\((.+)\)\z/.match(rule))
|
81
188
|
key = m[1].strip.to_sym
|
82
|
-
return
|
189
|
+
return build_same_as(key)
|
83
190
|
end
|
84
|
-
|
191
|
+
|
192
|
+
if (m = /\Aunify\(([^,]+),\s*([^)]+)\)\z/.match(rule))
|
85
193
|
k1 = m[1].strip.to_sym
|
86
194
|
k2 = m[2].strip.to_sym
|
87
|
-
return
|
195
|
+
return build_unify(k1, k2)
|
88
196
|
end
|
197
|
+
|
89
198
|
if (m = /\Acommon_type\((.+)\)\z/.match(rule))
|
90
199
|
param_name = m[1].strip.to_sym
|
91
|
-
return
|
200
|
+
return build_common_type(param_name)
|
92
201
|
end
|
202
|
+
|
93
203
|
if (m = /\Aarray\((.+)\)\z/.match(rule))
|
94
204
|
inner_rule = m[1].strip
|
95
|
-
inner_compiled = compile_dtype_rule(inner_rule, [])
|
205
|
+
inner_compiled = compile_dtype_rule(inner_rule, [])
|
96
206
|
return ->(named) { array_type(inner_compiled.call(named)) }
|
97
207
|
end
|
208
|
+
|
98
209
|
if (m = /\Atuple\(types\((.+)\)\)\z/.match(rule))
|
99
210
|
param_name = m[1].strip.to_sym
|
100
|
-
return
|
211
|
+
return build_tuple(param_name)
|
101
212
|
end
|
102
213
|
|
103
|
-
|
214
|
+
# Constant scalar type
|
215
|
+
build_scalar(rule.to_sym)
|
216
|
+
end
|
217
|
+
|
218
|
+
def float_type?(t)
|
219
|
+
t.is_a?(Kumi::Core::Types::ScalarType) ? t.kind == :float : t == :float
|
220
|
+
end
|
221
|
+
|
222
|
+
def integer_type?(t)
|
223
|
+
t.is_a?(Kumi::Core::Types::ScalarType) ? t.kind == :integer : t == :integer
|
104
224
|
end
|
105
225
|
end
|
106
226
|
end
|
@@ -9,31 +9,38 @@ module Kumi
|
|
9
9
|
class Inference
|
10
10
|
def self.infer_from_value(value)
|
11
11
|
case value
|
12
|
-
when String
|
13
|
-
|
14
|
-
when
|
15
|
-
|
16
|
-
when
|
17
|
-
|
18
|
-
when
|
19
|
-
|
20
|
-
when
|
12
|
+
when String
|
13
|
+
Kumi::Core::Types.scalar(:string)
|
14
|
+
when Integer
|
15
|
+
Kumi::Core::Types.scalar(:integer)
|
16
|
+
when Float
|
17
|
+
Kumi::Core::Types.scalar(:float)
|
18
|
+
when TrueClass, FalseClass
|
19
|
+
Kumi::Core::Types.scalar(:boolean)
|
20
|
+
when Symbol
|
21
|
+
Kumi::Core::Types.scalar(:symbol)
|
22
|
+
when Regexp
|
23
|
+
Kumi::Core::Types.scalar(:regexp)
|
24
|
+
when Time
|
25
|
+
Kumi::Core::Types.scalar(:time)
|
26
|
+
when DateTime
|
27
|
+
Kumi::Core::Types.scalar(:datetime)
|
28
|
+
when Date
|
29
|
+
Kumi::Core::Types.scalar(:date)
|
21
30
|
when Array
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
31
|
+
if value.empty?
|
32
|
+
Kumi::Core::Types.array(Kumi::Core::Types.scalar(:any))
|
33
|
+
else
|
34
|
+
# Infer element type from first element (simple heuristic)
|
35
|
+
elem_type = infer_from_value(value.first)
|
36
|
+
Kumi::Core::Types.array(elem_type)
|
37
|
+
end
|
27
38
|
when Hash
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
first_key, first_value = value.first
|
32
|
-
key_type = infer_from_value(first_key)
|
33
|
-
value_type = infer_from_value(first_value)
|
34
|
-
Kumi::Core::Types.hash(key_type, value_type)
|
39
|
+
# Kumi treats hash as scalar, not key/value pair type
|
40
|
+
# So we just return scalar(:hash) regardless of contents
|
41
|
+
Kumi::Core::Types.scalar(:hash)
|
35
42
|
else
|
36
|
-
:any
|
43
|
+
Kumi::Core::Types.scalar(:any)
|
37
44
|
end
|
38
45
|
end
|
39
46
|
end
|