add float constraint type
This commit is contained in:
@@ -181,7 +181,29 @@
|
|||||||
- disallow: `b :: undefined` since assigning undefined to something that can't change defeats the purpose
|
- disallow: `b :: undefined` since assigning undefined to something that can't change defeats the purpose
|
||||||
- disallow assigning `undefined` after declaration; use optionals and `none` for values that intentionally move back to an empty state
|
- disallow assigning `undefined` after declaration; use optionals and `none` for values that intentionally move back to an empty state
|
||||||
|
|
||||||
13. broaden type inference from surrounding context
|
13. introduce a `float` type constraint (similar to `int`) (implemented)
|
||||||
|
- resolves a local binding to any float scalar (`f32`/`f64`) via static analysis; widens
|
||||||
|
`f32` -> `f64` across assignments, mirroring how `int` picks the smallest fitting integer
|
||||||
|
- on a local declaration, integer literals satisfy `float` and default to `f64`
|
||||||
|
(`pi float = 3` is `3.0`); a runtime integer (`x float = some_i32`) stays a
|
||||||
|
`cannot implicitly convert` error
|
||||||
|
- a local initializer whose numeric family doesn't satisfy the constraint now errors for
|
||||||
|
both `int` and `float` instead of silently taking the initializer's natural type
|
||||||
|
- as with `int`, a constraint in a param/result position is a generic passthrough (it
|
||||||
|
forwards the inferred type unchanged, e.g. an identity `func(v int) int` over a range),
|
||||||
|
so the literal-as-float and family checks apply to local bindings, not passthroughs
|
||||||
|
|
||||||
|
14. add slice-by-range
|
||||||
|
- allow the use of a range in slice expressions:
|
||||||
|
```
|
||||||
|
excl_range range :: 0..10
|
||||||
|
some_arr[excl_range] # slice by named exclusive range
|
||||||
|
|
||||||
|
incl_range range :: 0..=10
|
||||||
|
some_arr[incl_range] # slice by named inclusive range
|
||||||
|
```
|
||||||
|
|
||||||
|
15. broaden type inference from surrounding context
|
||||||
|
|
||||||
## A word on multi-unwrap
|
## A word on multi-unwrap
|
||||||
|
|
||||||
|
|||||||
@@ -1541,7 +1541,7 @@ merge_infer_local_type :: proc(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if types.is_constraint(local.declared) {
|
if types.is_constraint(local.declared) {
|
||||||
if !types.is_concrete_integer(inferred) {
|
if !types.constraint_accepts(local.declared, inferred) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !is_runtime_type(checker, local.type) {
|
if !is_runtime_type(checker, local.type) {
|
||||||
@@ -1553,7 +1553,7 @@ merge_infer_local_type :: proc(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
merged := types.widest(local.type, inferred)
|
merged := types.widest(local.type, inferred)
|
||||||
if types.is_concrete_integer(merged) {
|
if types.constraint_accepts(local.declared, merged) {
|
||||||
local.type = merged
|
local.type = merged
|
||||||
record_infer_local_type(local^, local_types)
|
record_infer_local_type(local^, local_types)
|
||||||
return true
|
return true
|
||||||
@@ -1605,6 +1605,10 @@ infer_statements :: proc(
|
|||||||
}
|
}
|
||||||
if is_runtime_type(checker, declared_local) {
|
if is_runtime_type(checker, declared_local) {
|
||||||
value_type = 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{
|
local := Infer_Local{
|
||||||
name=statement.name,
|
name=statement.name,
|
||||||
@@ -1749,6 +1753,9 @@ infer_spec_locals_and_result :: proc(
|
|||||||
result := types.INVALID
|
result := types.INVALID
|
||||||
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
infer_statements(checker, function.body, &locals, local_types, function.pkg, function.file, demanded, &result, result_hint)
|
||||||
if types.is_constraint(declared) {
|
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, result
|
||||||
}
|
}
|
||||||
return local_types, declared
|
return local_types, declared
|
||||||
@@ -2067,6 +2074,25 @@ build_constant_expr :: proc(
|
|||||||
if types.is_concrete_integer(expected) {
|
if types.is_concrete_integer(expected) {
|
||||||
recovery_type = 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 {
|
if constant.kind == .Div_By_Zero {
|
||||||
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
|
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
|
||||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
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)) {
|
(types.is_constraint(declared) || is_undefined_expr(checker, statement.expr)) {
|
||||||
declared = ctx.local_types[statement_id]
|
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
|
expected := types.INVALID
|
||||||
value := hir.INVALID_EXPR
|
value := hir.INVALID_EXPR
|
||||||
value_type := types.INVALID
|
value_type := types.INVALID
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "void": return .Keyword_Void
|
case "void": return .Keyword_Void
|
||||||
case "bool": return .Keyword_Bool
|
case "bool": return .Keyword_Bool
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
|
case "float": return .Keyword_Float
|
||||||
case "i8": return .Keyword_I8
|
case "i8": return .Keyword_I8
|
||||||
case "i16": return .Keyword_I16
|
case "i16": return .Keyword_I16
|
||||||
case "i32": return .Keyword_I32
|
case "i32": return .Keyword_I32
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast
|
|||||||
|
|
||||||
is_type_token :: proc(kind: token.Kind) -> bool {
|
is_type_token :: proc(kind: token.Kind) -> bool {
|
||||||
#partial switch kind {
|
#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_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||||
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||||
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
||||||
@@ -216,6 +216,9 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Int:
|
case .Keyword_Int:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.INT
|
return types.INT
|
||||||
|
case .Keyword_Float:
|
||||||
|
advance(parser)
|
||||||
|
return types.FLOAT
|
||||||
case .Keyword_I8:
|
case .Keyword_I8:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.I8
|
return types.I8
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Void,
|
Keyword_Void,
|
||||||
Keyword_Bool,
|
Keyword_Bool,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
|
Keyword_Float,
|
||||||
Keyword_I8,
|
Keyword_I8,
|
||||||
Keyword_I16,
|
Keyword_I16,
|
||||||
Keyword_I32,
|
Keyword_I32,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ C_DOUBLE :: Type(27)
|
|||||||
C_LONGDOUBLE :: Type(28)
|
C_LONGDOUBLE :: Type(28)
|
||||||
|
|
||||||
BOOL :: Type(29)
|
BOOL :: Type(29)
|
||||||
|
FLOAT :: Type(30)
|
||||||
|
|
||||||
DYNAMIC_START :: Type(64)
|
DYNAMIC_START :: Type(64)
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ Kind :: enum u8 {
|
|||||||
Invalid,
|
Invalid,
|
||||||
Void,
|
Void,
|
||||||
Int_Constraint,
|
Int_Constraint,
|
||||||
|
Float_Constraint,
|
||||||
Scalar,
|
Scalar,
|
||||||
Array,
|
Array,
|
||||||
Pointer,
|
Pointer,
|
||||||
@@ -295,6 +297,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|||||||
return .Void
|
return .Void
|
||||||
case INT:
|
case INT:
|
||||||
return .Int_Constraint
|
return .Int_Constraint
|
||||||
|
case FLOAT:
|
||||||
|
return .Float_Constraint
|
||||||
case BOOL:
|
case BOOL:
|
||||||
return .Scalar
|
return .Scalar
|
||||||
}
|
}
|
||||||
@@ -334,7 +338,37 @@ is_bool :: proc(value: Type) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is_constraint :: 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 {
|
is_c :: proc(value: Type) -> bool {
|
||||||
@@ -1140,6 +1174,7 @@ name :: proc(value: Type) -> string {
|
|||||||
case VOID: return "void"
|
case VOID: return "void"
|
||||||
case BOOL: return "bool"
|
case BOOL: return "bool"
|
||||||
case INT: return "int"
|
case INT: return "int"
|
||||||
|
case FLOAT: return "float"
|
||||||
case I8: return "i8"
|
case I8: return "i8"
|
||||||
case I16: return "i16"
|
case I16: return "i16"
|
||||||
case I32: return "i32"
|
case I32: return "i32"
|
||||||
|
|||||||
+208
-5
@@ -5136,6 +5136,209 @@ main :: func() void {
|
|||||||
testing.expect(t, found_value_i16)
|
testing.expect(t, found_value_i16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float_local_type :: proc(hir_module: ^hir.Module, symbols: ^symbol.Table, function_name, local_name: string) -> (types.Type, bool) {
|
||||||
|
fn_symbol := symbol.intern(symbols, function_name)
|
||||||
|
loc_symbol := symbol.intern(symbols, local_name)
|
||||||
|
for function in hir_module.functions {
|
||||||
|
if function.name != fn_symbol {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for local in function.locals {
|
||||||
|
if local.name == loc_symbol {
|
||||||
|
return local.type, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.INVALID, false
|
||||||
|
}
|
||||||
|
|
||||||
|
float_result_type :: proc(hir_module: ^hir.Module, symbols: ^symbol.Table, function_name: string) -> (types.Type, bool) {
|
||||||
|
fn_symbol := symbol.intern(symbols, function_name)
|
||||||
|
for function in hir_module.functions {
|
||||||
|
if function.name == fn_symbol {
|
||||||
|
return function.result, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return types.INVALID, false
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_constraint_resolves_to_f64 :: proc(t: ^testing.T) {
|
||||||
|
text := `make :: func() float {
|
||||||
|
pi float = 3.14
|
||||||
|
return pi
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = make()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
pi_type, found := float_local_type(&hir_module, &symbols, "make", "pi")
|
||||||
|
result_type, _ := float_result_type(&hir_module, &symbols, "make")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, pi_type, types.F64)
|
||||||
|
testing.expect_value(t, result_type, types.F64)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_constraint_accepts_integer_literal :: proc(t: ^testing.T) {
|
||||||
|
text := `make :: func() float {
|
||||||
|
pi float = 3
|
||||||
|
return pi
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = make()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
pi_type, found := float_local_type(&hir_module, &symbols, "make", "pi")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, pi_type, types.F64)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_constraint_result_resolves_to_f64 :: proc(t: ^testing.T) {
|
||||||
|
text := `make :: func() float {
|
||||||
|
return 3.0
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = make()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
result_type, found := float_result_type(&hir_module, &symbols, "make")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, result_type, types.F64)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_constraint_widens_f32_to_f64 :: proc(t: ^testing.T) {
|
||||||
|
text := `wide :: func(a f32, b f64) float {
|
||||||
|
x float = a
|
||||||
|
x = b
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = wide(1.0, 2.0)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
x_type, found := float_local_type(&hir_module, &symbols, "wide", "x")
|
||||||
|
result_type, _ := float_result_type(&hir_module, &symbols, "wide")
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, x_type, types.F64)
|
||||||
|
testing.expect_value(t, result_type, types.F64)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
int_constraint_rejects_float_initializer :: proc(t: ^testing.T) {
|
||||||
|
text := `main :: func() void {
|
||||||
|
x int = 1.0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_constraint_error := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_constraint_error = found_constraint_error ||
|
||||||
|
strings.contains(diagnostic.message, "could not resolve the 'int' constraint for local 'x'")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect(t, found_constraint_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
float_constraint_rejects_runtime_integer :: proc(t: ^testing.T) {
|
||||||
|
text := `take :: func(n i32) void {
|
||||||
|
x float = n
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
take(7)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
found_convert_error := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_convert_error = found_convert_error ||
|
||||||
|
strings.contains(diagnostic.message, "cannot implicitly convert i32 to f64")
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.expect(t, found_convert_error)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
undefined_accepts_concrete_runtime_annotations :: proc(t: ^testing.T) {
|
undefined_accepts_concrete_runtime_annotations :: proc(t: ^testing.T) {
|
||||||
text := `Point :: struct {
|
text := `Point :: struct {
|
||||||
@@ -5423,11 +5626,11 @@ compound_assignment_preserves_checked_numeric_operations :: proc(t: ^testing.T)
|
|||||||
unsigned -= 2
|
unsigned -= 2
|
||||||
unsigned *= 3
|
unsigned *= 3
|
||||||
unsigned /= 4
|
unsigned /= 4
|
||||||
float f64 = 24.0
|
real f64 = 24.0
|
||||||
float += 6.0
|
real += 6.0
|
||||||
float -= 2.0
|
real -= 2.0
|
||||||
float *= 3.0
|
real *= 3.0
|
||||||
float /= 4.0
|
real /= 4.0
|
||||||
return signed
|
return signed
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user