close some gaps in the type system
This commit is contained in:
+4
-4
@@ -41,7 +41,7 @@ roadmap and milestone history.
|
|||||||
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
- source-order native structs, opaque nominal records with `Name :: opaque`, complete `c_struct { ... }`, keyed record literals, native untagged unions, and native tagged unions `union(Enum)` / `union(enum)`
|
||||||
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
- named native struct fields may declare defaults with `field T = expression`; keyed literals use defaults for omitted fields and explicit initializers override them
|
||||||
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
- void-payload tagged-union variants, anonymous struct payloads, contextual `.variant`, `.variant{payload}`, and `.variant{field = value}` construction
|
||||||
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
- native sum composition with `A | B` for unbacked enums and tagged unions, optionally grouped as `(A | B)`, using program-global `u16` variant ids
|
||||||
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition; `void ! E` functions complete successfully on fallthrough, and void-success `catch` handlers may fall through without `yield`
|
||||||
|
|
||||||
#### native record constraint fields
|
#### native record constraint fields
|
||||||
@@ -98,7 +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
|
||||||
- bare void `return`, same-line `return value`, value blocks, value `if`, 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
|
||||||
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||||
@@ -188,7 +188,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
|
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, tagged unions, and bare function identities; equal structural values and aliases of one function declaration share specializations, while distinct declarations remain distinct and pointers, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
|
||||||
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
|
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
|
||||||
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
|
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
|
||||||
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, exact type `==`/`!=`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
|
||||||
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
|
||||||
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
|
||||||
- `typeinfo!`, `field!`, `compile_error!`, and semantic `expand for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and expand-loop `break` / `continue` must be selected entirely at comptime
|
- `typeinfo!`, `field!`, `compile_error!`, and semantic `expand for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and expand-loop `break` / `continue` must be selected entirely at comptime
|
||||||
@@ -216,7 +216,7 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
|||||||
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
- `std/io` explicit `Io` capabilities, provider-bound `Reader`/`Writer` handles, existing-file open/close operations, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
|
||||||
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
|
||||||
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
|
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
|
||||||
- `std/testing` supplies fallible `expect` and expected-first `expect_equal`; direct calls through
|
- `std/testing` supplies fallible `expect`, expected-first `expect_equal`, and exact compile-time `expect_type`; direct calls through
|
||||||
an alias of exactly `@std/testing` receive compiler-injected source locations
|
an alias of exactly `@std/testing` receive compiler-injected source locations
|
||||||
|
|
||||||
### compiler behavior
|
### compiler behavior
|
||||||
|
|||||||
+156
-57
@@ -164,6 +164,8 @@ Generated_Type_Entry :: struct {
|
|||||||
expr: ast.Expr_Id,
|
expr: ast.Expr_Id,
|
||||||
values: []Comptime_Value,
|
values: []Comptime_Value,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
Type_Factory_Origin :: struct {
|
Type_Factory_Origin :: struct {
|
||||||
@@ -493,6 +495,10 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
|
|||||||
write_type_label(checker, builder, item.child)
|
write_type_label(checker, builder, item.child)
|
||||||
strings.write_string(builder, " ! ")
|
strings.write_string(builder, " ! ")
|
||||||
write_type_label(checker, builder, item.extra)
|
write_type_label(checker, builder, item.extra)
|
||||||
|
case .Sum:
|
||||||
|
write_type_label(checker, builder, item.child)
|
||||||
|
strings.write_string(builder, " | ")
|
||||||
|
write_type_label(checker, builder, item.extra)
|
||||||
case .Type_Call:
|
case .Type_Call:
|
||||||
strings.write_string(builder, "<type factory call>")
|
strings.write_string(builder, "<type factory call>")
|
||||||
case .Struct:
|
case .Struct:
|
||||||
@@ -1136,7 +1142,14 @@ type_from_syntax :: proc(
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .Pointer, .Slice, .Optional, .Range, .Distinct, .Enum, .Fallible:
|
return types.intern(store, item)
|
||||||
|
case .Pointer, .Slice, .Optional, .Range, .Fallible:
|
||||||
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
||||||
|
item.child = child
|
||||||
|
item.extra = extra
|
||||||
|
return types.intern(store, item)
|
||||||
|
case .Distinct, .Enum:
|
||||||
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
||||||
changed = child != item.child || extra != item.extra
|
changed = child != item.child || extra != item.extra
|
||||||
@@ -1146,15 +1159,24 @@ type_from_syntax :: proc(
|
|||||||
params := types.params_for(store, value)
|
params := types.params_for(store, value)
|
||||||
resolved_params := make([]types.Type, len(params), checker.allocator)
|
resolved_params := make([]types.Type, len(params), checker.allocator)
|
||||||
defer delete(resolved_params, checker.allocator)
|
defer delete(resolved_params, checker.allocator)
|
||||||
params_changed := false
|
|
||||||
for param, index in params {
|
for param, index in params {
|
||||||
resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1)
|
resolved_params[index] = type_from_syntax(checker, param.type, pkg, file, depth+1)
|
||||||
params_changed = params_changed || resolved_params[index] != param.type
|
|
||||||
}
|
}
|
||||||
result := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
result := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
if params_changed || result != item.child {
|
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
||||||
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
case .Sum:
|
||||||
|
left := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
|
right := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
||||||
|
composed, compose_error := types.compose_sum(store, left, right)
|
||||||
|
if compose_error == .Unsupported {
|
||||||
|
source.add(checker.diagnostics, source.Span{}, "only native unbacked enums and tagged unions can be composed with '|'")
|
||||||
|
return types.INVALID
|
||||||
}
|
}
|
||||||
|
if compose_error == .Conflict {
|
||||||
|
source.add(checker.diagnostics, source.Span{}, "sum composition contains the same variant name with different payload types")
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
return composed
|
||||||
case .Type_Call:
|
case .Type_Call:
|
||||||
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
|
return resolve_type_factory_call(checker, ast.Expr_Id(item.count_expr), pkg, file)
|
||||||
}
|
}
|
||||||
@@ -1176,6 +1198,31 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool {
|
|||||||
return types.is_runtime_value(value, &checker.module.types)
|
return types.is_runtime_value(value, &checker.module.types)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type_contains_unresolved_named :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
|
||||||
|
if depth > 64 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
item, ok := types.node(&checker.module.types, value)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if item.kind == .Named && !item.declared {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if type_contains_unresolved_named(checker, item.child, depth+1) ||
|
||||||
|
type_contains_unresolved_named(checker, item.extra, depth+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if item.kind == .Function {
|
||||||
|
for param in types.params_for(&checker.module.types, value) {
|
||||||
|
if type_contains_unresolved_named(checker, param.type, depth+1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
|
is_comptime_value_type :: proc(checker: ^Checker, value: types.Type, depth := 0) -> bool {
|
||||||
if depth > 256 || !types.is_valid(value) {
|
if depth > 256 || !types.is_valid(value) {
|
||||||
return false
|
return false
|
||||||
@@ -1960,6 +2007,9 @@ explicit_comptime_argument_valid :: proc(
|
|||||||
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
||||||
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
|
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
|
||||||
}
|
}
|
||||||
|
if type_pattern_mentions_comptime(checker, function, comptime_param_count(function), param.type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
||||||
return ok
|
return ok
|
||||||
@@ -2324,11 +2374,14 @@ match_inferred_type_pattern :: proc(
|
|||||||
pattern_item, pattern_ok := types.node(store, pattern)
|
pattern_item, pattern_ok := types.node(store, pattern)
|
||||||
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
|
if pattern_ok && pattern_item.qualifier == 0 && pattern_item.name != 0 {
|
||||||
name := symbol.Id(pattern_item.name)
|
name := symbol.Id(pattern_item.name)
|
||||||
if _, is_binding := comptime_binding_index(function, prefix, name); is_binding {
|
if binding_index, is_binding := comptime_binding_index(function, prefix, name); is_binding {
|
||||||
param, param_ok := comptime_param_for_name(function, name)
|
param, param_ok := comptime_param_for_name(function, name)
|
||||||
if !param_ok || !is_comptime_type_param(checker, param) {
|
if !param_ok || !is_comptime_type_param(checker, param) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if bound[binding_index] && can_implicitly_convert_type(checker, actual_type, values[binding_index].type) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return bind_inferred_comptime(
|
return bind_inferred_comptime(
|
||||||
checker, function, prefix, values, bound, name,
|
checker, function, prefix, values, bound, name,
|
||||||
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
|
Comptime_Value{type=actual_type, kind=.Type}, span, diagnose,
|
||||||
@@ -2584,7 +2637,6 @@ infer_call_comptime_values :: proc(
|
|||||||
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
|
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
|
||||||
bound[binding_index] = true
|
bound[binding_index] = true
|
||||||
} else {
|
} else {
|
||||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
|
||||||
available: [dynamic]Comptime_Value
|
available: [dynamic]Comptime_Value
|
||||||
available.allocator = checker.allocator
|
available.allocator = checker.allocator
|
||||||
append(&available, ..checker.current_comptime_values)
|
append(&available, ..checker.current_comptime_values)
|
||||||
@@ -2593,6 +2645,10 @@ infer_call_comptime_values :: proc(
|
|||||||
append(&available, prior)
|
append(&available, prior)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
previous := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = available[:]
|
||||||
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
|
checker.current_comptime_values = previous
|
||||||
value, value_ok := eval_static_comptime_value(
|
value, value_ok := eval_static_comptime_value(
|
||||||
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
|
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
|
||||||
)
|
)
|
||||||
@@ -2880,6 +2936,8 @@ resolve_generated_struct_type :: proc(checker: ^Checker, expr_id: ast.Expr_Id, p
|
|||||||
expr=expr_id,
|
expr=expr_id,
|
||||||
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
values=clone_comptime_values(checker.current_comptime_values, checker.allocator),
|
||||||
result=result,
|
result=result,
|
||||||
|
pkg=pkg,
|
||||||
|
file=file,
|
||||||
})
|
})
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -2961,32 +3019,40 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
|||||||
ct_state_destroy(&state)
|
ct_state_destroy(&state)
|
||||||
checker.type_factories[entry_index].result = result
|
checker.type_factories[entry_index].result = result
|
||||||
checker.type_factories[entry_index].resolving = false
|
checker.type_factories[entry_index].resolving = false
|
||||||
if types.is_valid(result) {
|
record_type_factory_origin(checker, result, template, values)
|
||||||
generated := false
|
return result
|
||||||
for entry in checker.generated_types {
|
}
|
||||||
if types.equal(entry.result, result) {
|
|
||||||
generated = true
|
record_type_factory_origin :: proc(
|
||||||
break
|
checker: ^Checker,
|
||||||
}
|
result: types.Type,
|
||||||
}
|
template: ast.Function_Id,
|
||||||
if generated {
|
values: []Comptime_Value,
|
||||||
has_origin := false
|
) {
|
||||||
for origin in checker.type_factory_origins {
|
if !types.is_valid(result) {
|
||||||
if types.equal(origin.result, result) {
|
return
|
||||||
has_origin = true
|
}
|
||||||
break
|
generated := false
|
||||||
}
|
for entry in checker.generated_types {
|
||||||
}
|
if types.equal(entry.result, result) {
|
||||||
if !has_origin {
|
generated = true
|
||||||
append(&checker.type_factory_origins, Type_Factory_Origin{
|
break
|
||||||
result=result,
|
|
||||||
template=template,
|
|
||||||
values=clone_comptime_values(values, checker.allocator),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
if !generated {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for origin in checker.type_factory_origins {
|
||||||
|
if origin.template == template && types.equal(origin.result, result) &&
|
||||||
|
comptime_values_equal(origin.values, values) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
append(&checker.type_factory_origins, Type_Factory_Origin{
|
||||||
|
result=result,
|
||||||
|
template=template,
|
||||||
|
values=clone_comptime_values(values, checker.allocator),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
collect_comptime_values :: proc(
|
collect_comptime_values :: proc(
|
||||||
@@ -3056,7 +3122,15 @@ collect_comptime_values :: proc(
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
resolution_values: [dynamic]Comptime_Value
|
||||||
|
resolution_values.allocator = checker.allocator
|
||||||
|
append(&resolution_values, ..extra_values)
|
||||||
|
append(&resolution_values, ..values[:])
|
||||||
|
previous := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = resolution_values[:]
|
||||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
|
checker.current_comptime_values = previous
|
||||||
|
delete(resolution_values)
|
||||||
if types.is_concrete_integer(declared) {
|
if types.is_concrete_integer(declared) {
|
||||||
constant := Constant{kind = .Not_Constant}
|
constant := Constant{kind = .Not_Constant}
|
||||||
if index < len(args) {
|
if index < len(args) {
|
||||||
@@ -4037,6 +4111,13 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if item.kind == .Function {
|
if item.kind == .Function {
|
||||||
|
unresolved := type_contains_unresolved_named(checker, item.child)
|
||||||
|
for param in types.params_for(&checker.module.types, id) {
|
||||||
|
unresolved = unresolved || type_contains_unresolved_named(checker, param.type)
|
||||||
|
}
|
||||||
|
if unresolved {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if item.c_abi {
|
if item.c_abi {
|
||||||
if types.kind(item.child, &checker.module.types) == .Fallible {
|
if types.kind(item.child, &checker.module.types) == .Fallible {
|
||||||
source.add(checker.diagnostics, source.Span{}, "c_func pointer results cannot be fallible")
|
source.add(checker.diagnostics, source.Span{}, "c_func pointer results cannot be fallible")
|
||||||
@@ -4545,11 +4626,14 @@ infer_compound_expr :: proc(
|
|||||||
if initialized[index] {
|
if initialized[index] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if field_default, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
|
if field_default, default_values, ok := find_struct_field_default(checker, value, symbol.Id(field.name)); ok {
|
||||||
|
previous := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = default_values
|
||||||
_ = infer_nested_expr(
|
_ = infer_nested_expr(
|
||||||
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
checker, field_default.expr, nil, field_default.pkg, field_default.file,
|
||||||
demanded, expected=field.type,
|
demanded, expected=field.type,
|
||||||
)
|
)
|
||||||
|
checker.current_comptime_values = previous
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6841,15 +6925,35 @@ find_struct_field_default :: proc(
|
|||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
struct_type: types.Type,
|
struct_type: types.Type,
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
) -> (ast.Struct_Field_Default, bool) {
|
) -> (ast.Struct_Field_Default, []Comptime_Value, bool) {
|
||||||
resolved := types.resolve_alias(struct_type, &checker.module.types)
|
resolved := types.resolve_alias(struct_type, &checker.module.types)
|
||||||
for field_default in checker.ast_module.struct_field_defaults {
|
for field_default in checker.ast_module.struct_field_defaults {
|
||||||
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
|
if types.resolve_alias(field_default.record, &checker.module.types) == resolved &&
|
||||||
field_default.field == name {
|
field_default.field == name {
|
||||||
return field_default, true
|
return field_default, nil, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}, false
|
for entry in checker.generated_types {
|
||||||
|
if !types.equal(entry.result, resolved) || entry.expr == ast.INVALID_EXPR ||
|
||||||
|
int(entry.expr) >= len(checker.ast_module.exprs) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[entry.expr]
|
||||||
|
fields := types.fields_for(&checker.module.types, resolved)
|
||||||
|
for field, index in fields {
|
||||||
|
if field.name != u32(name) || index >= len(expr.args) || expr.args[index] == ast.INVALID_EXPR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return ast.Struct_Field_Default{
|
||||||
|
record=resolved,
|
||||||
|
field=name,
|
||||||
|
expr=expr.args[index],
|
||||||
|
pkg=entry.pkg,
|
||||||
|
file=entry.file,
|
||||||
|
}, entry.values, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {}, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
|
find_tuple_field :: proc(checker: ^Checker, tuple_type: types.Type, index: u64) -> (int, types.Field, bool) {
|
||||||
@@ -7656,10 +7760,6 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "'try' requires an enclosing fallible function")
|
id := source.add(checker.diagnostics, expr.span, "'try' requires an enclosing fallible function")
|
||||||
return invalid_hir_expr(checker, expr.span, id, success)
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
}
|
}
|
||||||
if !types.equal(success, enclosing_success) {
|
|
||||||
id := source.add(checker.diagnostics, expr.span, "'try' success type must match the enclosing fallible result")
|
|
||||||
return invalid_hir_expr(checker, expr.span, id, success)
|
|
||||||
}
|
|
||||||
error_type := types.fallible_error(channel_type, store)
|
error_type := types.fallible_error(channel_type, store)
|
||||||
if !types.equal(error_type, enclosing_error) &&
|
if !types.equal(error_type, enclosing_error) &&
|
||||||
!types.can_sum_widen(error_type, enclosing_error, store) {
|
!types.can_sum_widen(error_type, enclosing_error, store) {
|
||||||
@@ -8076,11 +8176,14 @@ build_compound_expr :: proc(
|
|||||||
if values[index] != hir.INVALID_EXPR {
|
if values[index] != hir.INVALID_EXPR {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if field_default, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
|
if field_default, default_values, ok := find_struct_field_default(checker, struct_type, symbol.Id(field.name)); ok {
|
||||||
|
previous := checker.current_comptime_values
|
||||||
|
checker.current_comptime_values = default_values
|
||||||
values[index] = build_nested_expr(
|
values[index] = build_nested_expr(
|
||||||
checker, field_default.expr, nil, global_reads, calls,
|
checker, field_default.expr, nil, global_reads, calls,
|
||||||
field.type, field_default.pkg, field_default.file,
|
field.type, field_default.pkg, field_default.file,
|
||||||
)
|
)
|
||||||
|
checker.current_comptime_values = previous
|
||||||
values[index] = coerce_expr(
|
values[index] = coerce_expr(
|
||||||
checker, values[index], field.type,
|
checker, values[index], field.type,
|
||||||
checker.ast_module.exprs[field_default.expr].span,
|
checker.ast_module.exprs[field_default.expr].span,
|
||||||
@@ -11385,6 +11488,18 @@ emit_value_branch :: proc(
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
checker := ctx.checker
|
checker := ctx.checker
|
||||||
n := len(branch_stmts)
|
n := len(branch_stmts)
|
||||||
|
if n == 1 && checker.ast_module.statements[branch_stmts[0]].kind == .Expression {
|
||||||
|
expr_stmt := checker.ast_module.statements[branch_stmts[0]]
|
||||||
|
expected := slot_type^ if slot^ != hir.INVALID_LOCAL else types.INVALID
|
||||||
|
value := build_expr(checker, expr_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, expected, ctx.pkg, ctx.file)
|
||||||
|
if checker.module.exprs[value].kind == .Invalid {
|
||||||
|
ctx.problematic^ = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
value = adopt_value_slot(ctx, slot, slot_type, value, checker.module.exprs[value].type, span)
|
||||||
|
emit_slot_assign(checker, out, slot^, value, span)
|
||||||
|
return true
|
||||||
|
}
|
||||||
ends_in_yield := n > 0 &&
|
ends_in_yield := n > 0 &&
|
||||||
checker.ast_module.statements[branch_stmts[n - 1]].kind == .Yield &&
|
checker.ast_module.statements[branch_stmts[n - 1]].kind == .Yield &&
|
||||||
!symbol.is_valid(checker.ast_module.statements[branch_stmts[n - 1]].label)
|
!symbol.is_valid(checker.ast_module.statements[branch_stmts[n - 1]].label)
|
||||||
@@ -11992,9 +12107,7 @@ build_match_arm_body :: proc(
|
|||||||
return result[:], body_ok
|
return result[:], body_ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// build_value_arm appends a value-match arm's slot assignment(s) to `out`: a single bare
|
// build_value_arm appends a value-match arm's slot assignment(s) to `out`.
|
||||||
// expression yields implicitly; anything else reuses the value-branch rule (trailing
|
|
||||||
// `yield`, or exit on every path).
|
|
||||||
build_value_arm :: proc(
|
build_value_arm :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
out: ^[dynamic]hir.Stmt_Id,
|
out: ^[dynamic]hir.Stmt_Id,
|
||||||
@@ -12003,20 +12116,6 @@ build_value_arm :: proc(
|
|||||||
slot_type: ^types.Type,
|
slot_type: ^types.Type,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
checker := ctx.checker
|
|
||||||
if len(arm_body) == 1 && checker.ast_module.statements[arm_body[0]].kind == .Expression {
|
|
||||||
expr_stmt := checker.ast_module.statements[arm_body[0]]
|
|
||||||
expected := slot_type^ if slot^ != hir.INVALID_LOCAL else types.INVALID
|
|
||||||
value := build_expr(checker, expr_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, expected, ctx.pkg, ctx.file)
|
|
||||||
if checker.module.exprs[value].kind == .Invalid {
|
|
||||||
ctx.problematic^ = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
vtype := checker.module.exprs[value].type
|
|
||||||
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
|
|
||||||
emit_slot_assign(checker, out, slot^, value, span)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return emit_value_branch(ctx, out, arm_body, slot, slot_type, span)
|
return emit_value_branch(ctx, out, arm_body, slot, slot_type, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -625,14 +625,19 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
|
|||||||
}
|
}
|
||||||
if value.kind == .Function {
|
if value.kind == .Function {
|
||||||
actual_item, actual_ok := types.node(store, value.type)
|
actual_item, actual_ok := types.node(store, value.type)
|
||||||
_, _, expected_function, expected_ok := types.function_pointer(expected, store)
|
_, _, expected_function, expected_pointer := types.function_pointer(expected, store)
|
||||||
|
expected_ok := expected_pointer
|
||||||
|
if expected_item, ok := types.node(store, expected); ok && expected_item.kind == .Function {
|
||||||
|
expected_function = expected
|
||||||
|
expected_ok = true
|
||||||
|
}
|
||||||
if actual_ok && actual_item.kind == .Function && expected_ok &&
|
if actual_ok && actual_item.kind == .Function && expected_ok &&
|
||||||
types.equal(value.type, expected_function) {
|
types.equal(value.type, expected_function) {
|
||||||
value.type = expected
|
value.type = expected
|
||||||
return ct_add_value(state, value), true
|
return ct_add_value(state, value), true
|
||||||
}
|
}
|
||||||
_, _, actual_function, actual_pointer := types.function_pointer(value.type, store)
|
_, _, actual_function, actual_pointer := types.function_pointer(value.type, store)
|
||||||
if actual_pointer && expected_ok && types.equal(actual_function, expected_function) {
|
if actual_pointer && expected_pointer && types.equal(actual_function, expected_function) {
|
||||||
value.type = expected
|
value.type = expected
|
||||||
return ct_add_value(state, value), true
|
return ct_add_value(state, value), true
|
||||||
}
|
}
|
||||||
@@ -1522,14 +1527,17 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
|
|||||||
if values[index] != INVALID_CT_VALUE {
|
if values[index] != INVALID_CT_VALUE {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
field_default, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
field_default, default_values, has_default := find_struct_field_default(checker, struct_type, symbol.Id(field.name))
|
||||||
if !has_default {
|
if !has_default {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
|
||||||
}
|
}
|
||||||
previous_pkg, previous_file := state.pkg, state.file
|
previous_pkg, previous_file := state.pkg, state.file
|
||||||
|
previous_comptime := checker.current_comptime_values
|
||||||
state.pkg, state.file = field_default.pkg, field_default.file
|
state.pkg, state.file = field_default.pkg, field_default.file
|
||||||
|
checker.current_comptime_values = default_values
|
||||||
value, flow, ok := ct_eval_expr(state, field_default.expr, field.type, depth+1)
|
value, flow, ok := ct_eval_expr(state, field_default.expr, field.type, depth+1)
|
||||||
state.pkg, state.file = previous_pkg, previous_file
|
state.pkg, state.file = previous_pkg, previous_file
|
||||||
|
checker.current_comptime_values = previous_comptime
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
}
|
}
|
||||||
@@ -2102,6 +2110,16 @@ 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 == .Type || right.kind == .Type {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
ok := types.equal(types.Type(left.index), types.Type(right.index))
|
||||||
|
if op == .Ne {
|
||||||
|
ok = !ok
|
||||||
|
}
|
||||||
|
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||||
|
}
|
||||||
if left.kind == .Bool && right.kind == .Bool {
|
if left.kind == .Bool && right.kind == .Bool {
|
||||||
if op != .Eq && op != .Ne {
|
if op != .Eq && op != .Ne {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
|
||||||
@@ -2897,6 +2915,12 @@ ct_eval_template_call :: proc(
|
|||||||
if ct_value_references_dead_storage(state, result) {
|
if ct_value_references_dead_storage(state, result) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
|
||||||
}
|
}
|
||||||
|
if is_type_metatype_syntax(checker, function.result) && result != INVALID_CT_VALUE &&
|
||||||
|
int(result) < len(state.values) && state.values[result].kind == .Type {
|
||||||
|
record_type_factory_origin(
|
||||||
|
checker, types.Type(state.values[result].index), template, comptime_values,
|
||||||
|
)
|
||||||
|
}
|
||||||
if types.is_valid(expected) {
|
if types.is_valid(expected) {
|
||||||
return ct_coerce_expr_value(state, result, expected, span)
|
return ct_coerce_expr_value(state, result, expected, span)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1747,6 +1747,19 @@ canonical_type :: proc(
|
|||||||
mapping[index] = resolved
|
mapping[index] = resolved
|
||||||
return resolved
|
return resolved
|
||||||
}
|
}
|
||||||
|
if item.kind == .Sum {
|
||||||
|
left := canonical_type(module, item.child, mapping, visiting)
|
||||||
|
right := canonical_type(module, item.extra, mapping, visiting)
|
||||||
|
if resolved, compose_error := types.compose_sum(&module.type_store, left, right); compose_error == .None {
|
||||||
|
mapping[index] = resolved
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
item.child = left
|
||||||
|
item.extra = right
|
||||||
|
resolved := types.intern(&module.type_store, item)
|
||||||
|
mapping[index] = resolved
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
if item.kind == .Function {
|
if item.kind == .Function {
|
||||||
params := make([]types.Type, int(item.field_count), context.temp_allocator)
|
params := make([]types.Type, int(item.field_count), context.temp_allocator)
|
||||||
for param, param_index in types.params_for(&module.type_store, value) {
|
for param, param_index in types.params_for(&module.type_store, value) {
|
||||||
|
|||||||
@@ -379,6 +379,13 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Bool:
|
case .Keyword_Bool:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.BOOL
|
return types.BOOL
|
||||||
|
case .Left_Paren:
|
||||||
|
advance(parser)
|
||||||
|
grouped := parse_type(parser)
|
||||||
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after grouped type")
|
||||||
|
}
|
||||||
|
return grouped
|
||||||
case .Keyword_Func, .Keyword_C_Func:
|
case .Keyword_Func, .Keyword_C_Func:
|
||||||
c_abi := tok.kind == .Keyword_C_Func
|
c_abi := tok.kind == .Keyword_C_Func
|
||||||
advance(parser)
|
advance(parser)
|
||||||
@@ -447,6 +454,24 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sum_syntax_requires_resolution :: proc(parser: ^Parser, value: types.Type, depth := 0) -> bool {
|
||||||
|
if depth > 64 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
item, ok := types.node(&parser.module.type_store, value)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if item.kind == .Named || item.kind == .Type_Call {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if item.kind == .Alias || item.kind == .Sum {
|
||||||
|
return sum_syntax_requires_resolution(parser, item.child, depth+1) ||
|
||||||
|
sum_syntax_requires_resolution(parser, item.extra, depth+1)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
|
parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type_Syntax {
|
||||||
result := left
|
result := left
|
||||||
for current(parser).kind == .Pipe {
|
for current(parser).kind == .Pipe {
|
||||||
@@ -454,8 +479,12 @@ parse_type_pipe_tail :: proc(parser: ^Parser, left: ast.Type_Syntax) -> ast.Type
|
|||||||
right := parse_type_atom(parser)
|
right := parse_type_atom(parser)
|
||||||
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
|
composed, compose_error := types.compose_sum(&parser.module.type_store, result, right)
|
||||||
if compose_error == .Unsupported {
|
if compose_error == .Unsupported {
|
||||||
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
|
if sum_syntax_requires_resolution(parser, result) || sum_syntax_requires_resolution(parser, right) {
|
||||||
result = types.INVALID
|
result = types.sum_syntax(&parser.module.type_store, result, right)
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, operator.span, "only native unbacked enums and tagged unions can be composed with '|'")
|
||||||
|
result = types.INVALID
|
||||||
|
}
|
||||||
} else if compose_error == .Conflict {
|
} else if compose_error == .Conflict {
|
||||||
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
|
source.add(parser.diagnostics, operator.span, "sum composition contains the same variant name with different payload types")
|
||||||
result = types.INVALID
|
result = types.INVALID
|
||||||
@@ -738,9 +767,15 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
|
|||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
fields: [dynamic]types.Field
|
fields: [dynamic]types.Field
|
||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
|
defaults: [dynamic]ast.Expr_Id
|
||||||
|
defaults.allocator = parser.module.allocator
|
||||||
tuple := false
|
tuple := false
|
||||||
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type", tuple_result=&tuple) {
|
if !parse_record_body(
|
||||||
|
parser, &fields, "expected '{' after anonymous struct type",
|
||||||
|
tuple_result=&tuple, defaults=&defaults,
|
||||||
|
) {
|
||||||
delete(fields)
|
delete(fields)
|
||||||
|
delete(defaults)
|
||||||
return invalid_expr(parser, start.span, "invalid anonymous struct type")
|
return invalid_expr(parser, start.span, "invalid anonymous struct type")
|
||||||
}
|
}
|
||||||
end := previous(parser)
|
end := previous(parser)
|
||||||
@@ -752,6 +787,7 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
|
|||||||
kind=.Anonymous_Struct_Type,
|
kind=.Anonymous_Struct_Type,
|
||||||
span=span_from(start.span, end.span),
|
span=span_from(start.span, end.span),
|
||||||
integer=u64(field_start)<<32 | u64(field_count),
|
integer=u64(field_start)<<32 | u64(field_count),
|
||||||
|
args=defaults[:],
|
||||||
tuple=tuple,
|
tuple=tuple,
|
||||||
left=ast.INVALID_EXPR,
|
left=ast.INVALID_EXPR,
|
||||||
right=ast.INVALID_EXPR,
|
right=ast.INVALID_EXPR,
|
||||||
|
|||||||
@@ -122,11 +122,12 @@ inject_assertion_locations :: proc(
|
|||||||
) {
|
) {
|
||||||
expect := symbol.intern(symbols, "expect")
|
expect := symbol.intern(symbols, "expect")
|
||||||
expect_equal := symbol.intern(symbols, "expect_equal")
|
expect_equal := symbol.intern(symbols, "expect_equal")
|
||||||
|
expect_type := symbol.intern(symbols, "expect_type")
|
||||||
original_count := len(module.exprs)
|
original_count := len(module.exprs)
|
||||||
for index in 0..<original_count {
|
for index in 0..<original_count {
|
||||||
expr := &module.exprs[index]
|
expr := &module.exprs[index]
|
||||||
if expr.kind != .Call || expr.intrinsic || !symbol.is_valid(expr.qualifier) ||
|
if expr.kind != .Call || expr.intrinsic || !symbol.is_valid(expr.qualifier) ||
|
||||||
(expr.name != expect && expr.name != expect_equal) {
|
(expr.name != expect && expr.name != expect_equal && expr.name != expect_type) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
file := source_file_id(module, expr.span.file)
|
file := source_file_id(module, expr.span.file)
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Kind :: enum u8 {
|
|||||||
Struct,
|
Struct,
|
||||||
Union,
|
Union,
|
||||||
Fallible,
|
Fallible,
|
||||||
|
Sum,
|
||||||
Type_Call,
|
Type_Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,6 +412,10 @@ fallible_error :: proc(value: Type, store: ^Store) -> Type {
|
|||||||
return item.extra if ok && item.kind == .Fallible else INVALID
|
return item.extra if ok && item.kind == .Fallible else INVALID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sum_syntax :: proc(store: ^Store, left, right: Type) -> Type {
|
||||||
|
return intern(store, Node{kind=.Sum, child=left, extra=right})
|
||||||
|
}
|
||||||
|
|
||||||
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
|
append_sum_variants :: proc(store: ^Store, value: Type, out: ^[dynamic]Sum_Variant) -> bool {
|
||||||
item, ok := node(store, value)
|
item, ok := node(store, value)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
+98
-10
@@ -5812,7 +5812,7 @@ main func() i32 {
|
|||||||
testing.expect(t, found_yield)
|
testing.expect(t, found_yield)
|
||||||
testing.expect(t, found_misplaced_yield)
|
testing.expect(t, found_misplaced_yield)
|
||||||
|
|
||||||
success_text := `A :: enum {
|
local_success_text := `A :: enum {
|
||||||
a
|
a
|
||||||
}
|
}
|
||||||
B :: enum {
|
B :: enum {
|
||||||
@@ -5822,23 +5822,26 @@ Both :: alias A | B
|
|||||||
fs func() i64 ! A {
|
fs func() i64 ! A {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
bad_success func() i32 ! Both {
|
use_success func() void ! Both {
|
||||||
x :: try fs()
|
x :: try fs()
|
||||||
return x
|
_ = x
|
||||||
}
|
}
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
return bad_success() catch 0
|
use_success() catch |_| { return 1 }
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
directory := "/tmp/brolang-test-try-success-mismatch"
|
directory := "/tmp/brolang-test-try-local-success"
|
||||||
main_path := "/tmp/brolang-test-try-success-mismatch/main.bro"
|
main_path := "/tmp/brolang-test-try-local-success/main.bro"
|
||||||
output := "/tmp/brolang-test-try-success-mismatch-output"
|
output := "/tmp/brolang-test-try-local-success-output"
|
||||||
_ = os2.remove_all(directory)
|
_ = os2.remove_all(directory)
|
||||||
defer _ = os2.remove_all(directory)
|
defer _ = os2.remove_all(directory)
|
||||||
defer _ = os.remove(output)
|
defer _ = os.remove(output)
|
||||||
testing.expect(t, os.make_directory(directory) == nil)
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)success_text))
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)local_success_text))
|
||||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 1)
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -6032,7 +6035,10 @@ OtherError :: union(enum) {
|
|||||||
}
|
}
|
||||||
other void
|
other void
|
||||||
}
|
}
|
||||||
BothError :: alias PayloadError | OtherError
|
BothError :: alias (PayloadError | OtherError)
|
||||||
|
grouped_error func() void ! (PayloadError | OtherError) {
|
||||||
|
return .other
|
||||||
|
}
|
||||||
accept func(value PayloadError) i32 {
|
accept func(value PayloadError) i32 {
|
||||||
match value {
|
match value {
|
||||||
.not_found |info|: return info.path + info.line
|
.not_found |info|: return info.path + info.line
|
||||||
@@ -13609,6 +13615,10 @@ milestone_44_struct_field_defaults_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
Lists :: struct { items [][]u8 = &[] }
|
Lists :: struct { items [][]u8 = &[] }
|
||||||
|
|
||||||
|
Generated func($T type, $initial i32) type {
|
||||||
|
return struct { value T = initial }
|
||||||
|
}
|
||||||
|
|
||||||
read func(value Config) i32 {
|
read func(value Config) i32 {
|
||||||
return value.required + value.count
|
return value.required + value.count
|
||||||
}
|
}
|
||||||
@@ -13619,10 +13629,12 @@ main func() i32 {
|
|||||||
defaults Config = Config{required = 35}
|
defaults Config = Config{required = 35}
|
||||||
overridden Config = Config{count = 1, enabled = false, name = "x", required = 40}
|
overridden Config = Config{count = 1, enabled = false, name = "x", required = 40}
|
||||||
lists Lists = Lists{}
|
lists Lists = Lists{}
|
||||||
|
generated Generated(i32, 9) = Generated(i32, 9){}
|
||||||
if (ANSWER != 42) return 1
|
if (ANSWER != 42) return 1
|
||||||
if (defaults.count != 7 or defaults.enabled == false) return 2
|
if (defaults.count != 7 or defaults.enabled == false) return 2
|
||||||
if (defaults.name.len != 3 or lists.items.len != 0) return 3
|
if (defaults.name.len != 3 or lists.items.len != 0) return 3
|
||||||
if (overridden.count != 1 or overridden.enabled or overridden.name.len != 1) return 4
|
if (overridden.count != 1 or overridden.enabled or overridden.name.len != 1) return 4
|
||||||
|
if (generated.value != 9) return 5
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@@ -13635,3 +13647,79 @@ main func() i32 {
|
|||||||
state := run_executable(output)
|
state := run_executable(output)
|
||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
dependent_comptime_callbacks_and_imported_grouped_sums_compile :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-dependent-callbacks"
|
||||||
|
app := "/tmp/brolang-test-dependent-callbacks/app"
|
||||||
|
dependency := "/tmp/brolang-test-dependent-callbacks/dependency"
|
||||||
|
app_path := "/tmp/brolang-test-dependent-callbacks/app/main.bro"
|
||||||
|
dependency_path := "/tmp/brolang-test-dependent-callbacks/dependency/errors.bro"
|
||||||
|
output := "/tmp/brolang-test-dependent-callbacks-output"
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os2.make_directory_all(app) == nil)
|
||||||
|
testing.expect(t, os2.make_directory_all(dependency) == nil)
|
||||||
|
|
||||||
|
dependency_text := `AllocError :: enum { out_of_memory }
|
||||||
|
`
|
||||||
|
app_text := `dependency :: import "../dependency"
|
||||||
|
|
||||||
|
PutError :: enum { key_exists }
|
||||||
|
|
||||||
|
Entry func($K, $V type) type {
|
||||||
|
return struct { key K, value V }
|
||||||
|
}
|
||||||
|
|
||||||
|
Map func(
|
||||||
|
$K, $V type,
|
||||||
|
$hash_key func(key K) usize,
|
||||||
|
$keys_eql func(a, b K) bool,
|
||||||
|
) type {
|
||||||
|
return struct { entry Entry(K, V) }
|
||||||
|
}
|
||||||
|
|
||||||
|
hash func(key []u8) usize { return key.len }
|
||||||
|
eql func(a, b []u8) bool { return a.len == b.len }
|
||||||
|
|
||||||
|
StringMap func($V type) type {
|
||||||
|
return Map([]u8, V, hash, eql)
|
||||||
|
}
|
||||||
|
|
||||||
|
make func(
|
||||||
|
$K, $V type,
|
||||||
|
$hash_key func(key K) usize,
|
||||||
|
$keys_eql func(a, b K) bool,
|
||||||
|
) Map(K, V, hash_key, keys_eql) {
|
||||||
|
return Map(K, V, hash_key, keys_eql) {
|
||||||
|
entry = Entry(K, V) {key = "", value = 0},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
put func(
|
||||||
|
$K, $V type,
|
||||||
|
$hash_key func(key K) usize,
|
||||||
|
$keys_eql func(a, b K) bool,
|
||||||
|
map @Map(K, V, hash_key, keys_eql),
|
||||||
|
key K,
|
||||||
|
) void {
|
||||||
|
_ = map
|
||||||
|
_ = key
|
||||||
|
}
|
||||||
|
|
||||||
|
fallible func() void ! (PutError | dependency.AllocError) { return .key_exists }
|
||||||
|
|
||||||
|
main func() void {
|
||||||
|
map StringMap(u32) = make()
|
||||||
|
put(&map, "key")
|
||||||
|
_ = map
|
||||||
|
fallible() catch |_| {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(dependency_path, transmute([]byte)dependency_text))
|
||||||
|
testing.expect(t, os.write_entire_file(app_path, transmute([]byte)app_text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(app, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ fixed_len func($T type, $N usize, value @Fixed(T, N)) usize {
|
|||||||
return value.values.len
|
return value.values.len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
same_type func($Expected, $Actual type, _ Actual) bool {
|
||||||
|
return $(Expected == Actual)
|
||||||
|
}
|
||||||
|
|
||||||
take_i32 func(value i32) i32 {
|
take_i32 func(value i32) i32 {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
@@ -87,5 +91,12 @@ main func() i32 {
|
|||||||
_ = assigned
|
_ = assigned
|
||||||
_ = take_i32(zero())
|
_ = take_i32(zero())
|
||||||
_ = return_zero()
|
_ = return_zero()
|
||||||
|
optional ?u32 = none
|
||||||
|
if !same_type(?u32, optional) {
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
if same_type(?u16, optional) {
|
||||||
|
return 9
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ vif_defer func() i32 {
|
|||||||
return r # 5
|
return r # 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vif_expression func(old_entries []u8) usize {
|
||||||
|
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
|
||||||
|
return new_size
|
||||||
|
}
|
||||||
|
|
||||||
# --- value loops (milestone 20.5) --------------------------------------------
|
# --- value loops (milestone 20.5) --------------------------------------------
|
||||||
|
|
||||||
# Labeled `for` used as a value: `yield :blk i` exits early with a value, the
|
# Labeled `for` used as a value: `yield :blk i` exits early with a value, the
|
||||||
@@ -306,6 +311,8 @@ main func() i32 {
|
|||||||
if (vif_reassign(0) != 7) return 110
|
if (vif_reassign(0) != 7) return 110
|
||||||
if (vif_reassign(9) != 9) return 111
|
if (vif_reassign(9) != 9) return 111
|
||||||
if (vif_defer() != 5) return 112
|
if (vif_defer() != 5) return 112
|
||||||
|
if (vif_expression("") != 8) return 136
|
||||||
|
if (vif_expression("abc") != 6) return 137
|
||||||
|
|
||||||
if (loop_search() != 0) return 113
|
if (loop_search() != 0) return 113
|
||||||
if (loop_none() != 0) return 114
|
if (loop_none() != 0) return 114
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ handles_append test {
|
|||||||
|
|
||||||
try append(&list, 42)
|
try append(&list, 42)
|
||||||
|
|
||||||
|
try testing.expect_type(usize, list.items.len)
|
||||||
try testing.expect_equal(1, list.items.len)
|
try testing.expect_equal(1, list.items.len)
|
||||||
try testing.expect_equal(42, list.items[0])
|
try testing.expect_equal(42, list.items[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ expect_equal func($T type, expected, actual T, location SourceLocation) void ! E
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
|
||||||
|
try expect($(Expected == Actual), location)
|
||||||
|
}
|
||||||
|
|
||||||
run func(name []u8, callback *func() void ! Error) bool {
|
run func(name []u8, callback *func() void ! Error) bool {
|
||||||
callback() catch |_| {
|
callback() catch |_| {
|
||||||
debug.print("{s} [failed]\n", {name,})
|
debug.print("{s} [failed]\n", {name,})
|
||||||
|
|||||||
Reference in New Issue
Block a user