Commit 4cc95174a7

Andrew Kelley <superjoe30@gmail.com>
2015-11-28 02:55:06
add tests for compile errors
1 parent 4068897
Changed files (5)
doc/vim/syntax/zig.vim
@@ -1,14 +1,16 @@
 " Vim syntax file
 " Language: Zig
 " Maintainer: Andrew Kelley
-" Latest Revision: 24 November 2015
+" Latest Revision: 27 November 2015
 
 if exists("b:current_syntax")
   finish
 endif
 
 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"
 
 hi def link zigKeyword Keyword
+hi def link zigType Type
src/codegen.cpp
@@ -83,6 +83,7 @@ struct TypeNode {
 
 struct FnDefNode {
     bool add_implicit_return;
+    bool skip;
 };
 
 struct CodeGenNode {
@@ -214,7 +215,7 @@ static void find_declarations(CodeGen *g, AstNode *node) {
                 if (buf_eql_str(name, "link")) {
                     g->link_table.put(param, true);
                 } else {
-                    add_node_error(g, node,
+                    add_node_error(g, directive_node,
                             buf_sprintf("invalid directive: '%s'", buf_ptr(name)));
                 }
             }
@@ -242,6 +243,9 @@ static void find_declarations(CodeGen *g, AstNode *node) {
                 if (entry) {
                     add_node_error(g, node,
                             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 {
                     FnTableEntry *fn_table_entry = allocate<FnTableEntry>(1);
                     fn_table_entry->proto_node = proto_node;
@@ -261,6 +265,12 @@ static void find_declarations(CodeGen *g, AstNode *node) {
             }
         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) {
                     AstNode *child = node->data.fn_proto.params.at(i);
                     find_declarations(g, child);
@@ -363,11 +373,18 @@ static void analyze_node(CodeGen *g, AstNode *node) {
             break;
         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;
                 assert(proto_node->type == NodeTypeFnProto);
                 analyze_node(g, proto_node);
 
                 check_fn_def_control_flow(g, node);
+                analyze_node(g, node->data.fn_def.body);
                 break;
             }
         case NodeTypeFnDecl:
src/util.hpp
@@ -16,6 +16,8 @@
 
 #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, ...)
     __attribute__((cold))
     __attribute__ ((noreturn))
test/run_tests.cpp
@@ -10,6 +10,7 @@
 #include "os.hpp"
 
 #include <stdio.h>
+#include <stdarg.h>
 
 struct TestSourceFile {
     const char *relative_path;
@@ -25,9 +26,10 @@ struct TestCase {
     ZigList<const char *> program_args;
 };
 
-ZigList<TestCase*> test_cases = {0};
-const char *tmp_source_path = ".tmp_source.zig";
-const char *tmp_exe_path = "./.tmp_exe";
+static ZigList<TestCase*> test_cases = {0};
+static const char *tmp_source_path = ".tmp_source.zig";
+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) {
     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);
 }
 
-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(
         #link("c")
         extern {
@@ -102,23 +129,102 @@ static void add_all_test_cases(void) {
     )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) {
     os_write_file(buf_create_from_str(tmp_source_path), buf_create_from_str(test_case->source));
 
     Buf zig_stderr = BUF_INIT;
     Buf zig_stdout = BUF_INIT;
     int return_code;
-    static const char *zig_exe = "./zig";
     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) {
         printf("\nCompile failed with return code %d:\n", return_code);
-        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));
+        print_compiler_invokation(test_case, &zig_stderr);
         exit(1);
     }
 
@@ -164,7 +270,8 @@ static void cleanup(void) {
 }
 
 int main(int argc, char **argv) {
-    add_all_test_cases();
+    add_compiling_test_cases();
+    add_compile_failure_test_cases();
     run_all_tests();
     cleanup();
 }
README.md
@@ -32,11 +32,15 @@ readable, safe, optimal, and concise code to solve any computing problem.
 
 ## Roadmap
 
- * test framework to test for compile errors
  * Simple .so library
  * Multiple files
  * inline assembly and syscalls
  * 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
  * How should the Widget use case be solved? In Genesis I'm using C++ and inheritance.