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_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 {
|
Expr_Kind :: enum u8 {
|
||||||
Invalid,
|
Invalid,
|
||||||
Integer,
|
Integer,
|
||||||
@@ -325,6 +333,7 @@ Module :: struct {
|
|||||||
c_trampolines: [dynamic]Trampoline,
|
c_trampolines: [dynamic]Trampoline,
|
||||||
strings: [dynamic]string,
|
strings: [dynamic]string,
|
||||||
type_fields: [dynamic]types.Field,
|
type_fields: [dynamic]types.Field,
|
||||||
|
type_uses: [dynamic]Type_Use,
|
||||||
type_store: types.Store,
|
type_store: types.Store,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -345,6 +354,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
|||||||
module.c_trampolines.allocator = allocator
|
module.c_trampolines.allocator = allocator
|
||||||
module.strings.allocator = allocator
|
module.strings.allocator = allocator
|
||||||
module.type_fields.allocator = allocator
|
module.type_fields.allocator = allocator
|
||||||
|
module.type_uses.allocator = allocator
|
||||||
return module
|
return module
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,5 +407,6 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(module.c_trampolines)
|
delete(module.c_trampolines)
|
||||||
delete(module.strings)
|
delete(module.strings)
|
||||||
delete(module.type_fields)
|
delete(module.type_fields)
|
||||||
|
delete(module.type_uses)
|
||||||
types.destroy_store(&module.type_store)
|
types.destroy_store(&module.type_store)
|
||||||
}
|
}
|
||||||
|
|||||||
+277
-35
@@ -208,6 +208,7 @@ Checker :: struct {
|
|||||||
record_field_conflicts: []types.Type,
|
record_field_conflicts: []types.Type,
|
||||||
record_field_conflict_spans: []source.Span,
|
record_field_conflict_spans: []source.Span,
|
||||||
record_field_demands_dirty: bool,
|
record_field_demands_dirty: bool,
|
||||||
|
poisoned_packages: []bool,
|
||||||
external_global_canonical: []ast.Global_Id,
|
external_global_canonical: []ast.Global_Id,
|
||||||
external_global_diagnostics: []source.Diagnostic_Id,
|
external_global_diagnostics: []source.Diagnostic_Id,
|
||||||
constants: []Constant,
|
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
|
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 {
|
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 {
|
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))
|
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 {
|
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 {
|
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) {
|
if symbol.is_valid(expr.qualifier) {
|
||||||
return source.addf(
|
return source.addf(
|
||||||
@@ -1516,12 +1531,20 @@ add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
|
|||||||
symbol_text(checker, expr.name),
|
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 {
|
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 {
|
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) {
|
if symbol.is_valid(expr.qualifier) {
|
||||||
return source.addf(
|
return source.addf(
|
||||||
@@ -1532,7 +1555,10 @@ add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
|
|||||||
symbol_text(checker, expr.name),
|
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) {
|
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)
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
if !available {
|
if !available {
|
||||||
|
_ = add_package_resolution_diagnostic(checker, expr, file)
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
template := find_template(checker, expr.name, target_pkg, expr_lookup_file(expr, file))
|
||||||
@@ -3145,6 +3172,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
has_comptime := function_has_comptime_params(function)
|
has_comptime := function_has_comptime_params(function)
|
||||||
|
signature_poisoned := function.diagnostic != source.INVALID_DIAGNOSTIC
|
||||||
locals: [dynamic]symbol.Id
|
locals: [dynamic]symbol.Id
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
@@ -3152,7 +3180,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
if param.comptime_value || !has_comptime {
|
if param.comptime_value || !has_comptime {
|
||||||
param_type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
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 {
|
if function.c_abi {
|
||||||
checker.template_diagnostics[function_id] = source.add(
|
checker.template_diagnostics[function_id] = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -3169,7 +3197,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, param.name),
|
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);
|
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, param_type);
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
checker.template_diagnostics[function_id] = diagnostic
|
checker.template_diagnostics[function_id] = diagnostic
|
||||||
@@ -3197,7 +3225,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
append(&locals, param.name)
|
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.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
param.span,
|
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)
|
result_type := type_from_syntax(checker, function.result, function.pkg, function.file)
|
||||||
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, result_type);
|
||||||
diagnostic != source.INVALID_DIAGNOSTIC {
|
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_type := type_from_syntax(checker, function.error, function.pkg, function.file)
|
||||||
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
error_sum := types.is_enum(error_type, &checker.module.types) ||
|
||||||
types.is_tagged_union(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.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
function.span,
|
function.span,
|
||||||
@@ -3247,7 +3275,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, function.name),
|
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.template_diagnostics[function_id] = source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
function.span,
|
function.span,
|
||||||
@@ -3255,7 +3283,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, function.name),
|
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 {
|
for param in function.params {
|
||||||
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
param_type := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||||
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
|
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])
|
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 {
|
for item in checker.module.types.nodes {
|
||||||
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
if !(item.declared && (item.kind == .Struct || item.kind == .Union) &&
|
||||||
!item.c_layout && symbol.is_valid(symbol.Id(item.name))) {
|
!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)
|
start := int(item.field_start)
|
||||||
if slot >= start && slot < start+int(item.field_count) &&
|
if slot >= start && slot < start+int(item.field_count) &&
|
||||||
slot >= 0 && slot < len(checker.module.types.fields) {
|
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) {
|
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) {
|
if !types.is_constraint(constraint) {
|
||||||
continue
|
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 {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -3509,7 +3537,8 @@ finalize_record_field_inference :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, record_name), symbol_text(checker, field_name),
|
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(
|
source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
source.Span{},
|
source.Span{},
|
||||||
@@ -5622,6 +5651,10 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
// Demands accumulate in global_demands so the default never blocks a later
|
// Demands accumulate in global_demands so the default never blocks a later
|
||||||
// cross-family demand (e.g. integer literal -> unsigned or float).
|
// cross-family demand (e.g. integer literal -> unsigned or float).
|
||||||
for global, index in checker.ast_module.globals {
|
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(
|
declared := resolve_inferred_array(
|
||||||
checker,
|
checker,
|
||||||
type_from_syntax(checker, global.type, global.pkg, global.file),
|
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
|
// Backward demands: a global pushes its own (declared or already-resolved) type
|
||||||
// onto open numeric slots reachable through names and numeric arithmetic.
|
// onto open numeric slots reachable through names and numeric arithmetic.
|
||||||
for global, index in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
if global.external {
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
demand := checker.global_types[index]
|
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
|
// concrete-typed ones) for its side effect of specializing called functions and
|
||||||
// recording demands from call arguments in their initializers.
|
// recording demands from call arguments in their initializers.
|
||||||
for global, index in checker.ast_module.globals {
|
for global, index in checker.ast_module.globals {
|
||||||
if global.external {
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
declared := resolve_inferred_array(
|
declared := resolve_inferred_array(
|
||||||
@@ -5765,7 +5798,8 @@ infer_all :: proc(checker: ^Checker) {
|
|||||||
// No authoritative demand can still arrive. Assign final defaults, then
|
// No authoritative demand can still arrive. Assign final defaults, then
|
||||||
// continue the same fixpoint so dependent globals/specs observe them.
|
// continue the same fixpoint so dependent globals/specs observe them.
|
||||||
for global, index in checker.ast_module.globals {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if checker.global_open_const[index] {
|
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)
|
mark_spec_demanded(checker, find_spec(checker, checker.io_provider_template, nil), &stack)
|
||||||
}
|
}
|
||||||
for global in checker.ast_module.globals {
|
for global in checker.ast_module.globals {
|
||||||
if global.external {
|
if global.external || global.diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
_ = 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) {
|
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
|
||||||
for existing in values {
|
for existing in values {
|
||||||
if existing == value {
|
if existing == value {
|
||||||
@@ -5935,6 +5986,9 @@ coerce_expr :: proc(
|
|||||||
if expr_id == hir.INVALID_EXPR {
|
if expr_id == hir.INVALID_EXPR {
|
||||||
return expr_id
|
return expr_id
|
||||||
}
|
}
|
||||||
|
if _, invalid := invalid_expr_diagnostic(checker, expr_id); invalid {
|
||||||
|
return expr_id
|
||||||
|
}
|
||||||
actual := checker.module.exprs[expr_id].type
|
actual := checker.module.exprs[expr_id].type
|
||||||
if types.equal(actual, expected) {
|
if types.equal(actual, expected) {
|
||||||
return expr_id
|
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)
|
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) {
|
if !hir_is_location(checker, value) {
|
||||||
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
|
id := source.add(checker.diagnostics, expr.span, "'&' requires an addressable location")
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
@@ -6936,6 +6993,9 @@ build_compound_expr :: proc(
|
|||||||
})
|
})
|
||||||
case .Deref:
|
case .Deref:
|
||||||
pointer := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
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
|
pointer_type := checker.module.exprs[pointer].type
|
||||||
if !types.is_pointer(pointer_type, store) {
|
if !types.is_pointer(pointer_type, store) {
|
||||||
id := source.add(checker.diagnostics, expr.span, "postfix '^' requires a pointer")
|
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)
|
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 := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file)
|
||||||
index = coerce_expr(checker, index, types.USIZE, expr.span)
|
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
|
container_type := checker.module.exprs[container].type
|
||||||
item, ok := types.container(container_type, store)
|
item, ok := types.container(container_type, store)
|
||||||
if !ok {
|
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)
|
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
|
container_type := checker.module.exprs[container].type
|
||||||
item, ok := types.container(container_type, store)
|
item, ok := types.container(container_type, store)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -6987,6 +7053,10 @@ build_compound_expr :: proc(
|
|||||||
if bound != ast.INVALID_EXPR {
|
if bound != ast.INVALID_EXPR {
|
||||||
bounds[index] = build_nested_expr(checker, bound, locals, global_reads, calls, types.USIZE, pkg, file)
|
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] = 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
|
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)
|
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)
|
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
|
base_type := checker.module.exprs[base].type
|
||||||
result, _ := build_field_from_value(checker, expr, base, base_type)
|
result, _ := build_field_from_value(checker, expr, base, base_type)
|
||||||
return result
|
return result
|
||||||
case .Unwrap:
|
case .Unwrap:
|
||||||
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
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
|
optional_type := checker.module.exprs[optional].type
|
||||||
if !types.is_optional(optional_type, store) {
|
if !types.is_optional(optional_type, store) {
|
||||||
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
|
id := source.add(checker.diagnostics, expr.span, "postfix '?' requires an optional")
|
||||||
@@ -7016,6 +7092,9 @@ build_compound_expr :: proc(
|
|||||||
})
|
})
|
||||||
case .Orelse:
|
case .Orelse:
|
||||||
optional := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
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
|
optional_type := checker.module.exprs[optional].type
|
||||||
if !types.is_optional(optional_type, store) {
|
if !types.is_optional(optional_type, store) {
|
||||||
id := source.add(checker.diagnostics, expr.span, "'orelse' requires an optional left operand")
|
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
|
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)
|
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
|
channel_type := checker.module.exprs[channel].type
|
||||||
success := types.fallible_success(channel_type, store)
|
success := types.fallible_success(channel_type, store)
|
||||||
if !types.is_valid(success) {
|
if !types.is_valid(success) {
|
||||||
@@ -7076,6 +7158,9 @@ build_compound_expr :: proc(
|
|||||||
case .Catch:
|
case .Catch:
|
||||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
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)
|
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
|
channel_type := checker.module.exprs[channel].type
|
||||||
success := types.fallible_success(channel_type, store)
|
success := types.fallible_success(channel_type, store)
|
||||||
if !types.is_valid(success) {
|
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)
|
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)
|
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
|
child := expected_child
|
||||||
if !types.is_valid(child) {
|
if !types.is_valid(child) {
|
||||||
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
child = types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||||
@@ -7174,6 +7262,9 @@ build_compound_expr :: proc(
|
|||||||
})
|
})
|
||||||
case .Not:
|
case .Not:
|
||||||
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
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
|
operand_type := checker.module.exprs[operand].type
|
||||||
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(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")
|
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
|
||||||
@@ -7186,6 +7277,9 @@ build_compound_expr :: proc(
|
|||||||
case .And, .Or:
|
case .And, .Or:
|
||||||
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
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)
|
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
|
left_type := checker.module.exprs[left].type
|
||||||
right_type := checker.module.exprs[right].type
|
right_type := checker.module.exprs[right].type
|
||||||
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_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)
|
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)
|
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
|
left_type := checker.module.exprs[left].type
|
||||||
right_type := checker.module.exprs[right].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
|
operand_type := types.INVALID
|
||||||
if types.is_enum(left_type, store) || types.is_enum(right_type, store) {
|
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) {
|
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,
|
left, right: hir.Expr_Id,
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
) -> hir.Expr_Id {
|
) -> 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).
|
// Pointer arithmetic is only defined for `+` (many-pointer + usize).
|
||||||
if op == .Add &&
|
if op == .Add &&
|
||||||
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
types.is_many_pointer(checker.module.exprs[left].type, &checker.module.types) &&
|
||||||
@@ -7960,6 +8057,9 @@ build_expr :: proc(
|
|||||||
checker.diagnostics, expr.span, message,
|
checker.diagnostics, expr.span, message,
|
||||||
symbol_text(checker, expr.name), len(function.params), len(expr.args),
|
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 {
|
} else if len(mapping_failure) > 0 {
|
||||||
id = source.addf(
|
id = source.addf(
|
||||||
checker.diagnostics, expr.span,
|
checker.diagnostics, expr.span,
|
||||||
@@ -8009,6 +8109,11 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
if frame.stage == 5 {
|
if frame.stage == 5 {
|
||||||
operand := last
|
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
|
operand_type := checker.module.exprs[operand].type
|
||||||
if !types.is_signed(operand_type, checker.target) && !types.is_float(operand_type, checker.target) {
|
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")
|
id := source.add(checker.diagnostics, expr.span, "negation requires a signed integer or float")
|
||||||
@@ -8071,6 +8176,17 @@ build_expr :: proc(
|
|||||||
continue
|
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]
|
function := checker.ast_module.functions[frame.template]
|
||||||
comptime_values: []Comptime_Value
|
comptime_values: []Comptime_Value
|
||||||
comptime_ok := false
|
comptime_ok := false
|
||||||
@@ -8236,6 +8352,11 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
if frame.stage == 6 {
|
if frame.stage == 6 {
|
||||||
callee := last
|
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)
|
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
|
||||||
if !ok {
|
if !ok {
|
||||||
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
|
||||||
@@ -8279,6 +8400,17 @@ build_expr :: proc(
|
|||||||
continue
|
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
|
callee_type := checker.module.exprs[frame.left].type
|
||||||
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -8897,6 +9029,22 @@ build_block :: proc(
|
|||||||
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
|
duplicate_start := scope_start if duplicate_scope_start < 0 else duplicate_scope_start
|
||||||
for statement_id in statements {
|
for statement_id in statements {
|
||||||
statement := checker.ast_module.statements[statement_id]
|
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 {
|
switch statement.kind {
|
||||||
case .Declaration:
|
case .Declaration:
|
||||||
// A value block (`x :: { ... yield v }` / `x T = { ... }`): the parser
|
// 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,
|
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
types.INVALID, ctx.pkg, ctx.file,
|
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
|
target_type := checker.module.exprs[target_expr].type
|
||||||
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
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,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
rhs_expected, ctx.pkg, ctx.file,
|
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) {
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||||
assignment_op = .Pointer_Add
|
assignment_op = .Pointer_Add
|
||||||
if statement.assignment_op != .Add {
|
if statement.assignment_op != .Add {
|
||||||
@@ -9208,6 +9374,15 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
target_expr := build_global_reference(checker, global, statement.span, ctx.global_reads)
|
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
|
target_type := checker.module.exprs[target_expr].type
|
||||||
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||||
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
||||||
@@ -9425,7 +9600,14 @@ build_block :: proc(
|
|||||||
})
|
})
|
||||||
case .Expression:
|
case .Expression:
|
||||||
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
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 '_'")
|
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(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
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
|
checker.current_comptime_values = spec.comptime_values
|
||||||
defer checker.current_comptime_values = previous_comptime
|
defer checker.current_comptime_values = previous_comptime
|
||||||
signature_diagnostic := source.INVALID_DIAGNOSTIC
|
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
|
checker.specs[id].result = types.I64
|
||||||
spec.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 {
|
for arg in spec.args {
|
||||||
if !is_runtime_type(checker, arg) {
|
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
|
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
|
||||||
native_main := function.pkg == 0 && function.name == checker.main_symbol && checker.entry_point == .Plain
|
native_main := function.pkg == 0 && function.name == checker.main_symbol && checker.entry_point == .Plain
|
||||||
if !function.has_body {
|
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)))
|
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
|
||||||
append(
|
append(
|
||||||
&checker.module.functions,
|
&checker.module.functions,
|
||||||
@@ -11665,6 +11852,22 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
append(&body, block_stmt)
|
append(&body, block_stmt)
|
||||||
}
|
}
|
||||||
delete(block, checker.allocator)
|
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 &&
|
fallible_void := types.kind(spec.result, &checker.module.types) == .Fallible &&
|
||||||
types.is_void(types.fallible_success(spec.result, &checker.module.types))
|
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) {
|
build_globals :: proc(checker: ^Checker) {
|
||||||
for global, global_index in checker.ast_module.globals {
|
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 {
|
if global.external {
|
||||||
global_type := checker.global_types[global_index]
|
global_type := checker.global_types[global_index]
|
||||||
writable := global.writable
|
writable := global.writable
|
||||||
@@ -11841,6 +12066,12 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
global_type = checker.module.exprs[expr].type
|
global_type = checker.module.exprs[expr].type
|
||||||
}
|
}
|
||||||
diagnostic := source.INVALID_DIAGNOSTIC
|
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) {
|
if !global.immutable && is_undefined_expr(checker, global.expr) {
|
||||||
diagnostic = source.add(
|
diagnostic = source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
@@ -12188,6 +12419,7 @@ check :: proc(
|
|||||||
checker.record_field_defaults = make([]types.Type, len(checker.module.types.fields), allocator)
|
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_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.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)
|
init_record_field_inference(&checker)
|
||||||
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
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)
|
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.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||||
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
||||||
for &diagnostic in checker.template_diagnostics {
|
for &diagnostic, index in checker.template_diagnostics {
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC
|
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 {
|
defer {
|
||||||
for spec in checker.specs {
|
for spec in checker.specs {
|
||||||
@@ -12220,6 +12461,7 @@ check :: proc(
|
|||||||
delete(checker.record_field_defaults, allocator)
|
delete(checker.record_field_defaults, allocator)
|
||||||
delete(checker.record_field_conflicts, allocator)
|
delete(checker.record_field_conflicts, allocator)
|
||||||
delete(checker.record_field_conflict_spans, allocator)
|
delete(checker.record_field_conflict_spans, allocator)
|
||||||
|
delete(checker.poisoned_packages, allocator)
|
||||||
delete(checker.external_global_canonical, allocator)
|
delete(checker.external_global_canonical, allocator)
|
||||||
delete(checker.external_global_diagnostics, allocator)
|
delete(checker.external_global_diagnostics, allocator)
|
||||||
delete(checker.constants, 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)
|
import_id := find_type_import(state.module, alias.file, alias.qualifier)
|
||||||
if import_id == ast.INVALID_IMPORT {
|
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
|
alias.valid = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1613,6 +1613,81 @@ canonical_type :: proc(
|
|||||||
return resolved
|
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) {
|
canonicalize_types :: proc(module: ^ast.Module, allocator: mem.Allocator) {
|
||||||
original_count := len(module.type_store.nodes)
|
original_count := len(module.type_store.nodes)
|
||||||
mapping := make([]types.Type, original_count, allocator)
|
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(mapping, allocator)
|
||||||
defer delete(visiting, allocator)
|
defer delete(visiting, allocator)
|
||||||
for &function in module.functions {
|
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 {
|
for ¶m in function.params {
|
||||||
param.type = canonical_type(module, param.type, mapping, visiting)
|
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)
|
function.error = canonical_type(module, function.error, mapping, visiting)
|
||||||
}
|
}
|
||||||
for &global in module.globals {
|
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)
|
global.type = canonical_type(module, global.type, mapping, visiting)
|
||||||
}
|
}
|
||||||
for &statement in module.statements {
|
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)
|
statement.type = canonical_type(module, statement.type, mapping, visiting)
|
||||||
}
|
}
|
||||||
for &field in module.type_fields {
|
for &field in module.type_fields {
|
||||||
@@ -1687,6 +1783,7 @@ load :: proc(
|
|||||||
}
|
}
|
||||||
validate_imports(&state)
|
validate_imports(&state)
|
||||||
validate_declaration_aliases(&state)
|
validate_declaration_aliases(&state)
|
||||||
|
diagnose_qualified_type_uses(&state)
|
||||||
canonicalize_types(&module, allocator)
|
canonicalize_types(&module, allocator)
|
||||||
return module, !state.root_failed
|
return module, !state.root_failed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -428,6 +428,13 @@ parse_type_atom :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
count_expr=u32(call),
|
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
|
return named
|
||||||
}
|
}
|
||||||
source.add(parser.diagnostics, tok.span, "expected a type")
|
source.add(parser.diagnostics, tok.span, "expected a type")
|
||||||
|
|||||||
+233
-10
@@ -2,6 +2,7 @@ package source
|
|||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:strings"
|
||||||
|
|
||||||
Source_Id :: distinct u32
|
Source_Id :: distinct u32
|
||||||
Diagnostic_Id :: distinct u32
|
Diagnostic_Id :: distinct u32
|
||||||
@@ -58,6 +59,20 @@ Diagnostic :: struct {
|
|||||||
severity: Severity,
|
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 {
|
Severity :: enum u8 {
|
||||||
Error,
|
Error,
|
||||||
Warning,
|
Warning,
|
||||||
@@ -67,6 +82,7 @@ Diagnostics :: struct {
|
|||||||
source: ^Source,
|
source: ^Source,
|
||||||
store: ^Store,
|
store: ^Store,
|
||||||
items: [dynamic]Diagnostic,
|
items: [dynamic]Diagnostic,
|
||||||
|
annotations: [dynamic]Annotation,
|
||||||
index: map[Diagnostic_Key]Diagnostic_Id,
|
index: map[Diagnostic_Key]Diagnostic_Id,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
@@ -130,6 +146,7 @@ init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -
|
|||||||
result.source = source_file
|
result.source = source_file
|
||||||
result.allocator = allocator
|
result.allocator = allocator
|
||||||
result.items.allocator = allocator
|
result.items.allocator = allocator
|
||||||
|
result.annotations.allocator = allocator
|
||||||
result.index.allocator = allocator
|
result.index.allocator = allocator
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -139,6 +156,7 @@ init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) ->
|
|||||||
result.store = store
|
result.store = store
|
||||||
result.allocator = allocator
|
result.allocator = allocator
|
||||||
result.items.allocator = allocator
|
result.items.allocator = allocator
|
||||||
|
result.annotations.allocator = allocator
|
||||||
result.index.allocator = allocator
|
result.index.allocator = allocator
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -148,7 +166,11 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
|||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
delete(diagnostic.message, diagnostics.allocator)
|
delete(diagnostic.message, diagnostics.allocator)
|
||||||
}
|
}
|
||||||
|
for annotation in diagnostics.annotations {
|
||||||
|
delete(annotation.message, diagnostics.allocator)
|
||||||
|
}
|
||||||
delete(diagnostics.items)
|
delete(diagnostics.items)
|
||||||
|
delete(diagnostics.annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_with_severity :: proc(diagnostics: ^Diagnostics, span: Span, message: string, severity: Severity) -> Diagnostic_Id {
|
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)
|
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) {
|
line_and_column :: proc(source_file: ^Source, offset: Offset) -> (line, column: int) {
|
||||||
if len(source_file.line_starts) > 0 {
|
if len(source_file.line_starts) > 0 {
|
||||||
limit := min(int(offset), len(source_file.text))
|
limit := min(int(offset), len(source_file.text))
|
||||||
@@ -232,6 +298,140 @@ source_for_span :: proc(diagnostics: ^Diagnostics, span: Span) -> ^Source {
|
|||||||
return diagnostics.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 {
|
format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := context.allocator) -> string {
|
||||||
index, ok := diagnostic_index(id, len(diagnostics.items))
|
index, ok := diagnostic_index(id, len(diagnostics.items))
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -246,19 +446,42 @@ format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := contex
|
|||||||
diagnostic := diagnostics.items[index]
|
diagnostic := diagnostics.items[index]
|
||||||
source_file := source_for_span(diagnostics, diagnostic.span)
|
source_file := source_for_span(diagnostics, diagnostic.span)
|
||||||
severity := "warning" if diagnostic.severity == .Warning else "error"
|
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 {
|
if source_file == nil {
|
||||||
return fmt.aprintf("<unknown>: %s: %s", severity, diagnostic.message, allocator=allocator)
|
return fmt.aprintf("<unknown>: %s: %s", severity, diagnostic.message, allocator=allocator)
|
||||||
}
|
}
|
||||||
line, column := line_and_column(source_file, diagnostic.span.start)
|
builder := strings.builder_make(allocator)
|
||||||
return fmt.aprintf(
|
fmt.sbprintf(&builder, "%s: %s\n", severity, diagnostic.message)
|
||||||
"%s:%d:%d: %s: %s",
|
primary_label := ""
|
||||||
source_file.path,
|
if annotation, found := annotation_for(diagnostics, id, .Primary); found {
|
||||||
line,
|
primary_label = annotation.message
|
||||||
column,
|
}
|
||||||
severity,
|
_ = write_excerpt(&builder, diagnostics, diagnostic.span, primary_label, true)
|
||||||
diagnostic.message,
|
for annotation in diagnostics.annotations {
|
||||||
allocator=allocator,
|
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) {
|
print_all :: proc(diagnostics: ^Diagnostics) {
|
||||||
|
|||||||
+222
-15
@@ -917,8 +917,8 @@ multi_source_diagnostics_report_the_originating_file :: proc(t: ^testing.T) {
|
|||||||
found := false
|
found := false
|
||||||
for _, diagnostic_index in diagnostics.items {
|
for _, diagnostic_index in diagnostics.items {
|
||||||
message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index))
|
message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index))
|
||||||
if strings.contains(message, "/b.bro:2:9:") &&
|
if strings.contains(message, "--> ") && strings.contains(message, "/b.bro:2:9") &&
|
||||||
strings.contains(message, "unknown package alias 'math'") {
|
strings.contains(message, "unknown symbol 'math'") {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
delete(message)
|
delete(message)
|
||||||
@@ -1956,7 +1956,7 @@ old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
found := [len(names)]bool{}
|
found := [len(names)]bool{}
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
if !strings.contains(diagnostic.message, "unresolved function") {
|
if !strings.contains(diagnostic.message, "unknown symbol") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for name, index in names {
|
for name, index in names {
|
||||||
@@ -4306,6 +4306,34 @@ main func() i32 {
|
|||||||
testing.expect(t, indirect_calls > 0)
|
testing.expect(t, indirect_calls > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
qualified_value_calls_do_not_imply_package_resolution :: proc(t: ^testing.T) {
|
||||||
|
text := `Callbacks :: struct {
|
||||||
|
call @func() void
|
||||||
|
}
|
||||||
|
main func() void {
|
||||||
|
callbacks Callbacks = Callbacks{call = func() void {}}
|
||||||
|
callbacks.call()
|
||||||
|
missing.call()
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 1)
|
||||||
|
testing.expect_value(t, diagnostics.items[0].message, "unknown symbol 'missing'")
|
||||||
|
testing.expect(t, !strings.contains(diagnostics.items[0].message, "package"))
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
||||||
text := `main func() i32 {
|
text := `main func() i32 {
|
||||||
@@ -4330,7 +4358,7 @@ function_literals_do_not_capture_locals :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
found := false
|
found := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
found = found || strings.contains(diagnostic.message, "unresolved global 'offset'")
|
found = found || strings.contains(diagnostic.message, "unknown symbol 'offset'")
|
||||||
}
|
}
|
||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
@@ -7210,7 +7238,7 @@ source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t:
|
|||||||
testing.expect_value(t, len(store.items[source_id].line_starts), 3)
|
testing.expect_value(t, len(store.items[source_id].line_starts), 3)
|
||||||
testing.expect_value(t, first, second)
|
testing.expect_value(t, first, second)
|
||||||
testing.expect_value(t, len(diagnostics.items), 2)
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
testing.expect(t, strings.contains(formatted, "owned.bro:2:1:"))
|
testing.expect(t, strings.contains(formatted, "--> owned.bro:2:1"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7233,8 +7261,92 @@ diagnostic_warnings_format_and_dedupe_by_severity :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, len(diagnostics.items), 2)
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
testing.expect_value(t, diagnostics.items[warning].severity, source.Severity.Warning)
|
testing.expect_value(t, diagnostics.items[warning].severity, source.Severity.Warning)
|
||||||
testing.expect_value(t, diagnostics.items[err].severity, source.Severity.Error)
|
testing.expect_value(t, diagnostics.items[err].severity, source.Severity.Error)
|
||||||
testing.expect(t, strings.contains(formatted_warning, "test.bro:1:1: warning: same"))
|
testing.expect(t, strings.contains(formatted_warning, "warning: same\n --> test.bro:1:1"))
|
||||||
testing.expect(t, strings.contains(formatted_error, "test.bro:1:1: error: same"))
|
testing.expect(t, strings.contains(formatted_error, "error: same\n --> test.bro:1:1"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
rich_diagnostics_render_labels_notes_help_and_tabs :: proc(t: ^testing.T) {
|
||||||
|
store := source.init_store()
|
||||||
|
defer source.destroy_store(&store)
|
||||||
|
primary_file := source.add_source(&store, "main.bro", "\tmissing()\nnext()\n")
|
||||||
|
definition_file := source.add_source(&store, "dep.bro", "value :: 1\n")
|
||||||
|
diagnostics := source.init_store_diagnostics(&store)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
id := source.add(&diagnostics, source.Span{file=primary_file, start=1, end=8}, "unknown symbol 'missing'")
|
||||||
|
source.set_primary_label(&diagnostics, id, "unknown symbol")
|
||||||
|
source.set_primary_label(&diagnostics, id, "unknown symbol")
|
||||||
|
source.add_secondary_label(
|
||||||
|
&diagnostics, id,
|
||||||
|
source.Span{file=definition_file, start=0, end=5},
|
||||||
|
"related declaration",
|
||||||
|
)
|
||||||
|
source.add_note(&diagnostics, id, "names resolve in the current scope")
|
||||||
|
source.add_note(&diagnostics, id, "names resolve in the current scope")
|
||||||
|
source.add_help(&diagnostics, id, "declare 'missing' before using it")
|
||||||
|
formatted := source.format(&diagnostics, id)
|
||||||
|
defer delete(formatted)
|
||||||
|
multiline := source.add(
|
||||||
|
&diagnostics,
|
||||||
|
source.Span{file=primary_file, start=1, end=17},
|
||||||
|
"multiline failure",
|
||||||
|
)
|
||||||
|
multiline_formatted := source.format(&diagnostics, multiline)
|
||||||
|
defer delete(multiline_formatted)
|
||||||
|
unknown := source.add(&diagnostics, source.Span{}, "no location")
|
||||||
|
unknown_formatted := source.format(&diagnostics, unknown)
|
||||||
|
defer delete(unknown_formatted)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.annotations), 4)
|
||||||
|
testing.expect(t, strings.contains(formatted, "error: unknown symbol 'missing'"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "--> main.bro:1:2"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "^^^^^^^ unknown symbol"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "::: dep.bro:1:1"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "----- related declaration"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "note: names resolve in the current scope"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "help: declare 'missing' before using it"))
|
||||||
|
testing.expect(t, strings.contains(multiline_formatted, "1 | missing()"))
|
||||||
|
testing.expect(t, !strings.contains(multiline_formatted, "next()"))
|
||||||
|
testing.expect_value(t, unknown_formatted, "main.bro: error: no location")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
poisoned_expressions_preserve_independent_root_diagnostics :: proc(t: ^testing.T) {
|
||||||
|
text := `bad :: missing_global
|
||||||
|
sink func(value i32) void { _ = value }
|
||||||
|
broken func(value int) int { return missing_return + value }
|
||||||
|
main func() void {
|
||||||
|
_ = missing_add + 1
|
||||||
|
_ = try missing_try()
|
||||||
|
missing_catch() catch |_| {}
|
||||||
|
sink(missing_arg)
|
||||||
|
missing_stmt()
|
||||||
|
missing_target.field = 1
|
||||||
|
value i32 = 0
|
||||||
|
value += missing_rhs
|
||||||
|
_ = broken(1)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 9)
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
testing.expect(t, strings.has_prefix(diagnostic.message, "unknown symbol 'missing_"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "fallible expression"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "must be consumed"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "compatible numeric operands"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "implicitly convert"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7923,7 +8035,7 @@ referenced_missing_package_traps_at_reference :: proc(t: ^testing.T) {
|
|||||||
defer delete(stdout)
|
defer delete(stdout)
|
||||||
defer delete(stderr)
|
defer delete(stderr)
|
||||||
testing.expect(t, !state.success)
|
testing.expect(t, !state.success)
|
||||||
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6:"))
|
testing.expect(t, strings.contains(string(stderr), "/missing_used/app/main.bro:4:6"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -7991,8 +8103,8 @@ file_hidden_declarations_are_not_package_members :: proc(t: ^testing.T) {
|
|||||||
found_import := false
|
found_import := false
|
||||||
found_collision := false
|
found_collision := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
found_sibling = found_sibling || strings.contains(diagnostic.message, "unresolved function 'sibling'")
|
found_sibling = found_sibling || strings.contains(diagnostic.message, "unknown symbol 'sibling'")
|
||||||
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unresolved global 'sibling_value'")
|
found_sibling_value = found_sibling_value || strings.contains(diagnostic.message, "unknown symbol 'sibling_value'")
|
||||||
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'Sibling'")
|
found_sibling_type = found_sibling_type || strings.contains(diagnostic.message, "unknown or opaque record type 'Sibling'")
|
||||||
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member 'secret'")
|
found_import = found_import || strings.contains(diagnostic.message, "package 'dep' has no member 'secret'")
|
||||||
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
|
found_collision = found_collision || strings.contains(diagnostic.message, "duplicate function 'collision'")
|
||||||
@@ -8221,7 +8333,7 @@ main func() void {}
|
|||||||
wants := []string{
|
wants := []string{
|
||||||
"has no member 'missing'",
|
"has no member 'missing'",
|
||||||
"is file-hidden",
|
"is file-hidden",
|
||||||
"unknown package alias 'nope'",
|
"unknown symbol 'nope'",
|
||||||
"unavailable imported package 'gone'",
|
"unavailable imported package 'gone'",
|
||||||
"package member 'dep.ambiguous' is ambiguous",
|
"package member 'dep.ambiguous' is ambiguous",
|
||||||
"duplicate declaration alias 'duplicate'",
|
"duplicate declaration alias 'duplicate'",
|
||||||
@@ -8937,10 +9049,10 @@ conditional_unwrap_diagnostics_cover_counts_guards_and_capture_scope :: proc(t:
|
|||||||
duplicate = duplicate || strings.contains(diagnostic.message, "unwrap captures must have distinct names")
|
duplicate = duplicate || strings.contains(diagnostic.message, "unwrap captures must have distinct names")
|
||||||
non_optional = non_optional || strings.contains(diagnostic.message, "unwrap requires an optional value")
|
non_optional = non_optional || strings.contains(diagnostic.message, "unwrap requires an optional value")
|
||||||
guard = guard || strings.contains(diagnostic.message, "unwrap guard must be a bool")
|
guard = guard || strings.contains(diagnostic.message, "unwrap guard must be a bool")
|
||||||
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unresolved global 'earlier'")
|
outer_operand_scope = outer_operand_scope || strings.contains(diagnostic.message, "unknown symbol 'earlier'")
|
||||||
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'value'")
|
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'value'")
|
||||||
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'value'")
|
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'value'")
|
||||||
capture_scope += 1 if strings.contains(diagnostic.message, "unresolved global 'value'") else 0
|
capture_scope += 1 if strings.contains(diagnostic.message, "unknown symbol 'value'") else 0
|
||||||
}
|
}
|
||||||
testing.expect_value(t, count_mismatches, 2)
|
testing.expect_value(t, count_mismatches, 2)
|
||||||
testing.expect(t, duplicate)
|
testing.expect(t, duplicate)
|
||||||
@@ -8978,7 +9090,7 @@ if_unwrap_binding_is_scoped_to_then_block :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
found := false
|
found := false
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
found = found || strings.contains(diagnostic.message, "unresolved global 'v'")
|
found = found || strings.contains(diagnostic.message, "unknown symbol 'v'")
|
||||||
}
|
}
|
||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
@@ -9433,7 +9545,7 @@ main func() void {
|
|||||||
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'item'")
|
redeclaration = redeclaration || strings.contains(diagnostic.message, "duplicate local 'item'")
|
||||||
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'item'")
|
immutable = immutable || strings.contains(diagnostic.message, "cannot assign immutable local 'item'")
|
||||||
immutable_pointer = immutable_pointer || strings.contains(diagnostic.message, "assignment target is not writable")
|
immutable_pointer = immutable_pointer || strings.contains(diagnostic.message, "assignment target is not writable")
|
||||||
scope = scope || strings.contains(diagnostic.message, "unresolved global 'item'")
|
scope = scope || strings.contains(diagnostic.message, "unknown symbol 'item'")
|
||||||
integer_bounds += 1 if strings.contains(diagnostic.message, "range bounds must be compatible concrete integers") else 0
|
integer_bounds += 1 if strings.contains(diagnostic.message, "range bounds must be compatible concrete integers") else 0
|
||||||
}
|
}
|
||||||
testing.expect(t, unsupported)
|
testing.expect(t, unsupported)
|
||||||
@@ -11920,6 +12032,101 @@ main func() void {
|
|||||||
testing.expect(t, found)
|
testing.expect(t, found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
missing_qualified_signature_symbol_reports_one_root_error :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-root-diagnostic"
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
token_text := `Token :: struct { start int }
|
||||||
|
`
|
||||||
|
lexer_text := `scan func(cursor usize) void ! missing.Error {
|
||||||
|
token Token = Token{start = cursor}
|
||||||
|
_ = token
|
||||||
|
}
|
||||||
|
`
|
||||||
|
main_text := `main func() void {
|
||||||
|
scan(1) catch |_| { return }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
"/tmp/brolang-test-root-diagnostic/token.bro",
|
||||||
|
transmute([]byte)token_text,
|
||||||
|
))
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
"/tmp/brolang-test-root-diagnostic/lexer.bro",
|
||||||
|
transmute([]byte)lexer_text,
|
||||||
|
))
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
"/tmp/brolang-test-root-diagnostic/main.bro",
|
||||||
|
transmute([]byte)main_text,
|
||||||
|
))
|
||||||
|
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 1)
|
||||||
|
testing.expect_value(t, diagnostics.items[0].message, "unknown symbol 'missing'")
|
||||||
|
formatted := source.format(&diagnostics, source.Diagnostic_Id(0))
|
||||||
|
defer delete(formatted)
|
||||||
|
testing.expect(t, strings.contains(formatted, "/lexer.bro:1:32"))
|
||||||
|
testing.expect(t, strings.contains(formatted, "^^^^^^^ unknown symbol"))
|
||||||
|
testing.expect(t, !strings.contains(formatted, "fallible expression"))
|
||||||
|
testing.expect(t, !strings.contains(formatted, "must be consumed"))
|
||||||
|
testing.expect(t, !strings.contains(formatted, "could not resolve the 'int' constraint"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
poisoned_global_and_local_types_do_not_create_inference_fallbacks :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-poisoned-declarations"
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
text := `bad missing.Global :: 1
|
||||||
|
main func() void {
|
||||||
|
value absent.Local = 1
|
||||||
|
_ = value
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testing.expect(t, os.write_entire_file(
|
||||||
|
"/tmp/brolang-test-poisoned-declarations/main.bro",
|
||||||
|
transmute([]byte)text,
|
||||||
|
))
|
||||||
|
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
module, loaded := loader.load(directory, &sources, &diagnostics, &symbols)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
hir_module := checker.check(&module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
|
unknown_missing := false
|
||||||
|
unknown_absent := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
unknown_missing = unknown_missing || diagnostic.message == "unknown symbol 'missing'"
|
||||||
|
unknown_absent = unknown_absent || diagnostic.message == "unknown symbol 'absent'"
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "could not resolve"))
|
||||||
|
testing.expect(t, !strings.contains(diagnostic.message, "could not infer"))
|
||||||
|
}
|
||||||
|
testing.expect(t, unknown_missing)
|
||||||
|
testing.expect(t, unknown_absent)
|
||||||
|
}
|
||||||
|
|
||||||
named_record_field_type :: proc(
|
named_record_field_type :: proc(
|
||||||
module: ^hir.Module,
|
module: ^hir.Module,
|
||||||
symbols: ^symbol.Table,
|
symbols: ^symbol.Table,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
mem :: import "@std/mem"
|
import "@std/mem"
|
||||||
|
|
||||||
ArrayList func($T type) type {
|
ArrayList func($T type) type {
|
||||||
return struct {
|
return struct {
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
c :: import "@ffi/c"
|
import "@ffi/c"
|
||||||
io :: import "@std/io"
|
import "@std/io"
|
||||||
|
|
||||||
hide write func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
hide write func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
|
||||||
request usize = bytes.len
|
request usize = bytes.len
|
||||||
|
|||||||
+4
-9
@@ -1,5 +1,5 @@
|
|||||||
c :: import "@ffi/c"
|
import "@ffi/c"
|
||||||
meta :: import "@std/meta"
|
import "@std/meta"
|
||||||
|
|
||||||
ReadError :: enum {
|
ReadError :: enum {
|
||||||
read_failed
|
read_failed
|
||||||
@@ -276,7 +276,7 @@ hide write_integer func(writer Writer, $T type, value T, base u64, uppercase boo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# ponytail: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
|
# note: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
|
||||||
hide write_float func(writer Writer, $T type, value T, scientific bool) void ! WriteError {
|
hide write_float func(writer Writer, $T type, value T, scientific bool) void ! WriteError {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.float: {
|
.float: {
|
||||||
@@ -353,12 +353,7 @@ hide write_default func(writer Writer, $T type, value T) void ! WriteError {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
print func(
|
print func(writer Writer, $format []u8, $Args type, args Args) void ! WriteError {
|
||||||
writer Writer,
|
|
||||||
$format []u8,
|
|
||||||
$Args type,
|
|
||||||
args Args,
|
|
||||||
) void ! WriteError {
|
|
||||||
inline for parse_format(format.len, format, Args) |token| {
|
inline for parse_format(format.len, format, Args) |token| {
|
||||||
if (token.kind == .unused) {
|
if (token.kind == .unused) {
|
||||||
break
|
break
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
c :: import "@ffi/c"
|
import "@ffi/c"
|
||||||
|
|
||||||
AllocError :: enum {
|
AllocError :: enum {
|
||||||
out_of_memory
|
out_of_memory
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
io :: import "@std/io"
|
import "@std/io"
|
||||||
|
|
||||||
Init :: struct {
|
Init :: struct {
|
||||||
io io.Io
|
io io.Io
|
||||||
|
|||||||
Reference in New Issue
Block a user