add range constraint type

This commit is contained in:
2026-06-25 23:02:37 +02:00
parent c3208fdb61
commit 70a6d69d29
9 changed files with 222 additions and 40 deletions
+52 -13
View File
@@ -538,7 +538,14 @@ call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
if index < 0 || index >= len(function.params) {
return types.INVALID
}
return type_from_syntax(function.params[index].type)
declared := type_from_syntax(function.params[index].type)
// A `float` param defaults to f64 so an integer-literal argument builds as a
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
// `int`/`range` constraints have no single default and keep building naturally.
if declared == types.FLOAT {
return types.F64
}
return declared
}
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
@@ -994,7 +1001,7 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
if param_index < len(actual_args) {
actual = actual_args[param_index]
}
if !types.equal(spec.args[param_index], specialized_param_type(param.type, actual)) {
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual)) {
matches = false
break
}
@@ -1006,10 +1013,14 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
return INVALID_SPEC
}
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
// specialized_param_type maps a parameter's declared type to its monomorphized
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
// resolves to the actual's family member (INVALID if out of family), so a call
// passing an out-of-family argument fails to specialize and is rejected.
specialized_param_type :: proc(checker: ^Checker, syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
declared := type_from_syntax(syntax)
if types.is_constraint(declared) {
return actual
return types.constraint_target(declared, actual, &checker.module.types)
}
return declared
}
@@ -1020,7 +1031,7 @@ can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: [
if index < len(actual_args) {
actual = actual_args[index]
}
if !is_runtime_type(checker, specialized_param_type(param.type, actual)) {
if !is_runtime_type(checker, specialized_param_type(checker, param.type, actual)) {
return false
}
}
@@ -1039,7 +1050,7 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
if index < len(actual_args) {
actual = actual_args[index]
}
append(&signature, specialized_param_type(param.type, actual))
append(&signature, specialized_param_type(checker, param.type, actual))
}
result := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
@@ -1541,7 +1552,7 @@ merge_infer_local_type :: proc(
return false
}
if types.is_constraint(local.declared) {
if !types.constraint_accepts(local.declared, inferred) {
if !types.constraint_accepts(local.declared, inferred, &checker.module.types) {
return false
}
if !is_runtime_type(checker, local.type) {
@@ -1553,7 +1564,7 @@ merge_infer_local_type :: proc(
return false
}
merged := types.widest(local.type, inferred)
if types.constraint_accepts(local.declared, merged) {
if types.constraint_accepts(local.declared, merged, &checker.module.types) {
local.type = merged
record_infer_local_type(local^, local_types)
return true
@@ -1608,7 +1619,7 @@ infer_statements :: proc(
} else if types.is_constraint(declared_local) {
// Seed the binding in-family (INVALID on mismatch, which
// build_block reports). FLOAT defaults integers to f64.
value_type = types.constraint_target(declared_local, value_type)
value_type = types.constraint_target(declared_local, value_type, &checker.module.types)
}
local := Infer_Local{
name=statement.name,
@@ -1753,10 +1764,10 @@ infer_spec_locals_and_result :: proc(
result := types.INVALID
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
if types.is_constraint(declared) {
// Params/results use a constraint as a generic passthrough (e.g. an
// identity `func(v int) int` forwarding a range), so the result keeps
// the inferred type as-is rather than being narrowed to the family.
return local_types, result
// Narrow the inferred result to the constraint's family; an out-of-family
// result (e.g. returning a non-integer from an `int` function) yields
// INVALID and is rejected downstream.
return local_types, types.constraint_target(declared, result, &checker.module.types)
}
return local_types, declared
}
@@ -3230,9 +3241,37 @@ build_expr :: proc(
continue
}
}
arg_violation := source.INVALID_DIAGNOSTIC
{
params := checker.ast_module.functions[frame.template].params
for index in 0..<len(params) {
if index >= len(stack[frame_index].arg_types) {
break
}
declared := type_from_syntax(params[index].type)
actual := stack[frame_index].arg_types[index]
if types.is_constraint(declared) && types.is_valid(actual) &&
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
arg_violation = source.addf(
checker.diagnostics, expr.span,
"cannot pass %s to '%s' parameter '%s'",
types.name(actual), types.name(declared),
symbol_text(checker, params[index].name),
)
break
}
}
}
spec := find_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
if arg_violation != source.INVALID_DIAGNOSTIC {
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, arg_violation)
_ = pop(&stack)
continue
}
if spec == INVALID_SPEC {
id := source.addf(
checker.diagnostics,