enforce integer division via explicit builtins
This commit is contained in:
@@ -399,6 +399,31 @@ Type_Builtin :: enum u8 {
|
||||
Max_Value,
|
||||
}
|
||||
|
||||
Division_Builtin :: enum u8 {
|
||||
None,
|
||||
Trunc,
|
||||
Floor,
|
||||
Exact,
|
||||
Ceil,
|
||||
Rem,
|
||||
Mod,
|
||||
}
|
||||
|
||||
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
|
||||
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
}
|
||||
switch symbol_text(checker, expr.name) {
|
||||
case "div_trunc": return .Trunc
|
||||
case "div_floor": return .Floor
|
||||
case "div_exact": return .Exact
|
||||
case "div_ceil": return .Ceil
|
||||
case "rem": return .Rem
|
||||
case "mod": return .Mod
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
|
||||
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
@@ -598,6 +623,10 @@ type_from_syntax :: proc(
|
||||
changed = true
|
||||
}
|
||||
} else {
|
||||
if constant.kind == .Integer_Division {
|
||||
source.add(checker.diagnostics, span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
|
||||
return types.INVALID
|
||||
}
|
||||
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
|
||||
return types.INVALID
|
||||
}
|
||||
@@ -2568,6 +2597,35 @@ infer_nested_expr :: proc(
|
||||
return result
|
||||
}
|
||||
|
||||
infer_division_builtin :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
locals: []Infer_Local,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
demanded: ^[dynamic]Spec_Id,
|
||||
local_types: []types.Type,
|
||||
expected: types.Type,
|
||||
) -> types.Type {
|
||||
if len(expr.args) != 2 {
|
||||
return types.INVALID
|
||||
}
|
||||
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
||||
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
||||
left, right := types.INVALID, types.INVALID
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
||||
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, right)
|
||||
} else {
|
||||
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, hint)
|
||||
right_hint := hint if types.is_valid(hint) else left
|
||||
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, right_hint)
|
||||
}
|
||||
result := types.widest(left, right)
|
||||
return result if types.is_concrete_scalar(result) && !types.is_bool(result) else types.INVALID
|
||||
}
|
||||
|
||||
infer_compound_expr :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
@@ -2938,6 +2996,11 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if division_builtin_call(checker, expr) != .None {
|
||||
last = infer_division_builtin(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
last = types.INVALID
|
||||
@@ -3952,6 +4015,12 @@ record_demand :: proc(
|
||||
right := record_demand(checker, expr.right, demand, locals, local_types, pkg, file)
|
||||
return left || right
|
||||
}
|
||||
case .Call:
|
||||
if division_builtin_call(checker, expr) != .None && len(expr.args) == 2 && is_numeric_demand(demand, checker.target) {
|
||||
left := record_demand(checker, expr.args[0], demand, locals, local_types, pkg, file)
|
||||
right := record_demand(checker, expr.args[1], demand, locals, local_types, pkg, file)
|
||||
return left || right
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -4435,6 +4504,14 @@ build_constant_expr :: proc(
|
||||
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Non_Exact {
|
||||
id := source.add(checker.diagnostics, expr.span, "exact division has a remainder")
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Integer_Division {
|
||||
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Overflow ||
|
||||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
||||
id := source.add(
|
||||
@@ -4868,6 +4945,89 @@ build_nested_expr :: proc(
|
||||
return result
|
||||
}
|
||||
|
||||
try_build_comptime_division :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
kind: Division_Builtin,
|
||||
expected: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> (hir.Expr_Id, bool) {
|
||||
state := ct_state_make(checker, pkg, file, diagnose=false)
|
||||
defer ct_state_destroy(&state)
|
||||
value, flow, ok := ct_eval_division_call(&state, expr, kind, expected, 0)
|
||||
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE {
|
||||
return ct_materialize_value(&state, value, expr.span, expected), true
|
||||
}
|
||||
message := ""
|
||||
#partial switch state.error {
|
||||
case .Div_By_Zero: message = "division builtin denominator is zero"
|
||||
case .Overflow: message = "signed integer division overflow"
|
||||
case .Non_Exact: message = "exact division has a remainder"
|
||||
}
|
||||
if len(message) == 0 {
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
id := source.add(checker.diagnostics, expr.span, message)
|
||||
recovery := expected if types.is_concrete_scalar(expected) else types.I64
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery), true
|
||||
}
|
||||
|
||||
build_division_builtin :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
kind: Division_Builtin,
|
||||
locals: []Build_Local,
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
calls: ^[dynamic]hir.Function_Id,
|
||||
expected: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(
|
||||
checker.diagnostics, expr.span, "%s expects 2 arguments, got %d",
|
||||
symbol_text(checker, expr.name), len(expr.args),
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
if value, handled := try_build_comptime_division(checker, expr, kind, expected, pkg, file); handled {
|
||||
return value
|
||||
}
|
||||
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
||||
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
||||
left, right := hir.INVALID_EXPR, hir.INVALID_EXPR
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
|
||||
} else {
|
||||
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, hint, pkg, file)
|
||||
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
|
||||
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file)
|
||||
}
|
||||
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_concrete_scalar(result) || types.is_bool(result) {
|
||||
id := source.add(checker.diagnostics, expr.span, "division builtins require compatible numeric operands")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||
result_kind := hir.Expr_Kind.Div_Trunc
|
||||
#partial switch kind {
|
||||
case .Floor: result_kind = .Div_Floor
|
||||
case .Exact: result_kind = .Div_Exact
|
||||
case .Ceil: result_kind = .Div_Ceil
|
||||
case .Rem: result_kind = .Rem
|
||||
case .Mod: result_kind = .Mod
|
||||
case:
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=result_kind, span=expr.span, type=result, left=left, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
fallible_aggregate :: proc(
|
||||
checker: ^Checker,
|
||||
span: source.Span,
|
||||
@@ -5578,6 +5738,13 @@ build_binary_arith :: proc(
|
||||
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
|
||||
return invalid_hir_expr(checker, span, id)
|
||||
}
|
||||
if op == .Div && !types.is_float(result, checker.target) {
|
||||
id := source.add(
|
||||
checker.diagnostics, span,
|
||||
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
|
||||
)
|
||||
return invalid_hir_expr(checker, span, id, result)
|
||||
}
|
||||
result_kind := hir.Expr_Kind.Add
|
||||
#partial switch op {
|
||||
case .Sub: result_kind = .Sub
|
||||
@@ -5627,7 +5794,7 @@ build_expr :: proc(
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
||||
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
|
||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
@@ -5807,6 +5974,13 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if builtin := division_builtin_call(checker, expr); builtin != .None {
|
||||
last = build_division_builtin(
|
||||
checker, expr, builtin, locals, global_reads, calls, frame.expected, pkg, file,
|
||||
)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_ptr_cast_call(checker, expr) {
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args))
|
||||
@@ -6653,7 +6827,14 @@ build_block :: proc(
|
||||
}
|
||||
rhs_type := checker.module.exprs[value].type
|
||||
result_type := types.widest(target_type, rhs_type)
|
||||
if !types.is_concrete_scalar(result_type) ||
|
||||
if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
"integer '/=' is not allowed; assign through an explicit division builtin",
|
||||
)
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
} else if !types.is_concrete_scalar(result_type) ||
|
||||
types.is_bool(result_type) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
|
||||
+172
-26
@@ -6,6 +6,8 @@ import "../source"
|
||||
import "../symbol"
|
||||
import "../types"
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
|
||||
COMPTIME_EVAL_QUOTA :: 100_000
|
||||
@@ -28,6 +30,8 @@ Constant_Kind :: enum {
|
||||
Value,
|
||||
Overflow,
|
||||
Div_By_Zero,
|
||||
Non_Exact,
|
||||
Integer_Division,
|
||||
}
|
||||
|
||||
Constant :: struct {
|
||||
@@ -94,8 +98,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
|
||||
expr.kind != .Div && expr.kind != .Negate {
|
||||
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul && expr.kind != .Negate {
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if expr.kind == .Integer {
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
@@ -118,9 +121,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
||||
operand = checker.constants[expr.left]
|
||||
}
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if operand.kind == .Div_By_Zero {
|
||||
result = Constant{kind = .Div_By_Zero}
|
||||
} else if operand.kind == .Overflow {
|
||||
if operand.kind == .Overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if operand.kind == .Value {
|
||||
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
|
||||
@@ -147,29 +148,19 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
|
||||
right = checker.constants[expr.right]
|
||||
}
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
|
||||
result = Constant{kind = .Div_By_Zero}
|
||||
} else if left.kind == .Overflow || right.kind == .Overflow {
|
||||
if left.kind == .Overflow || right.kind == .Overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if left.kind == .Value && right.kind == .Value {
|
||||
value: i128
|
||||
overflow: bool
|
||||
div_by_zero: 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 {
|
||||
div_by_zero = true
|
||||
} else {
|
||||
value = left.value / right.value
|
||||
}
|
||||
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
|
||||
}
|
||||
switch {
|
||||
case div_by_zero: result = Constant{kind = .Div_By_Zero}
|
||||
case overflow: result = Constant{kind = .Overflow}
|
||||
case: result = Constant{kind = .Value, value = value}
|
||||
case overflow: result = Constant{kind = .Overflow}
|
||||
case: result = Constant{kind = .Value, value = value}
|
||||
}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
@@ -226,6 +217,8 @@ Ct_Error_Kind :: enum u8 {
|
||||
Not_Comptime,
|
||||
Overflow,
|
||||
Div_By_Zero,
|
||||
Non_Exact,
|
||||
Integer_Division,
|
||||
Quota,
|
||||
}
|
||||
|
||||
@@ -1105,7 +1098,8 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_unary(state, expr.kind, value, expr.span)
|
||||
case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
||||
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
@@ -1858,6 +1852,12 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Div {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Integer_Division, span,
|
||||
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
|
||||
)
|
||||
}
|
||||
value: i128
|
||||
overflow := false
|
||||
#partial switch op {
|
||||
@@ -1865,12 +1865,6 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
value, overflow = intrinsics.overflow_sub(left.integer, right.integer)
|
||||
case .Mul:
|
||||
value, overflow = intrinsics.overflow_mul(left.integer, right.integer)
|
||||
case .Div:
|
||||
if right.integer == 0 {
|
||||
state.error = .Div_By_Zero
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
value = left.integer / right.integer
|
||||
case:
|
||||
value, overflow = intrinsics.overflow_add(left.integer, right.integer)
|
||||
}
|
||||
@@ -1887,6 +1881,137 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
|
||||
ct_eval_division_builtin :: proc(
|
||||
state: ^Ct_State,
|
||||
kind: Division_Builtin,
|
||||
left_id, right_id: Ct_Value_Id,
|
||||
span: source.Span,
|
||||
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
|
||||
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
left := state.values[left_id]
|
||||
right := state.values[right_id]
|
||||
result_type := types.widest(left.type, right.type)
|
||||
if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
|
||||
state, .Not_Comptime, span, "division builtins require compatible numeric operands",
|
||||
)
|
||||
}
|
||||
left_id, left_ok := ct_coerce_value(state, left_id, result_type, span)
|
||||
right_id, right_ok := ct_coerce_value(state, right_id, result_type, span)
|
||||
if !left_ok || !right_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
left = state.values[left_id]
|
||||
right = state.values[right_id]
|
||||
if left.kind == .Float && right.kind == .Float {
|
||||
if right.float == 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
|
||||
}
|
||||
quotient := left.float / right.float
|
||||
result := quotient
|
||||
#partial switch kind {
|
||||
case .Trunc: result = math.trunc(quotient)
|
||||
case .Floor: result = math.floor(quotient)
|
||||
case .Ceil: result = math.ceil(quotient)
|
||||
case .Exact:
|
||||
result = math.trunc(quotient)
|
||||
if result * right.float != left.float {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
|
||||
}
|
||||
case .Rem, .Mod:
|
||||
result = left.float - math.trunc(quotient) * right.float
|
||||
if kind == .Mod && result != 0 && (result < 0) != (right.float < 0) {
|
||||
result += right.float
|
||||
}
|
||||
}
|
||||
if types.bits(result_type, state.checker.target) == 32 {
|
||||
result = f64(f32(result))
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
|
||||
}
|
||||
if left.kind != .Integer || right.kind != .Integer {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands")
|
||||
}
|
||||
if right.integer == 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
|
||||
}
|
||||
is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil
|
||||
if is_quotient && types.is_signed(result_type, state.checker.target) {
|
||||
minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1))
|
||||
if left.integer == minimum && right.integer == -1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow")
|
||||
}
|
||||
}
|
||||
quotient := left.integer / right.integer
|
||||
remainder := left.integer % right.integer
|
||||
result := quotient
|
||||
#partial switch kind {
|
||||
case .Floor:
|
||||
if remainder != 0 && (left.integer < 0) != (right.integer < 0) {
|
||||
result -= 1
|
||||
}
|
||||
case .Ceil:
|
||||
if remainder != 0 && (left.integer < 0) == (right.integer < 0) {
|
||||
result += 1
|
||||
}
|
||||
case .Exact:
|
||||
if remainder != 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
|
||||
}
|
||||
case .Rem: result = remainder
|
||||
case .Mod:
|
||||
result = remainder
|
||||
if result != 0 && (result < 0) != (right.integer < 0) {
|
||||
result += right.integer
|
||||
}
|
||||
case:
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_division_call :: proc(
|
||||
state: ^Ct_State,
|
||||
expr: ast.Expr,
|
||||
kind: Division_Builtin,
|
||||
expected: types.Type,
|
||||
depth: int,
|
||||
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if len(expr.args) != 2 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state, .Not_Comptime, expr.span, "%s expects 2 arguments, got %d",
|
||||
symbol_text(checker, expr.name), len(expr.args),
|
||||
)
|
||||
}
|
||||
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.args[0])
|
||||
right_const := is_numeric_constant_expr(checker, expr.args[1])
|
||||
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
|
||||
flow := ct_flow(.Normal)
|
||||
ok := false
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
right, flow, ok = ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1)
|
||||
} else {
|
||||
left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
right_hint := hint if types.is_valid(hint) else state.values[left].type
|
||||
right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1)
|
||||
}
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
return ct_eval_division_builtin(state, kind, left, right, expr.span)
|
||||
}
|
||||
|
||||
ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
@@ -1943,6 +2068,9 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
|
||||
}
|
||||
if builtin := division_builtin_call(checker, expr); builtin != .None {
|
||||
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
|
||||
if !available {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
|
||||
@@ -2899,6 +3027,10 @@ eval_integer_constant_in_context :: proc(
|
||||
return Constant{kind=.Overflow}
|
||||
case .Div_By_Zero:
|
||||
return Constant{kind=.Div_By_Zero}
|
||||
case .Non_Exact:
|
||||
return Constant{kind=.Non_Exact}
|
||||
case .Integer_Division:
|
||||
return Constant{kind=.Integer_Division}
|
||||
}
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
@@ -2927,6 +3059,10 @@ eval_comptime_statements :: proc(
|
||||
return Constant{kind=.Overflow}, false, false
|
||||
case .Div_By_Zero:
|
||||
return Constant{kind=.Div_By_Zero}, false, false
|
||||
case .Non_Exact:
|
||||
return Constant{kind=.Non_Exact}, false, false
|
||||
case .Integer_Division:
|
||||
return Constant{kind=.Integer_Division}, false, false
|
||||
}
|
||||
return Constant{kind=.Not_Constant}, false, false
|
||||
}
|
||||
@@ -2958,6 +3094,10 @@ eval_comptime_call :: proc(
|
||||
return Constant{kind=.Overflow}
|
||||
case .Div_By_Zero:
|
||||
return Constant{kind=.Div_By_Zero}
|
||||
case .Non_Exact:
|
||||
return Constant{kind=.Non_Exact}
|
||||
case .Integer_Division:
|
||||
return Constant{kind=.Integer_Division}
|
||||
}
|
||||
return Constant{kind=.Not_Constant}
|
||||
}
|
||||
@@ -2996,7 +3136,7 @@ infer_comptime_expr_type :: proc(
|
||||
}
|
||||
}
|
||||
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) {
|
||||
if state.error == .Overflow || state.error == .Div_By_Zero {
|
||||
if state.error == .Overflow || state.error == .Div_By_Zero || state.error == .Non_Exact || state.error == .Integer_Division {
|
||||
return types.I64
|
||||
}
|
||||
return types.INVALID
|
||||
@@ -3038,6 +3178,12 @@ build_comptime_expr :: proc(
|
||||
if state.error == .Overflow {
|
||||
return build_constant_expr(checker, expr, Constant{kind=.Overflow}, expected)
|
||||
}
|
||||
if state.error == .Non_Exact {
|
||||
return build_constant_expr(checker, expr, Constant{kind=.Non_Exact}, expected)
|
||||
}
|
||||
if state.error == .Integer_Division {
|
||||
return build_constant_expr(checker, expr, Constant{kind=.Integer_Division}, expected)
|
||||
}
|
||||
diagnostic := state.diagnostic
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")
|
||||
|
||||
Reference in New Issue
Block a user