unsigned integer constraint (uint)

This commit is contained in:
2026-07-18 00:12:59 +02:00
parent b09787029d
commit ef91f13e7b
15 changed files with 4618 additions and 2849 deletions
+98 -34
View File
@@ -501,7 +501,7 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
strings.write_string(builder, "enum")
case .Alias, .Distinct, .Named:
write_type_label(checker, builder, item.child)
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
strings.write_string(builder, types.name(value))
}
}
@@ -1052,6 +1052,26 @@ fits_i64 :: proc(value: i128) -> bool {
return fits_signed_type(value, types.I64)
}
fits_u64 :: proc(value: i128) -> bool {
return value >= 0 && value <= i128(0xffff_ffff_ffff_ffff)
}
constraint_integer_literal_type :: proc(constraint: types.Type, value: i128) -> types.Type {
if constraint == types.UINT {
return types.smallest_unsigned_for_literal(u64(value)) if fits_u64(value) else types.INVALID
}
return types.smallest_signed_for_literal(i64(value)) if fits_i64(value) else types.INVALID
}
constraint_recovery_type :: proc(checker: ^Checker, constraint: types.Type) -> types.Type {
switch constraint {
case types.UINT: return types.U64
case types.FLOAT: return types.F64
case types.RANGE: return types.range(&checker.module.types, types.I64)
case: return types.I64
}
}
type_from_syntax :: proc(
checker: ^Checker,
value: ast.Type_Syntax,
@@ -2647,7 +2667,7 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
// 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.
// Integer/range constraints keep building naturally from their literals.
if declared == types.FLOAT {
return types.F64
}
@@ -3624,13 +3644,21 @@ record_field_expr_candidate :: proc(
}
expr := checker.ast_module.exprs[expr_id]
constraint := checker.record_field_constraints[slot]
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
constant.kind == .Value && fits_i64(constant.value) {
candidate := types.smallest_signed_for_literal(i64(constant.value))
if constraint == types.FLOAT {
candidate = types.F64
if !numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
if is_runtime_type(checker, concrete) {
return merge_record_field_demand(checker, slot, concrete, expr.span)
}
}
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
constant.kind == .Value {
candidate := constraint_integer_literal_type(constraint, constant.value)
if constraint == types.FLOAT {
candidate = types.F64 if fits_i64(constant.value) else types.INVALID
}
if types.is_valid(candidate) {
return merge_record_field_default(checker, slot, candidate, expr.span)
}
return merge_record_field_default(checker, slot, candidate, expr.span)
}
if is_float_constant_expr(checker, expr_id) {
return merge_record_field_default(checker, slot, types.F64, expr.span)
@@ -3693,14 +3721,7 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
)
}
if types.is_constraint(current) {
switch constraint {
case types.INT:
checker.module.types.fields[slot].type = types.I64
case types.FLOAT:
checker.module.types.fields[slot].type = types.F64
case types.RANGE:
checker.module.types.fields[slot].type = types.range(&checker.module.types, types.I64)
}
checker.module.types.fields[slot].type = constraint_recovery_type(checker, constraint)
}
}
}
@@ -3999,7 +4020,7 @@ find_spec :: proc(
}
// specialized_param_type maps a parameter's declared type to its monomorphized
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
// type for a given actual argument. A constraint param (`int`/`uint`/`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(
@@ -4426,14 +4447,16 @@ infer_expr :: proc(
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
constant = eval_constant(checker, frame.expr)
}
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
(constant.kind == .Value && !fits_i64(constant.value)) {
if constant.kind == .Overflow || constant.kind == .Div_By_Zero {
last = types.I64
_ = pop(&stack)
continue
}
if constant.kind == .Value {
last = types.smallest_signed_for_literal(i64(constant.value))
last = constraint_integer_literal_type(frame.expected, constant.value)
if !types.is_valid(last) {
last = types.I64
}
_ = pop(&stack)
continue
}
@@ -5066,7 +5089,7 @@ infer_statements :: proc(
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
value_type := types.INVALID
if !is_undefined_expr(checker, statement.expr) {
expected := declared_local if is_runtime_type(checker, declared_local) else types.INVALID
expected := declared_local if is_runtime_type(checker, declared_local) || declared_local == types.UINT else types.INVALID
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
}
@@ -5082,7 +5105,8 @@ infer_statements :: proc(
const_val := i128(0)
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
constant := eval_integer_constant_in_context(checker, statement.expr, pkg, file)
if constant.kind == .Value && fits_i64(constant.value) {
if constant.kind == .Value &&
(fits_i64(constant.value) || declared_local == types.UINT && fits_u64(constant.value)) {
open = true
const_val = constant.value
} else if is_float_constant_expr(checker, statement.expr) {
@@ -5123,11 +5147,25 @@ infer_statements :: proc(
expected_assignment := types.INVALID
if statement.target != ast.INVALID_EXPR {
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
target_expr := checker.ast_module.exprs[statement.target]
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
locals^[local_index].declared == types.UINT {
expected_assignment = types.UINT
} else if global := find_global(checker, target_expr.name, pkg, file); global != ast.INVALID_GLOBAL {
ast_global := checker.ast_module.globals[global]
if type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file) == types.UINT {
expected_assignment = types.UINT
}
}
}
} else if statement.name != checker.sink_symbol {
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
expected_assignment = locals^[local_index].type
expected_assignment = types.UINT if locals^[local_index].declared == types.UINT else locals^[local_index].type
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
expected_assignment = checker.global_types[global]
ast_global := checker.ast_module.globals[global]
declared_global := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
expected_assignment = types.UINT if declared_global == types.UINT else checker.global_types[global]
}
}
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
@@ -5430,7 +5468,7 @@ infer_spec_locals_and_result :: proc(
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
declared = types.I32
}
result_hint := declared if is_runtime_type(checker, declared) else types.INVALID
result_hint := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
locals: [dynamic]Infer_Local
locals.allocator = checker.allocator
@@ -5615,7 +5653,7 @@ open_const_default_type :: proc(
if index, ok := find_infer_local_index(locals, expr.name); ok {
local := locals[index]
if local.open_const {
return types.smallest_signed_for_literal(i64(local.const_value))
return constraint_integer_literal_type(local.declared, local.const_value)
}
if local.open_float {
return types.F64
@@ -5633,7 +5671,9 @@ open_const_default_type :: proc(
return types.INVALID
}
if checker.global_open_const[index] {
return types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
ast_global := checker.ast_module.globals[global]
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
return constraint_integer_literal_type(declared, checker.global_const_value[index])
}
if checker.global_open_float[index] {
return types.F64
@@ -5899,7 +5939,8 @@ infer_all :: proc(checker: ^Checker) {
}
}
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
if constant.kind == .Value && fits_i64(constant.value) {
if constant.kind == .Value &&
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) {
checker.global_open_const[index] = true
checker.global_const_value[index] = constant.value
} else if is_float_constant_expr(checker, global.expr) {
@@ -5947,7 +5988,7 @@ infer_all :: proc(checker: ^Checker) {
type_from_syntax(checker, global.type, global.pkg, global.file),
global.expr,
)
expected := declared if is_runtime_type(checker, declared) else types.INVALID
expected := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
resolved != declared {
@@ -6020,7 +6061,8 @@ infer_all :: proc(checker: ^Checker) {
continue
}
if checker.global_open_const[index] {
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
checker.global_types[index] = constraint_integer_literal_type(declared, checker.global_const_value[index])
defaulted = true
} else if checker.global_open_float[index] {
checker.global_types[index] = types.F64
@@ -6394,7 +6436,7 @@ build_constant_expr :: proc(
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Overflow ||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
(!types.is_concrete_integer(expected) && expected != types.UINT && !fits_i64(constant.value)) {
id := source.add(
checker.diagnostics,
expr.span,
@@ -6407,7 +6449,16 @@ build_constant_expr :: proc(
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
value = transmute(i64)u64(constant.value)
}
result_type := types.smallest_signed_for_literal(value)
result_type := constraint_integer_literal_type(expected, constant.value)
if expected == types.UINT && !types.is_valid(result_type) {
id := source.addf(
checker.diagnostics,
expr.span,
"integer constant %d does not satisfy uint",
constant.value,
)
return invalid_hir_expr(checker, expr.span, id, types.U64)
}
if types.is_concrete_integer(expected) {
if !fits_integer_type(constant.value, expected, checker.target) {
id := source.addf(
@@ -12375,11 +12426,12 @@ build_globals :: proc(checker: ^Checker) {
if is_runtime_type(checker, declared) {
expected = declared
} else if constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file);
constant.kind == .Value && fits_i64(constant.value) &&
constant.kind == .Value &&
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) &&
is_runtime_type(checker, checker.global_types[global_index]) {
// Open integer constant: build against its demanded/defaulted type. Gated
// to the infer-side open-constant condition so out-of-range constants keep
// their original "exceeds signed i64 range" diagnostic.
// their original range diagnostic.
expected = checker.global_types[global_index]
} else if (is_float_constant_expr(checker, global.expr) ||
is_numeric_arithmetic_expr(checker, global.expr)) &&
@@ -12401,6 +12453,18 @@ build_globals :: proc(checker: ^Checker) {
global_type = types.I64
}
}
if diagnostic == source.INVALID_DIAGNOSTIC && types.is_constraint(declared) &&
!types.constraint_accepts(declared, global_type, &checker.module.types) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve the '%s' constraint for global '%s'",
types.name(declared),
symbol_text(checker, global.name),
)
global_type = constraint_recovery_type(checker, declared)
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if !global.immutable && is_undefined_expr(checker, global.expr) {
diagnostic = source.add(
checker.diagnostics,