aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 09:39:39 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 09:39:39 +0000
commit0ed3f650e50bc6fa7712e1b3e650ec9bc678ac91 (patch)
treea34cc20ef06d80caf58a430a7f5b0b60494bbe35
parenttodo: tick struct constants and member typos; add follow-ups and the spec work (diff)
parentadd c-style typedef and a built-in string alias (diff)
Merge branch 'worktree-agent-a3354164c07827b2e' into ccr-0054c02e-dfdvnd
-rw-r--r--README.md4
-rw-r--r--editors/vscode/syntaxes/zc.tmLanguage.json2
-rw-r--r--src/compiler/compiler.zc107
-rw-r--r--src/lsp/server.zc50
-rw-r--r--tests/drivers/lsp_test.js18
-rw-r--r--tests/langerrors/typedef_generic_bare.zc6
-rw-r--r--tests/langerrors/typedef_local_redefine.zc8
-rw-r--r--tests/langerrors/typedef_redefine.zc6
-rw-r--r--tests/langerrors/typedef_scope.zc8
-rw-r--r--tests/langerrors/typedef_string_redefine.zc5
-rw-r--r--tests/langerrors/typedef_tag.zc5
-rw-r--r--tests/langerrors/typedef_unknown.zc5
-rw-r--r--tests/langerrors/typedef_variable_clash.zc8
-rw-r--r--tests/langerrors/typedef_void_variable.zc5
-rw-r--r--tests/programs/typedef.zc89
-rwxr-xr-xtests/test.sh1
-rw-r--r--tests/typeerrors/typedef_mismatch.zc6
-rw-r--r--tests/typeerrors/typedef_pointer.zc7
18 files changed, 326 insertions, 14 deletions
diff --git a/README.md b/README.md
index 2752600..0b59a6b 100644
--- a/README.md
+++ b/README.md
@@ -169,6 +169,8 @@ fn main() {
declarations are C-style, type first: `int a = 1, b;`, `node* p = rt.new(node);`, `char[4] buf;`, `point origin;`. `let` declares without a type: it infers `char*` from a word-string literal and `bstr` from a packed byte-string literal, infers other pointers and function types from the initializer, uses a pointer alias for an in-place array or struct, and keeps numbers as plain words. `let a[10];` is an untyped array. `const k = 5;` inside a function is a local constant with no type: it takes the initializer's type, needs an initializer, and can't be assigned. statements are declarations, `if`/`else`, `while`, `do`/`while`, `for`, `switch`, `break`, `continue`, `return`, `{ }` blocks, `asm { }` and expressions. `do` body `while (` cond `);` runs the body once before the test, and `continue` jumps to the test. `enum color { RED, GREEN = 5, BLUE };` makes global constants, and `color` is a type of its own in typed code (`typecheck on;`): a member takes the enum, a plain int does not become a `color`, and a `color` still becomes an `int`. untyped code treats the members as plain ints. each member is also reached as `color.BLUE`, in an expression or a `case` label. a member can carry fields too: `enum shape { circle { int r; }, rect { int w; int h; }, dot };` makes the constructors `shape.circle(3)` and `shape.rect(4, 5)`, which return an owned `shape*`, and `shape.dot()`. the tag is the word at offset 0 of the object. a `switch` over a `shape*` matches a member with `case circle(r):`, which binds its fields in that arm only. a switch on a tagged enum that leaves out a member and has no `default` gets a warning on its line (not an error). `(T)e` is a cast with the same meaning as `e as T`: the bits stay, the type changes. it has to be a simple type the parser can see in a few tokens, `int` or `*node`, not a nested array. `include "path";` reads a file and compiles it at that point, signatures first, so a library can live in its own file. each file has its own namespace and imports; dependencies included by a file do not import into the caller. `u`, `m` and `d` are ordinary names. a tag is `.u`, `.m` or `.d`, and `u*x` with no space before the star is still a recast. `switch (e) { case 1, 2: ... default: ... }` takes constant labels, and its cases don't fall through: each ends where the next begins, and `break` leaves early. operators follow C precedence, including `c ? a : b`, plus `p.x` and `p->x` (the same thing: `.` goes through a pointer on its own), `e as T` to change a type without changing the bits, and the casts below. comparisons and `&& ||` give 0 or 1. `/`, `%` and `<<` compile to native enhancement opcodes. character literals, `0x` hex and the usual escapes are there.
+`typedef T name;` gives a type another name, as in c, at the top level or inside a function (where it lasts to the end of its block, like a local): `typedef node* link;`, `typedef int[4] quad;`, `typedef int(int, int) binop;`, `typedef list<int> ints;`. `type name = T;` says the same thing. a typedef is only a new spelling: `link` and `node*` are the very same type record, so they mix freely in declarations, parameters, results, casts, `sizeof`, struct fields, generic arguments and `typecheck on;` code, and they print as the target type. `T` can be any type the parser accepts, but not a tag (`.u`, `.m` and `.d` belong to a declaration). a typedef can't name a type that isn't declared yet, so a struct comes before the typedefs of its pointers; using a name that is already defined is an error, and a typedef of `void` is `void` (a variable of it is the usual error). `string` is built in as a typedef of `char*`, so `string s = "hi";` and `string f(string a)` work with no declaration; a function, variable or parameter of the same name shadows it.
+
functions are declared like C, return type first: `int f(int x)`, `node*.u make()`, `.u grab()` (a tag on its own), `void log(char* s)`. `fn f(a, b)` starts without explicit parameter or return types; constraints from calls and returned values propagate through callers and callees until inference reaches a fixed point, including recursive calls anchored by typed callees. conflicting argument types or an unknown reassignment leave the affected type unresolved instead of choosing a type. the learning example [32](examples/learn/32-inferred-calls.zc) shows this propagation and an owned raw-list return. a generic function lists its type parameters after its name, `T first<T>(*list<T> l)`, the same place a call gives explicit ones, `first<int>(l)`. the return type can use them before they're declared: while it's read, an unknown name is taken as a type parameter, and the list after the name has to confirm it. function types are written the same way, `int(int, *char)`, and `fn(int)` for one that returns a word. the earlier `fn f(...) -> T` spelling is gone.
array types put the element type first. `T[n]` has its size in the type and lives in place. `T[]` also lives in place; its length is inferred from an initializer such as `int[] a = {1, 2, 3};` and cannot grow. `T[?]` can also be initialized in a function with a list, such as `int[?] a = {1, 2};`. it is a growable array: a small handle object that owns its items, made with `rt.new(T[?])` or `rt.new(T[?], n)`, indexed with a bounds check, and changed with `push(a, x)`, `pop(a)` and `resize(a, n)`. `len(a)` works on both (for `T[n]` it's the constant) and `cap(a)` gives the room before the next grow. `fixed T[?]` is a `T[?]` that can't change size: `rt.new(fixed T[?], n)` needs the length, and `push`, `pop` and `resize` on it are compile errors. a `fixed T[?]` can't be passed where a `T[?]` is expected, the other way round is fine, and a constant index outside a `T[n]` is caught at compile time. the items belong to the array, so freeing it frees them, and objects stored into a `T*[?]` are its children like any container's.
@@ -296,7 +298,7 @@ the kinds are `word`, `int`, `char`, `bool`, `void`, pointers `T*`, arrays `T[n]
### pointer types
-a pointer type places its star after the type, like c: `int*`, `point*`, `vec<T>*`, `vec<point*>*`, `point**`. a tag follows the stars (`vec<T>*.u`, `int*.d x`). there is no prefix form: `*int` as a type is an error, and a star in front of an expression is only ever a dereference. a pointer to an array or to a function type is spelled with a `type` alias (`type row = int[3];` then `row*`), because `int*[3]` reads as an array of pointers. `tprint`, and so error messages and reflection, print the same form.
+a pointer type places its star after the type, like c: `int*`, `point*`, `vec<T>*`, `vec<point*>*`, `point**`. a tag follows the stars (`vec<T>*.u`, `int*.d x`). there is no prefix form: `*int` as a type is an error, and a star in front of an expression is only ever a dereference. a pointer to an array or to a function type is spelled with a `type` or `typedef` alias (`type row = int[3];` or `typedef int[3] row;`, then `row*`), because `int*[3]` reads as an array of pointers. `tprint`, and so error messages and reflection, print the same form.
### methods and closures
diff --git a/editors/vscode/syntaxes/zc.tmLanguage.json b/editors/vscode/syntaxes/zc.tmLanguage.json
index 36f1c63..6f76e03 100644
--- a/editors/vscode/syntaxes/zc.tmLanguage.json
+++ b/editors/vscode/syntaxes/zc.tmLanguage.json
@@ -87,7 +87,7 @@
},
{
"name": "keyword.declaration.zc",
- "match": "\\b(fn|let|struct|type|enum|const|enhance|micro|vm)\\b"
+ "match": "\\b(fn|let|struct|type|typedef|enum|const|enhance|micro|vm)\\b"
},
{
"name": "keyword.other.zc",
diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc
index 8335ab9..ac504d7 100644
--- a/src/compiler/compiler.zc
+++ b/src/compiler/compiler.zc
@@ -285,8 +285,12 @@ int fldst, fldi; // the one struct a bare address's field name resolved to
char*[8] tpn; int ntp; // type parameters in scope
// reading a return type: unknown names are type parameters declared after the function's name
int tvauto;
+// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the
+// compile context it was made in), newest last. a block's end unlinks them from their names.
+word tdstk; int ntd, tdcap;
word readonly_types;
int dty, dtag; // result of dtype()
+int s_typedef;
int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t;
int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob;
int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32;
@@ -1102,6 +1106,7 @@ int source_lookup(char* name) {
// drop the symbols above index to, which ends a scope.
int popsyms(int to) {
while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; }
+ while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them
return 0;
}
// look a name up from a plain string.
@@ -2952,6 +2957,7 @@ int block_close(int save) {
while (i < save) { if (lhome[i] >= fdepth) { lstate[i] = S_DEAD; lhome[i] = fdepth - 1; } i++; }
local_drop(save);
fdrop(save);
+ td_end(fdepth);
return 0;
}
@@ -4388,6 +4394,89 @@ int kw_type() {
expect(s_semi);
return 0;
}
+// does a parameter start with a type? a type name straight before , or ) is the parameter's
+// own name (fn f(string)), not a type, since a typed parameter always has a name after it.
+int paramstart() {
+ if (tk == T_OP) return tv == s_lk;
+ if (tk != T_ID) return 0;
+ if (tv == s_fn) return 1;
+ if (!istypename(tv)) return 0;
+ peek();
+ return !(pk_tk == T_OP && (pk_tv == s_comma || pk_tv == s_rp));
+}
+// does a typedef start here? typedef followed by a name is one, so typedef can still be
+// a variable or a function's name.
+int typedef_ahead() {
+ if (tk != T_ID || tv != s_typedef) return 0;
+ peekat(0);
+ return pk_tk == T_ID;
+}
+// unlink the symbol s from its name, leaving the rest of the name's chain as it was.
+int td_unlink(int s) {
+ char* nm = sname[s]; int i = nm[-1] - 1;
+ if (i == s) { nm[-1] = snext[s]; return 0; }
+ while (i >= 0 && snext[i] - 1 != s) i = snext[i] - 1;
+ if (i >= 0) snext[i] = snext[s];
+ return 0;
+}
+// end the scope of the local typedefs made at block depth depth or deeper in this
+// compile context (and of any made in a context nested inside it).
+int td_end(int depth) {
+ int k;
+ while (ntd) {
+ k = ntd * 3 - 3;
+ if (tdstk[k + 2] < ctxsp || (tdstk[k + 2] == ctxsp && tdstk[k + 1] < depth)) break;
+ td_unlink(tdstk[k]);
+ ntd--;
+ }
+ return 0;
+}
+// is the name q (as a symbol is named) already a typedef of the block being compiled?
+int td_here(char* q) {
+ int k = ntd;
+ while (k) {
+ k--;
+ if (tdstk[k * 3 + 2] != ctxsp || tdstk[k * 3 + 1] != fdepth) break;
+ if (sname[tdstk[k * 3]] == q) return 1;
+ }
+ return 0;
+}
+// typedef T name;
+// the C spelling of type name = T;. name becomes another way to write T: the very same
+// type record, so the two are interchangeable everywhere. at the top level the name is
+// global; in a function it lasts to the end of its block, like a local. a name that is
+// already in use is an error, except the same type under the same name again. a tag
+// (.u, .m, .d) belongs to a declaration, not to a type, so a typedef has none.
+int kw_typedef() {
+ char* n; char* q; word t; int s, at, clash = 0, local = infn;
+ next();
+ t = ptype();
+ if (panic) return 0;
+ if (is_op(s_dot)) { die("a typedef names a type, which has no tag: .u, .m and .d go on a declaration"); return 0; }
+ at = toff; n = name();
+ if (panic) return 0;
+ q = module_symbol(n);
+ if (local) {
+ s = findloc(n);
+ if ((s >= 0 && lhome[s] >= fdepth) || td_here(q)) clash = 1;
+ } else {
+ s = lookup_raw(q);
+ if (s < 0 && module_name) { s = lookup_raw(n); if (s >= 0 && skind[s] != K_TYPE && skind[s] != K_KW) s = 0 - 1; }
+ if (s >= 0 && skind[s] == K_TYPE && sval[s] == t) return expect(s_semi); // the same typedef again
+ if (s >= 0) clash = 1;
+ }
+ if (clash) serrn(n, "already defined");
+ else {
+ s = addsym(n, K_TYPE, t);
+ rs_put(at, RS_SYM, rs_sym(s), s);
+ if (local) {
+ if (ntd == tdcap) { if (tdcap) tdcap = tdcap * 2; else tdcap = 16; tdstk = chk_resize(tdstk, ntd * 3, tdcap * 3); }
+ tdstk[ntd * 3] = s; tdstk[ntd * 3 + 1] = fdepth; tdstk[ntd * 3 + 2] = ctxsp; ntd++;
+ }
+ }
+ expect(s_semi);
+ return 0;
+}
// (T) or (T*) ahead of the current '(', within the lookahead we have
// the parser sees ( and has to decide between a parenthesized expression and a
// cast. a cast is ( type stars ) so peek ahead through the stars to the ).
@@ -5556,6 +5645,7 @@ int types_init() {
addsym(tword[2], K_TYPE, tword); addsym(tint[2], K_TYPE, tint); addsym(tchar[2], K_TYPE, tchar);
addsym(tbool[2], K_TYPE, tbool); addsym(tvoid[2], K_TYPE, tvoid); addsym(tfloat[2], K_TYPE, tfloat);
addsym(intern("f64"), K_TYPE, tfloat);
+ addsym(intern("string"), K_TYPE, tptr(tchar)); // string is char*, as if by typedef char* string;
// f32: single precision. a variable holds the rounded value as a double; an element
// of an array or pointer of f32 is packed two to a word, like the sized integers
f32p = tnew(TY_FLOAT, 1, intern("f32"), 0);
@@ -5570,7 +5660,7 @@ int types_init() {
s_le = intern("<=");
s_ge = intern(">=");
s_eqeq = intern("=="); s_ne = intern("!="); s_plus = intern("+");
- s_struct = intern("struct"); s_type = intern("type"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small");
+ s_struct = intern("struct"); s_type = intern("type"); s_typedef = intern("typedef"); s_typecheck = intern("typecheck"); s_docs = intern("docs"); s_std = intern("std"); s_icon = intern("icon"); s_small = intern("small");
s_on = intern("on");
s_off = intern("off");
s_new = intern("rt.new");
@@ -6485,7 +6575,7 @@ int closure() {
while (1) {
pty[np] = tword;
if (expected && expected[0] == TY_FN && np < expected[3]) pty[np] = expected[7 + np];
- if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) {
+ if (!is_op(s_dot) && paramstart()) {
pty[np] = ptype(); if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
}
tag = 0;
@@ -7660,7 +7750,7 @@ word stmtstart() {
int s;
if (tk == T_OP) return tv == s_semi || tv == s_lb || tstart();
if (tk != T_ID || findloc(tv) >= 0) return 0;
- if (tstart()) return 1;
+ if (tstart() || typedef_ahead()) return 1;
s = lookup(tv);
return s >= 0 && skind[s] == K_KW && !(sflag[s] & F_EXPR);
}
@@ -7762,6 +7852,7 @@ int stmt_inner() {
if (s >= 0 && skind[s] == K_KW && (sflag[s] & F_STMT)) { kwcall(s, 0); return 0; }
}
if (untyped_const()) { next(); localdecl(2); return 0; }
+ if (typedef_ahead() && findloc(tv) < 0) return kw_typedef();
if (tstart()) { localdecl(1); return 0; }
if (tk == T_OP) {
if (tv == s_semi) { next(); return 0; }
@@ -8646,7 +8737,7 @@ int prescan(int kind, int obj) {
if (is_op(s_lb)) depth++;
else if (is_op(s_rb)) { depth--; pend = 0; po3 = 0; }
else if (depth <= 0) {
- if (is_id(s_type)) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; }
+ if (is_id(s_type) || typedef_ahead()) { while (tk != T_EOF && !is_op(s_semi)) next(); if (is_op(s_semi)) next(); pend = 0; po3 = 0; continue; }
if (tk == T_ID && rt.eq_w(tv, "namespace")) { kw_namespace(); continue; }
if (is_op(s_semi)) { pend = 0; po3 = 0; }
else if (is_op(s_dot)) { peek(); if (pk_tk == T_ID && pk_tv == s_u) pend = 1; }
@@ -8795,6 +8886,7 @@ int epilogue() {
infn = 0;
local_drop(lbase);
fdrop(lbase);
+ td_end(0);
return 0;
}
// the statements of a function between its braces, then the end-of-function
@@ -8850,7 +8942,7 @@ int fndef(int chk, int rty, int ret, int n) {
t = 0;
pty[np] = tword;
pk = 0;
- if (!is_op(s_dot) && ((tk == T_OP && tv == s_lk) || (tk == T_ID && (tv == s_fn || istypename(tv))))) {
+ if (!is_op(s_dot) && paramstart()) {
pty[np] = ptype(); typed = 1; pk = 1;
if (pty[np][0] == TY_ARR) pty[np] = decay(pty[np]);
if (isagg(pty[np])) {
@@ -9115,7 +9207,7 @@ int topsync() {
while (tk != T_EOF) {
if (is_op(s_lb)) depth++;
else if (is_op(s_rb)) { depth--; if (depth < 0) depth = 0; }
- else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type
+ else if (!depth && tk == T_ID && (tv == s_fn || tv == s_var || tv == s_const || tv == s_struct || tv == s_type || tv == s_typedef
|| tv == s_enhance || tv == s_typecheck || tv == s_docs || tv == s_unchecked || tv == s_immediate || tv == s_o3
|| tv == s_memdebug || tv == s_enum || tv == s_include)) break;
next();
@@ -9225,6 +9317,7 @@ int topitem1() {
if (is_id(s_var)) return gdecl(0);
if (is_id(s_struct)) { kw_struct(); return 0; }
if (is_id(s_type)) return kw_type();
+ if (typedef_ahead()) return kw_typedef();
if (is_id(s_typecheck)) return kw_typecheck();
if (is_id(s_docs)) return kw_docs();
if (is_id(s_memdebug)) return kw_memdebug();
@@ -10088,7 +10181,7 @@ int zc_init() {
poolend = R_POOLEND; poolblocks = 0; namecount = 0;
rt.fill_w(namehash, 0, hashcap);
nsym = 0; nfixu = 0; fixfree = 0; nbin = 0; nun = 0; nasg = 0; nkop = 0; seqtop = 0;
- lsp = 0; ctxsp = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0;
+ lsp = 0; ctxsp = 0; ntd = 0; snaptop = 0; nloc = 0; lbase = 0; npm = 0; npe = 0; rt.fill_w(lkeys, 0, lkeycap); lkeyn = 0;
s_lp = intern("("); s_rp = intern(")"); s_lb = intern("{"); s_rb = intern("}");
s_lk = intern("["); s_rk = intern("]"); s_comma = intern(","); s_semi = intern(";");
s_eq = intern("="); s_arrow = intern("->"); s_inc = intern("++"); s_dec = intern("--");
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 1608c4c..3262e39 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -390,7 +390,7 @@ int isopc(int c) {
int iskeyword(int n) {
return n == run.s_fn || n == run.s_var || n == run.s_const || n == run.s_else || n == run.s_as || n == run.s_unchecked || n == run.s_immediate
|| n == run.s_case || n == run.s_default || n == run.s_do || n == run.s_while || n == run.s_enum || n == run.s_include || n == run.s_memdebug
- || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typecheck || n == run.s_static;
+ || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typedef || n == run.s_typecheck || n == run.s_static;
}
// the record of the name at byte offset o (see rs_note in the compiler), or -1.
// the records are sorted by offset, so this is a binary search.
@@ -934,7 +934,7 @@ int reply_error(int code) {
return 0;
}
-int hs_name = -1, hs_first = -1, hs_const;
+int hs_name = -1, hs_first = -1, hs_const, hs_typedef, hs_tname;
int bs_k, bs_off; // the embedded source and name offset found by bs_find
// ---- hover ----
@@ -999,8 +999,8 @@ word hover_decl(int base, int ls, int le) {
// it (hcs is -1 when none) which runs to the line at hdl. a declaration needs
// no comment, so the signature still shows without one.
int hover_scan(int i) {
- hs_name = -1; hs_first = -1; hs_const = 0;
- if (hover_source_decl(i) || hover_source_function(i)) return 1;
+ hs_name = -1; hs_first = -1; hs_const = 0; hs_typedef = 0;
+ if (hover_typedef(i) || hover_source_decl(i) || hover_source_function(i)) return 1;
int base = doff[i], size = dsize[i], ls = 0, le, cs = 0 - 1, c0;
while (ls < size) {
le = ls;
@@ -1182,6 +1182,12 @@ int hover_docs(int i, int head, int name, int length, int tail) {
int hover_fill(int i) {
int base = doff[i], o = hdl, c, cs, ls, typed = 0, k, run.start, end;
hn = 0;
+ if (hs_typedef) { // typedef T name; as written, from its first word to its ;
+ hov_text("```zc\n"); c = hdl;
+ while (c < hdle) { hov_put(dc(base, c)); c++; }
+ hov_text("\n```"); hover_docs(i, hdl, hdl, 0 - 1, hdle);
+ return hn;
+ }
if (hs_name >= 0) {
hov_text("```zc\n"); c = hdl;
while (c < hs_first) { hov_put(dc(base, c)); c++; }
@@ -1624,9 +1630,11 @@ int hover_type_span(int i) {
}
if (best < 0) return 0;
t = run.ht_type[best];
+ // a typedef name is shown as its typedef, then the type it stands for
+ if (width == rt.len_w(hname) && run.ht_start[best] == hoff && hover_typedef(i)) { hover_fill(i); hover_type_info(t); return 1; }
hov_text("```zc\n"); hov_type(t); hov_text("\n```"); hover_type_info(t);
k = kwtext();
- if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) {
+ if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("string") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) {
while (*k && *k != '~') k++;
if (*k) { hov_text("\n\n"); hov_text(k + 1); }
}
@@ -1686,6 +1694,8 @@ char* kwtext() {
if (hname_is("return")) return "return e; or return;~leaves the function with a value, or leaves a void function. returning an owned local transfers it to the caller automatically.";
if (hname_is("enum")) return "enum name { A, B = 5 };~global constants. under typecheck on they are a type of their own, and a member can carry fields to make a constructor.";
if (hname_is("struct")) return "struct name { fields };~a record type. struct name<T> { } makes it generic, one copy for each set of type arguments.";
+ if (hname_is("typedef")) return "typedef T name;~an alias for a type, the same as type name = T; but in C order. the name is another way to write T, not a new type. inside a function it lasts to the end of its block.";
+ if (hname_is("string")) return "string~a built-in alias for char*, a word string, as if typedef char* string; were written.";
if (hname_is("type")) return "type name = T;~an alias for a type, such as type row = int[3]; for a pointer to an array.";
if (hname_is("include")) return "include \"path\"; / include std;~compiles a file or the embedded standard library at that point, its signatures first.";
if (hname_is("embed")) return "embed \"path\" as [type] name;~declares name with the bytes of the file at path, read when the unit is compiled. with no type, or bstr, name is a byte string; char* holds one character per word; a number type reads the bytes little-endian; T[] and T[n] lay them out as array elements.";
@@ -2160,6 +2170,7 @@ int bs_name(char* path) {
}
// the offset of the name a declaration line declares: the word before its first delimiter
int decl_off(int base, int ls, int le) {
+ if (hs_typedef) return hs_tname;
if (hs_name >= 0) return hs_name;
int dl = ls, e, b;
while (dl < le && !hdelim(dc(base, dl))) dl++;
@@ -2441,6 +2452,35 @@ int hover_source_function(int i) {
}
return 0;
}
+// the statement typedef T name; that declares hname, found by tokens, at the top level or in a block
+// (the last such one before the cursor, else the first). hdl and hdle are the typedef word and the end
+// of the ;, and hs_tname is where the name is.
+int hover_typedef(int i) {
+ int t = 0, w, depth, last, found = -1, first = -1, fend, fname, hend, hname_at;
+ fm_lex(i);
+ while (t < fm_nt) {
+ if (fm_k[t] == 7) break;
+ if (!fm_is(i, t, "typedef")) { t++; continue; }
+ w = t + 1; depth = 0; last = -1;
+ while (w < fm_nt && fm_k[w] != 7) {
+ if (fm_k[w] == 6 && fm_o[w] == 2 && !depth) break;
+ if (fm_k[w] == 6 && (fm_o[w] == 5 || fm_o[w] == 6)) break;
+ if (fm_k[w] == 6 && fm_o[w] == 3) depth++;
+ else if (fm_k[w] == 6 && fm_o[w] == 4) depth--;
+ else if (fm_k[w] == 1 && !depth) last = w;
+ w++;
+ }
+ if (w < fm_nt && fm_k[w] == 6 && fm_o[w] == 2 && last > t && hs_match(i, last)) {
+ if (first < 0) { first = t; fend = fm_e[w]; fname = fm_s[last]; }
+ if (fm_s[t] <= hcursor) { found = t; hend = fm_e[w]; hname_at = fm_s[last]; }
+ }
+ t = w + 1;
+ }
+ if (first < 0) return 0;
+ if (found < 0) { found = first; hend = fend; hname_at = fname; }
+ hdl = fm_s[found]; hdle = hend; hs_tname = hname_at; hs_typedef = 1; hs_name = -1; hs_first = -1; hs_const = 0; hcs = -1;
+ return 1;
+}
int hover_source_decl(int i) {
int t = 0, head = -1, first = -1, candidate = -1, depth = 0, brackets = 0, init = 0, constant = 0, matched = 0, tail, prev = -1;
fm_lex(i);
diff --git a/tests/drivers/lsp_test.js b/tests/drivers/lsp_test.js
index 18d8bf7..0f4abe1 100644
--- a/tests/drivers/lsp_test.js
+++ b/tests/drivers/lsp_test.js
@@ -250,5 +250,23 @@ check(hcv && hcv.includes('parameter'), 'hover on a use of a parameter named lik
hcv = hover(scp, 4, at(scp, 4, 'len'));
check(hcv && hcv.includes('the number of items'), 'hover on the built-in still shows its text where no local shadows it', hcv);
+// typedefs: the name shows its declaration, at the declaration, at a use in a declaration, and in a block
+const tdsrc = 'struct node { int v; node* next; };\n// another name for node*\ntypedef node* link;\ntypedef int(int, int) binop;\nlink head(link n) { return n; }\nint use(string s) {\n\ttypedef int cell;\n\tcell c = 1;\n\tbinop f = 0;\n\treturn c;\n}\n';
+hv = hover(tdsrc, 2, at(tdsrc, 2, 'link'));
+check(hv && hv.includes('typedef node* link;') && hv.includes('another name for node*'), 'hover on the name in a typedef shows the typedef and its doc', hv);
+hv = hover(tdsrc, 4, at(tdsrc, 4, 'link'));
+check(hv && hv.includes('typedef node* link;') && hv.includes('a pointer to `node`'), 'hover on a typedef name used as a type shows the typedef and the type', hv);
+hv = hover(tdsrc, 4, at(tdsrc, 4, 'link', 1));
+check(hv && hv.includes('typedef node* link;'), 'hover on a typedef name used as a parameter type shows the typedef', hv);
+hv = hover(tdsrc, 3, at(tdsrc, 3, 'binop'));
+check(hv && hv.includes('typedef int(int, int) binop;'), 'hover on a function type typedef shows the whole typedef', hv);
+hv = hover(tdsrc, 7, at(tdsrc, 7, 'cell'));
+check(hv && hv.includes('typedef int cell;'), 'hover on a typedef made in a block shows the typedef', hv);
+hv = hover(tdsrc, 5, at(tdsrc, 5, 'string'));
+check(hv && hv.includes('char*') && hv.includes('built-in alias'), 'hover on string shows char* and what string is', hv);
+hv = hover(tdsrc, 2, 3);
+check(hv && hv.includes('typedef T name;') && hv.includes('type name = T;'), 'hover on the typedef keyword explains it', hv);
+check(tokens(tdsrc).includes('typedef:keyword'), 'typedef is highlighted as a keyword', tokens(tdsrc));
+
console.log(ok + ' lsp checks passed, ' + bad + ' failed');
process.exit(bad ? 1 : 0);
diff --git a/tests/langerrors/typedef_generic_bare.zc b/tests/langerrors/typedef_generic_bare.zc
new file mode 100644
index 0000000..5f3e906
--- /dev/null
+++ b/tests/langerrors/typedef_generic_bare.zc
@@ -0,0 +1,6 @@
+namespace;
+
+// expect: expected '<', found 'plain'
+struct box<T> { T item; };
+typedef box plain;
+fn main() { return 0; }
diff --git a/tests/langerrors/typedef_local_redefine.zc b/tests/langerrors/typedef_local_redefine.zc
new file mode 100644
index 0000000..e51c32f
--- /dev/null
+++ b/tests/langerrors/typedef_local_redefine.zc
@@ -0,0 +1,8 @@
+namespace;
+
+// expect: cell: already defined
+fn main() {
+ typedef int cell;
+ typedef char* cell;
+ return 0;
+}
diff --git a/tests/langerrors/typedef_redefine.zc b/tests/langerrors/typedef_redefine.zc
new file mode 100644
index 0000000..05085ec
--- /dev/null
+++ b/tests/langerrors/typedef_redefine.zc
@@ -0,0 +1,6 @@
+namespace;
+
+// expect: num: already defined
+typedef int num;
+typedef char* num;
+fn main() { return 0; }
diff --git a/tests/langerrors/typedef_scope.zc b/tests/langerrors/typedef_scope.zc
new file mode 100644
index 0000000..833e30e
--- /dev/null
+++ b/tests/langerrors/typedef_scope.zc
@@ -0,0 +1,8 @@
+namespace;
+
+// expect: expected ';', found 'x'
+fn main() {
+ { typedef int cell; cell a = 1; }
+ cell x = 2;
+ return 0;
+}
diff --git a/tests/langerrors/typedef_string_redefine.zc b/tests/langerrors/typedef_string_redefine.zc
new file mode 100644
index 0000000..226a94e
--- /dev/null
+++ b/tests/langerrors/typedef_string_redefine.zc
@@ -0,0 +1,5 @@
+namespace;
+
+// expect: string: already defined
+typedef int string;
+fn main() { return 0; }
diff --git a/tests/langerrors/typedef_tag.zc b/tests/langerrors/typedef_tag.zc
new file mode 100644
index 0000000..70ab491
--- /dev/null
+++ b/tests/langerrors/typedef_tag.zc
@@ -0,0 +1,5 @@
+namespace;
+
+// expect: a typedef names a type, which has no tag
+typedef char*.u owned;
+fn main() { return 0; }
diff --git a/tests/langerrors/typedef_unknown.zc b/tests/langerrors/typedef_unknown.zc
new file mode 100644
index 0000000..8076e86
--- /dev/null
+++ b/tests/langerrors/typedef_unknown.zc
@@ -0,0 +1,5 @@
+namespace;
+
+// expect: missing_t: isn't a type
+typedef missing_t thing;
+fn main() { return 0; }
diff --git a/tests/langerrors/typedef_variable_clash.zc b/tests/langerrors/typedef_variable_clash.zc
new file mode 100644
index 0000000..2c843f2
--- /dev/null
+++ b/tests/langerrors/typedef_variable_clash.zc
@@ -0,0 +1,8 @@
+namespace;
+
+// expect: count: already defined
+fn main() {
+ int count = 1;
+ typedef int count;
+ return count;
+}
diff --git a/tests/langerrors/typedef_void_variable.zc b/tests/langerrors/typedef_void_variable.zc
new file mode 100644
index 0000000..c5c7ced
--- /dev/null
+++ b/tests/langerrors/typedef_void_variable.zc
@@ -0,0 +1,5 @@
+namespace;
+
+// expect: a variable can't be void
+typedef void nothing;
+fn main() { nothing x; return 0; }
diff --git a/tests/programs/typedef.zc b/tests/programs/typedef.zc
new file mode 100644
index 0000000..307ef75
--- /dev/null
+++ b/tests/programs/typedef.zc
@@ -0,0 +1,89 @@
+namespace;
+include rt;
+typecheck on;
+
+// typedef T name; makes name another way to write T: the same type, so the two mix freely
+struct node { int v; node* next; };
+struct pair<A, B> { A a; B b; };
+
+typedef int count;
+typedef char* label;
+typedef node* link;
+typedef link* links;
+typedef int[4] quad;
+typedef int(int, int) binop;
+typedef pair<int, int> ipair;
+typedef int[?] ints;
+typedef count counter;
+
+struct box { count n; link head; ipair p; };
+
+count twice(count x) { return x + x; }
+count add(int a, int b) { return a + b; }
+count apply(binop f, count a, count b) { return f(a, b); }
+count sum(link n) { count s = 0; while (n) { s += n.v; n = n.next; } return s; }
+label hello() { return "hello"; }
+string greet(string s) { return s; }
+// string is only a type where a type is expected: an untyped parameter can still be called string
+fn twice_name(string) { return string + string; }
+count first_of(links ls) { return ls[0].v; }
+T pick<T>(T x) { typedef T item; item y = x; return y; }
+count scoped() {
+ count total = 0;
+ { typedef int cell; cell c = 3; total += c; }
+ { typedef char* cell; cell c = "ab"; total += rt.len_w(c); }
+ return total;
+}
+count scoped_again() { typedef quad cell; cell c; c[2] = 7; return c[2] + sizeof(cell); }
+
+// a link is a node*: every use below mixes the two
+count same_ptr(node* p, link q) { return p == q; }
+count casts(link p) {
+ word w = p as word;
+ link back = w as link;
+ node* n = (node*)back;
+ return back.v + ((link)n == p);
+}
+count boxed(link p) {
+ box bx; bx.n = 6; bx.head = 0; bx.p.a = 1; bx.p.b = 2;
+ pair<link, count> pl; pl.a = 0; pl.b = 5;
+ return bx.n + p.v + bx.p.a + bx.p.b + pl.b + !bx.head + !pl.a;
+}
+count by_ref(links ls) { return first_of(ls); }
+
+fn main() {
+ link a = rt.new(node), b = rt.new(node);
+ a.v = 3; b.v = 4; a.next = b;
+ rt.put_n(sum(a)); rt.put_c(' ');
+ rt.put_n(same_ptr(a, a)); rt.put_c(' ');
+ rt.put_n(twice(21)); rt.put_c(' ');
+ rt.put_n(apply(add, 2, 3)); rt.put_c(' ');
+ binop op = add;
+ rt.put_n(op(10, 20)); rt.put_c(10);
+
+ quad q; q[3] = 9;
+ rt.put_n(q[3] + sizeof(quad)); rt.put_c(' ');
+ rt.put_n(sizeof(count) + sizeof(link) + sizeof(ipair)); rt.put_c(' ');
+ rt.put_n(boxed(a)); rt.put_c(' ');
+ ints xs = {1, 2, 3};
+ push(xs, 4);
+ rt.put_n(len(xs)); rt.put_c(10);
+
+ rt.put_n(casts(a)); rt.put_c(' ');
+ counter k = (count)3;
+ rt.put_n(k); rt.put_c(' ');
+ links ls = &a;
+ rt.put_n(by_ref(ls)); rt.put_c(' ');
+ rt.put_n(pick<count>(11) + pick(1)); rt.put_c(10);
+
+ rt.put_n(scoped()); rt.put_c(' ');
+ rt.put_n(scoped_again()); rt.put_c(10);
+
+ string s = "hi";
+ label l = s;
+ char* c = l;
+ rt.put_s(s); rt.put_s(l); rt.put_s(c); rt.put_s(hello()); rt.put_s(greet("ok")); rt.put_c(10);
+ rt.put_n(twice_name(4)); rt.put_c(10);
+ rt.free(d*a);
+ return 0;
+}
diff --git a/tests/test.sh b/tests/test.sh
index 7ae7b9d..709d09c 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -106,6 +106,7 @@ run(){ # $1 = vm command, $2 = label
chk "$($vm < tests/programs/meta.zc | tr '\n' '/')" "1024 19/unless ok/rrr/15 42/7/42/7/144/" "$l parse words, quote, eval, load, operator tables"
chk "$($vm < tests/programs/misc.zc | tr '\n' '/')" "45/54321/ok/" "$l compile-time eval, patched mnemonics, unchecked"
chk "$($vm < tests/programs/types.zc | tr '\n' '/')" "12 5 2 15 56 42 7 14 3/rect rect* list<int>* list<T>*.u(int) T(list<T>*) fn(word.d)/a:point@0 b:point@2 tag:int@4 /point 1 1/" "$l types, generics, reflection"
+ chk "$($vm < tests/programs/typedef.zc | tr '\n' '/')" "7 1 42 5 30/13 4 19 4/4 3 3 12/5 11/hihihihellook/8/" "$l typedef, global and local, in every place a type goes, and string"
chk "$($vm < tests/programs/typeok.zc)" "14" "$l a program the typechecker accepts"
chk "$($vm < tests/programs/typeoff.zc)" "6" "$l the typechecker is off by default"
chk "$($vm < tests/programs/enhance.zc)" "100 0 343 3,2 5 -2 0" "$l virtual enhancements"
diff --git a/tests/typeerrors/typedef_mismatch.zc b/tests/typeerrors/typedef_mismatch.zc
new file mode 100644
index 0000000..26453dd
--- /dev/null
+++ b/tests/typeerrors/typedef_mismatch.zc
@@ -0,0 +1,6 @@
+namespace;
+
+// expect: type error: initializer: expected int, got char*
+typecheck on;
+typedef int count;
+fn main() { count c = "text"; return 0; }
diff --git a/tests/typeerrors/typedef_pointer.zc b/tests/typeerrors/typedef_pointer.zc
new file mode 100644
index 0000000..5dbbb17
--- /dev/null
+++ b/tests/typeerrors/typedef_pointer.zc
@@ -0,0 +1,7 @@
+namespace;
+
+// expect: type error: initializer: expected node*, got int*
+typecheck on;
+struct node { int v; };
+typedef node* link;
+fn main() { int n = 0; link p = &n; return 0; }