diagnostics upgrade
This commit is contained in:
@@ -63,6 +63,14 @@ index :: proc(id: $T, invalid: T, count: int) -> (int, bool) {
|
||||
|
||||
Type_Syntax :: types.Type
|
||||
|
||||
Type_Use :: struct {
|
||||
type: Type_Syntax,
|
||||
span: source.Span,
|
||||
pkg: Package_Id,
|
||||
file: File_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
|
||||
Expr_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Integer,
|
||||
@@ -325,6 +333,7 @@ Module :: struct {
|
||||
c_trampolines: [dynamic]Trampoline,
|
||||
strings: [dynamic]string,
|
||||
type_fields: [dynamic]types.Field,
|
||||
type_uses: [dynamic]Type_Use,
|
||||
type_store: types.Store,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
@@ -345,6 +354,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
||||
module.c_trampolines.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
module.type_fields.allocator = allocator
|
||||
module.type_uses.allocator = allocator
|
||||
return module
|
||||
}
|
||||
|
||||
@@ -397,5 +407,6 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(module.c_trampolines)
|
||||
delete(module.strings)
|
||||
delete(module.type_fields)
|
||||
delete(module.type_uses)
|
||||
types.destroy_store(&module.type_store)
|
||||
}
|
||||
|
||||
+277
-35
@@ -208,6 +208,7 @@ Checker :: struct {
|
||||
record_field_conflicts: []types.Type,
|
||||
record_field_conflict_spans: []source.Span,
|
||||
record_field_demands_dirty: bool,
|
||||
poisoned_packages: []bool,
|
||||
external_global_canonical: []ast.Global_Id,
|
||||
external_global_diagnostics: []source.Diagnostic_Id,
|
||||
constants: []Constant,
|
||||
@@ -1496,16 +1497,30 @@ expr_lookup_file :: proc(expr: ast.Expr, file: ast.File_Id) -> ast.File_Id {
|
||||
return ast.INVALID_FILE if symbol.is_valid(expr.qualifier) else file
|
||||
}
|
||||
|
||||
expr_symbol_span :: proc(checker: ^Checker, expr: ast.Expr, name: symbol.Id) -> source.Span {
|
||||
span := expr.span
|
||||
span.end = span.start+source.Offset(len(symbol_text(checker, name)))
|
||||
return span
|
||||
}
|
||||
|
||||
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
|
||||
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
|
||||
span := expr_symbol_span(checker, expr, expr.qualifier)
|
||||
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.qualifier))
|
||||
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
||||
return id
|
||||
}
|
||||
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
|
||||
}
|
||||
|
||||
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
|
||||
if find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_FUNCTION {
|
||||
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
|
||||
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
||||
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a function, not a global value", symbol_text(checker, expr.name))
|
||||
if !checker.ast_module.functions[template].imported {
|
||||
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.functions[template].span, "function declared here")
|
||||
}
|
||||
return id
|
||||
}
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
return source.addf(
|
||||
@@ -1516,12 +1531,20 @@ add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
|
||||
symbol_text(checker, expr.name),
|
||||
)
|
||||
}
|
||||
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
|
||||
span := expr_symbol_span(checker, expr, expr.name)
|
||||
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
|
||||
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
||||
return id
|
||||
}
|
||||
|
||||
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id, file: ast.File_Id) -> source.Diagnostic_Id {
|
||||
if find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file)) != ast.INVALID_GLOBAL {
|
||||
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
|
||||
global := find_global(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
||||
id := source.addf(checker.diagnostics, expr_symbol_span(checker, expr, expr.name), "'%s' is a global, not a function", symbol_text(checker, expr.name))
|
||||
if !checker.ast_module.globals[global].external {
|
||||
source.add_secondary_label(checker.diagnostics, id, checker.ast_module.globals[global].span, "global declared here")
|
||||
}
|
||||
return id
|
||||
}
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
return source.addf(
|
||||
@@ -1532,7 +1555,10 @@ add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
|
||||
symbol_text(checker, expr.name),
|
||||
)
|
||||
}
|
||||
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
|
||||
span := expr_symbol_span(checker, expr, expr.name)
|
||||
id := source.addf(checker.diagnostics, span, "unknown symbol '%s'", symbol_text(checker, expr.name))
|
||||
source.set_primary_label(checker.diagnostics, id, "unknown symbol")
|
||||
return id
|
||||
}
|
||||
|
||||
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
|
||||
@@ -2649,6 +2675,7 @@ resolve_type_factory_call :: proc(checker: ^Checker, expr_id: ast.Expr_Id, pkg:
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
_ = add_package_resolution_diagnostic(checker, expr, file)
|
||||
return types.INVALID
|
||||
}
|
||||
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
||||
@@ -3145,6 +3172,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
has_comptime := function_has_comptime_params(function)
|
||||
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
|
||||
locals: [dynamic]symbol.Id
|
||||
locals.allocator = checker.allocator
|
||||
for param in function.params {
|
||||
@@ -3152,7 +3180,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
if param.comptime_value || !has_comptime {
|
||||
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
}
|
||||
if param.comptime_value {
|
||||
if param.comptime_value && !signature_poisoned {
|
||||
if function.c_abi {
|
||||
checker.template_diagnostics[function_id] = source.add(
|
||||
checker.diagnostics,
|
||||
@@ -3169,7 +3197,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
symbol_text(checker, param.name),
|
||||
)
|
||||
}
|
||||
} else if !has_comptime {
|
||||
} else if !has_comptime && !signature_poisoned {
|
||||
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
checker.template_diagnostics[function_id] = diagnostic
|
||||
@@ -3197,7 +3225,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
append(&locals, param.name)
|
||||
if !has_comptime && types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
||||
if !has_comptime && !signature_poisoned && types.contains_c_struct_by_value(param_type, &checker.module.types) {
|
||||
checker.template_diagnostics[function_id] = source.addf(
|
||||
checker.diagnostics,
|
||||
param.span,
|
||||
@@ -3206,7 +3234,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if !has_comptime {
|
||||
if !has_comptime && !signature_poisoned {
|
||||
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
@@ -3221,7 +3249,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if types.is_valid(function.error) {
|
||||
if types.is_valid(function.error) && !signature_poisoned {
|
||||
error_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
||||
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
||||
types.is_tagged_union(error_type, &checker.module.types)
|
||||
@@ -3239,7 +3267,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if !function.has_body && !function.c_abi {
|
||||
if !function.has_body && !function.c_abi && !signature_poisoned {
|
||||
checker.template_diagnostics[function_id] = source.addf(
|
||||
checker.diagnostics,
|
||||
function.span,
|
||||
@@ -3247,7 +3275,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
symbol_text(checker, function.name),
|
||||
)
|
||||
}
|
||||
if function.variadic && (!function.c_abi || function.has_body) {
|
||||
if function.variadic && (!function.c_abi || function.has_body) && !signature_poisoned {
|
||||
checker.template_diagnostics[function_id] = source.addf(
|
||||
checker.diagnostics,
|
||||
function.span,
|
||||
@@ -3255,7 +3283,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
symbol_text(checker, function.name),
|
||||
)
|
||||
}
|
||||
if !function.has_body && function.c_abi {
|
||||
if !function.has_body && function.c_abi && !signature_poisoned {
|
||||
for param in function.params {
|
||||
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
|
||||
@@ -3351,7 +3379,7 @@ is_inferred_record_field :: proc(checker: ^Checker, slot: int) -> bool {
|
||||
types.is_constraint(checker.record_field_constraints[slot])
|
||||
}
|
||||
|
||||
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, bool) {
|
||||
record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.Id, ast.Package_Id, bool) {
|
||||
for item in checker.module.types.nodes {
|
||||
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
||||
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
||||
@@ -3360,10 +3388,10 @@ record_field_owner :: proc(checker: ^Checker, slot: int) -> (symbol.Id, symbol.I
|
||||
start := int(item.field_start)
|
||||
if slot >= start && slot < start+int(item.field_count) &&
|
||||
slot >= 0 && slot < len(checker.module.types.fields) {
|
||||
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), true
|
||||
return symbol.Id(item.name), symbol.Id(checker.module.types.fields[slot].name), ast.Package_Id(item.pkg), true
|
||||
}
|
||||
}
|
||||
return symbol.INVALID, symbol.INVALID, false
|
||||
return symbol.INVALID, symbol.INVALID, ast.INVALID_PACKAGE, false
|
||||
}
|
||||
|
||||
record_field_conflict :: proc(checker: ^Checker, slot: int, actual: types.Type, span: source.Span) {
|
||||
@@ -3484,7 +3512,7 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
|
||||
if !types.is_constraint(constraint) {
|
||||
continue
|
||||
}
|
||||
record_name, field_name, ok := record_field_owner(checker, slot)
|
||||
record_name, field_name, record_pkg, ok := record_field_owner(checker, slot)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -3509,7 +3537,8 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
|
||||
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
||||
)
|
||||
}
|
||||
} else if types.is_constraint(current) {
|
||||
} else if types.is_constraint(current) &&
|
||||
!(int(record_pkg) < len(checker.poisoned_packages) && checker.poisoned_packages[record_pkg]) {
|
||||
source.addf(
|
||||
checker.diagnostics,
|
||||
source.Span{},
|
||||
@@ -5622,6 +5651,10 @@ infer_all :: proc(checker: ^Checker) {
|
||||
// Demands accumulate in global_demands so the default never blocks a later
|
||||
// cross-family demand (e.g. integer literal -> unsigned or float).
|
||||
for global, index in checker.ast_module.globals {
|
||||
if global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
checker.global_types[index] = types.I64
|
||||
continue
|
||||
}
|
||||
declared := resolve_inferred_array(
|
||||
checker,
|
||||
type_from_syntax(checker, global.type, global.pkg, global.file),
|
||||
@@ -5675,7 +5708,7 @@ infer_all :: proc(checker: ^Checker) {
|
||||
// Backward demands: a global pushes its own (declared or already-resolved) type
|
||||
// onto open numeric slots reachable through names and numeric arithmetic.
|
||||
for global, index in checker.ast_module.globals {
|
||||
if global.external {
|
||||
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
continue
|
||||
}
|
||||
demand := checker.global_types[index]
|
||||
@@ -5689,7 +5722,7 @@ infer_all :: proc(checker: ^Checker) {
|
||||
// concrete-typed ones) for its side effect of specializing called functions and
|
||||
// recording demands from call arguments in their initializers.
|
||||
for global, index in checker.ast_module.globals {
|
||||
if global.external {
|
||||
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
continue
|
||||
}
|
||||
declared := resolve_inferred_array(
|
||||
@@ -5765,7 +5798,8 @@ infer_all :: proc(checker: ^Checker) {
|
||||
// No authoritative demand can still arrive. Assign final defaults, then
|
||||
// continue the same fixpoint so dependent globals/specs observe them.
|
||||
for global, index in checker.ast_module.globals {
|
||||
if global.external || is_runtime_type(checker, checker.global_types[index]) {
|
||||
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC ||
|
||||
is_runtime_type(checker, checker.global_types[index]) {
|
||||
continue
|
||||
}
|
||||
if checker.global_open_const[index] {
|
||||
@@ -5807,7 +5841,7 @@ prune_specs :: proc(checker: ^Checker) {
|
||||
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
|
||||
}
|
||||
for global in checker.ast_module.globals {
|
||||
if global.external {
|
||||
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
continue
|
||||
}
|
||||
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
||||
@@ -5859,6 +5893,23 @@ invalid_hir_expr :: proc(
|
||||
)
|
||||
}
|
||||
|
||||
invalid_expr_diagnostic :: proc(checker: ^Checker, id: hir.Expr_Id) -> (source.Diagnostic_Id, bool) {
|
||||
if id == hir.INVALID_EXPR || int(id) >= len(checker.module.exprs) {
|
||||
return source.INVALID_DIAGNOSTIC, false
|
||||
}
|
||||
expr := checker.module.exprs[id]
|
||||
return expr.diagnostic, expr.kind == .Invalid && expr.diagnostic != source.INVALID_DIAGNOSTIC
|
||||
}
|
||||
|
||||
propagate_invalid_expr :: proc(checker: ^Checker, span: source.Span, values: ..hir.Expr_Id) -> (hir.Expr_Id, bool) {
|
||||
for value in values {
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
||||
return invalid_hir_expr(checker, span, diagnostic), true
|
||||
}
|
||||
}
|
||||
return hir.INVALID_EXPR, false
|
||||
}
|
||||
|
||||
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
|
||||
for existing in values {
|
||||
if existing == value {
|
||||
@@ -5935,6 +5986,9 @@ coerce_expr :: proc(
|
||||
if expr_id == hir.INVALID_EXPR {
|
||||
return expr_id
|
||||
}
|
||||
if _, invalid := invalid_expr_diagnostic(checker, expr_id); invalid {
|
||||
return expr_id
|
||||
}
|
||||
actual := checker.module.exprs[expr_id].type
|
||||
if types.equal(actual, expected) {
|
||||
return expr_id
|
||||
@@ -6918,6 +6972,9 @@ build_compound_expr :: proc(
|
||||
})
|
||||
}
|
||||
value := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, value); propagated {
|
||||
return invalid
|
||||
}
|
||||
if !hir_is_location(checker, value) {
|
||||
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
@@ -6936,6 +6993,9 @@ build_compound_expr :: proc(
|
||||
})
|
||||
case .Deref:
|
||||
pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, pointer); propagated {
|
||||
return invalid
|
||||
}
|
||||
pointer_type := checker.module.exprs[pointer].type
|
||||
if !types.is_pointer(pointer_type, store) {
|
||||
id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer")
|
||||
@@ -6949,6 +7009,9 @@ build_compound_expr :: proc(
|
||||
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)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container, index); propagated {
|
||||
return invalid
|
||||
}
|
||||
container_type := checker.module.exprs[container].type
|
||||
item, ok := types.container(container_type, store)
|
||||
if !ok {
|
||||
@@ -6970,6 +7033,9 @@ build_compound_expr :: proc(
|
||||
})
|
||||
}
|
||||
container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, container); propagated {
|
||||
return invalid
|
||||
}
|
||||
container_type := checker.module.exprs[container].type
|
||||
item, ok := types.container(container_type, store)
|
||||
if !ok {
|
||||
@@ -6987,6 +7053,10 @@ build_compound_expr :: proc(
|
||||
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)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, bounds[index]); propagated {
|
||||
delete(bounds, checker.allocator)
|
||||
return invalid
|
||||
}
|
||||
}
|
||||
}
|
||||
preserve_sentinel := item.has_sentinel && expr.args[1] == ast.INVALID_EXPR
|
||||
@@ -7000,11 +7070,17 @@ build_compound_expr :: proc(
|
||||
return enum_member_hir(checker, enum_type, expr.name, expr.span)
|
||||
}
|
||||
base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, base); propagated {
|
||||
return invalid
|
||||
}
|
||||
base_type := checker.module.exprs[base].type
|
||||
result, _ := build_field_from_value(checker, expr, base, base_type)
|
||||
return result
|
||||
case .Unwrap:
|
||||
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
|
||||
return invalid
|
||||
}
|
||||
optional_type := checker.module.exprs[optional].type
|
||||
if !types.is_optional(optional_type, store) {
|
||||
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
|
||||
@@ -7016,6 +7092,9 @@ build_compound_expr :: proc(
|
||||
})
|
||||
case .Orelse:
|
||||
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, optional); propagated {
|
||||
return invalid
|
||||
}
|
||||
optional_type := checker.module.exprs[optional].type
|
||||
if !types.is_optional(optional_type, store) {
|
||||
id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand")
|
||||
@@ -7035,6 +7114,9 @@ build_compound_expr :: proc(
|
||||
}
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
|
||||
return invalid_hir_expr(checker, expr.span, diagnostic)
|
||||
}
|
||||
channel_type := checker.module.exprs[channel].type
|
||||
success := types.fallible_success(channel_type, store)
|
||||
if !types.is_valid(success) {
|
||||
@@ -7076,6 +7158,9 @@ build_compound_expr :: proc(
|
||||
case .Catch:
|
||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, channel); invalid {
|
||||
return invalid_hir_expr(checker, expr.span, diagnostic)
|
||||
}
|
||||
channel_type := checker.module.exprs[channel].type
|
||||
success := types.fallible_success(channel_type, store)
|
||||
if !types.is_valid(success) {
|
||||
@@ -7145,6 +7230,9 @@ build_compound_expr :: proc(
|
||||
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)
|
||||
}
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
child := expected_child
|
||||
if !types.is_valid(child) {
|
||||
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||
@@ -7174,6 +7262,9 @@ build_compound_expr :: proc(
|
||||
})
|
||||
case .Not:
|
||||
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
||||
return invalid
|
||||
}
|
||||
operand_type := checker.module.exprs[operand].type
|
||||
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
|
||||
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
|
||||
@@ -7186,6 +7277,9 @@ build_compound_expr :: proc(
|
||||
case .And, .Or:
|
||||
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, 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
|
||||
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
|
||||
@@ -7226,11 +7320,11 @@ build_compound_expr :: proc(
|
||||
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)
|
||||
}
|
||||
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 checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
|
||||
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
|
||||
}
|
||||
operand_type := types.INVALID
|
||||
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
||||
if !types.equal(left_type, right_type) || (expr.kind != .Eq && expr.kind != .Ne) {
|
||||
@@ -7420,6 +7514,9 @@ build_binary_arith :: proc(
|
||||
left, right: hir.Expr_Id,
|
||||
span: source.Span,
|
||||
) -> hir.Expr_Id {
|
||||
if invalid, propagated := propagate_invalid_expr(checker, span, left, right); propagated {
|
||||
return invalid
|
||||
}
|
||||
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
|
||||
if op == .Add &&
|
||||
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
||||
@@ -7960,6 +8057,9 @@ build_expr :: proc(
|
||||
checker.diagnostics, expr.span, message,
|
||||
symbol_text(checker, expr.name), len(function.params), len(expr.args),
|
||||
)
|
||||
if !function.imported {
|
||||
source.add_secondary_label(checker.diagnostics, id, function.span, "function declared here")
|
||||
}
|
||||
} else if len(mapping_failure) > 0 {
|
||||
id = source.addf(
|
||||
checker.diagnostics, expr.span,
|
||||
@@ -8009,6 +8109,11 @@ build_expr :: proc(
|
||||
}
|
||||
if frame.stage == 5 {
|
||||
operand := last
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
|
||||
last = invalid
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
operand_type := checker.module.exprs[operand].type
|
||||
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
|
||||
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
|
||||
@@ -8071,6 +8176,17 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
}
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args); propagated {
|
||||
delete(stack[frame_index].arg_types, checker.allocator)
|
||||
stack[frame_index].arg_types = nil
|
||||
delete(stack[frame_index].built_args, checker.allocator)
|
||||
stack[frame_index].built_args = nil
|
||||
delete(stack[frame_index].mapping, checker.allocator)
|
||||
stack[frame_index].mapping = nil
|
||||
last = invalid
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
function := checker.ast_module.functions[frame.template]
|
||||
comptime_values: []Comptime_Value
|
||||
comptime_ok := false
|
||||
@@ -8236,6 +8352,11 @@ build_expr :: proc(
|
||||
}
|
||||
if frame.stage == 6 {
|
||||
callee := last
|
||||
if invalid, propagated := propagate_invalid_expr(checker, expr.span, callee); propagated {
|
||||
last = invalid
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
|
||||
if !ok {
|
||||
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
||||
@@ -8279,6 +8400,17 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
}
|
||||
invalid, propagated := propagate_invalid_expr(checker, expr.span, frame.left)
|
||||
if !propagated {
|
||||
invalid, propagated = propagate_invalid_expr(checker, expr.span, ..stack[frame_index].built_args)
|
||||
}
|
||||
if propagated {
|
||||
delete(stack[frame_index].built_args, checker.allocator)
|
||||
stack[frame_index].built_args = nil
|
||||
last = invalid
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
callee_type := checker.module.exprs[frame.left].type
|
||||
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
||||
if !ok {
|
||||
@@ -8897,6 +9029,22 @@ build_block :: proc(
|
||||
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
|
||||
for statement_id in statements {
|
||||
statement := checker.ast_module.statements[statement_id]
|
||||
if statement.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
if statement.kind == .Declaration && symbol.is_valid(statement.name) {
|
||||
if _, found := find_build_local(ctx.locals^[duplicate_start:], statement.name); !found {
|
||||
_ = append_build_local(
|
||||
ctx, statement.name, types.I64, !statement.immutable, statement.span,
|
||||
)
|
||||
}
|
||||
}
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
switch statement.kind {
|
||||
case .Declaration:
|
||||
// A value block (`x :: { ... yield v }` / `x T = { ... }`): the parser
|
||||
@@ -9070,6 +9218,15 @@ build_block :: proc(
|
||||
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
types.INVALID, ctx.pkg, ctx.file,
|
||||
)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
target_type := checker.module.exprs[target_expr].type
|
||||
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
||||
@@ -9092,6 +9249,15 @@ build_block :: proc(
|
||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||
rhs_expected, ctx.pkg, ctx.file,
|
||||
)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||
assignment_op = .Pointer_Add
|
||||
if statement.assignment_op != .Add {
|
||||
@@ -9208,6 +9374,15 @@ build_block :: proc(
|
||||
continue
|
||||
}
|
||||
target_expr := build_global_reference(checker, global, statement.span, ctx.global_reads)
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, target_expr); invalid {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
||||
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
continue
|
||||
}
|
||||
target_type := checker.module.exprs[target_expr].type
|
||||
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
||||
@@ -9425,7 +9600,14 @@ build_block :: proc(
|
||||
})
|
||||
case .Expression:
|
||||
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||
if !types.is_void(checker.module.exprs[value].type) {
|
||||
if diagnostic, invalid := invalid_expr_diagnostic(checker, value); invalid {
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||
local = hir.INVALID_LOCAL, diagnostic = diagnostic,
|
||||
})
|
||||
ctx.problematic^ = true
|
||||
} else if !types.is_void(checker.module.exprs[value].type) {
|
||||
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
@@ -11504,15 +11686,10 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
checker.current_comptime_values = spec.comptime_values
|
||||
defer checker.current_comptime_values = previous_comptime
|
||||
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
||||
if !types.is_void(spec.result) && !is_runtime_type(checker, spec.result) {
|
||||
unresolved_result := !types.is_void(spec.result) && !is_runtime_type(checker, spec.result)
|
||||
if unresolved_result {
|
||||
checker.specs[id].result = types.I64
|
||||
spec.result = types.I64
|
||||
signature_diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
function.span,
|
||||
"could not resolve a concrete result type for '%s'",
|
||||
symbol_text(checker, function.name),
|
||||
)
|
||||
}
|
||||
for arg in spec.args {
|
||||
if !is_runtime_type(checker, arg) {
|
||||
@@ -11582,6 +11759,16 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
|
||||
native_main := function.pkg == 0 && function.name == checker.main_symbol && checker.entry_point == .Plain
|
||||
if !function.has_body {
|
||||
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
|
||||
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC {
|
||||
signature_diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
function.span,
|
||||
"could not resolve a concrete result type for '%s'",
|
||||
symbol_text(checker, function.name),
|
||||
)
|
||||
problematic = true
|
||||
}
|
||||
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
|
||||
append(
|
||||
&checker.module.functions,
|
||||
@@ -11665,6 +11852,22 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||
append(&body, block_stmt)
|
||||
}
|
||||
delete(block, checker.allocator)
|
||||
if unresolved_result && signature_diagnostic == source.INVALID_DIAGNOSTIC &&
|
||||
checker.template_diagnostics[spec.template] == source.INVALID_DIAGNOSTIC && !problematic {
|
||||
signature_diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
function.span,
|
||||
"could not resolve a concrete result type for '%s'",
|
||||
symbol_text(checker, function.name),
|
||||
)
|
||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||
append(&checker.module.statements, hir.Stmt{
|
||||
kind=.Trap, span=function.span, expr=hir.INVALID_EXPR,
|
||||
local=hir.INVALID_LOCAL, diagnostic=signature_diagnostic,
|
||||
})
|
||||
problematic = true
|
||||
returns = true
|
||||
}
|
||||
|
||||
fallible_void := types.kind(spec.result, &checker.module.types) == .Fallible &&
|
||||
types.is_void(types.fallible_success(spec.result, &checker.module.types))
|
||||
@@ -11773,6 +11976,28 @@ static_integer_value :: proc(module: ^hir.Module, expr_id: hir.Expr_Id) -> (i64,
|
||||
|
||||
build_globals :: proc(checker: ^Checker) {
|
||||
for global, global_index in checker.ast_module.globals {
|
||||
if global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
global_type := checker.global_types[global_index]
|
||||
if !is_runtime_type(checker, global_type) {
|
||||
global_type = types.I64
|
||||
}
|
||||
expr := hir.INVALID_EXPR
|
||||
if !global.external {
|
||||
expr = invalid_hir_expr(checker, global.span, global.diagnostic, global_type)
|
||||
}
|
||||
append(&checker.module.globals, hir.Global{
|
||||
name=global.name,
|
||||
link_name=strings.clone(global.link_name, checker.allocator),
|
||||
type=global_type,
|
||||
expr=expr,
|
||||
external=global.external,
|
||||
writable=global.writable || !global.immutable,
|
||||
direct_problem=true,
|
||||
problematic=true,
|
||||
diagnostic=global.diagnostic,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if global.external {
|
||||
global_type := checker.global_types[global_index]
|
||||
writable := global.writable
|
||||
@@ -11841,6 +12066,12 @@ build_globals :: proc(checker: ^Checker) {
|
||||
global_type = checker.module.exprs[expr].type
|
||||
}
|
||||
diagnostic := source.INVALID_DIAGNOSTIC
|
||||
if root, invalid := invalid_expr_diagnostic(checker, expr); invalid {
|
||||
diagnostic = root
|
||||
if !is_runtime_type(checker, global_type) {
|
||||
global_type = types.I64
|
||||
}
|
||||
}
|
||||
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
||||
diagnostic = source.add(
|
||||
checker.diagnostics,
|
||||
@@ -12188,6 +12419,7 @@ check :: proc(
|
||||
checker.record_field_defaults = make([]types.Type, len(checker.module.types.fields), allocator)
|
||||
checker.record_field_conflicts = make([]types.Type, len(checker.module.types.fields), allocator)
|
||||
checker.record_field_conflict_spans = make([]source.Span, len(checker.module.types.fields), allocator)
|
||||
checker.poisoned_packages = make([]bool, max(len(ast_module.packages), 1), allocator)
|
||||
init_record_field_inference(&checker)
|
||||
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
||||
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
||||
@@ -12199,8 +12431,17 @@ check :: proc(
|
||||
}
|
||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
||||
for &diagnostic in checker.template_diagnostics {
|
||||
diagnostic = source.INVALID_DIAGNOSTIC
|
||||
for &diagnostic, index in checker.template_diagnostics {
|
||||
diagnostic = ast_module.functions[index].diagnostic
|
||||
pkg := ast_module.functions[index].pkg
|
||||
if diagnostic != source.INVALID_DIAGNOSTIC && int(pkg) < len(checker.poisoned_packages) {
|
||||
checker.poisoned_packages[pkg] = true
|
||||
}
|
||||
}
|
||||
for global in ast_module.globals {
|
||||
if global.diagnostic != source.INVALID_DIAGNOSTIC && int(global.pkg) < len(checker.poisoned_packages) {
|
||||
checker.poisoned_packages[global.pkg] = true
|
||||
}
|
||||
}
|
||||
defer {
|
||||
for spec in checker.specs {
|
||||
@@ -12220,6 +12461,7 @@ check :: proc(
|
||||
delete(checker.record_field_defaults, allocator)
|
||||
delete(checker.record_field_conflicts, allocator)
|
||||
delete(checker.record_field_conflict_spans, allocator)
|
||||
delete(checker.poisoned_packages, allocator)
|
||||
delete(checker.external_global_canonical, allocator)
|
||||
delete(checker.external_global_diagnostics, allocator)
|
||||
delete(checker.constants, allocator)
|
||||
|
||||
@@ -1486,7 +1486,7 @@ validate_declaration_aliases :: proc(state: ^State) {
|
||||
|
||||
import_id := find_type_import(state.module, alias.file, alias.qualifier)
|
||||
if import_id == ast.INVALID_IMPORT {
|
||||
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown package alias '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
||||
alias.diagnostic = source.addf(state.diagnostics, alias.span, "unknown symbol '%s'", symbol.resolve(state.symbols, alias.qualifier))
|
||||
alias.valid = false
|
||||
continue
|
||||
}
|
||||
@@ -1613,6 +1613,81 @@ canonical_type :: proc(
|
||||
return resolved
|
||||
}
|
||||
|
||||
diagnose_qualified_type_uses :: proc(state: ^State) {
|
||||
module := state.module
|
||||
for &type_use in module.type_uses {
|
||||
item, ok := types.node(&module.type_store, type_use.type)
|
||||
if !ok || item.qualifier == 0 || item.kind != .Named {
|
||||
continue
|
||||
}
|
||||
qualifier := symbol.Id(item.qualifier)
|
||||
name := symbol.Id(item.name)
|
||||
import_id := find_type_import(module, type_use.file, qualifier)
|
||||
if import_id == ast.INVALID_IMPORT {
|
||||
type_use.diagnostic = source.addf(
|
||||
state.diagnostics,
|
||||
type_use.span,
|
||||
"unknown symbol '%s'",
|
||||
symbol.resolve(state.symbols, qualifier),
|
||||
)
|
||||
source.set_primary_label(state.diagnostics, type_use.diagnostic, "unknown symbol")
|
||||
continue
|
||||
}
|
||||
import_item := module.imports[import_id]
|
||||
if !import_item.valid || import_item.target == ast.INVALID_PACKAGE ||
|
||||
int(import_item.target) >= len(module.packages) || !module.packages[import_item.target].available {
|
||||
type_use.diagnostic = source.addf(
|
||||
state.diagnostics,
|
||||
type_use.span,
|
||||
"unavailable imported package '%s'",
|
||||
symbol.resolve(state.symbols, qualifier),
|
||||
)
|
||||
continue
|
||||
}
|
||||
if !types.is_valid(types.find_named(&module.type_store, u32(import_item.target), u32(name))) {
|
||||
type_use.diagnostic = source.addf(
|
||||
state.diagnostics,
|
||||
type_use.span,
|
||||
"package '%s' has no member '%s'",
|
||||
symbol.resolve(state.symbols, qualifier),
|
||||
symbol.resolve(state.symbols, name),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type_resolution_diagnostic :: proc(module: ^ast.Module, value: types.Type, file: ast.File_Id, depth := 0) -> source.Diagnostic_Id {
|
||||
if depth > 64 {
|
||||
return source.INVALID_DIAGNOSTIC
|
||||
}
|
||||
for type_use in module.type_uses {
|
||||
if type_use.file == file && type_use.type == value && type_use.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
return type_use.diagnostic
|
||||
}
|
||||
}
|
||||
item, ok := types.node(&module.type_store, value)
|
||||
if !ok {
|
||||
return source.INVALID_DIAGNOSTIC
|
||||
}
|
||||
if diagnostic := type_resolution_diagnostic(module, item.child, file, depth+1);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
return diagnostic
|
||||
}
|
||||
if diagnostic := type_resolution_diagnostic(module, item.extra, file, depth+1);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
return diagnostic
|
||||
}
|
||||
if item.kind == .Function {
|
||||
for param in types.params_for(&module.type_store, value) {
|
||||
if diagnostic := type_resolution_diagnostic(module, param.type, file, depth+1);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
return diagnostic
|
||||
}
|
||||
}
|
||||
}
|
||||
return source.INVALID_DIAGNOSTIC
|
||||
}
|
||||
|
||||
canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||
original_count := len(module.type_store.nodes)
|
||||
mapping := make([]types.Type, original_count, allocator)
|
||||
@@ -1620,6 +1695,19 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||
defer delete(mapping, allocator)
|
||||
defer delete(visiting, allocator)
|
||||
for &function in module.functions {
|
||||
for param in function.params {
|
||||
if diagnostic := type_resolution_diagnostic(module, param.type, function.file);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
function.diagnostic = diagnostic
|
||||
}
|
||||
}
|
||||
signature_results := [2]types.Type{function.result, function.error}
|
||||
for value in signature_results {
|
||||
if diagnostic := type_resolution_diagnostic(module, value, function.file);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC && function.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
function.diagnostic = diagnostic
|
||||
}
|
||||
}
|
||||
for ¶m in function.params {
|
||||
param.type = canonical_type(module, param.type, mapping, visiting)
|
||||
}
|
||||
@@ -1627,9 +1715,17 @@ canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||
function.error = canonical_type(module, function.error, mapping, visiting)
|
||||
}
|
||||
for &global in module.globals {
|
||||
if diagnostic := type_resolution_diagnostic(module, global.type, global.file);
|
||||
diagnostic != source.INVALID_DIAGNOSTIC && global.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
global.diagnostic = diagnostic
|
||||
}
|
||||
global.type = canonical_type(module, global.type, mapping, visiting)
|
||||
}
|
||||
for &statement in module.statements {
|
||||
if diagnostic := type_resolution_diagnostic(module, statement.type, ast.File_Id(statement.span.file));
|
||||
diagnostic != source.INVALID_DIAGNOSTIC && statement.diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
statement.diagnostic = diagnostic
|
||||
}
|
||||
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
||||
}
|
||||
for &field in module.type_fields {
|
||||
@@ -1687,6 +1783,7 @@ load :: proc(
|
||||
}
|
||||
validate_imports(&state)
|
||||
validate_declaration_aliases(&state)
|
||||
diagnose_qualified_type_uses(&state)
|
||||
canonicalize_types(&module, allocator)
|
||||
return module, !state.root_failed
|
||||
}
|
||||
|
||||
@@ -428,6 +428,13 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
count_expr=u32(call),
|
||||
})
|
||||
}
|
||||
append(&parser.module.type_uses, ast.Type_Use{
|
||||
type=named,
|
||||
span=first.span,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return named
|
||||
}
|
||||
source.add(parser.diagnostics, tok.span, "expected a type")
|
||||
|
||||
+233
-10
@@ -2,6 +2,7 @@ package source
|
||||
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
|
||||
Source_Id :: distinct u32
|
||||
Diagnostic_Id :: distinct u32
|
||||
@@ -58,6 +59,20 @@ Diagnostic :: struct {
|
||||
severity: Severity,
|
||||
}
|
||||
|
||||
Annotation_Kind :: enum u8 {
|
||||
Primary,
|
||||
Secondary,
|
||||
Note,
|
||||
Help,
|
||||
}
|
||||
|
||||
Annotation :: struct {
|
||||
owner: Diagnostic_Id,
|
||||
span: Span,
|
||||
message: string,
|
||||
kind: Annotation_Kind,
|
||||
}
|
||||
|
||||
Severity :: enum u8 {
|
||||
Error,
|
||||
Warning,
|
||||
@@ -67,6 +82,7 @@ Diagnostics :: struct {
|
||||
source: ^Source,
|
||||
store: ^Store,
|
||||
items: [dynamic]Diagnostic,
|
||||
annotations: [dynamic]Annotation,
|
||||
index: map[Diagnostic_Key]Diagnostic_Id,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
@@ -130,6 +146,7 @@ init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -
|
||||
result.source = source_file
|
||||
result.allocator = allocator
|
||||
result.items.allocator = allocator
|
||||
result.annotations.allocator = allocator
|
||||
result.index.allocator = allocator
|
||||
return result
|
||||
}
|
||||
@@ -139,6 +156,7 @@ init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) ->
|
||||
result.store = store
|
||||
result.allocator = allocator
|
||||
result.items.allocator = allocator
|
||||
result.annotations.allocator = allocator
|
||||
result.index.allocator = allocator
|
||||
return result
|
||||
}
|
||||
@@ -148,7 +166,11 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||
for diagnostic in diagnostics.items {
|
||||
delete(diagnostic.message, diagnostics.allocator)
|
||||
}
|
||||
for annotation in diagnostics.annotations {
|
||||
delete(annotation.message, diagnostics.allocator)
|
||||
}
|
||||
delete(diagnostics.items)
|
||||
delete(diagnostics.annotations)
|
||||
}
|
||||
|
||||
add_with_severity :: proc(diagnostics: ^Diagnostics, span: Span, message: string, severity: Severity) -> Diagnostic_Id {
|
||||
@@ -192,6 +214,50 @@ addf_warning :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args
|
||||
return addf_with_severity(diagnostics, span, .Warning, format, ..args)
|
||||
}
|
||||
|
||||
add_annotation :: proc(
|
||||
diagnostics: ^Diagnostics,
|
||||
owner: Diagnostic_Id,
|
||||
kind: Annotation_Kind,
|
||||
span: Span,
|
||||
message: string,
|
||||
) {
|
||||
if _, ok := diagnostic_index(owner, len(diagnostics.items)); !ok {
|
||||
return
|
||||
}
|
||||
for annotation in diagnostics.annotations {
|
||||
if annotation.owner == owner && annotation.kind == kind && annotation.span == span &&
|
||||
annotation.message == message {
|
||||
return
|
||||
}
|
||||
}
|
||||
append(&diagnostics.annotations, Annotation{
|
||||
owner=owner,
|
||||
kind=kind,
|
||||
span=span,
|
||||
message=fmt.aprintf("%s", message, allocator=diagnostics.allocator),
|
||||
})
|
||||
}
|
||||
|
||||
set_primary_label :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
|
||||
index, ok := diagnostic_index(owner, len(diagnostics.items))
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
add_annotation(diagnostics, owner, .Primary, diagnostics.items[index].span, message)
|
||||
}
|
||||
|
||||
add_secondary_label :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, span: Span, message: string) {
|
||||
add_annotation(diagnostics, owner, .Secondary, span, message)
|
||||
}
|
||||
|
||||
add_note :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
|
||||
add_annotation(diagnostics, owner, .Note, {}, message)
|
||||
}
|
||||
|
||||
add_help :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, message: string) {
|
||||
add_annotation(diagnostics, owner, .Help, {}, message)
|
||||
}
|
||||
|
||||
line_and_column :: proc(source_file: ^Source, offset: Offset) -> (line, column: int) {
|
||||
if len(source_file.line_starts) > 0 {
|
||||
limit := min(int(offset), len(source_file.text))
|
||||
@@ -232,6 +298,140 @@ source_for_span :: proc(diagnostics: ^Diagnostics, span: Span) -> ^Source {
|
||||
return diagnostics.source
|
||||
}
|
||||
|
||||
annotation_for :: proc(diagnostics: ^Diagnostics, owner: Diagnostic_Id, kind: Annotation_Kind) -> (Annotation, bool) {
|
||||
for annotation in diagnostics.annotations {
|
||||
if annotation.owner == owner && annotation.kind == kind {
|
||||
return annotation, true
|
||||
}
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
line_bounds :: proc(source_file: ^Source, line: int) -> (start, end: int, ok: bool) {
|
||||
if line <= 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
if len(source_file.line_starts) == 0 {
|
||||
current := 1
|
||||
start = 0
|
||||
for value, index in transmute([]byte)source_file.text {
|
||||
if current == line && value == '\n' {
|
||||
end = index
|
||||
if end > start && source_file.text[end-1] == '\r' {
|
||||
end -= 1
|
||||
}
|
||||
return start, end, true
|
||||
}
|
||||
if value == '\n' {
|
||||
current += 1
|
||||
start = index+1
|
||||
}
|
||||
}
|
||||
if current == line {
|
||||
return start, len(source_file.text), true
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
if line > len(source_file.line_starts) {
|
||||
return 0, 0, false
|
||||
}
|
||||
start = int(source_file.line_starts[line-1])
|
||||
end = len(source_file.text)
|
||||
if line < len(source_file.line_starts) {
|
||||
end = int(source_file.line_starts[line])-1
|
||||
}
|
||||
if end > start && source_file.text[end-1] == '\r' {
|
||||
end -= 1
|
||||
}
|
||||
return start, end, true
|
||||
}
|
||||
|
||||
write_spaces :: proc(builder: ^strings.Builder, count: int) {
|
||||
for _ in 0..<max(count, 0) {
|
||||
strings.write_byte(builder, ' ')
|
||||
}
|
||||
}
|
||||
|
||||
write_expanded :: proc(builder: ^strings.Builder, text: string, start_column := 0) -> int {
|
||||
column := start_column
|
||||
for value in transmute([]byte)text {
|
||||
if value == '\t' {
|
||||
width := 4-column%4
|
||||
write_spaces(builder, width)
|
||||
column += width
|
||||
} else {
|
||||
strings.write_byte(builder, value)
|
||||
column += 1
|
||||
}
|
||||
}
|
||||
return column
|
||||
}
|
||||
|
||||
display_width :: proc(text: string, start_column := 0) -> int {
|
||||
column := start_column
|
||||
for value in transmute([]byte)text {
|
||||
column += 4-column%4 if value == '\t' else 1
|
||||
}
|
||||
return column-start_column
|
||||
}
|
||||
|
||||
decimal_width :: proc(value: int) -> int {
|
||||
width := 1
|
||||
for remaining := value; remaining >= 10; remaining /= 10 {
|
||||
width += 1
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
write_excerpt :: proc(
|
||||
builder: ^strings.Builder,
|
||||
diagnostics: ^Diagnostics,
|
||||
span: Span,
|
||||
label: string,
|
||||
primary: bool,
|
||||
) -> bool {
|
||||
if span == (Span{}) {
|
||||
return false
|
||||
}
|
||||
source_file := source_for_span(diagnostics, span)
|
||||
if source_file == nil {
|
||||
return false
|
||||
}
|
||||
line, column := line_and_column(source_file, span.start)
|
||||
line_start, line_end, ok := line_bounds(source_file, line)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
prefix := " -->" if primary else " :::"
|
||||
fmt.sbprintf(builder, "%s %s:%d:%d\n", prefix, source_file.path, line, column)
|
||||
gutter := decimal_width(line)
|
||||
write_spaces(builder, gutter+1)
|
||||
strings.write_string(builder, "|\n")
|
||||
fmt.sbprintf(builder, "%*d | ", gutter, line)
|
||||
_ = write_expanded(builder, source_file.text[line_start:line_end])
|
||||
strings.write_byte(builder, '\n')
|
||||
write_spaces(builder, gutter+1)
|
||||
strings.write_string(builder, "| ")
|
||||
start := clamp(int(span.start), line_start, line_end)
|
||||
indent := display_width(source_file.text[line_start:start])
|
||||
width := 1
|
||||
if start < line_end {
|
||||
end := clamp(int(span.end), start+1, line_end)
|
||||
width = max(display_width(source_file.text[start:end], indent), 1)
|
||||
}
|
||||
write_spaces(builder, indent)
|
||||
marker := u8('^') if primary else u8('-')
|
||||
for _ in 0..<width {
|
||||
strings.write_byte(builder, marker)
|
||||
}
|
||||
if len(label) > 0 {
|
||||
strings.write_byte(builder, ' ')
|
||||
strings.write_string(builder, label)
|
||||
}
|
||||
strings.write_byte(builder, '\n')
|
||||
return true
|
||||
}
|
||||
|
||||
format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := context.allocator) -> string {
|
||||
index, ok := diagnostic_index(id, len(diagnostics.items))
|
||||
if !ok {
|
||||
@@ -246,19 +446,42 @@ format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := contex
|
||||
diagnostic := diagnostics.items[index]
|
||||
source_file := source_for_span(diagnostics, diagnostic.span)
|
||||
severity := "warning" if diagnostic.severity == .Warning else "error"
|
||||
if diagnostic.span == (Span{}) {
|
||||
path := source_file.path if source_file != nil else "<unknown>"
|
||||
return fmt.aprintf("%s: %s: %s", path, severity, diagnostic.message, allocator=allocator)
|
||||
}
|
||||
if source_file == nil {
|
||||
return fmt.aprintf("<unknown>: %s: %s", severity, diagnostic.message, allocator=allocator)
|
||||
}
|
||||
line, column := line_and_column(source_file, diagnostic.span.start)
|
||||
return fmt.aprintf(
|
||||
"%s:%d:%d: %s: %s",
|
||||
source_file.path,
|
||||
line,
|
||||
column,
|
||||
severity,
|
||||
diagnostic.message,
|
||||
allocator=allocator,
|
||||
)
|
||||
builder := strings.builder_make(allocator)
|
||||
fmt.sbprintf(&builder, "%s: %s\n", severity, diagnostic.message)
|
||||
primary_label := ""
|
||||
if annotation, found := annotation_for(diagnostics, id, .Primary); found {
|
||||
primary_label = annotation.message
|
||||
}
|
||||
_ = write_excerpt(&builder, diagnostics, diagnostic.span, primary_label, true)
|
||||
for annotation in diagnostics.annotations {
|
||||
if annotation.owner == id && annotation.kind == .Secondary {
|
||||
_ = write_excerpt(&builder, diagnostics, annotation.span, annotation.message, false)
|
||||
}
|
||||
}
|
||||
for annotation in diagnostics.annotations {
|
||||
if annotation.owner != id {
|
||||
continue
|
||||
}
|
||||
#partial switch annotation.kind {
|
||||
case .Note:
|
||||
fmt.sbprintf(&builder, "note: %s\n", annotation.message)
|
||||
case .Help:
|
||||
fmt.sbprintf(&builder, "help: %s\n", annotation.message)
|
||||
case:
|
||||
}
|
||||
}
|
||||
result := strings.to_string(builder)
|
||||
if len(result) > 0 && result[len(result)-1] == '\n' {
|
||||
return result[:len(result)-1]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
print_all :: proc(diagnostics: ^Diagnostics) {
|
||||
|
||||
Reference in New Issue
Block a user