disambiguate enum blocks and complete distinct type operations
This commit is contained in:
+388
-128
@@ -942,13 +942,21 @@ type_builtin_value :: proc(checker: ^Checker, kind: Type_Builtin, value: types.T
|
||||
case .Align_Of:
|
||||
return i128(types.alignment_of(value, &checker.module.types, checker.target))
|
||||
case .Min_Value:
|
||||
if types.is_unsigned(value, checker.target) {
|
||||
representation := value
|
||||
if backing, ok := types.distinct_scalar_backing(value, &checker.module.types); ok {
|
||||
representation = backing
|
||||
}
|
||||
if types.is_unsigned(representation, checker.target) {
|
||||
return 0
|
||||
}
|
||||
return -(i128(1) << u32(types.bits(value, checker.target)-1))
|
||||
return -(i128(1) << u32(types.bits(representation, checker.target)-1))
|
||||
case .Max_Value:
|
||||
bit_count := types.bits(value, checker.target)
|
||||
sign_bit_count := 1 if types.is_signed(value, checker.target) else 0
|
||||
representation := value
|
||||
if backing, ok := types.distinct_scalar_backing(value, &checker.module.types); ok {
|
||||
representation = backing
|
||||
}
|
||||
bit_count := types.bits(representation, checker.target)
|
||||
sign_bit_count := 1 if types.is_signed(representation, checker.target) else 0
|
||||
return (i128(1) << u32(bit_count-sign_bit_count))-1
|
||||
case:
|
||||
return 0
|
||||
@@ -976,16 +984,22 @@ build_type_builtin :: proc(
|
||||
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(target) {
|
||||
bound_representation := target
|
||||
if backing, ok := types.distinct_scalar_backing(target, &checker.module.types); ok {
|
||||
bound_representation = backing
|
||||
}
|
||||
if (kind == .Min_Value || kind == .Max_Value) && !types.is_concrete_integer(bound_representation) {
|
||||
id := source.addf(checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.USIZE)
|
||||
}
|
||||
result_type := types.USIZE if kind == .Size_Of || kind == .Align_Of else target
|
||||
_, distinct_ok := types.distinct_scalar_backing(result_type, &checker.module.types)
|
||||
return build_constant_expr(
|
||||
checker,
|
||||
expr,
|
||||
Constant{kind=.Value, value=type_builtin_value(checker, kind, target)},
|
||||
result_type,
|
||||
distinct_ok,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4363,7 +4377,9 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
||||
(tag == "array" && !types.equal(field.type, array_info)) ||
|
||||
(tag == "record" && !types.equal(field.type, record_info)) ||
|
||||
(tag == "enum" && !types.equal(field.type, enum_info)) ||
|
||||
(tag != "array" && tag != "record" && tag != "enum" && !types.is_void(field.type)) {
|
||||
(tag == "distinct" && !is_type_metatype_syntax(checker, field.type)) ||
|
||||
(tag != "array" && tag != "record" && tag != "enum" && tag != "distinct" &&
|
||||
!types.is_void(field.type)) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
@@ -4704,6 +4720,43 @@ mark_spec_demanded :: proc(checker: ^Checker, id: Spec_Id, stack: ^[dynamic]Spec
|
||||
append(stack, id)
|
||||
}
|
||||
|
||||
Numeric_Operation_Type :: struct {
|
||||
result: types.Type,
|
||||
representation: types.Type,
|
||||
}
|
||||
|
||||
numeric_operation_type :: proc(checker: ^Checker, left, right: types.Type) -> (Numeric_Operation_Type, bool) {
|
||||
if types.equal(left, right) {
|
||||
if representation, ok := types.distinct_scalar_backing(left, &checker.module.types); ok {
|
||||
return Numeric_Operation_Type{result=left, representation=representation}, true
|
||||
}
|
||||
}
|
||||
result := types.widest(left, right)
|
||||
if !types.is_concrete_scalar(result) {
|
||||
return {}, false
|
||||
}
|
||||
return Numeric_Operation_Type{result=result, representation=result}, true
|
||||
}
|
||||
|
||||
numeric_literal_accepts_type :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr_Id,
|
||||
demand: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> bool {
|
||||
representation := demand
|
||||
if backing, ok := types.distinct_scalar_backing(demand, &checker.module.types); ok {
|
||||
representation = backing
|
||||
}
|
||||
if constant := eval_integer_constant_in_context(checker, expr, pkg, file); constant.kind == .Value {
|
||||
return types.is_float(representation, checker.target) ||
|
||||
types.is_concrete_integer(representation) &&
|
||||
fits_integer_type(constant.value, representation, checker.target)
|
||||
}
|
||||
return is_float_constant_expr(checker, expr) && types.is_float(representation, checker.target)
|
||||
}
|
||||
|
||||
Infer_Frame :: struct {
|
||||
expr: ast.Expr_Id,
|
||||
expected: types.Type,
|
||||
@@ -4715,6 +4768,8 @@ Infer_Frame :: struct {
|
||||
mapping: []int,
|
||||
args: []types.Type,
|
||||
template: ast.Function_Id,
|
||||
numeric_operation: bool,
|
||||
reverse_operands: bool,
|
||||
}
|
||||
|
||||
merge_inferred_test_error :: proc(checker: ^Checker, incoming: types.Type) {
|
||||
@@ -4772,8 +4827,16 @@ infer_division_builtin :: proc(
|
||||
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
|
||||
if left_const && types.is_distinct(right, &checker.module.types) &&
|
||||
numeric_literal_accepts_type(checker, expr.args[0], right, pkg, file) {
|
||||
left = right
|
||||
}
|
||||
if right_const && types.is_distinct(left, &checker.module.types) &&
|
||||
numeric_literal_accepts_type(checker, expr.args[1], left, pkg, file) {
|
||||
right = left
|
||||
}
|
||||
operation, ok := numeric_operation_type(checker, left, right)
|
||||
return operation.result if ok && !types.is_bool(operation.representation) else types.INVALID
|
||||
}
|
||||
|
||||
infer_compound_expr :: proc(
|
||||
@@ -4796,28 +4859,38 @@ infer_compound_expr :: proc(
|
||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
return types.BOOL
|
||||
case .Bit_Not:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
return operand if types.is_concrete_integer(operand) else types.INVALID
|
||||
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
representation := types.runtime_representation(operand, store)
|
||||
return operand if types.is_concrete_integer(representation) else types.INVALID
|
||||
case .Bit_And, .Bit_Or, .Bit_Xor:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
left, right := types.INVALID, types.INVALID
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
if types.is_concrete_integer(expected) {
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, expected)
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, expected)
|
||||
} else if left_const && !right_const {
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
} else {
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, hint if types.is_valid(hint) else left)
|
||||
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
|
||||
}
|
||||
result := types.widest(left, right)
|
||||
return result if types.is_concrete_integer(result) else types.INVALID
|
||||
if left_const && types.is_distinct(right, store) &&
|
||||
numeric_literal_accepts_type(checker, expr.left, right, pkg, file) {
|
||||
left = right
|
||||
}
|
||||
if right_const && types.is_distinct(left, store) &&
|
||||
numeric_literal_accepts_type(checker, expr.right, left, pkg, file) {
|
||||
right = left
|
||||
}
|
||||
operation, ok := numeric_operation_type(checker, left, right)
|
||||
return operation.result if ok && types.is_concrete_integer(operation.representation) else types.INVALID
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
|
||||
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
|
||||
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
|
||||
representation := types.runtime_representation(left, store)
|
||||
return left if types.is_concrete_integer(representation) && types.is_unsigned(right, checker.target) else types.INVALID
|
||||
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||
left_expr := checker.ast_module.exprs[expr.left]
|
||||
right_expr := checker.ast_module.exprs[expr.right]
|
||||
@@ -5135,6 +5208,16 @@ infer_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if constant.kind == .Value {
|
||||
if frame.numeric_operation {
|
||||
if representation, ok := types.distinct_scalar_backing(frame.expected, &checker.module.types);
|
||||
ok && (types.is_float(representation, checker.target) ||
|
||||
types.is_concrete_integer(representation) &&
|
||||
fits_integer_type(constant.value, representation, checker.target)) {
|
||||
last = frame.expected
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
}
|
||||
last = constraint_integer_literal_type(frame.expected, constant.value)
|
||||
if !types.is_valid(last) {
|
||||
last = types.I64
|
||||
@@ -5162,7 +5245,15 @@ infer_expr :: proc(
|
||||
}
|
||||
_ = pop(&stack)
|
||||
case .Float:
|
||||
last = types.F64
|
||||
if frame.numeric_operation {
|
||||
if representation, ok := types.distinct_scalar_backing(frame.expected, &checker.module.types);
|
||||
ok && types.is_float(representation, checker.target) {
|
||||
last = frame.expected
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
}
|
||||
last = frame.expected if types.is_float(frame.expected, checker.target) else types.F64
|
||||
_ = pop(&stack)
|
||||
case .String, .Array, .Null, .Unreachable, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
||||
@@ -5294,7 +5385,17 @@ infer_expr :: proc(
|
||||
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
||||
case .Add, .Sub, .Mul, .Div:
|
||||
stack[frame_index].stage = 1
|
||||
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
stack[frame_index].reverse_operands = left_const && !right_const
|
||||
first := expr.right if stack[frame_index].reverse_operands else expr.left
|
||||
first_expected := frame.expected if
|
||||
types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) else types.INVALID
|
||||
append(&stack, Infer_Frame{
|
||||
expr=first,
|
||||
expected=first_expected,
|
||||
template=ast.INVALID_FUNCTION,
|
||||
})
|
||||
case .Call:
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||
@@ -5567,38 +5668,54 @@ infer_expr :: proc(
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].left = last
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION})
|
||||
second := expr.left if frame.reverse_operands else expr.right
|
||||
second_expected := types.INVALID
|
||||
numeric_operation := false
|
||||
if is_numeric_constant_expr(checker, second) {
|
||||
second_expected = last
|
||||
if representation, ok := types.distinct_scalar_backing(last, &checker.module.types);
|
||||
ok && !types.is_bool(representation) {
|
||||
numeric_operation = true
|
||||
}
|
||||
}
|
||||
append(&stack, Infer_Frame{
|
||||
expr=second,
|
||||
expected=second_expected,
|
||||
template=ast.INVALID_FUNCTION,
|
||||
numeric_operation=numeric_operation,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if frame.stage == 2 {
|
||||
right := last
|
||||
if expr.kind == .Add && types.is_many_pointer(frame.left, &checker.module.types) && types.is_concrete_integer(right) {
|
||||
last = frame.left
|
||||
first, second := frame.left, last
|
||||
left, right := first, second
|
||||
if frame.reverse_operands {
|
||||
left, right = second, first
|
||||
}
|
||||
if expr.kind == .Add && types.is_many_pointer(left, &checker.module.types) && types.is_concrete_integer(right) {
|
||||
last = left
|
||||
} else if operation, ok := numeric_operation_type(checker, left, right); ok {
|
||||
last = operation.result
|
||||
} else if is_numeric_constant_expr(checker, expr.right) &&
|
||||
is_numeric_demand(frame.left, checker.target) &&
|
||||
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
|
||||
last = frame.left
|
||||
is_numeric_demand(left, checker.target) &&
|
||||
expr_accepts_numeric_demand(checker, expr.right, left, locals, pkg, file) {
|
||||
last = left
|
||||
} else if is_numeric_constant_expr(checker, expr.left) &&
|
||||
is_numeric_demand(right, checker.target) &&
|
||||
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
|
||||
last = right
|
||||
} else if is_numeric_demand(frame.left, checker.target) &&
|
||||
} else if is_numeric_demand(left, checker.target) &&
|
||||
!numeric_operand_is_open(checker, expr.left, locals, pkg, file) &&
|
||||
expr_accepts_numeric_demand(checker, expr.right, frame.left, locals, pkg, file) {
|
||||
// Propagate only from an authoritative (fixed-type) left operand. A left
|
||||
// operand that is still a provisional open constant carries only its
|
||||
// smallest-signed default, which must not poison the sibling's family;
|
||||
// two provisional operands are resolved together by the backward demand
|
||||
// from the declaration/use.
|
||||
_ = record_demand(checker, expr.right, frame.left, locals, local_types, pkg, file)
|
||||
last = frame.left
|
||||
expr_accepts_numeric_demand(checker, expr.right, left, locals, pkg, file) {
|
||||
_ = record_demand(checker, expr.right, left, locals, local_types, pkg, file)
|
||||
last = left
|
||||
} else if is_numeric_demand(right, checker.target) &&
|
||||
!numeric_operand_is_open(checker, expr.right, locals, pkg, file) &&
|
||||
expr_accepts_numeric_demand(checker, expr.left, right, locals, pkg, file) {
|
||||
_ = record_demand(checker, expr.left, right, locals, local_types, pkg, file)
|
||||
last = right
|
||||
} else {
|
||||
last = types.widest(frame.left, right)
|
||||
last = types.INVALID
|
||||
}
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
@@ -7214,23 +7331,33 @@ build_constant_expr :: proc(
|
||||
expr: ast.Expr,
|
||||
constant: Constant,
|
||||
expected: types.Type,
|
||||
numeric_operation := false,
|
||||
) -> hir.Expr_Id {
|
||||
materialized := expected
|
||||
nominal := types.INVALID
|
||||
if numeric_operation {
|
||||
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
|
||||
ok && !types.is_bool(representation) {
|
||||
materialized = representation
|
||||
nominal = expected
|
||||
}
|
||||
}
|
||||
recovery_type := types.I64
|
||||
if types.is_concrete_integer(expected) {
|
||||
if types.is_concrete_integer(materialized) {
|
||||
recovery_type = expected
|
||||
}
|
||||
// An integer constant in a float context (e.g. `pi float = 3`) folds to a
|
||||
// float literal, mirroring build_float_expr's bit packing.
|
||||
if constant.kind == .Value && types.is_float(expected, checker.target) {
|
||||
if constant.kind == .Value && types.is_float(materialized, checker.target) {
|
||||
fval := f64(constant.value) // ponytail: silent precision loss past 2^53, like C int->double
|
||||
bits := transmute(i64)fval
|
||||
if types.bits(expected, checker.target) == 32 {
|
||||
if types.bits(materialized, checker.target) == 32 {
|
||||
bits = i64(transmute(u32)f32(fval))
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind = .Float,
|
||||
span = expr.span,
|
||||
type = expected,
|
||||
type = nominal if types.is_valid(nominal) else materialized,
|
||||
integer = bits,
|
||||
target = hir.INVALID_REF,
|
||||
left = hir.INVALID_EXPR,
|
||||
@@ -7251,7 +7378,7 @@ build_constant_expr :: proc(
|
||||
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
||||
}
|
||||
if constant.kind == .Overflow ||
|
||||
(!types.is_concrete_integer(expected) && expected != types.UINT && !fits_i64(constant.value)) {
|
||||
(!types.is_concrete_integer(materialized) && materialized != types.UINT && !fits_i64(constant.value)) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -7264,8 +7391,8 @@ build_constant_expr :: proc(
|
||||
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
|
||||
value = transmute(i64)u64(constant.value)
|
||||
}
|
||||
result_type := constraint_integer_literal_type(expected, constant.value)
|
||||
if expected == types.UINT && !types.is_valid(result_type) {
|
||||
result_type := constraint_integer_literal_type(materialized, constant.value)
|
||||
if materialized == types.UINT && !types.is_valid(result_type) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -7274,8 +7401,8 @@ build_constant_expr :: proc(
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, types.U64)
|
||||
}
|
||||
if types.is_concrete_integer(expected) {
|
||||
if !fits_integer_type(constant.value, expected, checker.target) {
|
||||
if types.is_concrete_integer(materialized) {
|
||||
if !fits_integer_type(constant.value, materialized, checker.target) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -7285,7 +7412,7 @@ build_constant_expr :: proc(
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, expected)
|
||||
}
|
||||
result_type = expected
|
||||
result_type = nominal if types.is_valid(nominal) else materialized
|
||||
}
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
@@ -7302,11 +7429,26 @@ build_constant_expr :: proc(
|
||||
)
|
||||
}
|
||||
|
||||
build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type) -> hir.Expr_Id {
|
||||
build_float_expr :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
expected: types.Type,
|
||||
numeric_operation := false,
|
||||
) -> hir.Expr_Id {
|
||||
materialized := expected
|
||||
result_type := types.F64
|
||||
if types.is_float(expected, checker.target) {
|
||||
result_type = expected
|
||||
} else if types.is_valid(expected) {
|
||||
if numeric_operation {
|
||||
if representation, ok := types.distinct_scalar_backing(expected, &checker.module.types);
|
||||
ok && types.is_float(representation, checker.target) {
|
||||
materialized = representation
|
||||
result_type = expected
|
||||
}
|
||||
}
|
||||
if types.is_float(materialized, checker.target) {
|
||||
if !types.is_valid(result_type) || !types.is_distinct(result_type, &checker.module.types) {
|
||||
result_type = materialized
|
||||
}
|
||||
} else if types.is_valid(materialized) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -7317,7 +7459,7 @@ build_float_expr :: proc(checker: ^Checker, expr: ast.Expr, expected: types.Type
|
||||
}
|
||||
value := transmute(f64)expr.integer
|
||||
bits := transmute(i64)value
|
||||
if types.bits(result_type, checker.target) == 32 {
|
||||
if types.bits(materialized, checker.target) == 32 {
|
||||
bits = i64(transmute(u32)f32(value))
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
@@ -7346,6 +7488,8 @@ Build_Expr_Frame :: struct {
|
||||
arg_types: []types.Type,
|
||||
template: ast.Function_Id,
|
||||
resolution: int,
|
||||
numeric_operation: bool,
|
||||
reverse_operands: bool,
|
||||
}
|
||||
|
||||
hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: []Build_Local) -> bool {
|
||||
@@ -7731,11 +7875,23 @@ build_scalar_cast :: proc(
|
||||
) -> hir.Expr_Id {
|
||||
store := &checker.module.types
|
||||
actual := checker.module.exprs[value].type
|
||||
if types.can_retype_distinct(actual, target, store) {
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Retype,
|
||||
span=span,
|
||||
type=target,
|
||||
left=value,
|
||||
target=hir.INVALID_REF,
|
||||
right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
valid_target := types.is_concrete_scalar(target) && !types.is_bool(target)
|
||||
actual_repr := types.runtime_representation(actual, store)
|
||||
actual_item, actual_item_ok := types.node(store, actual)
|
||||
explicit_enum := actual_item_ok && actual_item.kind == .Enum && actual_item.explicit_backing
|
||||
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum) &&
|
||||
_, distinct_scalar := types.distinct_scalar_backing(actual, store)
|
||||
valid_actual := (types.is_concrete_scalar(actual) || explicit_enum || distinct_scalar) &&
|
||||
types.is_concrete_scalar(actual_repr) && !types.is_bool(actual_repr)
|
||||
if !valid_target || !valid_actual {
|
||||
id := source.addf(
|
||||
@@ -7757,6 +7913,38 @@ build_scalar_cast :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
normalize_index_expr :: proc(checker: ^Checker, value: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
|
||||
store := &checker.module.types
|
||||
current := value
|
||||
current_type := checker.module.exprs[current].type
|
||||
representation, distinct_ok := types.distinct_scalar_backing(current_type, store)
|
||||
if distinct_ok && types.is_concrete_integer(representation) {
|
||||
for {
|
||||
backing, ok := types.distinct_backing(current_type, store)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
current = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Retype,
|
||||
span=span,
|
||||
type=backing,
|
||||
left=current,
|
||||
target=hir.INVALID_REF,
|
||||
right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
current_type = backing
|
||||
}
|
||||
}
|
||||
current_expr := &checker.module.exprs[current]
|
||||
if current_expr.kind == .Integer &&
|
||||
fits_integer_type(i128(current_expr.integer), types.USIZE, checker.target) {
|
||||
current_expr.type = types.USIZE
|
||||
return current
|
||||
}
|
||||
return coerce_expr(checker, current, types.USIZE, span)
|
||||
}
|
||||
|
||||
|
||||
build_function_value :: proc(
|
||||
checker: ^Checker,
|
||||
@@ -7836,11 +8024,12 @@ build_nested_expr :: proc(
|
||||
expected: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
numeric_operation := false,
|
||||
) -> hir.Expr_Id {
|
||||
outer := checker.build_stack
|
||||
checker.build_stack = nil
|
||||
checker.build_stack.allocator = checker.allocator
|
||||
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file)
|
||||
result := build_expr(checker, expr_id, locals, global_reads, calls, expected, pkg, file, numeric_operation)
|
||||
delete(checker.build_stack)
|
||||
checker.build_stack = outer
|
||||
return result
|
||||
@@ -7901,19 +8090,29 @@ build_division_builtin :: proc(
|
||||
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)
|
||||
right_type := checker.module.exprs[right].type
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_type, &checker.module.types)
|
||||
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, right_type, pkg, file, distinct_ok)
|
||||
} 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)
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_hint, &checker.module.types)
|
||||
right = build_nested_expr(
|
||||
checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file,
|
||||
distinct_ok && right_const,
|
||||
)
|
||||
}
|
||||
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_concrete_scalar(result) || types.is_bool(result) {
|
||||
operation, ok := numeric_operation_type(
|
||||
checker,
|
||||
checker.module.exprs[left].type,
|
||||
checker.module.exprs[right].type,
|
||||
)
|
||||
if !ok || types.is_bool(operation.representation) {
|
||||
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)
|
||||
left = coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
|
||||
result_kind := hir.Expr_Kind.Div_Trunc
|
||||
#partial switch kind {
|
||||
case .Floor: result_kind = .Div_Floor
|
||||
@@ -7924,7 +8123,7 @@ build_division_builtin :: proc(
|
||||
case:
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=result_kind, span=expr.span, type=result, left=left, right=right,
|
||||
kind=result_kind, span=expr.span, type=operation.result, left=left, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
@@ -8303,8 +8502,8 @@ build_compound_expr :: proc(
|
||||
})
|
||||
case .Index:
|
||||
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 = coerce_expr(checker, index, types.USIZE, expr.span)
|
||||
index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
index = normalize_index_expr(checker, index, expr.span)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
|
||||
return invalid
|
||||
}
|
||||
@@ -8347,8 +8546,8 @@ build_compound_expr :: proc(
|
||||
bounds[1] = hir.INVALID_EXPR
|
||||
for bound, index in expr.args {
|
||||
if bound != ast.INVALID_EXPR {
|
||||
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)
|
||||
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
bounds[index] = normalize_index_expr(checker, bounds[index], checker.ast_module.exprs[bound].span)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
|
||||
delete(bounds, checker.allocator)
|
||||
return invalid
|
||||
@@ -8578,13 +8777,13 @@ build_compound_expr :: proc(
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Bit_Not:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
||||
return invalid
|
||||
}
|
||||
operand_type := checker.module.exprs[operand].type
|
||||
if !types.is_concrete_integer(operand_type) {
|
||||
representation := types.runtime_representation(operand_type, store)
|
||||
if !types.is_concrete_integer(representation) {
|
||||
id := source.add(checker.diagnostics, expr.span, "'~' requires a concrete integer operand")
|
||||
return invalid_hir_expr(checker, expr.span, id, operand_type)
|
||||
}
|
||||
@@ -8593,47 +8792,59 @@ build_compound_expr :: proc(
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Bit_And, .Bit_Or, .Bit_Xor:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
left, right: hir.Expr_Id
|
||||
if left_const && !right_const && !types.is_valid(hint) {
|
||||
if types.is_concrete_integer(expected) {
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, expected, pkg, file)
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, expected, pkg, file)
|
||||
} else if left_const && !right_const {
|
||||
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)
|
||||
right_type := checker.module.exprs[right].type
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_type, store)
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, right_type, pkg, file, distinct_ok)
|
||||
} else {
|
||||
left = build_nested_expr(checker, expr.left, 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.right, locals, global_reads, calls, right_hint, pkg, file)
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
right_hint := checker.module.exprs[left].type
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_hint, store)
|
||||
right = build_nested_expr(
|
||||
checker, expr.right, locals, global_reads, calls, right_hint, pkg, file,
|
||||
distinct_ok && right_const,
|
||||
)
|
||||
}
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
result_type := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
operation, ok := numeric_operation_type(
|
||||
checker,
|
||||
checker.module.exprs[left].type,
|
||||
checker.module.exprs[right].type,
|
||||
)
|
||||
if !ok || !types.is_concrete_integer(operation.representation) {
|
||||
id := source.add(checker.diagnostics, expr.span, "bitwise operation requires compatible concrete integer operands")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
left = coerce_expr(checker, left, result_type, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result_type, checker.module.exprs[right].span)
|
||||
left = coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
|
||||
kind := hir.Expr_Kind.Bit_And
|
||||
#partial switch expr.kind {
|
||||
case .Bit_Or: kind = .Bit_Or
|
||||
case .Bit_Xor: kind = .Bit_Xor
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=kind, span=expr.span, type=result_type, left=left, right=right,
|
||||
kind=kind, span=expr.span, type=operation.result, left=left, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
hint := expected if types.is_concrete_integer(expected) else types.INVALID
|
||||
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, types.INVALID, pkg, file)
|
||||
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.U64, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
left_type := checker.module.exprs[left].type
|
||||
right_type := checker.module.exprs[right].type
|
||||
if !types.is_concrete_integer(left_type) {
|
||||
left_representation := types.runtime_representation(left_type, store)
|
||||
if !types.is_concrete_integer(left_representation) {
|
||||
id := source.add(checker.diagnostics, checker.module.exprs[left].span, "shifted value must be a concrete integer")
|
||||
return invalid_hir_expr(checker, expr.span, id, left_type)
|
||||
}
|
||||
@@ -8643,7 +8854,7 @@ build_compound_expr :: proc(
|
||||
}
|
||||
if constant := eval_integer_constant_in_context(checker, expr.right, pkg, file);
|
||||
constant.kind == .Value && expr.kind != .Shift_Left_Saturating &&
|
||||
constant.value >= i128(types.bits(left_type, checker.target)) {
|
||||
constant.value >= i128(types.bits(left_representation, checker.target)) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, checker.module.exprs[right].span,
|
||||
"shift count %d exceeds %s width", constant.value, types.name(left_type),
|
||||
@@ -8703,11 +8914,13 @@ build_compound_expr :: proc(
|
||||
} else if right_numeric_const && !left_numeric_const {
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
hint := checker.module.exprs[left].type
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
|
||||
_, distinct_ok := types.distinct_scalar_backing(hint, store)
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file, distinct_ok)
|
||||
} else if left_numeric_const && !right_numeric_const {
|
||||
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
hint := checker.module.exprs[right].type
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||
_, distinct_ok := types.distinct_scalar_backing(hint, store)
|
||||
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file, distinct_ok)
|
||||
} else {
|
||||
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, types.INVALID, pkg, file)
|
||||
@@ -8738,11 +8951,13 @@ build_compound_expr :: proc(
|
||||
}
|
||||
operand_type = types.BOOL
|
||||
} else {
|
||||
operand_type = types.widest(left_type, right_type)
|
||||
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
|
||||
operation, ok := numeric_operation_type(checker, left_type, right_type)
|
||||
if !ok ||
|
||||
types.is_bool(operation.representation) && expr.kind != .Eq && expr.kind != .Ne {
|
||||
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
|
||||
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||
}
|
||||
operand_type = operation.result
|
||||
}
|
||||
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
|
||||
@@ -8978,17 +9193,21 @@ build_binary_arith :: proc(
|
||||
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
if !types.is_concrete_scalar(result) {
|
||||
operation, ok := numeric_operation_type(
|
||||
checker,
|
||||
checker.module.exprs[left].type,
|
||||
checker.module.exprs[right].type,
|
||||
)
|
||||
if !ok || types.is_bool(operation.representation) {
|
||||
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) {
|
||||
if op == .Div && !types.is_float(operation.representation, checker.target) {
|
||||
id := source.add(
|
||||
checker.diagnostics, span,
|
||||
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
return invalid_hir_expr(checker, span, id, result)
|
||||
return invalid_hir_expr(checker, span, id, operation.result)
|
||||
}
|
||||
result_kind := hir.Expr_Kind.Add
|
||||
#partial switch op {
|
||||
@@ -8996,10 +9215,10 @@ build_binary_arith :: proc(
|
||||
case .Mul: result_kind = .Mul
|
||||
case .Div: result_kind = .Div
|
||||
}
|
||||
coerced_left := coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||
coerced_right := coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||
coerced_left := coerce_expr(checker, left, operation.result, checker.module.exprs[left].span)
|
||||
coerced_right := coerce_expr(checker, right, operation.result, checker.module.exprs[right].span)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=result_kind, span=span, type=result, left=coerced_left, right=coerced_right,
|
||||
kind=result_kind, span=span, type=operation.result, left=coerced_left, right=coerced_right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
@@ -9013,6 +9232,7 @@ build_expr :: proc(
|
||||
expected := types.INVALID,
|
||||
pkg := ast.Package_Id(0),
|
||||
file := ast.File_Id(0),
|
||||
numeric_operation := false,
|
||||
) -> hir.Expr_Id {
|
||||
stack := checker.build_stack
|
||||
checker.build_stack = nil
|
||||
@@ -9030,7 +9250,12 @@ build_expr :: proc(
|
||||
delete(stack)
|
||||
}
|
||||
}
|
||||
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
|
||||
append(&stack, Build_Expr_Frame{
|
||||
expr=expr_id,
|
||||
expected=expected,
|
||||
template=ast.INVALID_FUNCTION,
|
||||
numeric_operation=numeric_operation,
|
||||
})
|
||||
last := hir.INVALID_EXPR
|
||||
|
||||
for len(stack) > 0 {
|
||||
@@ -9067,7 +9292,7 @@ build_expr :: proc(
|
||||
constant = eval_constant(checker, frame.expr)
|
||||
}
|
||||
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)
|
||||
last = build_constant_expr(checker, expr, constant, frame.expected, frame.numeric_operation)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
@@ -9093,7 +9318,7 @@ build_expr :: proc(
|
||||
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||
_ = pop(&stack)
|
||||
case .Float:
|
||||
last = build_float_expr(checker, expr, frame.expected)
|
||||
last = build_float_expr(checker, expr, frame.expected, frame.numeric_operation)
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
last = hir.INVALID_EXPR
|
||||
@@ -9269,13 +9494,20 @@ build_expr :: proc(
|
||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
|
||||
case .Add, .Sub, .Mul, .Div:
|
||||
stack[frame_index].stage = 1
|
||||
// Preserve assignment/return context for literal operands, e.g.
|
||||
// assigning `i + 1` back into a `u32` local.
|
||||
left_expected := types.INVALID
|
||||
if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
||||
left_expected = frame.expected
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
stack[frame_index].reverse_operands = left_const && !right_const
|
||||
first := expr.right if stack[frame_index].reverse_operands else expr.left
|
||||
first_expected := types.INVALID
|
||||
if !stack[frame_index].reverse_operands &&
|
||||
types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
||||
first_expected = frame.expected
|
||||
}
|
||||
append(&stack, Build_Expr_Frame{expr=expr.left, expected=left_expected, template=ast.INVALID_FUNCTION})
|
||||
append(&stack, Build_Expr_Frame{
|
||||
expr=first,
|
||||
expected=first_expected,
|
||||
template=ast.INVALID_FUNCTION,
|
||||
})
|
||||
case .Call:
|
||||
if expr.left != ast.INVALID_EXPR {
|
||||
stack[frame_index].stage = 6
|
||||
@@ -9481,7 +9713,7 @@ build_expr :: proc(
|
||||
constructor_type := types.resolve_alias(named_type, &checker.module.types)
|
||||
constructor_item, constructor_ok := types.node(&checker.module.types, constructor_type)
|
||||
scalar_alias := named_ok && named_item.kind == .Alias &&
|
||||
types.is_concrete_scalar(constructor_type) && !types.is_bool(constructor_type)
|
||||
types.is_concrete_scalar(constructor_type)
|
||||
if scalar_alias {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(
|
||||
@@ -9681,7 +9913,8 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
operand_type := checker.module.exprs[operand].type
|
||||
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
|
||||
representation := types.runtime_representation(operand_type, &checker.module.types)
|
||||
if !types.is_signed(representation, checker.target) && !types.is_float(representation, checker.target) {
|
||||
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
@@ -9689,7 +9922,10 @@ build_expr :: proc(
|
||||
kind=.Negate, span=expr.span, type=operand_type, left=operand,
|
||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
if types.is_signed(frame.expected, checker.target) || types.is_float(frame.expected, checker.target) {
|
||||
expected_representation := types.runtime_representation(frame.expected, &checker.module.types)
|
||||
if types.equal(operand_type, frame.expected) &&
|
||||
(types.is_signed(expected_representation, checker.target) ||
|
||||
types.is_float(expected_representation, checker.target)) {
|
||||
last = coerce_expr(checker, last, frame.expected, expr.span)
|
||||
}
|
||||
}
|
||||
@@ -9699,21 +9935,36 @@ build_expr :: proc(
|
||||
if frame.stage == 1 {
|
||||
stack[frame_index].left = last
|
||||
stack[frame_index].stage = 2
|
||||
right_expected := types.INVALID
|
||||
if types.is_many_pointer(checker.module.exprs[last].type, &checker.module.types) {
|
||||
right_expected = types.USIZE
|
||||
} else if eval_constant(checker, expr.right).kind == .Value {
|
||||
// A constant RHS adopts the concrete LHS type before numeric
|
||||
// compatibility is checked.
|
||||
right_expected = checker.module.exprs[last].type
|
||||
first_type := checker.module.exprs[last].type
|
||||
second := expr.left if frame.reverse_operands else expr.right
|
||||
second_expected := types.INVALID
|
||||
numeric_operation := false
|
||||
if types.is_many_pointer(first_type, &checker.module.types) {
|
||||
second_expected = types.USIZE
|
||||
} else if is_numeric_constant_expr(checker, second) {
|
||||
second_expected = first_type
|
||||
if representation, ok := types.distinct_scalar_backing(first_type, &checker.module.types);
|
||||
ok && !types.is_bool(representation) {
|
||||
numeric_operation = true
|
||||
}
|
||||
} else if types.is_concrete_scalar(frame.expected) && !types.is_bool(frame.expected) {
|
||||
right_expected = frame.expected
|
||||
second_expected = frame.expected
|
||||
}
|
||||
append(&stack, Build_Expr_Frame{expr=expr.right, expected=right_expected, template=ast.INVALID_FUNCTION})
|
||||
append(&stack, Build_Expr_Frame{
|
||||
expr=second,
|
||||
expected=second_expected,
|
||||
template=ast.INVALID_FUNCTION,
|
||||
numeric_operation=numeric_operation,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if frame.stage == 2 {
|
||||
last = build_binary_arith(checker, expr.kind, frame.left, last, expr.span)
|
||||
first, second := frame.left, last
|
||||
left, right := first, second
|
||||
if frame.reverse_operands {
|
||||
left, right = second, first
|
||||
}
|
||||
last = build_binary_arith(checker, expr.kind, left, right, expr.span)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
@@ -10062,7 +10313,8 @@ build_expr :: proc(
|
||||
if frame.stage == 8 {
|
||||
distinct_item, ok := types.node(&checker.module.types, frame.target_type)
|
||||
actual := checker.module.exprs[last].type
|
||||
if !ok || distinct_item.kind != .Distinct || !types.equal(actual, distinct_item.child) {
|
||||
if !ok || distinct_item.kind != .Distinct ||
|
||||
!types.can_retype_distinct(actual, frame.target_type, &checker.module.types) {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
@@ -10892,9 +11144,16 @@ build_block :: proc(
|
||||
statement.assignment_op == .Shift_Left_Saturating {
|
||||
rhs_expected = types.U64
|
||||
}
|
||||
rhs_numeric_operation := false
|
||||
if statement.assignment_op != .Shift_Left &&
|
||||
statement.assignment_op != .Shift_Right &&
|
||||
statement.assignment_op != .Shift_Left_Saturating &&
|
||||
is_numeric_constant_expr(checker, statement.expr) {
|
||||
_, rhs_numeric_operation = types.distinct_scalar_backing(target_type, &checker.module.types)
|
||||
}
|
||||
value = build_expr(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
rhs_expected, ctx.pkg, ctx.file,
|
||||
rhs_expected, ctx.pkg, ctx.file, rhs_numeric_operation,
|
||||
)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
@@ -10937,14 +11196,15 @@ build_block :: proc(
|
||||
is_bitwise := statement.assignment_op == .Bit_And ||
|
||||
statement.assignment_op == .Bit_Or ||
|
||||
statement.assignment_op == .Bit_Xor
|
||||
result_type := types.widest(target_type, rhs_type)
|
||||
operation, compatible := numeric_operation_type(checker, target_type, rhs_type)
|
||||
target_representation := types.runtime_representation(target_type, &checker.module.types)
|
||||
if is_shift {
|
||||
if !types.is_concrete_integer(target_type) || !types.is_unsigned(rhs_type, checker.target) {
|
||||
if !types.is_concrete_integer(target_representation) || !types.is_unsigned(rhs_type, checker.target) {
|
||||
id := source.add(checker.diagnostics, statement.span, "shift assignment requires an integer target and unsigned integer count")
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
} else if constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file);
|
||||
constant.kind == .Value && statement.assignment_op != .Shift_Left_Saturating &&
|
||||
constant.value >= i128(types.bits(target_type, checker.target)) {
|
||||
constant.value >= i128(types.bits(target_representation, checker.target)) {
|
||||
id := source.addf(
|
||||
checker.diagnostics, statement.span,
|
||||
"shift count %d exceeds %s width", constant.value, types.name(target_type),
|
||||
@@ -10952,21 +11212,21 @@ build_block :: proc(
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
}
|
||||
} else if is_bitwise {
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
if !compatible || !types.is_concrete_integer(operation.representation) {
|
||||
id := source.add(checker.diagnostics, statement.span, "bitwise assignment requires compatible concrete integer operands")
|
||||
value = invalid_hir_expr(checker, statement.span, id, target_type)
|
||||
} else {
|
||||
value = coerce_expr(checker, value, target_type, statement.span)
|
||||
}
|
||||
} else if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
|
||||
} else if statement.assignment_op == .Div &&
|
||||
compatible && types.is_concrete_integer(operation.representation) {
|
||||
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) {
|
||||
} else if !compatible || types.is_bool(operation.representation) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
statement.span,
|
||||
|
||||
+263
-93
@@ -10,7 +10,6 @@ import "base:intrinsics"
|
||||
import "core:fmt"
|
||||
import "core:hash"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
|
||||
COMPTIME_EVAL_QUOTA :: 100_000
|
||||
@@ -79,6 +78,13 @@ current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type
|
||||
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
|
||||
return value.type, true
|
||||
}
|
||||
if binding, ok := current_static_binding(checker, name);
|
||||
ok && binding.value != INVALID_CT_VALUE && int(binding.value) < len(checker.static_state.values) {
|
||||
value := checker.static_state.values[binding.value]
|
||||
if value.kind == .Type {
|
||||
return types.Type(value.index), true
|
||||
}
|
||||
}
|
||||
return types.INVALID, false
|
||||
}
|
||||
|
||||
@@ -1089,16 +1095,21 @@ ct_materialize_value :: proc(
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return build_constant_expr(checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer}, value.type)
|
||||
_, distinct_ok := types.distinct_scalar_backing(value.type, &checker.module.types)
|
||||
return build_constant_expr(
|
||||
checker, ast.Expr{span=span}, Constant{kind=.Value, value=value.integer},
|
||||
value.type, distinct_ok,
|
||||
)
|
||||
case .Bool:
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Bool, span=span, type=types.BOOL, integer=i64(value.integer),
|
||||
kind=.Bool, span=span, type=value.type, integer=i64(value.integer),
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Float:
|
||||
bits := transmute(i64)value.float
|
||||
if types.bits(value.type, checker.target) == 32 {
|
||||
representation := types.runtime_representation(value.type, &checker.module.types)
|
||||
if types.bits(representation, checker.target) == 32 {
|
||||
bits = i64(transmute(u32)f32(value.float))
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
@@ -1251,6 +1262,7 @@ ct_eval_expr :: proc(
|
||||
expr_id: ast.Expr_Id,
|
||||
expected := types.INVALID,
|
||||
depth := 0,
|
||||
numeric_operation := false,
|
||||
) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||
checker := state.checker
|
||||
if depth > 128 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
|
||||
@@ -1263,6 +1275,22 @@ ct_eval_expr :: proc(
|
||||
store := &checker.module.types
|
||||
#partial switch expr.kind {
|
||||
case .Integer:
|
||||
if numeric_operation {
|
||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store); distinct_ok {
|
||||
if types.is_float(representation, checker.target) {
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Float, type=expected, float=f64(expr.integer),
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
value := i128(expr.integer)
|
||||
if types.is_concrete_integer(representation) &&
|
||||
fits_integer_type(value, representation, checker.target) {
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Integer, type=expected, integer=value,
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
}
|
||||
value_type := expected if types.is_concrete_integer(expected) || types.is_enum(expected, store) else ct_default_integer_type(i128(expr.integer))
|
||||
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value_type, integer=i128(expr.integer)})
|
||||
if types.is_valid(expected) {
|
||||
@@ -1272,6 +1300,14 @@ ct_eval_expr :: proc(
|
||||
case .Bool:
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=i128(expr.integer)}), ct_flow(.Normal), true
|
||||
case .Float:
|
||||
if numeric_operation {
|
||||
if representation, distinct_ok := types.distinct_scalar_backing(expected, store);
|
||||
distinct_ok && types.is_float(representation, checker.target) {
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Float, type=expected, float=transmute(f64)expr.integer,
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
}
|
||||
value_type := expected if types.is_float(expected, checker.target) else types.F64
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type, float=transmute(f64)expr.integer}), ct_flow(.Normal), true
|
||||
case .String:
|
||||
@@ -1416,10 +1452,17 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_field_value(state, base_id, expr.name, expr.span)
|
||||
case .Index:
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.USIZE, depth+1)
|
||||
index_id, index_flow, index_ok := ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||
if !index_ok || index_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, index_flow, index_ok
|
||||
}
|
||||
index_type := state.values[index_id].type
|
||||
index_representation := types.runtime_representation(index_type, store)
|
||||
index_literal := is_numeric_constant_expr(checker, expr.right)
|
||||
if !types.is_concrete_integer(index_representation) ||
|
||||
!index_literal && !can_implicitly_convert_type(checker, index_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be coercible to usize")
|
||||
}
|
||||
index_value, index_is_int := ct_integer_value(state, index_id)
|
||||
if !index_is_int || index_value < 0 || index_value > i128(0x7fff_ffff) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime index must be a non-negative integer")
|
||||
@@ -1541,17 +1584,12 @@ ct_eval_expr :: proc(
|
||||
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
|
||||
}), ct_flow(.Normal), true
|
||||
case .Negate, .Not, .Bit_Not:
|
||||
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
||||
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1, numeric_operation)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
return ct_eval_unary(state, expr.kind, value, expr.span)
|
||||
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
|
||||
if (expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor) &&
|
||||
types.is_concrete_integer(expected) {
|
||||
left_expected = expected
|
||||
}
|
||||
left_expr := checker.ast_module.exprs[expr.left]
|
||||
right_expr := checker.ast_module.exprs[expr.right]
|
||||
if left_expr.kind == .Null && right_expr.kind != .Null &&
|
||||
@@ -1566,14 +1604,39 @@ ct_eval_expr :: proc(
|
||||
}
|
||||
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
||||
}
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||
left_const := is_numeric_constant_expr(checker, expr.left)
|
||||
right_const := is_numeric_constant_expr(checker, expr.right)
|
||||
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
|
||||
flow := ct_flow(.Normal)
|
||||
ok := false
|
||||
if left_const && !right_const {
|
||||
right, flow, ok = ct_eval_expr(state, expr.right, types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
_, distinct_ok := types.distinct_scalar_backing(state.values[right].type, store)
|
||||
left, flow, ok = ct_eval_expr(
|
||||
state, expr.left, state.values[right].type, depth+1, distinct_ok,
|
||||
)
|
||||
} else {
|
||||
left_expected := types.INVALID
|
||||
if types.is_concrete_integer(expected) ||
|
||||
expr.kind == .Div && types.is_float(expected, checker.target) {
|
||||
left_expected = expected
|
||||
}
|
||||
left, flow, ok = ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
right_expected := state.values[left].type
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_expected, store)
|
||||
right, flow, ok = ct_eval_expr(
|
||||
state, expr.right, right_expected, depth+1, distinct_ok && right_const,
|
||||
)
|
||||
}
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
right, right_flow, right_ok := ct_eval_expr(state, expr.right, state.values[left].type, depth+1)
|
||||
if !right_ok || right_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, right_flow, right_ok
|
||||
}
|
||||
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
||||
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
|
||||
left, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
||||
@@ -1899,10 +1962,17 @@ ct_eval_slice_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_
|
||||
if bound == ast.INVALID_EXPR {
|
||||
continue
|
||||
}
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.USIZE, depth+1)
|
||||
value, bound_flow, bound_ok := ct_eval_expr(state, bound, types.INVALID, depth+1)
|
||||
if !bound_ok || bound_flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, bound_flow, bound_ok
|
||||
}
|
||||
bound_type := state.values[value].type
|
||||
bound_representation := types.runtime_representation(bound_type, store)
|
||||
bound_literal := is_numeric_constant_expr(checker, bound)
|
||||
if !types.is_concrete_integer(bound_representation) ||
|
||||
!bound_literal && !can_implicitly_convert_type(checker, bound_representation, types.USIZE) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be coercible to usize")
|
||||
}
|
||||
integer, integer_ok := ct_integer_value(state, value)
|
||||
if !integer_ok || integer < 0 || integer > i128(0x7fff_ffff) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "slice bounds must be non-negative integers")
|
||||
@@ -2356,10 +2426,11 @@ ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span)
|
||||
}
|
||||
|
||||
ct_normalize_integer :: proc(state: ^Ct_State, value: i128, type: types.Type) -> i128 {
|
||||
bits := types.bits(type, state.checker.target)
|
||||
representation := types.runtime_representation(type, &state.checker.module.types)
|
||||
bits := types.bits(representation, state.checker.target)
|
||||
mask := (i128(1) << u32(bits))-1
|
||||
raw := value & mask
|
||||
if types.is_signed(type, state.checker.target) {
|
||||
if types.is_signed(representation, state.checker.target) {
|
||||
sign := i128(1) << u32(bits-1)
|
||||
if raw & sign != 0 {
|
||||
return raw-(i128(1) << u32(bits))
|
||||
@@ -2380,13 +2451,15 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Bit_Not {
|
||||
if value.kind != .Integer || !types.is_concrete_integer(value.type) {
|
||||
representation := types.runtime_representation(value.type, &state.checker.module.types)
|
||||
if value.kind != .Integer || !types.is_concrete_integer(representation) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'~' requires a concrete integer operand")
|
||||
}
|
||||
value.integer = ct_normalize_integer(state, ~value.integer, value.type)
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
if value.kind == .Integer {
|
||||
representation := types.runtime_representation(value.type, &state.checker.module.types)
|
||||
if value.kind == .Integer && types.is_signed(representation, state.checker.target) {
|
||||
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
|
||||
if overflow {
|
||||
state.error = .Overflow
|
||||
@@ -2395,7 +2468,7 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
|
||||
value.integer = result
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
if value.kind == .Float {
|
||||
if value.kind == .Float && types.is_float(representation, state.checker.target) {
|
||||
value.float = -value.float
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
@@ -2409,10 +2482,11 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
}
|
||||
left := state.values[left_id]
|
||||
right := state.values[right_id]
|
||||
store := &state.checker.module.types
|
||||
is_compare := op == .Eq || op == .Ne || op == .Lt || op == .Le || op == .Gt || op == .Ge
|
||||
if left.kind == .Null || right.kind == .Null {
|
||||
if (op != .Eq && op != .Ne) ||
|
||||
!types.is_optional(left.type, &state.checker.module.types) ||
|
||||
!types.is_optional(left.type, store) ||
|
||||
!types.equal(left.type, right.type) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'null' only supports '==' and '!=' with an optional value")
|
||||
}
|
||||
@@ -2426,36 +2500,43 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
if left.kind != .Type || right.kind != .Type || (op != .Eq && op != .Ne) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "type values only support '==' and '!=' with another type")
|
||||
}
|
||||
ok := types.equal(types.Type(left.index), types.Type(right.index))
|
||||
equal := types.equal(types.Type(left.index), types.Type(right.index))
|
||||
if op == .Ne {
|
||||
ok = !ok
|
||||
equal = !equal
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if left.kind == .Bool && right.kind == .Bool {
|
||||
if op != .Eq && op != .Ne {
|
||||
if left.kind == .Bool || right.kind == .Bool {
|
||||
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
|
||||
if left.kind != .Bool || right.kind != .Bool || !compatible ||
|
||||
!types.is_bool(operation.representation) || (op != .Eq && op != .Ne) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bool values only support '==' and '!='")
|
||||
}
|
||||
ok := left.integer == right.integer
|
||||
equal := left.integer == right.integer
|
||||
if op == .Ne {
|
||||
ok = !ok
|
||||
equal = !equal
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if left.kind == .Float || right.kind == .Float {
|
||||
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
|
||||
distinct_operation := types.is_distinct(left.type, store) || types.is_distinct(right.type, store)
|
||||
if distinct_operation && (!compatible || !types.is_float(operation.representation, state.checker.target)) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
lf := left.float if left.kind == .Float else f64(left.integer)
|
||||
rf := right.float if right.kind == .Float else f64(right.integer)
|
||||
if is_compare {
|
||||
ok := false
|
||||
result := false
|
||||
#partial switch op {
|
||||
case .Eq: ok = lf == rf
|
||||
case .Ne: ok = lf != rf
|
||||
case .Lt: ok = lf < rf
|
||||
case .Le: ok = lf <= rf
|
||||
case .Gt: ok = lf > rf
|
||||
case .Ge: ok = lf >= rf
|
||||
case .Eq: result = lf == rf
|
||||
case .Ne: result = lf != rf
|
||||
case .Lt: result = lf < rf
|
||||
case .Le: result = lf <= rf
|
||||
case .Gt: result = lf > rf
|
||||
case .Ge: result = lf >= rf
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if result else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
result := lf
|
||||
#partial switch op {
|
||||
@@ -2463,57 +2544,46 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
case .Sub: result = lf - rf
|
||||
case .Mul: result = lf * rf
|
||||
case .Div: result = lf / rf
|
||||
case:
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
result_type := types.widest(left.type, right.type)
|
||||
if !types.is_float(result_type, state.checker.target) {
|
||||
result_type = types.F64
|
||||
result_type := operation.result
|
||||
if !distinct_operation {
|
||||
result_type = types.widest(left.type, right.type)
|
||||
if !types.is_float(result_type, state.checker.target) {
|
||||
result_type = types.F64
|
||||
}
|
||||
}
|
||||
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 ct_is_integer_like(left) && ct_is_integer_like(right) {
|
||||
if is_compare {
|
||||
ok := false
|
||||
#partial switch op {
|
||||
case .Eq: ok = left.integer == right.integer
|
||||
case .Ne: ok = left.integer != right.integer
|
||||
case .Lt: ok = left.integer < right.integer
|
||||
case .Le: ok = left.integer <= right.integer
|
||||
case .Gt: ok = left.integer > right.integer
|
||||
case .Ge: ok = left.integer >= right.integer
|
||||
if types.is_enum(left.type, store) || types.is_enum(right.type, store) {
|
||||
if !types.equal(left.type, right.type) || (op != .Eq && op != .Ne) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "enum values only support '==' and '!=' with the same enum type")
|
||||
}
|
||||
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 divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
}
|
||||
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
|
||||
result_type := types.widest(left.type, right.type)
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
|
||||
equal := left.integer == right.integer
|
||||
if op == .Ne {
|
||||
equal = !equal
|
||||
}
|
||||
value := left.integer & right.integer
|
||||
#partial switch op {
|
||||
case .Bit_Or: value = left.integer | right.integer
|
||||
case .Bit_Xor: value = left.integer ~ right.integer
|
||||
}
|
||||
value = ct_normalize_integer(state, value, result_type)
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if equal else 0}), ct_flow(.Normal), true
|
||||
}
|
||||
if op == .Shift_Left || op == .Shift_Right || op == .Shift_Left_Saturating {
|
||||
if !types.is_concrete_integer(left.type) || !types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
|
||||
left_representation := types.runtime_representation(left.type, store)
|
||||
if !types.is_concrete_integer(left_representation) ||
|
||||
!types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift requires a concrete integer value and unsigned integer count")
|
||||
}
|
||||
bits := types.bits(left.type, state.checker.target)
|
||||
bits := types.bits(left_representation, state.checker.target)
|
||||
if right.integer >= i128(bits) {
|
||||
if op != .Shift_Left_Saturating {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift count exceeds integer width")
|
||||
}
|
||||
endpoint := i128(0)
|
||||
if left.integer != 0 {
|
||||
if types.is_signed(left.type, state.checker.target) {
|
||||
if types.is_signed(left_representation, state.checker.target) {
|
||||
endpoint = -(i128(1) << u32(bits-1)) if left.integer < 0 else (i128(1) << u32(bits-1))-1
|
||||
} else {
|
||||
endpoint = (i128(1) << u32(bits))-1
|
||||
@@ -2524,7 +2594,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
count := u32(right.integer)
|
||||
if op == .Shift_Right {
|
||||
value := left.integer >> count
|
||||
if !types.is_signed(left.type, state.checker.target) {
|
||||
if !types.is_signed(left_representation, state.checker.target) {
|
||||
value = ct_normalize_integer(state, left.integer, left.type) >> count
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
||||
@@ -2532,7 +2602,7 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
if op == .Shift_Left_Saturating {
|
||||
factor := i128(1) << count
|
||||
value := left.integer*factor
|
||||
if types.is_signed(left.type, state.checker.target) {
|
||||
if types.is_signed(left_representation, state.checker.target) {
|
||||
minimum := -(i128(1) << u32(bits-1))
|
||||
maximum := (i128(1) << u32(bits-1))-1
|
||||
value = max(minimum, min(maximum, value))
|
||||
@@ -2545,6 +2615,42 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
value := ct_normalize_integer(state, ct_normalize_integer(state, left.integer, left.type) << count, left.type)
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
|
||||
distinct_operation := types.is_distinct(left.type, store) || types.is_distinct(right.type, store)
|
||||
if distinct_operation && (!compatible || !types.is_concrete_integer(operation.representation)) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
|
||||
}
|
||||
if is_compare {
|
||||
result := false
|
||||
#partial switch op {
|
||||
case .Eq: result = left.integer == right.integer
|
||||
case .Ne: result = left.integer != right.integer
|
||||
case .Lt: result = left.integer < right.integer
|
||||
case .Le: result = left.integer <= right.integer
|
||||
case .Gt: result = left.integer > right.integer
|
||||
case .Ge: result = left.integer >= right.integer
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if result 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 divtrunc!, divfloor!, divexact!, or divceil!",
|
||||
)
|
||||
}
|
||||
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
|
||||
result_type := operation.result if distinct_operation else types.widest(left.type, right.type)
|
||||
if !types.is_concrete_integer(types.runtime_representation(result_type, store)) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
|
||||
}
|
||||
value := left.integer & right.integer
|
||||
#partial switch op {
|
||||
case .Bit_Or: value = left.integer | right.integer
|
||||
case .Bit_Xor: value = left.integer ~ right.integer
|
||||
}
|
||||
value = ct_normalize_integer(state, value, result_type)
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
value: i128
|
||||
overflow := false
|
||||
#partial switch op {
|
||||
@@ -2559,9 +2665,12 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
|
||||
state.error = .Overflow
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
result_type := types.widest(left.type, right.type)
|
||||
if !types.is_concrete_integer(result_type) && !types.is_enum(result_type, &state.checker.module.types) {
|
||||
result_type = ct_default_integer_type(value)
|
||||
result_type := operation.result
|
||||
if !distinct_operation {
|
||||
result_type = types.widest(left.type, right.type)
|
||||
if !types.is_concrete_integer(result_type) {
|
||||
result_type = ct_default_integer_type(value)
|
||||
}
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
|
||||
}
|
||||
@@ -2580,14 +2689,14 @@ ct_eval_division_builtin :: proc(
|
||||
}
|
||||
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) {
|
||||
operation, compatible := numeric_operation_type(state.checker, left.type, right.type)
|
||||
if !compatible || types.is_bool(operation.representation) {
|
||||
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)
|
||||
left_id, left_ok := ct_coerce_value(state, left_id, operation.result, span)
|
||||
right_id, right_ok := ct_coerce_value(state, right_id, operation.result, span)
|
||||
if !left_ok || !right_ok {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
@@ -2614,10 +2723,10 @@ ct_eval_division_builtin :: proc(
|
||||
result += right.float
|
||||
}
|
||||
}
|
||||
if types.bits(result_type, state.checker.target) == 32 {
|
||||
if types.bits(operation.representation, 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
|
||||
return ct_add_value(state, Ct_Value{kind=.Float, type=operation.result, 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")
|
||||
@@ -2626,8 +2735,8 @@ ct_eval_division_builtin :: proc(
|
||||
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 is_quotient && types.is_signed(operation.representation, state.checker.target) {
|
||||
minimum := -(i128(1) << u32(types.bits(operation.representation, 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")
|
||||
}
|
||||
@@ -2656,7 +2765,7 @@ ct_eval_division_builtin :: proc(
|
||||
}
|
||||
case:
|
||||
}
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
|
||||
return ct_add_value(state, Ct_Value{kind=.Integer, type=operation.result, integer=result}), ct_flow(.Normal), true
|
||||
}
|
||||
|
||||
ct_eval_division_call :: proc(
|
||||
@@ -2684,14 +2793,20 @@ ct_eval_division_call :: proc(
|
||||
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)
|
||||
_, distinct_ok := types.distinct_scalar_backing(state.values[right].type, &checker.module.types)
|
||||
left, flow, ok = ct_eval_expr(
|
||||
state, expr.args[0], state.values[right].type, depth+1, distinct_ok,
|
||||
)
|
||||
} 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)
|
||||
_, distinct_ok := types.distinct_scalar_backing(right_hint, &checker.module.types)
|
||||
right, flow, ok = ct_eval_expr(
|
||||
state, expr.args[1], right_hint, depth+1, distinct_ok && right_const,
|
||||
)
|
||||
}
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
@@ -2864,6 +2979,10 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
||||
}
|
||||
value := state.values[id]
|
||||
if types.can_retype_distinct(value.type, target, &state.checker.module.types) {
|
||||
value.type = target
|
||||
return ct_add_value(state, value), ct_flow(.Normal), true
|
||||
}
|
||||
if !types.is_concrete_scalar(target) || types.is_bool(target) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
|
||||
}
|
||||
@@ -3039,6 +3158,16 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
||||
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
if tag == "distinct" {
|
||||
child_value := ct_add_value(state, Ct_Value{
|
||||
kind=.Type, type=types.INVALID, index=u64(item.child),
|
||||
})
|
||||
payload_start := u32(len(state.children))
|
||||
append(&state.children, child_value)
|
||||
return ct_add_value(state, Ct_Value{
|
||||
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
|
||||
}), ct_flow(.Normal), true
|
||||
}
|
||||
if tag != "record" {
|
||||
start := u32(len(state.children))
|
||||
return ct_add_value(state, Ct_Value{
|
||||
@@ -3522,7 +3651,11 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
if (builtin == .Size_Of || builtin == .Align_Of) && !valid_layout_type(checker, target) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "layout target must be a sized runtime value type, got %s", type_label(checker, target))
|
||||
}
|
||||
if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(target) {
|
||||
bound_representation := target
|
||||
if backing, ok := types.distinct_scalar_backing(target, &checker.module.types); ok {
|
||||
bound_representation = backing
|
||||
}
|
||||
if (builtin == .Min_Value || builtin == .Max_Value) && !types.is_concrete_integer(bound_representation) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, checker.ast_module.exprs[expr.args[0]].span, "integer bound target must be a concrete integer type, got %s", type_label(checker, target))
|
||||
}
|
||||
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
|
||||
@@ -3593,8 +3726,39 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
)
|
||||
named_item, named_ok := types.node(&checker.module.types, named_type)
|
||||
target := types.resolve_alias(named_type, &checker.module.types)
|
||||
if named_ok && named_item.kind == .Alias &&
|
||||
types.is_concrete_scalar(target) && !types.is_bool(target) {
|
||||
target_item, target_ok := types.node(&checker.module.types, target)
|
||||
if target_ok && target_item.kind == .Distinct {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
expr.span,
|
||||
"distinct type '%s' expects 1 argument, got %d",
|
||||
symbol_text(checker, expr.name),
|
||||
len(expr.args),
|
||||
)
|
||||
}
|
||||
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
|
||||
if !ok || flow.kind != .Normal {
|
||||
return INVALID_CT_VALUE, flow, ok
|
||||
}
|
||||
actual := state.values[value].type
|
||||
if !types.can_retype_distinct(actual, target, &checker.module.types) {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
.Not_Comptime,
|
||||
expr.span,
|
||||
"distinct type '%s' requires an exact %s value, got %s",
|
||||
symbol_text(checker, expr.name),
|
||||
types.name(target_item.child),
|
||||
types.name(actual),
|
||||
)
|
||||
}
|
||||
result := state.values[value]
|
||||
result.type = target
|
||||
return ct_add_value(state, result), ct_flow(.Normal), true
|
||||
}
|
||||
if named_ok && named_item.kind == .Alias && types.is_concrete_scalar(target) {
|
||||
if len(expr.args) != 1 {
|
||||
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
|
||||
state,
|
||||
@@ -5027,11 +5191,17 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
||||
}
|
||||
} else {
|
||||
value_expected := expected
|
||||
numeric_operation := false
|
||||
if statement.assignment_op == .Shift_Left || statement.assignment_op == .Shift_Right ||
|
||||
statement.assignment_op == .Shift_Left_Saturating {
|
||||
value_expected = types.U64
|
||||
} else if statement.assignment_op != .Set &&
|
||||
is_numeric_constant_expr(checker, statement.expr) {
|
||||
_, numeric_operation = types.distinct_scalar_backing(expected, &checker.module.types)
|
||||
}
|
||||
value, flow, ok = ct_eval_expr(state, statement.expr, value_expected, depth+1)
|
||||
value, flow, ok = ct_eval_expr(
|
||||
state, statement.expr, value_expected, depth+1, numeric_operation,
|
||||
)
|
||||
}
|
||||
if !ok || flow.kind != .Normal {
|
||||
return flow, ok
|
||||
|
||||
Reference in New Issue
Block a user