fix: require exact contextual integer to float typing
This commit is contained in:
@@ -31,6 +31,8 @@ roadmap and milestone history.
|
|||||||
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
|
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
|
||||||
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
|
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
|
||||||
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar casts through keywords or transparent aliases, such as `i32(x)`, `c_float(x)`, or `StringId(x)`
|
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar casts through keywords or transparent aliases, such as `i32(x)`, `c_float(x)`, or `StringId(x)`
|
||||||
|
- implicit contextual integer constants must be exactly representable in the destination float, including folded expressions and inferred constants; `f32` rejects `16777217` but accepts `16777218`, while `f64` rejects `9007199254740993` but accepts `9007199254740994`; powers of two remain exact
|
||||||
|
- float literals retain IEEE rounding, and explicit scalar casts such as `f32(16777217)` or `f64(9007199254740993)` deliberately permit rounding; existing typed-value widening and C scalar coercion rules are unchanged
|
||||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
|
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native, C, and scalar-backed distinct integer types; the result retains `T`
|
||||||
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
|
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
|
||||||
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
|
- pointer mutability via `mut`, optional pointers as nullable pointers, pointer arithmetic for many-item pointers, postfix dereference `^`, and trapping optional unwrap `?`
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ Current prototype features:
|
|||||||
- String literals as immutable pointers to static zero-terminated byte arrays
|
- String literals as immutable pointers to static zero-terminated byte arrays
|
||||||
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
|
||||||
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
|
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
|
||||||
|
- Contextual integer-to-float constants require exact representation (`f32` accepts `16777218`, not `16777217`; `f64` accepts `9007199254740994`, not `9007199254740993`), including folded and inferred constants; float literals and explicit scalar casts retain IEEE rounding
|
||||||
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
|
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
|
||||||
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds, reflection, and recursive standard formatting
|
- Scalar-backed nominal `distinct` types with same-identity runtime/comptime operators, explicit backing extraction casts, integer bounds, reflection, and recursive standard formatting
|
||||||
- Directory packages with merged declarations and file-local relative imports
|
- Directory packages with merged declarations and file-local relative imports
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "../source"
|
|||||||
import "../symbol"
|
import "../symbol"
|
||||||
import "../target"
|
import "../target"
|
||||||
import "../types"
|
import "../types"
|
||||||
|
import "base:intrinsics"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
@@ -1206,6 +1207,33 @@ fits_integer_type :: proc(value: i128, value_type: types.Type, selected := targe
|
|||||||
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
|
return fits_signed_type(value, value_type, selected) || fits_unsigned_type(value, value_type, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integer_fits_float :: proc(value: i128, expected: types.Type, selected: target.Target) -> bool {
|
||||||
|
if !types.is_float(expected, selected) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
precision := 0
|
||||||
|
switch types.bits(expected, selected) {
|
||||||
|
case 32: precision = 24
|
||||||
|
case 64: precision = 53
|
||||||
|
case: return false
|
||||||
|
}
|
||||||
|
if value == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
magnitude := u128(0)
|
||||||
|
if value < 0 {
|
||||||
|
magnitude = u128(-(value + 1)) + 1
|
||||||
|
} else {
|
||||||
|
magnitude = u128(value)
|
||||||
|
}
|
||||||
|
// Binary floats preserve the significant bits between the first and last one.
|
||||||
|
// Trailing zeros cost no precision, so large powers of two remain exact.
|
||||||
|
// Unsigned magnitude also handles i128's minimum without a float round-trip.
|
||||||
|
significant_bits := 128 - int(intrinsics.count_leading_zeros(magnitude)) -
|
||||||
|
int(intrinsics.count_trailing_zeros(magnitude))
|
||||||
|
return significant_bits <= precision
|
||||||
|
}
|
||||||
|
|
||||||
fits_i64 :: proc(value: i128) -> bool {
|
fits_i64 :: proc(value: i128) -> bool {
|
||||||
return fits_signed_type(value, types.I64)
|
return fits_signed_type(value, types.I64)
|
||||||
}
|
}
|
||||||
@@ -7625,12 +7653,15 @@ build_constant_expr :: proc(
|
|||||||
numeric_operation := false,
|
numeric_operation := false,
|
||||||
) -> hir.Expr_Id {
|
) -> hir.Expr_Id {
|
||||||
materialized := expected
|
materialized := expected
|
||||||
|
for types.is_optional(materialized, &checker.module.types) {
|
||||||
|
materialized = types.child_type(materialized, &checker.module.types)
|
||||||
|
}
|
||||||
nominal := types.INVALID
|
nominal := types.INVALID
|
||||||
if numeric_operation {
|
if numeric_operation {
|
||||||
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
|
if representation, ok := types.distinct_scalar_backing(materialized, &checker.module.types);
|
||||||
ok && !types.is_bool(representation) {
|
ok && !types.is_bool(representation) {
|
||||||
|
nominal = materialized
|
||||||
materialized = representation
|
materialized = representation
|
||||||
nominal = expected
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recovery_type := types.I64
|
recovery_type := types.I64
|
||||||
@@ -7640,7 +7671,15 @@ build_constant_expr :: proc(
|
|||||||
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
|
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
|
||||||
// float literal, mirroring build_float_expr's bit packing.
|
// float literal, mirroring build_float_expr's bit packing.
|
||||||
if constant.kind == .Value && types.is_float(materialized, checker.target) {
|
if constant.kind == .Value && types.is_float(materialized, checker.target) {
|
||||||
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
|
if !integer_fits_float(constant.value, materialized, checker.target) {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics, expr.span,
|
||||||
|
"integer constant %d is not exactly representable in %s",
|
||||||
|
constant.value, type_label(checker, materialized),
|
||||||
|
)
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
|
}
|
||||||
|
fval := f64(constant.value)
|
||||||
bits := transmute(i64)fval
|
bits := transmute(i64)fval
|
||||||
if types.bits(materialized, checker.target) == 32 {
|
if types.bits(materialized, checker.target) == 32 {
|
||||||
bits = i64(transmute(u32)f32(fval))
|
bits = i64(transmute(u32)f32(fval))
|
||||||
|
|||||||
@@ -761,11 +761,20 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
|||||||
return ct_add_value(state, value), true
|
return ct_add_value(state, value), true
|
||||||
}
|
}
|
||||||
if types.is_float(expected, state.checker.target) {
|
if types.is_float(expected, state.checker.target) {
|
||||||
|
if !integer_fits_float(value.integer, expected, state.checker.target) {
|
||||||
|
return INVALID_CT_VALUE, ct_failf(
|
||||||
|
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
|
||||||
|
value.integer, type_label(state.checker, expected),
|
||||||
|
)
|
||||||
|
}
|
||||||
return ct_add_value(state, Ct_Value{kind=.Float, type=expected, float=f64(value.integer)}), true
|
return ct_add_value(state, Ct_Value{kind=.Float, type=expected, float=f64(value.integer)}), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value.kind == .Float && types.is_float(expected, state.checker.target) {
|
if value.kind == .Float && types.is_float(expected, state.checker.target) {
|
||||||
value.type = expected
|
value.type = expected
|
||||||
|
if types.bits(expected, state.checker.target) == 32 {
|
||||||
|
value.float = f64(f32(value.float))
|
||||||
|
}
|
||||||
return ct_add_value(state, value), true
|
return ct_add_value(state, value), true
|
||||||
}
|
}
|
||||||
return INVALID_CT_VALUE, ct_failf(
|
return INVALID_CT_VALUE, ct_failf(
|
||||||
@@ -1351,6 +1360,12 @@ ct_eval_expr :: proc(
|
|||||||
if numeric_operation {
|
if numeric_operation {
|
||||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
|
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
|
||||||
if types.is_float(representation, checker.target) {
|
if types.is_float(representation, checker.target) {
|
||||||
|
if !integer_fits_float(i128(expr.integer), representation, checker.target) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||||
|
state, .Not_Comptime, expr.span, "integer constant %d is not exactly representable in %s",
|
||||||
|
i128(expr.integer), type_label(checker, expected),
|
||||||
|
)
|
||||||
|
}
|
||||||
return ct_add_value(state, Ct_Value{
|
return ct_add_value(state, Ct_Value{
|
||||||
kind=.Float, type=expected, float=f64(expr.integer),
|
kind=.Float, type=expected, float=f64(expr.integer),
|
||||||
}), ct_flow(.Normal), true
|
}), ct_flow(.Normal), true
|
||||||
@@ -1376,13 +1391,21 @@ ct_eval_expr :: proc(
|
|||||||
if numeric_operation {
|
if numeric_operation {
|
||||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
|
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
|
||||||
distinct_ok && types.is_float(representation, checker.target) {
|
distinct_ok && types.is_float(representation, checker.target) {
|
||||||
|
value := transmute(f64)expr.integer
|
||||||
|
if types.bits(representation, checker.target) == 32 {
|
||||||
|
value = f64(f32(value))
|
||||||
|
}
|
||||||
return ct_add_value(state, Ct_Value{
|
return ct_add_value(state, Ct_Value{
|
||||||
kind=.Float, type=expected, float=transmute(f64)expr.integer,
|
kind=.Float, type=expected, float=value,
|
||||||
}), ct_flow(.Normal), true
|
}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value_type := expected if types.is_float(expected, checker.target) else types.F64
|
value_type := expected if types.is_float(expected, checker.target) else types.F64
|
||||||
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
|
value := transmute(f64)expr.integer
|
||||||
|
if types.bits(value_type, checker.target) == 32 {
|
||||||
|
value = f64(f32(value))
|
||||||
|
}
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=value}), ct_flow(.Normal), true
|
||||||
case .String:
|
case .String:
|
||||||
return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.String, type=string_literal_type(checker, expr.integer), index=expr.integer}), ct_flow(.Normal), true
|
||||||
case .Name:
|
case .Name:
|
||||||
@@ -2679,6 +2702,30 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
|||||||
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
|
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||||
}
|
}
|
||||||
|
result_type := operation.result
|
||||||
|
if !distinct_operation {
|
||||||
|
result_type = types.widest(left.type, right.type)
|
||||||
|
if left.kind != .Float {
|
||||||
|
result_type = right.type
|
||||||
|
} else if right.kind != .Float {
|
||||||
|
result_type = left.type
|
||||||
|
}
|
||||||
|
if !types.is_float(result_type, state.checker.target) {
|
||||||
|
result_type = types.F64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
representation := types.runtime_representation(result_type, store)
|
||||||
|
operands := [2]Ct_Value_Id{left_id, right_id}
|
||||||
|
for operand_id in operands {
|
||||||
|
operand := &state.values[operand_id]
|
||||||
|
if operand.kind != .Float &&
|
||||||
|
!integer_fits_float(operand.integer, representation, state.checker.target) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||||
|
state, .Not_Comptime, span, "integer constant %d is not exactly representable in %s",
|
||||||
|
operand.integer, type_label(state.checker, result_type),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
lf := left.float if left.kind == .Float else f64(left.integer)
|
lf := left.float if left.kind == .Float else f64(left.integer)
|
||||||
rf := right.float if right.kind == .Float else f64(right.integer)
|
rf := right.float if right.kind == .Float else f64(right.integer)
|
||||||
if is_compare {
|
if is_compare {
|
||||||
@@ -2702,14 +2749,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
|||||||
case:
|
case:
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||||
}
|
}
|
||||||
result_type := operation.result
|
if types.bits(representation, state.checker.target) == 32 {
|
||||||
if !distinct_operation {
|
|
||||||
result_type = types.widest(left.type, right.type)
|
|
||||||
if !types.is_float(result_type, state.checker.target) {
|
|
||||||
result_type = types.F64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if types.bits(result_type, state.checker.target) == 32 {
|
|
||||||
result = f64(f32(result))
|
result = f64(f32(result))
|
||||||
}
|
}
|
||||||
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
||||||
@@ -3144,13 +3184,20 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
|
|||||||
if value.kind == .Float {
|
if value.kind == .Float {
|
||||||
if types.is_float(target, state.checker.target) {
|
if types.is_float(target, state.checker.target) {
|
||||||
value.type = target
|
value.type = target
|
||||||
|
if types.bits(target, state.checker.target) == 32 {
|
||||||
|
value.float = f64(f32(value.float))
|
||||||
|
}
|
||||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.Integer, type=target, integer=i128(value.float)}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
if ct_is_integer_like(value) {
|
if ct_is_integer_like(value) {
|
||||||
if types.is_float(target, state.checker.target) {
|
if types.is_float(target, state.checker.target) {
|
||||||
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=f64(value.integer)}), ct_flow(.Normal), true
|
result := f64(value.integer)
|
||||||
|
if types.bits(target, state.checker.target) == 32 {
|
||||||
|
result = f64(f32(result))
|
||||||
|
}
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Float, type=target, float=result}), ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
value.kind = .Integer
|
value.kind = .Integer
|
||||||
value.type = target
|
value.type = target
|
||||||
|
|||||||
@@ -15686,6 +15686,143 @@ main func() void {
|
|||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
contextual_integer_float_precision_loss_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
|
Boundary :: struct {scalar, integer: string}
|
||||||
|
boundaries := []Boundary{
|
||||||
|
{"f32", "16777217"},
|
||||||
|
{"f32", "-16777217"},
|
||||||
|
{"f64", "9007199254740993"},
|
||||||
|
{"f64", "-9007199254740993"},
|
||||||
|
}
|
||||||
|
Context :: struct {declarations, body: string}
|
||||||
|
contexts := []Context{
|
||||||
|
{"bad Scalar :: VALUE", "_ = bad"},
|
||||||
|
{"bad Scalar := VALUE", "_ = bad"},
|
||||||
|
{"bad Scalar :: $VALUE", "_ = bad"},
|
||||||
|
{"bad Scalar :: ${ yield VALUE }", "_ = bad"},
|
||||||
|
{"", "value Scalar := VALUE\n_ = value"},
|
||||||
|
{"", "local :: VALUE\nvalue Scalar := local\n_ = value"},
|
||||||
|
{"", "local :: VALUE + 2 - 2\nvalue Scalar := local\n_ = value"},
|
||||||
|
{"folded :: VALUE + 2 - 2", "value Scalar := folded\n_ = value"},
|
||||||
|
{"", "value Scalar := G\n_ = value"},
|
||||||
|
{"", "value Scalar := VALUE + 2 - 2\n_ = value"},
|
||||||
|
{"", "_ = take(VALUE)"},
|
||||||
|
{"", "return VALUE"},
|
||||||
|
{"", "values [1]Scalar := [VALUE]\n_ = values"},
|
||||||
|
{"", "value ?Scalar := VALUE\n_ = value"},
|
||||||
|
{"", "value ?Scalar := some!(VALUE)\n_ = value"},
|
||||||
|
{"", "seed Nominal := Nominal(0.0)\n_ = seed + VALUE"},
|
||||||
|
{"", "seed Scalar := 0.0\n_ = seed + VALUE"},
|
||||||
|
{"", "local :: VALUE\nseed Scalar := 0.0\n_ = local + seed"},
|
||||||
|
{"", "seed Scalar := 0.0\n_ = seed == VALUE"},
|
||||||
|
}
|
||||||
|
for boundary in boundaries {
|
||||||
|
for test_context in contexts {
|
||||||
|
body, body_allocated := strings.replace_all(test_context.body, "VALUE", boundary.integer)
|
||||||
|
declarations, declarations_allocated := strings.replace_all(test_context.declarations, "VALUE", boundary.integer)
|
||||||
|
evaluation_modes := [2]bool{false, true}
|
||||||
|
for comptime in evaluation_modes {
|
||||||
|
invocation := "$exercise()" if comptime else "exercise()"
|
||||||
|
text := fmt.aprintf(
|
||||||
|
"Scalar :: alias %s\nNominal :: distinct %s\nG :: %s\n%s\n"+
|
||||||
|
"take func(value Scalar) Scalar {{ return value }}\n"+
|
||||||
|
"exercise func() Scalar {{\n%s\nreturn 0\n}}\n"+
|
||||||
|
"main func() void {{ _ = %s }}\n",
|
||||||
|
boundary.scalar, boundary.scalar, boundary.integer, declarations, body, invocation,
|
||||||
|
)
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "not exactly representable")
|
||||||
|
}
|
||||||
|
testing.expect(t, found, text)
|
||||||
|
hir.destroy_module(&hir_module)
|
||||||
|
ast.destroy_module(&ast_module)
|
||||||
|
delete(stream.items)
|
||||||
|
symbol.destroy_table(&symbols)
|
||||||
|
source.destroy_diagnostics(&diagnostics)
|
||||||
|
delete(text)
|
||||||
|
}
|
||||||
|
if body_allocated { delete(body) }
|
||||||
|
if declarations_allocated { delete(declarations) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
exact_contextual_integer_floats_and_explicit_rounding_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-exact-integer-floats"
|
||||||
|
main_path := "/tmp/brolang-test-exact-integer-floats/main.bro"
|
||||||
|
output := "/tmp/brolang-test-exact-integer-floats-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os2.make_directory_all(directory) == nil)
|
||||||
|
text := `Nominal :: distinct f32
|
||||||
|
G :: 16777216
|
||||||
|
H :: 9007199254740992
|
||||||
|
take32 func(value f32) f32 { return value }
|
||||||
|
take64 func(value f64) f64 { return value }
|
||||||
|
give32 func() f32 { return -16777216 }
|
||||||
|
give64 func() f64 { return -9007199254740992 }
|
||||||
|
exercise func() i32 {
|
||||||
|
local32 :: 16777216
|
||||||
|
local64 :: 9007199254740992
|
||||||
|
a f32 := local32
|
||||||
|
b f64 := local64
|
||||||
|
c f32 := G
|
||||||
|
d f64 := H
|
||||||
|
folded32 f32 := 16777215 + 1
|
||||||
|
folded64 f64 := 9007199254740991 + 1
|
||||||
|
power32 f32 := 9223372036854775808
|
||||||
|
power64 f64 := -9223372036854775808
|
||||||
|
next32 f32 := 16777218
|
||||||
|
next64 f64 := 9007199254740994
|
||||||
|
values32 [2]f32 := [16777216, -16777216]
|
||||||
|
values64 [2]f64 := [9007199254740992, -9007199254740992]
|
||||||
|
optional32 ?f32 := 16777216
|
||||||
|
optional64 ?f64 := some!(-9007199254740992)
|
||||||
|
nominal := Nominal(0.0) + 16777216
|
||||||
|
literal32 f32 := 16777217.0
|
||||||
|
literal64 f64 := 9007199254740993.0
|
||||||
|
cast32 f32 := f32(16777217)
|
||||||
|
cast64 f64 := f64(9007199254740993)
|
||||||
|
negative_literal64 f64 := -9007199254740993.0
|
||||||
|
negative_cast32 f32 := f32(-16777217)
|
||||||
|
negative_cast64 f64 := f64(-9007199254740993)
|
||||||
|
if a != 16777216.0 or b != 9007199254740992.0 { return 1 }
|
||||||
|
if c != a or d != b or folded32 != a or folded64 != b { return 2 }
|
||||||
|
if take32(16777216) != a or take64(9007199254740992) != b { return 3 }
|
||||||
|
if give32() != -a or give64() != -b { return 4 }
|
||||||
|
if values32[1] != -a or values64[1] != -b { return 5 }
|
||||||
|
if (optional32 orelse 0.0) != a or (optional64 orelse 0.0) != -b { return 6 }
|
||||||
|
if f32(nominal) != a { return 7 }
|
||||||
|
if literal32 != a or literal64 != b { return 8 }
|
||||||
|
if cast32 != a or cast64 != b { return 9 }
|
||||||
|
if power32 != 9223372036854775808.0 or power64 != -9223372036854775808.0 { return 10 }
|
||||||
|
if next32 != 16777218.0 or next64 != 9007199254740994.0 { return 11 }
|
||||||
|
if negative_literal64 != -b { return 12 }
|
||||||
|
if negative_cast32 != -a or negative_cast64 != -b { return 13 }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
checked i32 :: $exercise()
|
||||||
|
if checked != 0 { return checked }
|
||||||
|
return exercise()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
|
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-root-diagnostic"
|
directory := "/tmp/brolang-test-root-diagnostic"
|
||||||
|
|||||||
Reference in New Issue
Block a user