add float constraint type

This commit is contained in:
2026-06-25 22:09:08 +02:00
parent ca6cc89da0
commit c3208fdb61
7 changed files with 319 additions and 10 deletions
+46 -2
View File
@@ -1541,7 +1541,7 @@ merge_infer_local_type :: proc(
return false
}
if types.is_constraint(local.declared) {
if !types.is_concrete_integer(inferred) {
if !types.constraint_accepts(local.declared, inferred) {
return false
}
if !is_runtime_type(checker, local.type) {
@@ -1553,7 +1553,7 @@ merge_infer_local_type :: proc(
return false
}
merged := types.widest(local.type, inferred)
if types.is_concrete_integer(merged) {
if types.constraint_accepts(local.declared, merged) {
local.type = merged
record_infer_local_type(local^, local_types)
return true
@@ -1605,6 +1605,10 @@ infer_statements :: proc(
}
if is_runtime_type(checker, declared_local) {
value_type = declared_local
} 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)
}
local := Infer_Local{
name=statement.name,
@@ -1749,6 +1753,9 @@ 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
}
return local_types, declared
@@ -2067,6 +2074,25 @@ build_constant_expr :: proc(
if types.is_concrete_integer(expected) {
recovery_type = expected
}
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
// float literal, mirroring build_float_expr's bit packing.
if constant.kind == .Value && types.is_float(expected, checker.target) {
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
bits := transmute(i64)fval
if types.bits(expected, checker.target) == 32 {
bits = i64(transmute(u32)f32(fval))
}
return add_hir_expr(checker, hir.Expr{
kind = .Float,
span = expr.span,
type = expected,
integer = bits,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
})
}
if constant.kind == .Div_By_Zero {
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
@@ -3427,6 +3453,24 @@ build_block :: proc(
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr)) {
declared = ctx.local_types[statement_id]
}
// A still-unresolved constraint means the initializer's numeric
// family did not satisfy `int`/`float` (`undefined` reports its own).
if types.is_constraint(declared) && !is_undefined_expr(checker, statement.expr) {
id := source.addf(
checker.diagnostics,
statement.span,
"could not resolve the '%s' constraint for local '%s'",
types.name(declared),
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(&checker.module.statements, hir.Stmt{
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL, diagnostic = id,
})
ctx.problematic^ = true
continue
}
expected := types.INVALID
value := hir.INVALID_EXPR
value_type := types.INVALID
+1
View File
@@ -38,6 +38,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "void": return .Keyword_Void
case "bool": return .Keyword_Bool
case "int": return .Keyword_Int
case "float": return .Keyword_Float
case "i8": return .Keyword_I8
case "i16": return .Keyword_I16
case "i32": return .Keyword_I32
+4 -1
View File
@@ -89,7 +89,7 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast
is_type_token :: proc(kind: token.Kind) -> bool {
#partial switch kind {
case .Keyword_Int, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
case .Keyword_Int, .Keyword_Float, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
@@ -216,6 +216,9 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
case .Keyword_Int:
advance(parser)
return types.INT
case .Keyword_Float:
advance(parser)
return types.FLOAT
case .Keyword_I8:
advance(parser)
return types.I8
+1
View File
@@ -72,6 +72,7 @@ Kind :: enum u8 {
Keyword_Void,
Keyword_Bool,
Keyword_Int,
Keyword_Float,
Keyword_I8,
Keyword_I16,
Keyword_I32,
+36 -1
View File
@@ -41,6 +41,7 @@ C_DOUBLE :: Type(27)
C_LONGDOUBLE :: Type(28)
BOOL :: Type(29)
FLOAT :: Type(30)
DYNAMIC_START :: Type(64)
@@ -55,6 +56,7 @@ Kind :: enum u8 {
Invalid,
Void,
Int_Constraint,
Float_Constraint,
Scalar,
Array,
Pointer,
@@ -295,6 +297,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
return .Void
case INT:
return .Int_Constraint
case FLOAT:
return .Float_Constraint
case BOOL:
return .Scalar
}
@@ -334,7 +338,37 @@ is_bool :: proc(value: Type) -> bool {
}
is_constraint :: proc(value: Type) -> bool {
return value == INT
return value == INT || value == FLOAT
}
// constraint_target reports the concrete type a constraint binding takes for an
// inferred value, or INVALID if the value's numeric family is incompatible.
// FLOAT accepts integers by defaulting them to f64: a constant integer becomes a
// float literal in build_constant_expr, while a runtime integer then fails the
// cross-family f64 coercion in coerce_expr (the intended mismatch error).
constraint_target :: proc(constraint, inferred: Type) -> Type {
switch constraint {
case INT:
return inferred if is_concrete_integer(inferred) else INVALID
case FLOAT:
if is_float(inferred) {
return inferred
}
return F64 if is_concrete_integer(inferred) else INVALID
}
return INVALID
}
// constraint_accepts reports strict numeric-family membership, used when widening
// a constraint binding across assignments (no integer-to-float defaulting here).
constraint_accepts :: proc(constraint, concrete: Type) -> bool {
switch constraint {
case INT:
return is_concrete_integer(concrete)
case FLOAT:
return is_float(concrete)
}
return false
}
is_c :: proc(value: Type) -> bool {
@@ -1140,6 +1174,7 @@ name :: proc(value: Type) -> string {
case VOID: return "void"
case BOOL: return "bool"
case INT: return "int"
case FLOAT: return "float"
case I8: return "i8"
case I16: return "i16"
case I32: return "i32"