shithub: sl

Download patch

ref: 4725ee7c0550674bb6e4500bd0c3a962c9784945
parent: dd8b10efc2d43bc81c75bb44ab5261a331a49c53
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Fri Apr 18 12:52:28 EDT 2025

make type errors more descriptive

--- a/boot/sl.boot
+++ b/boot/sl.boot
@@ -134,7 +134,7 @@
 8(53)  seta #u8(15)  seta.l #u8(73)  brnn #u8(26)  loadnil #u8(65)  loadg #u8(7)  loada #u8(8)  tcall #u8(6))
             S #fn("z1700215286380861}2:7223062:" #(getprop constructor error "no default constructor for struct: ") S)
             __finish #fn("n120Z3>021220>17062:q:" #(*exit-hooks* #fn(for-each)
-w4^147025d;350426;J50427w8429w:4qw;47<w=47>w?47@wA:" #(*os-name*
+w4^147025d;350426;J50427w8429w:4qw;47<w=47>w?47@wA:" #(*os-name*
  #fn("n0702161:" #(princ "#;> ")) *prompt* "dos" "\\" "/" *directory-separator* "\n" *linefeed*
   *exit-hooks* *stdout* *io-out* *stdin* *io-in* *stderr* *io-err*) __init_globals)
             __rcscript #fn("n0708421c360q@T08422c37023@G08424c3=07526514q@4027^184;3904288451708622c37029@402:^185;3=042;857<865387;3D042=8751;39047>8761:" #(*os-name*
@@ -187,18 +187,19 @@
 3q07401q895440r40r4GKMp4750183=530r40r4G8:UMp47608237027@40288:63:89[;3904798951892:Cf07089152J\\0212:517:d3P07;83r2523E07401q83T5447602:62:89B3N089<2<CF07=83513=07>01828364:8:360q@F07401q895440r40r4GKMp4750183=530r40r4G8;UMp48:360q@=00r40r4Gr/Mp48:3C07?018283898:8;67:760823702@@402A8;63:" #(in-env?
   #fn(top-level-value) length> 255 compile-in compile-arglist emit tcall.l call.l
   builtin->instruction cadr length= λ inlineable? compile-let compile-builtin-call tcall call) compile-app)
-8:360q@=00r40r4Gr/Mp48:3C07?018283898:8;67:760823702@@402A8;63:" #(in-env?
-  #fn(top-level-value) length> 255 compile-in compile-arglist emit tcall.l call.l
+8:360q@=00r40r4Gr/Mp48:3C07?018283898:8;67:760823702@@402A8;63:" #(in-env?
+  #fn(top-level-value) length> 255 compile-in compile-arglist emit tcall.l call.l
+  builtin->instruction cadr length= λ inlineable? compile-let compile-builtin-call tcall call) compile-app)
 in->instruction cadr length= λ inlineable? compile-let compile-builtin-call tcall call) compile-app)
             compile-arglist #fn("n3202101>282524228261:" #(#fn(for-each)
                                                            #fn("n170AFq0544Ar4Ar4GKMp:" #(compile-in))
- #(compile-in
-  void emit pop compile-begin) compile-begin)
+ile-builtin-call #fn("n7I202186850>3?;514227385q538<3I07483=8<52J=075858<52@30q4858=26CL086El23:07702862:770858663:8=29C708;60:8=2:C708;60:8=2;C]086El23:07702<62:86r2l23:07702=62:770858663:8=2>Cm086El23:07585K62:86Kl23:07702?62:86r2l23:07702@62:770858663:8=2ACL086El23:07702B62:770858663:8=2CCL086El23:07585K62:770858663:8=2DCN086El23<07702E2F63:770858663:8=2GCX086r2L23;07585r262:770823702H@402G8663:8=2ICb086r2l23:07702J62:r286L23?07708586r3~63:7585r262:7708562:" #(#0#
+  #fn("n0AEl239070FK62:7192FA63:" #(argc-error emit) num-compare)
 63:8=29C708;60:8=2:C708;60:8=2;C]086El23:07702<62:86r2l23:07702=62:770858663:8=2>Cm086El23:07585K62:86Kl23:07702?62:86r2l23:07702@62:770858663:8=2ACL086El23:07702B62:770858663:8=2CCL086El23:07585K62:770858663:8=2DCN086El23<07702E2F63:770858663:8=2GCX086r2L23;07585r262:770823702H@402G8663:8=2ICb086r2l23:07702J62:r286L23?07708586r3~63:7585r262:7708562:" #(#0#
   #fn("n0AEl239070FK62:7192FA63:" #(argc-error emit) num-compare)
   #fn(get) arg-counts length= argc-error list emit loadnil < = + load0 add2 - neg sub2 * load1 /
   vec loadv #() apply tapply aref aref2) compile-builtin-call)
-) arg-counts length= argc-error list emit loadnil < = + load0 add2 - neg sub2 * load1 /
+neg sub2 * load1 /
 8@55@30q48<3?02M8:2N8<53@408:8<3=02M8:8<52@408:7O08D7P7Q1518E52537R878F<52487r42;8E51r4Mp47S878FD7Q1515447?872T5247U2V7W87EG517X87518>5387r3G62:" #(#1=#fn("z0I:" #() void)
   #fn("n10<0=863J0702185>18652;J904A<8661:q:" #(any #fn("n1A0Q;3404A:")) any-duplicates)
   make-code-emitter lastcdr lambda:vars filter cons? #fn(map) caddr cddr λ #fn(length) keyword-arg?
@@ -205,8 +206,8 @@
 licates)
   make-code-emitter lastcdr lambda:vars filter cons? #fn(map) caddr cddr λ #fn(length) keyword-arg?
   error "compile error: duplicate argument: " emit optargs bcode:indexfor make-perfect-hash-table
-t-optional-arg-inits #fn(append)
-  (NIL NIL NIL NIL) extend-env complex-bindings lambda:body box-vars compile-in ret values #fn(fn)
+onal-arg-inits #fn(append)
+  (NIL NIL NIL NIL) extend-env complex-bindings lambda:body box-vars compile-in ret values #fn(fn)
 compile-if #fn("n420502050205083T718351728351B3;0738351@30q8;DC=07401828<64:8;J=07401828=64:7401q8;89554750268953475027885347401828<544823<07502852@;0750298:53475027895347401828=544750278:63:" #(#fn(gensym)
   caddr cdddr cadddr compile-in emit brn label ret jmp) compile-if)
             compile-in #fn("\x8740005000W4000J60q?4483R3<0700183D64:83H3\x97083EC:07102262:83KC:07102362:83DC:07102462:83J:07102562:7683513:07102762:7883513<0710298363:7102:8363:83<2;C<07<0183=63:83<RS;JD0483<Z;J;047=83<1523=07>01828364:83<892?CS07@83T513>07A018283T64:7102:83T63:892BC=07C01828364:892DC>07E018283=64:892FC;07G018363:892HCD07I2J183>22K01>262:892LC@07M018283=8465:892NC>07O018283=64:892PCE07Q0183T2D7R8351P64:892SCS07A01D83=B38083T@607T505447102U62:892VC\x91083T7W7R8351518;<<8;=8:R360q@807X2Y5148<3Y07Z8:8<8=<B;3F048=<<2HQ;3:047[8=<5153@30q47\\018:8=<64:892]Cp07A01q2Hq83Te35447^7_835151360q@807X2`5147A01q7_83515447102]62:7>01828364:" #(compile-sym
@@ -263,7 +264,7 @@
 n eq? brne nreconc) emit)
             emit-optional-arg-inits #fn("n582B3\xa60205071022845347382513<07102452@30q4710258953476077178838452q53q7982515447102:845347102;5247102<895347=0182=8384KM65:q:" #(#fn(gensym)
   emit bounda cddar dup brnn compile-in extend-env list-head cadar seta pop label
--arg-inits) emit-optional-arg-inits)
+-arg-inits) emit-optional-arg-inits)
 452255025502650qqEI8>87L23\xbc14868>G?<48<27CP02889868>KMG298;515348>r2M?>@\x8b12:8;2;7<883k08<8C2=C702>@X08C2?C702@@L08C2AC702B@@08C2CC702D@408<^1@408<525248>KM?>48>87L2;3804868>G?=42E8<2F523`0288:298;518=5342:8;883707G@407HE515248>KM?>@\xeb08<2ICH02:8;2J8=515248>KM?>@\xce08=X3\xc708<2E8?2K523H02:8;2J8=515248>KM?>@\x9f02E8?2L523\x8102:8;2J8=515248>KM?>42:8;2J868>G515248>KM?>48<2MCK02:8;2J868>G515248>KM?>@30q@E02:8;2N8=515248>KM?>^1@30q@\x9f.42O2P8;8889>38:5242Q8;61:" #(reverse!
   list->vec #fn(length) >= 65536 #fn(table) #fn(buffer) label #fn(put!)
   #fn(sizeof) #fn(io-write) #fn(get) Instructions jmp jmp.l brne brne.l brnn brnn.l brn brn.l #fn(memq)
@@ -311,11 +312,10 @@
 08708253;J50482:" #(#fn(get) *properties*) getprop)
             help-print-header #fn("O200020004000W2000J60q?24W3000J7021?3413A02223830>2152@V07483514823@074750512652@70770514785047860:" #(#(:kind
   0 NIL NIL :lpad 1 NIL NIL) "" #fn(for-each) #fn("n170A51471F0P5147260:" #(princ print newline))
-F0P5147260:" #(princ print newline))
-  princ caddr " (group)" print newline) help-print-header)
-            hex5 #fn("n170210r@52r52263:" #(str-lpad #fn(num->str) #\0) hex5) identity
-            #fn("n10:" #() identity) in-env? #fn("n21B;3F042001<52;J:047101=62:" #(#fn(assq)
-                                                                                   in-env?) in-env?)
+F0P5147260:" #(princ print newline))
+  princ caddr " (group)" print newline) help-print-header)
+            hex5 #fn("n170210r@52r52263:" #(str-lpad #fn(num->str) #\0) hex5) identity
+            #fn("n10:" #() identity) in-env? #fn("n21B;3F042001<52;J:047101=62:" #(#fn(assq)
 l #fn("n1205021850524228561:" #(#fn(buffer)
                                                      #fn(io-copy)
                                                      #fn(io->str)) io-readall)
@@ -390,12 +390,13 @@
                               top-level-value #fn(write)
                                                                               #fn(io-write)
                                                                               *linefeed* #fn(io-close)))
-61:" #(#fn(raise)))) make-system-image)
-            map! #fn("n21I1B3B04101<51_41=?1@\x1d/4:" #() map!) map-int
-            #fn("n2E1L2;3S040E51qPqb78786_4K7015121870>2|486:" #(1- #fn("n1A<F051qPN4AA<=_:")) map-int)
-            max #fn("z113;070210163:0:" #(foldl #fn("n201L23401:0:")) max) member
-            #fn("n21<0d3401:13:07001=62:q:" #(member) member) memv #fn("n21<0c3401:13:07001=62:q:" #(memv) memv)
-            min #fn("z113;070210163:0:" #(foldl #fn("n201L23400:1:")) min) mod
+61:" #(#fn(raise)))) make-system-image)
+            map! #fn("n21I1B3B04101<51_41=?1@\x1d/4:" #() map!) map-int
+            #fn("n2E1L2;3S040E51qPqb78786_4K7015121870>2|486:" #(1- #fn("n1A<F051qPN4AA<=_:")) map-int)
+            max #fn("z113;070210163:0:" #(foldl #fn("n201L23401:0:")) max) member
+            #fn("n21<0d3401:13:07001=62:q:" #(member) member) memv #fn("n21<0c3401:13:07001=62:q:" #(memv) memv)
+            min #fn("z113;070210163:0:" #(foldl #fn("n201L23400:1:")) min) mod
+            #fn("n207001521i2~:" #(div) mod) mod0 #fn("n2001k1i2~:" #() mod0) negative?
 ion #fn("n2I2021?65148601qe1qe164:" #(#0#
                                                                                        #fn("n48283PI1B3Z0401<513?0821<qPN=?2@<0831<qPN=?341=?1@\x05/47088<=88==62:" #(values) partition-)) partition)
             positive? #fn("n1E0L2:" #() positive?) princ
--- a/src/builtins.c
+++ b/src/builtins.c
@@ -40,7 +40,7 @@
 				c = ptr(c->cdr);
 			pcdr = &c->cdr;
 		}else if(lst != sl_nil){
-			bthrow(type_error("cons", lst));
+			bthrow(type_error(nil, "cons", lst));
 		}
 	}
 	*pcdr = lst;
@@ -118,7 +118,7 @@
 	}
 	if(a == sl_nil)
 		return fixnum(0);
-	bthrow(type_error("sequence", a));
+	bthrow(type_error(nil, "sequence", a));
 }
 
 _Noreturn
@@ -288,7 +288,7 @@
 		sl_cv *p = ptr(v);
 		return fixnum(conv_to_s64(v, cv_data(p), cv_numtype(p)));
 	}
-	bthrow(type_error("num", v));
+	bthrow(type_error(nil, "num", v));
 }
 
 BUILTIN("truncate", truncate)
@@ -320,7 +320,7 @@
 			return return_from_s64((s64int)d);
 		}
 	}
-	bthrow(type_error("num", v));
+	bthrow(type_error(nil, "num", v));
 }
 
 BUILTIN("vec-alloc", vec_alloc)
@@ -365,7 +365,7 @@
 		sl_numtype nt = cv_numtype(cv);
 		return conv_to_double(a, cv_data(cv), nt);
 	}
-	bthrow(type_error("num", a));
+	bthrow(type_error(nil, "num", a));
 }
 
 BUILTIN("time->str", time_str)
--- a/src/compiler.sl
+++ b/src/compiler.sl
@@ -9,6 +9,9 @@
 (defmacro (bcode:sp     b) `(aref ,b 4))
 (defmacro (bcode:stack  b n) `(aset! ,b 4 (+ (bcode:sp ,b) ,n)))
 
+(def (compile-error . rest)
+  (apply error (cons "compile error: " rest)))
+
 ;; get an index for a referenced value in a bytecode object
 (def (bcode:indexfor b v)
   (let ((const-to-idx (bcode:ctable b))
@@ -340,10 +343,9 @@
   (length lst))
 
 (def (argc-error head count)
-  (error "compile error: " head " expects " count
-         (if (= count 1)
-             " argument."
-             " arguments.")))
+  (compile-error head " expects " count (if (= count 1)
+                                            " argument."
+                                            " arguments.")))
 
 (def builtin->instruction
   (let ((b2i (table num? 'num?  cons 'cons
@@ -511,7 +513,7 @@
                             (doc (car (car doc+value)))
                             (value (cdr doc+value)))
                        (unless (sym? name)
-                         (error "set!: name must be a symbol"))
+                         (type-error "set!" 'symbol name "name"))
                        (when doc
                          (sym-set-doc name doc (and (cons? (car value))
                                                     (eq? (car (car value)) 'λ)
@@ -519,7 +521,7 @@
                      (compile-set! g env name (car value))))
            (trycatch (compile-in g env NIL `(λ () ,(cadr x)))
                      (unless (1arg-lambda? (caddr x))
-                             (error "trycatch: second form must be a 1-argument lambda"))
+                             (compile-error "trycatch: second form must be a 1-argument lambda"))
                      (compile-in g env NIL (caddr x))
                      (emit g 'trycatch))
            (else   (compile-app g env tail? x))))))
@@ -538,7 +540,7 @@
     (cond ((or (not l) (sym? l)) T)
           ((and (cons? l) (sym? (car l)))
            (if (or opt kw)
-               (error "compile error: invalid argument list "
+               (compile-error "invalid argument list "
                       o ": optional arguments must come after required")
                (check-formals (cdr l) o opt kw)))
           ((and (cons? l) (cons? (car l)))
@@ -546,21 +548,21 @@
                         (or (length= (car l) 2) ; default value
                             (and (length= (car l) 3) ; default value and "var-supplied"
                                  (sym? (caddar l)))))
-                   (error "compile error: invalid optional argument " (car l)
+                   (compile-error "invalid optional argument " (car l)
                           " in list " o))
            (if (keyword? (caar l))
                (check-formals (cdr l) o opt T)
                (if kw
-                   (error "compile error: invalid argument list "
+                   (compile-error "invalid argument list "
                           o ": keyword arguments must come last.")
                    (check-formals (cdr l) o T kw))))
           ((cons? l)
-           (error "compile error: invalid formal argument " (car l)
+           (compile-error "invalid formal argument " (car l)
                   " in list " o))
           (else
            (if (eq? l o)
-               (error "compile error: invalid argument list " o)
-               (error "compile error: invalid formal argument " l
+               (compile-error "invalid argument list " o)
+               (compile-error "invalid formal argument " l
                       " in list " o)))))
   (check-formals l l NIL NIL)
   (map (λ (s) (if (cons? s) (keyword->sym (car s)) s))
@@ -589,7 +591,7 @@
                    (cddr x)
                    (if (sym? form)
                        #.void
-                       (error "compile error: invalid syntax " (print-to-str x))))))
+                       (compile-error "invalid syntax " (print-to-str x))))))
     (if (sym? form)
         `(set! ,form ,(car body))
         `(set! ,(car form)
@@ -721,7 +723,7 @@
          (dupv  (any-duplicates vars)))
 
     (when dupv
-      (error "compile error: duplicate argument: " dupv))
+      (compile-error "duplicate argument: " dupv))
 
     ;; emit argument checking prologue
     (when opta
--- a/src/compress.c
+++ b/src/compress.c
@@ -14,7 +14,7 @@
 		argcount(nargs, 2);
 
 	if(!isarr(args[0]))
-		bthrow(type_error("arr", args[0]));
+		bthrow(type_error("data", "arr", args[0]));
 	u8int *in;
 	usize insz;
 	uintptr u;
@@ -51,7 +51,7 @@
 	uintptr u;
 	to_sized_ptr(args[0], &in, &insz, &u);
 	if(!isarr(args[0]))
-		bthrow(type_error("arr", args[0]));
+		bthrow(type_error("data", "arr", args[0]));
 	usize outsz;
 	u8int *out;
 	sl_v v;
--- a/src/cvalues.c
+++ b/src/cvalues.c
@@ -203,7 +203,7 @@
 		sl_fx v = ubnumval(a);
 		r = conv_to_u32(a, &v, ubnumtype(a));
 	}else
-		cthrow(type_error("num", a), type);
+		cthrow(type_error(nil, "num", a), type);
 	*((Rune*)dest) = r;
 }
 
@@ -221,7 +221,7 @@
 		}else if(iscvalue(a)){ \
 			n = (ctype)conv_to_##cnvt(a, cv_data(ptr(a)), cv_numtype(ptr(a))); \
 		}else \
-			cthrow(type_error("num", a), type); \
+			cthrow(type_error(nil, "num", a), type); \
 		*((ctype*)dest) = n; \
 	}
 
@@ -351,7 +351,7 @@
 		void *p = cv_data(cv);
 		n = conv_to_bignum(a, p, cv_numtype(cv));
 	}else
-		cthrow(type_error("num", a), type);
+		cthrow(type_error(nil, "num", a), type);
 	*((mpint**)dest) = n;
 }
 
@@ -409,7 +409,7 @@
 			return conv_to_u64(n, cv_data(cv), cv_numtype(cv));
 		return conv_to_u32(n, cv_data(cv), cv_numtype(cv));
 	}
-	cthrow(type_error("num", n), n);
+	cthrow(type_error(nil, "num", n), n);
 }
 
 soffset
@@ -425,7 +425,7 @@
 			return conv_to_s64(n, cv_data(cv), cv_numtype(cv));
 		return conv_to_s32(n, cv_data(cv), cv_numtype(cv));
 	}
-	cthrow(type_error("num", n), n);
+	cthrow(type_error(nil, "num", n), n);
 }
 
 bool
@@ -508,7 +508,7 @@
 	}
 	if(cnt == 1)
 		cvalue_init(eltype, arg, dest);
-	cthrow(type_error("sequence", arg), ft);
+	cthrow(type_error(nil, "sequence", arg), ft);
 }
 
 BUILTIN("arr", arr)
@@ -529,7 +529,7 @@
 	int i;
 	FOR_ARGS(i, 1, arg, args){
 		if(!sl_isnum(arg) && type->eltype != sl_runetype)
-			bthrow(type_error("num", arg));
+			bthrow(type_error("element", "num", arg));
 		cvalue_init(type->eltype, arg, dest);
 		dest += elsize;
 	}
@@ -556,8 +556,8 @@
 	a = 2;
 	for(i = 0; i < cnt; i++){
 		sl_v arg = args[a];
-		if(!sl_isnum(arg))
-			bthrow(type_error("num", arg));
+		if(!sl_isnum(arg) && type->eltype != sl_runetype)
+			bthrow(type_error("element", "num", arg));
 		cvalue_init(type->eltype, arg, dest);
 		dest += elsize;
 		if((a = (a + 1) % nargs) < 2)
@@ -629,7 +629,7 @@
 			return;
 		}
 	}
-	cthrow(type_error("plain-old-data", v), v);
+	cthrow(type_error(nil, "plain-old-data", v), v);
 }
 
 BUILTIN("sizeof", sizeof)
@@ -1021,7 +1021,7 @@
 		}
 	}
 
-	cthrow(type_error("num", n), n);
+	cthrow(type_error(nil, "num", n), n);
 }
 
 bool
@@ -1071,12 +1071,12 @@
 	}
 	if(!num_to_ptr(a, &ai, &ta, &aptr)){
 		if(typeerr)
-			cthrow(type_error("num", a), a);
+			cthrow(type_error("a", "num", a), a);
 		return 2;
 	}
 	if(!num_to_ptr(b, &bi, &tb, &bptr)){
 		if(typeerr)
-			cthrow(type_error("num", b), a);
+			cthrow(type_error("b", "num", b), a);
 		return 2;
 	}
 	if(eq && eqnans && ((ta >= T_FLT) != (tb >= T_FLT)))
@@ -1105,14 +1105,14 @@
 	void *aptr, *bptr;
 
 	if(!num_to_ptr(a, &ai, &ta, &aptr))
-		cthrow(type_error("num", a), a);
+		cthrow(type_error("a", "num", a), a);
 	if(!num_to_ptr(b, &bi, &tb, &bptr))
-		cthrow(type_error("num", b), a);
+		cthrow(type_error("b", "num", b), a);
 	// a pointer is not exactly a number
 	if(ta == T_P32 || ta == T_P64)
-		cthrow(type_error("num", a), a);
+		cthrow(type_error("a", "num", a), a);
 	if(tb == T_P32 || tb == T_P64)
-		cthrow(type_error("num", b), a);
+		cthrow(type_error("b", "num", b), a);
 
 	da = conv_to_double(a, aptr, ta);
 	db = conv_to_double(b, bptr, tb);
@@ -1137,14 +1137,14 @@
 	mpint *x;
 
 	if(!num_to_ptr(a, &ai, &ta, &aptr))
-		cthrow(type_error("num", a), a);
+		cthrow(type_error("a", "num", a), a);
 	if(!num_to_ptr(b, &bi, &tb, &bptr))
-		cthrow(type_error("num", b), a);
+		cthrow(type_error("b", "num", b), a);
 	// a pointer is not exactly a number
 	if(ta == T_P32 || ta == T_P64)
-		cthrow(type_error("num", a), a);
+		cthrow(type_error("a", "num", a), a);
 	if(tb == T_P32 || tb == T_P64)
-		cthrow(type_error("num", b), a);
+		cthrow(type_error("b", "num", b), a);
 
 	if(ta == T_BIG){
 		if(tb == T_BIG){
@@ -1201,9 +1201,9 @@
 	s64int b64;
 
 	if(!num_to_ptr(a, &ai, &ta, &aptr) || ta >= T_FLT)
-		cthrow(type_error("int", a), a);
+		cthrow(type_error("a", "int", a), a);
 	if(!num_to_ptr(b, &bi, &tb, &bptr) || tb >= T_FLT)
-		cthrow(type_error("int", b), a);
+		cthrow(type_error("b", "int", b), a);
 
 	if(ta < tb){
 		itmp = ta; ta = tb; tb = itmp;
@@ -1349,7 +1349,7 @@
 		default: abort();
 		}
 	}
-	bthrow(type_error("int", a));
+	bthrow(type_error(nil, "int", a));
 }
 
 #define sash_overflow_64(a, b, c) ( \
@@ -1418,7 +1418,7 @@
 		assert(fits_fixnum(accum));
 		return fixnum((sl_fx)accum);
 	}
-	bthrow(type_error("int", a));
+	bthrow(type_error(nil, "int", a));
 }
 
 void
--- a/src/io.c
+++ b/src/io.c
@@ -71,7 +71,7 @@
 toio(sl_v v)
 {
 	if(sl_unlikely(!isio(v)))
-		cthrow(type_error("io", v), v);
+		cthrow(type_error(nil, "io", v), v);
 	return value2c(ios*, v);
 }
 
@@ -197,7 +197,7 @@
 	argcount(nargs, 2);
 	ios *s = toio(args[0]);
 	if(!isrune(args[1]))
-		bthrow(type_error("rune", args[1]));
+		bthrow(type_error(nil, "rune", args[1]));
 	return fixnum(ios_putrune(s, torune(args[1])));
 }
 
--- a/src/operators.c
+++ b/src/operators.c
@@ -13,11 +13,11 @@
 	case T_U32: case T_P32: return uitomp(*(u32int*)data, nil);
 	case T_S64:             return vtomp(*(s64int*)data, nil);
 	case T_U64: case T_P64: return uvtomp(*(u64int*)data, nil);
-	case T_BIG:          return mpcopy(*(mpint**)data);
-	case T_FLT:           return dtomp(*(float*)data, nil);
-	case T_DBL:          return dtomp(*(double*)data, nil);
+	case T_BIG:             return mpcopy(*(mpint**)data);
+	case T_FLT:             return dtomp(*(float*)data, nil);
+	case T_DBL:             return dtomp(*(double*)data, nil);
 	}
-	cthrow(type_error("num", v), v);
+	cthrow(type_error(nil, "num", v), v);
 }
 
 sl_purefn
@@ -42,7 +42,7 @@
 	case T_FLT:             return *(float*)data;
 	case T_DBL:             return *(double*)data;
 	}
-	cthrow(type_error("num", v), v);
+	cthrow(type_error(nil, "num", v), v);
 }
 
 // FIXME sign with mpint
@@ -64,7 +64,7 @@
 	case T_FLT:             return (ctype)*(float*)data; \
 	case T_DBL:             return (ctype)*(double*)data; \
 	} \
-	cthrow(type_error("num", v), v); \
+	cthrow(type_error(nil, "num", v), v); \
 }
 
 CONV_TO_INTTYPE(s64, s64int)
@@ -100,7 +100,7 @@
 		s = *(double*)data;
 		return s;
 	}
-	cthrow(type_error("num", v), v);
+	cthrow(type_error(nil, "num", v), v);
 }
 
 sl_purefn
--- a/src/plan9/lsd.c
+++ b/src/plan9/lsd.c
@@ -249,7 +249,7 @@
 	argcount(nargs, 3);
 	for(a = args; a < args+3; a++)
 		if(sl_unlikely(!sl_isnum(*a)))
-			bthrow(type_error("num", *a));
+			bthrow(type_error(nil, "num", *a));
 
 	pc = tosize(args[0]);
 	sp = tosize(args[1]);
@@ -281,7 +281,7 @@
 	pid = -1;
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isstr(args[0]) && !sl_isnum(args[0])))
-		bthrow(type_error("str|num", args[0]));
+		bthrow(type_error("program", "(or str num)", args[0]));
 
 	if(sl_isnum(args[0])){
 		pid = tosize(args[0]);
@@ -343,7 +343,7 @@
 	argv[0] = aout;
 	for(i = 0; i < nargs; i++){
 		if(sl_unlikely(!sl_isstr(args[i])))
-			bthrow(type_error("str", args[i]));
+			bthrow(type_error(nil, "str", args[i]));
 		argv[i+1] = cvalue_data(args[i]);
 	}
 	argv[i+1] = nil;
@@ -372,7 +372,7 @@
 
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isnum(args[0])))
-		bthrow(type_error("num", args[0]));
+		bthrow(type_error("addr", "num", args[0]));
 	addr = tosize(args[0]);
 
 	n = machdata->foll(coremap, addr, rget, f);
@@ -394,7 +394,7 @@
 
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isnum(args[0])))
-		bthrow(type_error("num", args[0]));
+		bthrow(type_error("addr", "num", args[0]));
 
 	addr = tosize(args[0]);
 	if(machdata->das(coremap, addr, 'i', buf, sizeof(buf)) < 0)
@@ -409,7 +409,7 @@
 
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isnum(args[0])))
-		bthrow(type_error("num", args[0]));
+		bthrow(type_error("addr", "num", args[0]));
 
 	addr = tosize(args[0]);
 	sz = machdata->instsize(coremap, addr);
@@ -425,7 +425,7 @@
 
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isnum(args[0])))
-		bthrow(type_error("num", args[0]));
+		bthrow(type_error("addr", "num", args[0]));
 
 	addr = tosize(args[0]);
 	if(!fileline(buf, sizeof(buf), addr))
@@ -441,9 +441,9 @@
 
 	argcount(nargs, 2);
 	if(sl_unlikely(!sl_isstr(args[0])))
-		bthrow(type_error("str", args[0]));
+		bthrow(type_error("file", "str", args[0]));
 	if(sl_unlikely(!isfixnum(args[1])))
-		bthrow(type_error("num", args[1]));
+		bthrow(type_error("line", "num", args[1]));
 
 	file = cvalue_data(args[0]);
 	line = numval(args[1]);
@@ -460,7 +460,7 @@
 
 	argcount(nargs, 1);
 	if(sl_unlikely(!sl_isnum(args[0])))
-		bthrow(type_error("num", args[0]));
+		bthrow(type_error("addr", "num", args[0]));
 
 	addr = tosize(args[0]);
 	if(!findsym(addr, CTEXT, &s))
--- a/src/sl.c
+++ b/src/sl.c
@@ -168,9 +168,11 @@
 }
 
 _Noreturn void
-type_error(const char *expected, sl_v got)
+type_error(const char *what, const char *expected, sl_v got)
 {
-	sl_raise(mk_listn(3, sl_errtype, mk_sym(expected, false), got));
+	if(what == nil)
+		sl_raise(mk_listn(3, sl_errtype, mk_sym(expected, false), got));
+	sl_raise(mk_listn(4, sl_errtype, mk_sym(what, false), mk_sym(expected, false), got));
 }
 
 _Noreturn void
@@ -205,7 +207,7 @@
 	{ \
 		if(sl_likely(is##type(v))) \
 			return (ctype)cnvt(v); \
-		cthrow(type_error(#type, v), v); \
+		cthrow(type_error("value", #type, v), v); \
 	}
 SAFECAST_OP(cons, sl_cons*, ptr)
 SAFECAST_OP(sym, sl_sym*, ptr)
@@ -600,7 +602,7 @@
 	}else if(isfn(f)){
 		v = apply_cl(n);
 	}else{
-		cthrow(type_error("fn", f), n);
+		cthrow(type_error(nil, "fn", f), n);
 	}
 	sl.sp = saveSP;
 	return v;
@@ -969,12 +971,12 @@
 	if(nargs < 1 || nargs > 4)
 		argcount(nargs, 1);
 	if(sl_unlikely(!sl_isstr(args[0])))
-		bthrow(type_error("str", args[0]));
+		bthrow(type_error("code", "str", args[0]));
 	sl_v vals = sl_emptyvec;
 	if(nargs > 1){
 		vals = args[1];
 		if(sl_unlikely(!isvec(vals)))
-			bthrow(type_error("vec", vals));
+			bthrow(type_error("vals", "vec", vals));
 	}
 	sl_cv *arr = ptr(args[0]);
 	cv_pin(arr);
@@ -1000,12 +1002,12 @@
 			fn->env = args[2];
 			if(nargs > 3){
 				if(sl_unlikely(!issym(args[3])))
-					bthrow(type_error("sym", args[3]));
+					bthrow(type_error("name", "sym", args[3]));
 				fn->name = args[3];
 			}
 		}
 		if(sl_unlikely(isgensym(fn->name)))
-			bthrow(lerrorf(sl_errarg, "name should not be a gensym"));
+			bthrow(lerrorf(sl_errarg, "name must not be a gensym"));
 	}
 	return fv;
 }
@@ -1016,7 +1018,7 @@
 	argcount(nargs, 1);
 	sl_v v = args[0];
 	if(sl_unlikely(!isfn(v)))
-		bthrow(type_error("fn", v));
+		bthrow(type_error(nil, "fn", v));
 	if(sl_unlikely(iscbuiltin(v) || isbuiltin(v)))
 		return v;
 	return fn_bcode(v);
@@ -1028,7 +1030,7 @@
 	argcount(nargs, 1);
 	sl_v v = args[0];
 	if(sl_unlikely(!isfn(v)))
-		bthrow(type_error("fn", v));
+		bthrow(type_error(nil, "fn", v));
 	if(sl_unlikely(iscbuiltin(v) || isbuiltin(v)))
 		return sl_emptyvec;
 	return fn_vals(v);
@@ -1040,7 +1042,7 @@
 	argcount(nargs, 1);
 	sl_v v = args[0];
 	if(sl_unlikely(!isfn(v)))
-		bthrow(type_error("fn", v));
+		bthrow(type_error(nil, "fn", v));
 	if(sl_unlikely(iscbuiltin(v) || isbuiltin(v)))
 		return sl_nil;
 	return fn_env(v);
@@ -1060,7 +1062,7 @@
 	}
 	if(isfn(v))
 		return fn_name(v);
-	bthrow(type_error("fn", v));
+	bthrow(type_error(nil, "fn", v));
 }
 
 BUILTIN("copy-list", copy_list)
@@ -1087,7 +1089,7 @@
 				cdr_(lastcons) = lst;
 			lastcons = tagptr((((sl_cons*)slg.curheap)-1), TAG_CONS);
 		}else if(lst != sl_nil){
-			bthrow(type_error("cons", lst));
+			bthrow(type_error(nil, "cons", lst));
 		}
 	}
 	sl_free_gc_handles(2);
@@ -1138,15 +1140,15 @@
 		else if(rt == sl_tablesym)
 			rtype = RT_TBL;
 		else
-			bthrow(type_error("sequence type designator", rt));
+			bthrow(type_error("result-type", "sequence type specifier", rt));
 	}else if(iscons(rt)){
 		if(car_(rt) != sl_arrsym)
-			bthrow(type_error("array type designator", rt));
+			bthrow(type_error("result-type", "array type specifier", rt));
 		rtype = RT_ARR;
 		arrtype = car_(cdr_(rt));
 		get_arr_type(arrtype);
 	}else{
-		bthrow(type_error("sequence type designator", rt));
+		bthrow(type_error("result-type", "sequence type specifier", rt));
 	}
 	sl_v *k = sl.sp;
 	PUSH(sl_nil);
--- a/src/sl.h
+++ b/src/sl.h
@@ -295,7 +295,7 @@
 void sl_savestate(sl_exctx *_ctx);
 void sl_restorestate(sl_exctx *_ctx);
 _Noreturn void sl_raise(sl_v e);
-_Noreturn void type_error(const char *expected, sl_v got);
+_Noreturn void type_error(const char *what, const char *expected, sl_v got);
 _Noreturn void bounds_error(sl_v arr, sl_v ind);
 _Noreturn void const_error(sl_v sym);
 _Noreturn void unbound_error(sl_v sym);
--- a/src/sl_arith_any.h
+++ b/src/sl_arith_any.h
@@ -32,7 +32,7 @@
 typeerr:
 				mpfree(Maccum);
 				mpfree(m);
-				type_error("num", arg);
+				type_error(nil, "num", arg);
 			}
 			switch(pt){
 			case T_DBL: Faccum = ARITH_OP(Faccum, *(double*)a); inexact = true; continue;
--- a/src/str.c
+++ b/src/str.c
@@ -22,7 +22,7 @@
 	if(nargs < 1 || nargs > 3)
 		argcount(nargs, 1);
 	if(!sl_isstr(args[0]))
-		bthrow(type_error("str", args[0]));
+		bthrow(type_error(nil, "str", args[0]));
 	usize len = cv_len(ptr(args[0]));
 	usize stop = len;
 	if(nargs > 1){
@@ -49,7 +49,7 @@
 		return w < 0 ? sl_nil : fixnum(w);
 	}
 	if(!sl_isstr(args[0]))
-		bthrow(type_error("str", args[0]));
+		bthrow(type_error(nil, "str", args[0]));
 	char *str = tostr(args[0]);
 	usize len = cv_len(ptr(args[0]));
 	ssize w = u8_strwidth(str, len);
@@ -60,7 +60,7 @@
 {
 	argcount(nargs, 1);
 	if(!sl_isstr(args[0]))
-		bthrow(type_error("str", args[0]));
+		bthrow(type_error(nil, "str", args[0]));
 	usize len = cv_len(ptr(args[0]));
 	sl_v ns = cvalue_str(len);
 	u8_reverse(cvalue_data(ns), cvalue_data(args[0]), len);
@@ -202,7 +202,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return mk_rune(toupperrune(torune(args[0])));
 }
 
@@ -211,7 +211,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return mk_rune(tolowerrune(torune(args[0])));
 }
 
@@ -220,7 +220,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return mk_rune(totitlerune(torune(args[0])));
 }
 
@@ -229,7 +229,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return isalpharune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -238,7 +238,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return islowerrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -247,7 +247,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return isupperrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -256,7 +256,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return istitlerune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -265,7 +265,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return isdigitrune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -274,7 +274,7 @@
 {
 	argcount(nargs, 1);
 	if(!isrune(args[0]))
-		bthrow(type_error("rune", args[0]));
+		bthrow(type_error(nil, "rune", args[0]));
 	return isspacerune(torune(args[0])) ? sl_t : sl_nil;
 }
 
@@ -305,7 +305,7 @@
 		ndbytes = cv_len(cv);
 		nd = (char*)cv_data(cv);
 	}else{
-		bthrow(type_error("str or rune", args[1]));
+		bthrow(type_error("needle", "(or str rune)", args[1]));
 	}
 	if(ndbytes == 0)
 		return size_wrap(startrune);
@@ -372,7 +372,7 @@
 		else
 			return fn_builtin_str(args, nargs);
 	}else{
-		bthrow(type_error("int", n));
+		bthrow(type_error(nil, "num", n));
 	}
 	if(numval(sl_compare(args[0], fixnum(0), false)) < 0){
 		num = -num;
--- a/src/system.sl
+++ b/src/system.sl
@@ -1466,7 +1466,12 @@
     (when loc
       (princ (io-filename (car loc)) ":" (cadr loc) ":" (caddr loc) ": "))
     (cond ((eq? k 'type-error)
-           (princ "type error: expected " (car a) ", got " (type-of (cadr a)) ": ")
+           (princ "type error: ")
+           (unless (length= a 2)
+             (princ (car a) ": ")
+             (set! a (cdr a)))
+           (for-each (λ (s) (princ s ": ")) (cddr a))
+           (princ "expected " (car a) ", got " (type-of (cadr a)) ": ")
            (print (cadr a)))
 
           ((eq? k 'bounds-error)
--- a/src/table.c
+++ b/src/table.c
@@ -85,7 +85,7 @@
 totable(sl_v v)
 {
 	if(!ishashtable(v))
-		cthrow(type_error("table", v), v);
+		cthrow(type_error(nil, "table", v), v);
 	return cvalue_data(v);
 }
 
--- a/src/vm.h
+++ b/src/vm.h
@@ -55,7 +55,7 @@
 		}
 		int i = uintval(v);
 		if(!isbuiltin(v))
-			type_error("fn", v);
+			type_error("builtin", "fn", v);
 		sl_fx s = builtins[i].nargs;
 		if(s >= 0)
 			argcount(n, s);
@@ -100,7 +100,7 @@
 		sp[-1] = v;
 		NEXT_OP;
 	}
-	type_error("fn", v);
+	type_error(tail ? "tcall" : "call", "fn", v);
 }
 
 OP(OP_ARGC) {
@@ -260,7 +260,7 @@
 		v = car_(v);
 	else if(sl_unlikely(v != sl_nil)){
 		SYNC;
-		type_error("cons", v);
+		type_error("car", "cons", v);
 	}
 	sp[-1] = v;
 	NEXT_OP;
@@ -322,7 +322,7 @@
 		v = cdr_(v);
 	else if(sl_unlikely(v != sl_nil)){
 		SYNC;
-		type_error("cons", v);
+		type_error("cdr", "cons", v);
 	}
 	sp[-1] = v;
 	NEXT_OP;
@@ -386,7 +386,7 @@
 	sl_v v = sp[-2];
 	if(sl_unlikely(!iscons(v))){
 		SYNC;
-		type_error("cons", v);
+		type_error("set-car!", "cons", v);
 	}
 	car_(v) = sp[-1];
 	sp--;
@@ -439,7 +439,7 @@
 			continue;
 		}
 		if(!iscons(v))
-			type_error("sequence", v);
+			type_error("aref", "sequence", v);
 		for(sl_v v0 = v;; isz--){
 			if(isz == 0){
 				v = car_(v);
@@ -485,7 +485,7 @@
 	sl_v v = sp[-2];
 	if(sl_unlikely(!iscons(v))){
 		SYNC;
-		type_error("cons", v);
+		type_error("set-cdr!", "cons", v);
 	}
 	cdr_(v) = sp[-1];
 	sp--;
@@ -518,7 +518,7 @@
 				continue;
 			}
 			if(sl_unlikely(!iscons(v)))
-				type_error("sequence", v);
+				type_error("aset!", "sequence", v);
 			for(sl_v v0 = v;; isz--){
 				if(isz == 0){
 					v = car_(v);
@@ -550,7 +550,7 @@
 	}else if(isarr(v)){
 		e = cvalue_arr_aset(sp-3);
 	}else{
-		type_error("sequence", v);
+		type_error("aset!", "sequence", v);
 	}
 	sp -= n;
 	*sp++ = e;
@@ -679,7 +679,7 @@
 LABEL(cadr_nil):
 		if(sl_unlikely(v != sl_nil)){
 			SYNC;
-			type_error("cons", v);
+			type_error("cadr", "cons", v);
 		}
 	}
 	sp[-1] = v;