add noreturn and unreachable
This commit is contained in:
+2
-1
@@ -25,7 +25,7 @@ roadmap and milestone history.
|
|||||||
|
|
||||||
### scalar, aggregate, and pointer types
|
### scalar, aggregate, and pointer types
|
||||||
|
|
||||||
- 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
|
- 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 keyword casts such as `i32(x)` / `c_float(x)`
|
||||||
@@ -98,6 +98,7 @@ fields. `_` is not a keyword member name.
|
|||||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `expand for` specializes a comptime aggregate into one checked body per element
|
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures; `expand for` specializes a comptime aggregate into one checked body per element
|
||||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks; `break :label` can cross nested scopes to exit a labeled block
|
||||||
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||||
|
- always-trapping `unreachable`, a `noreturn` expression that diagnoses use during comptime evaluation and terminates the current runtime path
|
||||||
- bare void `return`, same-line `return value`, value blocks, value `if` with implicit single-expression branches, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
|
- bare void `return`, same-line `return value`, value blocks, value `if` with implicit single-expression branches, value loops, value `match`, and strictly value-producing `yield value` / `yield :label value`
|
||||||
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
|
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
|
||||||
- a final `expand |value|:` enum arm or `expand |payload[, tag]|:` tagged-union arm generates one specialized arm for each variant not covered earlier; enum values and optional tags are comptime-known, while union payloads keep their concrete variant type
|
- a final `expand |value|:` enum arm or `expand |payload[, tag]|:` tagged-union arm generates one specialized arm for each variant not covered earlier; enum values and optional tags are comptime-known, while union payloads keep their concrete variant type
|
||||||
|
|||||||
@@ -949,6 +949,12 @@
|
|||||||
|
|
||||||
47. rename optional `none` to `null` (implemented)
|
47. rename optional `none` to `null` (implemented)
|
||||||
|
|
||||||
|
48. add `noreturn` and `unreachable` (implemented)
|
||||||
|
- `noreturn` is the native bottom type, permitted as a function result but rejected for storage,
|
||||||
|
parameters, record fields, and the C ABI
|
||||||
|
- `noreturn` expressions coerce to any expected value type and terminate path analysis
|
||||||
|
- `unreachable` always traps at runtime and reports an error during comptime evaluation
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
For casts that bypass safety checks, Honey provides builtin functions:
|
For casts that bypass safety checks, Honey provides builtin functions:
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Bool,
|
Bool,
|
||||||
Array,
|
Array,
|
||||||
Null,
|
Null,
|
||||||
|
Unreachable,
|
||||||
Undefined,
|
Undefined,
|
||||||
Inference_Hole,
|
Inference_Hole,
|
||||||
Type,
|
Type,
|
||||||
|
|||||||
@@ -394,7 +394,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
|||||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
||||||
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&expr_stack, expr.left, expr.right)
|
append(&expr_stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Undefined, .Inference_Hole,
|
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Unreachable, .Undefined, .Inference_Hole,
|
||||||
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
|
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,7 +518,7 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
|
|||||||
strings.write_string(builder, "enum")
|
strings.write_string(builder, "enum")
|
||||||
case .Alias, .Distinct, .Named:
|
case .Alias, .Distinct, .Named:
|
||||||
write_type_label(checker, builder, item.child)
|
write_type_label(checker, builder, item.child)
|
||||||
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
case .Invalid, .Void, .Noreturn, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
||||||
strings.write_string(builder, types.name(value))
|
strings.write_string(builder, types.name(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2219,7 +2219,8 @@ call_mapping_semantically_valid :: proc(
|
|||||||
}
|
}
|
||||||
if is_runtime_type(checker, expected) {
|
if is_runtime_type(checker, expected) {
|
||||||
result := function_channel_type(checker, function)
|
result := function_channel_type(checker, function)
|
||||||
if !is_runtime_type(checker, result) || !can_implicitly_convert_type(checker, result, expected) {
|
if !types.is_noreturn(result) && !is_runtime_type(checker, result) ||
|
||||||
|
!can_implicitly_convert_type(checker, result, expected) {
|
||||||
return false, fmt.aprintf(
|
return false, fmt.aprintf(
|
||||||
"result type %s cannot convert to expected type %s",
|
"result type %s cannot convert to expected type %s",
|
||||||
type_label(checker, result), type_label(checker, expected),
|
type_label(checker, result), type_label(checker, expected),
|
||||||
@@ -3365,7 +3366,7 @@ function_value_signature :: proc(
|
|||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
result = function_channel_type(checker, function)
|
result = function_channel_type(checker, function)
|
||||||
if !types.is_void(result) && !is_runtime_type(checker, result) {
|
if !types.is_void(result) && !types.is_noreturn(result) && !is_runtime_type(checker, result) {
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
params = make([]types.Type, len(function.params), checker.allocator)
|
params = make([]types.Type, len(function.params), checker.allocator)
|
||||||
@@ -3402,6 +3403,9 @@ function_type_for_template :: proc(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
spec = find_spec(checker, template, params)
|
spec = find_spec(checker, template, params)
|
||||||
|
if spec == INVALID_SPEC {
|
||||||
|
spec = ensure_spec(checker, template, params)
|
||||||
|
}
|
||||||
mark_spec_demanded(checker, spec, demanded)
|
mark_spec_demanded(checker, spec, demanded)
|
||||||
}
|
}
|
||||||
return function_type, spec, spec != INVALID_SPEC || !demand_spec
|
return function_type, spec, spec != INVALID_SPEC || !demand_spec
|
||||||
@@ -3506,7 +3510,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
|
||||||
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
|
case .Invalid, .Integer, .Float, .String, .Bool, .Null, .Unreachable, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3695,6 +3699,13 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
"void is only valid as a function result type",
|
"void is only valid as a function result type",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if !param.comptime_value && types.is_noreturn(param_type) {
|
||||||
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
param.span,
|
||||||
|
"noreturn is only valid as a native function result type",
|
||||||
|
)
|
||||||
|
}
|
||||||
if param.name != checker.sink_symbol && contains_name(locals[:], param.name) {
|
if param.name != checker.sink_symbol && contains_name(locals[:], param.name) {
|
||||||
source.addf(
|
source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -3720,6 +3731,13 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
if !has_comptime && !signature_poisoned {
|
if !has_comptime && !signature_poisoned {
|
||||||
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
|
if function.c_abi && types.is_noreturn(result_type) {
|
||||||
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
|
checker.diagnostics,
|
||||||
|
function.span,
|
||||||
|
"noreturn is not supported across the C ABI",
|
||||||
|
)
|
||||||
|
}
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
@@ -3788,6 +3806,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
result := type_from_syntax(checker, function.result, function.pkg, function.file)
|
result := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
|
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
|
||||||
|
!types.is_noreturn(result) &&
|
||||||
!types.contains_c_struct_by_value(result, &checker.module.types) &&
|
!types.contains_c_struct_by_value(result, &checker.module.types) &&
|
||||||
!types.is_c_signature_type(result, &checker.module.types, true) {
|
!types.is_c_signature_type(result, &checker.module.types, true) {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
@@ -4116,7 +4135,7 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
|||||||
type_item, type_ok := types.node(&checker.module.types, type_info)
|
type_item, type_ok := types.node(&checker.module.types, type_info)
|
||||||
type_fields := types.fields_for(&checker.module.types, type_info)
|
type_fields := types.fields_for(&checker.module.types, type_info)
|
||||||
expected_tags := []string{
|
expected_tags := []string{
|
||||||
"invalid", "void", "anyopaque", "bool", "integer", "float", "array", "pointer", "slice",
|
"invalid", "void", "noreturn", "anyopaque", "bool", "integer", "float", "array", "pointer", "slice",
|
||||||
"range", "optional", "function", "enum", "record", "union", "fallible", "distinct",
|
"range", "optional", "function", "enum", "record", "union", "fallible", "distinct",
|
||||||
}
|
}
|
||||||
valid = valid && type_ok && type_item.kind == .Union &&
|
valid = valid && type_ok && type_item.kind == .Union &&
|
||||||
@@ -4308,8 +4327,8 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
source.add(checker.diagnostics, source.Span{}, "native function pointer parameters must be concrete runtime types")
|
source.add(checker.diagnostics, source.Span{}, "native function pointer parameters must be concrete runtime types")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !types.is_void(item.child) && !is_runtime_type(checker, item.child) {
|
if !types.is_void(item.child) && !types.is_noreturn(item.child) && !is_runtime_type(checker, item.child) {
|
||||||
source.add(checker.diagnostics, source.Span{}, "native function pointer results must be concrete runtime types or void")
|
source.add(checker.diagnostics, source.Span{}, "native function pointer results must be concrete runtime types, void, or noreturn")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4631,6 +4650,8 @@ 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 .Null:
|
case .Null:
|
||||||
return expected if types.is_optional(expected, store) else types.INVALID
|
return expected if types.is_optional(expected, store) else types.INVALID
|
||||||
|
case .Unreachable:
|
||||||
|
return types.NORETURN
|
||||||
case .Undefined:
|
case .Undefined:
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
case .Enum_Literal:
|
case .Enum_Literal:
|
||||||
@@ -4928,7 +4949,7 @@ infer_expr :: proc(
|
|||||||
case .Float:
|
case .Float:
|
||||||
last = types.F64
|
last = types.F64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .Null, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||||
.Comptime, .Bool, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
.Comptime, .Bool, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
||||||
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
@@ -5274,7 +5295,7 @@ infer_expr :: proc(
|
|||||||
}
|
}
|
||||||
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
|
||||||
declared := function_channel_type(checker, checker.ast_module.functions[template])
|
declared := function_channel_type(checker, checker.ast_module.functions[template])
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -5415,6 +5436,9 @@ infer_expr :: proc(
|
|||||||
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
} else {
|
} else {
|
||||||
spec = find_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
spec = find_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
|
if spec == INVALID_SPEC {
|
||||||
|
spec = ensure_spec(checker, frame.template, stack[frame_index].args, comptime_values)
|
||||||
|
}
|
||||||
mark_spec_demanded(checker, spec, demanded)
|
mark_spec_demanded(checker, spec, demanded)
|
||||||
}
|
}
|
||||||
if spec != INVALID_SPEC {
|
if spec != INVALID_SPEC {
|
||||||
@@ -5424,7 +5448,7 @@ infer_expr :: proc(
|
|||||||
checker.current_comptime_values = comptime_values
|
checker.current_comptime_values = comptime_values
|
||||||
declared := function_channel_type(checker, function)
|
declared := function_channel_type(checker, function)
|
||||||
checker.current_comptime_values = previous_comptime
|
checker.current_comptime_values = previous_comptime
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
declared := function_channel_type(checker, function)
|
declared := function_channel_type(checker, function)
|
||||||
@@ -5432,7 +5456,7 @@ infer_expr :: proc(
|
|||||||
!types.is_valid(function.error) {
|
!types.is_valid(function.error) {
|
||||||
last = types.I32
|
last = types.I32
|
||||||
} else {
|
} else {
|
||||||
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
|
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) || types.is_noreturn(declared) else types.INVALID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete(stack[frame_index].args, checker.allocator)
|
delete(stack[frame_index].args, checker.allocator)
|
||||||
@@ -5957,7 +5981,7 @@ infer_spec_locals_and_result :: proc(
|
|||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
||||||
declared = types.I32
|
declared = types.I32
|
||||||
}
|
}
|
||||||
result_hint := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
result_hint := declared if is_runtime_type(checker, declared) || types.is_noreturn(declared) || declared == types.UINT else types.INVALID
|
||||||
|
|
||||||
locals: [dynamic]Infer_Local
|
locals: [dynamic]Infer_Local
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
@@ -6746,7 +6770,8 @@ find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local
|
|||||||
|
|
||||||
can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.Type) -> bool {
|
can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.Type) -> bool {
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
if types.equal(actual, expected) ||
|
if types.is_noreturn(actual) ||
|
||||||
|
types.equal(actual, expected) ||
|
||||||
types.can_widen(actual, expected) ||
|
types.can_widen(actual, expected) ||
|
||||||
types.can_coerce_c_integer(actual, expected, checker.target) ||
|
types.can_coerce_c_integer(actual, expected, checker.target) ||
|
||||||
types.can_coerce_c_scalar(actual, expected, checker.target) ||
|
types.can_coerce_c_scalar(actual, expected, checker.target) ||
|
||||||
@@ -6777,6 +6802,9 @@ coerce_expr :: proc(
|
|||||||
return expr_id
|
return expr_id
|
||||||
}
|
}
|
||||||
actual := checker.module.exprs[expr_id].type
|
actual := checker.module.exprs[expr_id].type
|
||||||
|
if types.is_noreturn(actual) {
|
||||||
|
return expr_id
|
||||||
|
}
|
||||||
if types.equal(actual, expected) {
|
if types.equal(actual, expected) {
|
||||||
return expr_id
|
return expr_id
|
||||||
}
|
}
|
||||||
@@ -7782,6 +7810,11 @@ build_compound_expr :: proc(
|
|||||||
kind=.Null, span=expr.span, type=expected, target=hir.INVALID_REF,
|
kind=.Null, span=expr.span, type=expected, target=hir.INVALID_REF,
|
||||||
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Unreachable:
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Unreachable, span=expr.span, type=types.NORETURN, target=hir.INVALID_REF,
|
||||||
|
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Undefined:
|
case .Undefined:
|
||||||
id := source.add(
|
id := source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -8703,7 +8736,7 @@ build_expr :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .String, .Array, .Null, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
||||||
.Bool, .Cast, .Comptime, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
.Bool, .Cast, .Comptime, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
||||||
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||||
@@ -9413,12 +9446,16 @@ build_expr :: proc(
|
|||||||
checker.current_comptime_values = previous_comptime
|
checker.current_comptime_values = previous_comptime
|
||||||
}
|
}
|
||||||
spec := INVALID_SPEC
|
spec := INVALID_SPEC
|
||||||
if comptime_ok && frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
if comptime_ok {
|
||||||
|
if frame.resolution >= 0 && frame.resolution < len(checker.call_resolutions) {
|
||||||
spec = find_spec(
|
spec = find_spec(
|
||||||
checker, frame.template,
|
checker, frame.template,
|
||||||
checker.call_resolutions[frame.resolution].runtime_types,
|
checker.call_resolutions[frame.resolution].runtime_types,
|
||||||
comptime_values,
|
comptime_values,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
spec = find_spec(checker, frame.template, stack[frame_index].arg_types, comptime_values)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete(stack[frame_index].arg_types, checker.allocator)
|
delete(stack[frame_index].arg_types, checker.allocator)
|
||||||
stack[frame_index].arg_types = nil
|
stack[frame_index].arg_types = nil
|
||||||
@@ -10329,7 +10366,7 @@ build_block :: proc(
|
|||||||
value_type = checker.module.exprs[value].type
|
value_type = checker.module.exprs[value].type
|
||||||
if is_runtime_type(checker, declared) {
|
if is_runtime_type(checker, declared) {
|
||||||
value = coerce_expr(checker, value, declared, statement.span)
|
value = coerce_expr(checker, value, declared, statement.span)
|
||||||
value_type = checker.module.exprs[value].type
|
value_type = declared
|
||||||
} else if types.is_void(declared) {
|
} else if types.is_void(declared) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
|
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
|
||||||
value = invalid_hir_expr(checker, statement.span, id)
|
value = invalid_hir_expr(checker, statement.span, id)
|
||||||
@@ -10351,6 +10388,24 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if types.is_noreturn(value_type) {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
statement.span,
|
||||||
|
"local '%s' cannot store a noreturn value",
|
||||||
|
symbol_text(checker, statement.name),
|
||||||
|
)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Trap, span=statement.span, expr=hir.INVALID_EXPR,
|
||||||
|
local=hir.INVALID_LOCAL, diagnostic=id,
|
||||||
|
})
|
||||||
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); !found {
|
||||||
|
_ = append_build_local(ctx, statement.name, types.I64, !statement.immutable, statement.span)
|
||||||
|
}
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics, statement.span,
|
checker.diagnostics, statement.span,
|
||||||
@@ -10816,7 +10871,8 @@ build_block :: proc(
|
|||||||
local = hir.INVALID_LOCAL, diagnostic = diagnostic,
|
local = hir.INVALID_LOCAL, diagnostic = diagnostic,
|
||||||
})
|
})
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
} else if !types.is_void(checker.module.exprs[value].type) {
|
} else if !types.is_void(checker.module.exprs[value].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[value].type) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -10926,7 +10982,8 @@ build_block :: proc(
|
|||||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||||
diagnostic = checker.module.exprs[guard].diagnostic
|
diagnostic = checker.module.exprs[guard].diagnostic
|
||||||
}
|
}
|
||||||
} else if !types.is_bool(checker.module.exprs[guard].type) {
|
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||||
diagnostic = source.add(
|
diagnostic = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
checker.ast_module.exprs[statement.guard].span,
|
checker.ast_module.exprs[statement.guard].span,
|
||||||
@@ -10977,7 +11034,9 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||||
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
|
if checker.module.exprs[condition].kind != .Invalid &&
|
||||||
|
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
|
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
|
||||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
@@ -11002,7 +11061,8 @@ build_block :: proc(
|
|||||||
types.BOOL, ctx.pkg, ctx.file,
|
types.BOOL, ctx.pkg, ctx.file,
|
||||||
)
|
)
|
||||||
if checker.module.exprs[condition].kind != .Invalid &&
|
if checker.module.exprs[condition].kind != .Invalid &&
|
||||||
!types.is_bool(checker.module.exprs[condition].type) {
|
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
id := source.add(checker.diagnostics, statement.span, "'while' condition must be a bool")
|
||||||
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
@@ -11797,7 +11857,8 @@ emit_value_if :: proc(
|
|||||||
guard = build_expr(checker, if_stmt.guard, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
guard = build_expr(checker, if_stmt.guard, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||||
if checker.module.exprs[guard].kind == .Invalid {
|
if checker.module.exprs[guard].kind == .Invalid {
|
||||||
ok = false
|
ok = false
|
||||||
} else if !types.is_bool(checker.module.exprs[guard].type) {
|
} else if !types.is_bool(checker.module.exprs[guard].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[guard].type) {
|
||||||
source.add(checker.diagnostics, checker.ast_module.exprs[if_stmt.guard].span, "'if' unwrap guard must be a bool")
|
source.add(checker.diagnostics, checker.ast_module.exprs[if_stmt.guard].span, "'if' unwrap guard must be a bool")
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
@@ -11814,7 +11875,9 @@ emit_value_if :: proc(
|
|||||||
unwraps = unwrap_list[:]
|
unwraps = unwrap_list[:]
|
||||||
} else {
|
} else {
|
||||||
condition = build_expr(checker, if_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
condition = build_expr(checker, if_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||||
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
|
if checker.module.exprs[condition].kind != .Invalid &&
|
||||||
|
!types.is_bool(checker.module.exprs[condition].type) &&
|
||||||
|
!types.is_noreturn(checker.module.exprs[condition].type) {
|
||||||
id := source.add(checker.diagnostics, if_stmt.span, "'if' condition must be a bool")
|
id := source.add(checker.diagnostics, if_stmt.span, "'if' condition must be a bool")
|
||||||
condition = invalid_hir_expr(checker, if_stmt.span, id, types.BOOL)
|
condition = invalid_hir_expr(checker, if_stmt.span, id, types.BOOL)
|
||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
@@ -13000,6 +13063,10 @@ enum_guards_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []
|
|||||||
all_paths_return :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id, locals: []hir.Local = nil) -> 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]
|
||||||
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) &&
|
||||||
|
types.is_noreturn(module.exprs[statement.expr].type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
case .Return, .Trap:
|
case .Return, .Trap:
|
||||||
return true
|
return true
|
||||||
@@ -13049,6 +13116,10 @@ loop_body_breaks :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
|||||||
all_paths_exit :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
all_paths_exit :: proc(module: ^hir.Module, stmts: []hir.Stmt_Id) -> bool {
|
||||||
for id in stmts {
|
for id in stmts {
|
||||||
statement := module.statements[id]
|
statement := module.statements[id]
|
||||||
|
if statement.expr != hir.INVALID_EXPR && int(statement.expr) < len(module.exprs) &&
|
||||||
|
types.is_noreturn(module.exprs[statement.expr].type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
case .Return, .Trap, .Break, .Continue:
|
case .Return, .Trap, .Break, .Continue:
|
||||||
return true
|
return true
|
||||||
@@ -13078,7 +13149,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
checker.current_comptime_values = spec.comptime_values
|
checker.current_comptime_values = spec.comptime_values
|
||||||
defer checker.current_comptime_values = previous_comptime
|
defer checker.current_comptime_values = previous_comptime
|
||||||
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
||||||
unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result)
|
unresolved_result := !types.is_void(spec.result) && !types.is_noreturn(spec.result) && !is_runtime_type(checker, spec.result)
|
||||||
if unresolved_result {
|
if unresolved_result {
|
||||||
if types.is_comptime_only(spec.result, &checker.module.types) {
|
if types.is_comptime_only(spec.result, &checker.module.types) {
|
||||||
signature_diagnostic = source.addf(
|
signature_diagnostic = source.addf(
|
||||||
|
|||||||
@@ -1159,6 +1159,10 @@ ct_eval_expr :: proc(
|
|||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'null' requires an optional context")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "'null' requires an optional context")
|
||||||
}
|
}
|
||||||
return ct_add_value(state, Ct_Value{kind=.Null, type=expected}), ct_flow(.Normal), true
|
return ct_add_value(state, Ct_Value{kind=.Null, type=expected}), ct_flow(.Normal), true
|
||||||
|
case .Unreachable:
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||||
|
state, .Not_Comptime, expr.span, "reached unreachable code during comptime evaluation",
|
||||||
|
)
|
||||||
case .Field:
|
case .Field:
|
||||||
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, state.pkg, state.file); enum_ok {
|
if enum_type, enum_ok := enum_type_from_field_expr(checker, expr, state.pkg, state.file); enum_ok {
|
||||||
member, ok := find_enum_member(checker, enum_type, expr.name)
|
member, ok := find_enum_member(checker, enum_type, expr.name)
|
||||||
@@ -2697,6 +2701,7 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
|||||||
tag := "invalid"
|
tag := "invalid"
|
||||||
if !item_ok {
|
if !item_ok {
|
||||||
if types.is_void(resolved) { tag = "void" }
|
if types.is_void(resolved) { tag = "void" }
|
||||||
|
else if types.is_noreturn(resolved) { tag = "noreturn" }
|
||||||
else if types.is_anyopaque(resolved) { tag = "anyopaque" }
|
else if types.is_anyopaque(resolved) { tag = "anyopaque" }
|
||||||
else if types.is_bool(resolved) { tag = "bool" }
|
else if types.is_bool(resolved) { tag = "bool" }
|
||||||
else if types.is_concrete_integer(resolved) { tag = "integer" }
|
else if types.is_concrete_integer(resolved) { tag = "integer" }
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Array,
|
Array,
|
||||||
Struct,
|
Struct,
|
||||||
Null,
|
Null,
|
||||||
|
Unreachable,
|
||||||
Optional_Some,
|
Optional_Some,
|
||||||
Local,
|
Local,
|
||||||
Global,
|
Global,
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "true": return .Keyword_True
|
case "true": return .Keyword_True
|
||||||
case "false": return .Keyword_False
|
case "false": return .Keyword_False
|
||||||
case "void": return .Keyword_Void
|
case "void": return .Keyword_Void
|
||||||
|
case "noreturn": return .Keyword_Noreturn
|
||||||
case "anyopaque": return .Keyword_Anyopaque
|
case "anyopaque": return .Keyword_Anyopaque
|
||||||
|
case "unreachable": return .Keyword_Unreachable
|
||||||
case "bool": return .Keyword_Bool
|
case "bool": return .Keyword_Bool
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
case "uint": return .Keyword_Uint
|
case "uint": return .Keyword_Uint
|
||||||
|
|||||||
+36
-6
@@ -142,6 +142,11 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
|
|||||||
if types.is_void(resolved) {
|
if types.is_void(resolved) {
|
||||||
return "void"
|
return "void"
|
||||||
}
|
}
|
||||||
|
// `noreturn` has no value representation. Keep malformed storage recoverable with
|
||||||
|
// a byte carrier; function and call result positions map it to LLVM `void` below.
|
||||||
|
if types.is_noreturn(resolved) {
|
||||||
|
return "i8"
|
||||||
|
}
|
||||||
if types.is_bool(resolved) {
|
if types.is_bool(resolved) {
|
||||||
return "i1"
|
return "i1"
|
||||||
}
|
}
|
||||||
@@ -183,6 +188,9 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri
|
|||||||
if function.is_main {
|
if function.is_main {
|
||||||
return "i32"
|
return "i32"
|
||||||
}
|
}
|
||||||
|
if types.is_noreturn(function.result) {
|
||||||
|
return "void"
|
||||||
|
}
|
||||||
if function.calling_convention == .C {
|
if function.calling_convention == .C {
|
||||||
return c_abi_result_type(function.result, store)
|
return c_abi_result_type(function.result, store)
|
||||||
}
|
}
|
||||||
@@ -2121,7 +2129,7 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, " ")
|
strings.write_string(&emitter.builder, " ")
|
||||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||||
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
||||||
} else if !types.is_void(instruction.type) {
|
} else if !types.is_void(instruction.type) && !types.is_noreturn(instruction.type) {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
||||||
} else {
|
} else {
|
||||||
strings.write_string(&emitter.builder, " ")
|
strings.write_string(&emitter.builder, " ")
|
||||||
@@ -2137,7 +2145,10 @@ emit_instruction_stream :: proc(
|
|||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types))
|
strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types))
|
||||||
} else {
|
} else {
|
||||||
strings.write_string(&emitter.builder, llvm_type(function_item.child, &emitter.module.types))
|
strings.write_string(
|
||||||
|
&emitter.builder,
|
||||||
|
"void" if types.is_noreturn(function_item.child) else llvm_type(function_item.child, &emitter.module.types),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if function_item.variadic {
|
if function_item.variadic {
|
||||||
strings.write_string(&emitter.builder, " (")
|
strings.write_string(&emitter.builder, " (")
|
||||||
@@ -2187,6 +2198,10 @@ emit_instruction_stream :: proc(
|
|||||||
wrote_arg = true
|
wrote_arg = true
|
||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, ")\n")
|
strings.write_string(&emitter.builder, ")\n")
|
||||||
|
if types.is_noreturn(instruction.type) {
|
||||||
|
strings.write_string(&emitter.builder, " unreachable\n")
|
||||||
|
after_terminator = true
|
||||||
|
}
|
||||||
if result_abi.kind == .Indirect {
|
if result_abi.kind == .Indirect {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index)
|
||||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||||
@@ -2248,7 +2263,7 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, " ")
|
strings.write_string(&emitter.builder, " ")
|
||||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||||
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
|
||||||
} else if !types.is_void(instruction.type) {
|
} else if !types.is_void(instruction.type) && !types.is_noreturn(instruction.type) {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
|
||||||
} else {
|
} else {
|
||||||
strings.write_string(&emitter.builder, " ")
|
strings.write_string(&emitter.builder, " ")
|
||||||
@@ -2304,6 +2319,10 @@ emit_instruction_stream :: proc(
|
|||||||
wrote_arg = true
|
wrote_arg = true
|
||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, ")\n")
|
strings.write_string(&emitter.builder, ")\n")
|
||||||
|
if types.is_noreturn(instruction.type) {
|
||||||
|
strings.write_string(&emitter.builder, " unreachable\n")
|
||||||
|
after_terminator = true
|
||||||
|
}
|
||||||
if result_abi.kind == .Indirect {
|
if result_abi.kind == .Indirect {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index)
|
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index)
|
||||||
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
|
||||||
@@ -2355,8 +2374,11 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, ", label %%bro_block_%d, label %%bro_block_%d\n", instruction.integer, u32(instruction.target))
|
fmt.sbprintf(&emitter.builder, ", label %%bro_block_%d, label %%bro_block_%d\n", instruction.integer, u32(instruction.target))
|
||||||
after_terminator = true
|
after_terminator = true
|
||||||
case .Trap:
|
case .Trap:
|
||||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
fallback := "reached unreachable code" if instruction.integer != 0 else "invalid recovered source"
|
||||||
|
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
|
||||||
emit_trap_call(emitter, message)
|
emit_trap_call(emitter, message)
|
||||||
|
strings.write_string(&emitter.builder, " unreachable\n")
|
||||||
|
after_terminator = true
|
||||||
case .Return:
|
case .Return:
|
||||||
if global_initializer {
|
if global_initializer {
|
||||||
return_value = instruction.a
|
return_value = instruction.a
|
||||||
@@ -2681,10 +2703,18 @@ emit_functions :: proc(emitter: ^Emitter) {
|
|||||||
strings.write_string(&emitter.builder, "...")
|
strings.write_string(&emitter.builder, "...")
|
||||||
}
|
}
|
||||||
if function.implementation == .Declaration {
|
if function.implementation == .Declaration {
|
||||||
strings.write_string(&emitter.builder, ")\n\n")
|
strings.write_string(&emitter.builder, ")")
|
||||||
|
if types.is_noreturn(function.result) {
|
||||||
|
strings.write_string(&emitter.builder, " noreturn")
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, "\n\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
strings.write_string(&emitter.builder, ")")
|
||||||
|
if types.is_noreturn(function.result) {
|
||||||
|
strings.write_string(&emitter.builder, " noreturn")
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, " {\nentry:\n")
|
||||||
emit_entry_allocas(emitter, function.instructions)
|
emit_entry_allocas(emitter, function.instructions)
|
||||||
if function.calling_convention == .C {
|
if function.calling_convention == .C {
|
||||||
for param_type, index in function.param_types {
|
for param_type, index in function.param_types {
|
||||||
|
|||||||
@@ -716,6 +716,13 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
|
case .Unreachable:
|
||||||
|
last = append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap, span=expr.span, type=types.NORETURN, integer=1,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
_ = pop(&stack)
|
||||||
case .String, .Array, .Struct, .Range, .Null, .Optional_Some, .Address, .Deref,
|
case .String, .Array, .Struct, .Range, .Null, .Optional_Some, .Address, .Deref,
|
||||||
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||||
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
@@ -1037,6 +1044,9 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
value := lower_expr(state, statement.expr)
|
value := lower_expr(state, statement.expr)
|
||||||
|
if types.is_noreturn(state.hir_module.exprs[statement.expr].type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Return,
|
op=.Return,
|
||||||
span=statement.span,
|
span=statement.span,
|
||||||
@@ -1680,11 +1690,22 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
}
|
}
|
||||||
|
|
||||||
lower_statements(&state, function.body)
|
lower_statements(&state, function.body)
|
||||||
if len(state.instructions) == 0 ||
|
last_terminates := false
|
||||||
(state.instructions[len(state.instructions)-1].op != .Return &&
|
if len(state.instructions) > 0 {
|
||||||
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
last_instruction := state.instructions[len(state.instructions)-1]
|
||||||
|
last_terminates = last_instruction.op == .Return || last_instruction.op == .Return_Void ||
|
||||||
|
last_instruction.op == .Trap ||
|
||||||
|
last_instruction.op == .Call && types.is_noreturn(last_instruction.type)
|
||||||
|
}
|
||||||
|
if !last_terminates {
|
||||||
if types.is_void(function.result) {
|
if types.is_void(function.result) {
|
||||||
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
|
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
|
||||||
|
} else if types.is_noreturn(function.result) {
|
||||||
|
append_instruction(&state, ir.Instruction{
|
||||||
|
op=.Trap, type=types.NORETURN, integer=1,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
value := append_instruction(&state, ir.Instruction{
|
value := append_instruction(&state, ir.Instruction{
|
||||||
op=.Const,
|
op=.Const,
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ is_type_token :: proc(kind: token.Kind) -> bool {
|
|||||||
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
||||||
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||||
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
||||||
.Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func,
|
.Keyword_Void, .Keyword_Noreturn, .Keyword_Anyopaque, .Keyword_Bool, .Keyword_Func, .Keyword_C_Func,
|
||||||
.Identifier, .Question, .At, .Star, .Left_Bracket:
|
.Identifier, .Question, .At, .Star, .Left_Bracket:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -373,6 +373,9 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Void:
|
case .Keyword_Void:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.VOID
|
return types.VOID
|
||||||
|
case .Keyword_Noreturn:
|
||||||
|
advance(parser)
|
||||||
|
return types.NORETURN
|
||||||
case .Keyword_Anyopaque:
|
case .Keyword_Anyopaque:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.ANYOPAQUE
|
return types.ANYOPAQUE
|
||||||
@@ -851,7 +854,7 @@ parse_integer_magnitude :: proc(text: string) -> (u64, bool) {
|
|||||||
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||||
tok := current(parser)
|
tok := current(parser)
|
||||||
#partial switch tok.kind {
|
#partial switch tok.kind {
|
||||||
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Anyopaque, .Keyword_Bool:
|
case .Keyword_Int, .Keyword_Uint, .Keyword_Float, .Keyword_Range, .Keyword_Void, .Keyword_Noreturn, .Keyword_Anyopaque, .Keyword_Bool:
|
||||||
start := tok
|
start := tok
|
||||||
target := parse_type_atom(parser)
|
target := parse_type_atom(parser)
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
@@ -975,6 +978,15 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
right=ast.INVALID_EXPR,
|
right=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Keyword_Unreachable:
|
||||||
|
advance(parser)
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Unreachable,
|
||||||
|
span=tok.span,
|
||||||
|
left=ast.INVALID_EXPR,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Keyword_Undefined:
|
case .Keyword_Undefined:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ Kind :: enum u8 {
|
|||||||
Keyword_True,
|
Keyword_True,
|
||||||
Keyword_False,
|
Keyword_False,
|
||||||
Keyword_Void,
|
Keyword_Void,
|
||||||
|
Keyword_Noreturn,
|
||||||
Keyword_Anyopaque,
|
Keyword_Anyopaque,
|
||||||
|
Keyword_Unreachable,
|
||||||
Keyword_Bool,
|
Keyword_Bool,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
Keyword_Uint,
|
Keyword_Uint,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ FLOAT :: Type(30)
|
|||||||
RANGE :: Type(31)
|
RANGE :: Type(31)
|
||||||
ANYOPAQUE :: Type(32)
|
ANYOPAQUE :: Type(32)
|
||||||
UINT :: Type(33)
|
UINT :: Type(33)
|
||||||
|
NORETURN :: Type(34)
|
||||||
|
|
||||||
DYNAMIC_START :: Type(64)
|
DYNAMIC_START :: Type(64)
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ Numeric_Category :: enum u8 {
|
|||||||
Kind :: enum u8 {
|
Kind :: enum u8 {
|
||||||
Invalid,
|
Invalid,
|
||||||
Void,
|
Void,
|
||||||
|
Noreturn,
|
||||||
Anyopaque,
|
Anyopaque,
|
||||||
Int_Constraint,
|
Int_Constraint,
|
||||||
Uint_Constraint,
|
Uint_Constraint,
|
||||||
@@ -599,6 +601,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|||||||
return .Invalid
|
return .Invalid
|
||||||
case VOID:
|
case VOID:
|
||||||
return .Void
|
return .Void
|
||||||
|
case NORETURN:
|
||||||
|
return .Noreturn
|
||||||
case ANYOPAQUE:
|
case ANYOPAQUE:
|
||||||
return .Anyopaque
|
return .Anyopaque
|
||||||
case INT:
|
case INT:
|
||||||
@@ -643,6 +647,10 @@ is_void :: proc(value: Type) -> bool {
|
|||||||
return value == VOID
|
return value == VOID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_noreturn :: proc(value: Type) -> bool {
|
||||||
|
return value == NORETURN
|
||||||
|
}
|
||||||
|
|
||||||
is_anyopaque :: proc(value: Type) -> bool {
|
is_anyopaque :: proc(value: Type) -> bool {
|
||||||
return value == ANYOPAQUE
|
return value == ANYOPAQUE
|
||||||
}
|
}
|
||||||
@@ -1717,6 +1725,12 @@ can_coerce_c_scalar :: proc(from, to: Type, selected := target.DEFAULT) -> bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
widest :: proc(a, b: Type) -> Type {
|
widest :: proc(a, b: Type) -> Type {
|
||||||
|
if is_noreturn(a) {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
if is_noreturn(b) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
if equal(a, b) && is_concrete_scalar(a) {
|
if equal(a, b) && is_concrete_scalar(a) {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
@@ -1759,6 +1773,7 @@ name :: proc(value: Type) -> string {
|
|||||||
switch value {
|
switch value {
|
||||||
case INVALID: return "<invalid>"
|
case INVALID: return "<invalid>"
|
||||||
case VOID: return "void"
|
case VOID: return "void"
|
||||||
|
case NORETURN: return "noreturn"
|
||||||
case ANYOPAQUE: return "anyopaque"
|
case ANYOPAQUE: return "anyopaque"
|
||||||
case BOOL: return "bool"
|
case BOOL: return "bool"
|
||||||
case INT: return "int"
|
case INT: return "int"
|
||||||
|
|||||||
@@ -14271,6 +14271,131 @@ std_meta_tests_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
noreturn_functions_function_pointers_and_peer_types_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-noreturn"
|
||||||
|
main_path := "/tmp/brolang-test-noreturn/main.bro"
|
||||||
|
output := "/tmp/brolang-test-noreturn-output"
|
||||||
|
text := `meta :: import "@std/meta"
|
||||||
|
|
||||||
|
die func() noreturn {
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
forward func() noreturn {
|
||||||
|
die()
|
||||||
|
}
|
||||||
|
|
||||||
|
choose func(flag bool) i32 {
|
||||||
|
return if flag { yield 42 } else { yield die() }
|
||||||
|
}
|
||||||
|
|
||||||
|
reflects_bottom func($T type) bool {
|
||||||
|
match typeinfo!(T) {
|
||||||
|
.noreturn: return true
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
callback @func() noreturn = forward
|
||||||
|
_ = callback
|
||||||
|
if choose(true) != 42 { return 1 }
|
||||||
|
if !reflects_bottom(noreturn) { return 2 }
|
||||||
|
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, nil, target.DEFAULT, cimport.Options{}, ".",
|
||||||
|
), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
unreachable_traps_at_runtime_and_fails_at_comptime :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-unreachable"
|
||||||
|
main_path := "/tmp/brolang-test-unreachable/main.bro"
|
||||||
|
output := "/tmp/brolang-test-unreachable-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
runtime_text := `main func() void {
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)runtime_text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}}, context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
testing.expect(t, err == nil)
|
||||||
|
testing.expect(t, !state.success)
|
||||||
|
testing.expect(t, strings.contains(string(stderr), "runtime trap: reached unreachable code"))
|
||||||
|
|
||||||
|
comptime_text := `value :: $unreachable
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="unreachable_comptime.bro", text=comptime_text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "reached unreachable code during comptime evaluation")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
noreturn_storage_c_abi_and_fallthrough_are_rejected :: proc(t: ^testing.T) {
|
||||||
|
text := `Bad :: struct { value noreturn }
|
||||||
|
foreign c_func() noreturn
|
||||||
|
fallthrough func() noreturn {}
|
||||||
|
parameter func(value noreturn) void { _ = value }
|
||||||
|
main func() void {
|
||||||
|
if false { fallthrough() }
|
||||||
|
stored noreturn = unreachable
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="invalid_noreturn.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)
|
||||||
|
|
||||||
|
field, c_abi, missing_return, parameter, storage := false, false, false, false, false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
field = field || strings.contains(diagnostic.message, "record fields must have runtime value types")
|
||||||
|
c_abi = c_abi || strings.contains(diagnostic.message, "noreturn is not supported across the C ABI")
|
||||||
|
missing_return = missing_return || strings.contains(diagnostic.message, "does not return a value")
|
||||||
|
parameter = parameter || strings.contains(diagnostic.message, "only valid as a native function result type")
|
||||||
|
storage = storage || strings.contains(diagnostic.message, "cannot store a noreturn value")
|
||||||
|
}
|
||||||
|
testing.expect(t, field && c_abi && missing_return && parameter && storage)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
anonymous_records_and_struct_type_report_targeted_errors :: proc(t: ^testing.T) {
|
anonymous_records_and_struct_type_report_targeted_errors :: proc(t: ^testing.T) {
|
||||||
directory := "/tmp/brolang-test-bad-struct-type"
|
directory := "/tmp/brolang-test-bad-struct-type"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
[
|
[
|
||||||
(null)
|
(null)
|
||||||
|
(unreachable)
|
||||||
(undefined)
|
(undefined)
|
||||||
] @constant.builtin
|
] @constant.builtin
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ EnumInfo :: struct {
|
|||||||
TypeInfo :: union(enum) {
|
TypeInfo :: union(enum) {
|
||||||
invalid void
|
invalid void
|
||||||
void void
|
void void
|
||||||
|
noreturn void
|
||||||
anyopaque void
|
anyopaque void
|
||||||
bool void
|
bool void
|
||||||
integer void
|
integer void
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ module.exports = grammar({
|
|||||||
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
|
_type_constant: $ => seq(optional('-'), choice($.integer, $.character)),
|
||||||
|
|
||||||
builtin_type: _ => choice(
|
builtin_type: _ => choice(
|
||||||
'void', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
|
'void', 'noreturn', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
|
||||||
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
||||||
'isize', 'usize', 'f32', 'f64',
|
'isize', 'usize', 'f32', 'f64',
|
||||||
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
||||||
@@ -462,6 +462,7 @@ module.exports = grammar({
|
|||||||
$.character,
|
$.character,
|
||||||
$.boolean,
|
$.boolean,
|
||||||
$.null,
|
$.null,
|
||||||
|
$.unreachable,
|
||||||
$.undefined,
|
$.undefined,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -645,6 +646,7 @@ module.exports = grammar({
|
|||||||
|
|
||||||
boolean: _ => choice('true', 'false'),
|
boolean: _ => choice('true', 'false'),
|
||||||
null: _ => 'null',
|
null: _ => 'null',
|
||||||
|
unreachable: _ => 'unreachable',
|
||||||
undefined: _ => 'undefined',
|
undefined: _ => 'undefined',
|
||||||
sink: _ => '_',
|
sink: _ => '_',
|
||||||
|
|
||||||
@@ -653,10 +655,10 @@ module.exports = grammar({
|
|||||||
alias(choice(
|
alias(choice(
|
||||||
'test', 'func', 'c_func', 'struct', 'c_struct', 'opaque', 'union', 'enum',
|
'test', 'func', 'c_func', 'struct', 'c_struct', 'opaque', 'union', 'enum',
|
||||||
'distinct', 'alias', 'import', 'hide', 'return', 'try', 'catch', 'mut',
|
'distinct', 'alias', 'import', 'hide', 'return', 'try', 'catch', 'mut',
|
||||||
'null', 'undefined', 'orelse', 'and', 'or', 'xor', 'if', 'while', 'for',
|
'null', 'unreachable', 'undefined', 'orelse', 'and', 'or', 'xor', 'if', 'while', 'for',
|
||||||
'expand', 'break', 'continue', 'defer', 'errdefer', 'yield', 'match', 'else',
|
'expand', 'break', 'continue', 'defer', 'errdefer', 'yield', 'match', 'else',
|
||||||
'true', 'false',
|
'true', 'false',
|
||||||
'void', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
|
'void', 'noreturn', 'type', 'anyopaque', 'bool', 'int', 'uint', 'float', 'range',
|
||||||
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64',
|
||||||
'isize', 'usize', 'f32', 'f64',
|
'isize', 'usize', 'f32', 'f64',
|
||||||
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
'c_char', 'c_schar', 'c_uchar', 'c_short', 'c_ushort',
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
[
|
[
|
||||||
(null)
|
(null)
|
||||||
|
(unreachable)
|
||||||
(undefined)
|
(undefined)
|
||||||
] @constant.builtin
|
] @constant.builtin
|
||||||
|
|
||||||
|
|||||||
@@ -1577,6 +1577,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "void"
|
"value": "void"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "noreturn"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "type"
|
"value": "type"
|
||||||
@@ -3215,6 +3219,10 @@
|
|||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "null"
|
"name": "null"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "unreachable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "SYMBOL",
|
"type": "SYMBOL",
|
||||||
"name": "undefined"
|
"name": "undefined"
|
||||||
@@ -5003,6 +5011,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "null"
|
"value": "null"
|
||||||
},
|
},
|
||||||
|
"unreachable": {
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "unreachable"
|
||||||
|
},
|
||||||
"undefined": {
|
"undefined": {
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "undefined"
|
"value": "undefined"
|
||||||
@@ -5094,6 +5106,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "null"
|
"value": "null"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "unreachable"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "undefined"
|
"value": "undefined"
|
||||||
@@ -5170,6 +5186,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "void"
|
"value": "void"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "noreturn"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "type"
|
"value": "type"
|
||||||
|
|||||||
@@ -882,6 +882,10 @@
|
|||||||
{
|
{
|
||||||
"type": "undefined",
|
"type": "undefined",
|
||||||
"named": true
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "unreachable",
|
||||||
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2356,6 +2360,11 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "unreachable",
|
||||||
|
"named": true,
|
||||||
|
"fields": {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "variable_declaration",
|
"type": "variable_declaration",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -2924,6 +2933,10 @@
|
|||||||
"type": "mut",
|
"type": "mut",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "noreturn",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "null",
|
"type": "null",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -3004,6 +3017,10 @@
|
|||||||
"type": "union",
|
"type": "union",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "unreachable",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "usize",
|
"type": "usize",
|
||||||
"named": false
|
"named": false
|
||||||
|
|||||||
+286289
-279201
File diff suppressed because it is too large
Load Diff
@@ -22,3 +22,9 @@ clear func($K, $V type) void {
|
|||||||
|
|
||||||
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {null})
|
Generated :: alias struct_type!(.auto, {"value"}, {i32}, {null})
|
||||||
# ^^^^^^^^^^^^ function.builtin
|
# ^^^^^^^^^^^^ function.builtin
|
||||||
|
|
||||||
|
fail func() noreturn {
|
||||||
|
# ^^^^^^^^ type.builtin
|
||||||
|
unreachable
|
||||||
|
# ^^^^^^^^^^^ constant.builtin
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user