diff --git a/py/builtin.c b/py/builtin.c
index 91e54faedbe389b6e1cc038834a871c5156a4793..ef9e70c940cca635bdc68bcd12a3d016fe7a341b 100644
--- a/py/builtin.c
+++ b/py/builtin.c
@@ -36,7 +36,7 @@ STATIC mp_obj_t mp_builtin___build_class__(uint n_args, const mp_obj_t *args) {
     mp_obj_t meta;
     if (n_args == 2) {
         // no explicit bases, so use 'type'
-        meta = (mp_obj_t)&mp_const_type;
+        meta = (mp_obj_t)&mp_type_type;
     } else {
         // use type of first base object
         meta = mp_obj_get_type(args[2]);
@@ -142,7 +142,7 @@ STATIC mp_obj_t mp_builtin_chr(mp_obj_t o_in) {
         byte str[1] = {ord};
         return mp_obj_new_str(str, 1, true);
     } else {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "chr() arg not in range(0x110000)"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "chr() arg not in range(0x110000)"));
     }
 }
 
@@ -187,7 +187,7 @@ STATIC mp_obj_t mp_builtin_divmod(mp_obj_t o1_in, mp_obj_t o2_in) {
         args[1] = MP_OBJ_NEW_SMALL_INT(i1 % i2);
         return rt_build_tuple(2, args);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "unsupported operand type(s) for divmod(): '%s' and '%s'", mp_obj_get_type_str(o1_in), mp_obj_get_type_str(o2_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "unsupported operand type(s) for divmod(): '%s' and '%s'", mp_obj_get_type_str(o1_in), mp_obj_get_type_str(o2_in)));
     }
 }
 
@@ -209,7 +209,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(mp_builtin_iter_obj, mp_builtin_iter);
 STATIC mp_obj_t mp_builtin_len(mp_obj_t o_in) {
     mp_obj_t len = mp_obj_len_maybe(o_in);
     if (len == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "object of type '%s' has no len()", mp_obj_get_type_str(o_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "object of type '%s' has no len()", mp_obj_get_type_str(o_in)));
     } else {
         return len;
     }
@@ -229,7 +229,7 @@ STATIC mp_obj_t mp_builtin_max(uint n_args, const mp_obj_t *args) {
             }
         }
         if (max_obj == NULL) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "max() arg is an empty sequence"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "max() arg is an empty sequence"));
         }
         return max_obj;
     } else {
@@ -258,7 +258,7 @@ STATIC mp_obj_t mp_builtin_min(uint n_args, const mp_obj_t *args) {
             }
         }
         if (min_obj == NULL) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "min() arg is an empty sequence"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "min() arg is an empty sequence"));
         }
         return min_obj;
     } else {
@@ -278,7 +278,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR(mp_builtin_min_obj, 1, mp_builtin_min);
 STATIC mp_obj_t mp_builtin_next(mp_obj_t o) {
     mp_obj_t ret = rt_iternext(o);
     if (ret == mp_const_stop_iteration) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_StopIteration));
+        nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
     } else {
         return ret;
     }
@@ -294,7 +294,7 @@ STATIC mp_obj_t mp_builtin_ord(mp_obj_t o_in) {
         // TODO unicode
         return mp_obj_new_int(((const byte*)str)[0]);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "ord() expected a character, but string of length %d found", len));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "ord() expected a character, but string of length %d found", len));
     }
 }
 
@@ -364,7 +364,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_sum_obj, 1, 2, mp_builtin_sum);
 STATIC mp_obj_t mp_builtin_sorted(uint n_args, const mp_obj_t *args, mp_map_t *kwargs) {
     assert(n_args >= 1);
     if (n_args > 1) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "must use keyword argument for key function"));
     }
     mp_obj_t self = list_type.make_new((mp_obj_t)&list_type, 1, 0, args);
diff --git a/py/builtinevex.c b/py/builtinevex.c
index 3e3c9a6109625dcfa28d59b1da01a84691ba90c1..6e920e85f075ad247f315e22f9e6d15c225bc216 100644
--- a/py/builtinevex.c
+++ b/py/builtinevex.c
@@ -13,6 +13,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -28,14 +29,13 @@ STATIC mp_obj_t parse_compile_execute(mp_obj_t o_in, mp_parse_input_kind_t parse
     qstr source_name = mp_lexer_source_name(lex);
 
     // parse the string
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, parse_input_kind, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, parse_input_kind, &parse_error_kind);
     mp_lexer_free(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error; raise exception
-        nlr_jump(mp_obj_new_exception_msg(parse_exc_id, parse_exc_msg));
+        nlr_jump(mp_parse_make_exception(parse_error_kind));
     }
 
     // compile the string
@@ -74,6 +74,7 @@ STATIC mp_obj_t mp_builtin_exec(uint n_args, const mp_obj_t *args) {
         rt_locals_set(mp_obj_dict_get_map(locals));
     }
     mp_obj_t res = parse_compile_execute(args[0], MP_PARSE_FILE_INPUT);
+    // TODO if the above call throws an exception, then we never get to reset the globals/locals
     rt_globals_set(old_globals);
     rt_locals_set(old_locals);
     return res;
diff --git a/py/builtinimport.c b/py/builtinimport.c
index 0e44676c3ba4a83b2675ca26bf8724e7a64f37a9..c90625e9e01ba20f308e62d6dd956dfe58565e26 100644
--- a/py/builtinimport.c
+++ b/py/builtinimport.c
@@ -13,6 +13,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -77,7 +78,7 @@ void do_load(mp_obj_t module_obj, vstr_t *file) {
 
     if (lex == NULL) {
         // we verified the file exists using stat, but lexer could still fail
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ImportError, "ImportError: No module named '%s'", vstr_str(file)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ImportError, "ImportError: No module named '%s'", vstr_str(file)));
     }
 
     qstr source_name = mp_lexer_source_name(lex);
@@ -91,16 +92,15 @@ void do_load(mp_obj_t module_obj, vstr_t *file) {
     rt_globals_set(mp_obj_module_get_globals(module_obj));
 
     // parse the imported script
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_error_kind);
     mp_lexer_free(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error; clean up and raise exception
         rt_locals_set(old_locals);
         rt_globals_set(old_globals);
-        nlr_jump(mp_obj_new_exception_msg(parse_exc_id, parse_exc_msg));
+        nlr_jump(mp_parse_make_exception(parse_error_kind));
     }
 
     // compile the imported script
@@ -172,7 +172,7 @@ mp_obj_t mp_builtin___import__(int n_args, mp_obj_t *args) {
 
             // fail if we couldn't find the file
             if (stat == MP_IMPORT_STAT_NO_EXIST) {
-                nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ImportError, "ImportError: No module named '%s'", qstr_str(mod_name)));
+                nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ImportError, "ImportError: No module named '%s'", qstr_str(mod_name)));
             }
 
             module_obj = mp_obj_module_get(mod_name);
diff --git a/py/lexer.c b/py/lexer.c
index c3c992aeeb15ff860febc316be2c76332e49098e..be0b1883c5b80b60779732a45a68825e0446531f 100644
--- a/py/lexer.c
+++ b/py/lexer.c
@@ -51,6 +51,7 @@ bool str_strn_equal(const char *str, const char *strn, int len) {
     return i == len && *str == 0;
 }
 
+#ifdef MICROPY_DEBUG_PRINTERS
 void mp_token_show(const mp_token_t *tok) {
     printf("(%d:%d) kind:%d str:%p len:%d", tok->src_line, tok->src_column, tok->kind, tok->str, tok->len);
     if (tok->str != NULL && tok->len > 0) {
@@ -69,6 +70,7 @@ void mp_token_show(const mp_token_t *tok) {
     }
     printf("\n");
 }
+#endif
 
 #define CUR_CHAR(lex) ((lex)->chr0)
 
@@ -711,35 +713,3 @@ const mp_token_t *mp_lexer_cur(const mp_lexer_t *lex) {
 bool mp_lexer_is_kind(mp_lexer_t *lex, mp_token_kind_t kind) {
     return lex->tok_cur.kind == kind;
 }
-
-/*
-bool mp_lexer_is_str(mp_lexer_t *lex, const char *str) {
-    return mp_token_is_str(&lex->tok_cur, str);
-}
-
-bool mp_lexer_opt_kind(mp_lexer_t *lex, mp_token_kind_t kind) {
-    if (mp_lexer_is_kind(lex, kind)) {
-        mp_lexer_to_next(lex);
-        return true;
-    }
-    return false;
-}
-
-bool mp_lexer_opt_str(mp_lexer_t *lex, const char *str) {
-    if (mp_lexer_is_str(lex, str)) {
-        mp_lexer_to_next(lex);
-        return true;
-    }
-    return false;
-}
-*/
-
-bool mp_lexer_show_error_pythonic_prefix(mp_lexer_t *lex) {
-    printf("  File \"%s\", line %d column %d\n", qstr_str(lex->source_name), lex->tok_cur.src_line, lex->tok_cur.src_column);
-    return false;
-}
-
-bool mp_lexer_show_error_pythonic(mp_lexer_t *lex, const char *msg) {
-    printf("  File \"%s\", line %d column %d\n%s\n", qstr_str(lex->source_name), lex->tok_cur.src_line, lex->tok_cur.src_column, msg);
-    return false;
-}
diff --git a/py/mpconfig.h b/py/mpconfig.h
index 6ff0692915b5d8f903da3cad29548a24aa75cfec..00e2439e4d0fc246a7345bfc3f00ab259e63ae70 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -40,6 +40,7 @@
 #endif
 
 // Whether to build functions that print debugging info:
+//   mp_token_show
 //   mp_byte_code_print
 //   mp_parse_node_print
 #ifndef MICROPY_DEBUG_PRINTERS
diff --git a/py/obj.c b/py/obj.c
index 86c0edc15b69cd0de28f2ce5c452430e6926bd5f..0068af4c4378f8aed9869df860c2bbe24aadf027 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -51,7 +51,7 @@ void mp_obj_print(mp_obj_t o_in, mp_print_kind_t kind) {
 
 // helper function to print an exception with traceback
 void mp_obj_print_exception(mp_obj_t exc) {
-    if (MP_OBJ_IS_TYPE(exc, &exception_type)) {
+    if (mp_obj_is_exception_instance(exc)) {
         machine_uint_t n, *values;
         mp_obj_exception_get_traceback(exc, &n, &values);
         if (n > 0) {
@@ -133,7 +133,7 @@ bool mp_obj_equal(mp_obj_t o1, mp_obj_t o2) {
             }
         }
 
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_NotImplementedError,
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_NotImplementedError,
             "Equality for '%s' and '%s' types not yet implemented", mp_obj_get_type_str(o1), mp_obj_get_type_str(o2)));
         return false;
     }
@@ -160,7 +160,7 @@ machine_int_t mp_obj_get_int(mp_obj_t arg) {
     } else if (MP_OBJ_IS_TYPE(arg, &int_type)) {
         return mp_obj_int_get_checked(arg);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to int", mp_obj_get_type_str(arg)));
     }
 }
 
@@ -175,7 +175,7 @@ machine_float_t mp_obj_get_float(mp_obj_t arg) {
     } else if (MP_OBJ_IS_TYPE(arg, &float_type)) {
         return mp_obj_float_get(arg);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to float", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to float", mp_obj_get_type_str(arg)));
     }
 }
 
@@ -195,7 +195,7 @@ void mp_obj_get_complex(mp_obj_t arg, mp_float_t *real, mp_float_t *imag) {
     } else if (MP_OBJ_IS_TYPE(arg, &complex_type)) {
         mp_obj_complex_get(arg, real, imag);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "can't convert %s to complex", mp_obj_get_type_str(arg)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "can't convert %s to complex", mp_obj_get_type_str(arg)));
     }
 }
 #endif
@@ -210,11 +210,11 @@ mp_obj_t *mp_obj_get_array_fixed_n(mp_obj_t o_in, machine_int_t n) {
             mp_obj_list_get(o_in, &seq_len, &seq_items);
         }
         if (seq_len != n) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_IndexError, "requested length %d but object has length %d", n, seq_len));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_IndexError, "requested length %d but object has length %d", n, seq_len));
         }
         return seq_items;
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "object '%s' is not a tuple or list", mp_obj_get_type_str(o_in)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "object '%s' is not a tuple or list", mp_obj_get_type_str(o_in)));
     }
 }
 
@@ -226,11 +226,11 @@ uint mp_get_index(const mp_obj_type_t *type, machine_uint_t len, mp_obj_t index)
             i += len;
         }
         if (i < 0 || i >= len) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_IndexError, "%s index out of range", qstr_str(type->name)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_IndexError, "%s index out of range", qstr_str(type->name)));
         }
         return i;
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "%s indices must be integers, not %s", qstr_str(type->name), mp_obj_get_type_str(index)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "%s indices must be integers, not %s", qstr_str(type->name), mp_obj_get_type_str(index)));
     }
 }
 
diff --git a/py/obj.h b/py/obj.h
index bed119c0a24339390a0728109f02004ac27d14b5..dd6d217e4d822dcb49dbc33959f240ab1d19aa77 100644
--- a/py/obj.h
+++ b/py/obj.h
@@ -188,9 +188,27 @@ struct _mp_obj_type_t {
 
 typedef struct _mp_obj_type_t mp_obj_type_t;
 
+// Constant types, globally accessible
+
+extern const mp_obj_type_t mp_type_type;
+extern const mp_obj_type_t mp_type_BaseException;
+extern const mp_obj_type_t mp_type_AssertionError;
+extern const mp_obj_type_t mp_type_AttributeError;
+extern const mp_obj_type_t mp_type_ImportError;
+extern const mp_obj_type_t mp_type_IndentationError;
+extern const mp_obj_type_t mp_type_IndexError;
+extern const mp_obj_type_t mp_type_KeyError;
+extern const mp_obj_type_t mp_type_NameError;
+extern const mp_obj_type_t mp_type_SyntaxError;
+extern const mp_obj_type_t mp_type_TypeError;
+extern const mp_obj_type_t mp_type_ValueError;
+extern const mp_obj_type_t mp_type_OverflowError;
+extern const mp_obj_type_t mp_type_OSError;
+extern const mp_obj_type_t mp_type_NotImplementedError;
+extern const mp_obj_type_t mp_type_StopIteration;
+
 // Constant objects, globally accessible
 
-extern const mp_obj_type_t mp_const_type;
 extern const mp_obj_t mp_const_none;
 extern const mp_obj_t mp_const_false;
 extern const mp_obj_t mp_const_true;
@@ -213,9 +231,9 @@ mp_obj_t mp_obj_new_bytes(const byte* data, uint len);
 mp_obj_t mp_obj_new_float(mp_float_t val);
 mp_obj_t mp_obj_new_complex(mp_float_t real, mp_float_t imag);
 #endif
-mp_obj_t mp_obj_new_exception(qstr id);
-mp_obj_t mp_obj_new_exception_msg(qstr id, const char *msg);
-mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
+mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type);
+mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg);
+mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...); // counts args by number of % symbols in fmt, excluding %%; can only handle void* sizes (ie no float/double!)
 mp_obj_t mp_obj_new_range(int start, int stop, int step);
 mp_obj_t mp_obj_new_range_iterator(int cur, int stop, int step);
 mp_obj_t mp_obj_new_fun_bc(int n_args, mp_obj_t def_args, uint n_state, const byte *code);
@@ -235,6 +253,7 @@ mp_obj_t mp_obj_new_module(qstr module_name);
 
 mp_obj_type_t *mp_obj_get_type(mp_obj_t o_in);
 const char *mp_obj_get_type_str(mp_obj_t o_in);
+bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo);
 
 void mp_obj_print_helper(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind);
 void mp_obj_print(mp_obj_t o, mp_print_kind_t kind);
@@ -274,8 +293,9 @@ machine_int_t mp_obj_int_get(mp_obj_t self_in);
 machine_int_t mp_obj_int_get_checked(mp_obj_t self_in);
 
 // exception
-extern const mp_obj_type_t exception_type;
-qstr mp_obj_exception_get_type(mp_obj_t self_in);
+bool mp_obj_is_exception_type(mp_obj_t self_in);
+bool mp_obj_is_exception_instance(mp_obj_t self_in);
+void mp_obj_exception_clear_traceback(mp_obj_t self_in);
 void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t line, qstr block);
 void mp_obj_exception_get_traceback(mp_obj_t self_in, machine_uint_t *n, machine_uint_t **values);
 
diff --git a/py/objarray.c b/py/objarray.c
index 4a70f9f7f5ea6af03ce06d2686530072d18a22c1..9e36196e5b01e78d4582d4e2ed10976ab6d5ed48 100644
--- a/py/objarray.c
+++ b/py/objarray.c
@@ -82,7 +82,7 @@ STATIC mp_obj_t array_construct(char typecode, mp_obj_t initializer) {
 
 STATIC mp_obj_t array_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args < 1 || n_args > 2) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "unexpected # of arguments, %d given", n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "unexpected # of arguments, %d given", n_args));
     }
     // TODO check args
     uint l;
@@ -160,7 +160,7 @@ STATIC const mp_method_t array_type_methods[] = {
 };
 
 const mp_obj_type_t array_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_array,
     .print = array_print,
     .make_new = array_make_new,
@@ -222,7 +222,7 @@ mp_obj_t array_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t array_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = array_it_iternext,
 };
diff --git a/py/objbool.c b/py/objbool.c
index 2dd019b7204cf94996abbfe02fa7ed211d77e54a..1dc5e5760ebdd3292ea107f87d8391efbd5807b4 100644
--- a/py/objbool.c
+++ b/py/objbool.c
@@ -29,7 +29,7 @@ STATIC mp_obj_t bool_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp
     switch (n_args) {
         case 0: return mp_const_false;
         case 1: if (rt_is_true(args[0])) { return mp_const_true; } else { return mp_const_false; }
-        default: nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "bool takes at most 1 argument, %d given", n_args));
+        default: nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "bool takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -46,7 +46,7 @@ STATIC mp_obj_t bool_unary_op(int op, mp_obj_t o_in) {
 }
 
 const mp_obj_type_t bool_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bool,
     .print = bool_print,
     .make_new = bool_make_new,
diff --git a/py/objboundmeth.c b/py/objboundmeth.c
index 72fbc233f19729a45a792cedbc99da37ff3af4d2..0b5fc10a0749d9ee17d028c71bdc2c4fb9193dea 100644
--- a/py/objboundmeth.c
+++ b/py/objboundmeth.c
@@ -40,7 +40,7 @@ mp_obj_t bound_meth_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_
 }
 
 const mp_obj_type_t bound_meth_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bound_method,
     .call = bound_meth_call,
 };
diff --git a/py/objcell.c b/py/objcell.c
index ce8f360141209d8b069f95afec9d4ed1a2fc821d..3666617474bf626abf8090eae54fe12905444c4f 100644
--- a/py/objcell.c
+++ b/py/objcell.c
@@ -25,7 +25,7 @@ void mp_obj_cell_set(mp_obj_t self_in, mp_obj_t obj) {
 }
 
 const mp_obj_type_t cell_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_, // should never need to print cell type
 };
 
diff --git a/py/objclosure.c b/py/objclosure.c
index 39e38c9d27244199842b07ac1d6e89e5f5fadc13..e2de0e04519b3c9c226ed3c66338d803f86a6738 100644
--- a/py/objclosure.c
+++ b/py/objclosure.c
@@ -41,7 +41,7 @@ mp_obj_t closure_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *
 }
 
 const mp_obj_type_t closure_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_closure,
     .call = closure_call,
 };
diff --git a/py/objcomplex.c b/py/objcomplex.c
index 3b5de03ed30ae28af6c252d4edce03a35d27e74e..188c334132c3fe64561b16377bb36f4a8cf4a06e 100644
--- a/py/objcomplex.c
+++ b/py/objcomplex.c
@@ -66,7 +66,7 @@ STATIC mp_obj_t complex_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "complex takes at most 2 arguments, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "complex takes at most 2 arguments, %d given", n_args));
     }
 }
 
@@ -86,7 +86,7 @@ STATIC mp_obj_t complex_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
 }
 
 const mp_obj_type_t complex_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_complex,
     .print = complex_print,
     .make_new = complex_make_new,
diff --git a/py/objdict.c b/py/objdict.c
index 15e738dff117d42fa669f218eb36b6fb027b89b1..31a80bd6cb2a25f9c600eaa83e53b9aa69966115 100644
--- a/py/objdict.c
+++ b/py/objdict.c
@@ -60,7 +60,7 @@ STATIC mp_obj_t dict_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
             // dict load
             mp_map_elem_t *elem = mp_map_lookup(&o->map, rhs_in, MP_MAP_LOOKUP);
             if (elem == NULL) {
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "<value>"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "<value>"));
             } else {
                 return elem->value;
             }
@@ -112,7 +112,7 @@ mp_obj_t dict_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t dict_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = dict_it_iternext,
 };
@@ -187,7 +187,7 @@ STATIC mp_obj_t dict_get_helper(mp_map_t *self, mp_obj_t key, mp_obj_t deflt, mp
     if (elem == NULL || elem->value == NULL) {
         if (deflt == NULL) {
             if (lookup_kind == MP_MAP_LOOKUP_REMOVE_IF_FOUND) {
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "<value>"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "<value>"));
             } else {
                 value = mp_const_none;
             }
@@ -246,7 +246,7 @@ STATIC mp_obj_t dict_popitem(mp_obj_t self_in) {
     assert(MP_OBJ_IS_TYPE(self_in, &dict_type));
     mp_obj_dict_t *self = self_in;
     if (self->map.used == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "popitem(): dictionary is empty"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "popitem(): dictionary is empty"));
     }
     mp_obj_dict_it_t *iter = mp_obj_new_dict_iterator(self, 0);
 
@@ -276,7 +276,7 @@ STATIC mp_obj_t dict_update(mp_obj_t self_in, mp_obj_t iterable) {
             || value == mp_const_stop_iteration
             || stop != mp_const_stop_iteration) {
             nlr_jump(mp_obj_new_exception_msg(
-                         MP_QSTR_ValueError,
+                         &mp_type_ValueError,
                          "dictionary update sequence has the wrong length"));
         } else {
             mp_map_lookup(&self->map, key, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND)->value = value;
@@ -341,7 +341,7 @@ STATIC mp_obj_t dict_view_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t dict_view_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = dict_view_it_iternext,
     .methods = NULL,            /* set operations still to come */
@@ -385,7 +385,7 @@ STATIC mp_obj_t dict_view_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
 
 
 STATIC const mp_obj_type_t dict_view_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_dict_view,
     .print = dict_view_print,
     .binary_op = dict_view_binary_op,
@@ -440,7 +440,7 @@ STATIC const mp_method_t dict_type_methods[] = {
 };
 
 const mp_obj_type_t dict_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_dict,
     .print = dict_print,
     .make_new = dict_make_new,
diff --git a/py/objenumerate.c b/py/objenumerate.c
index 2ca4dcd775fb8a193968c065715f107a3024e895..1c858ff56fb2a867dfac133add2c960751dea6d1 100644
--- a/py/objenumerate.c
+++ b/py/objenumerate.c
@@ -27,7 +27,7 @@ STATIC mp_obj_t enumerate_make_new(mp_obj_t type_in, uint n_args, uint n_kw, con
 }
 
 const mp_obj_type_t enumerate_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_enumerate,
     .make_new = enumerate_make_new,
     .iternext = enumerate_iternext,
diff --git a/py/objexcept.c b/py/objexcept.c
index e2c154de97087b757f1e7f180e48019d582ca3a6..d5c056a8d98e29049aef69383276696666a80005 100644
--- a/py/objexcept.c
+++ b/py/objexcept.c
@@ -17,19 +17,18 @@
 typedef struct mp_obj_exception_t {
     mp_obj_base_t base;
     mp_obj_t traceback; // a list object, holding (file,line,block) as numbers (not Python objects); a hack for now
-    qstr id;
     vstr_t *msg;
     mp_obj_tuple_t args;
 } mp_obj_exception_t;
 
-STATIC void exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
+STATIC void mp_obj_exception_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t o_in, mp_print_kind_t kind) {
     mp_obj_exception_t *o = o_in;
     if (o->msg != NULL) {
-        print(env, "%s: %s", qstr_str(o->id), vstr_str(o->msg));
+        print(env, "%s: %s", qstr_str(o->base.type->name), vstr_str(o->msg));
     } else {
         // Yes, that's how CPython has it
         if (kind == PRINT_REPR) {
-            print(env, "%s", qstr_str(o->id));
+            print(env, "%s", qstr_str(o->base.type->name));
         }
         if (kind == PRINT_STR) {
             if (o->args.len == 0) {
@@ -44,46 +43,74 @@ STATIC void exception_print(void (*print)(void *env, const char *fmt, ...), void
     }
 }
 
-STATIC mp_obj_t exception_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
-    mp_obj_exception_t *base = self_in;
+STATIC mp_obj_t mp_obj_exception_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
+    mp_obj_type_t *type = type_in;
 
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "%s does not take keyword arguments", qstr_str(base->id)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "%s does not take keyword arguments", type->name));
     }
 
     mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, n_args);
-    o->base.type = &exception_type;
+    o->base.type = type;
     o->traceback = MP_OBJ_NULL;
-    o->id = base->id;
     o->msg = NULL;
     o->args.len = n_args;
     memcpy(o->args.items, args, n_args * sizeof(mp_obj_t));
     return o;
 }
 
-const mp_obj_type_t exception_type = {
-    { &mp_const_type },
-    .name = MP_QSTR_, // TODO proper exception names
-    .print = exception_print,
-    .call = exception_call,
+const mp_obj_type_t mp_type_BaseException = {
+    { &mp_type_type },
+    .name = MP_QSTR_BaseException,
+    .print = mp_obj_exception_print,
+    .make_new = mp_obj_exception_make_new,
 };
 
-mp_obj_t mp_obj_new_exception(qstr id) {
-    return mp_obj_new_exception_msg_varg(id, NULL);
+#define MP_DEFINE_EXCEPTION(exc_name) \
+STATIC const mp_obj_tuple_t mp_type_ ## exc_name ## _bases_tuple = {{&tuple_type}, 1, {(mp_obj_t)&mp_type_BaseException}};\
+const mp_obj_type_t mp_type_ ## exc_name = { \
+    { &mp_type_type }, \
+    .name = MP_QSTR_ ## exc_name, \
+    .print = mp_obj_exception_print, \
+    .make_new = mp_obj_exception_make_new, \
+    .bases_tuple = (mp_obj_t)&mp_type_ ## exc_name ## _bases_tuple, \
+};
+
+MP_DEFINE_EXCEPTION(AssertionError)
+MP_DEFINE_EXCEPTION(AttributeError)
+MP_DEFINE_EXCEPTION(ImportError)
+MP_DEFINE_EXCEPTION(IndentationError)
+MP_DEFINE_EXCEPTION(IndexError)
+MP_DEFINE_EXCEPTION(KeyError)
+MP_DEFINE_EXCEPTION(NameError)
+MP_DEFINE_EXCEPTION(SyntaxError)
+MP_DEFINE_EXCEPTION(TypeError)
+MP_DEFINE_EXCEPTION(ValueError)
+MP_DEFINE_EXCEPTION(OverflowError)
+MP_DEFINE_EXCEPTION(OSError)
+MP_DEFINE_EXCEPTION(NotImplementedError)
+MP_DEFINE_EXCEPTION(StopIteration)
+
+mp_obj_t mp_obj_new_exception(const mp_obj_type_t *exc_type) {
+    return mp_obj_new_exception_msg_varg(exc_type, NULL);
 }
 
-mp_obj_t mp_obj_new_exception_msg(qstr id, const char *msg) {
-    return mp_obj_new_exception_msg_varg(id, msg);
+mp_obj_t mp_obj_new_exception_msg(const mp_obj_type_t *exc_type, const char *msg) {
+    return mp_obj_new_exception_msg_varg(exc_type, msg);
 }
 
-mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...) {
+mp_obj_t mp_obj_new_exception_msg_varg(const mp_obj_type_t *exc_type, const char *fmt, ...) {
+    // check that the given type is an exception type
+    assert(exc_type->make_new == mp_obj_exception_make_new);
+
     // make exception object
-    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t*, 0);
-    o->base.type = &exception_type;
+    mp_obj_exception_t *o = m_new_obj_var(mp_obj_exception_t, mp_obj_t, 0);
+    o->base.type = exc_type;
     o->traceback = MP_OBJ_NULL;
-    o->id = id;
     o->args.len = 0;
+
     if (fmt == NULL) {
+        // no message
         o->msg = NULL;
     } else {
         // render exception message
@@ -97,18 +124,41 @@ mp_obj_t mp_obj_new_exception_msg_varg(qstr id, const char *fmt, ...) {
     return o;
 }
 
-qstr mp_obj_exception_get_type(mp_obj_t self_in) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+// return true if the given object is an exception type
+// TODO make this work for user defined exceptions
+bool mp_obj_is_exception_type(mp_obj_t self_in) {
+    if (MP_OBJ_IS_TYPE(self_in, &mp_type_type)) {
+        mp_obj_type_t *self = self_in;
+        return self->make_new == mp_obj_exception_make_new;
+    } else {
+        return false;
+    }
+}
+
+// return true if the given object is an instance of an exception type
+// TODO make this work for user defined exceptions
+bool mp_obj_is_exception_instance(mp_obj_t self_in) {
+    return mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new;
+}
+
+void mp_obj_exception_clear_traceback(mp_obj_t self_in) {
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
-    return self->id;
+
+    // just set the traceback to the null object
+    // we don't want to call any memory management functions here
+    self->traceback = MP_OBJ_NULL;
 }
 
 void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t line, qstr block) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
+
     // for traceback, we are just using the list object for convenience, it's not really a list of Python objects
     if (self->traceback == MP_OBJ_NULL) {
-        self->traceback = mp_obj_new_list(0, NULL);
+        self->traceback = mp_obj_new_list(3, NULL);
     }
     mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)file);
     mp_obj_list_append(self->traceback, (mp_obj_t)(machine_uint_t)line);
@@ -116,8 +166,10 @@ void mp_obj_exception_add_traceback(mp_obj_t self_in, qstr file, machine_uint_t
 }
 
 void mp_obj_exception_get_traceback(mp_obj_t self_in, machine_uint_t *n, machine_uint_t **values) {
-    assert(MP_OBJ_IS_TYPE(self_in, &exception_type));
+    // make sure self_in is an exception instance
+    assert(mp_obj_get_type(self_in)->make_new == mp_obj_exception_make_new);
     mp_obj_exception_t *self = self_in;
+
     if (self->traceback == MP_OBJ_NULL) {
         *n = 0;
         *values = NULL;
diff --git a/py/objfilter.c b/py/objfilter.c
index 4dde7fac8cbe8364cd7d282126b561e25827cbac..dc400f1a2fd58fc038a9a18134876daa41075213 100644
--- a/py/objfilter.c
+++ b/py/objfilter.c
@@ -16,7 +16,7 @@ typedef struct _mp_obj_filter_t {
 
 STATIC mp_obj_t filter_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args != 2 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "filter expected 2 arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "filter expected 2 arguments"));
     }
     assert(n_args == 2);
     mp_obj_filter_t *o = m_new_obj(mp_obj_filter_t);
@@ -45,7 +45,7 @@ STATIC mp_obj_t filter_iternext(mp_obj_t self_in) {
 }
 
 const mp_obj_type_t filter_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_filter,
     .make_new = filter_make_new,
     .getiter = mp_identity,
diff --git a/py/objfloat.c b/py/objfloat.c
index 83b98266e040f05b6bacc474127ed6eb6f43f003..9d7b796895c0ba847b89087e38eb63c81ad67804 100644
--- a/py/objfloat.c
+++ b/py/objfloat.c
@@ -40,7 +40,7 @@ STATIC mp_obj_t float_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const m
             }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "float takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "float takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -64,7 +64,7 @@ STATIC mp_obj_t float_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
 }
 
 const mp_obj_type_t float_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_float,
     .print = float_print,
     .make_new = float_make_new,
diff --git a/py/objfun.c b/py/objfun.c
index 56ea692eaf39e0453b40346a1e96120cf5eb5b55..433da039bb2614e9e8173428fa4c7f4601225fa6 100644
--- a/py/objfun.c
+++ b/py/objfun.c
@@ -20,23 +20,23 @@
 
 STATIC void check_nargs(mp_obj_fun_native_t *self, int n_args, int n_kw) {
     if (n_kw && !self->is_kw) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "function does not take keyword arguments"));
     }
 
     if (self->n_args_min == self->n_args_max) {
         if (n_args != self->n_args_min) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                      "function takes %d positional arguments but %d were given",
                                                      self->n_args_min, n_args));
         }
     } else {
         if (n_args < self->n_args_min) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                     "<fun name>() missing %d required positional arguments: <list of names of params>",
                                                     self->n_args_min - n_args));
         } else if (n_args > self->n_args_max) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
                                                      "<fun name> expected at most %d arguments, got %d",
                                                      self->n_args_max, n_args));
         }
@@ -89,7 +89,7 @@ STATIC mp_obj_t fun_native_call(mp_obj_t self_in, uint n_args, uint n_kw, const
 }
 
 const mp_obj_type_t fun_native_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_native_call,
 };
@@ -143,10 +143,10 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
     mp_obj_fun_bc_t *self = self_in;
 
     if (n_args < self->n_args - self->n_def_args || n_args > self->n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     uint use_def_args = self->n_args - n_args;
@@ -159,7 +159,7 @@ STATIC mp_obj_t fun_bc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_o
 }
 
 const mp_obj_type_t fun_bc_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_bc_call,
 };
@@ -252,10 +252,10 @@ STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_
     mp_obj_fun_asm_t *self = self_in;
 
     if (n_args != self->n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", self->n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     machine_uint_t ret;
@@ -276,7 +276,7 @@ STATIC mp_obj_t fun_asm_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_
 }
 
 STATIC const mp_obj_type_t fun_asm_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_function,
     .call = fun_asm_call,
 };
diff --git a/py/objgenerator.c b/py/objgenerator.c
index 4e7d3a199b72ca71595af92688d847bf60a31758..226b902dafaf85b9f091692325a36a62beb312c3 100644
--- a/py/objgenerator.c
+++ b/py/objgenerator.c
@@ -28,17 +28,17 @@ STATIC mp_obj_t gen_wrap_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp
     const byte *bc_code;
     mp_obj_fun_bc_get(self_fun, &bc_n_args, &bc_n_state, &bc_code);
     if (n_args != bc_n_args) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes %d positional arguments but %d were given", bc_n_args, n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes %d positional arguments but %d were given", bc_n_args, n_args));
     }
     if (n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "function does not take keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "function does not take keyword arguments"));
     }
 
     return mp_obj_new_gen_instance(bc_code, bc_n_state, n_args, args);
 }
 
 const mp_obj_type_t gen_wrap_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_generator,
     .call = gen_wrap_call,
 };
@@ -77,7 +77,7 @@ STATIC mp_obj_t gen_next_send(mp_obj_t self_in, mp_obj_t send_value) {
     }
     if (self->sp == self->state - 1) {
         if (send_value != mp_const_none) {
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "can't send non-None value to a just-started generator"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "can't send non-None value to a just-started generator"));
         }
     } else {
         *self->sp = send_value;
@@ -108,10 +108,12 @@ mp_obj_t gen_instance_iternext(mp_obj_t self_in) {
 STATIC mp_obj_t gen_instance_send(mp_obj_t self_in, mp_obj_t send_value) {
     mp_obj_t ret = gen_next_send(self_in, send_value);
     if (ret == mp_const_stop_iteration) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_StopIteration));
+        nlr_jump(mp_obj_new_exception(&mp_type_StopIteration));
+    } else {
+        return ret;
     }
-    return ret;
 }
+
 STATIC MP_DEFINE_CONST_FUN_OBJ_2(gen_instance_send_obj, gen_instance_send);
 
 STATIC const mp_method_t gen_type_methods[] = {
@@ -120,7 +122,7 @@ STATIC const mp_method_t gen_type_methods[] = {
 };
 
 const mp_obj_type_t gen_instance_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_generator,
     .print = gen_instance_print,
     .getiter = gen_instance_getiter,
diff --git a/py/objgetitemiter.c b/py/objgetitemiter.c
index 28b118a526d133941828a30ef684112cb3c0c1e8..bf466c05d936e27179fc01a9de35f6b50334b7e5 100644
--- a/py/objgetitemiter.c
+++ b/py/objgetitemiter.c
@@ -26,7 +26,7 @@ STATIC mp_obj_t it_iternext(mp_obj_t self_in) {
         return value;
     } else {
         // an exception was raised
-        if (MP_OBJ_IS_TYPE(nlr.ret_val, &exception_type) && mp_obj_exception_get_type(nlr.ret_val) == MP_QSTR_StopIteration) {
+        if (mp_obj_get_type(nlr.ret_val) == &mp_type_StopIteration) {
             // return mp_const_stop_iteration instead of raising StopIteration
             return mp_const_stop_iteration;
         } else {
@@ -37,7 +37,7 @@ STATIC mp_obj_t it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = it_iternext
 };
diff --git a/py/objint.c b/py/objint.c
index 1ae3cebbf7f0d0c2ca12d45229165b88b533d991..b33557b64d6bead8d7606c3ed3ee846e39e24108 100644
--- a/py/objint.c
+++ b/py/objint.c
@@ -39,7 +39,7 @@ STATIC mp_obj_t int_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "int takes at most 2 arguments, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "int takes at most 2 arguments, %d given", n_args));
     }
 }
 
@@ -65,7 +65,7 @@ mp_obj_t int_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
 
 // This is called only with strings whose value doesn't fit in SMALL_INT
 mp_obj_t mp_obj_new_int_from_long_str(const char *s) {
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "long int not supported in this build"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "long int not supported in this build"));
     return mp_const_none;
 }
 
@@ -75,7 +75,7 @@ mp_obj_t mp_obj_new_int_from_uint(machine_uint_t value) {
     if ((value & (WORD_MSBIT_HIGH | (WORD_MSBIT_HIGH >> 1))) == 0) {
         return MP_OBJ_NEW_SMALL_INT(value);
     }
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "small int overflow"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "small int overflow"));
     return mp_const_none;
 }
 
@@ -83,7 +83,7 @@ mp_obj_t mp_obj_new_int(machine_int_t value) {
     if (MP_OBJ_FITS_SMALL_INT(value)) {
         return MP_OBJ_NEW_SMALL_INT(value);
     }
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OverflowError, "small int overflow"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_OverflowError, "small int overflow"));
     return mp_const_none;
 }
 
@@ -98,7 +98,7 @@ machine_int_t mp_obj_int_get_checked(mp_obj_t self_in) {
 #endif // MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE
 
 const mp_obj_type_t int_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_int,
     .print = int_print,
     .make_new = int_make_new,
diff --git a/py/objlist.c b/py/objlist.c
index 844f9cc81f46304374938b798a1eaa7c433db42b..a6fbe4e423c58075f23f63c9617d323280998ef0 100644
--- a/py/objlist.c
+++ b/py/objlist.c
@@ -62,7 +62,7 @@ STATIC mp_obj_t list_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "list takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "list takes at most 1 argument, %d given", n_args));
     }
     return NULL;
 }
@@ -188,7 +188,7 @@ STATIC mp_obj_t list_pop(uint n_args, const mp_obj_t *args) {
     assert(MP_OBJ_IS_TYPE(args[0], &list_type));
     mp_obj_list_t *self = args[0];
     if (self->len == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_IndexError, "pop from empty list"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "pop from empty list"));
     }
     uint index = mp_get_index(self->base.type, self->len, n_args == 1 ? mp_obj_new_int(-1) : args[1]);
     mp_obj_t ret = self->items[index];
@@ -228,7 +228,7 @@ mp_obj_t mp_obj_list_sort(uint n_args, const mp_obj_t *args, mp_map_t *kwargs) {
     assert(n_args >= 1);
     assert(MP_OBJ_IS_TYPE(args[0], &list_type));
     if (n_args > 1) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError,
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError,
                                           "list.sort takes no positional arguments"));
     }
     mp_obj_list_t *self = args[0];
@@ -346,7 +346,7 @@ STATIC const mp_method_t list_type_methods[] = {
 };
 
 const mp_obj_type_t list_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_list,
     .print = list_print,
     .make_new = list_make_new,
@@ -408,7 +408,7 @@ mp_obj_t list_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t list_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = list_it_iternext,
 };
diff --git a/py/objmap.c b/py/objmap.c
index 012f0aadb551c805d90f6a77ca41cff6b2288b78..cbaef6fb9a94b78ad0b8823c08127ef9a7d76994 100644
--- a/py/objmap.c
+++ b/py/objmap.c
@@ -17,7 +17,7 @@ typedef struct _mp_obj_map_t {
 
 STATIC mp_obj_t map_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) {
     if (n_args < 2 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "map must have at least 2 arguments and no keyword arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "map must have at least 2 arguments and no keyword arguments"));
     }
     assert(n_args >= 2);
     mp_obj_map_t *o = m_new_obj_var(mp_obj_map_t, mp_obj_t, n_args - 1);
@@ -51,7 +51,7 @@ STATIC mp_obj_t map_iternext(mp_obj_t self_in) {
 }
 
 const mp_obj_type_t map_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_map,
     .make_new = map_make_new,
     .getiter = map_getiter,
diff --git a/py/objmodule.c b/py/objmodule.c
index 14a2491711c477e3f4900ee16027d8d133f03c86..ab460fbd3918584866a4c264f27a690bdbf8008c 100644
--- a/py/objmodule.c
+++ b/py/objmodule.c
@@ -38,7 +38,7 @@ STATIC bool module_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
 }
 
 const mp_obj_type_t module_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_module,
     .print = module_print,
     .load_attr = module_load_attr,
diff --git a/py/objnone.c b/py/objnone.c
index 73f2601be4834a8621a9496c7187d492605f79ba..489d34d138ca4d1cf34f26b17def9a0d87de8d6d 100644
--- a/py/objnone.c
+++ b/py/objnone.c
@@ -24,7 +24,7 @@ STATIC mp_obj_t none_unary_op(int op, mp_obj_t o_in) {
 }
 
 const mp_obj_type_t none_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_NoneType,
     .print = none_print,
     .unary_op = none_unary_op,
diff --git a/py/objrange.c b/py/objrange.c
index a526ebcec6214a1be980d30eb7b302ba8da08549..80c592838555e9a873fb712faeda891abb1d4758 100644
--- a/py/objrange.c
+++ b/py/objrange.c
@@ -24,7 +24,7 @@ STATIC mp_obj_t range_getiter(mp_obj_t o_in) {
 }
 
 STATIC const mp_obj_type_t range_type = {
-    { &mp_const_type} ,
+    { &mp_type_type} ,
     .name = MP_QSTR_range,
     .getiter = range_getiter,
 };
@@ -62,7 +62,7 @@ STATIC mp_obj_t range_it_iternext(mp_obj_t o_in) {
 }
 
 STATIC const mp_obj_type_t range_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = range_it_iternext,
 };
diff --git a/py/objset.c b/py/objset.c
index 580b9de8e6600ea574d32d752141539b64b4b748..aea107fc1ca27427bf81a70f8a2ab668b89c7a98 100644
--- a/py/objset.c
+++ b/py/objset.c
@@ -67,12 +67,12 @@ STATIC mp_obj_t set_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "set takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "set takes at most 1 argument, %d given", n_args));
     }
 }
 
 const mp_obj_type_t set_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = set_it_iternext,
 };
@@ -310,7 +310,7 @@ STATIC mp_obj_t set_pop(mp_obj_t self_in) {
     mp_obj_set_t *self = self_in;
 
     if (self->set.used == 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_KeyError, "pop from an empty set"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_KeyError, "pop from an empty set"));
     }
     mp_obj_t obj = mp_set_lookup(&self->set, NULL,
                          MP_MAP_LOOKUP_REMOVE_IF_FOUND | MP_MAP_LOOKUP_FIRST);
@@ -322,7 +322,7 @@ STATIC mp_obj_t set_remove(mp_obj_t self_in, mp_obj_t item) {
     assert(MP_OBJ_IS_TYPE(self_in, &set_type));
     mp_obj_set_t *self = self_in;
     if (mp_set_lookup(&self->set, item, MP_MAP_LOOKUP_REMOVE_IF_FOUND) == MP_OBJ_NULL) {
-        nlr_jump(mp_obj_new_exception(MP_QSTR_KeyError));
+        nlr_jump(mp_obj_new_exception(&mp_type_KeyError));
     }
     return mp_const_none;
 }
@@ -446,7 +446,7 @@ STATIC const mp_method_t set_type_methods[] = {
 };
 
 const mp_obj_type_t set_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_set,
     .print = set_print,
     .make_new = set_make_new,
diff --git a/py/objslice.c b/py/objslice.c
index 66a3c7a7a7eebb94a84e300f885507313920272f..10df671fea0460a0607d2b7a2ee4a116d8327f93 100644
--- a/py/objslice.c
+++ b/py/objslice.c
@@ -22,7 +22,7 @@ void ellipsis_print(void (*print)(void *env, const char *fmt, ...), void *env, m
 }
 
 const mp_obj_type_t ellipsis_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Ellipsis,
     .print = ellipsis_print,
 };
@@ -49,7 +49,7 @@ void slice_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_o
 }
 
 const mp_obj_type_t slice_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_slice,
     .print = slice_print,
 };
diff --git a/py/objstr.c b/py/objstr.c
index 6ccd239959a5fc4c44ecd800d3e05ff07a70b20e..a1291a220356e9e58ed26fdec3788468fddeb75c 100644
--- a/py/objstr.c
+++ b/py/objstr.c
@@ -124,7 +124,7 @@ STATIC mp_obj_t str_binary_op(int op, mp_obj_t lhs_in, mp_obj_t rhs_in) {
             } else {
                 // Message doesn't match CPython, but we don't have so much bytes as they
                 // to spend them on verbose wording
-                nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "index must be int"));
+                nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "index must be int"));
             }
 
         case RT_BINARY_OP_ADD:
@@ -235,7 +235,7 @@ STATIC mp_obj_t str_join(mp_obj_t self_in, mp_obj_t arg) {
     return mp_obj_str_builder_end(joined_str);
 
 bad_arg:
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "?str.join expecting a list of str's"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "?str.join expecting a list of str's"));
 }
 
 #define is_ws(c) ((c) == ' ' || (c) == '\t')
@@ -387,7 +387,7 @@ mp_obj_t str_format(uint n_args, const mp_obj_t *args) {
             } else {
                 while (str < top && *str != '}') str++;
                 if (arg_i >= n_args) {
-                    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_IndexError, "tuple index out of range"));
+                    nlr_jump(mp_obj_new_exception_msg(&mp_type_IndexError, "tuple index out of range"));
                 }
                 // TODO: may be PRINT_REPR depending on formatting code
                 mp_obj_print_helper((void (*)(void*, const char*, ...))vstr_printf, vstr, args[arg_i], PRINT_STR);
@@ -507,7 +507,7 @@ STATIC const mp_method_t str_type_methods[] = {
 };
 
 const mp_obj_type_t str_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_str,
     .print = str_print,
     .binary_op = str_binary_op,
@@ -517,7 +517,7 @@ const mp_obj_type_t str_type = {
 
 // Reuses most of methods from str
 const mp_obj_type_t bytes_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_bytes,
     .print = str_print,
     .binary_op = str_binary_op,
@@ -589,7 +589,7 @@ bool mp_obj_str_equal(mp_obj_t s1, mp_obj_t s2) {
 
 void bad_implicit_conversion(mp_obj_t self_in) __attribute__((noreturn));
 void bad_implicit_conversion(mp_obj_t self_in) {
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "Can't convert '%s' object to str implicitly", mp_obj_get_type_str(self_in)));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "Can't convert '%s' object to str implicitly", mp_obj_get_type_str(self_in)));
 }
 
 uint mp_obj_str_get_hash(mp_obj_t self_in) {
@@ -667,7 +667,7 @@ STATIC mp_obj_t str_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t str_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = str_it_iternext,
 };
@@ -685,7 +685,7 @@ STATIC mp_obj_t bytes_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t bytes_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = bytes_it_iternext,
 };
diff --git a/py/objtuple.c b/py/objtuple.c
index de49ce74efcdbf03a2fa85090d05041352a8f4b8..41ad8a5d58c760d2d255f30b19736844e3dc55ff 100644
--- a/py/objtuple.c
+++ b/py/objtuple.c
@@ -70,7 +70,7 @@ STATIC mp_obj_t tuple_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const m
         }
 
         default:
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "tuple takes at most 1 argument, %d given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "tuple takes at most 1 argument, %d given", n_args));
     }
 }
 
@@ -174,7 +174,7 @@ STATIC const mp_method_t tuple_type_methods[] = {
 };
 
 const mp_obj_type_t tuple_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_tuple,
     .print = tuple_print,
     .make_new = tuple_make_new,
@@ -241,7 +241,7 @@ STATIC mp_obj_t tuple_it_iternext(mp_obj_t self_in) {
 }
 
 STATIC const mp_obj_type_t tuple_it_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_iterator,
     .iternext = tuple_it_iternext,
 };
diff --git a/py/objtype.c b/py/objtype.c
index a1592140cb3677b747165ed15980906d266eb603..46b96c7314fa3adfad26ced14222e9af3092768b 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -64,7 +64,7 @@ STATIC mp_obj_t mp_obj_class_lookup(const mp_obj_type_t *type, qstr attr) {
             return NULL;
         }
         for (uint i = 0; i < len - 1; i++) {
-            assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type));
+            assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
             mp_obj_t obj = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr);
             if (obj != MP_OBJ_NULL) {
                 return obj;
@@ -72,7 +72,7 @@ STATIC mp_obj_t mp_obj_class_lookup(const mp_obj_type_t *type, qstr attr) {
         }
 
         // search last base (simple tail recursion elimination)
-        assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(items[len - 1], &mp_type_type));
         type = (mp_obj_type_t*)items[len - 1];
     }
 }
@@ -82,7 +82,7 @@ STATIC void class_print(void (*print)(void *env, const char *fmt, ...), void *en
 }
 
 STATIC mp_obj_t class_make_new(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *args) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
 
     mp_obj_t o = mp_obj_new_class(self_in);
@@ -103,13 +103,13 @@ STATIC mp_obj_t class_make_new(mp_obj_t self_in, uint n_args, uint n_kw, const m
             m_del(mp_obj_t, args2, 1 + n_args + 2 * n_kw);
         }
         if (init_ret != mp_const_none) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "__init__() should return None, not '%s'", mp_obj_get_type_str(init_ret)));
         }
 
     } else {
         // TODO
         if (n_args != 0) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes 0 positional arguments but %d were given", n_args));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes 0 positional arguments but %d were given", n_args));
         }
     }
 
@@ -252,7 +252,7 @@ bool class_store_item(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) {
 /******************************************************************************/
 // type object
 //  - the struct is mp_obj_type_t and is defined in obj.h so const types can be made
-//  - there is a constant mp_obj_type_t (called mp_const_type) for the 'type' object
+//  - there is a constant mp_obj_type_t (called mp_type_type) for the 'type' object
 //  - creating a new class (a new type) creates a new mp_obj_type_t
 
 STATIC void type_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) {
@@ -274,7 +274,7 @@ STATIC mp_obj_t type_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp
             return mp_obj_new_type(mp_obj_str_get_qstr(args[0]), args[1], args[2]);
 
         default:
-            nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "type takes 1 or 3 arguments"));
+            nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "type takes 1 or 3 arguments"));
     }
 }
 
@@ -284,7 +284,7 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj
     mp_obj_type_t *self = self_in;
 
     if (self->make_new == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "cannot create '%s' instances", qstr_str(self->name)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "cannot create '%s' instances", qstr_str(self->name)));
     }
 
     // make new instance
@@ -296,7 +296,7 @@ STATIC mp_obj_t type_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj
 
 // for fail, do nothing; for attr, dest[0] = value; for method, dest[0] = method, dest[1] = self
 STATIC void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
     mp_obj_t member = mp_obj_class_lookup(self, attr);
     if (member != MP_OBJ_NULL) {
@@ -318,7 +318,7 @@ STATIC void type_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
 }
 
 STATIC bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
-    assert(MP_OBJ_IS_TYPE(self_in, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self_in, &mp_type_type));
     mp_obj_type_t *self = self_in;
 
     // TODO CPython allows STORE_ATTR to a class, but is this the correct implementation?
@@ -333,8 +333,8 @@ STATIC bool type_store_attr(mp_obj_t self_in, qstr attr, mp_obj_t value) {
     }
 }
 
-const mp_obj_type_t mp_const_type = {
-    { &mp_const_type },
+const mp_obj_type_t mp_type_type = {
+    { &mp_type_type },
     .name = MP_QSTR_type,
     .print = type_print,
     .make_new = type_make_new,
@@ -347,7 +347,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
     assert(MP_OBJ_IS_TYPE(bases_tuple, &tuple_type)); // Micro Python restriction, for now
     assert(MP_OBJ_IS_TYPE(locals_dict, &dict_type)); // Micro Python restriction, for now
     mp_obj_type_t *o = m_new0(mp_obj_type_t, 1);
-    o->base.type = &mp_const_type;
+    o->base.type = &mp_type_type;
     o->name = name;
     o->print = class_print;
     o->make_new = class_make_new;
@@ -383,7 +383,7 @@ STATIC mp_obj_t super_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const m
     if (n_args != 2 || n_kw != 0) {
         // 0 arguments are turned into 2 in the compiler
         // 1 argument is not yet implemented
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "super() requires 2 arguments"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "super() requires 2 arguments"));
     }
     return mp_obj_new_super(args[0], args[1]);
 }
@@ -393,7 +393,7 @@ STATIC void super_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
     assert(MP_OBJ_IS_TYPE(self_in, &super_type));
     mp_obj_super_t *self = self_in;
 
-    assert(MP_OBJ_IS_TYPE(self->type, &mp_const_type));
+    assert(MP_OBJ_IS_TYPE(self->type, &mp_type_type));
 
     mp_obj_type_t *type = self->type;
 
@@ -406,7 +406,7 @@ STATIC void super_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
     mp_obj_t *items;
     mp_obj_tuple_get(type->bases_tuple, &len, &items);
     for (uint i = 0; i < len; i++) {
-        assert(MP_OBJ_IS_TYPE(items[i], &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(items[i], &mp_type_type));
         mp_obj_t member = mp_obj_class_lookup((mp_obj_type_t*)items[i], attr);
         if (member != MP_OBJ_NULL) {
             // XXX this and the code in class_load_attr need to be factored out
@@ -438,7 +438,7 @@ STATIC void super_load_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
 }
 
 const mp_obj_type_t super_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_super,
     .print = super_print,
     .make_new = super_make_new,
@@ -452,42 +452,42 @@ mp_obj_t mp_obj_new_super(mp_obj_t type, mp_obj_t obj) {
 }
 
 /******************************************************************************/
-// built-ins specific to types
+// subclassing and built-ins specific to types
 
-STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) {
-    if (!MP_OBJ_IS_TYPE(object, &mp_const_type)) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "issubclass() arg 1 must be a class"));
+bool mp_obj_is_subclass(mp_obj_t object, mp_obj_t classinfo) {
+    if (!MP_OBJ_IS_TYPE(object, &mp_type_type)) {
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 1 must be a class"));
     }
 
     // TODO support a tuple of classes for second argument
-    if (!MP_OBJ_IS_TYPE(classinfo, &mp_const_type)) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "issubclass() arg 2 must be a class"));
+    if (!MP_OBJ_IS_TYPE(classinfo, &mp_type_type)) {
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "issubclass() arg 2 must be a class"));
     }
 
     for (;;) {
         if (object == classinfo) {
-            return mp_const_true;
+            return true;
         }
 
         // not equivalent classes, keep searching base classes
 
-        assert(MP_OBJ_IS_TYPE(object, &mp_const_type));
+        assert(MP_OBJ_IS_TYPE(object, &mp_type_type));
         mp_obj_type_t *self = object;
 
         // for a const struct, this entry might be NULL
         if (self->bases_tuple == MP_OBJ_NULL) {
-            return mp_const_false;
+            return false;
         }
 
         uint len;
         mp_obj_t *items;
         mp_obj_tuple_get(self->bases_tuple, &len, &items);
         if (len == 0) {
-            return mp_const_false;
+            return false;
         }
         for (uint i = 0; i < len - 1; i++) {
-            if (mp_builtin_issubclass(items[i], classinfo) == mp_const_true) {
-                return mp_const_true;
+            if (mp_obj_is_subclass(items[i], classinfo)) {
+                return true;
             }
         }
 
@@ -496,10 +496,14 @@ STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) {
     }
 }
 
+STATIC mp_obj_t mp_builtin_issubclass(mp_obj_t object, mp_obj_t classinfo) {
+    return MP_BOOL(mp_obj_is_subclass(object, classinfo));
+}
+
 MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_issubclass_obj, mp_builtin_issubclass);
 
 STATIC mp_obj_t mp_builtin_isinstance(mp_obj_t object, mp_obj_t classinfo) {
-    return mp_builtin_issubclass(mp_obj_get_type(object), classinfo);
+    return MP_BOOL(mp_obj_is_subclass(mp_obj_get_type(object), classinfo));
 }
 
 MP_DEFINE_CONST_FUN_OBJ_2(mp_builtin_isinstance_obj, mp_builtin_isinstance);
@@ -511,7 +515,7 @@ STATIC mp_obj_t static_class_method_make_new(mp_obj_t self_in, uint n_args, uint
     assert(self_in == &mp_type_staticmethod || self_in == &mp_type_classmethod);
 
     if (n_args != 1 || n_kw != 0) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "function takes 1 positional argument but %d were given", n_args));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "function takes 1 positional argument but %d were given", n_args));
     }
 
     mp_obj_static_class_method_t *o = m_new_obj(mp_obj_static_class_method_t);
@@ -520,13 +524,13 @@ STATIC mp_obj_t static_class_method_make_new(mp_obj_t self_in, uint n_args, uint
 }
 
 const mp_obj_type_t mp_type_staticmethod = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_staticmethod,
     .make_new = static_class_method_make_new
 };
 
 const mp_obj_type_t mp_type_classmethod = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_classmethod,
     .make_new = static_class_method_make_new
 };
diff --git a/py/objzip.c b/py/objzip.c
index 8f1bfe143f60c003631aa64274d8dfb93747c5c0..b939ff6cb9629234a87cd809932406270c25e7bc 100644
--- a/py/objzip.c
+++ b/py/objzip.c
@@ -51,7 +51,7 @@ STATIC mp_obj_t zip_iternext(mp_obj_t self_in) {
 }
 
 const mp_obj_type_t zip_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_zip,
     .make_new = zip_make_new,
     .getiter = zip_getiter,
diff --git a/py/parse.c b/py/parse.c
index d2e892b39033e115377fd9d8923b3d3e49cc7abe..bbab19d352b00e7fd3af3dd9214d4324fdc15e89 100644
--- a/py/parse.c
+++ b/py/parse.c
@@ -302,7 +302,7 @@ STATIC void push_result_rule(parser_t *parser, int src_line, const rule_t *rule,
     push_result_node(parser, (mp_parse_node_t)pn);
 }
 
-mp_parse_node_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, qstr *exc_id_out, const char **exc_msg_out) {
+mp_parse_node_t mp_parse(mp_lexer_t *lex, mp_parse_input_kind_t input_kind, mp_parse_error_kind_t *parse_error_kind_out) {
 
     // allocate memory for the parser and its stacks
 
@@ -634,18 +634,17 @@ finished:
 
 syntax_error:
     if (mp_lexer_is_kind(lex, MP_TOKEN_INDENT)) {
-        *exc_id_out = MP_QSTR_IndentationError;
-        *exc_msg_out = "unexpected indent";
+        *parse_error_kind_out = MP_PARSE_ERROR_UNEXPECTED_INDENT;
     } else if (mp_lexer_is_kind(lex, MP_TOKEN_DEDENT_MISMATCH)) {
-        *exc_id_out = MP_QSTR_IndentationError;
-        *exc_msg_out = "unindent does not match any outer indentation level";
+        *parse_error_kind_out = MP_PARSE_ERROR_UNMATCHED_UNINDENT;
     } else {
-        *exc_id_out = MP_QSTR_SyntaxError;
-        *exc_msg_out = "invalid syntax";
+        *parse_error_kind_out = MP_PARSE_ERROR_INVALID_SYNTAX;
 #ifdef USE_RULE_NAME
         // debugging: print the rule name that failed and the token
-        mp_lexer_show_error_pythonic(lex, rule->rule_name);
+        printf("rule: %s\n", rule->rule_name);
+#if MICROPY_DEBUG_PRINTERS
         mp_token_show(mp_lexer_cur(lex));
+#endif
 #endif
     }
     result = MP_PARSE_NODE_NULL;
diff --git a/py/parse.h b/py/parse.h
index 9797873d1bc3c158469d266c4860d3af0a72334d..66efd8a208967f0520530afff4e91fbd79b05fd8 100644
--- a/py/parse.h
+++ b/py/parse.h
@@ -63,5 +63,11 @@ typedef enum {
     MP_PARSE_EVAL_INPUT,
 } mp_parse_input_kind_t;
 
-// returns MP_PARSE_NODE_NULL on error, and then exc_id_out and exc_msg_out are valid
-mp_parse_node_t mp_parse(struct _mp_lexer_t *lex, mp_parse_input_kind_t input_kind, qstr *exc_id_out, const char **exc_msg_out);
+typedef enum {
+    MP_PARSE_ERROR_UNEXPECTED_INDENT,
+    MP_PARSE_ERROR_UNMATCHED_UNINDENT,
+    MP_PARSE_ERROR_INVALID_SYNTAX,
+} mp_parse_error_kind_t;
+
+// returns MP_PARSE_NODE_NULL on error, and then parse_error_kind_out is valid
+mp_parse_node_t mp_parse(struct _mp_lexer_t *lex, mp_parse_input_kind_t input_kind, mp_parse_error_kind_t *parse_error_kind_out);
diff --git a/py/parsehelper.c b/py/parsehelper.c
new file mode 100644
index 0000000000000000000000000000000000000000..3177e9a341ce3882f24c991b9aa0a87f8ac6dda2
--- /dev/null
+++ b/py/parsehelper.c
@@ -0,0 +1,49 @@
+// these functions are separate from parse.c to keep parser independent of mp_obj_t
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "misc.h"
+#include "mpconfig.h"
+#include "qstr.h"
+#include "lexer.h"
+#include "parse.h"
+#include "obj.h"
+#include "parsehelper.h"
+
+#define STR_UNEXPECTED_INDENT "unexpected indent"
+#define STR_UNMATCHED_UNINDENT "unindent does not match any outer indentation level"
+#define STR_INVALID_SYNTAX "invalid syntax"
+
+void mp_parse_show_exception(mp_lexer_t *lex, mp_parse_error_kind_t parse_error_kind) {
+    printf("  File \"%s\", line %d, column %d\n", qstr_str(mp_lexer_source_name(lex)), mp_lexer_cur(lex)->src_line, mp_lexer_cur(lex)->src_column);
+    switch (parse_error_kind) {
+        case MP_PARSE_ERROR_UNEXPECTED_INDENT:
+            printf("IndentationError: %s\n", STR_UNEXPECTED_INDENT);
+            break;
+
+        case MP_PARSE_ERROR_UNMATCHED_UNINDENT:
+            printf("IndentationError: %s\n", STR_UNMATCHED_UNINDENT);
+            break;
+
+        case MP_PARSE_ERROR_INVALID_SYNTAX:
+        default:
+            printf("SyntaxError: %s\n", STR_INVALID_SYNTAX);
+            break;
+    }
+}
+
+mp_obj_t mp_parse_make_exception(mp_parse_error_kind_t parse_error_kind) {
+    // TODO add source file and line number to exception?
+    switch (parse_error_kind) {
+        case MP_PARSE_ERROR_UNEXPECTED_INDENT:
+            return mp_obj_new_exception_msg(&mp_type_IndentationError, STR_UNEXPECTED_INDENT);
+
+        case MP_PARSE_ERROR_UNMATCHED_UNINDENT:
+            return mp_obj_new_exception_msg(&mp_type_IndentationError, STR_UNMATCHED_UNINDENT);
+
+        case MP_PARSE_ERROR_INVALID_SYNTAX:
+        default:
+            return mp_obj_new_exception_msg(&mp_type_SyntaxError, STR_INVALID_SYNTAX);
+    }
+}
diff --git a/py/parsehelper.h b/py/parsehelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..1de70d19d649239f07f9f037894d129f7c225be5
--- /dev/null
+++ b/py/parsehelper.h
@@ -0,0 +1,2 @@
+void mp_parse_show_exception(mp_lexer_t *lex, mp_parse_error_kind_t parse_error_kind);
+mp_obj_t mp_parse_make_exception(mp_parse_error_kind_t parse_error_kind);
diff --git a/py/py.mk b/py/py.mk
index 1d9eb535aec90fec81d26cac081765987b68cbbe..cbb63b4bfc97125c8d8dfad8dcb95724581fbe94 100644
--- a/py/py.mk
+++ b/py/py.mk
@@ -19,6 +19,7 @@ PY_O_BASENAME = \
 	lexerstr.o \
 	lexerunix.o \
 	parse.o \
+	parsehelper.o \
 	scope.o \
 	compile.o \
 	emitcommon.o \
diff --git a/py/qstrdefs.h b/py/qstrdefs.h
index ac106f98307b8774a065337f3dbb8fa196b96484..9f002777bb9efc21ce4cfb78bcf34944589fdfdf 100644
--- a/py/qstrdefs.h
+++ b/py/qstrdefs.h
@@ -30,6 +30,7 @@ Q(asm_thumb)
 Q(Ellipsis)
 Q(StopIteration)
 
+Q(BaseException)
 Q(AssertionError)
 Q(AttributeError)
 Q(ImportError)
diff --git a/py/runtime.c b/py/runtime.c
index b473a951f320de1dc43a971eb9dc162707ea98d9..68b8fe0778d8981fb3eb8325dd8ff73da3f33db1 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -108,7 +108,7 @@ STATIC const mp_builtin_elem_t builtin_table[] = {
     { MP_QSTR_set, (mp_obj_t)&set_type },
     { MP_QSTR_super, (mp_obj_t)&super_type },
     { MP_QSTR_tuple, (mp_obj_t)&tuple_type },
-    { MP_QSTR_type, (mp_obj_t)&mp_const_type },
+    { MP_QSTR_type, (mp_obj_t)&mp_type_type },
     { MP_QSTR_zip, (mp_obj_t)&zip_type },
 
     { MP_QSTR_classmethod, (mp_obj_t)&mp_type_classmethod },
@@ -144,6 +144,25 @@ STATIC const mp_builtin_elem_t builtin_table[] = {
     { MP_QSTR_str, (mp_obj_t)&mp_builtin_str_obj },
     { MP_QSTR_bytearray, (mp_obj_t)&mp_builtin_bytearray_obj },
 
+    // built-in exceptions
+    { MP_QSTR_BaseException, (mp_obj_t)&mp_type_BaseException },
+    { MP_QSTR_AssertionError, (mp_obj_t)&mp_type_AssertionError },
+    { MP_QSTR_AttributeError, (mp_obj_t)&mp_type_AttributeError },
+    { MP_QSTR_ImportError, (mp_obj_t)&mp_type_ImportError },
+    { MP_QSTR_IndentationError, (mp_obj_t)&mp_type_IndentationError },
+    { MP_QSTR_IndexError, (mp_obj_t)&mp_type_IndexError },
+    { MP_QSTR_KeyError, (mp_obj_t)&mp_type_KeyError },
+    { MP_QSTR_NameError, (mp_obj_t)&mp_type_NameError },
+    { MP_QSTR_SyntaxError, (mp_obj_t)&mp_type_SyntaxError },
+    { MP_QSTR_TypeError, (mp_obj_t)&mp_type_TypeError },
+    { MP_QSTR_ValueError, (mp_obj_t)&mp_type_ValueError },
+    // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/
+    // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation
+    { MP_QSTR_OverflowError, (mp_obj_t)&mp_type_OverflowError },
+    { MP_QSTR_OSError, (mp_obj_t)&mp_type_OSError },
+    { MP_QSTR_NotImplementedError, (mp_obj_t)&mp_type_NotImplementedError },
+    { MP_QSTR_StopIteration, (mp_obj_t)&mp_type_StopIteration },
+
     // Extra builtins as defined by a port
     MICROPY_EXTRA_BUILTINS
 
@@ -166,23 +185,6 @@ void rt_init(void) {
     // init loaded modules table
     mp_map_init(&map_loaded_modules, 3);
 
-    // built-in exceptions (TODO, make these proper classes, and const if possible)
-    mp_map_add_qstr(&map_builtins, MP_QSTR_AttributeError, mp_obj_new_exception(MP_QSTR_AttributeError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_ImportError, mp_obj_new_exception(MP_QSTR_ImportError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_IndexError, mp_obj_new_exception(MP_QSTR_IndexError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_KeyError, mp_obj_new_exception(MP_QSTR_KeyError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_NameError, mp_obj_new_exception(MP_QSTR_NameError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_TypeError, mp_obj_new_exception(MP_QSTR_TypeError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_SyntaxError, mp_obj_new_exception(MP_QSTR_SyntaxError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_ValueError, mp_obj_new_exception(MP_QSTR_ValueError));
-    // Somehow CPython managed to have OverflowError not inherit from ValueError ;-/
-    // TODO: For MICROPY_CPYTHON_COMPAT==0 use ValueError to avoid exc proliferation
-    mp_map_add_qstr(&map_builtins, MP_QSTR_OverflowError, mp_obj_new_exception(MP_QSTR_OverflowError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_OSError, mp_obj_new_exception(MP_QSTR_OSError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_AssertionError, mp_obj_new_exception(MP_QSTR_AssertionError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_NotImplementedError, mp_obj_new_exception(MP_QSTR_NotImplementedError));
-    mp_map_add_qstr(&map_builtins, MP_QSTR_StopIteration, mp_obj_new_exception(MP_QSTR_StopIteration));
-
     // built-in objects
     mp_map_add_qstr(&map_builtins, MP_QSTR_Ellipsis, mp_const_ellipsis);
 
@@ -413,7 +415,7 @@ mp_obj_t rt_load_const_dec(qstr qstr) {
         }
     }
     if (*s != 0) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_SyntaxError, "invalid syntax for number"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_SyntaxError, "invalid syntax for number"));
     }
     if (exp_neg) {
         exp_val = -exp_val;
@@ -431,7 +433,7 @@ mp_obj_t rt_load_const_dec(qstr qstr) {
         return mp_obj_new_float(dec_val);
     }
 #else
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_SyntaxError, "decimal numbers not supported"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_SyntaxError, "decimal numbers not supported"));
 #endif
 }
 
@@ -470,7 +472,7 @@ mp_obj_t rt_load_global(qstr qstr) {
                     return e->fun;
                 }
             }
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_NameError, "name '%s' is not defined", qstr_str(qstr)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_NameError, "name '%s' is not defined", qstr_str(qstr)));
         }
     }
     return elem->value;
@@ -529,7 +531,7 @@ mp_obj_t rt_unary_op(int op, mp_obj_t arg) {
             }
         }
         // TODO specify in error message what the operator is
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "bad operand type for unary operator: '%s'", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "bad operand type for unary operator: '%s'", type->name));
     }
 }
 
@@ -569,9 +571,13 @@ mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) {
 
     // deal with exception_match for all types
     if (op == RT_BINARY_OP_EXCEPTION_MATCH) {
-        // TODO properly! at the moment it just compares the exception identifier for equality
-        if (MP_OBJ_IS_TYPE(lhs, &exception_type) && MP_OBJ_IS_TYPE(rhs, &exception_type)) {
-            if (mp_obj_exception_get_type(lhs) == mp_obj_exception_get_type(rhs)) {
+        // rhs must be issubclass(rhs, BaseException)
+        if (mp_obj_is_exception_type(rhs)) {
+            // if lhs is an instance of an exception, then extract and use its type
+            if (mp_obj_is_exception_instance(lhs)) {
+                lhs = mp_obj_get_type(lhs);
+            }
+            if (mp_obj_is_subclass(lhs, rhs)) {
                 return mp_const_true;
             } else {
                 return mp_const_false;
@@ -673,7 +679,7 @@ mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) {
         }
 
         nlr_jump(mp_obj_new_exception_msg_varg(
-                     MP_QSTR_TypeError, "'%s' object is not iterable",
+                     &mp_type_TypeError, "'%s' object is not iterable",
                      mp_obj_get_type_str(rhs)));
         return mp_const_none;
     }
@@ -690,7 +696,7 @@ mp_obj_t rt_binary_op(int op, mp_obj_t lhs, mp_obj_t rhs) {
     // TODO implement dispatch for reverse binary ops
 
     // TODO specify in error message what the operator is
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError,
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
         "unsupported operand types for binary operator: '%s', '%s'",
         mp_obj_get_type_str(lhs), mp_obj_get_type_str(rhs)));
     return mp_const_none;
@@ -772,7 +778,7 @@ mp_obj_t rt_call_function_n_kw(mp_obj_t fun_in, uint n_args, uint n_kw, const mp
     if (type->call != NULL) {
         return type->call(fun_in, n_args, n_kw, args);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not callable", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not callable", type->name));
     }
 }
 
@@ -836,9 +842,9 @@ void rt_unpack_sequence(mp_obj_t seq_in, uint num, mp_obj_t *items) {
     return;
 
 too_short:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "need more than %d values to unpack", seq_len));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "need more than %d values to unpack", seq_len));
 too_long:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "too many values to unpack (expected %d)", num));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "too many values to unpack (expected %d)", num));
 }
 
 mp_obj_t rt_build_map(int n_args) {
@@ -925,10 +931,10 @@ void rt_load_method(mp_obj_t base, qstr attr, mp_obj_t *dest) {
     if (dest[0] == MP_OBJ_NULL) {
         // no attribute/method called attr
         // following CPython, we give a more detailed error message for type objects
-        if (MP_OBJ_IS_TYPE(base, &mp_const_type)) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "type object '%s' has no attribute '%s'", ((mp_obj_type_t*)base)->name, qstr_str(attr)));
+        if (MP_OBJ_IS_TYPE(base, &mp_type_type)) {
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "type object '%s' has no attribute '%s'", ((mp_obj_type_t*)base)->name, qstr_str(attr)));
         } else {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
         }
     }
 }
@@ -941,7 +947,7 @@ void rt_store_attr(mp_obj_t base, qstr attr, mp_obj_t value) {
             return;
         }
     }
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_AttributeError, "'%s' object has no attribute '%s'", mp_obj_get_type_str(base), qstr_str(attr)));
 }
 
 void rt_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) {
@@ -961,7 +967,7 @@ void rt_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t value) {
             }
             // TODO: call base classes here?
         }
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object does not support item assignment", mp_obj_get_type_str(base)));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object does not support item assignment", mp_obj_get_type_str(base)));
     }
 }
 
@@ -978,7 +984,7 @@ mp_obj_t rt_getiter(mp_obj_t o_in) {
             return mp_obj_new_getitem_iter(dest);
         } else {
             // object not iterable
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not iterable", type->name));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not iterable", type->name));
         }
     }
 }
@@ -988,7 +994,22 @@ mp_obj_t rt_iternext(mp_obj_t o_in) {
     if (type->iternext != NULL) {
         return type->iternext(o_in);
     } else {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_TypeError, "'%s' object is not an iterator", type->name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "'%s' object is not an iterator", type->name));
+    }
+}
+
+mp_obj_t rt_make_raise_obj(mp_obj_t o) {
+    DEBUG_printf("raise %p\n", o);
+    if (mp_obj_is_exception_type(o)) {
+        // o is an exception type (it is derived from BaseException (or is BaseException))
+        // create and return a new exception instance by calling o
+        return rt_call_function_n_kw(o, 0, 0, NULL);
+    } else if (mp_obj_is_exception_instance(o)) {
+        // o is an instance of an exception, so use it as the exception
+        return o;
+    } else {
+        // o cannot be used as an exception, so return a type error (which will be raised by the caller)
+        return mp_obj_new_exception_msg(&mp_type_TypeError, "exceptions must derive from BaseException");
     }
 }
 
diff --git a/py/runtime.h b/py/runtime.h
index f5a9f2abc7e2f7117eb9ea6ff882a24b4f9dff80..1eef99d2ff21f700bc7f4ce4b8b7bbd68bc73164 100644
--- a/py/runtime.h
+++ b/py/runtime.h
@@ -37,6 +37,7 @@ void rt_store_attr(mp_obj_t base, qstr attr, mp_obj_t val);
 void rt_store_subscr(mp_obj_t base, mp_obj_t index, mp_obj_t val);
 mp_obj_t rt_getiter(mp_obj_t o);
 mp_obj_t rt_iternext(mp_obj_t o);
+mp_obj_t rt_make_raise_obj(mp_obj_t o);
 mp_obj_t rt_import_name(qstr name, mp_obj_t fromlist, mp_obj_t level);
 mp_obj_t rt_import_from(mp_obj_t module, qstr name);
 void rt_import_all(mp_obj_t module);
diff --git a/py/sequence.c b/py/sequence.c
index f5310737a71c59d614f12f09b4e14bfd40663499..d3c3e328545d7f97d23b5dead4525ca89e013e55 100644
--- a/py/sequence.c
+++ b/py/sequence.c
@@ -162,7 +162,7 @@ mp_obj_t mp_seq_index_obj(const mp_obj_t *items, uint len, uint n_args, const mp
         }
     }
 
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "object not in sequence"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "object not in sequence"));
 }
 
 mp_obj_t mp_seq_count_obj(const mp_obj_t *items, uint len, mp_obj_t value) {
diff --git a/py/stream.c b/py/stream.c
index f3487cc6ed3faaa9282d1fb6ad41c9402aa68d15..59877d72424e8da4a99a67371f1894190d4e3a54 100644
--- a/py/stream.c
+++ b/py/stream.c
@@ -16,7 +16,7 @@ STATIC mp_obj_t stream_read(uint n_args, const mp_obj_t *args) {
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)args[0];
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     machine_int_t sz;
@@ -27,7 +27,7 @@ STATIC mp_obj_t stream_read(uint n_args, const mp_obj_t *args) {
     int error;
     machine_int_t out_sz = o->type->stream_p.read(o, buf, sz, &error);
     if (out_sz == -1) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
     } else {
         mp_obj_t s = mp_obj_new_str(buf, out_sz, false); // will reallocate to use exact size
         m_free(buf, sz);
@@ -39,7 +39,7 @@ STATIC mp_obj_t stream_write(mp_obj_t self_in, mp_obj_t arg) {
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)self_in;
     if (o->type->stream_p.write == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     uint sz;
@@ -47,7 +47,7 @@ STATIC mp_obj_t stream_write(mp_obj_t self_in, mp_obj_t arg) {
     int error;
     machine_int_t out_sz = o->type->stream_p.write(self_in, buf, sz, &error);
     if (out_sz == -1) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
     } else {
         // http://docs.python.org/3/library/io.html#io.RawIOBase.write
         // "None is returned if the raw stream is set not to block and no single byte could be readily written to it."
@@ -62,7 +62,7 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)self_in;
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     int total_size = 0;
@@ -74,7 +74,7 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
     while (true) {
         machine_int_t out_sz = o->type->stream_p.read(self_in, p, current_read, &error);
         if (out_sz == -1) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
         }
         if (out_sz == 0) {
             break;
@@ -88,7 +88,7 @@ STATIC mp_obj_t stream_readall(mp_obj_t self_in) {
             p = vstr_extend(vstr, current_read);
             if (p == NULL) {
                 // TODO
-                nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError/*MP_QSTR_RuntimeError*/, "Out of memory"));
+                nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError/*&mp_type_RuntimeError*/, "Out of memory"));
             }
         }
     }
@@ -103,7 +103,7 @@ STATIC mp_obj_t stream_unbuffered_readline(uint n_args, const mp_obj_t *args) {
     struct _mp_obj_base_t *o = (struct _mp_obj_base_t *)args[0];
     if (o->type->stream_p.read == NULL) {
         // CPython: io.UnsupportedOperation, OSError subclass
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Operation not supported"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Operation not supported"));
     }
 
     machine_int_t max_size = -1;
@@ -123,12 +123,12 @@ STATIC mp_obj_t stream_unbuffered_readline(uint n_args, const mp_obj_t *args) {
         char *p = vstr_add_len(vstr, 1);
         if (p == NULL) {
             // TODO
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError/*MP_QSTR_RuntimeError*/, "Out of memory"));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError/*&mp_type_RuntimeError*/, "Out of memory"));
         }
 
         machine_int_t out_sz = o->type->stream_p.read(o, p, 1, &error);
         if (out_sz == -1) {
-            nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error));
+            nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error));
         }
         if (out_sz == 0) {
             // Back out previously added byte
diff --git a/py/strtonum.c b/py/strtonum.c
index 74fa3d6759fed9ea61b0122dba4a7afca73278e9..e83b9751dc9968330bc818b66a5aa8be7c4dbcbb 100644
--- a/py/strtonum.c
+++ b/py/strtonum.c
@@ -18,7 +18,7 @@ long strtonum(const char *restrict s, int base) {
 
     // check radix base
     if ((base != 0 && base < 2) || base > 36) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "ValueError: int() arg 2 must be >=2 and <= 36"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "ValueError: int() arg 2 must be >=2 and <= 36"));
     }
     // skip surrounded whitespace
     while (isspace((c = *(p++))));
@@ -84,7 +84,7 @@ done:
     return (found ^ neg) - neg;
 
 value_error:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "invalid literal for int() with base %d: '%s'", base, s));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "invalid literal for int() with base %d: '%s'", base, s));
 }
 
 #else /* defined(UNIX) */
diff --git a/py/vm.c b/py/vm.c
index 1fc5b4a57afdba895b8868beb0ba767b5bd8e255..461fecbda87e8f142286aaff995c8bb4a0ce9da8 100644
--- a/py/vm.c
+++ b/py/vm.c
@@ -371,7 +371,7 @@ unwind_jump:
                         // if TOS is None, just pops it and continues
                         // if TOS is an integer, does something else
                         // else error
-                        if (MP_OBJ_IS_TYPE(TOP(), &exception_type)) {
+                        if (mp_obj_is_exception_instance(TOP())) {
                             nlr_jump(TOP());
                         }
                         if (TOP() == mp_const_none) {
@@ -575,7 +575,7 @@ unwind_return:
                         unum = *ip++;
                         assert(unum == 1);
                         obj1 = POP();
-                        nlr_jump(obj1);
+                        nlr_jump(rt_make_raise_obj(obj1));
 
                     case MP_BC_YIELD_VALUE:
                         nlr_pop();
@@ -613,7 +613,7 @@ unwind_return:
             // set file and line number that the exception occurred at
             // TODO: don't set traceback for exceptions re-raised by END_FINALLY.
             // But consider how to handle nested exceptions.
-            if (MP_OBJ_IS_TYPE(nlr.ret_val, &exception_type)) {
+            if (mp_obj_is_exception_instance(nlr.ret_val)) {
                 machine_uint_t code_info_size = code_info[0] | (code_info[1] << 8) | (code_info[2] << 16) | (code_info[3] << 24);
                 qstr source_file = code_info[4] | (code_info[5] << 8) | (code_info[6] << 16) | (code_info[7] << 24);
                 qstr block_name = code_info[8] | (code_info[9] << 8) | (code_info[10] << 16) | (code_info[11] << 24);
diff --git a/stm/adc.c b/stm/adc.c
index 89f137a08c74f7ba1cc9673456dd4c900a0f5fcd..cd915b172447f7ab9b2734968d30473306b2fa55 100644
--- a/stm/adc.c
+++ b/stm/adc.c
@@ -332,7 +332,7 @@ static const mp_method_t adc_all_methods[] = {
 };
 
 static const mp_obj_type_t adc_all_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_ADC,
     .print = adc_all_print,
     .methods = adc_all_methods,
@@ -386,7 +386,7 @@ static const mp_method_t adc_methods[] = {
 };
 
 static const mp_obj_type_t adc_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_ADC,
     .print = adc_print,
     .methods = adc_methods,
@@ -427,7 +427,7 @@ mp_obj_t pyb_ADC(mp_obj_t pin_name_obj) {
     }
 
     if (i == ADC_NUM_CHANNELS) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "pin %s does not have ADC capabilities", pin_name));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "pin %s does not have ADC capabilities", pin_name));
     }
 
     // init ADC just for this channel
@@ -438,7 +438,7 @@ mp_obj_t pyb_ADC(mp_obj_t pin_name_obj) {
     return o;
 
 pin_error:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "pin %s does not exist", pin_name));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "pin %s does not exist", pin_name));
 }
 
 MP_DEFINE_CONST_FUN_OBJ_1(pyb_ADC_obj, pyb_ADC);
diff --git a/stm/file.c b/stm/file.c
index f50d3771a0cdc261ccb249397b7021a88a0c103e..283159a69b42d3577a86f73195e50b7f6043607d 100644
--- a/stm/file.c
+++ b/stm/file.c
@@ -60,7 +60,7 @@ static const mp_method_t file_methods[] = {
 };
 
 static const mp_obj_type_t file_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_File,
     .print = file_obj_print,
     .methods = file_methods,
diff --git a/stm/i2c.c b/stm/i2c.c
index f355878f2bcccd2a6a2305941b4be207a14d6a56..33d9a43f3a8d9aa0a0001b6468319d01c81184d3 100644
--- a/stm/i2c.c
+++ b/stm/i2c.c
@@ -335,7 +335,7 @@ static const mp_method_t i2c_methods[] = {
 };
 
 static const mp_obj_type_t i2c_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_I2C,
     .print = i2c_obj_print,
     .methods = i2c_methods,
diff --git a/stm/led.c b/stm/led.c
index 3a7d8a7a7f50ed6145c38d058465787fd950ef24..683e82bc66ace561aa33d9147773673a40fd8ce3 100644
--- a/stm/led.c
+++ b/stm/led.c
@@ -148,7 +148,7 @@ static const mp_method_t led_methods[] = {
 };
 
 static const mp_obj_type_t led_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Led,
     .print = led_obj_print,
     .methods = led_methods,
diff --git a/stm/main.c b/stm/main.c
index 3484fd28d0607338aa4dd84388553586ea803aed..2bf7c1ba36f6773c3ef6446dc7b26181d8193119 100644
--- a/stm/main.c
+++ b/stm/main.c
@@ -24,6 +24,7 @@
 #include "lexerfatfs.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -404,15 +405,13 @@ void do_repl(void) {
         }
 
         mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr_str(&line), vstr_len(&line), 0);
-        qstr parse_exc_id;
-        const char *parse_exc_msg;
-        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_SINGLE_INPUT, &parse_exc_id, &parse_exc_msg);
+        mp_parse_error_kind_t parse_error_kind;
+        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_SINGLE_INPUT, &parse_error_kind);
         qstr source_name = mp_lexer_source_name(lex);
 
         if (pn == MP_PARSE_NODE_NULL) {
             // parse error
-            mp_lexer_show_error_pythonic_prefix(lex);
-            printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+            mp_parse_show_exception(lex, parse_error_kind);
             mp_lexer_free(lex);
         } else {
             // parse okay
@@ -456,15 +455,13 @@ bool do_file(const char *filename) {
         return false;
     }
 
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_error_kind);
     qstr source_name = mp_lexer_source_name(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error
-        mp_lexer_show_error_pythonic_prefix(lex);
-        printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+        mp_parse_show_exception(lex, parse_error_kind);
         mp_lexer_free(lex);
         return false;
     }
@@ -536,7 +533,7 @@ mp_obj_t pyb_gpio(uint n_args, mp_obj_t *args) {
     }
 
 pin_error:
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_ValueError, "pin %s does not exist", pin_name));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "pin %s does not exist", pin_name));
 }
 
 MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_gpio_obj, 1, 2, pyb_gpio);
diff --git a/stm/sdcard.c b/stm/sdcard.c
index c98bab4d91b80b71a1f14d884caf4654f6d1effd..0b5fdb2c87f2ff34883b83b40c30f1ea32a37daa 100644
--- a/stm/sdcard.c
+++ b/stm/sdcard.c
@@ -202,7 +202,7 @@ static const mp_method_t sdcard_methods[] = {
 };
 
 static const mp_obj_type_t sdcard_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_SDcard,
     .methods = sdcard_methods,
 };
diff --git a/stm/servo.c b/stm/servo.c
index 4b69eefcfb40df8530efe28c17ee849ec3fd30b5..4943a6442560af5593ee8394ffa768a091caae29 100644
--- a/stm/servo.c
+++ b/stm/servo.c
@@ -144,7 +144,7 @@ static const mp_method_t servo_methods[] = {
 };
 
 static const mp_obj_type_t servo_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Servo,
     .print = servo_obj_print,
     .methods = servo_methods,
diff --git a/stm/usart.c b/stm/usart.c
index e24211a83c9f81c09b0a54ee12b9e53129097436..6478f0115281b102b14a1ee7cbd3bff54a39fcda 100644
--- a/stm/usart.c
+++ b/stm/usart.c
@@ -242,7 +242,7 @@ static const mp_method_t usart_methods[] = {
 };
 
 static const mp_obj_type_t usart_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Usart,
     .print = usart_obj_print,
     .methods = usart_methods,
diff --git a/stm/usb.c b/stm/usb.c
index 0fe56d321231cf760c052d324dd03ae38399492d..718d432e61fdd40fa57c443ec277a2e00b3fed15 100644
--- a/stm/usb.c
+++ b/stm/usb.c
@@ -41,7 +41,7 @@ void pyb_usb_dev_init(void) {
     dev_is_enabled = 1;
 
     // create an exception object for interrupting by VCP
-    mp_const_vcp_interrupt = mp_obj_new_exception(qstr_from_str("VCPInterrupt"));
+    mp_const_vcp_interrupt = mp_obj_new_exception_msg(&mp_type_OSError, "VCPInterrupt");
 #endif
 }
 
@@ -66,6 +66,7 @@ void usb_vcp_receive(const char *buf, uint32_t len) {
             // catch special interrupt character
             if (buf[i] == interrupt_char) {
                 // raise exception when interrupts are finished
+                mp_obj_exception_clear_traceback(mp_const_vcp_interrupt);
                 pendsv_nlr_jump(mp_const_vcp_interrupt);
                 interrupt_char = VCP_CHAR_NONE;
                 continue;
diff --git a/teensy/led.c b/teensy/led.c
index 49ba46b4d2bdbc42e0f560d08436d945e5e1fc68..e2a057416806e20cda1fd5383b9d3a7160277165 100644
--- a/teensy/led.c
+++ b/teensy/led.c
@@ -70,7 +70,7 @@ static const mp_method_t led_methods[] = {
 };
 
 static const mp_obj_type_t led_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Led,
     .print = led_obj_print,
     .methods = led_methods,
diff --git a/teensy/main.c b/teensy/main.c
index 70067927fe77d5cd7ba15207d10da1611d87fe9d..ab7bd46812f8b7de459fe822cc8c6ae6a6e34da8 100644
--- a/teensy/main.c
+++ b/teensy/main.c
@@ -346,15 +346,13 @@ bool do_file(const char *filename) {
         return false;
     }
 
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_error_kind);
     qstr source_name = mp_lexer_source_name(lex);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error
-        mp_lexer_show_error_pythonic_prefix(lex);
-        printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+        mp_parse_show_exception(lex, parse_error_kind);
         mp_lexer_free(lex);
         return false;
     }
@@ -413,15 +411,13 @@ void do_repl(void) {
         }
 
         mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr_str(&line), vstr_len(&line), 0);
-        qstr parse_exc_id;
-        const char *parse_exc_msg;
-        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_SINGLE_INPUT, &parse_exc_id, &parse_exc_msg);
+        mp_parse_error_kind_t parse_error_kind;
+        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_SINGLE_INPUT, &parse_error_kind);
         qstr source_name = mp_lexer_source_name(lex);
 
         if (pn == MP_PARSE_NODE_NULL) {
             // parse error
-            mp_lexer_show_error_pythonic_prefix(lex);
-            printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+            mp_parse_show_exception(lex, parse_error_kind);
             mp_lexer_free(lex);
         } else {
             // parse okay
diff --git a/teensy/servo.c b/teensy/servo.c
index c3a2b28889668bc5a0fdac32d195baad17ed869d..51714dbcc5cdb40befbd5d916c1906e4643cca81 100644
--- a/teensy/servo.c
+++ b/teensy/servo.c
@@ -189,7 +189,7 @@ static const mp_method_t servo_methods[] = {
  */
 
 static const mp_obj_type_t servo_obj_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Servo,
     .print = servo_obj_print,
     .methods = servo_methods,
@@ -217,7 +217,7 @@ mp_obj_t pyb_Servo(void) {
         self->servo_id++;
     }
     m_del_obj(pyb_servo_obj_t, self);
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_ValueError, "No available servo ids"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_ValueError, "No available servo ids"));
     return mp_const_none;
 }
 
diff --git a/unix-cpy/main.c b/unix-cpy/main.c
index 10003c992973c168f330917aac962d68d36c924f..52d63273cee5942355d9f8231ea0e9e7dded91b5 100644
--- a/unix-cpy/main.c
+++ b/unix-cpy/main.c
@@ -10,6 +10,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 
@@ -29,14 +30,12 @@ void do_file(const char *file) {
 
     } else {
         // parse
-        qstr parse_exc_id;
-        const char *parse_exc_msg;
-        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_exc_id, &parse_exc_msg);
+        mp_parse_error_kind_t parse_error_kind;
+        mp_parse_node_t pn = mp_parse(lex, MP_PARSE_FILE_INPUT, &parse_error_kind);
 
         if (pn == MP_PARSE_NODE_NULL) {
             // parse error
-            mp_lexer_show_error_pythonic_prefix(lex);
-            printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+            mp_parse_show_exception(lex, parse_error_kind);
             mp_lexer_free(lex);
             return;
         }
diff --git a/unix/ffi.c b/unix/ffi.c
index a82e4e33868ec0eab2c94b5f6119a651c0a9a8fd..ef9c6ea9d2976e38b62a399628de1dc9a8bf1fe8 100644
--- a/unix/ffi.c
+++ b/unix/ffi.c
@@ -46,7 +46,7 @@ typedef struct _mp_obj_fficallback_t {
     ffi_type *params[];
 } mp_obj_fficallback_t;
 
-static const mp_obj_type_t opaque_type;
+//static const mp_obj_type_t opaque_type;
 static const mp_obj_type_t ffimod_type;
 static const mp_obj_type_t ffifunc_type;
 static const mp_obj_type_t fficallback_type;
@@ -80,7 +80,7 @@ static ffi_type *get_ffi_type(mp_obj_t o_in)
     }
     // TODO: Support actual libffi type objects
 
-    nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Unknown type"));
+    nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Unknown type"));
 }
 
 static mp_obj_t return_ffi_value(ffi_arg val, char type)
@@ -118,7 +118,7 @@ static mp_obj_t ffimod_func(uint n_args, const mp_obj_t *args) {
 
     void *sym = dlsym(self->handle, symname);
     if (sym == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", errno));
     }
     int nparams = MP_OBJ_SMALL_INT_VALUE(mp_obj_len_maybe(args[3]));
     mp_obj_ffifunc_t *o = m_new_obj_var(mp_obj_ffifunc_t, ffi_type*, nparams);
@@ -136,7 +136,7 @@ static mp_obj_t ffimod_func(uint n_args, const mp_obj_t *args) {
 
     int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params);
     if (res != FFI_OK) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Error in ffi_prep_cif"));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error in ffi_prep_cif"));
     }
 
     return o;
@@ -173,12 +173,12 @@ static mp_obj_t mod_ffi_callback(mp_obj_t rettype_in, mp_obj_t func_in, mp_obj_t
 
     int res = ffi_prep_cif(&o->cif, FFI_DEFAULT_ABI, nparams, char2ffi_type(*rettype), o->params);
     if (res != FFI_OK) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "Error in ffi_prep_cif"));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "Error in ffi_prep_cif"));
     }
 
     res = ffi_prep_closure_loc(o->clo, &o->cif, call_py_func, func_in, o->func);
     if (res != FFI_OK) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "ffi_prep_closure_loc"));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "ffi_prep_closure_loc"));
     }
 
     return o;
@@ -192,7 +192,7 @@ static mp_obj_t ffimod_var(mp_obj_t self_in, mp_obj_t vartype_in, mp_obj_t symna
 
     void *sym = dlsym(self->handle, symname);
     if (sym == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", errno));
     }
     mp_obj_ffivar_t *o = m_new_obj(mp_obj_ffivar_t);
     o->base.type = &ffivar_type;
@@ -208,7 +208,7 @@ static mp_obj_t ffimod_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const
     void *mod = dlopen(fname, RTLD_NOW | RTLD_LOCAL);
 
     if (mod == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", errno));
     }
     mp_obj_ffimod_t *o = m_new_obj(mp_obj_ffimod_t);
     o->base.type = type_in;
@@ -224,8 +224,8 @@ static const mp_method_t ffimod_type_methods[] = {
 };
 
 static const mp_obj_type_t ffimod_type = {
-    { &mp_const_type },
-    "ffimod",
+    { &mp_type_type },
+    .name = MP_QSTR_ffimod,
     .print = ffimod_print,
     .make_new = ffimod_make_new,
     .methods = ffimod_type_methods,
@@ -270,8 +270,8 @@ mp_obj_t ffifunc_call(mp_obj_t self_in, uint n_args, uint n_kw, const mp_obj_t *
 }
 
 static const mp_obj_type_t ffifunc_type = {
-    { &mp_const_type },
-    "ffifunc",
+    { &mp_type_type },
+    .name = MP_QSTR_ffifunc,
     .print = ffifunc_print,
     .call = ffifunc_call,
 };
@@ -284,8 +284,8 @@ static void fficallback_print(void (*print)(void *env, const char *fmt, ...), vo
 }
 
 static const mp_obj_type_t fficallback_type = {
-    { &mp_const_type },
-    "fficallback",
+    { &mp_type_type },
+    .name = MP_QSTR_fficallback,
     .print = fficallback_print,
 };
 
@@ -316,20 +316,21 @@ static const mp_method_t ffivar_type_methods[] = {
 };
 
 static const mp_obj_type_t ffivar_type = {
-    { &mp_const_type },
-    "ffivar",
+    { &mp_type_type },
+    .name = MP_QSTR_ffivar,
     .print = ffivar_print,
     .methods = ffivar_type_methods,
 };
 
-// Generic opaque storage object
+// Generic opaque storage object (unused)
 
+/*
 static const mp_obj_type_t opaque_type = {
-    { &mp_const_type },
-    "opaqueval",
+    { &mp_type_type },
+    .name = MP_QSTR_opaqueval,
 //    .print = opaque_print,
 };
-
+*/
 
 mp_obj_t mod_ffi_open(uint n_args, const mp_obj_t *args) {
     return ffimod_make_new((mp_obj_t)&ffimod_type, n_args, 0, args);
diff --git a/unix/file.c b/unix/file.c
index 4e8fba54c88e6bfe7259a8730f01b6370dc55510..444a05d49135589e5b87c2e876bf5e0efe296648 100644
--- a/unix/file.c
+++ b/unix/file.c
@@ -99,7 +99,7 @@ static mp_obj_t fdfile_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const
 
     int fd = open(fname, mode, 0644);
     if (fd == -1) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", errno));
     }
     return fdfile_new(fd);
 }
@@ -115,7 +115,7 @@ static const mp_method_t rawfile_type_methods[] = {
 };
 
 static const mp_obj_type_t rawfile_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_io_dot_FileIO,
     .print = fdfile_print,
     .make_new = fdfile_make_new,
diff --git a/unix/main.c b/unix/main.c
index 6aafe94ddd89f9ca0f5cf5e2258ff977a29a2d35..8e6a76bbad34e4d1065d42207cbdb9d239c16206 100644
--- a/unix/main.c
+++ b/unix/main.c
@@ -11,6 +11,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -49,14 +50,12 @@ static void execute_from_lexer(mp_lexer_t *lex, mp_parse_input_kind_t input_kind
         return;
     }
 
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, input_kind, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, input_kind, &parse_error_kind);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error
-        mp_lexer_show_error_pythonic_prefix(lex);
-        printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+        mp_parse_show_exception(lex, parse_error_kind);
         mp_lexer_free(lex);
         return;
     }
@@ -194,7 +193,7 @@ static const mp_method_t test_methods[] = {
 };
 
 static const mp_obj_type_t test_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_Test,
     .print = test_print,
     .methods = test_methods,
diff --git a/unix/qstrdefsport.h b/unix/qstrdefsport.h
index 42c88b469357278fa2873ba6d29d3f23f011e720..867afaeac06f68286d1340c27398ac1cb52e5b59 100644
--- a/unix/qstrdefsport.h
+++ b/unix/qstrdefsport.h
@@ -17,3 +17,7 @@ Q(getaddrinfo)
 Q(microsocket)
 
 Q(io.FileIO)
+Q(ffimod)
+Q(ffifunc)
+Q(fficallback)
+Q(ffivar)
diff --git a/unix/socket.c b/unix/socket.c
index 25c4bfcb4627d4accc30ec255de09efedaf7a906..a9cf4a81a02db9dd3af6ec82119d2b438c6ec55f 100644
--- a/unix/socket.c
+++ b/unix/socket.c
@@ -29,7 +29,7 @@ static const mp_obj_type_t microsocket_type;
 // Helper functions
 #define RAISE_ERRNO(err_flag, error_val) \
     { if (err_flag == -1) \
-        { nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", error_val)); } }
+        { nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", error_val)); } }
 
 static void get_buffer(mp_obj_t obj, buffer_info_t *bufinfo) {
     mp_obj_base_t *o = (mp_obj_base_t *)obj;
@@ -43,7 +43,7 @@ static void get_buffer(mp_obj_t obj, buffer_info_t *bufinfo) {
     return;
 
 error:
-    nlr_jump(mp_obj_new_exception_msg(MP_QSTR_TypeError, "Operation not supported"));
+    nlr_jump(mp_obj_new_exception_msg(&mp_type_TypeError, "Operation not supported"));
 }
 
 static mp_obj_socket_t *socket_new(int fd) {
@@ -237,7 +237,7 @@ static const mp_method_t microsocket_type_methods[] = {
 };
 
 static const mp_obj_type_t microsocket_type = {
-    { &mp_const_type },
+    { &mp_type_type },
     .name = MP_QSTR_socket,
     .print = socket_print,
     .make_new = socket_make_new,
@@ -260,7 +260,7 @@ static mp_obj_t mod_socket_inet_aton(mp_obj_t arg) {
     const char *s = mp_obj_str_get_str(arg);
     struct in_addr addr;
     if (!inet_aton(s, &addr)) {
-        nlr_jump(mp_obj_new_exception_msg(MP_QSTR_OSError, "Invalid IP address"));
+        nlr_jump(mp_obj_new_exception_msg(&mp_type_OSError, "Invalid IP address"));
     }
 
     return mp_obj_new_int(addr.s_addr);
@@ -273,7 +273,7 @@ static mp_obj_t mod_socket_gethostbyname(mp_obj_t arg) {
     const char *s = mp_obj_str_get_str(arg);
     struct hostent *h = gethostbyname(s);
     if (h == NULL) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[Errno %d]", errno));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[Errno %d]", errno));
     }
     assert(h->h_length == 4);
     return mp_obj_new_int(*(int*)*h->h_addr_list);
@@ -305,7 +305,7 @@ static mp_obj_t mod_socket_getaddrinfo(uint n_args, const mp_obj_t *args) {
     int res = getaddrinfo(host, serv, NULL/*&hints*/, &addr);
 
     if (res != 0) {
-        nlr_jump(mp_obj_new_exception_msg_varg(MP_QSTR_OSError, "[addrinfo error %d]", res));
+        nlr_jump(mp_obj_new_exception_msg_varg(&mp_type_OSError, "[addrinfo error %d]", res));
     }
     assert(addr);
 
diff --git a/windows/main.c b/windows/main.c
index da0cbca44a66d6469f520bf218d60968a252825d..4456a796d0939ce92fd38ea1b16fe48da6e54982 100644
--- a/windows/main.c
+++ b/windows/main.c
@@ -11,6 +11,7 @@
 #include "lexerunix.h"
 #include "parse.h"
 #include "obj.h"
+#include "parsehelper.h"
 #include "compile.h"
 #include "runtime0.h"
 #include "runtime.h"
@@ -39,14 +40,12 @@ static void execute_from_lexer(mp_lexer_t *lex, mp_parse_input_kind_t input_kind
         return;
     }
 
-    qstr parse_exc_id;
-    const char *parse_exc_msg;
-    mp_parse_node_t pn = mp_parse(lex, input_kind, &parse_exc_id, &parse_exc_msg);
+    mp_parse_error_kind_t parse_error_kind;
+    mp_parse_node_t pn = mp_parse(lex, input_kind, &parse_error_kind);
 
     if (pn == MP_PARSE_NODE_NULL) {
         // parse error
-        mp_lexer_show_error_pythonic_prefix(lex);
-        printf("%s: %s\n", qstr_str(parse_exc_id), parse_exc_msg);
+        mp_parse_show_exception(lex, parse_error_kind);
         mp_lexer_free(lex);
         return;
     }