mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
add tests for compile errors
This commit is contained in:
parent
4068897b6b
commit
4cc95174a7
5 changed files with 147 additions and 15 deletions
|
|
@ -32,11 +32,15 @@ readable, safe, optimal, and concise code to solve any computing problem.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
* test framework to test for compile errors
|
|
||||||
* Simple .so library
|
* Simple .so library
|
||||||
* Multiple files
|
* Multiple files
|
||||||
* inline assembly and syscalls
|
* inline assembly and syscalls
|
||||||
* running code at compile time
|
* running code at compile time
|
||||||
|
* print! macro that takes var args
|
||||||
|
* panic! macro that prints a stack trace to stderr in debug mode and calls
|
||||||
|
abort() in release mode
|
||||||
|
* unreachable codegen to panic("unreachable") in debug mode, and nothing in
|
||||||
|
release mode
|
||||||
* implement a simple game using SDL2
|
* implement a simple game using SDL2
|
||||||
* How should the Widget use case be solved? In Genesis I'm using C++ and inheritance.
|
* How should the Widget use case be solved? In Genesis I'm using C++ and inheritance.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
" Vim syntax file
|
" Vim syntax file
|
||||||
" Language: Zig
|
" Language: Zig
|
||||||
" Maintainer: Andrew Kelley
|
" Maintainer: Andrew Kelley
|
||||||
" Latest Revision: 24 November 2015
|
" Latest Revision: 27 November 2015
|
||||||
|
|
||||||
if exists("b:current_syntax")
|
if exists("b:current_syntax")
|
||||||
finish
|
finish
|
||||||
endif
|
endif
|
||||||
|
|
||||||
syn keyword zigKeyword fn return mut const extern unreachable export pub
|
syn keyword zigKeyword fn return mut const extern unreachable export pub
|
||||||
|
syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void
|
||||||
|
|
||||||
let b:current_syntax = "zig"
|
let b:current_syntax = "zig"
|
||||||
|
|
||||||
hi def link zigKeyword Keyword
|
hi def link zigKeyword Keyword
|
||||||
|
hi def link zigType Type
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ struct TypeNode {
|
||||||
|
|
||||||
struct FnDefNode {
|
struct FnDefNode {
|
||||||
bool add_implicit_return;
|
bool add_implicit_return;
|
||||||
|
bool skip;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CodeGenNode {
|
struct CodeGenNode {
|
||||||
|
|
@ -214,7 +215,7 @@ static void find_declarations(CodeGen *g, AstNode *node) {
|
||||||
if (buf_eql_str(name, "link")) {
|
if (buf_eql_str(name, "link")) {
|
||||||
g->link_table.put(param, true);
|
g->link_table.put(param, true);
|
||||||
} else {
|
} else {
|
||||||
add_node_error(g, node,
|
add_node_error(g, directive_node,
|
||||||
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +243,9 @@ static void find_declarations(CodeGen *g, AstNode *node) {
|
||||||
if (entry) {
|
if (entry) {
|
||||||
add_node_error(g, node,
|
add_node_error(g, node,
|
||||||
buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
|
buf_sprintf("redefinition of '%s'", buf_ptr(proto_name)));
|
||||||
|
assert(!node->codegen_node);
|
||||||
|
node->codegen_node = allocate<CodeGenNode>(1);
|
||||||
|
node->codegen_node->data.fn_def_node.skip = true;
|
||||||
} else {
|
} else {
|
||||||
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
|
||||||
fn_table_entry->proto_node = proto_node;
|
fn_table_entry->proto_node = proto_node;
|
||||||
|
|
@ -261,6 +265,12 @@ static void find_declarations(CodeGen *g, AstNode *node) {
|
||||||
}
|
}
|
||||||
case NodeTypeFnProto:
|
case NodeTypeFnProto:
|
||||||
{
|
{
|
||||||
|
for (int i = 0; i < node->data.fn_proto.directives->length; i += 1) {
|
||||||
|
AstNode *directive_node = node->data.fn_proto.directives->at(i);
|
||||||
|
Buf *name = &directive_node->data.directive.name;
|
||||||
|
add_node_error(g, directive_node,
|
||||||
|
buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
|
||||||
|
}
|
||||||
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
|
for (int i = 0; i < node->data.fn_proto.params.length; i += 1) {
|
||||||
AstNode *child = node->data.fn_proto.params.at(i);
|
AstNode *child = node->data.fn_proto.params.at(i);
|
||||||
find_declarations(g, child);
|
find_declarations(g, child);
|
||||||
|
|
@ -363,11 +373,18 @@ static void analyze_node(CodeGen *g, AstNode *node) {
|
||||||
break;
|
break;
|
||||||
case NodeTypeFnDef:
|
case NodeTypeFnDef:
|
||||||
{
|
{
|
||||||
|
if (node->codegen_node && node->codegen_node->data.fn_def_node.skip) {
|
||||||
|
// we detected an error with this function definition which prevents us
|
||||||
|
// from further analyzing it.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
AstNode *proto_node = node->data.fn_def.fn_proto;
|
AstNode *proto_node = node->data.fn_def.fn_proto;
|
||||||
assert(proto_node->type == NodeTypeFnProto);
|
assert(proto_node->type == NodeTypeFnProto);
|
||||||
analyze_node(g, proto_node);
|
analyze_node(g, proto_node);
|
||||||
|
|
||||||
check_fn_def_control_flow(g, node);
|
check_fn_def_control_flow(g, node);
|
||||||
|
analyze_node(g, node->data.fn_def.body);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case NodeTypeFnDecl:
|
case NodeTypeFnDecl:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
#define BREAKPOINT __asm("int $0x03")
|
#define BREAKPOINT __asm("int $0x03")
|
||||||
|
|
||||||
|
static const int COMPILE_FAILED_ERR_CODE = 10; // chosen with a random number generator
|
||||||
|
|
||||||
void zig_panic(const char *format, ...)
|
void zig_panic(const char *format, ...)
|
||||||
__attribute__((cold))
|
__attribute__((cold))
|
||||||
__attribute__ ((noreturn))
|
__attribute__ ((noreturn))
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "os.hpp"
|
#include "os.hpp"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
struct TestSourceFile {
|
struct TestSourceFile {
|
||||||
const char *relative_path;
|
const char *relative_path;
|
||||||
|
|
@ -25,9 +26,10 @@ struct TestCase {
|
||||||
ZigList<const char *> program_args;
|
ZigList<const char *> program_args;
|
||||||
};
|
};
|
||||||
|
|
||||||
ZigList<TestCase*> test_cases = {0};
|
static ZigList<TestCase*> test_cases = {0};
|
||||||
const char *tmp_source_path = ".tmp_source.zig";
|
static const char *tmp_source_path = ".tmp_source.zig";
|
||||||
const char *tmp_exe_path = "./.tmp_exe";
|
static const char *tmp_exe_path = "./.tmp_exe";
|
||||||
|
static const char *zig_exe = "./zig";
|
||||||
|
|
||||||
static void add_simple_case(const char *case_name, const char *source, const char *output) {
|
static void add_simple_case(const char *case_name, const char *source, const char *output) {
|
||||||
TestCase *test_case = allocate<TestCase>(1);
|
TestCase *test_case = allocate<TestCase>(1);
|
||||||
|
|
@ -45,7 +47,32 @@ static void add_simple_case(const char *case_name, const char *source, const cha
|
||||||
test_cases.append(test_case);
|
test_cases.append(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_all_test_cases(void) {
|
static void add_compile_fail_case(const char *case_name, const char *source, int count, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, count);
|
||||||
|
|
||||||
|
TestCase *test_case = allocate<TestCase>(1);
|
||||||
|
test_case->case_name = case_name;
|
||||||
|
test_case->source = source;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i += 1) {
|
||||||
|
const char *arg = va_arg(ap, const char *);
|
||||||
|
test_case->compile_errors.append(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
test_case->compiler_args.append("build");
|
||||||
|
test_case->compiler_args.append(tmp_source_path);
|
||||||
|
test_case->compiler_args.append("--output");
|
||||||
|
test_case->compiler_args.append(tmp_exe_path);
|
||||||
|
test_case->compiler_args.append("--release");
|
||||||
|
test_case->compiler_args.append("--strip");
|
||||||
|
|
||||||
|
test_cases.append(test_case);
|
||||||
|
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_compiling_test_cases(void) {
|
||||||
add_simple_case("hello world with libc", R"SOURCE(
|
add_simple_case("hello world with libc", R"SOURCE(
|
||||||
#link("c")
|
#link("c")
|
||||||
extern {
|
extern {
|
||||||
|
|
@ -102,23 +129,102 @@ static void add_all_test_cases(void) {
|
||||||
)SOURCE", "OK\n");
|
)SOURCE", "OK\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void add_compile_failure_test_cases(void) {
|
||||||
|
add_compile_fail_case("multiple function definitions", R"SOURCE(
|
||||||
|
fn a() {}
|
||||||
|
fn a() {}
|
||||||
|
)SOURCE", 1, "Line 3, column 1: redefinition of 'a'");
|
||||||
|
|
||||||
|
add_compile_fail_case("bad directive", R"SOURCE(
|
||||||
|
#bogus1("")
|
||||||
|
extern {
|
||||||
|
fn b();
|
||||||
|
}
|
||||||
|
#bogus2("")
|
||||||
|
fn a() {}
|
||||||
|
)SOURCE", 2, "Line 2, column 1: invalid directive: 'bogus1'",
|
||||||
|
"Line 6, column 1: invalid directive: 'bogus2'");
|
||||||
|
|
||||||
|
add_compile_fail_case("unreachable with return", R"SOURCE(
|
||||||
|
fn a() -> unreachable {return;}
|
||||||
|
)SOURCE", 1, "Line 2, column 24: return statement in function with unreachable return type");
|
||||||
|
|
||||||
|
add_compile_fail_case("control reaches end of non-void function", R"SOURCE(
|
||||||
|
fn a() -> i32 {}
|
||||||
|
)SOURCE", 1, "Line 2, column 1: control reaches end of non-void function");
|
||||||
|
|
||||||
|
add_compile_fail_case("undefined function call", R"SOURCE(
|
||||||
|
fn a() {
|
||||||
|
b();
|
||||||
|
}
|
||||||
|
)SOURCE", 1, "Line 3, column 5: undefined function: 'b'");
|
||||||
|
|
||||||
|
add_compile_fail_case("wrong number of arguments", R"SOURCE(
|
||||||
|
fn a() {
|
||||||
|
b(1);
|
||||||
|
}
|
||||||
|
fn b(a: i32, b: i32, c: i32) { }
|
||||||
|
)SOURCE", 1, "Line 3, column 5: wrong number of arguments. Expected 3, got 1.");
|
||||||
|
|
||||||
|
add_compile_fail_case("invalid type", R"SOURCE(
|
||||||
|
fn a() -> bogus {}
|
||||||
|
)SOURCE", 1, "Line 2, column 11: invalid type name: 'bogus'");
|
||||||
|
|
||||||
|
add_compile_fail_case("pointer to unreachable", R"SOURCE(
|
||||||
|
fn a() -> *mut unreachable {}
|
||||||
|
)SOURCE", 1, "Line 2, column 11: pointer to unreachable not allowed");
|
||||||
|
|
||||||
|
add_compile_fail_case("unreachable code", R"SOURCE(
|
||||||
|
fn a() {
|
||||||
|
return;
|
||||||
|
b();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b() {}
|
||||||
|
)SOURCE", 1, "Line 4, column 5: unreachable code");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_compiler_invokation(TestCase *test_case, Buf *zig_stderr) {
|
||||||
|
printf("%s", zig_exe);
|
||||||
|
for (int i = 0; i < test_case->compiler_args.length; i += 1) {
|
||||||
|
printf(" %s", test_case->compiler_args.at(i));
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
printf("%s\n", buf_ptr(zig_stderr));
|
||||||
|
}
|
||||||
|
|
||||||
static void run_test(TestCase *test_case) {
|
static void run_test(TestCase *test_case) {
|
||||||
os_write_file(buf_create_from_str(tmp_source_path), buf_create_from_str(test_case->source));
|
os_write_file(buf_create_from_str(tmp_source_path), buf_create_from_str(test_case->source));
|
||||||
|
|
||||||
Buf zig_stderr = BUF_INIT;
|
Buf zig_stderr = BUF_INIT;
|
||||||
Buf zig_stdout = BUF_INIT;
|
Buf zig_stdout = BUF_INIT;
|
||||||
int return_code;
|
int return_code;
|
||||||
static const char *zig_exe = "./zig";
|
|
||||||
os_exec_process(zig_exe, test_case->compiler_args, &return_code, &zig_stderr, &zig_stdout);
|
os_exec_process(zig_exe, test_case->compiler_args, &return_code, &zig_stderr, &zig_stdout);
|
||||||
|
|
||||||
|
if (test_case->compile_errors.length) {
|
||||||
|
if (return_code) {
|
||||||
|
for (int i = 0; i < test_case->compile_errors.length; i += 1) {
|
||||||
|
const char *err_text = test_case->compile_errors.at(i);
|
||||||
|
if (!strstr(buf_ptr(&zig_stderr), err_text)) {
|
||||||
|
printf("\n");
|
||||||
|
printf("========= Expected this compile error: =========\n");
|
||||||
|
printf("%s\n", err_text);
|
||||||
|
printf("================================================\n");
|
||||||
|
print_compiler_invokation(test_case, &zig_stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return; // success
|
||||||
|
} else {
|
||||||
|
printf("\nCompile failed with return code 0 (Expected failure):\n");
|
||||||
|
print_compiler_invokation(test_case, &zig_stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (return_code != 0) {
|
if (return_code != 0) {
|
||||||
printf("\nCompile failed with return code %d:\n", return_code);
|
printf("\nCompile failed with return code %d:\n", return_code);
|
||||||
printf("%s", zig_exe);
|
print_compiler_invokation(test_case, &zig_stderr);
|
||||||
for (int i = 0; i < test_case->compiler_args.length; i += 1) {
|
|
||||||
printf(" %s", test_case->compiler_args.at(i));
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
printf("%s\n", buf_ptr(&zig_stderr));
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,7 +270,8 @@ static void cleanup(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
add_all_test_cases();
|
add_compiling_test_cases();
|
||||||
|
add_compile_failure_test_cases();
|
||||||
run_all_tests();
|
run_all_tests();
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue