feat: support compound updates and bitwise operators

This commit is contained in:
2026-10-30 00:18:44 +01:00
parent 670ba8f756
commit d7e637dc3b
20 changed files with 890 additions and 88 deletions
+105 -7
View File
@@ -438,7 +438,7 @@ check_elaborated proc(
match ast.expr_kind(tree, id) {
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate) try require_numeric(&state, id, expression.operand)
if (expression.operator == .negate or expression.operator == .bit_not) try require_numeric(&state, id, expression.operand)
}
.binary: {
expression :: ast.binary_expr(tree, id)
@@ -464,7 +464,7 @@ check_elaborated proc(
match ast.expr_kind(tree, id) {
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate) try numeric_operand(&state, id, expression.operand)
if (expression.operator == .negate or expression.operator == .bit_not) try numeric_operand(&state, id, expression.operand)
}
.binary: {
expression :: ast.binary_expr(tree, id)
@@ -473,7 +473,11 @@ check_elaborated proc(
if (!bool_equality(&state, id)) try comparison_operands(&state, id)
} else {
try numeric_operand(&state, id, expression.left)
try numeric_operand(&state, id, expression.right)
if (shift(expression.operator)) {
count_group :: root(&state, usize(expression.right))
if (state.cells[count_group].type.kind == .none and state.cells[count_group].demand == .none)
try impose(&state, expression.right, .usize, .invalid_operand, expression.span)
} else try numeric_operand(&state, id, expression.right)
if (expression.operator == .divide) {
try impose(&state, id, .float, .integer_division, expression.span)
}
@@ -1370,10 +1374,10 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
}
.unary: {
expression :: ast.unary_expr(tree, id)
if (expression.operator == .negate) state.cells[usize(id)].producer = id
if (expression.operator == .negate or expression.operator == .bit_not) state.cells[usize(id)].producer = id
if (expression.operator == .logical_not) {
state.cells[usize(id)].type = Type{ kind = .bool }
} else if (expression.operator == .negate) {
} else if (expression.operator == .negate or expression.operator == .bit_not) {
state.cells[usize(id)].numeric = true
}
operand :: expression.operand
@@ -1570,6 +1574,27 @@ logical proc(operator ast.BinaryOperator) bool {
return operator == .logical_and or operator == .logical_or
}
@hide:file
shift proc(operator ast.BinaryOperator) bool {
return operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating
}
@hide:file
bitwise proc(operator ast.BinaryOperator) bool {
return operator == .bit_and or operator == .bit_or or operator == .bit_xor or shift(operator)
}
@hide:file
integer_width proc(kind TypeKind) usize {
return match kind {
.i8, .u8: 8
.i16, .u16: 16
.i32, .u32: 32
.int, .isize, .u64, .usize: 64
else: 0
}
}
@hide:file
pointer_addition proc(state @mut State, id ast.NodeId) bool {
expression :: ast.binary_expr(state.tree, id)
@@ -2153,7 +2178,9 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
return
}
operand :: state.types[usize(expression.operand)]
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
if (expression.operator == .bit_not) {
if (operand.kind != .invalid and !integer_kind(operand.kind)) try fail(state, id, .invalid_operand, expression.span)
} else if (operand.kind != .invalid and (!numeric_kind(operand.kind) or unsigned_kind(operand.kind))) try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.binary: {
expression :: ast.binary_expr(tree, id)
@@ -2165,6 +2192,21 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
left :: state.types[usize(expression.left)]
right :: state.types[usize(expression.right)]
if (left.kind == .invalid or right.kind == .invalid) return
if (shift(expression.operator)) {
if (!integer_kind(left.kind) or !unsigned_kind(right.kind)) {
try fail(state, id, .invalid_operand, expression.span)
} else if (expression.operator != .shift_left_saturating) {
if (evaluate_integer(state, expression.right, true)) |count| {
if (count.negative or count.magnitude >= u64(integer_width(left.kind)))
try fail(state, id, .invalid_shift_count, expression.span)
}
}
return
}
if (bitwise(expression.operator) and (!integer_kind(left.kind) or !integer_kind(right.kind))) {
try fail(state, id, .invalid_operand, expression.span)
return
}
if (pointer_addition(state, id)) {
if (right.kind != .usize or right.nominal != ast.NodeId(maxval!(u32))) try fail(state, id, .invalid_operand, expression.span)
return
@@ -2378,6 +2420,53 @@ constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
return evaluate_integer(state, id, false)
}
@hide:file
constant_bits proc(value ConstantInteger, mask u64) u64 {
if (value.negative) return (value.magnitude - 1) xor mask
return value.magnitude & mask
}
@hide:file
constant_from_bits proc(bits u64, kind TypeKind) ConstantInteger {
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
negative :: !unsigned_kind(kind) and bits > (mask >> 1)
magnitude :: if (negative) 1 + (bits xor mask) else bits
return ConstantInteger{
magnitude = magnitude,
negative = negative,
}
}
@hide:file
constant_bitwise proc(state @mut State, id ast.NodeId, left, right ConstantInteger) ?ConstantInteger {
view :: ast.binary_expr(state.tree, id)
family :: bound_family(state, id)
kind :: if (family == .none) TypeKind.int else family
width :: integer_width(kind)
if (width == 0 or !integer_fits(left, kind)) return null
mask :: maxval!(u64) >> u64(64 - width)
a :: constant_bits(left, mask)
b :: constant_bits(right, mask)
if (view.operator == .bit_and) return constant_from_bits(a & b, kind)
if (view.operator == .bit_or) return constant_from_bits(a | b, kind)
if (view.operator == .bit_xor) return constant_from_bits(a xor b, kind)
if (right.negative) return null
count :: right.magnitude
if (view.operator == .shift_left_saturating) {
signed_limit :: if (left.negative) 1 + (mask >> 1) else mask >> 1
limit :: if (unsigned_kind(kind)) mask else signed_limit
if (left.magnitude == 0) return left
if (count >= u64(width) or left.magnitude > (limit >> count))
return ConstantInteger{ magnitude = limit, negative = left.negative }
} else if (count >= u64(width)) return null
if (view.operator == .shift_right) {
shifted :: a >> count
bits :: if (left.negative) shifted | (mask xor (mask >> count)) else shifted
return constant_from_bits(bits, kind)
}
return constant_from_bits((a << count) & mask, kind)
}
@hide:file
evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?ConstantInteger {
if (state.cells[usize(id)].constant_visiting) return null
@@ -2431,8 +2520,15 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
}
.unary: {
view :: ast.unary_expr(state.tree, id)
if (view.operator != .negate) return null
if (view.operator != .negate and view.operator != .bit_not) return null
if (evaluate_integer(state, view.operand, allow_local)) |value| {
if (view.operator == .bit_not) {
family :: bound_family(state, id)
kind :: if (family == .none) TypeKind.int else family
if (!integer_kind(kind) or !integer_fits(value, kind)) return null
mask :: maxval!(u64) >> u64(64 - integer_width(kind))
return constant_from_bits(constant_bits(value, mask) xor mask, kind)
}
return ConstantInteger{ magnitude = value.magnitude, negative = value.magnitude != 0 and !value.negative }
}
return null
@@ -2443,6 +2539,7 @@ evaluate_integer proc(state @mut State, id ast.NodeId, allow_local bool) ?Consta
right := ConstantInteger{ magnitude = 0 }
if (evaluate_integer(state, view.left, allow_local)) |value| left = value else return null
if (evaluate_integer(state, view.right, allow_local)) |value| right = value else return null
if (bitwise(view.operator)) return constant_bitwise(state, id, left, right)
if (view.operator == .multiply) {
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
magnitude :: left.magnitude * right.magnitude
@@ -3422,6 +3519,7 @@ bound_family proc(state @mut State, id ast.NodeId) TypeKind {
if (ast.expr_kind(state.tree, producer) == .unary) return bound_family(state, ast.unary_expr(state.tree, producer).operand)
view :: ast.binary_expr(state.tree, producer)
left :: bound_family(state, view.left)
if (shift(view.operator)) return left
right :: bound_family(state, view.right)
if (left == .float or right == .float) return .float
if (integer_kind(left)) return left
+1 -1
View File
@@ -718,7 +718,7 @@ resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
` return
` data [missing_count]MissingElement :: [absent, missing_element]
` (missing_base[missing_index])[missing_outer] = missing_rhs
` malformed [~]i64 :: [~]
` malformed [%]i64 :: [%]
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
+32 -3
View File
@@ -125,6 +125,8 @@ elaborate proc(
if (class == .expression
and (ast.expr_kind(&original, id) == .comptime_expr or ast.expr_kind(&original, id) == .block_expr))
needed = true
if (class == .expression and ast.expr_kind(&original, id) == .binary
and ast.binary_expr(&original, id).operator == .bit_or) needed = true
if (class == .statement and ast.stmt_kind(&original, id) == .for_stmt
and ast.for_stmt(&original, id).is_inline) needed = true
if (class == .match_arm and ast.match_arm_view(&original, id).is_inline) needed = true
@@ -451,6 +453,14 @@ expand proc(state @mut State, id ast.NodeId) void ! Error {
}
if (class == .expression and ast.expr_kind(&state.tree, id) == .binary) {
view :: ast.binary_expr(&state.tree, id)
if (view.operator == .bit_or
and (is_type(state, try expression_type(state, view.left, 0))
or is_type(state, try expression_type(state, view.right, 0)))) {
composed :: try ast.add_binary_type(state.builder, view.span, view.left, view.right, false)
try replace(state, id, composed)
try refresh(state)
return
}
if (
(view.operator == .equal or view.operator == .not_equal)
and branch_constant(state, view.left, 0) and branch_constant(state, view.right, 0)
@@ -1502,7 +1512,7 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
.unary: {
view :: ast.unary_expr(&state.tree, id)
return (
(view.operator == .negate or view.operator == .logical_not)
(view.operator == .negate or view.operator == .logical_not or view.operator == .bit_not)
and branch_constant(state, view.operand, depth + 1)
)
}
@@ -1955,6 +1965,15 @@ contextual_literal proc(state @State, id ast.NodeId) bool {
kind :: ast.expr_kind(&state.tree, id)
if (kind == .builtin_call)
return mem.eql(ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name), "some")
if (kind == .unary) return ast.unary_expr(&state.tree, id).operator == .bit_not and flexible(state, id)
if (kind == .binary) {
operator :: ast.binary_expr(&state.tree, id).operator
return (
(operator == .bit_and or operator == .bit_or or operator == .bit_xor
or operator == .shift_left or operator == .shift_right or operator == .shift_left_saturating)
and flexible(state, id)
)
}
return kind == .record_literal or kind == .array_literal or kind == .enum_literal or kind == .block_expr
}
@@ -3963,6 +3982,7 @@ execute_type_statement_body proc(state @mut State, execution @mut TypeExecution,
if (target != assignment.target) {
normalized :: try ast.add_assignment_stmt(state.builder,
ast.node_span(&state.tree, statement), target, assignment.value)
if (assignment.compound) ast.set_assignment_compound(state.builder, normalized)
try refresh(state)
# synthesized places still need local substitution in snapshots.
previous :: execution.mapping.len
@@ -4442,6 +4462,8 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
.alias_expr: return try evaluate(state, ast.type_operand(&state.tree, id))
.unary: {
view :: ast.unary_expr(&state.tree, id)
if (view.operator == .bit_not)
return try evaluate_ir(state, id, try expression_type(state, id, 0))
value := try evaluate(state, view.operand)
if (view.operator == .negate and value.kind == .integer) value.negative = value.magnitude != 0 and !value.negative
else if (view.operator == .negate and value.kind == .real) value.magnitude = value.magnitude xor (u64(1) << 63)
@@ -4484,6 +4506,9 @@ evaluate proc(state @mut State, id ast.NodeId) Value ! Error {
evaluate_binary proc(state @mut State, id ast.NodeId) Value ! Error {
view :: ast.binary_expr(&state.tree, id)
if (view.operator == .divide) return try evaluate_ir(state, id, try builtin(state, id, .float))
if (view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
return try evaluate_ir(state, id, try expression_type(state, id, 0))
left :: try evaluate(state, view.left)
if (left.kind == .invalid) return Value{}
if (left.kind == .boolean) {
@@ -4978,6 +5003,8 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
or view.operator == .less_equal or view.operator == .greater or view.operator == .greater_equal
or view.operator == .logical_and or view.operator == .logical_or
) return try builtin(state, id, .bool)
if ((view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor)
and flexible(state, view.left)) return try expression_type(state, view.right, depth + 1)
return try expression_type(state, view.left, depth + 1)
}
.selection: {
@@ -5326,12 +5353,14 @@ flexible proc(state @State, id ast.NodeId) bool {
if (kind == .literal_int or kind == .literal_float) return true
if (kind == .unary) {
view :: ast.unary_expr(&state.tree, id)
return view.operator == .negate and flexible(state, view.operand)
return (view.operator == .negate or view.operator == .bit_not) and flexible(state, view.operand)
}
if (kind == .binary) {
view :: ast.binary_expr(&state.tree, id)
return (
(view.operator == .add or view.operator == .subtract or view.operator == .multiply or view.operator == .divide)
(view.operator == .add or view.operator == .subtract or view.operator == .multiply or view.operator == .divide
or view.operator == .bit_and or view.operator == .bit_or or view.operator == .bit_xor
or view.operator == .shift_left or view.operator == .shift_right or view.operator == .shift_left_saturating)
and flexible(state, view.left) and flexible(state, view.right)
)
}