diff --git a/bare-arm/main.c b/bare-arm/main.c
index 983810c892aeb26b6295cfa42bcfc59b12a110b2..084374d3f1a6c83e5b42346e2c94e5a074fb3902 100644
--- a/bare-arm/main.c
+++ b/bare-arm/main.c
@@ -36,8 +36,9 @@ void do_str(const char *src) {
     mp_lexer_free(lex);
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, true);
 
-    if (module_fun == mp_const_none) {
+    if (mp_obj_is_exception_instance(module_fun)) {
         // compile error
+        mp_obj_print_exception(module_fun);
         return;
     }
 
diff --git a/py/builtinevex.c b/py/builtinevex.c
index 63c1547fbe9ba234fe1f44d5e3602c37ead3b2db..a115ec669e69599895605ebdffcb7e30ba66ebba 100644
--- a/py/builtinevex.c
+++ b/py/builtinevex.c
@@ -64,9 +64,9 @@ STATIC mp_obj_t parse_compile_execute(mp_obj_t o_in, mp_parse_input_kind_t parse
     // compile the string
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, false);
 
-    if (module_fun == mp_const_none) {
-        // TODO handle compile error correctly
-        return mp_const_none;
+    // check if there was a compile error
+    if (mp_obj_is_exception_instance(module_fun)) {
+        nlr_raise(module_fun);
     }
 
     // complied successfully, execute it
diff --git a/py/builtinimport.c b/py/builtinimport.c
index 7270e1f8404ec5377cbd00870e9c629ea07d87e7..04786b21042047161e502f553f93114e4c8c0c4e 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -142,11 +142,10 @@ STATIC void do_load(mp_obj_t module_obj, vstr_t *file) {
     // compile the imported script
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, false);
 
-    if (module_fun == mp_const_none) {
-        // TODO handle compile error correctly
+    if (mp_obj_is_exception_instance(module_fun)) {
         mp_locals_set(old_locals);
         mp_globals_set(old_globals);
-        nlr_raise(mp_obj_new_exception_msg(&mp_type_SyntaxError, "Syntax error in imported module"));
+        nlr_raise(module_fun);
     }
 
     // complied successfully, execute it
diff --git a/py/compile.c b/py/compile.c
index e08469460d8deeeedeed9edbc2fd19dd6f4cf13c..644e55be08fb4fc1af6d6a3abf1c9499de4541b4 100644
--- a/py/compile.c
+++ b/py/compile.c
@@ -62,23 +62,28 @@ typedef enum {
 #define EMIT_INLINE_ASM(fun) (comp->emit_inline_asm_method_table->fun(comp->emit_inline_asm))
 #define EMIT_INLINE_ASM_ARG(fun, ...) (comp->emit_inline_asm_method_table->fun(comp->emit_inline_asm, __VA_ARGS__))
 
+// elements in this struct are ordered to make it compact
 typedef struct _compiler_t {
     qstr source_file;
+
     uint8_t is_repl;
     uint8_t pass; // holds enum type pass_kind_t
-    uint8_t had_error; // try to keep compiler clean from nlr
     uint8_t func_arg_is_super; // used to compile special case of super() function call
+    uint8_t have_star;
+
+    // try to keep compiler clean from nlr
+    // this is set to an exception object if we have a compile error
+    mp_obj_t compile_error;
 
     uint next_label;
 
+    uint16_t num_dict_params;
+    uint16_t num_default_params;
+
     uint16_t break_label; // highest bit set indicates we are breaking out of a for loop
     uint16_t continue_label;
-    int break_continue_except_level;
     uint16_t cur_except_level; // increased for SETUP_EXCEPT, SETUP_FINALLY; decreased for POP_BLOCK, POP_EXCEPT
-
-    uint8_t have_star;
-    uint16_t num_dict_params;
-    uint16_t num_default_params;
+    int break_continue_except_level;
 
     scope_t *scope_head;
     scope_t *scope_cur;
@@ -91,14 +96,15 @@ typedef struct _compiler_t {
 } compiler_t;
 
 STATIC void compile_syntax_error(compiler_t *comp, mp_parse_node_t pn, const char *msg) {
-    // TODO store the error message to a variable in compiler_t instead of printing it
+    mp_obj_t exc = mp_obj_new_exception_msg(&mp_type_SyntaxError, msg);
+    // we don't have a 'block' name, so just pass the NULL qstr to indicate this
     if (MP_PARSE_NODE_IS_STRUCT(pn)) {
-        printf("  File \"%s\", line " UINT_FMT "\n", qstr_str(comp->source_file), (mp_uint_t)((mp_parse_node_struct_t*)pn)->source_line);
+        mp_obj_exception_add_traceback(exc, comp->source_file, (mp_uint_t)((mp_parse_node_struct_t*)pn)->source_line, MP_QSTR_NULL);
     } else {
-        printf("  File \"%s\"\n", qstr_str(comp->source_file));
+        // we don't have a line number, so just pass 0
+        mp_obj_exception_add_traceback(exc, comp->source_file, 0, MP_QSTR_NULL);
     }
-    printf("SyntaxError: %s\n", msg);
-    comp->had_error = true;
+    comp->compile_error = exc;
 }
 
 STATIC const mp_map_elem_t mp_constants_table[] = {
@@ -1107,7 +1113,7 @@ qstr compile_funcdef_helper(compiler_t *comp, mp_parse_node_struct_t *pns, uint
     comp->num_default_params = 0;
     apply_to_single_or_list(comp, pns->nodes[1], PN_typedargslist, compile_funcdef_param);
 
-    if (comp->had_error) {
+    if (comp->compile_error != MP_OBJ_NULL) {
         return MP_QSTR_NULL;
     }
 
@@ -3415,7 +3421,8 @@ STATIC void compile_scope_inline_asm(compiler_t *comp, scope_t *scope, pass_kind
     if (comp->pass > MP_PASS_SCOPE) {
         bool success = EMIT_INLINE_ASM(end_pass);
         if (!success) {
-            comp->had_error = true;
+            // TODO get proper exception from inline assembler
+            compile_syntax_error(comp, MP_PARSE_NODE_NULL, "inline assembler error");
         }
     }
 }
@@ -3550,6 +3557,7 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
     compiler_t *comp = m_new0(compiler_t, 1);
     comp->source_file = source_file;
     comp->is_repl = is_repl;
+    comp->compile_error = MP_OBJ_NULL;
 
     // optimise constants
     mp_map_t consts;
@@ -3566,7 +3574,7 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
     comp->emit_inline_asm = NULL;
     comp->emit_inline_asm_method_table = NULL;
     uint max_num_labels = 0;
-    for (scope_t *s = comp->scope_head; s != NULL && !comp->had_error; s = s->next) {
+    for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {
         if (false) {
 #if MICROPY_EMIT_INLINE_THUMB
         } else if (s->emit_options == MP_EMIT_OPT_ASM_THUMB) {
@@ -3583,7 +3591,7 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
     }
 
     // compute some things related to scope and identifiers
-    for (scope_t *s = comp->scope_head; s != NULL && !comp->had_error; s = s->next) {
+    for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {
         compile_scope_compute_things(comp, s);
     }
 
@@ -3600,7 +3608,7 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
     emit_inline_asm_t *emit_inline_thumb = NULL;
 #endif
 #endif // !MICROPY_EMIT_CPYTHON
-    for (scope_t *s = comp->scope_head; s != NULL && !comp->had_error; s = s->next) {
+    for (scope_t *s = comp->scope_head; s != NULL && comp->compile_error == MP_OBJ_NULL; s = s->next) {
         if (false) {
             // dummy
 
@@ -3615,7 +3623,7 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
             comp->emit_inline_asm = emit_inline_thumb;
             comp->emit_inline_asm_method_table = &emit_inline_thumb_method_table;
             compile_scope_inline_asm(comp, s, MP_PASS_CODE_SIZE);
-            if (!comp->had_error) {
+            if (comp->compile_error == MP_OBJ_NULL) {
                 compile_scope_inline_asm(comp, s, MP_PASS_EMIT);
             }
 #endif
@@ -3674,12 +3682,12 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
 #endif // !MICROPY_EMIT_CPYTHON
 
             // second last pass: compute code size
-            if (!comp->had_error) {
+            if (comp->compile_error == MP_OBJ_NULL) {
                 compile_scope(comp, s, MP_PASS_CODE_SIZE);
             }
 
             // final pass: emit code
-            if (!comp->had_error) {
+            if (comp->compile_error == MP_OBJ_NULL) {
                 compile_scope(comp, s, MP_PASS_EMIT);
             }
         }
@@ -3722,12 +3730,11 @@ mp_obj_t mp_compile(mp_parse_node_t pn, qstr source_file, uint emit_opt, bool is
     }
 
     // free the compiler
-    bool had_error = comp->had_error;
+    mp_obj_t compile_error = comp->compile_error;
     m_del_obj(compiler_t, comp);
 
-    if (had_error) {
-        // TODO return a proper error message
-        return mp_const_none;
+    if (compile_error != MP_OBJ_NULL) {
+        return compile_error;
     } else {
 #if MICROPY_EMIT_CPYTHON
         // can't create code, so just return true
diff --git a/qemu-arm/main.c b/qemu-arm/main.c
index 97591fe9448783c76046161c076a5bd70ce44810..7bb8472541bf0c2b0bd99da61cca3fbbace1c68c 100644
--- a/qemu-arm/main.c
+++ b/qemu-arm/main.c
@@ -36,8 +36,9 @@ void do_str(const char *src) {
     mp_lexer_free(lex);
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, true);
 
-    if (module_fun == mp_const_none) {
+    if (mp_obj_is_exception_instance(module_fun)) {
         // compile error
+        mp_obj_print_exception(module_fun);
         return;
     }
 
diff --git a/qemu-arm/test_main.c b/qemu-arm/test_main.c
index 563e1c91dfac78f57da5017682dae6b658d17300..099cd94ba19f2a4aaee05e28cb6488bcae16e24a 100644
--- a/qemu-arm/test_main.c
+++ b/qemu-arm/test_main.c
@@ -39,8 +39,9 @@ inline void do_str(const char *src) {
     mp_lexer_free(lex);
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, true);
 
-    if (module_fun == mp_const_none) {
-        tt_abort_msg("Computer error");
+    if (mp_obj_is_exception_instance(module_fun)) {
+        mp_obj_print_exception(module_fun);
+        tt_abort_msg("Compile error");
     }
 
     nlr_buf_t nlr;
diff --git a/stmhal/pyexec.c b/stmhal/pyexec.c
index 01c0173ef45268cc2377b0bf849c6ad1a125e5b8..0ce4882e20153f19fdc033318b3adc4e004f3d25 100644
--- a/stmhal/pyexec.c
+++ b/stmhal/pyexec.c
@@ -72,7 +72,8 @@ bool parse_compile_execute(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, bo
 
     mp_obj_t module_fun = mp_compile(pn, source_name, MP_EMIT_OPT_NONE, is_repl);
 
-    if (module_fun == mp_const_none) {
+    if (mp_obj_is_exception_instance(module_fun)) {
+        mp_obj_print_exception(module_fun);
         return false;
     }
 
diff --git a/tests/basics/eval1.py b/tests/basics/builtin_eval.py
similarity index 100%
rename from tests/basics/eval1.py
rename to tests/basics/builtin_eval.py
diff --git a/tests/basics/builtin_eval_error.py b/tests/basics/builtin_eval_error.py
new file mode 100644
index 0000000000000000000000000000000000000000..3e8a8ff0d8b0f85cf8c215f9ca26ad726bcaa48d
--- /dev/null
+++ b/tests/basics/builtin_eval_error.py
@@ -0,0 +1,6 @@
+# test if eval raises SyntaxError
+
+try:
+    print(eval("[1, *a]"))
+except SyntaxError:
+    print("SyntaxError")
diff --git a/unix-cpy/main.c b/unix-cpy/main.c
index ac142f50ff0becc16c0fc7206136e98112e8de90..6695c7e84fad49489c0e785498402db387fbc868 100644
--- a/unix-cpy/main.c
+++ b/unix-cpy/main.c
@@ -79,8 +79,8 @@ void do_file(const char *file) {
 
             //printf("----------------\n");
 
-            if (module_fun == mp_const_none) {
-                printf("compile error\n");
+            if (mp_obj_is_exception_instance(module_fun)) {
+                mp_obj_print_exception(module_fun);
             }
         }
     }