fix: bug hunt
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@ roadmap and milestone history.
|
|||||||
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, `noreturn`, and `anyopaque`; `noreturn` is a bottom type valid as a native function result and coerces to any expected value type; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
|
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, `noreturn`, and `anyopaque`; `noreturn` is a bottom type valid as a native function result and coerces to any expected value type; 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
|
- 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 keyword casts such as `i32(x)` / `c_float(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)`
|
||||||
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
|
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
|
||||||
- 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 `?`
|
||||||
|
|||||||
@@ -5407,10 +5407,15 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
|
_, function_item, function_type, ok := types.callable_function(callee_type, &checker.module.types)
|
||||||
if !ok {
|
if !ok {
|
||||||
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
||||||
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
named_item, named_ok := types.node(&checker.module.types, named_type)
|
||||||
if available && distinct_ok && distinct_item.kind == .Distinct && len(expr.args) == 1 {
|
constructor_type := types.resolve_alias(named_type, &checker.module.types)
|
||||||
stack[frame_index].left = distinct_type
|
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
|
||||||
|
scalar_alias := named_ok && named_item.kind == .Alias &&
|
||||||
|
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
|
||||||
|
distinct_constructor := constructor_ok && constructor_item.kind == .Distinct
|
||||||
|
if available && len(expr.args) == 1 && (scalar_alias || distinct_constructor) {
|
||||||
|
stack[frame_index].left = constructor_type
|
||||||
stack[frame_index].stage = 7
|
stack[frame_index].stage = 7
|
||||||
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
|
||||||
continue
|
continue
|
||||||
@@ -7633,6 +7638,41 @@ enum_member_hir :: proc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build_scalar_cast :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
value: hir.Expr_Id,
|
||||||
|
target: types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
store := &checker.module.types
|
||||||
|
actual := checker.module.exprs[value].type
|
||||||
|
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
||||||
|
actual_repr := types.runtime_representation(actual, store)
|
||||||
|
actual_item, actual_item_ok := types.node(store, actual)
|
||||||
|
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
|
||||||
|
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
|
||||||
|
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
|
||||||
|
if !valid_target || !valid_actual {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
span,
|
||||||
|
"scalar cast requires numeric scalar types, got %s to %s",
|
||||||
|
types.name(actual),
|
||||||
|
types.name(target),
|
||||||
|
)
|
||||||
|
return invalid_hir_expr(checker, span, id, target)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Scalar_Cast,
|
||||||
|
span=span,
|
||||||
|
type=target,
|
||||||
|
left=value,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
build_function_value :: proc(
|
build_function_value :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
template: ast.Function_Id,
|
template: ast.Function_Id,
|
||||||
@@ -8041,17 +8081,22 @@ build_compound_expr :: proc(
|
|||||||
)
|
)
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
case .Enum_Literal:
|
case .Enum_Literal:
|
||||||
if types.is_tagged_union(expected, store) {
|
literal_expected := expected
|
||||||
index, field, found := find_struct_field(checker, expected, expr.name)
|
optional_expected := types.is_optional(expected, store)
|
||||||
|
if optional_expected {
|
||||||
|
literal_expected = types.child_type(expected, store)
|
||||||
|
}
|
||||||
|
if types.is_tagged_union(literal_expected, store) {
|
||||||
|
index, field, found := find_struct_field(checker, literal_expected, expr.name)
|
||||||
if !found {
|
if !found {
|
||||||
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
|
id := source.addf(checker.diagnostics, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, literal_expected))
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
}
|
}
|
||||||
values := make([]hir.Expr_Id, 1, checker.allocator)
|
values := make([]hir.Expr_Id, 1, checker.allocator)
|
||||||
if expr.left == ast.INVALID_EXPR {
|
if expr.left == ast.INVALID_EXPR {
|
||||||
if !types.is_void(field.type) {
|
if !types.is_void(field.type) {
|
||||||
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
|
id := source.addf(checker.diagnostics, expr.span, "variant '.%s' on '%s' needs a payload; only void variants can be built from a bare '.%s'",
|
||||||
symbol_text(checker, expr.name), type_label(checker, expected), symbol_text(checker, expr.name))
|
symbol_text(checker, expr.name), type_label(checker, literal_expected), symbol_text(checker, expr.name))
|
||||||
delete(values, checker.allocator)
|
delete(values, checker.allocator)
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
}
|
}
|
||||||
@@ -8065,11 +8110,12 @@ build_compound_expr :: proc(
|
|||||||
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
|
values[0] = build_nested_expr(checker, expr.left, locals, global_reads, calls, field.type, pkg, file)
|
||||||
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
|
values[0] = coerce_expr(checker, values[0], field.type, checker.module.exprs[values[0]].span)
|
||||||
}
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
result := add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Struct, span=expr.span, type=expected, args=values, integer=i64(index),
|
kind=.Struct, span=expr.span, type=literal_expected, args=values, integer=i64(index),
|
||||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
|
||||||
}
|
}
|
||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
@@ -8080,7 +8126,7 @@ build_compound_expr :: proc(
|
|||||||
)
|
)
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
}
|
}
|
||||||
if !types.is_enum(expected, store) {
|
if !types.is_enum(literal_expected, store) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
expr.span,
|
expr.span,
|
||||||
@@ -8089,36 +8135,12 @@ build_compound_expr :: proc(
|
|||||||
)
|
)
|
||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
}
|
}
|
||||||
return enum_member_hir(checker, expected, expr.name, expr.span)
|
result := enum_member_hir(checker, literal_expected, expr.name, expr.span)
|
||||||
|
return coerce_expr(checker, result, expected, expr.span) if optional_expected else result
|
||||||
case .Cast:
|
case .Cast:
|
||||||
target := type_from_syntax(checker, expr.type, pkg, file)
|
target := type_from_syntax(checker, expr.type, pkg, file)
|
||||||
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
actual := checker.module.exprs[value].type
|
return build_scalar_cast(checker, value, target, expr.span)
|
||||||
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
|
||||||
actual_repr := types.runtime_representation(actual, store)
|
|
||||||
actual_item, actual_item_ok := types.node(store, actual)
|
|
||||||
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
|
|
||||||
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
|
|
||||||
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
|
|
||||||
if !valid_target || !valid_actual {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
expr.span,
|
|
||||||
"scalar cast requires numeric scalar types, got %s to %s",
|
|
||||||
types.name(actual),
|
|
||||||
types.name(target),
|
|
||||||
)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id, target)
|
|
||||||
}
|
|
||||||
return add_hir_expr(checker, hir.Expr{
|
|
||||||
kind=.Scalar_Cast,
|
|
||||||
span=expr.span,
|
|
||||||
type=target,
|
|
||||||
left=value,
|
|
||||||
target=hir.INVALID_REF,
|
|
||||||
right=hir.INVALID_EXPR,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
case .Address:
|
case .Address:
|
||||||
// `&<array literal>` (Zig's `&.{...}`): the operand is an rvalue with no
|
// `&<array literal>` (Zig's `&.{...}`): the operand is an rvalue with no
|
||||||
// address, so promote it to an anonymous global constant and take *its*
|
// address, so promote it to an anonymous global constant and take *its*
|
||||||
@@ -9369,10 +9391,36 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if callee == hir.INVALID_EXPR {
|
if callee == hir.INVALID_EXPR {
|
||||||
distinct_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
named_type := types.find_named(&checker.module.types, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, file)))
|
||||||
distinct_item, distinct_ok := types.node(&checker.module.types, distinct_type)
|
named_item, named_ok := types.node(&checker.module.types, named_type)
|
||||||
if distinct_ok && distinct_item.kind == .Distinct {
|
constructor_type := types.resolve_alias(named_type, &checker.module.types)
|
||||||
if !is_runtime_type(checker, distinct_type) {
|
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
|
||||||
|
scalar_alias := named_ok && named_item.kind == .Alias &&
|
||||||
|
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
|
||||||
|
if scalar_alias {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
expr.span,
|
||||||
|
"type alias '%s' expects 1 argument, got %d",
|
||||||
|
symbol_text(checker, expr.name),
|
||||||
|
len(expr.args),
|
||||||
|
)
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id, constructor_type)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack[frame_index].target_type = constructor_type
|
||||||
|
stack[frame_index].stage = 11
|
||||||
|
append(&stack, Build_Expr_Frame{
|
||||||
|
expr=expr.args[0],
|
||||||
|
expected=types.INVALID,
|
||||||
|
template=ast.INVALID_FUNCTION,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if constructor_ok && constructor_item.kind == .Distinct {
|
||||||
|
if !is_runtime_type(checker, constructor_type) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
expr.span,
|
expr.span,
|
||||||
@@ -9391,15 +9439,15 @@ build_expr :: proc(
|
|||||||
symbol_text(checker, expr.name),
|
symbol_text(checker, expr.name),
|
||||||
len(expr.args),
|
len(expr.args),
|
||||||
)
|
)
|
||||||
last = invalid_hir_expr(checker, expr.span, id, distinct_type)
|
last = invalid_hir_expr(checker, expr.span, id, constructor_type)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
stack[frame_index].target_type = distinct_type
|
stack[frame_index].target_type = constructor_type
|
||||||
stack[frame_index].stage = 8
|
stack[frame_index].stage = 8
|
||||||
append(&stack, Build_Expr_Frame{
|
append(&stack, Build_Expr_Frame{
|
||||||
expr=expr.args[0],
|
expr=expr.args[0],
|
||||||
expected=distinct_item.child,
|
expected=constructor_item.child,
|
||||||
template=ast.INVALID_FUNCTION,
|
template=ast.INVALID_FUNCTION,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
@@ -9988,6 +10036,10 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
}
|
}
|
||||||
|
if frame.stage == 11 {
|
||||||
|
last = build_scalar_cast(checker, last, frame.target_type, expr.span)
|
||||||
|
_ = pop(&stack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1499,15 +1499,37 @@ ct_eval_expr :: proc(
|
|||||||
case .Catch:
|
case .Catch:
|
||||||
return ct_eval_catch_expr(state, expr, expected, depth+1)
|
return ct_eval_catch_expr(state, expr, expected, depth+1)
|
||||||
case .Range:
|
case .Range:
|
||||||
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
child_hint := types.INVALID
|
||||||
if !ok || flow.kind != .Normal {
|
if types.is_range(expected, store) {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
child_hint = types.child_type(expected, store)
|
||||||
}
|
}
|
||||||
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
|
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||||
if !right_ok || right_flow.kind != .Normal {
|
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||||
return INVALID_CT_VALUE, right_flow, right_ok
|
left, right: Ct_Value_Id
|
||||||
|
flow: Ct_Flow
|
||||||
|
ok: bool
|
||||||
|
if left_const && !right_const && !types.is_valid(child_hint) {
|
||||||
|
right, flow, ok = ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
left, flow, ok = ct_eval_expr(state, expr.left, state.values[right].type, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
left, flow, ok = ct_eval_expr(state, expr.left, child_hint, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
right_hint := child_hint if types.is_valid(child_hint) else state.values[left].type
|
||||||
|
right, flow, ok = ct_eval_expr(state, expr.right, right_hint, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
}
|
}
|
||||||
child_type := types.widest(state.values[left].type, state.values[right].type)
|
child_type := child_hint if types.is_valid(child_hint) else
|
||||||
|
types.widest(state.values[left].type, state.values[right].type)
|
||||||
if !types.is_concrete_integer(child_type) {
|
if !types.is_concrete_integer(child_type) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "range bounds must be compatible concrete integers")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "range bounds must be compatible concrete integers")
|
||||||
}
|
}
|
||||||
@@ -1905,10 +1927,15 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
|||||||
ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
if types.is_tagged_union(expected, store) {
|
literal_expected := expected
|
||||||
index, field, found := find_struct_field(checker, expected, expr.name)
|
optional_expected := types.is_optional(expected, store)
|
||||||
|
if optional_expected {
|
||||||
|
literal_expected = types.child_type(expected, store)
|
||||||
|
}
|
||||||
|
if types.is_tagged_union(literal_expected, store) {
|
||||||
|
index, field, found := find_struct_field(checker, literal_expected, expr.name)
|
||||||
if !found {
|
if !found {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, expected))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown variant '.%s' on '%s'", symbol_text(checker, expr.name), type_label(checker, literal_expected))
|
||||||
}
|
}
|
||||||
payload := INVALID_CT_VALUE
|
payload := INVALID_CT_VALUE
|
||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
@@ -1921,23 +1948,31 @@ ct_eval_enum_literal :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.T
|
|||||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
} else if !types.is_void(field.type) {
|
} else if !types.is_void(field.type) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, expected))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "variant '.%s' on '%s' needs a payload", symbol_text(checker, expr.name), type_label(checker, literal_expected))
|
||||||
}
|
}
|
||||||
start := u32(len(state.children))
|
start := u32(len(state.children))
|
||||||
append(&state.children, payload)
|
append(&state.children, payload)
|
||||||
return ct_add_value(state, Ct_Value{kind=.Struct, type=expected, start=start, count=1, active=i64(index)}), ct_flow(.Normal), true
|
result := ct_add_value(state, Ct_Value{kind=.Struct, type=literal_expected, start=start, count=1, active=i64(index)})
|
||||||
|
if optional_expected {
|
||||||
|
return ct_coerce_expr_value(state, result, expected, expr.span)
|
||||||
|
}
|
||||||
|
return result, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s{...}' requires a tagged-union context", symbol_text(checker, expr.name))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s{...}' requires a tagged-union context", symbol_text(checker, expr.name))
|
||||||
}
|
}
|
||||||
if !types.is_enum(expected, store) {
|
if !types.is_enum(literal_expected, store) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s' requires an enum context", symbol_text(checker, expr.name))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "'.%s' requires an enum context", symbol_text(checker, expr.name))
|
||||||
}
|
}
|
||||||
member, ok := find_enum_member(checker, expected, expr.name)
|
member, ok := find_enum_member(checker, literal_expected, expr.name)
|
||||||
if !ok {
|
if !ok {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", symbol_text(checker, expr.name))
|
||||||
}
|
}
|
||||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=expected, integer=member.value}), ct_flow(.Normal), true
|
result := ct_add_value(state, Ct_Value{kind=.Integer, type=literal_expected, integer=member.value})
|
||||||
|
if optional_expected {
|
||||||
|
return ct_coerce_expr_value(state, result, expected, expr.span)
|
||||||
|
}
|
||||||
|
return result, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol.Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol.Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||||
@@ -3550,6 +3585,32 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
named_type := types.find_named(
|
||||||
|
&checker.module.types,
|
||||||
|
u32(target_pkg),
|
||||||
|
u32(expr.name),
|
||||||
|
file=u32(expr_lookup_file(expr, state.file)),
|
||||||
|
)
|
||||||
|
named_item, named_ok := types.node(&checker.module.types, named_type)
|
||||||
|
target := types.resolve_alias(named_type, &checker.module.types)
|
||||||
|
if named_ok && named_item.kind == .Alias &&
|
||||||
|
types.is_concrete_scalar(target) && !types.is_bool(target) {
|
||||||
|
if len(expr.args) != 1 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||||
|
state,
|
||||||
|
.Not_Comptime,
|
||||||
|
expr.span,
|
||||||
|
"type alias '%s' expects 1 argument, got %d",
|
||||||
|
symbol_text(checker, expr.name),
|
||||||
|
len(expr.args),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, flow, ok
|
||||||
|
}
|
||||||
|
return ct_scalar_cast(state, value, target, expr.span)
|
||||||
|
}
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
||||||
}
|
}
|
||||||
if runtime_param_count(checker.ast_module.functions[template]) != 0 {
|
if runtime_param_count(checker.ast_module.functions[template]) != 0 {
|
||||||
|
|||||||
@@ -437,7 +437,8 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
u32(parser.file),
|
u32(parser.file),
|
||||||
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
|
!symbol.is_valid(qualifier) && file_hidden_name(parser, name.symbol),
|
||||||
)
|
)
|
||||||
if current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
if token_text(parser, name) == "struct_type" &&
|
||||||
|
current(parser).kind == .Bang && peek(parser).kind == .Left_Paren {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
if symbol.is_valid(qualifier) {
|
if symbol.is_valid(qualifier) {
|
||||||
source.add(parser.diagnostics, first.span, "intrinsic calls must be unqualified")
|
source.add(parser.diagnostics, first.span, "intrinsic calls must be unqualified")
|
||||||
@@ -1251,7 +1252,7 @@ is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
case .Integer, .Float, .String, .Bool, .Name:
|
case .Integer, .Float, .String, .Bool, .Name, .Call, .Cast:
|
||||||
return true
|
return true
|
||||||
case:
|
case:
|
||||||
return false
|
return false
|
||||||
|
|||||||
+155
-6
@@ -4570,6 +4570,75 @@ main func() i32 {
|
|||||||
testing.expect(t, undefined_found)
|
testing.expect(t, undefined_found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
zero_runtime_fold_tracks_mutated_locals_in_dependent_array_types :: proc(t: ^testing.T) {
|
||||||
|
text := `FoldedMap func($V type) type {
|
||||||
|
return struct {
|
||||||
|
keys [][]u8
|
||||||
|
values []V
|
||||||
|
indexes []u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pair func($V type) type { return struct { []u8, V } }
|
||||||
|
build_map func($V type, $N usize, $entries [N]Pair(V)) FoldedMap(V) {
|
||||||
|
keys [N]mut []u8 = undefined
|
||||||
|
values [N]mut V = undefined
|
||||||
|
for entries |entry, i| {
|
||||||
|
keys[i] = entry.0
|
||||||
|
values[i] = entry.1
|
||||||
|
}
|
||||||
|
for 1..N |i| {
|
||||||
|
key :: keys[i]
|
||||||
|
value :: values[i]
|
||||||
|
j usize = i
|
||||||
|
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||||
|
keys[j] = keys[j - 1]
|
||||||
|
values[j] = values[j - 1]
|
||||||
|
}
|
||||||
|
keys[j] = key
|
||||||
|
values[j] = value
|
||||||
|
}
|
||||||
|
max_len usize :: keys[N - 1].len
|
||||||
|
indexes [max_len + 1]mut u32 = undefined
|
||||||
|
for 0..=max_len |length| {
|
||||||
|
indexes[length] = 0
|
||||||
|
}
|
||||||
|
return FoldedMap(V){
|
||||||
|
keys = keys[..],
|
||||||
|
values = values[..],
|
||||||
|
indexes = indexes[..],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kind :: enum { short, long }
|
||||||
|
MAP FoldedMap(Kind) :: build_map([
|
||||||
|
{"four", .long},
|
||||||
|
{"a", .short},
|
||||||
|
])
|
||||||
|
main func() i32 {
|
||||||
|
if MAP.keys.len != 2 or MAP.indexes.len != 5 { return 1 }
|
||||||
|
return 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)
|
||||||
|
|
||||||
|
build_map_found := false
|
||||||
|
for function in hir_module.functions {
|
||||||
|
build_map_found = build_map_found || symbol.resolve(&symbols, function.name) == "build_map"
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, !build_map_found)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
zero_runtime_fold_preserves_reached_compile_error :: proc(t: ^testing.T) {
|
zero_runtime_fold_preserves_reached_compile_error :: proc(t: ^testing.T) {
|
||||||
text := `fail func() i32 {
|
text := `fail func() i32 {
|
||||||
@@ -6536,6 +6605,7 @@ AError :: enum { a }
|
|||||||
BError :: enum { b }
|
BError :: enum { b }
|
||||||
CError :: enum { c }
|
CError :: enum { c }
|
||||||
DetailError :: union(enum) { out_of_memory i32 }
|
DetailError :: union(enum) { out_of_memory i32 }
|
||||||
|
Code :: alias i32
|
||||||
|
|
||||||
key_or_alloc func(code i32) void ! (KeyError | AllocError) {
|
key_or_alloc func(code i32) void ! (KeyError | AllocError) {
|
||||||
if code == 1 { return .key_exists }
|
if code == 1 { return .key_exists }
|
||||||
@@ -6557,7 +6627,7 @@ via_else func() i32 ! AllocError {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
via_group func() i32 ! (AError | BError) {
|
via_group func() Code ! (AError | BError) {
|
||||||
abc(2) catch |err| {
|
abc(2) catch |err| {
|
||||||
match err {
|
match err {
|
||||||
.a, .b: return err
|
.a, .b: return err
|
||||||
@@ -9843,6 +9913,8 @@ answer :: alias dep.answer
|
|||||||
hide local_answer_alias :: alias dep.answer
|
hide local_answer_alias :: alias dep.answer
|
||||||
local_answer func() i32 { return local_answer_alias() }
|
local_answer func() i32 { return local_answer_alias() }
|
||||||
Scalar :: alias i32
|
Scalar :: alias i32
|
||||||
|
ScalarChain :: alias Scalar
|
||||||
|
static_scalar Scalar :: Scalar(usize(6))
|
||||||
MaybePoint :: alias ?@dep.Point
|
MaybePoint :: alias ?@dep.Point
|
||||||
Concrete :: alias dep.Box(i32)
|
Concrete :: alias dep.Box(i32)
|
||||||
`
|
`
|
||||||
@@ -9862,10 +9934,11 @@ main func() i32 {
|
|||||||
box top.Box(i32) :: top.Box(i32) { value = 2 }
|
box top.Box(i32) :: top.Box(i32) { value = 2 }
|
||||||
point top.Point :: top.Point { value = 3 }
|
point top.Point :: top.Point { value = 3 }
|
||||||
maybe facade.MaybePoint :: null
|
maybe facade.MaybePoint :: null
|
||||||
scalar facade.Scalar :: 5
|
scalar facade.Scalar :: facade.Scalar(usize(5))
|
||||||
|
chained facade.ScalarChain :: facade.ScalarChain(usize(6))
|
||||||
top.counter = 7
|
top.counter = 7
|
||||||
if box.value != 2 or point.value != 3 { return 1 }
|
if box.value != 2 or point.value != 3 { return 1 }
|
||||||
if scalar != 5 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 }
|
if scalar != 5 or chained != 6 or facade.static_scalar != 6 or top.answer() != 40 or facade.local_answer() != 40 or dep.counter != 7 { return 2 }
|
||||||
if apply(top.answer) != 40 or apply(facade.answer) != 40 { return 3 }
|
if apply(top.answer) != 40 or apply(facade.answer) != 40 { return 3 }
|
||||||
_ = maybe
|
_ = maybe
|
||||||
return 0
|
return 0
|
||||||
@@ -9882,6 +9955,75 @@ main func() i32 {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
scalar_alias_casts_reject_bad_arity_and_operands :: proc(t: ^testing.T) {
|
||||||
|
text := `StringId :: alias u32
|
||||||
|
main func() void {
|
||||||
|
_ = StringId()
|
||||||
|
_ = StringId(1, 2)
|
||||||
|
_ = StringId(true)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
arity := 0
|
||||||
|
bad_operand := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
arity += 1 if strings.contains(diagnostic.message, "type alias 'StringId' expects 1 argument") else 0
|
||||||
|
bad_operand = bad_operand || strings.contains(diagnostic.message, "scalar cast requires numeric scalar types")
|
||||||
|
}
|
||||||
|
testing.expect_value(t, arity, 2)
|
||||||
|
testing.expect(t, bad_operand)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
optional_contextual_enum_literals_compile_at_runtime_and_comptime :: proc(t: ^testing.T) {
|
||||||
|
text := `Kind :: enum { first, second }
|
||||||
|
Choice :: union(enum) { empty void, payload i32 }
|
||||||
|
STATIC ?Kind :: .second
|
||||||
|
STATIC_CHOICE ?Choice :: .payload{4}
|
||||||
|
choose func(first bool) ?Kind {
|
||||||
|
if first { return .first }
|
||||||
|
return .second
|
||||||
|
}
|
||||||
|
choose_choice func() ?Choice { return .empty }
|
||||||
|
main func() i32 {
|
||||||
|
if choose(true) |value| {
|
||||||
|
if value != Kind.first { return 1 }
|
||||||
|
} else { return 2 }
|
||||||
|
if STATIC |value| {
|
||||||
|
if value != Kind.second { return 3 }
|
||||||
|
} else { return 4 }
|
||||||
|
if choose_choice() |_| {} else { return 5 }
|
||||||
|
if STATIC_CHOICE |_| {} else { return 6 }
|
||||||
|
return 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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
|
declaration_aliases_diagnose_invalid_targets :: proc(t: ^testing.T) {
|
||||||
root :: "/tmp/brolang-test-declaration-alias-errors"
|
root :: "/tmp/brolang-test-declaration-alias-errors"
|
||||||
@@ -11095,7 +11237,8 @@ main func() i32 {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
|
range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
|
||||||
text := `main func() void {
|
text := `limit_func func() usize { return 3 }
|
||||||
|
main func() void {
|
||||||
limit :: 3
|
limit :: 3
|
||||||
for 0..limit + 1 |bad| {
|
for 0..limit + 1 |bad| {
|
||||||
_ = bad
|
_ = bad
|
||||||
@@ -11103,6 +11246,12 @@ range_bound_parenthesization_is_enforced :: proc(t: ^testing.T) {
|
|||||||
for 0..(limit + 1) |good| {
|
for 0..(limit + 1) |good| {
|
||||||
_ = good
|
_ = good
|
||||||
}
|
}
|
||||||
|
for 0..=usize(limit) |cast_bound| {
|
||||||
|
_ = cast_bound
|
||||||
|
}
|
||||||
|
for 0..limit_func() |call_bound| {
|
||||||
|
_ = call_bound
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
@@ -14934,7 +15083,7 @@ TokenKind :: enum {
|
|||||||
keyword_while
|
keyword_while
|
||||||
}
|
}
|
||||||
|
|
||||||
keywords std.StaticStringMap(TokenKind) = static_string_map.init([
|
keywords std.StaticStringMap(TokenKind) :: static_string_map.init([
|
||||||
{"if", .keyword_if},
|
{"if", .keyword_if},
|
||||||
{"else", .keyword_else},
|
{"else", .keyword_else},
|
||||||
{"for", .keyword_for},
|
{"for", .keyword_for},
|
||||||
@@ -14943,7 +15092,7 @@ keywords std.StaticStringMap(TokenKind) = static_string_map.init([
|
|||||||
fallback :: static_string_map.init(TokenKind, [
|
fallback :: static_string_map.init(TokenKind, [
|
||||||
{"while", .keyword_while},
|
{"while", .keyword_while},
|
||||||
])
|
])
|
||||||
empty std.StaticStringMap(TokenKind) = static_string_map.init([])
|
empty std.StaticStringMap(TokenKind) :: static_string_map.init([])
|
||||||
numbers []i32 = ${
|
numbers []i32 = ${
|
||||||
values [3]mut i32 = [7, 8, 9]
|
values [3]mut i32 = [7, 8, 9]
|
||||||
yield values[..]
|
yield values[..]
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
|||||||
max_len u32 :: u32(keys[N - 1].len)
|
max_len u32 :: u32(keys[N - 1].len)
|
||||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||||
entry_index usize = 0
|
entry_index usize = 0
|
||||||
for 0..=(usize(max_len)) |length| {
|
for 0..=usize(max_len) |length| {
|
||||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||||
len_indexes[length] = u32(entry_index)
|
len_indexes[length] = u32(entry_index)
|
||||||
}
|
}
|
||||||
@@ -90,4 +90,5 @@ get func($V type, map @StaticStringMap(V), key []u8) ?V {
|
|||||||
if (candidate.len != key.len) return null
|
if (candidate.len != key.len) return null
|
||||||
if mem.eql(u8, candidate, key) return map.values[idx]
|
if mem.eql(u8, candidate, key) return map.values[idx]
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user