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
+5 -4
View File
@@ -24,7 +24,7 @@ roadmap and milestone history.
### scalar, aggregate, and pointer types
- exact-width integers, `isize`, `usize`, `f32`, `f64`, `bool`, `void`, `anyopaque`, and contextual `int`, `float`, and `range` constraints
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, and `anyopaque`; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
- 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, and compile-time folding for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
@@ -44,16 +44,17 @@ roadmap and milestone history.
#### native record constraint fields
A direct `int`, `float`, or `range` field in a named native struct or union is a
A direct `int`, `uint`, `float`, or `range` field in a named native struct or union is a
program-wide constraint, not per-value polymorphism. Before record layout, all reachable keyed
constructors, field assignments, and concrete uses of field reads contribute demands and the field
resolves once to one concrete runtime type. Compatible scalar demands widen normally. Integer
literals remain provisional until inference settles, so a later `usize` use can resolve an `int`
field to `usize`; otherwise literal-only `int` fields use the widest smallest-signed type required,
and literal-only `float` fields use `f64`.
literal-only `uint` fields use the widest smallest-unsigned type required, and literal-only `float`
fields use `f64`.
An undemanded field or incompatible demands are errors. This inference applies only to direct
fields of named native records. `c_struct` fields, nested constraints such as `[]int`, and fields in
fields of named native records. `c_struct` fields, nested constraints such as `[]int` / `[]uint`, and fields in
anonymous generated records still require concrete runtime types.
#### keyword member names
+6
View File
@@ -907,6 +907,12 @@
- filtering, skipping, fixtures, snapshots, parallelism, isolation, allocators, and more assertion
families remain deferred
42. unsigned integer constraint (implemented)
- `uint` is the unsigned subset constraint while `int` remains the whole integer family
- literal-only values choose the smallest fitting `u8`, `u16`, `u32`, or `u64`
- native unsigned scalars and target-classified unsigned C scalars satisfy the constraint
- `isize` and `usize` remain concrete pointer-sized types
## A word on unchecked casts
For casts that bypass safety checks, Honey provides builtin functions:
+97 -33
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,14 +3644,22 @@ 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)
}
}
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,
+1
View File
@@ -52,6 +52,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "anyopaque": return .Keyword_Anyopaque
case "bool": return .Keyword_Bool
case "int": return .Keyword_Int
case "uint": return .Keyword_Uint
case "float": return .Keyword_Float
case "range": return .Keyword_Range
case "i8": return .Keyword_I8
+5 -2
View File
@@ -146,7 +146,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_Float, .Keyword_Range, .Keyword_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .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,
@@ -280,6 +280,9 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
case .Keyword_Int:
advance(parser)
return types.INT
case .Keyword_Uint:
advance(parser)
return types.UINT
case .Keyword_Float:
advance(parser)
return types.FLOAT
@@ -774,7 +777,7 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
tok := current(parser)
#partial switch tok.kind {
case .Keyword_Int, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
start := tok
target := parse_type_atom(parser)
return add_expr(parser, ast.Expr{
+1
View File
@@ -88,6 +88,7 @@ Kind :: enum u8 {
Keyword_Anyopaque,
Keyword_Bool,
Keyword_Int,
Keyword_Uint,
Keyword_Float,
Keyword_Range,
Keyword_I8,
+23 -1
View File
@@ -44,6 +44,7 @@ BOOL :: Type(29)
FLOAT :: Type(30)
RANGE :: Type(31)
ANYOPAQUE :: Type(32)
UINT :: Type(33)
DYNAMIC_START :: Type(64)
@@ -59,6 +60,7 @@ Kind :: enum u8 {
Void,
Anyopaque,
Int_Constraint,
Uint_Constraint,
Float_Constraint,
Range_Constraint,
Scalar,
@@ -595,6 +597,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
return .Anyopaque
case INT:
return .Int_Constraint
case UINT:
return .Uint_Constraint
case FLOAT:
return .Float_Constraint
case RANGE:
@@ -642,7 +646,7 @@ is_bool :: proc(value: Type) -> bool {
}
is_constraint :: proc(value: Type) -> bool {
return value == INT || value == FLOAT || value == RANGE
return value == INT || value == UINT || value == FLOAT || value == RANGE
}
// constraint_target reports the concrete type a constraint binding (local, or a
@@ -656,6 +660,8 @@ constraint_target :: proc(constraint, inferred: Type, store: ^Store = nil) -> Ty
switch constraint {
case INT:
return inferred if is_concrete_integer(inferred) else INVALID
case UINT:
return inferred if is_unsigned(inferred) else INVALID
case FLOAT:
if is_float(inferred) {
return inferred
@@ -673,6 +679,8 @@ constraint_accepts :: proc(constraint, concrete: Type, store: ^Store = nil) -> b
switch constraint {
case INT:
return is_concrete_integer(concrete)
case UINT:
return is_unsigned(concrete)
case FLOAT:
return is_float(concrete)
case RANGE:
@@ -1686,6 +1694,19 @@ smallest_signed_for_literal :: proc(value: i64) -> Type {
return I64
}
smallest_unsigned_for_literal :: proc(value: u64) -> Type {
if value <= 255 {
return U8
}
if value <= 65535 {
return U16
}
if value <= 4294967295 {
return U32
}
return U64
}
name :: proc(value: Type) -> string {
switch value {
case INVALID: return "<invalid>"
@@ -1693,6 +1714,7 @@ name :: proc(value: Type) -> string {
case ANYOPAQUE: return "anyopaque"
case BOOL: return "bool"
case INT: return "int"
case UINT: return "uint"
case FLOAT: return "float"
case RANGE: return "range"
case I8: return "i8"
+188 -1
View File
@@ -1485,6 +1485,7 @@ main func() void {
_ = maxval!(u8, u16)
_ = minval!(1)
_ = maxval!(int)
_ = maxval!(uint)
_ = maxval!(f32)
_ = maxval!(bool)
_ = maxval!(Named)
@@ -1513,7 +1514,7 @@ main func() void {
}
testing.expect_value(t, bad_arity, 2)
testing.expect(t, bad_type)
testing.expect_value(t, bad_target, 5)
testing.expect_value(t, bad_target, 6)
}
@(test)
@@ -12876,3 +12877,189 @@ dependency_passes test {
testing.expect(t, strings.contains(output, root_path))
testing.expect(t, strings.contains(output, "3 passed, 1 failed"))
}
@(test)
uint_constraint_parses_as_type_and_comptime_value :: proc(t: ^testing.T) {
text := `value uint :: 1
Constraint :: uint
`
source_file := source.Source{path="uint.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)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(module.globals), 2)
testing.expect_value(t, module.globals[0].type, types.UINT)
testing.expect_value(t, module.exprs[module.globals[1].expr].kind, ast.Expr_Kind.Type)
testing.expect_value(t, module.exprs[module.globals[1].expr].type, types.UINT)
}
@(test)
uint_constraint_uses_smallest_unsigned_types_and_widens :: proc(t: ^testing.T) {
text := `Zero uint :: 0
Byte uint :: 255
Word uint :: 256
Dword uint :: 65536
Maximum uint :: 18446744073709551615
Counter uint = 1
widen func() uint {
value uint = 1
value = 1000
return value
}
widen_global func() void { Counter = 1000 }
main func() void {
_ = Zero
_ = Byte
_ = Word
_ = Dword
_ = Maximum
_ = widen()
widen_global()
}
`
source_file := source.Source{path="uint_types.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)
expected := []types.Type{types.U8, types.U8, types.U16, types.U32, types.U64, types.U16}
for value_type, index in expected {
testing.expect_value(t, hir_module.globals[index].type, value_type)
}
widen_symbol := symbol.intern(&symbols, "widen")
found_widen := false
for function in hir_module.functions {
if function.name != widen_symbol {
continue
}
found_widen = true
testing.expect_value(t, function.result, types.U16)
testing.expect_value(t, function.locals[0].type, types.U16)
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, found_widen)
}
@(test)
uint_constraint_infers_boundaries_calls_and_records :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-uint-constraint"
main_path := "/tmp/brolang-test-uint-constraint/main.bro"
output := "/tmp/brolang-test-uint-constraint-output"
text := `Box :: struct { value uint }
identity func(value uint) uint { return value }
whole func(value int) int { return value }
max_value func() uint { return 18446744073709551615 }
make_box func(value usize) Box { return Box{value = value} }
main func() i32 {
zero uint :: 0
byte uint :: 255
word uint :: 256
word_max uint :: 65535
dword uint :: 65536
maximum uint :: 18446744073709551615
platform usize = 7
c_value c_uint = c_uint(9)
box Box = make_box(7)
signed isize = -1
if (identity(zero) != 0) return 1
if (identity(byte) != 255) return 2
if (identity(word) != 256) return 3
if (identity(word_max) != 65535) return 4
if (identity(dword) != 65536) return 5
if (identity(maximum) != 18446744073709551615) return 6
if (box.value != 7) return 7
if (signed != -1) return 8
if (identity(platform) != 7) return 9
if (identity(c_value) != c_value) return 10
if (whole(word) != 256) return 11
if (max_value() != 18446744073709551615) return 12
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
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)
uint_constraint_rejects_other_families_and_recovers_records_as_u64 :: proc(t: ^testing.T) {
text := `Unresolved :: struct { value uint }
Conflict :: struct { value uint }
take func(value uint) uint { return value }
bad_result func() uint { return -1 }
bad func(signed i32, decimal f64) void {
_ = take(signed)
_ = take(decimal)
negative uint :: -1
a Conflict = Conflict{value = signed}
_ = negative
_ = a
}
Overflow uint :: 18446744073709551616
NegativeGlobal uint :: -1
main func() void {
bad(1, 1.0)
_ = bad_result()
}
`
source_file := source.Source{path="bad_uint.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)
signed_error := false
float_error := false
negative_error := false
field_error := false
overflow_error := false
global_error := false
result_error := false
for diagnostic in diagnostics.items {
signed_error = signed_error || strings.contains(diagnostic.message, "cannot pass i32 to 'uint' parameter 'value'")
float_error = float_error || strings.contains(diagnostic.message, "cannot pass f64 to 'uint' parameter 'value'")
negative_error = negative_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for local 'negative'")
field_error = field_error || strings.contains(diagnostic.message, "type i32 does not satisfy the 'uint' constraint for field 'Conflict.value'")
overflow_error = overflow_error || strings.contains(diagnostic.message, "magnitude does not fit in u64")
global_error = global_error || strings.contains(diagnostic.message, "could not resolve the 'uint' constraint for global 'NegativeGlobal'")
result_error = result_error || strings.contains(diagnostic.message, "could not resolve a concrete result type for 'bad_result'")
}
unresolved, unresolved_ok := named_record_field_type(&hir_module, &symbols, "Unresolved", "value")
testing.expect(t, signed_error && float_error && negative_error && field_error && overflow_error && global_error && result_error)
testing.expect(t, unresolved_ok)
testing.expect_value(t, unresolved, types.U64)
}
+1 -1
View File
@@ -9,5 +9,5 @@ languages = ["languages/brolang"]
[grammars.brolang]
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
rev = "2e8234c6a9a12326f230ea16e901ac41a1b48c99"
rev = "71357b2866d2a558f80b6d5cc65761fb853e74d3"
path = "tree-sitter-brolang"
+1 -1
View File
@@ -228,7 +228,7 @@ module.exports = grammar({
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
builtin_type: _ => choice(
'void', 'anyopaque', 'bool', 'int', 'float', 'range',
'void', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
'isize', 'usize', 'f32', 'f64',
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
+4
View File
@@ -1476,6 +1476,10 @@
"type": "STRING",
"value": "int"
},
{
"type": "STRING",
"value": "uint"
},
{
"type": "STRING",
"value": "float"
+4
View File
@@ -2815,6 +2815,10 @@
"type": "u8",
"named": false
},
{
"type": "uint",
"named": false
},
{
"type": "undefined",
"named": true
File diff suppressed because it is too large Load Diff
@@ -493,3 +493,19 @@ visit func(kind Kind, value Value) void {
(sink))
(expression
(identifier))))))))))))
==================
Unsigned constraint
==================
value uint :: 255
---
(source_file
(global_constant_declaration
(identifier)
(type
(builtin_type))
(expression
(integer))))
@@ -1,6 +1,9 @@
hide helper func() void {}
# <- keyword
value uint :: 1
# ^^^^ type.builtin
main func() void {
_ = sizeof!(i32)
# ^^^^^^ function.builtin