mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
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:
parent
609ceffe58
commit
04eb0cce55
10 changed files with 575 additions and 7 deletions
|
|
@ -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 }
|
||||||
|
|
|
||||||
320
boot/gen.fch
320
boot/gen.fch
|
|
@ -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]
|
||||||
|
|
|
||||||
105
boot/parser.fch
105
boot/parser.fch
|
|
@ -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))
|
||||||
|
|
|
||||||
15
tests/new/fail/generic_body_error.fch
Normal file
15
tests/new/fail/generic_body_error.fch
Normal 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)))
|
||||||
|
}
|
||||||
10
tests/new/fail/generic_cant_infer.fch
Normal file
10
tests/new/fail/generic_cant_infer.fch
Normal 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)
|
||||||
|
}
|
||||||
9
tests/new/fail/generic_needs_type.fch
Normal file
9
tests/new/fail/generic_needs_type.fch
Normal 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)
|
||||||
|
}
|
||||||
8
tests/new/fail/generic_not_generic.fch
Normal file
8
tests/new/fail/generic_not_generic.fch
Normal 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))
|
||||||
|
}
|
||||||
9
tests/new/fail/generic_wrong_count.fch
Normal file
9
tests/new/fail/generic_wrong_count.fch
Normal 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
95
tests/new/generics.fch
Normal 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
10
tests/new/generics.out
Normal 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")])
|
||||||
Loading…
Reference in a new issue