fix interop and indexing oversights
This commit is contained in:
@@ -603,53 +603,22 @@
|
|||||||
- structurally identical anonymous struct payloads share type identity, so sum composition merges
|
- structurally identical anonymous struct payloads share type identity, so sum composition merges
|
||||||
matching variants and still rejects same-name variants with different payload shapes
|
matching variants and still rejects same-name variants with different payload shapes
|
||||||
|
|
||||||
24. bug fixes & interop/indexing oversights (surfaced by the raylib testbed)
|
24. bug fixes & interop/indexing oversights (implemented)
|
||||||
- the bouncing-shapes testbed (`testbed/game`) drove most of the language at once and
|
- `return match ...` and `yield match ...` are accepted as direct value-control-flow
|
||||||
exposed several gaps; grouped here as the next polish pass. it currently compiles only by
|
operands, matching declaration/assignment value sources
|
||||||
keeping three non-interconverting integer "worlds" (`usize` for indices, the `int`
|
- index and slice-bound expressions are contextually coerced to `usize`; unsigned narrower
|
||||||
constraint for `c_int` args, `c_float` for physics) and routing around the items below
|
integer indices work, while signed runtime indices diagnose instead of reaching LLVM
|
||||||
- `return match ...` doesn't work (and `yield match ...` probably doesn't either): a `match`
|
- concrete native scalars coerce to same-family C scalar types at call, return, assignment,
|
||||||
is not accepted directly as a `return`/`yield` operand, so the value must be bound to a
|
aggregate, and optional boundaries when the target C type can represent the source width
|
||||||
local first, or a statement-`match` with per-arm `return` used — the testbed's `next_kind`
|
- scalar keyword casts (`i32(x)`, `usize(x)`, `c_float(x)`, etc.) provide explicit numeric
|
||||||
had to do the latter
|
conversions for cases that should not be implicit
|
||||||
- signed-index array access must be a diagnostic, not a miscompile: `arr[i]` with a signed
|
- C scalar comparisons accept numeric literals by typing the literal from the concrete C
|
||||||
index (`i32`/`int`, including the `int` constraint) currently lowers to malformed LLVM IR
|
operand, so aliases like `ZF` are no longer needed
|
||||||
(the indexed slot's address and the stored value get swapped: `store <struct>, ptr` with a
|
- array counts accept compile-time integer expressions such as `[CAP]T` and `[N + 1]T`;
|
||||||
value where a `ptr` is expected) for both reads and writes; only `usize` and compile-time
|
runtime variables remain rejected because arrays are fixed-size values
|
||||||
literal indices work. settle the rule — index expressions require an unsigned
|
- string literals in value-`match` / value-`if` peers resolve to a common zero-terminated
|
||||||
(`usize`-coercible) type, as in Rust/Zig — and report a clear checker error instead of
|
byte slice when possible, and `[;0]u8` slices can decay to immutable `*c_char`/`?*c_char`
|
||||||
emitting bad IR
|
parameters
|
||||||
- native scalar types should coerce to their C equivalents in general: `f32 -> c_float`,
|
|
||||||
`f64 -> c_double`, and the integer cases (`i32 -> c_int`, `u8 -> c_uchar`,
|
|
||||||
`usize -> the matching C width`, …) at call/return/assignment boundaries, following C's own
|
|
||||||
same-width/family conversions. today only the `int` constraint coerces to `c_int`;
|
|
||||||
concrete-width native scalars (`i32`, `usize`, `f32`) are stranded, which is why the testbed
|
|
||||||
had to store physics directly in `c_float` and maintain parallel `usize`/`int` counters
|
|
||||||
- explicit scalar type casting: there is no cast syntax yet (`c_float(x)` is rejected — that
|
|
||||||
spelling is distinct-type construction, not a numeric cast), so any conversion the coercion
|
|
||||||
rules above don't cover is currently impossible. settle a cast spelling; this removes most
|
|
||||||
of the manual world-juggling the testbed needed
|
|
||||||
- c scalars must be comparable with numeric literals: `x < 0.0` and `x != 0.0` where
|
|
||||||
`x : c_float` currently error ("comparison requires compatible numeric operands") even
|
|
||||||
though `x + 1.0` / `x * 2.0` already accept the literal. a comparison operand literal should
|
|
||||||
adopt the other operand's concrete (c) scalar type exactly as arithmetic does — otherwise
|
|
||||||
every zero/bound needs a `c_float` constant alias (the testbed added `ZF`, `SPINMAX`, etc.)
|
|
||||||
- array sizes should accept compile-time-constant expressions, not just integer literals:
|
|
||||||
`[CAP]T` with `CAP :: 64` (and `[N + 1]T`, …) should resolve through the same constant
|
|
||||||
folding used for global initializers. runtime *variable* lengths stay rejected by design —
|
|
||||||
a runtime-length array is a slice + allocation (milestone 25), not an array type (Zig/Rust
|
|
||||||
parity: array lengths are comptime-known). the testbed had to hard-code `[64]` and keep a
|
|
||||||
separate `CAP usize :: 64` for the bounds checks
|
|
||||||
- peer-type resolution for string literals in value-`match` / value-`if` arms (Zig parity,
|
|
||||||
not a brolang-specific limitation): arms yielding differently-sized string literals
|
|
||||||
(`@[6;0]u8` for "circle" vs `@[8;0]u8` for "triangle") currently fail to unify, forcing a
|
|
||||||
per-arm `DrawText("…")` workaround instead of `tag :: match k { … }; DrawText(tag, …)`.
|
|
||||||
verified Zig 0.16 behavior: `switch`/`if` arms of string literals peer-resolve to a single
|
|
||||||
sentinel slice `[:0]const u8` (preserving the common `:0`), which then coerces directly to a
|
|
||||||
C string `[*c]const u8`. brolang should (a) peer-type-resolve same-sentinel string literals
|
|
||||||
to a sentinel byte slice `[;0]u8`, and (b) allow a zero-terminated byte slice to convert to
|
|
||||||
`*c_char` (extending milestone 3.5's pointer-view → `*c_char` rule to the matching sentinel
|
|
||||||
slice). with both, a value-`match` label passes straight to a `?*c_char` parameter
|
|
||||||
|
|
||||||
25. dynamic heap allocation
|
25. dynamic heap allocation
|
||||||
- see below for direction
|
- see below for direction
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Orelse,
|
Orelse,
|
||||||
Struct_Literal,
|
Struct_Literal,
|
||||||
Keyed,
|
Keyed,
|
||||||
|
Cast,
|
||||||
Negate,
|
Negate,
|
||||||
Not,
|
Not,
|
||||||
Add,
|
Add,
|
||||||
@@ -109,6 +110,7 @@ Expr :: struct {
|
|||||||
args: []Expr_Id,
|
args: []Expr_Id,
|
||||||
qualifier: symbol.Id,
|
qualifier: symbol.Id,
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
|
type: Type_Syntax,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
right: Expr_Id,
|
right: Expr_Id,
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
|
|||||||
+290
-58
@@ -296,6 +296,73 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
|||||||
return checker.constants[expr_id]
|
return checker.constants[expr_id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eval_integer_constant_in_context :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
expr_id: ast.Expr_Id,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
depth := 0,
|
||||||
|
) -> Constant {
|
||||||
|
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[expr_id]
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Integer:
|
||||||
|
return Constant{kind = .Value, value = i128(expr.integer)}
|
||||||
|
case .Name:
|
||||||
|
target_pkg, available := expr_package(checker, expr, pkg, file, false)
|
||||||
|
if !available {
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
global := find_global(checker, expr.name, target_pkg)
|
||||||
|
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
g := checker.ast_module.globals[global]
|
||||||
|
if g.external || !g.immutable {
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
return eval_integer_constant_in_context(checker, g.expr, g.pkg, g.file, depth+1)
|
||||||
|
case .Negate:
|
||||||
|
operand := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1)
|
||||||
|
if operand.kind == .Value {
|
||||||
|
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
||||||
|
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||||
|
}
|
||||||
|
return operand
|
||||||
|
case .Add, .Sub, .Mul, .Div:
|
||||||
|
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1)
|
||||||
|
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1)
|
||||||
|
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||||
|
return Constant{kind = .Div_By_Zero}
|
||||||
|
}
|
||||||
|
if left.kind == .Overflow || right.kind == .Overflow {
|
||||||
|
return Constant{kind = .Overflow}
|
||||||
|
}
|
||||||
|
if left.kind != .Value || right.kind != .Value {
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
value: i128
|
||||||
|
overflow: bool
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Sub:
|
||||||
|
value, overflow = intrinsics.overflow_sub(left.value, right.value)
|
||||||
|
case .Mul:
|
||||||
|
value, overflow = intrinsics.overflow_mul(left.value, right.value)
|
||||||
|
case .Div:
|
||||||
|
if right.value == 0 {
|
||||||
|
return Constant{kind = .Div_By_Zero}
|
||||||
|
}
|
||||||
|
value = left.value / right.value
|
||||||
|
case:
|
||||||
|
value, overflow = intrinsics.overflow_add(left.value, right.value)
|
||||||
|
}
|
||||||
|
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||||
|
}
|
||||||
|
return Constant{kind = .Not_Constant}
|
||||||
|
}
|
||||||
|
|
||||||
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
|
||||||
if !types.is_signed(value_type, selected) {
|
if !types.is_signed(value_type, selected) {
|
||||||
return false
|
return false
|
||||||
@@ -320,14 +387,83 @@ fits_i64 :: proc(value: i128) -> bool {
|
|||||||
return fits_signed_type(value, types.I64)
|
return fits_signed_type(value, types.I64)
|
||||||
}
|
}
|
||||||
|
|
||||||
type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
|
type_from_syntax :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
value: ast.Type_Syntax,
|
||||||
|
pkg := ast.Package_Id(0),
|
||||||
|
file := ast.File_Id(0),
|
||||||
|
depth := 0,
|
||||||
|
) -> types.Type {
|
||||||
|
if depth > 64 {
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
item, ok := types.node(&checker.module.types, value)
|
||||||
|
if !ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
store := &checker.module.types
|
||||||
|
changed := false
|
||||||
|
#partial switch item.kind {
|
||||||
|
case .Array:
|
||||||
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
|
changed = changed || child != item.child
|
||||||
|
item.child = child
|
||||||
|
if item.unresolved_count {
|
||||||
|
expr_id := ast.Expr_Id(item.count_expr)
|
||||||
|
span := source.Span{}
|
||||||
|
if expr_id != ast.INVALID_EXPR && int(expr_id) < len(checker.ast_module.exprs) {
|
||||||
|
span = checker.ast_module.exprs[expr_id].span
|
||||||
|
}
|
||||||
|
constant := eval_integer_constant_in_context(checker, expr_id, pkg, file)
|
||||||
|
if constant.kind == .Value {
|
||||||
|
switch {
|
||||||
|
case constant.value < 0:
|
||||||
|
source.add(checker.diagnostics, span, "array count must be non-negative")
|
||||||
|
return types.INVALID
|
||||||
|
case constant.value > i128(0xffff_ffff_ffff_ffff):
|
||||||
|
source.add(checker.diagnostics, span, "array count does not fit in u64")
|
||||||
|
return types.INVALID
|
||||||
|
case:
|
||||||
|
item.count = u64(constant.value)
|
||||||
|
item.unresolved_count = false
|
||||||
|
item.count_expr = 0
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .Pointer, .Slice, .Optional, .Range, .Distinct, .Enum, .Fallible:
|
||||||
|
child := type_from_syntax(checker, item.child, pkg, file, depth+1)
|
||||||
|
extra := type_from_syntax(checker, item.extra, pkg, file, depth+1)
|
||||||
|
changed = child != item.child || extra != item.extra
|
||||||
|
item.child = child
|
||||||
|
item.extra = extra
|
||||||
|
case .Function:
|
||||||
|
params := types.params_for(store, value)
|
||||||
|
resolved_params := make([]types.Type, len(params), checker.allocator)
|
||||||
|
defer delete(resolved_params, checker.allocator)
|
||||||
|
params_changed := false
|
||||||
|
for param, index in params {
|
||||||
|
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)
|
||||||
|
if params_changed || result != item.child {
|
||||||
|
return types.function(store, resolved_params, result, item.c_abi, item.variadic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
return types.intern(store, item)
|
||||||
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> types.Type {
|
function_channel_type :: proc(checker: ^Checker, function: ast.Function) -> types.Type {
|
||||||
result := type_from_syntax(function.result)
|
result := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if types.is_valid(function.error) {
|
if types.is_valid(function.error) {
|
||||||
return types.fallible(&checker.module.types, result, type_from_syntax(function.error))
|
return types.fallible(&checker.module.types, result, type_from_syntax(checker, function.error, function.pkg, function.file))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -642,11 +778,11 @@ valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
|
|||||||
return count >= len(function.params) if function.variadic else count == len(function.params)
|
return count >= len(function.params) if function.variadic else count == len(function.params)
|
||||||
}
|
}
|
||||||
|
|
||||||
call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
|
call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int) -> types.Type {
|
||||||
if index < 0 || index >= len(function.params) {
|
if index < 0 || index >= len(function.params) {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
declared := type_from_syntax(function.params[index].type)
|
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
||||||
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
||||||
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
||||||
// `int`/`range` constraints have no single default and keep building naturally.
|
// `int`/`range` constraints have no single default and keep building naturally.
|
||||||
@@ -682,13 +818,13 @@ function_value_signature :: proc(
|
|||||||
if !function.c_abi || types.is_valid(function.error) {
|
if !function.c_abi || types.is_valid(function.error) {
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
}
|
}
|
||||||
result = type_from_syntax(function.result)
|
result = type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if !types.is_void(result) && !is_runtime_type(checker, result) {
|
if !types.is_void(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)
|
||||||
for param, index in function.params {
|
for param, index in function.params {
|
||||||
param_type := type_from_syntax(param.type)
|
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
if !is_runtime_type(checker, param_type) {
|
if !is_runtime_type(checker, param_type) {
|
||||||
delete(params, checker.allocator)
|
delete(params, checker.allocator)
|
||||||
return nil, types.INVALID, false
|
return nil, types.INVALID, false
|
||||||
@@ -758,7 +894,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
}
|
}
|
||||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal:
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
case .Catch:
|
case .Catch:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
@@ -894,7 +1030,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
locals: [dynamic]symbol.Id
|
locals: [dynamic]symbol.Id
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type));
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(checker, param.type, function.pkg, function.file));
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
continue
|
continue
|
||||||
@@ -915,7 +1051,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
append(&locals, param.name)
|
append(&locals, param.name)
|
||||||
if types.contains_c_struct_by_value(type_from_syntax(param.type), &checker.module.types) {
|
if types.contains_c_struct_by_value(type_from_syntax(checker, param.type, function.pkg, function.file), &checker.module.types) {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
param.span,
|
param.span,
|
||||||
@@ -924,11 +1060,11 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(function.result));
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(checker, function.result, function.pkg, function.file));
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
}
|
}
|
||||||
if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) {
|
if types.contains_c_struct_by_value(type_from_syntax(checker, function.result, function.pkg, function.file), &checker.module.types) {
|
||||||
checker.template_diagnostics[function_id] = source.addf(
|
checker.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
function.span,
|
function.span,
|
||||||
@@ -937,7 +1073,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if types.is_valid(function.error) {
|
if types.is_valid(function.error) {
|
||||||
error_type := type_from_syntax(function.error)
|
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
||||||
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
||||||
types.is_tagged_union(error_type, &checker.module.types)
|
types.is_tagged_union(error_type, &checker.module.types)
|
||||||
if function.c_abi {
|
if function.c_abi {
|
||||||
@@ -972,7 +1108,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
if !function.has_body && function.c_abi {
|
if !function.has_body && function.c_abi {
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
param_type := type_from_syntax(param.type)
|
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
|
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
|
||||||
source.INVALID_DIAGNOSTIC {
|
source.INVALID_DIAGNOSTIC {
|
||||||
continue
|
continue
|
||||||
@@ -989,7 +1125,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result := type_from_syntax(function.result)
|
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.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) {
|
||||||
@@ -1174,7 +1310,7 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
|
|||||||
if param_index < len(actual_args) {
|
if param_index < len(actual_args) {
|
||||||
actual = actual_args[param_index]
|
actual = actual_args[param_index]
|
||||||
}
|
}
|
||||||
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual)) {
|
if !types.equal(spec.args[param_index], specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
||||||
matches = false
|
matches = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -1190,8 +1326,14 @@ find_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []t
|
|||||||
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
|
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
|
||||||
// resolves to the actual's family member (INVALID if out of family), so a call
|
// resolves to the actual's family member (INVALID if out of family), so a call
|
||||||
// passing an out-of-family argument fails to specialize and is rejected.
|
// passing an out-of-family argument fails to specialize and is rejected.
|
||||||
specialized_param_type :: proc(checker: ^Checker, syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
|
specialized_param_type :: proc(
|
||||||
declared := type_from_syntax(syntax)
|
checker: ^Checker,
|
||||||
|
syntax: ast.Type_Syntax,
|
||||||
|
actual: types.Type,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
) -> types.Type {
|
||||||
|
declared := type_from_syntax(checker, syntax, pkg, file)
|
||||||
if types.is_constraint(declared) {
|
if types.is_constraint(declared) {
|
||||||
return types.constraint_target(declared, actual, &checker.module.types)
|
return types.constraint_target(declared, actual, &checker.module.types)
|
||||||
}
|
}
|
||||||
@@ -1204,7 +1346,7 @@ can_specialize :: proc(checker: ^Checker, function: ast.Function, actual_args: [
|
|||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
}
|
}
|
||||||
if !is_runtime_type(checker, specialized_param_type(checker, param.type, actual)) {
|
if !is_runtime_type(checker, specialized_param_type(checker, param.type, actual, function.pkg, function.file)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1223,7 +1365,7 @@ ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: [
|
|||||||
if index < len(actual_args) {
|
if index < len(actual_args) {
|
||||||
actual = actual_args[index]
|
actual = actual_args[index]
|
||||||
}
|
}
|
||||||
append(&signature, specialized_param_type(checker, param.type, actual))
|
append(&signature, specialized_param_type(checker, param.type, actual, function.pkg, function.file))
|
||||||
}
|
}
|
||||||
result := function_channel_type(checker, function)
|
result := function_channel_type(checker, function)
|
||||||
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 &&
|
||||||
@@ -1335,6 +1477,9 @@ infer_compound_expr :: proc(
|
|||||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
}
|
}
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
|
case .Cast:
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
|
return type_from_syntax(checker, expr.type, pkg, file)
|
||||||
case .Address:
|
case .Address:
|
||||||
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
child := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
return types.pointer(store, child, false, false)
|
return types.pointer(store, child, false, false)
|
||||||
@@ -1487,7 +1632,7 @@ infer_expr :: proc(
|
|||||||
last = types.F64
|
last = types.F64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
|
||||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
@@ -1701,7 +1846,7 @@ infer_expr :: proc(
|
|||||||
// it (e.g. `take_u16(a)` resolves `a` to u16). Constraint params have no single
|
// it (e.g. `take_u16(a)` resolves `a` to u16). Constraint params have no single
|
||||||
// type to demand; the callee's result flowing back is milestone 14.5.
|
// type to demand; the callee's result flowing back is milestone 14.5.
|
||||||
for arg_index in 0..<len(expr.args) {
|
for arg_index in 0..<len(expr.args) {
|
||||||
record_demand(checker, expr.args[arg_index], call_arg_expected(function, arg_index), locals, local_types, pkg, file)
|
record_demand(checker, expr.args[arg_index], call_arg_expected(checker, function, arg_index), locals, local_types, pkg, file)
|
||||||
}
|
}
|
||||||
// Deferred defaulting leaves an undemanded open constant typeless; give such an
|
// Deferred defaulting leaves an undemanded open constant typeless; give such an
|
||||||
// argument its default so the call can still monomorphize (the default feeds only
|
// argument its default so the call can still monomorphize (the default feeds only
|
||||||
@@ -1865,7 +2010,7 @@ infer_statements :: proc(
|
|||||||
// binding (its declared type when annotated, else left open) and walk
|
// binding (its declared type when annotated, else left open) and walk
|
||||||
// the block body. The build pass resolves the yielded value's type
|
// the block body. The build pass resolves the yielded value's type
|
||||||
// independently — value blocks don't join the demand fixpoint.
|
// independently — value blocks don't join the demand fixpoint.
|
||||||
declared_block := type_from_syntax(statement.type)
|
declared_block := type_from_syntax(checker, statement.type, pkg, file)
|
||||||
block_type := declared_block if is_runtime_type(checker, declared_block) else types.INVALID
|
block_type := declared_block if is_runtime_type(checker, declared_block) else types.INVALID
|
||||||
local := Infer_Local{
|
local := Infer_Local{
|
||||||
name=statement.name, type=block_type, declared=declared_block,
|
name=statement.name, type=block_type, declared=declared_block,
|
||||||
@@ -1876,7 +2021,7 @@ infer_statements :: proc(
|
|||||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
declared_local := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
||||||
value_type := types.INVALID
|
value_type := types.INVALID
|
||||||
if !is_undefined_expr(checker, statement.expr) {
|
if !is_undefined_expr(checker, statement.expr) {
|
||||||
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types)
|
||||||
@@ -2107,7 +2252,7 @@ infer_spec_locals_and_result :: proc(
|
|||||||
) -> ([]types.Type, types.Type) {
|
) -> ([]types.Type, types.Type) {
|
||||||
spec := checker.specs[id]
|
spec := checker.specs[id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
declared := type_from_syntax(function.result)
|
declared := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -2446,7 +2591,7 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
// Demands accumulate in global_demands so the default never blocks a later
|
// Demands accumulate in global_demands so the default never blocks a later
|
||||||
// cross-family demand (e.g. integer literal -> unsigned or float).
|
// cross-family demand (e.g. integer literal -> unsigned or float).
|
||||||
for global, index in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
declared := type_from_syntax(global.type)
|
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
|
||||||
if is_runtime_type(checker, declared) {
|
if is_runtime_type(checker, declared) {
|
||||||
checker.global_types[index] = declared
|
checker.global_types[index] = declared
|
||||||
continue
|
continue
|
||||||
@@ -2494,7 +2639,7 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
||||||
if is_runtime_type(checker, type_from_syntax(global.type)) {
|
if is_runtime_type(checker, type_from_syntax(checker, global.type, global.pkg, global.file)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if is_runtime_type(checker, checker.global_demands[index]) {
|
if is_runtime_type(checker, checker.global_demands[index]) {
|
||||||
@@ -2660,9 +2805,11 @@ can_implicitly_convert_type :: proc(checker: ^Checker, actual, expected: types.T
|
|||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
if types.equal(actual, expected) ||
|
if types.equal(actual, expected) ||
|
||||||
types.can_widen(actual, expected) ||
|
types.can_widen(actual, expected) ||
|
||||||
types.can_coerce_c_integer(actual, expected) ||
|
types.can_coerce_c_integer(actual, expected, checker.target) ||
|
||||||
|
types.can_coerce_c_scalar(actual, expected, checker.target) ||
|
||||||
types.can_weaken_pointer(actual, expected, store) ||
|
types.can_weaken_pointer(actual, expected, store) ||
|
||||||
types.can_weaken_slice(actual, expected, store) ||
|
types.can_weaken_slice(actual, expected, store) ||
|
||||||
|
types.can_decay_slice_c_string(actual, expected, store) ||
|
||||||
types.can_decay_array_pointer(actual, expected, store) ||
|
types.can_decay_array_pointer(actual, expected, store) ||
|
||||||
types.can_sum_widen(actual, expected, store) {
|
types.can_sum_widen(actual, expected, store) {
|
||||||
return true
|
return true
|
||||||
@@ -2719,6 +2866,17 @@ coerce_expr :: proc(
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if types.can_decay_slice_c_string(actual, expected, &checker.module.types) {
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Slice_Ptr,
|
||||||
|
span=span,
|
||||||
|
type=expected,
|
||||||
|
left=expr_id,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
if types.can_sum_widen(actual, expected, &checker.module.types) {
|
if types.can_sum_widen(actual, expected, &checker.module.types) {
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Sum_Widen,
|
kind=.Sum_Widen,
|
||||||
@@ -2734,8 +2892,11 @@ coerce_expr :: proc(
|
|||||||
child := types.child_type(expected, &checker.module.types)
|
child := types.child_type(expected, &checker.module.types)
|
||||||
if types.equal(actual, child) ||
|
if types.equal(actual, child) ||
|
||||||
types.can_widen(actual, child) ||
|
types.can_widen(actual, child) ||
|
||||||
|
types.can_coerce_c_integer(actual, child, checker.target) ||
|
||||||
|
types.can_coerce_c_scalar(actual, child, checker.target) ||
|
||||||
types.can_weaken_pointer(actual, child, &checker.module.types) ||
|
types.can_weaken_pointer(actual, child, &checker.module.types) ||
|
||||||
types.can_weaken_slice(actual, child, &checker.module.types) ||
|
types.can_weaken_slice(actual, child, &checker.module.types) ||
|
||||||
|
types.can_decay_slice_c_string(actual, child, &checker.module.types) ||
|
||||||
types.can_decay_array_pointer(actual, child, &checker.module.types) {
|
types.can_decay_array_pointer(actual, child, &checker.module.types) {
|
||||||
value := coerce_expr(checker, expr_id, child, span)
|
value := coerce_expr(checker, expr_id, child, span)
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
@@ -2763,7 +2924,8 @@ coerce_expr :: proc(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if types.can_coerce_c_integer(actual, expected) {
|
if types.can_coerce_c_integer(actual, expected, checker.target) ||
|
||||||
|
types.can_coerce_c_scalar(actual, expected, checker.target) {
|
||||||
return add_hir_expr(
|
return add_hir_expr(
|
||||||
checker,
|
checker,
|
||||||
hir.Expr {
|
hir.Expr {
|
||||||
@@ -3311,6 +3473,31 @@ build_compound_expr :: proc(
|
|||||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||||
}
|
}
|
||||||
return enum_member_hir(checker, expected, expr.name, expr.span)
|
return enum_member_hir(checker, expected, expr.name, expr.span)
|
||||||
|
case .Cast:
|
||||||
|
target := type_from_syntax(checker, expr.type, pkg, file)
|
||||||
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
actual := checker.module.exprs[value].type
|
||||||
|
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
||||||
|
valid_actual := types.is_concrete_scalar(actual) && !types.is_bool(actual)
|
||||||
|
if !valid_target || !valid_actual {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
expr.span,
|
||||||
|
"scalar cast requires numeric scalar types, got %s to %s",
|
||||||
|
types.name(actual),
|
||||||
|
types.name(target),
|
||||||
|
)
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, target)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Scalar_Cast,
|
||||||
|
span=expr.span,
|
||||||
|
type=target,
|
||||||
|
left=value,
|
||||||
|
target=hir.INVALID_REF,
|
||||||
|
right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Address:
|
case .Address:
|
||||||
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
if !hir_is_location(checker, value) {
|
if !hir_is_location(checker, value) {
|
||||||
@@ -3343,6 +3530,7 @@ build_compound_expr :: proc(
|
|||||||
case .Index:
|
case .Index:
|
||||||
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
|
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
|
||||||
|
index = coerce_expr(checker, index, types.USIZE, expr.span)
|
||||||
container_type := checker.module.exprs[container].type
|
container_type := checker.module.exprs[container].type
|
||||||
item, ok := types.container(container_type, store)
|
item, ok := types.container(container_type, store)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -3367,6 +3555,7 @@ build_compound_expr :: proc(
|
|||||||
for bound, index in expr.args {
|
for bound, index in expr.args {
|
||||||
if bound != ast.INVALID_EXPR {
|
if bound != ast.INVALID_EXPR {
|
||||||
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
|
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
|
||||||
|
bounds[index] = coerce_expr(checker, bounds[index], types.USIZE, checker.ast_module.exprs[bound].span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||||
@@ -3603,17 +3792,19 @@ build_compound_expr :: proc(
|
|||||||
left, right: hir.Expr_Id
|
left, right: hir.Expr_Id
|
||||||
left_expr := checker.ast_module.exprs[expr.left]
|
left_expr := checker.ast_module.exprs[expr.left]
|
||||||
right_expr := checker.ast_module.exprs[expr.right]
|
right_expr := checker.ast_module.exprs[expr.right]
|
||||||
|
left_numeric_const := left_const.kind == .Value || is_float_constant_expr(checker, expr.left)
|
||||||
|
right_numeric_const := right_const.kind == .Value || is_float_constant_expr(checker, expr.right)
|
||||||
if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
|
if right_expr.kind == .Enum_Literal && left_expr.kind != .Enum_Literal {
|
||||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, checker.module.exprs[left].type, pkg, file)
|
||||||
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
|
} else if left_expr.kind == .Enum_Literal && right_expr.kind != .Enum_Literal {
|
||||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
||||||
} else if right_const.kind == .Value && left_const.kind != .Value {
|
} else if right_numeric_const && !left_numeric_const {
|
||||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
hint := checker.module.exprs[left].type
|
hint := checker.module.exprs[left].type
|
||||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
|
||||||
} else if left_const.kind == .Value && right_const.kind != .Value {
|
} else if left_numeric_const && !right_numeric_const {
|
||||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
hint := checker.module.exprs[right].type
|
hint := checker.module.exprs[right].type
|
||||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||||
@@ -3841,7 +4032,7 @@ build_expr :: proc(
|
|||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
||||||
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
.Bool, .Cast, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
|
||||||
.Enum_Literal:
|
.Enum_Literal:
|
||||||
last = build_compound_expr(
|
last = build_compound_expr(
|
||||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||||
@@ -4086,7 +4277,7 @@ build_expr :: proc(
|
|||||||
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
||||||
stack[frame_index].stage = 3
|
stack[frame_index].stage = 3
|
||||||
if len(expr.args) > 0 {
|
if len(expr.args) > 0 {
|
||||||
arg_expected := call_arg_expected(function, 0)
|
arg_expected := call_arg_expected(checker, function, 0)
|
||||||
if !is_runtime_type(checker, arg_expected) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -4141,7 +4332,7 @@ build_expr :: proc(
|
|||||||
stack[frame_index].arg_index += 1
|
stack[frame_index].arg_index += 1
|
||||||
if frame.arg_index+1 < len(expr.args) {
|
if frame.arg_index+1 < len(expr.args) {
|
||||||
next := frame.arg_index+1
|
next := frame.arg_index+1
|
||||||
arg_expected := call_arg_expected(checker.ast_module.functions[frame.template], next)
|
arg_expected := call_arg_expected(checker, checker.ast_module.functions[frame.template], next)
|
||||||
if !is_runtime_type(checker, arg_expected) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -4156,7 +4347,7 @@ build_expr :: proc(
|
|||||||
if index >= len(stack[frame_index].arg_types) {
|
if index >= len(stack[frame_index].arg_types) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
declared := type_from_syntax(params[index].type)
|
declared := type_from_syntax(checker, params[index].type, checker.ast_module.functions[frame.template].pkg, checker.ast_module.functions[frame.template].file)
|
||||||
actual := stack[frame_index].arg_types[index]
|
actual := stack[frame_index].arg_types[index]
|
||||||
if types.is_constraint(declared) && types.is_valid(actual) &&
|
if types.is_constraint(declared) && types.is_valid(actual) &&
|
||||||
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
!types.is_valid(types.constraint_target(declared, actual, &checker.module.types)) {
|
||||||
@@ -4413,9 +4604,9 @@ build_block :: proc(
|
|||||||
// declare the local from the yielded value (its type for an untyped `::`).
|
// declare the local from the yielded value (its type for an untyped `::`).
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
expected := types.INVALID
|
expected := types.INVALID
|
||||||
typed := is_runtime_type(checker, type_from_syntax(statement.type))
|
typed := is_runtime_type(checker, type_from_syntax(checker, statement.type, ctx.pkg, ctx.file))
|
||||||
if typed {
|
if typed {
|
||||||
expected = type_from_syntax(statement.type)
|
expected = type_from_syntax(checker, statement.type, ctx.pkg, ctx.file)
|
||||||
}
|
}
|
||||||
value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span, statement.label, statement.value_control_flow)
|
value, value_type := build_value_source(ctx, &body, statement.body, expected, statement.span, statement.label, statement.value_control_flow)
|
||||||
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); found {
|
||||||
@@ -4445,7 +4636,7 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
declared := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, ctx.pkg, ctx.file), statement.expr)
|
||||||
// Adopt the type inference resolved for this local when the declaration has no
|
// Adopt the type inference resolved for this local when the declaration has no
|
||||||
// concrete annotation and inference carried useful numeric context: constraints,
|
// concrete annotation and inference carried useful numeric context: constraints,
|
||||||
// `undefined`, open numeric constants, or arithmetic expressions.
|
// `undefined`, open numeric constants, or arithmetic expressions.
|
||||||
@@ -4717,7 +4908,7 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR && !statement.value_control_flow {
|
||||||
if types.kind(ctx.result, store) == .Fallible &&
|
if types.kind(ctx.result, store) == .Fallible &&
|
||||||
types.is_void(types.fallible_success(ctx.result, store)) {
|
types.is_void(types.fallible_success(ctx.result, store)) {
|
||||||
flush_defers(ctx, &body, 0)
|
flush_defers(ctx, &body, 0)
|
||||||
@@ -4756,7 +4947,17 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := hir.INVALID_EXPR
|
value := hir.INVALID_EXPR
|
||||||
if types.kind(ctx.result, store) == .Fallible {
|
if statement.value_control_flow {
|
||||||
|
if types.kind(ctx.result, store) == .Fallible {
|
||||||
|
success := types.fallible_success(ctx.result, store)
|
||||||
|
value, _ = build_value_source(ctx, &body, statement.body, success, statement.span, symbol.INVALID, statement.value_control_flow)
|
||||||
|
value = coerce_expr(checker, value, success, statement.span)
|
||||||
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, false)
|
||||||
|
} else {
|
||||||
|
value, _ = build_value_source(ctx, &body, statement.body, ctx.result, statement.span, symbol.INVALID, statement.value_control_flow)
|
||||||
|
value = coerce_expr(checker, value, ctx.result, statement.span)
|
||||||
|
}
|
||||||
|
} else if types.kind(ctx.result, store) == .Fallible {
|
||||||
success := types.fallible_success(ctx.result, store)
|
success := types.fallible_success(ctx.result, store)
|
||||||
error_type := types.fallible_error(ctx.result, store)
|
error_type := types.fallible_error(ctx.result, store)
|
||||||
error_path := false
|
error_path := false
|
||||||
@@ -5249,7 +5450,12 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
target := &ctx.yield_targets^[target_index]
|
target := &ctx.yield_targets^[target_index]
|
||||||
yielded := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
yielded := hir.INVALID_EXPR
|
||||||
|
if statement.value_control_flow {
|
||||||
|
yielded, _ = build_value_source(ctx, &body, statement.body, target.slot_type, statement.span, symbol.INVALID, statement.value_control_flow)
|
||||||
|
} else {
|
||||||
|
yielded = build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, target.slot_type, ctx.pkg, ctx.file)
|
||||||
|
}
|
||||||
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
|
yielded = resolve_loop_slot(ctx, target, yielded, checker.module.exprs[yielded].type if yielded != hir.INVALID_EXPR else types.INVALID, statement.span)
|
||||||
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
|
if target.slot == hir.INVALID_LOCAL || yielded == hir.INVALID_EXPR {
|
||||||
id := source.add(checker.diagnostics, statement.span,
|
id := source.add(checker.diagnostics, statement.span,
|
||||||
@@ -5391,10 +5597,14 @@ build_value_block :: proc(
|
|||||||
delete(leading, checker.allocator)
|
delete(leading, checker.allocator)
|
||||||
|
|
||||||
yield_stmt := checker.ast_module.statements[body_stmts[n - 1]]
|
yield_stmt := checker.ast_module.statements[body_stmts[n - 1]]
|
||||||
value = build_expr(
|
if yield_stmt.value_control_flow {
|
||||||
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
value, value_type = build_value_source(ctx, body, yield_stmt.body, expected, yield_stmt.span, symbol.INVALID, yield_stmt.value_control_flow)
|
||||||
expected, ctx.pkg, ctx.file,
|
} else {
|
||||||
)
|
value = build_expr(
|
||||||
|
checker, yield_stmt.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
expected, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
}
|
||||||
value_type = checker.module.exprs[value].type
|
value_type = checker.module.exprs[value].type
|
||||||
if is_runtime_type(checker, expected) {
|
if is_runtime_type(checker, expected) {
|
||||||
value = coerce_expr(checker, value, expected, yield_stmt.span)
|
value = coerce_expr(checker, value, expected, yield_stmt.span)
|
||||||
@@ -5486,6 +5696,38 @@ emit_slot_assign :: proc(checker: ^Checker, out: ^[dynamic]hir.Stmt_Id, slot: hi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string_peer_slice_type :: proc(checker: ^Checker, value: types.Type) -> types.Type {
|
||||||
|
pointer, array, ok := types.array_pointer(value, &checker.module.types)
|
||||||
|
if ok && !pointer.mutable && !array.mutable &&
|
||||||
|
array.child == types.U8 && array.has_sentinel && array.sentinel == 0 {
|
||||||
|
return types.slice(&checker.module.types, types.U8, false, true, 0)
|
||||||
|
}
|
||||||
|
return types.INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
adopt_value_slot :: proc(
|
||||||
|
ctx: ^Build_Ctx,
|
||||||
|
slot: ^hir.Local_Id,
|
||||||
|
slot_type: ^types.Type,
|
||||||
|
value: hir.Expr_Id,
|
||||||
|
vtype: types.Type,
|
||||||
|
span: source.Span,
|
||||||
|
) -> hir.Expr_Id {
|
||||||
|
checker := ctx.checker
|
||||||
|
if slot^ == hir.INVALID_LOCAL {
|
||||||
|
peer := string_peer_slice_type(checker, vtype)
|
||||||
|
if types.is_valid(peer) {
|
||||||
|
slot_type^ = peer
|
||||||
|
slot^ = new_value_slot(ctx, slot_type^)
|
||||||
|
return coerce_expr(checker, value, slot_type^, span)
|
||||||
|
}
|
||||||
|
slot_type^ = vtype
|
||||||
|
slot^ = new_value_slot(ctx, slot_type^)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return coerce_expr(checker, value, slot_type^, span)
|
||||||
|
}
|
||||||
|
|
||||||
// build_value_if turns `if c { … yield A } else { … yield B }` into a result slot
|
// build_value_if turns `if c { … yield A } else { … yield B }` into a result slot
|
||||||
// each branch assigns, read after the if. Every path must yield: a mandatory `else`,
|
// each branch assigns, read after the if. Every path must yield: a mandatory `else`,
|
||||||
// each branch ends in `yield`, and all branches share a type (the first establishes it
|
// each branch ends in `yield`, and all branches share a type (the first establishes it
|
||||||
@@ -5703,12 +5945,7 @@ emit_value_branch :: proc(
|
|||||||
if checker.module.exprs[value].kind == .Invalid {
|
if checker.module.exprs[value].kind == .Invalid {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if slot^ == hir.INVALID_LOCAL {
|
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
|
||||||
slot_type^ = vtype
|
|
||||||
slot^ = new_value_slot(ctx, slot_type^)
|
|
||||||
} else {
|
|
||||||
value = coerce_expr(checker, value, slot_type^, span)
|
|
||||||
}
|
|
||||||
emit_slot_assign(checker, out, slot^, value, span)
|
emit_slot_assign(checker, out, slot^, value, span)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -6192,12 +6429,7 @@ build_value_arm :: proc(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
vtype := checker.module.exprs[value].type
|
vtype := checker.module.exprs[value].type
|
||||||
if slot^ == hir.INVALID_LOCAL {
|
value = adopt_value_slot(ctx, slot, slot_type, value, vtype, span)
|
||||||
slot_type^ = vtype
|
|
||||||
slot^ = new_value_slot(ctx, slot_type^)
|
|
||||||
} else {
|
|
||||||
value = coerce_expr(checker, value, slot_type^, span)
|
|
||||||
}
|
|
||||||
emit_slot_assign(checker, out, slot^, value, span)
|
emit_slot_assign(checker, out, slot^, value, span)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -6937,7 +7169,7 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
dependencies.allocator = checker.allocator
|
dependencies.allocator = checker.allocator
|
||||||
calls: [dynamic]hir.Function_Id
|
calls: [dynamic]hir.Function_Id
|
||||||
calls.allocator = checker.allocator
|
calls.allocator = checker.allocator
|
||||||
declared := resolve_inferred_array(checker, type_from_syntax(global.type), global.expr)
|
declared := resolve_inferred_array(checker, type_from_syntax(checker, global.type, global.pkg, global.file), global.expr)
|
||||||
expected := types.INVALID
|
expected := types.INVALID
|
||||||
if is_runtime_type(checker, declared) {
|
if is_runtime_type(checker, declared) {
|
||||||
expected = declared
|
expected = declared
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ Expr_Kind :: enum u8 {
|
|||||||
C_Coerce,
|
C_Coerce,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Retype,
|
Retype,
|
||||||
|
Scalar_Cast,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
Weaken_Slice,
|
Weaken_Slice,
|
||||||
Decay_Array_Pointer,
|
Decay_Array_Pointer,
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ Opcode :: enum u8 {
|
|||||||
C_Coerce,
|
C_Coerce,
|
||||||
C_Vararg_Promote,
|
C_Vararg_Promote,
|
||||||
Retype,
|
Retype,
|
||||||
|
Scalar_Cast,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
Weaken_Slice,
|
Weaken_Slice,
|
||||||
Decay_Array_Pointer,
|
Decay_Array_Pointer,
|
||||||
|
|||||||
+43
-6
@@ -256,7 +256,7 @@ valid_value :: proc(
|
|||||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
|
||||||
.Fallible_Error, .Extract, .Select, .Unwrap,
|
.Fallible_Error, .Extract, .Select, .Unwrap,
|
||||||
.Optional_Is_Some, .Optional_Value, .Orelse,
|
.Optional_Is_Some, .Optional_Value, .Orelse,
|
||||||
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
||||||
return true
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
@@ -1403,16 +1403,15 @@ emit_instruction_stream :: proc(
|
|||||||
emit_recovery_value(emitter, instruction_index, instruction, "unsupported sum widening operand")
|
emit_recovery_value(emitter, instruction_index, instruction, "unsupported sum widening operand")
|
||||||
case .C_Coerce:
|
case .C_Coerce:
|
||||||
if !valid_instruction(instructions, instruction.a) ||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
!types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type) {
|
!(types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type, emitter.module.target) ||
|
||||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid C integer coercion operand")
|
types.can_coerce_c_scalar(instructions[instruction.a].type, instruction.type, emitter.module.target)) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid C scalar coercion operand")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
from_type := instructions[instruction.a].type
|
from_type := instructions[instruction.a].type
|
||||||
from_bits := types.bits(from_type, emitter.module.target)
|
from_bits := types.bits(from_type, emitter.module.target)
|
||||||
to_bits := types.bits(instruction.type, emitter.module.target)
|
to_bits := types.bits(instruction.type, emitter.module.target)
|
||||||
if from_bits == to_bits {
|
if from_bits == to_bits {
|
||||||
// Same-width signedness change: c_uint and c_int both lower to the
|
|
||||||
// identical `iN`, so this is a pure reinterpret (no-op `select`).
|
|
||||||
type_name := llvm_type(instruction.type, &emitter.module.types)
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
|
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
|
||||||
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
@@ -1421,7 +1420,8 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, "\n")
|
strings.write_string(&emitter.builder, "\n")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
operation := "sext" if types.is_signed(from_type, emitter.module.target) else "zext"
|
operation := "fpext" if types.is_float(from_type, emitter.module.target) else
|
||||||
|
("sext" if types.is_signed(from_type, emitter.module.target) else "zext")
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
||||||
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||||
@@ -1467,6 +1467,43 @@ emit_instruction_stream :: proc(
|
|||||||
&emitter.module.types,
|
&emitter.module.types,
|
||||||
)
|
)
|
||||||
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name)
|
||||||
|
case .Scalar_Cast:
|
||||||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
|
!types.is_concrete_scalar(instructions[instruction.a].type) ||
|
||||||
|
!types.is_concrete_scalar(instruction.type) ||
|
||||||
|
types.is_bool(instructions[instruction.a].type) ||
|
||||||
|
types.is_bool(instruction.type) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid scalar cast operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
from_type := instructions[instruction.a].type
|
||||||
|
from_bits := types.bits(from_type, emitter.module.target)
|
||||||
|
to_bits := types.bits(instruction.type, emitter.module.target)
|
||||||
|
from_float := types.is_float(from_type, emitter.module.target)
|
||||||
|
to_float := types.is_float(instruction.type, emitter.module.target)
|
||||||
|
if types.equal(from_type, instruction.type) || from_bits == to_bits && from_float == to_float {
|
||||||
|
type_name := llvm_type(instruction.type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
operation := ""
|
||||||
|
switch {
|
||||||
|
case from_float && to_float:
|
||||||
|
operation = "fpext" if from_bits < to_bits else "fptrunc"
|
||||||
|
case !from_float && !to_float:
|
||||||
|
operation = "trunc" if from_bits > to_bits else ("sext" if types.is_signed(from_type, emitter.module.target) else "zext")
|
||||||
|
case from_float:
|
||||||
|
operation = "fptosi" if types.is_signed(instruction.type, emitter.module.target) else "fptoui"
|
||||||
|
case:
|
||||||
|
operation = "sitofp" if types.is_signed(from_type, emitter.module.target) else "uitofp"
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
|
||||||
case .Weaken_Pointer:
|
case .Weaken_Pointer:
|
||||||
if !valid_instruction(instructions, instruction.a) ||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
|
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Negate:
|
case .Negate:
|
||||||
@@ -726,6 +726,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
case .C_Coerce: op = .C_Coerce
|
case .C_Coerce: op = .C_Coerce
|
||||||
case .C_Vararg_Promote: op = .C_Vararg_Promote
|
case .C_Vararg_Promote: op = .C_Vararg_Promote
|
||||||
case .Retype: op = .Retype
|
case .Retype: op = .Retype
|
||||||
|
case .Scalar_Cast: op = .Scalar_Cast
|
||||||
case: op = .Widen
|
case: op = .Widen
|
||||||
}
|
}
|
||||||
last = append_instruction(state, ir.Instruction{
|
last = append_instruction(state, ir.Instruction{
|
||||||
|
|||||||
@@ -212,9 +212,15 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
if _, ok := allow(parser, .Underscore); ok {
|
if _, ok := allow(parser, .Underscore); ok {
|
||||||
node.inferred_count = true
|
node.inferred_count = true
|
||||||
} else {
|
} else {
|
||||||
count, ok := parse_type_constant(parser)
|
if (current(parser).kind == .Integer || current(parser).kind == .Character) &&
|
||||||
if ok {
|
(peek(parser).kind == .Right_Bracket || peek(parser).kind == .Semicolon) {
|
||||||
|
count, ok := parse_type_constant(parser)
|
||||||
node.count = count
|
node.count = count
|
||||||
|
_ = ok
|
||||||
|
} else {
|
||||||
|
expr := parse_expression(parser)
|
||||||
|
node.count_expr = u32(expr)
|
||||||
|
node.unresolved_count = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := allow(parser, .Semicolon); ok {
|
if _, ok := allow(parser, .Semicolon); ok {
|
||||||
@@ -585,6 +591,37 @@ 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_I8, .Keyword_I16, .Keyword_I32, .Keyword_I64,
|
||||||
|
.Keyword_U8, .Keyword_U16, .Keyword_U32, .Keyword_U64,
|
||||||
|
.Keyword_Isize, .Keyword_Usize, .Keyword_F32, .Keyword_F64,
|
||||||
|
.Keyword_C_Char, .Keyword_C_Schar, .Keyword_C_Uchar,
|
||||||
|
.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_Float, .Keyword_C_Double, .Keyword_C_Longdouble:
|
||||||
|
start := tok
|
||||||
|
target := parse_type_atom(parser)
|
||||||
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
|
return invalid_expr(parser, current(parser).span, "expected '(' after scalar cast type")
|
||||||
|
}
|
||||||
|
parser.delimiter_depth += 1
|
||||||
|
skip_newlines(parser)
|
||||||
|
operand := parse_expression_bp(parser, 0, nesting+1)
|
||||||
|
skip_newlines(parser)
|
||||||
|
end := current(parser)
|
||||||
|
if close, ok := allow(parser, .Right_Paren); ok {
|
||||||
|
end = close
|
||||||
|
} else {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected ')' after scalar cast")
|
||||||
|
}
|
||||||
|
parser.delimiter_depth -= 1
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Cast,
|
||||||
|
span=span_from(start.span, end.span),
|
||||||
|
type=target,
|
||||||
|
left=operand,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Integer:
|
case .Integer:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
value, ok := parse_integer_magnitude(token_text(parser, tok))
|
value, ok := parse_integer_magnitude(token_text(parser, tok))
|
||||||
@@ -1126,6 +1163,21 @@ parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
if cf, is_cf := parse_value_control_flow(parser); is_cf {
|
||||||
|
cf_span := parser.module.statements[cf].span
|
||||||
|
body := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||||
|
body[0] = cf
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Return,
|
||||||
|
span=span_from(start.span, cf_span),
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
body=body,
|
||||||
|
value_control_flow=true,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
expr := parse_expression(parser)
|
expr := parse_expression(parser)
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
@@ -1154,6 +1206,22 @@ parse_yield :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'")
|
source.add(parser.diagnostics, current(parser).span, "expected a loop label after ':'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cf, is_cf := parse_value_control_flow(parser); is_cf {
|
||||||
|
cf_span := parser.module.statements[cf].span
|
||||||
|
body := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||||
|
body[0] = cf
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Yield,
|
||||||
|
span=span_from(start.span, cf_span),
|
||||||
|
label=label,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
body=body,
|
||||||
|
value_control_flow=true,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
expr := parse_expression(parser)
|
expr := parse_expression(parser)
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ Node :: struct {
|
|||||||
child: Type,
|
child: Type,
|
||||||
extra: Type,
|
extra: Type,
|
||||||
count: u64,
|
count: u64,
|
||||||
|
count_expr: u32,
|
||||||
sentinel: u64,
|
sentinel: u64,
|
||||||
explicit_size: u64,
|
explicit_size: u64,
|
||||||
field_start: u32,
|
field_start: u32,
|
||||||
@@ -93,6 +94,7 @@ Node :: struct {
|
|||||||
many: bool,
|
many: bool,
|
||||||
has_sentinel: bool,
|
has_sentinel: bool,
|
||||||
inferred_count: bool,
|
inferred_count: bool,
|
||||||
|
unresolved_count: bool,
|
||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
c_layout: bool,
|
c_layout: bool,
|
||||||
@@ -1308,6 +1310,8 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
|
|||||||
}
|
}
|
||||||
item.count = count
|
item.count = count
|
||||||
item.inferred_count = false
|
item.inferred_count = false
|
||||||
|
item.unresolved_count = false
|
||||||
|
item.count_expr = 0
|
||||||
return intern(store, item)
|
return intern(store, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1339,6 +1343,16 @@ can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool {
|
|||||||
(from_node.has_sentinel && from_node.sentinel == to_node.sentinel))
|
(from_node.has_sentinel && from_node.sentinel == to_node.sentinel))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
can_decay_slice_c_string :: proc(from, to: Type, store: ^Store) -> bool {
|
||||||
|
from_node, from_ok := node(store, from)
|
||||||
|
to_node, to_ok := node(store, to)
|
||||||
|
return from_ok && to_ok &&
|
||||||
|
from_node.kind == .Slice && to_node.kind == .Pointer && to_node.many &&
|
||||||
|
from_node.child == U8 && to_node.child == C_CHAR &&
|
||||||
|
from_node.has_sentinel && from_node.sentinel == 0 && !from_node.mutable &&
|
||||||
|
!to_node.mutable
|
||||||
|
}
|
||||||
|
|
||||||
can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool {
|
can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool {
|
||||||
from_pointer, array, from_ok := array_pointer(from, store)
|
from_pointer, array, from_ok := array_pointer(from, store)
|
||||||
to_node, to_ok := node(store, to)
|
to_node, to_ok := node(store, to)
|
||||||
@@ -1536,10 +1550,18 @@ can_widen :: proc(from, to: Type) -> bool {
|
|||||||
// make C interop cumbersome. Scope: widening (sext/zext) and same-width
|
// make C interop cumbersome. Scope: widening (sext/zext) and same-width
|
||||||
// signedness changes (no-op reinterpret); narrowing is intentionally excluded so
|
// signedness changes (no-op reinterpret); narrowing is intentionally excluded so
|
||||||
// lossy conversions stay an error, matching brolang's trap-on-narrow philosophy.
|
// lossy conversions stay an error, matching brolang's trap-on-narrow philosophy.
|
||||||
can_coerce_c_integer :: proc(from, to: Type) -> bool {
|
can_coerce_c_integer :: proc(from, to: Type, selected := target.DEFAULT) -> bool {
|
||||||
return from != to && is_c(from) && is_c(to) &&
|
return from != to && is_c(from) && is_c(to) &&
|
||||||
is_concrete_integer(from) && is_concrete_integer(to) &&
|
is_concrete_integer(from) && is_concrete_integer(to) &&
|
||||||
bits(from) <= bits(to)
|
bits(from, selected) <= bits(to, selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
can_coerce_c_scalar :: proc(from, to: Type, selected := target.DEFAULT) -> bool {
|
||||||
|
return from != to && !is_c(from) && is_c(to) &&
|
||||||
|
is_concrete_scalar(from) && is_concrete_scalar(to) &&
|
||||||
|
category(from, selected) == category(to, selected) &&
|
||||||
|
category(from, selected) != .None &&
|
||||||
|
bits(from, selected) <= bits(to, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
widest :: proc(a, b: Type) -> Type {
|
widest :: proc(a, b: Type) -> Type {
|
||||||
|
|||||||
@@ -2033,6 +2033,35 @@ sentinel_pointer_views_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 303)
|
testing.expect_value(t, state.exit_code, 303)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_24_regressions_compile_and_run :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-milestone-24"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/milestone_24", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||||
|
Case :: struct {
|
||||||
|
directory: string,
|
||||||
|
output: string,
|
||||||
|
}
|
||||||
|
cases := [?]Case{
|
||||||
|
{"examples/programs/index_signed_error", "/tmp/brolang-test-index-signed-error"},
|
||||||
|
{"examples/programs/index_int_constraint_error", "/tmp/brolang-test-index-int-constraint-error"},
|
||||||
|
{"examples/programs/scalar_cast_error", "/tmp/brolang-test-scalar-cast-error"},
|
||||||
|
{"examples/programs/array_const_size_error", "/tmp/brolang-test-array-const-size-error"},
|
||||||
|
}
|
||||||
|
for test_case in cases {
|
||||||
|
status := compiler_core.compile_package(test_case.directory, test_case.output)
|
||||||
|
testing.expect_value(t, status, 1)
|
||||||
|
_ = os.remove(test_case.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
control_flow_compiles_and_runs :: proc(t: ^testing.T) {
|
control_flow_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-control-flow"
|
output := "/tmp/brolang-test-control-flow"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
main func() i32 {
|
||||||
|
n usize = 4
|
||||||
|
items [n]mut i32 = undefined
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
main func() i32 {
|
||||||
|
items [3]mut i32 = undefined
|
||||||
|
i int = 1
|
||||||
|
items[i] = 42
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
main func() i32 {
|
||||||
|
items [3]mut i32 = undefined
|
||||||
|
i i32 = 1
|
||||||
|
items[i] = 42
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
CAP :: 4
|
||||||
|
LEN :: CAP + 1
|
||||||
|
|
||||||
|
Kind :: enum {
|
||||||
|
circle
|
||||||
|
square
|
||||||
|
triangle
|
||||||
|
}
|
||||||
|
|
||||||
|
take_c_int func(value c_int) c_int {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
take_c_uchar func(value c_uchar) c_uchar {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
take_c_float func(value c_float) c_float {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
take_c_double func(value c_double) c_double {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
take_c_string func(value ?*c_char) i32 {
|
||||||
|
_ = value
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
next_kind func(k Kind) Kind {
|
||||||
|
return match k {
|
||||||
|
.circle: .square
|
||||||
|
.square: .triangle
|
||||||
|
.triangle: .circle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
score_for func(k Kind) i32 {
|
||||||
|
score :: {
|
||||||
|
yield match k {
|
||||||
|
.circle: 1
|
||||||
|
.square: 2
|
||||||
|
.triangle: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 {
|
||||||
|
items [LEN]mut i32 = undefined
|
||||||
|
items[0] = 10
|
||||||
|
items[1] = 20
|
||||||
|
|
||||||
|
idx u8 = 2
|
||||||
|
items[idx] = items[0] + items[1]
|
||||||
|
if (items[2] != 30) return 1
|
||||||
|
|
||||||
|
native_i i32 = 12
|
||||||
|
if (take_c_int(native_i) != 12) return 2
|
||||||
|
|
||||||
|
native_u u8 = 7
|
||||||
|
if (take_c_uchar(native_u) != 7) return 3
|
||||||
|
|
||||||
|
native_f f32 = 3.25
|
||||||
|
cf :: take_c_float(native_f)
|
||||||
|
if (cf < 3.0 or cf > 4.0) return 4
|
||||||
|
if (cf == 0.0) return 5
|
||||||
|
|
||||||
|
native_d f64 = 5.0
|
||||||
|
cd :: take_c_double(native_d)
|
||||||
|
if (cd != 5.0) return 6
|
||||||
|
|
||||||
|
as_i32 :: i32(cf)
|
||||||
|
if (as_i32 != 3) return 7
|
||||||
|
as_float :: f32(native_i)
|
||||||
|
if (as_float < 11.5 or as_float > 12.5) return 8
|
||||||
|
as_c_float :: c_float(as_i32)
|
||||||
|
if (as_c_float != 3.0) return 9
|
||||||
|
|
||||||
|
kind :: next_kind(.circle)
|
||||||
|
if (kind != .square) return 10
|
||||||
|
if (score_for(kind) != 2) return 11
|
||||||
|
|
||||||
|
label :: match kind {
|
||||||
|
.circle: "circle"
|
||||||
|
.square: "square"
|
||||||
|
.triangle: "triangle"
|
||||||
|
}
|
||||||
|
if (take_c_string(label) != 0) return 12
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
main func() i32 {
|
||||||
|
flag bool = true
|
||||||
|
value :: i32(flag)
|
||||||
|
return value
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user