generics (in the compiler written in Finch)

fn max[T](T a, T b) -> T      struct Stack[T] { []T items }      fn Stack[T].push(T x)

The type is worked out from the arguments (max(3, 4), first([1, 2])) and written out
where it cannot be: max[float](1, 2.5), Stack[int](), Pair[str, int](...). Each use makes
its own copy, so a generic costs nothing at run time. Several parameters
(Pair[A, B]), nesting (Stack[Pair[int, str]]), methods, and printing (Stack[int](items: ...))
work. An error inside a generic says which version it was building.

boot.sh: 37 passed, 0 failed (stage 2 and 3 identical).
This commit is contained in:
Nexoniarz 2026-10-25 20:53:13 +01:00
commit 04eb0cce55
10 changed files with 575 additions and 7 deletions

View file

@ -97,6 +97,7 @@ struct Node {
str kind str kind
str name // identifier, operator, callee, field or method name str name // identifier, operator, callee, field or method name
str recv // fn Point.move(...): "Point"; empty for a plain function str recv // fn Point.move(...): "Point"; empty for a plain function
[]str tparams // fn max[T](...), struct Stack[T], fn Stack[T].push: the type parameters
str text // literal text str text // literal text
Type ty // declared type (decl, param, field, fn result) Type ty // declared type (decl, param, field, fn result)
bool flag // decl: has a type; or: the right side is a { block } bool flag // decl: has a type; or: the right side is a { block }

View file

@ -16,6 +16,23 @@ struct Var {
// A scope's variables, and the defers that run when it ends, newest first. // A scope's variables, and the defers that run when it ends, newest first.
// A defer sees only the variables declared before it: `orders` says how many. // A defer sees only the variables declared before it: `orders` says how many.
// A generic struct or function, kept as written until something asks for a version of it.
struct Template {
str module
str name
str recv // a method: the generic struct it belongs to
[]str tparams
int prog
int index
bool isStruct
bool mutself
}
struct Binds {
[]ast.Type types
[]bool bound
}
struct EnumDef { struct EnumDef {
str module str module
str name str name
@ -41,6 +58,9 @@ struct FnSig {
str recv // a method of this struct ("" for a function) str recv // a method of this struct ("" for a function)
str recvmod // and the module of that struct: "C" for a struct from a header str recvmod // and the module of that struct: "C" for a struct from a header
bool recvEnum // the receiver is an enum bool recvEnum // the receiver is an enum
[]str tnames // an instance of a generic function: its type parameters and what they stand for
[]ast.Type targs
str shown // max[int], for messages
bool fallible // -> T!: returns { i1 ok, T value, str error } bool fallible // -> T!: returns { i1 ok, T value, str error }
bool mutself // the method changes self bool mutself // the method changes self
str ll str ll
@ -58,6 +78,9 @@ struct StructDef {
bool isC // a struct from a C header: module "C", laid out as C laid it out bool isC // a struct from a C header: module "C", laid out as C laid it out
[]int lidx // the LLVM element number of each field (C structs have padding between) []int lidx // the LLVM element number of each field (C structs have padding between)
[]int offs // C: the byte offset of each field []int offs // C: the byte offset of each field
str base // an instance of a generic struct: Stack for Stack[int]
[]str tnames
[]ast.Type targs
str unsupported // C: why it can't be passed by value str unsupported // C: why it can't be passed by value
int csize int csize
int calign int calign
@ -104,6 +127,10 @@ struct Gen {
str module str module
[]FnSig sigs []FnSig sigs
[]StructDef structs []StructDef structs
[]Template templates
[]str tnames // the type parameters in force, and what they stand for
[]ast.Type targs
str instNote // " (while building max[Pixel])", added to errors found inside a generic
[]EnumDef enums []EnumDef enums
[]str done []str done
[]str mm // names of methods that change self, for the mutation analysis []str mm // names of methods that change self, for the mutation analysis
@ -121,7 +148,7 @@ struct Gen {
// ---------- basics ---------- // ---------- basics ----------
fn fail(ptr[Gen] g, ast.Node n, str msg) { fn fail(ptr[Gen] g, ast.Node n, str msg) {
print(g.files[n.file] + ":" + str(n.line) + ":" + str(n.col) + ": error: " + msg) print(g.files[n.file] + ":" + str(n.line) + ":" + str(n.col) + ": error: " + msg + g.instNote)
exit(1) exit(1)
} }
@ -325,6 +352,25 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
if t.kind != "named" { if t.kind != "named" {
return t return t
} }
// T inside a generic
if t.module.len == 0 && t.elem.len == 0 && g.tnames.contains(t.name) {
return g.targs[g.tnames.find(t.name)]
}
// Stack[int]
if t.elem.len > 0 {
[]ast.Type args
for a in t.elem {
args.push(resolve(g, a, at))
}
gm := g.module
if t.module.len > 0 {
if !imports(g, t.module) {
fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")")
}
gm = t.module
}
return instStruct(g, gm, t.name, args, at)
}
if t.module == "C" { if t.module == "C" {
if findStruct(g, "C", t.name) < 0 { if findStruct(g, "C", t.name) < 0 {
fail(g, at, "there is no C struct named '" + t.name + "'") fail(g, at, "there is no C struct named '" + t.name + "'")
@ -351,6 +397,11 @@ fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type {
return ast.Type(kind: "struct", name: t.name, module: module) return ast.Type(kind: "struct", name: t.name, module: module)
} }
// Stack[int] and Pair[str, int] as part of an LLVM name
fn mangle(str name) -> str {
return name.replace("[", ".").replace("]", "").replace(",", ".").replace(" ", "")
}
fn modName(str m) -> str { fn modName(str m) -> str {
if m.len == 0 { if m.len == 0 {
return "main" return "main"
@ -390,7 +441,7 @@ fn llty(ptr[Gen] g, ast.Type t) -> str {
if k == "void" { if k == "void" {
return "void" return "void"
} }
return "%S." + modName(t.module) + "." + t.name return "%S." + modName(t.module) + "." + mangle(t.name)
} }
// What a function returns as far as LLVM is concerned: a function that can fail // What a function returns as far as LLVM is concerned: a function that can fail
@ -453,7 +504,7 @@ fn typeKey(ast.Type t) -> str {
return "E_" + modName(t.module) + "_" + t.name return "E_" + modName(t.module) + "_" + t.name
} }
if t.kind == "struct" { if t.kind == "struct" {
return "S_" + modName(t.module) + "_" + t.name return "S_" + modName(t.module) + "_" + mangle(t.name)
} }
return t.kind return t.kind
} }
@ -1084,9 +1135,14 @@ fn declare(ptr[Gen] g) {
g.module = g.progs[pi].module g.module = g.progs[pi].module
for si in 0..g.progs[pi].structs.len { for si in 0..g.progs[pi].structs.len {
s := g.progs[pi].structs[si] s := g.progs[pi].structs[si]
if findStruct(g, g.module, s.name) >= 0 { if findStruct(g, g.module, s.name) >= 0 || findTemplate(g, g.module, s.name, "", true) >= 0 {
fail(g, s, "the struct '" + s.name + "' is defined twice") fail(g, s, "the struct '" + s.name + "' is defined twice")
} }
if s.tparams.len > 0 {
// a generic struct: it only becomes a real struct for the types it is used with
g.templates.push(Template(module: g.module, name: s.name, tparams: s.tparams, prog: pi, index: si, isStruct: true))
continue
}
g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si)) g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si))
} }
} }
@ -1126,6 +1182,17 @@ fn declare(ptr[Gen] g) {
g.module = g.progs[pi].module g.module = g.progs[pi].module
for fi in 0..g.progs[pi].fns.len { for fi in 0..g.progs[pi].fns.len {
f := g.progs[pi].fns[fi] f := g.progs[pi].fns[fi]
if f.tparams.len > 0 {
// generic: kept as written, and made for the types it is called with
if findTemplate(g, g.module, f.name, f.recv, false) >= 0 {
fail(g, f, "function '" + f.name + "' is defined twice")
}
if f.recv.len > 0 && findTemplate(g, g.module, f.recv, "", true) < 0 {
fail(g, f, "there is no generic struct named '" + f.recv + "' here (a method goes in the same file as its struct)")
}
g.templates.push(Template(module: g.module, name: f.name, recv: f.recv, tparams: f.tparams, prog: pi, index: fi))
continue
}
recvmod := g.module recvmod := g.module
recvEnum := false recvEnum := false
if f.recv.len > 0 && findEnum(g, g.module, f.recv) >= 0 { if f.recv.len > 0 && findEnum(g, g.module, f.recv) >= 0 {
@ -1187,6 +1254,17 @@ fn declare(ptr[Gen] g) {
} }
} }
} }
for ti in 0..g.templates.len {
tpl := g.templates[ti]
if tpl.recv.len > 0 && !tpl.mutself {
tf := g.progs[tpl.prog].fns[tpl.index]
if mutates(g, "self", tf.kids[tf.kids.len - 1]) {
g.templates[ti].mutself = true
g.mm.push(tpl.name)
changed = true
}
}
}
} }
// a parameter nobody changes is borrowed rather than copied // a parameter nobody changes is borrowed rather than copied
for i in 0..g.sigs.len { for i in 0..g.sigs.len {
@ -1199,6 +1277,164 @@ fn declare(ptr[Gen] g) {
} }
} }
fn findTemplate(ptr[Gen] g, str module, str name, str recv, bool isStruct) -> int {
for i in 0..g.templates.len {
t := g.templates[i]
if t.module == module && t.name == name && t.recv == recv && t.isStruct == isStruct {
return i
}
}
return -1
}
fn shownTypes([]ast.Type args) -> str {
[]str parts
for a in args {
parts.push(ast.show(a))
}
return parts.join(", ")
}
// Stack[int]: the struct with T = int, made the first time it is asked for.
fn instStruct(ptr[Gen] g, str module, str base, []ast.Type args, ast.Node at) -> ast.Type {
ti := findTemplate(g, module, base, "", true)
if ti < 0 {
fail(g, at, "there is no generic struct named '" + base + "' (a struct written struct " + base + "[T] { ... } takes type arguments)")
}
tpl := g.templates[ti]
if args.len != tpl.tparams.len {
fail(g, at, base + " takes " + str(tpl.tparams.len) + " type argument(s), like " + base + "[" + tpl.tparams.join(", ") + "], but got " + str(args.len))
}
name := base + "[" + shownTypes(args) + "]"
if findStruct(g, module, name) < 0 {
g.structs.push(StructDef(module: module, name: name, prog: tpl.prog, index: tpl.index, base: base, tnames: tpl.tparams, targs: args))
resolveStruct(g, g.structs.len - 1)
}
return ast.Type(kind: "struct", name: name, module: module)
}
// max[int]: the function with T = int; for a method, `recvType` is the struct instance it belongs to.
fn instFn(ptr[Gen] g, int ti, []ast.Type args, ast.Type recvType, ast.Node at) -> int {
tpl := g.templates[ti]
shown := tpl.name + "[" + shownTypes(args) + "]"
if tpl.recv.len > 0 {
existing := findMethod(g, tpl.module, recvType.name, tpl.name)
if existing >= 0 {
return existing
}
} else {
existing := findFn(g, tpl.module, shown)
if existing >= 0 {
return existing
}
}
f := g.progs[tpl.prog].fns[tpl.index]
sig := FnSig(module: tpl.module, name: shown, recvmod: tpl.module, fallible: f.fallible, prog: tpl.prog, index: tpl.index, tnames: tpl.tparams, targs: args, mutself: tpl.mutself, shown: shown)
sig.ll = "@finch." + modName(tpl.module) + "." + mangle(shown)
if tpl.recv.len > 0 {
sig.name = tpl.name
sig.recv = recvType.name
sig.shown = recvType.name + "." + tpl.name
sig.ll = "@finch." + modName(tpl.module) + "." + mangle(recvType.name) + "." + tpl.name
}
savedN := g.tnames
savedA := g.targs
savedM := g.module
savedNote := g.instNote
g.tnames = tpl.tparams
g.targs = args
g.module = tpl.module
g.instNote = " (while building " + sig.shown + ")"
sig.ret = resolve(g, f.ty, f)
body := f.kids[f.kids.len - 1]
for k in 0..f.kids.len - 1 {
pt := resolve(g, f.kids[k].ty, f.kids[k])
sig.params.push(pt)
sig.pnames.push(f.kids[k].name)
sig.borrow.push(owning(g, pt) && !mutates(g, f.kids[k].name, body))
}
g.tnames = savedN
g.targs = savedA
g.module = savedM
g.instNote = savedNote
g.sigs.push(sig)
return g.sigs.len - 1
}
// What each type parameter stands for, from the types the arguments turned out to have.
fn bindType(ptr[Gen] g, ast.Type pat, ast.Type actual, []str tparams, ptr[Binds] b) {
if pat.kind == "named" && pat.module.len == 0 && pat.elem.len == 0 && tparams.contains(pat.name) {
i := tparams.find(pat.name)
if !b.bound[i] {
b.types[i] = actual
b.bound[i] = true
}
return
}
if (pat.kind == "array" || pat.kind == "ptr" || pat.kind == "fixed") && pat.elem.len > 0 && actual.kind == pat.kind && actual.elem.len > 0 {
bindType(g, pat.elem[0], actual.elem[0], tparams, b)
return
}
if pat.kind == "map" && actual.kind == "map" {
bindType(g, pat.elem[0], actual.elem[0], tparams, b)
bindType(g, pat.elem[1], actual.elem[1], tparams, b)
return
}
if pat.kind == "named" && pat.elem.len > 0 && actual.kind == "struct" {
sd := g.structs[findStruct(g, actual.module, actual.name)]
if sd.base == pat.name && sd.targs.len == pat.elem.len {
for i in 0..pat.elem.len {
bindType(g, pat.elem[i], sd.targs[i], tparams, b)
}
}
}
}
// max(3, 4), or max[float](3, 4): make the version the arguments ask for, and call it.
fn genericCall(ptr[Gen] g, ast.Node c, int ti, int first, str shown) -> Val {
tpl := g.templates[ti]
f := g.progs[tpl.prog].fns[tpl.index]
nparams := f.kids.len - 1
checkArgs(g, c, first, nparams, shown)
[]ast.Type args
if c.ty.elem.len > 0 {
if c.ty.elem.len != tpl.tparams.len {
fail(g, c, shown + " takes " + str(tpl.tparams.len) + " type argument(s), but got " + str(c.ty.elem.len))
}
for a in c.ty.elem {
args.push(resolve(g, a, c))
}
return callFn(g, instFn(g, ti, args, T("void"), c), c, first, shown, "", "")
}
b := Binds()
for i in 0..tpl.tparams.len {
b.types.push(T("void"))
b.bound.push(false)
}
[]Val vals
for k in 0..nparams {
v := expr(g, c.kids[first + k])
if v.ty.kind == "void" || v.ty.kind == "null" {
fail(g, c.kids[first + k], "this has no type to work out " + tpl.tparams.join(", ") + " from")
}
vals.push(v)
bindType(g, f.kids[k].ty, v.ty, tpl.tparams, addr(b))
}
for i in 0..tpl.tparams.len {
if !b.bound[i] {
fail(g, c, "can't tell what " + tpl.tparams[i] + " is here; write it out: " + tpl.name + "[int](...)")
}
}
si := instFn(g, ti, b.types, T("void"), c)
sig := g.sigs[si]
[]str out
for k in 0..nparams {
v := coerce(g, vals[k], sig.params[k], c.kids[first + k], "argument '" + sig.pnames[k] + "'")
out.push(llty(g, sig.params[k]) + " " + v.v)
}
return emitCall(g, sig, c, out, shown)
}
// A struct from a C header: the fields clang says it has, at the offsets clang says, // A struct from a C header: the fields clang says it has, at the offsets clang says,
// with byte padding wherever C left a gap (or a field Finch can't see). // with byte padding wherever C left a gap (or a field Finch can't see).
fn resolveCStruct(ptr[Gen] g, int i) { fn resolveCStruct(ptr[Gen] g, int i) {
@ -1245,6 +1481,24 @@ fn resolveStruct(ptr[Gen] g, int i) {
resolveCStruct(g, i) resolveCStruct(g, i)
return return
} }
if g.structs[i].base.len == 0 {
resolveStructBody(g, i)
return
}
// an instance of a generic struct: its fields are written in terms of T
savedN := g.tnames
savedA := g.targs
savedNote := g.instNote
g.tnames = g.structs[i].tnames
g.targs = g.structs[i].targs
g.instNote = " (while building " + g.structs[i].name + ")"
resolveStructBody(g, i)
g.tnames = savedN
g.targs = savedA
g.instNote = savedNote
}
fn resolveStructBody(ptr[Gen] g, int i) {
decl := g.progs[g.structs[i].prog].structs[g.structs[i].index] decl := g.progs[g.structs[i].prog].structs[g.structs[i].index]
if g.structs[i].state == 1 { if g.structs[i].state == 1 {
fail(g, decl, "the struct " + decl.name + " contains itself; use ptr[" + decl.name + "] or []" + decl.name + " for that field") fail(g, decl, "the struct " + decl.name + " contains itself; use ptr[" + decl.name + "] or []" + decl.name + " for that field")
@ -1270,12 +1524,18 @@ fn resolveStruct(ptr[Gen] g, int i) {
g.structs[i].owning = owns g.structs[i].owning = owns
g.module = saved g.module = saved
g.structs[i].state = 2 g.structs[i].state = 2
g.head.push("%S." + modName(g.structs[i].module) + "." + decl.name + " = type { " + fields.join(", ") + " }") g.head.push("%S." + modName(g.structs[i].module) + "." + mangle(g.structs[i].name) + " = type { " + fields.join(", ") + " }")
} }
fn define(ptr[Gen] g, int si) { fn define(ptr[Gen] g, int si) {
g.cur = g.sigs[si] g.cur = g.sigs[si]
g.module = g.cur.module g.module = g.cur.module
g.tnames = g.cur.tnames
g.targs = g.cur.targs
g.instNote = ""
if g.cur.targs.len > 0 {
g.instNote = " (while building " + g.cur.shown + ")"
}
f := g.progs[g.cur.prog].fns[g.cur.index] f := g.progs[g.cur.prog].fns[g.cur.index]
[]str empty []str empty
g.body = empty g.body = empty
@ -3576,8 +3836,27 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
} }
fi := findFn(g, g.module, n) fi := findFn(g, g.module, n)
if fi >= 0 { if fi >= 0 {
if c.ty.elem.len > 0 {
fail(g, c, "'" + n + "' is not generic, so it takes no type arguments")
}
return callFn(g, fi, c, 0, n, "", "") return callFn(g, fi, c, 0, n, "", "")
} }
gti := findTemplate(g, g.module, n, "", false)
if gti >= 0 {
return genericCall(g, c, gti, 0, n)
}
if findTemplate(g, g.module, n, "", true) >= 0 {
// Stack[int](...): a generic struct made with its type
if c.ty.elem.len == 0 {
fail(g, c, "'" + n + "' is generic, so say which type it holds: " + n + "[int](...)")
}
[]ast.Type targs
for a in c.ty.elem {
targs.push(resolve(g, a, c))
}
gt := instStruct(g, g.module, n, targs, c)
return construct(g, findStruct(g, gt.module, gt.name), c.kids, c.names, c)
}
si := findStruct(g, g.module, n) si := findStruct(g, g.module, n)
if si >= 0 { if si >= 0 {
return construct(g, si, c.kids, c.names, c) return construct(g, si, c.kids, c.names, c)
@ -3619,6 +3898,11 @@ fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, st
} }
args.push(llty(g, sig.params[k]) + " " + v.v) args.push(llty(g, sig.params[k]) + " " + v.v)
} }
return emitCall(g, sig, c, args, shown)
}
// The call itself, once the arguments are values ("i64 5"); the receiver's address, if any, is already first.
fn emitCall(ptr[Gen] g, FnSig sig, ast.Node c, []str args, str shown) -> Val {
if sig.fallible && g.ftarget != nodeKey(c) { if sig.fallible && g.ftarget != nodeKey(c) {
fail(g, c, "'" + shown + "' can fail, so say what happens then: " + shown + "(...) or <value>, " + shown + "(...) or { ... }, or try " + shown + "(...)") fail(g, c, "'" + shown + "' can fail, so say what happens then: " + shown + "(...) or <value>, " + shown + "(...) or { ... }, or try " + shown + "(...)")
} }
@ -3683,12 +3967,18 @@ fn construct(ptr[Gen] g, int si, []ast.Node args, []str names, ast.Node at) -> V
// a default value is evaluated where the struct is defined, without the caller's variables // a default value is evaluated where the struct is defined, without the caller's variables
savedScopes := g.scopes savedScopes := g.scopes
savedModule := g.module savedModule := g.module
savedTN := g.tnames
savedTA := g.targs
g.tnames = sd.tnames
g.targs = sd.targs
[]Scope none []Scope none
g.scopes = none g.scopes = none
pushScope(g) pushScope(g)
g.module = sd.module g.module = sd.module
fv = coerce(g, exprWant(g, decl.kids[k].kids[0], ft), ft, decl.kids[k].kids[0], "the default of '" + sd.fnames[k] + "'") fv = coerce(g, exprWant(g, decl.kids[k].kids[0], ft), ft, decl.kids[k].kids[0], "the default of '" + sd.fnames[k] + "'")
g.module = savedModule g.module = savedModule
g.tnames = savedTN
g.targs = savedTA
g.scopes = savedScopes g.scopes = savedScopes
} else { } else {
continue continue
@ -3711,6 +4001,10 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
if fi >= 0 { if fi >= 0 {
return callFn(g, fi, m, 1, mn + "." + m.name, "", "") return callFn(g, fi, m, 1, mn + "." + m.name, "", "")
} }
mti := findTemplate(g, mn, m.name, "", false)
if mti >= 0 {
return genericCall(g, m, mti, 1, mn + "." + m.name)
}
mei := findEnum(g, mn, m.name) mei := findEnum(g, mn, m.name)
if mei >= 0 { if mei >= 0 {
return enumFrom(g, m, mei) return enumFrom(g, m, mei)
@ -3781,6 +4075,15 @@ fn method(ptr[Gen] g, ast.Node m) -> Val {
} }
if st.kind == "struct" || st.kind == "enum" { if st.kind == "struct" || st.kind == "enum" {
fi := findMethod(g, st.module, st.name, m.name) fi := findMethod(g, st.module, st.name, m.name)
if fi < 0 && st.kind == "struct" {
sdi := findStruct(g, st.module, st.name)
if g.structs[sdi].base.len > 0 {
mti := findTemplate(g, st.module, m.name, g.structs[sdi].base, false)
if mti >= 0 {
fi = instFn(g, mti, g.structs[sdi].targs, st, m)
}
}
}
if fi < 0 { if fi < 0 {
fail(g, m, "the " + st.kind + " " + ast.show(st) + " has no method '" + m.name + "'") fail(g, m, "the " + st.kind + " " + ast.show(st) + " has no method '" + m.name + "'")
} }
@ -4142,8 +4445,11 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci) -> str {
print(files[0] + ":1:1: error: there is no main function (add 'fn main() { ... }')") print(files[0] + ":1:1: error: there is no main function (add 'fn main() { ... }')")
exit(1) exit(1)
} }
for i in 0..g.sigs.len { // instantiating a generic adds functions while this goes on, so the end is not fixed
define(g, i) di := 0
while di < g.sigs.len {
define(g, di)
di += 1
} }
// the C entry point calls the Finch main // the C entry point calls the Finch main
m := g.sigs[main] m := g.sigs[main]

View file

@ -125,10 +125,80 @@ fn parseType(ptr[Parser] p) -> ast.Type {
t.module = name t.module = name
t.name = next(p).text t.name = next(p).text
} }
// Stack[int], Pair[str, int]: the type arguments of a generic struct
if at(p, "[") && !cur(p).nl && peekKind(p, 1) != "]" {
next(p)
while true {
t.elem.push(parseType(p))
if !accept(p, ",") {
break
}
}
expect(p, "]", "']' after the type arguments")
}
return t return t
} }
// Stack[int] name: a generic type, then a name on the same line. (a[i] by itself is an index.)
fn genericDeclAhead(ptr[Parser] p) -> bool {
if !at(p, "ident") || peekKind(p, 1) != "[" || peekKind(p, 2) == "]" {
return false
}
depth := 0
i := p.i + 1
while i < p.toks.len {
k := p.toks[i].kind
if k == "[" {
depth += 1
} else if k == "]" {
depth -= 1
if depth == 0 {
return i + 1 < p.toks.len && p.toks[i + 1].kind == "ident" && !p.toks[i + 1].nl
}
} else if k == "eof" || p.toks[i].nl && depth == 0 {
return false
}
i += 1
}
return false
}
// The same, when the name was just taken and the parser stands on the `[`.
fn genericCallAheadFrom(ptr[Parser] p) -> bool {
p.i -= 1
r := genericCallAhead(p)
p.i += 1
return r
}
// Is the `[` after this name followed, once it closes, by `(`? Then it is max[int](...) and not an index.
fn genericCallAhead(ptr[Parser] p) -> bool {
if peekKind(p, 1) != "[" || p.toks[p.i + 1].nl || peekKind(p, 2) == "]" {
return false
}
depth := 0
i := p.i + 1
while i < p.toks.len {
k := p.toks[i].kind
if k == "[" {
depth += 1
} else if k == "]" {
depth -= 1
if depth == 0 {
return i + 1 < p.toks.len && p.toks[i + 1].kind == "(" && !p.toks[i + 1].nl
}
} else if k == "eof" || p.toks[i].nl {
return false
}
i += 1
}
return false
}
fn atDeclaration(ptr[Parser] p) -> bool { fn atDeclaration(ptr[Parser] p) -> bool {
if genericDeclAhead(p) {
return true
}
if at(p, "[") { if at(p, "[") {
return peekKind(p, 1) == "]" return peekKind(p, 1) == "]"
} }
@ -262,10 +332,27 @@ fn parseMatch(ptr[Parser] p, bool asExpr) -> ast.Node {
return m return m
} }
// [T, U]
fn parseTParams(ptr[Parser] p) -> []str {
[]str names
expect(p, "[", "'['")
while true {
names.push(expect(p, "ident", "a type parameter name, like T").text)
if !accept(p, ",") {
break
}
}
expect(p, "]", "']' after the type parameters")
return names
}
fn parseStruct(ptr[Parser] p) -> ast.Node { fn parseStruct(ptr[Parser] p) -> ast.Node {
s := node(p, "struct") s := node(p, "struct")
expect(p, "struct", "'struct'") expect(p, "struct", "'struct'")
s.name = expect(p, "ident", "a struct name").text s.name = expect(p, "ident", "a struct name").text
if at(p, "[") {
s.tparams = parseTParams(p)
}
expect(p, "{", "'{'") expect(p, "{", "'{'")
while !at(p, "}") { while !at(p, "}") {
if at(p, "eof") { if at(p, "eof") {
@ -291,6 +378,9 @@ fn parseFn(ptr[Parser] p) -> ast.Node {
f := node(p, "fn") f := node(p, "fn")
expect(p, "fn", "'fn', 'struct' or 'import'") expect(p, "fn", "'fn', 'struct' or 'import'")
f.name = expect(p, "ident", "a function name").text f.name = expect(p, "ident", "a function name").text
if at(p, "[") {
f.tparams = parseTParams(p) // fn max[T](...) or the struct's, in fn Stack[T].push(...)
}
if accept(p, ".") { if accept(p, ".") {
f.recv = f.name f.recv = f.name
f.name = expect(p, "ident", "a method name").text f.name = expect(p, "ident", "a method name").text
@ -648,6 +738,21 @@ fn parsePrimary(ptr[Parser] p) -> ast.Node {
if k == "ident" { if k == "ident" {
next(p) next(p)
e.name = t.text e.name = t.text
if at(p, "[") && p.toks[p.i - 1].kind == "ident" && genericCallAheadFrom(p) {
// max[int](3, 4), Stack[int]()
next(p)
e.ty = ast.Type(kind: "targs")
while true {
e.ty.elem.push(parseType(p))
if !accept(p, ",") {
break
}
}
expect(p, "]", "']' after the type arguments")
e.kind = "call"
parseArgs(p, addr(e))
return e
}
if at(p, "(") && !cur(p).nl { if at(p, "(") && !cur(p).nl {
e.kind = "call" e.kind = "call"
parseArgs(p, addr(e)) parseArgs(p, addr(e))

View file

@ -0,0 +1,15 @@
// expect: (while building bigger[Point])
struct Point {
int x
}
fn bigger[T](T a, T b) -> T {
if a > b {
return a
}
return b
}
fn main() {
print(bigger(Point(1), Point(2)))
}

View file

@ -0,0 +1,10 @@
// expect: can't tell what T is here
fn make[T]() -> []T {
[]T out
return out
}
fn main() {
x := make()
print(x)
}

View file

@ -0,0 +1,9 @@
// expect: 'Stack' is generic, so say which type it holds
struct Stack[T] {
[]T items
}
fn main() {
s := Stack()
print(s)
}

View file

@ -0,0 +1,8 @@
// expect: 'add' is not generic, so it takes no type arguments
fn add(int a, int b) -> int {
return a + b
}
fn main() {
print(add[int](1, 2))
}

View file

@ -0,0 +1,9 @@
// expect: Stack takes 1 type argument(s)
struct Stack[T] {
[]T items
}
fn main() {
Stack[int, str] s
print(s)
}

95
tests/new/generics.fch Normal file
View file

@ -0,0 +1,95 @@
struct Stack[T] {
[]T items
}
fn Stack[T].push(T x) {
self.items.push(x)
}
fn Stack[T].pop() -> T {
return self.items.pop()
}
fn Stack[T].size() -> int {
return self.items.len
}
struct Pair[A, B] {
A first
B second
}
fn Pair[A, B].swapped() -> Pair[B, A] {
return Pair[B, A](self.second, self.first)
}
struct Point {
int x
int y
}
fn max[T](T a, T b) -> T {
if a > b {
return a
}
return b
}
fn first[T]([]T items) -> T {
return items[0]
}
fn count[T]([]T items, T wanted) -> int {
n := 0
for x in items {
if x == wanted {
n += 1
}
}
return n
}
fn show[T](T x) -> str {
return "<" + str(x) + ">"
}
fn convert[T](str s) -> T {
return T(s)
}
fn fill[T](int n, T value) -> []T {
[]T out
for i in 0..n {
out.push(value)
}
return out
}
fn main() {
print(max(3, 9), max(2.5, 1.5), max("apple", "pear"), max('a', 'b'))
print(max[float](1, 2.5))
print(first(["x", "y"]), first([7, 8]), count([1, 2, 1, 3, 1], 1), count(["a", "b"], "b"))
print(show(5), show("hi"), show(Point(1, 2)), show([1, 2]))
print(fill(3, "ab"), fill(2, 1.5), fill[int](2, 7))
Stack[int] s
s.push(10)
s.push(20)
print(s.pop(), s.size(), s)
names := Stack[str]()
names.push("ann")
names.push("bob")
print(names, names.size())
pts := Stack[Point]()
pts.push(Point(1, 2))
print(pts.pop().x)
p := Pair[str, int]("age", 31)
q := p.swapped()
print(p, q, q.first + 1)
nested := Stack[Pair[int, str]]()
nested.push(Pair[int, str](1, "one"))
print(nested)
}

10
tests/new/generics.out Normal file
View file

@ -0,0 +1,10 @@
9 2.5 pear b
2.5
x 7 3 1
<5> <hi> <Point(x: 1, y: 2)> <[1, 2]>
["ab", "ab", "ab"] [1.5, 1.5] [7, 7]
20 1 Stack[int](items: [10])
Stack[str](items: ["ann", "bob"]) 2
1
Pair[str, int](first: "age", second: 31) Pair[int, str](first: 31, second: "age") 32
Stack[Pair[int, str]](items: [Pair[int, str](first: 1, second: "one")])