Compare commits
5 Commits
10abba54a5
...
297f2e3078
| Author | SHA1 | Date | |
|---|---|---|---|
| 297f2e3078 | |||
| ad4802a270 | |||
| 05aa7d084a | |||
| ec36b6b861 | |||
| deab47e75e |
+165
-22
@@ -1189,7 +1189,12 @@ type_from_syntax :: proc(
|
|||||||
}
|
}
|
||||||
return composed
|
return composed
|
||||||
case .Type_Call:
|
case .Type_Call:
|
||||||
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
|
expr_id := ast.Expr_Id(item.count_expr)
|
||||||
|
call_file := file
|
||||||
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
||||||
|
call_file = ast.File_Id(checker.ast_module.exprs[expr_id].span.file)
|
||||||
|
}
|
||||||
|
return resolve_type_factory_call(checker, expr_id, pkg, call_file)
|
||||||
}
|
}
|
||||||
if changed {
|
if changed {
|
||||||
return types.intern(store, item)
|
return types.intern(store, item)
|
||||||
@@ -2730,8 +2735,9 @@ infer_call_comptime_values :: proc(
|
|||||||
for value_bound in bound {
|
for value_bound in bound {
|
||||||
all_bound = all_bound && value_bound
|
all_bound = all_bound && value_bound
|
||||||
}
|
}
|
||||||
// Concrete arguments bind first. Numeric constants are contextual and therefore
|
// Concrete arguments bind first. Numeric constants and `none` are contextual and
|
||||||
// only contribute their default type after stronger evidence has had a chance.
|
// therefore only contribute after stronger evidence has had a chance to bind the
|
||||||
|
// parameter type.
|
||||||
weak_passes := [2]bool{false, true}
|
weak_passes := [2]bool{false, true}
|
||||||
for weak in weak_passes {
|
for weak in weak_passes {
|
||||||
for arg_id, source_index in args {
|
for arg_id, source_index in args {
|
||||||
@@ -2742,7 +2748,9 @@ infer_call_comptime_values :: proc(
|
|||||||
if param_index < 0 || function.params[param_index].comptime_value {
|
if param_index < 0 || function.params[param_index].comptime_value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
is_weak := is_numeric_constant_expr(checker, arg_id)
|
arg_expr := checker.ast_module.exprs[arg_id]
|
||||||
|
is_none := arg_expr.kind == .None
|
||||||
|
is_weak := is_numeric_constant_expr(checker, arg_id) || is_none
|
||||||
if is_weak != weak {
|
if is_weak != weak {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2753,7 +2761,6 @@ infer_call_comptime_values :: proc(
|
|||||||
// keyed record must be checked against the specialized parameter type.
|
// keyed record must be checked against the specialized parameter type.
|
||||||
// Its provisional structural type intentionally contains only the supplied
|
// Its provisional structural type intentionally contains only the supplied
|
||||||
// fields, so comparing that type here would reject omitted defaulted fields.
|
// fields, so comparing that type here would reject omitted defaulted fields.
|
||||||
arg_expr := checker.ast_module.exprs[arg_id]
|
|
||||||
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
|
if all_bound && arg_expr.kind == .Struct_Literal && !arg_expr.tuple && !symbol.is_valid(arg_expr.name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -2766,9 +2773,22 @@ infer_call_comptime_values :: proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
actual := actual_args[param_index]
|
||||||
|
if is_none {
|
||||||
|
previous := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = values
|
||||||
|
contextual := type_from_syntax(
|
||||||
|
checker, function.params[param_index].type, function.pkg, function.file,
|
||||||
|
)
|
||||||
|
checker.current_comptime_values = previous
|
||||||
|
if types.is_optional(contextual, &checker.module.types) {
|
||||||
|
actual = contextual
|
||||||
|
actual_args[param_index] = contextual
|
||||||
|
}
|
||||||
|
}
|
||||||
matched = match_inferred_type_pattern(
|
matched = match_inferred_type_pattern(
|
||||||
checker, function, prefix, function.params[param_index].type,
|
checker, function, prefix, function.params[param_index].type,
|
||||||
actual_args[param_index], values, bound,
|
actual, values, bound,
|
||||||
checker.ast_module.exprs[arg_id].span, diagnose,
|
checker.ast_module.exprs[arg_id].span, diagnose,
|
||||||
) && matched
|
) && matched
|
||||||
}
|
}
|
||||||
@@ -4057,6 +4077,25 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate_type_nodes :: proc(checker: ^Checker) {
|
validate_type_nodes :: proc(checker: ^Checker) {
|
||||||
|
node_count := len(checker.module.types.nodes)
|
||||||
|
for index in 0..<node_count {
|
||||||
|
item := checker.module.types.nodes[index]
|
||||||
|
if item.kind != .Struct && item.kind != .Union {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
start := int(item.field_start)
|
||||||
|
end := start+int(item.field_count)
|
||||||
|
if start < 0 || end > len(checker.module.types.fields) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for slot in start..<end {
|
||||||
|
resolved := type_from_syntax(
|
||||||
|
checker, checker.module.types.fields[slot].type,
|
||||||
|
ast.Package_Id(item.pkg), ast.File_Id(item.file),
|
||||||
|
)
|
||||||
|
checker.module.types.fields[slot].type = resolved
|
||||||
|
}
|
||||||
|
}
|
||||||
validate_meta_schema(checker)
|
validate_meta_schema(checker)
|
||||||
for item, index in checker.module.types.nodes {
|
for item, index in checker.module.types.nodes {
|
||||||
id := types.DYNAMIC_START+types.Type(index)
|
id := types.DYNAMIC_START+types.Type(index)
|
||||||
@@ -4482,8 +4521,18 @@ infer_compound_expr :: proc(
|
|||||||
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
|
||||||
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
|
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
|
||||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
left_expr := checker.ast_module.exprs[expr.left]
|
||||||
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
right_expr := checker.ast_module.exprs[expr.right]
|
||||||
|
if left_expr.kind == .None && right_expr.kind != .None {
|
||||||
|
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
|
||||||
|
} else if right_expr.kind == .None && left_expr.kind != .None {
|
||||||
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, left)
|
||||||
|
} else {
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||||
|
}
|
||||||
return types.BOOL
|
return types.BOOL
|
||||||
case .Range:
|
case .Range:
|
||||||
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
@@ -4519,7 +4568,7 @@ infer_compound_expr :: proc(
|
|||||||
}
|
}
|
||||||
return types.array(store, element, u64(len(expr.args)), false)
|
return types.array(store, element, u64(len(expr.args)), false)
|
||||||
case .None:
|
case .None:
|
||||||
return types.INVALID
|
return expected if types.is_optional(expected, store) else types.INVALID
|
||||||
case .Undefined:
|
case .Undefined:
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
case .Enum_Literal:
|
case .Enum_Literal:
|
||||||
@@ -4695,12 +4744,15 @@ infer_compound_expr :: proc(
|
|||||||
if ok && item_ok {
|
if ok && item_ok {
|
||||||
initialized[slot-int(item.field_start)] = true
|
initialized[slot-int(item.field_start)] = true
|
||||||
}
|
}
|
||||||
if !ok || !is_inferred_record_field(checker, slot) {
|
if !ok {
|
||||||
_ = infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
field_expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
field_expected := field.type if is_runtime_type(checker, field.type) else types.INVALID
|
||||||
actual := infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types, field_expected)
|
actual := infer_nested_expr(checker, keyed_expr.left, locals, pkg, file, demanded, local_types, field_expected)
|
||||||
|
if !is_inferred_record_field(checker, slot) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_ = record_field_expr_candidate(checker, slot, keyed_expr.left, actual, locals, pkg, file)
|
_ = record_field_expr_candidate(checker, slot, keyed_expr.left, actual, locals, pkg, file)
|
||||||
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
if is_runtime_type(checker, checker.module.types.fields[slot].type) {
|
||||||
_ = record_demand(
|
_ = record_demand(
|
||||||
@@ -6506,11 +6558,12 @@ prune_specs :: proc(checker: ^Checker) {
|
|||||||
if checker.entry_point == .Process {
|
if checker.entry_point == .Process {
|
||||||
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
|
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
|
||||||
}
|
}
|
||||||
for global in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
expected := checker.global_types[index] if is_runtime_type(checker, checker.global_types[index]) else types.INVALID
|
||||||
|
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack, expected=expected)
|
||||||
}
|
}
|
||||||
for len(stack) > 0 {
|
for len(stack) > 0 {
|
||||||
id := pop(&stack)
|
id := pop(&stack)
|
||||||
@@ -8124,8 +8177,9 @@ build_compound_expr :: proc(
|
|||||||
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||||
// Contextualize a bare integer-literal operand to the other operand's type
|
// Contextualize literals whose type comes from their peer. This covers
|
||||||
// so comparisons like `count > 0` or `0 < count` type-check.
|
// integer and enum literals as well as optional presence tests such as
|
||||||
|
// `value == none` and `none != value`.
|
||||||
left_const := eval_constant(checker, expr.left)
|
left_const := eval_constant(checker, expr.left)
|
||||||
right_const := eval_constant(checker, expr.right)
|
right_const := eval_constant(checker, expr.right)
|
||||||
left, right: hir.Expr_Id
|
left, right: hir.Expr_Id
|
||||||
@@ -8133,7 +8187,13 @@ build_compound_expr :: proc(
|
|||||||
right_expr := checker.ast_module.exprs[expr.right]
|
right_expr := checker.ast_module.exprs[expr.right]
|
||||||
left_numeric_const := left_const.kind == .Value || is_float_constant_expr(checker, expr.left)
|
left_numeric_const := left_const.kind == .Value || is_float_constant_expr(checker, expr.left)
|
||||||
right_numeric_const := right_const.kind == .Value || is_float_constant_expr(checker, expr.right)
|
right_numeric_const := right_const.kind == .Value || is_float_constant_expr(checker, expr.right)
|
||||||
if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
|
if right_expr.kind == .None && left_expr.kind != .None {
|
||||||
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
||||||
|
} else if left_expr.kind == .None && right_expr.kind != .None {
|
||||||
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
||||||
|
} else if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
|
||||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
||||||
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
|
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
|
||||||
@@ -8157,7 +8217,14 @@ build_compound_expr :: proc(
|
|||||||
left_type := checker.module.exprs[left].type
|
left_type := checker.module.exprs[left].type
|
||||||
right_type := checker.module.exprs[right].type
|
right_type := checker.module.exprs[right].type
|
||||||
operand_type := types.INVALID
|
operand_type := types.INVALID
|
||||||
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
if left_expr.kind == .None || right_expr.kind == .None {
|
||||||
|
if expr.kind != .Eq && expr.kind != .Ne ||
|
||||||
|
!types.is_optional(left_type, store) || !types.equal(left_type, right_type) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'none' only supports '==' and '!=' with an optional value")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
|
}
|
||||||
|
operand_type = left_type
|
||||||
|
} else if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
||||||
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
|
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
|
||||||
id := source.add(checker.diagnostics, expr.span, "enum values only support '==' and '!=' with the same enum type")
|
id := source.add(checker.diagnostics, expr.span, "enum values only support '==' and '!=' with the same enum type")
|
||||||
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
@@ -12712,12 +12779,88 @@ build_value_loop :: proc(
|
|||||||
return value, target.slot_type
|
return value, target.slot_type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Return_Enum_Guard :: struct {
|
||||||
|
local: hir.Local_Id,
|
||||||
|
type: types.Type,
|
||||||
|
value: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
return_enum_guard :: proc(
|
||||||
|
module: ^hir.Module,
|
||||||
|
statement: hir.Stmt,
|
||||||
|
locals: []hir.Local,
|
||||||
|
) -> (Return_Enum_Guard, bool) {
|
||||||
|
if statement.kind != .If || statement.else_body != nil ||
|
||||||
|
statement.expr == hir.INVALID_EXPR || int(statement.expr) >= len(module.exprs) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
condition := module.exprs[statement.expr]
|
||||||
|
if condition.kind != .Eq || condition.left == hir.INVALID_EXPR || condition.right == hir.INVALID_EXPR ||
|
||||||
|
int(condition.left) >= len(module.exprs) || int(condition.right) >= len(module.exprs) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
left := module.exprs[condition.left]
|
||||||
|
right := module.exprs[condition.right]
|
||||||
|
if left.kind == .Integer {
|
||||||
|
left, right = right, left
|
||||||
|
}
|
||||||
|
if left.kind != .Local || right.kind != .Integer ||
|
||||||
|
!types.equal(left.type, right.type) || !types.is_enum(left.type, &module.types) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
local := hir.as_local(left.target)
|
||||||
|
if local == hir.INVALID_LOCAL || int(local) >= len(locals) || locals[local].mutable {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
if !all_paths_return(module, statement.then_body, locals) {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
return Return_Enum_Guard{local=local, type=left.type, value=right.integer}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_guards_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local) -> bool {
|
||||||
|
guards: [dynamic]Return_Enum_Guard
|
||||||
|
guards.allocator = module.allocator
|
||||||
|
defer delete(guards)
|
||||||
|
for id in stmts {
|
||||||
|
if guard, ok := return_enum_guard(module, module.statements[id], locals); ok {
|
||||||
|
append(&guards, guard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for guard, index in guards {
|
||||||
|
seen := false
|
||||||
|
for previous in guards[:index] {
|
||||||
|
seen = seen || previous.local == guard.local
|
||||||
|
}
|
||||||
|
if seen {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
members := types.enum_members_for(&module.types, guard.type)
|
||||||
|
if len(members) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
complete := true
|
||||||
|
for member in members {
|
||||||
|
value := i64(member.value) if member.value < 0 else transmute(i64)u64(member.value)
|
||||||
|
covered := false
|
||||||
|
for candidate in guards {
|
||||||
|
covered = covered || candidate.local == guard.local && candidate.value == value
|
||||||
|
}
|
||||||
|
complete = complete && covered
|
||||||
|
}
|
||||||
|
if complete {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
// Reports whether every control-flow path through `stmts` terminates (returns or traps),
|
||||||
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
// so the end of the block is unreachable. A `.Return` or `.Trap` terminates outright; an
|
||||||
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
// `.If` terminates only when it has an `else` and both arms terminate. A literal
|
||||||
// `while true` cannot fall through because the language has no `break` statement.
|
// `while true` cannot fall through because the language has no `break` statement.
|
||||||
// Recursion into the branch slices handles nested ifs and `else if` chains.
|
// Exhaustive equality guards over one immutable enum local also terminate collectively.
|
||||||
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local = nil) -> bool {
|
||||||
for id in stmts {
|
for id in stmts {
|
||||||
statement := module.statements[id]
|
statement := module.statements[id]
|
||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
@@ -12725,8 +12868,8 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|||||||
return true
|
return true
|
||||||
case .If:
|
case .If:
|
||||||
if statement.else_body != nil &&
|
if statement.else_body != nil &&
|
||||||
all_paths_return(module, statement.then_body) &&
|
all_paths_return(module, statement.then_body, locals) &&
|
||||||
all_paths_return(module, statement.else_body) {
|
all_paths_return(module, statement.else_body, locals) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case .While:
|
case .While:
|
||||||
@@ -12741,7 +12884,7 @@ all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return enum_guards_return(module, stmts, locals)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reports whether `stmts` contains a `break` that targets the enclosing loop:
|
// Reports whether `stmts` contains a `break` that targets the enclosing loop:
|
||||||
@@ -12967,7 +13110,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
block := build_block(&ctx, function.body)
|
block := build_block(&ctx, function.body)
|
||||||
checker.current_result = previous_result
|
checker.current_result = previous_result
|
||||||
checker.current_build_ctx = previous_ctx
|
checker.current_build_ctx = previous_ctx
|
||||||
returns := all_paths_return(&checker.module, block)
|
returns := all_paths_return(&checker.module, block, hir_locals[:])
|
||||||
for block_stmt in block {
|
for block_stmt in block {
|
||||||
append(&body, block_stmt)
|
append(&body, block_stmt)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1287,6 +1287,20 @@ ct_eval_expr :: proc(
|
|||||||
types.is_concrete_integer(expected) {
|
types.is_concrete_integer(expected) {
|
||||||
left_expected = expected
|
left_expected = expected
|
||||||
}
|
}
|
||||||
|
left_expr := checker.ast_module.exprs[expr.left]
|
||||||
|
right_expr := checker.ast_module.exprs[expr.right]
|
||||||
|
if left_expr.kind == .None && right_expr.kind != .None &&
|
||||||
|
(expr.kind == .Eq || expr.kind == .Ne) {
|
||||||
|
right, right_flow, right_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||||
|
if !right_ok || right_flow.kind != .Normal {
|
||||||
|
return INVALID_CT_VALUE, right_flow, right_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
|
||||||
|
}
|
||||||
|
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
||||||
|
}
|
||||||
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
@@ -2118,6 +2132,18 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
|||||||
left := state.values[left_id]
|
left := state.values[left_id]
|
||||||
right := state.values[right_id]
|
right := state.values[right_id]
|
||||||
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
|
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
|
||||||
|
if left.kind == .None || right.kind == .None {
|
||||||
|
if (op != .Eq && op != .Ne) ||
|
||||||
|
!types.is_optional(left.type, &state.checker.module.types) ||
|
||||||
|
!types.equal(left.type, right.type) {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'none' only supports '==' and '!=' with an optional value")
|
||||||
|
}
|
||||||
|
equal := left.kind == .None && right.kind == .None
|
||||||
|
if op == .Ne {
|
||||||
|
equal = !equal
|
||||||
|
}
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
|
||||||
|
}
|
||||||
if left.kind == .Type || right.kind == .Type {
|
if left.kind == .Type || right.kind == .Type {
|
||||||
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
|
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
|
||||||
|
|||||||
@@ -561,6 +561,25 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||||
|
left_expr := state.hir_module.exprs[expr.left]
|
||||||
|
right_expr := state.hir_module.exprs[expr.right]
|
||||||
|
if left_expr.kind == .None || right_expr.kind == .None {
|
||||||
|
optional_expr := expr.right if left_expr.kind == .None else expr.left
|
||||||
|
optional := lower_nested_expr(state, optional_expr)
|
||||||
|
present := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Optional_Is_Some, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=optional, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if expr.kind == .Ne {
|
||||||
|
return present
|
||||||
|
}
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Not, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=present, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
left := lower_nested_expr(state, expr.left)
|
left := lower_nested_expr(state, expr.left)
|
||||||
right := lower_nested_expr(state, expr.right)
|
right := lower_nested_expr(state, expr.right)
|
||||||
predicate := ir.Compare_Predicate.Eq
|
predicate := ir.Compare_Predicate.Eq
|
||||||
|
|||||||
@@ -2176,7 +2176,11 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
else_body: []ast.Stmt_Id = nil
|
else_body: []ast.Stmt_Id = nil
|
||||||
saved_cursor := parser.cursor
|
saved_cursor := parser.cursor
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind == .Keyword_Else {
|
// `else:` (and the reserved `else |...|:` shape) starts the next match arm;
|
||||||
|
// it is not the else-branch of a brace-less if used as the previous arm body.
|
||||||
|
match_arm_else := current(parser).kind == .Keyword_Else &&
|
||||||
|
(peek(parser).kind == .Colon || peek(parser).kind == .Pipe)
|
||||||
|
if current(parser).kind == .Keyword_Else && !match_arm_else {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if current(parser).kind == .Keyword_If {
|
if current(parser).kind == .Keyword_If {
|
||||||
|
|||||||
@@ -9519,6 +9519,42 @@ main func() void {
|
|||||||
testing.expect(t, !found)
|
testing.expect(t, !found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
exhaustive_enum_guard_sequences_return :: proc(t: ^testing.T) {
|
||||||
|
text := `E :: enum { a, b }
|
||||||
|
complete func(value E) i32 {
|
||||||
|
if (value == .a) { return 1 }
|
||||||
|
if (value == .b) { return 2 }
|
||||||
|
}
|
||||||
|
incomplete func(value E) i32 {
|
||||||
|
if (value == .a) { return 1 }
|
||||||
|
}
|
||||||
|
main func() void {
|
||||||
|
_ = complete(.a)
|
||||||
|
_ = incomplete(.a)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
missing_returns := 0
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
if strings.contains(diagnostic.message, "does not return a value") {
|
||||||
|
missing_returns += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, missing_returns, 1)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
conditional_unwrap_compiles_and_runs :: proc(t: ^testing.T) {
|
conditional_unwrap_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-conditional-unwrap"
|
output := "/tmp/brolang-test-conditional-unwrap"
|
||||||
@@ -9612,6 +9648,38 @@ main func() void {
|
|||||||
testing.expect_value(t, len(unwrap_if.body), 1)
|
testing.expect_value(t, len(unwrap_if.body), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
match_else_arm_is_not_captured_by_braceless_if :: proc(t: ^testing.T) {
|
||||||
|
text := `ready func() bool { return true }
|
||||||
|
main func() void {
|
||||||
|
value i32 = 0
|
||||||
|
match value {
|
||||||
|
0: if ready() _ = value
|
||||||
|
else: _ = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
match_statement := module.statements[module.functions[1].body[1]]
|
||||||
|
testing.expect_value(t, len(match_statement.body), 2)
|
||||||
|
first_arm := module.statements[match_statement.body[0]]
|
||||||
|
first_if := module.statements[first_arm.body[0]]
|
||||||
|
testing.expect_value(t, first_if.kind, ast.Stmt_Kind.If)
|
||||||
|
testing.expect_value(t, len(first_if.else_body), 0)
|
||||||
|
else_arm := module.statements[match_statement.body[1]]
|
||||||
|
testing.expect_value(t, len(else_arm.patterns), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) {
|
parser_diagnoses_braceless_if_without_parens_or_call :: proc(t: ^testing.T) {
|
||||||
text := `main func() void {
|
text := `main func() void {
|
||||||
@@ -13786,6 +13854,7 @@ enum_field_struct_and_contextual_anonymous_records_compile_and_run :: proc(t: ^t
|
|||||||
main_path := "/tmp/brolang-test-enum-field-struct/main.bro"
|
main_path := "/tmp/brolang-test-enum-field-struct/main.bro"
|
||||||
output := "/tmp/brolang-test-enum-field-struct-output"
|
output := "/tmp/brolang-test-enum-field-struct-output"
|
||||||
text := `meta :: import "@std/meta"
|
text := `meta :: import "@std/meta"
|
||||||
|
testing :: import "@std/testing"
|
||||||
|
|
||||||
TokenKind :: enum(u8) {
|
TokenKind :: enum(u8) {
|
||||||
ident = 3
|
ident = 3
|
||||||
@@ -13818,6 +13887,8 @@ Generated func($T type) type {
|
|||||||
|
|
||||||
GeneratedInt :: alias Generated(i32)
|
GeneratedInt :: alias Generated(i32)
|
||||||
ordered Names = {}
|
ordered Names = {}
|
||||||
|
static_none ?i32 :: none
|
||||||
|
static_some ?i32 :: 1
|
||||||
|
|
||||||
Map func($E, $V type) type {
|
Map func($E, $V type) type {
|
||||||
match typeinfo!(E) {
|
match typeinfo!(E) {
|
||||||
@@ -13844,8 +13915,22 @@ init func($E, $V type, values meta.EnumFieldStruct(E, ?V, some!(none))) Map(E, V
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get func($E, $V type, map @Map(E, V), key E) ?V {
|
||||||
|
match typeinfo!(E) {
|
||||||
|
.enum |info|: expand for info.fields |field, index| {
|
||||||
|
if key == field!(E, field.name) {
|
||||||
|
if map.present[index] { return map.values[index] }
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else: compile_error!("EnumMap key must be an enum")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
_ = ordered
|
_ = ordered
|
||||||
|
if !$(static_none == none) or !$(none == static_none) or
|
||||||
|
$(static_some == none) or $(none == static_some) { return 23 }
|
||||||
inferred :: {x = 40, name = "bro"}
|
inferred :: {x = 40, name = "bro"}
|
||||||
if inferred.x != 40 or inferred.name.len != 3 { return 1 }
|
if inferred.x != 40 or inferred.name.len != 3 { return 1 }
|
||||||
direct Direct = {x = 7}
|
direct Direct = {x = 7}
|
||||||
@@ -13889,6 +13974,17 @@ main func() i32 {
|
|||||||
})
|
})
|
||||||
if !map.present[0] or !map.present[1] or map.present[2] { return 9 }
|
if !map.present[0] or !map.present[1] or map.present[2] { return 9 }
|
||||||
if map.values[0].len != 10 or map.values[1].len != 7 { return 18 }
|
if map.values[0].len != 10 or map.values[1].len != 7 { return 18 }
|
||||||
|
ident :: get(TokenKind, []u8, &map, TokenKind.ident)
|
||||||
|
if ident == none or none == ident { return 19 }
|
||||||
|
if ident |value| {
|
||||||
|
if value.len != 10 { return 19 }
|
||||||
|
} else { return 20 }
|
||||||
|
eof :: get(TokenKind, []u8, &map, TokenKind.eof)
|
||||||
|
if eof != none or none != eof { return 21 }
|
||||||
|
location testing.SourceLocation = {file = "test.bro", line = 1, column = 1}
|
||||||
|
testing.expect_equal(none, eof, location) catch |_| { return 24 }
|
||||||
|
testing.expect_equal(ident, ident, location) catch |_| { return 25 }
|
||||||
|
if eof |_| { return 22 }
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -2,6 +2,23 @@ arraylist :: import "@std/arraylist"
|
|||||||
mem :: import "@std/mem"
|
mem :: import "@std/mem"
|
||||||
std :: import "@std"
|
std :: import "@std"
|
||||||
|
|
||||||
|
Token :: struct { value i32 }
|
||||||
|
ScanDiagnostic :: struct { value i32 }
|
||||||
|
|
||||||
|
State :: struct {
|
||||||
|
tokens std.ArrayList(Token)
|
||||||
|
diagnostics std.ArrayList(ScanDiagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
arrlist_test std.ArrayList(Token) = arraylist.init(mem.c_allocator)
|
||||||
|
|
||||||
|
init func(allocator mem.Allocator) State {
|
||||||
|
return State {
|
||||||
|
tokens = arraylist.init(allocator),
|
||||||
|
diagnostics = arraylist.init(allocator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
hide fail_alloc func(_ ?@mut anyopaque, _ usize, _ usize) ?*mut u8 {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
@@ -24,6 +41,9 @@ hide fail_allocator mem.Allocator :: mem.Allocator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run func() i32 ! mem.AllocError {
|
run func() i32 ! mem.AllocError {
|
||||||
|
state State :: init(mem.c_allocator)
|
||||||
|
_ = state
|
||||||
|
|
||||||
values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
values std.ArrayList(i32) = arraylist.init(mem.c_allocator)
|
||||||
defer arraylist.deinit(&values)
|
defer arraylist.deinit(&values)
|
||||||
if (values.items.len != 0 or values.capacity != 0) return 1
|
if (values.items.len != 0 or values.capacity != 0) return 1
|
||||||
|
|||||||
+24
-3
@@ -1,4 +1,5 @@
|
|||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
|
import "@std/mem"
|
||||||
|
|
||||||
Error :: enum {
|
Error :: enum {
|
||||||
expectation_failed
|
expectation_failed
|
||||||
@@ -18,9 +19,29 @@ expect func(condition bool, location SourceLocation) void ! Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
||||||
if expected != actual {
|
match typeinfo!(T) {
|
||||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
.optional: {
|
||||||
return .expectation_failed
|
if expected |expected_value| {
|
||||||
|
if actual |actual_value| {
|
||||||
|
try expect_equal(expected_value, actual_value, location)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debug.print("{s}:{d}:{d}: expected an optional value, found none\n", {location.file, location.line, location.column})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
if actual |_| {
|
||||||
|
debug.print("{s}:{d}:{d}: expected none, found an optional value\n", {location.file, location.line, location.column})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.slice: if !mem.eql(expected, actual) {
|
||||||
|
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
|
else: if expected != actual {
|
||||||
|
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||||
|
return .expectation_failed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user