|
|
|
@@ -501,7 +501,7 @@ write_type_label :: proc(checker: ^Checker, builder: ^strings.Builder, value: ty
|
|
|
|
|
strings.write_string(builder, "enum")
|
|
|
|
|
case .Alias, .Distinct, .Named:
|
|
|
|
|
write_type_label(checker, builder, item.child)
|
|
|
|
|
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
|
|
|
|
case .Invalid, .Void, .Anyopaque, .Int_Constraint, .Uint_Constraint, .Float_Constraint, .Range_Constraint, .Scalar:
|
|
|
|
|
strings.write_string(builder, types.name(value))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -1052,6 +1052,26 @@ fits_i64 :: proc(value: i128) -> bool {
|
|
|
|
|
return fits_signed_type(value, types.I64)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fits_u64 :: proc(value: i128) -> bool {
|
|
|
|
|
return value >= 0 && value <= i128(0xffff_ffff_ffff_ffff)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constraint_integer_literal_type :: proc(constraint: types.Type, value: i128) -> types.Type {
|
|
|
|
|
if constraint == types.UINT {
|
|
|
|
|
return types.smallest_unsigned_for_literal(u64(value)) if fits_u64(value) else types.INVALID
|
|
|
|
|
}
|
|
|
|
|
return types.smallest_signed_for_literal(i64(value)) if fits_i64(value) else types.INVALID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constraint_recovery_type :: proc(checker: ^Checker, constraint: types.Type) -> types.Type {
|
|
|
|
|
switch constraint {
|
|
|
|
|
case types.UINT: return types.U64
|
|
|
|
|
case types.FLOAT: return types.F64
|
|
|
|
|
case types.RANGE: return types.range(&checker.module.types, types.I64)
|
|
|
|
|
case: return types.I64
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type_from_syntax :: proc(
|
|
|
|
|
checker: ^Checker,
|
|
|
|
|
value: ast.Type_Syntax,
|
|
|
|
@@ -2647,7 +2667,7 @@ call_arg_expected :: proc(checker: ^Checker, function: ast.Function, index: int)
|
|
|
|
|
declared := type_from_syntax(checker, function.params[index].type, function.pkg, function.file)
|
|
|
|
|
// A `float` param defaults to f64 so an integer-literal argument builds as a
|
|
|
|
|
// float constant (e.g. `f(3)` -> 3.0), mirroring `pi float = 3` for locals.
|
|
|
|
|
// `int`/`range` constraints have no single default and keep building naturally.
|
|
|
|
|
// Integer/range constraints keep building naturally from their literals.
|
|
|
|
|
if declared == types.FLOAT {
|
|
|
|
|
return types.F64
|
|
|
|
|
}
|
|
|
|
@@ -3624,13 +3644,21 @@ record_field_expr_candidate :: proc(
|
|
|
|
|
}
|
|
|
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
|
|
|
constraint := checker.record_field_constraints[slot]
|
|
|
|
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
|
|
|
|
constant.kind == .Value && fits_i64(constant.value) {
|
|
|
|
|
candidate := types.smallest_signed_for_literal(i64(constant.value))
|
|
|
|
|
if constraint == types.FLOAT {
|
|
|
|
|
candidate = types.F64
|
|
|
|
|
if !numeric_operand_is_open(checker, expr_id, locals, pkg, file) {
|
|
|
|
|
concrete := types.constraint_target(constraint, inferred, &checker.module.types)
|
|
|
|
|
if is_runtime_type(checker, concrete) {
|
|
|
|
|
return merge_record_field_demand(checker, slot, concrete, expr.span)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if constant := eval_integer_constant_in_context(checker, expr_id, pkg, file);
|
|
|
|
|
constant.kind == .Value {
|
|
|
|
|
candidate := constraint_integer_literal_type(constraint, constant.value)
|
|
|
|
|
if constraint == types.FLOAT {
|
|
|
|
|
candidate = types.F64 if fits_i64(constant.value) else types.INVALID
|
|
|
|
|
}
|
|
|
|
|
if types.is_valid(candidate) {
|
|
|
|
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
|
|
|
|
}
|
|
|
|
|
return merge_record_field_default(checker, slot, candidate, expr.span)
|
|
|
|
|
}
|
|
|
|
|
if is_float_constant_expr(checker, expr_id) {
|
|
|
|
|
return merge_record_field_default(checker, slot, types.F64, expr.span)
|
|
|
|
@@ -3693,14 +3721,7 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if types.is_constraint(current) {
|
|
|
|
|
switch constraint {
|
|
|
|
|
case types.INT:
|
|
|
|
|
checker.module.types.fields[slot].type = types.I64
|
|
|
|
|
case types.FLOAT:
|
|
|
|
|
checker.module.types.fields[slot].type = types.F64
|
|
|
|
|
case types.RANGE:
|
|
|
|
|
checker.module.types.fields[slot].type = types.range(&checker.module.types, types.I64)
|
|
|
|
|
}
|
|
|
|
|
checker.module.types.fields[slot].type = constraint_recovery_type(checker, constraint)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -3999,7 +4020,7 @@ find_spec :: proc(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// specialized_param_type maps a parameter's declared type to its monomorphized
|
|
|
|
|
// type for a given actual argument. A constraint param (`int`/`float`/`range`)
|
|
|
|
|
// type for a given actual argument. A constraint param (`int`/`uint`/`float`/`range`)
|
|
|
|
|
// resolves to the actual's family member (INVALID if out of family), so a call
|
|
|
|
|
// passing an out-of-family argument fails to specialize and is rejected.
|
|
|
|
|
specialized_param_type :: proc(
|
|
|
|
@@ -4426,14 +4447,16 @@ infer_expr :: proc(
|
|
|
|
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
|
|
|
|
constant = eval_constant(checker, frame.expr)
|
|
|
|
|
}
|
|
|
|
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
|
|
|
|
|
(constant.kind == .Value && !fits_i64(constant.value)) {
|
|
|
|
|
if constant.kind == .Overflow || constant.kind == .Div_By_Zero {
|
|
|
|
|
last = types.I64
|
|
|
|
|
_ = pop(&stack)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if constant.kind == .Value {
|
|
|
|
|
last = types.smallest_signed_for_literal(i64(constant.value))
|
|
|
|
|
last = constraint_integer_literal_type(frame.expected, constant.value)
|
|
|
|
|
if !types.is_valid(last) {
|
|
|
|
|
last = types.I64
|
|
|
|
|
}
|
|
|
|
|
_ = pop(&stack)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
@@ -5066,7 +5089,7 @@ infer_statements :: proc(
|
|
|
|
|
declared_local := resolve_inferred_array(checker, type_from_syntax(checker, statement.type, pkg, file), statement.expr)
|
|
|
|
|
value_type := types.INVALID
|
|
|
|
|
if !is_undefined_expr(checker, statement.expr) {
|
|
|
|
|
expected := declared_local if is_runtime_type(checker, declared_local) else types.INVALID
|
|
|
|
|
expected := declared_local if is_runtime_type(checker, declared_local) || declared_local == types.UINT else types.INVALID
|
|
|
|
|
value_type = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected)
|
|
|
|
|
declared_local = resolve_inferred_array_from_type(checker, declared_local, value_type)
|
|
|
|
|
}
|
|
|
|
@@ -5082,7 +5105,8 @@ infer_statements :: proc(
|
|
|
|
|
const_val := i128(0)
|
|
|
|
|
if !is_runtime_type(checker, declared_local) && !is_undefined_expr(checker, statement.expr) {
|
|
|
|
|
constant := eval_integer_constant_in_context(checker, statement.expr, pkg, file)
|
|
|
|
|
if constant.kind == .Value && fits_i64(constant.value) {
|
|
|
|
|
if constant.kind == .Value &&
|
|
|
|
|
(fits_i64(constant.value) || declared_local == types.UINT && fits_u64(constant.value)) {
|
|
|
|
|
open = true
|
|
|
|
|
const_val = constant.value
|
|
|
|
|
} else if is_float_constant_expr(checker, statement.expr) {
|
|
|
|
@@ -5123,11 +5147,25 @@ infer_statements :: proc(
|
|
|
|
|
expected_assignment := types.INVALID
|
|
|
|
|
if statement.target != ast.INVALID_EXPR {
|
|
|
|
|
expected_assignment = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded, local_types)
|
|
|
|
|
target_expr := checker.ast_module.exprs[statement.target]
|
|
|
|
|
if target_expr.kind == .Name && !symbol.is_valid(target_expr.qualifier) {
|
|
|
|
|
if local_index, ok := find_infer_local_index(locals^[:], target_expr.name); ok &&
|
|
|
|
|
locals^[local_index].declared == types.UINT {
|
|
|
|
|
expected_assignment = types.UINT
|
|
|
|
|
} else if global := find_global(checker, target_expr.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
|
|
|
ast_global := checker.ast_module.globals[global]
|
|
|
|
|
if type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file) == types.UINT {
|
|
|
|
|
expected_assignment = types.UINT
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if statement.name != checker.sink_symbol {
|
|
|
|
|
if local_index, ok := find_infer_local_index(locals^[:], statement.name); ok {
|
|
|
|
|
expected_assignment = locals^[local_index].type
|
|
|
|
|
expected_assignment = types.UINT if locals^[local_index].declared == types.UINT else locals^[local_index].type
|
|
|
|
|
} else if global := find_global(checker, statement.name, pkg, file); global != ast.INVALID_GLOBAL {
|
|
|
|
|
expected_assignment = checker.global_types[global]
|
|
|
|
|
ast_global := checker.ast_module.globals[global]
|
|
|
|
|
declared_global := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
|
|
|
|
expected_assignment = types.UINT if declared_global == types.UINT else checker.global_types[global]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded, local_types, expected_assignment)
|
|
|
|
@@ -5430,7 +5468,7 @@ infer_spec_locals_and_result :: proc(
|
|
|
|
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == types.INT {
|
|
|
|
|
declared = types.I32
|
|
|
|
|
}
|
|
|
|
|
result_hint := declared if is_runtime_type(checker, declared) else types.INVALID
|
|
|
|
|
result_hint := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
|
|
|
|
|
|
|
|
|
locals: [dynamic]Infer_Local
|
|
|
|
|
locals.allocator = checker.allocator
|
|
|
|
@@ -5615,7 +5653,7 @@ open_const_default_type :: proc(
|
|
|
|
|
if index, ok := find_infer_local_index(locals, expr.name); ok {
|
|
|
|
|
local := locals[index]
|
|
|
|
|
if local.open_const {
|
|
|
|
|
return types.smallest_signed_for_literal(i64(local.const_value))
|
|
|
|
|
return constraint_integer_literal_type(local.declared, local.const_value)
|
|
|
|
|
}
|
|
|
|
|
if local.open_float {
|
|
|
|
|
return types.F64
|
|
|
|
@@ -5633,7 +5671,9 @@ open_const_default_type :: proc(
|
|
|
|
|
return types.INVALID
|
|
|
|
|
}
|
|
|
|
|
if checker.global_open_const[index] {
|
|
|
|
|
return types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
|
|
|
|
|
ast_global := checker.ast_module.globals[global]
|
|
|
|
|
declared := type_from_syntax(checker, ast_global.type, ast_global.pkg, ast_global.file)
|
|
|
|
|
return constraint_integer_literal_type(declared, checker.global_const_value[index])
|
|
|
|
|
}
|
|
|
|
|
if checker.global_open_float[index] {
|
|
|
|
|
return types.F64
|
|
|
|
@@ -5899,7 +5939,8 @@ infer_all :: proc(checker: ^Checker) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file)
|
|
|
|
|
if constant.kind == .Value && fits_i64(constant.value) {
|
|
|
|
|
if constant.kind == .Value &&
|
|
|
|
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) {
|
|
|
|
|
checker.global_open_const[index] = true
|
|
|
|
|
checker.global_const_value[index] = constant.value
|
|
|
|
|
} else if is_float_constant_expr(checker, global.expr) {
|
|
|
|
@@ -5947,7 +5988,7 @@ infer_all :: proc(checker: ^Checker) {
|
|
|
|
|
type_from_syntax(checker, global.type, global.pkg, global.file),
|
|
|
|
|
global.expr,
|
|
|
|
|
)
|
|
|
|
|
expected := declared if is_runtime_type(checker, declared) else types.INVALID
|
|
|
|
|
expected := declared if is_runtime_type(checker, declared) || declared == types.UINT else types.INVALID
|
|
|
|
|
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file, expected=expected)
|
|
|
|
|
if resolved := resolve_inferred_array_from_type(checker, declared, inferred);
|
|
|
|
|
resolved != declared {
|
|
|
|
@@ -6020,7 +6061,8 @@ infer_all :: proc(checker: ^Checker) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if checker.global_open_const[index] {
|
|
|
|
|
checker.global_types[index] = types.smallest_signed_for_literal(i64(checker.global_const_value[index]))
|
|
|
|
|
declared := type_from_syntax(checker, global.type, global.pkg, global.file)
|
|
|
|
|
checker.global_types[index] = constraint_integer_literal_type(declared, checker.global_const_value[index])
|
|
|
|
|
defaulted = true
|
|
|
|
|
} else if checker.global_open_float[index] {
|
|
|
|
|
checker.global_types[index] = types.F64
|
|
|
|
@@ -6394,7 +6436,7 @@ build_constant_expr :: proc(
|
|
|
|
|
return invalid_hir_expr(checker, expr.span, id, recovery_type)
|
|
|
|
|
}
|
|
|
|
|
if constant.kind == .Overflow ||
|
|
|
|
|
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
|
|
|
|
|
(!types.is_concrete_integer(expected) && expected != types.UINT && !fits_i64(constant.value)) {
|
|
|
|
|
id := source.add(
|
|
|
|
|
checker.diagnostics,
|
|
|
|
|
expr.span,
|
|
|
|
@@ -6407,7 +6449,16 @@ build_constant_expr :: proc(
|
|
|
|
|
if constant.value >= 0 && constant.value <= i128(0xffff_ffff_ffff_ffff) {
|
|
|
|
|
value = transmute(i64)u64(constant.value)
|
|
|
|
|
}
|
|
|
|
|
result_type := types.smallest_signed_for_literal(value)
|
|
|
|
|
result_type := constraint_integer_literal_type(expected, constant.value)
|
|
|
|
|
if expected == types.UINT && !types.is_valid(result_type) {
|
|
|
|
|
id := source.addf(
|
|
|
|
|
checker.diagnostics,
|
|
|
|
|
expr.span,
|
|
|
|
|
"integer constant %d does not satisfy uint",
|
|
|
|
|
constant.value,
|
|
|
|
|
)
|
|
|
|
|
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) {
|
|
|
|
|
id := source.addf(
|
|
|
|
@@ -12375,11 +12426,12 @@ build_globals :: proc(checker: ^Checker) {
|
|
|
|
|
if is_runtime_type(checker, declared) {
|
|
|
|
|
expected = declared
|
|
|
|
|
} else if constant := eval_integer_constant_in_context(checker, global.expr, global.pkg, global.file);
|
|
|
|
|
constant.kind == .Value && fits_i64(constant.value) &&
|
|
|
|
|
constant.kind == .Value &&
|
|
|
|
|
(fits_i64(constant.value) || declared == types.UINT && fits_u64(constant.value)) &&
|
|
|
|
|
is_runtime_type(checker, checker.global_types[global_index]) {
|
|
|
|
|
// Open integer constant: build against its demanded/defaulted type. Gated
|
|
|
|
|
// to the infer-side open-constant condition so out-of-range constants keep
|
|
|
|
|
// their original "exceeds signed i64 range" diagnostic.
|
|
|
|
|
// their original range diagnostic.
|
|
|
|
|
expected = checker.global_types[global_index]
|
|
|
|
|
} else if (is_float_constant_expr(checker, global.expr) ||
|
|
|
|
|
is_numeric_arithmetic_expr(checker, global.expr)) &&
|
|
|
|
@@ -12401,6 +12453,18 @@ build_globals :: proc(checker: ^Checker) {
|
|
|
|
|
global_type = types.I64
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if diagnostic == source.INVALID_DIAGNOSTIC && types.is_constraint(declared) &&
|
|
|
|
|
!types.constraint_accepts(declared, global_type, &checker.module.types) {
|
|
|
|
|
diagnostic = source.addf(
|
|
|
|
|
checker.diagnostics,
|
|
|
|
|
global.span,
|
|
|
|
|
"could not resolve the '%s' constraint for global '%s'",
|
|
|
|
|
types.name(declared),
|
|
|
|
|
symbol_text(checker, global.name),
|
|
|
|
|
)
|
|
|
|
|
global_type = constraint_recovery_type(checker, declared)
|
|
|
|
|
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
|
|
|
|
|
}
|
|
|
|
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
|
|
|
|
diagnostic = source.add(
|
|
|
|
|
checker.diagnostics,
|
|
|
|
|