CI: point LLVM's DIA SDK at the installed Visual Studio; semantic index for the language server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-08 21:53:42 +02:00
commit ae363b1fa3
10 changed files with 268 additions and 12 deletions

View file

@ -52,6 +52,17 @@ else()
target_compile_options(finch PRIVATE -Wall -Wextra) target_compile_options(finch PRIVATE -Wall -Wextra)
endif() endif()
# The official Windows LLVM package names the DIA SDK library at the path of the machine it was
# built on (Visual Studio 2019); point it at the Visual Studio installed here.
if(MSVC AND TARGET LLVMDebugInfoPDB AND DEFINED ENV{VSINSTALLDIR})
get_target_property(FINCH_PDB_LIBS LLVMDebugInfoPDB INTERFACE_LINK_LIBRARIES)
if(FINCH_PDB_LIBS)
file(TO_CMAKE_PATH "$ENV{VSINSTALLDIR}DIA SDK/lib/amd64/diaguids.lib" FINCH_DIA_LIB)
list(TRANSFORM FINCH_PDB_LIBS REPLACE ".*diaguids\\.lib$" "${FINCH_DIA_LIB}")
set_target_properties(LLVMDebugInfoPDB PROPERTIES INTERFACE_LINK_LIBRARIES "${FINCH_PDB_LIBS}")
endif()
endif()
# Link the one shared libLLVM when available: libclang uses it too, and two copies # Link the one shared libLLVM when available: libclang uses it too, and two copies
# of LLVM in one process crash on exit. # of LLVM in one process crash on exit.
if(TARGET LLVM) if(TARGET LLVM)

View file

@ -202,6 +202,7 @@ using StmtPtr = std::unique_ptr<Stmt>;
struct BlockStmt : Stmt { struct BlockStmt : Stmt {
std::vector<StmtPtr> body; std::vector<StmtPtr> body;
Pos end; // the closing '}'
explicit BlockStmt(Pos p) : Stmt(StmtKind::Block, p) {} explicit BlockStmt(Pos p) : Stmt(StmtKind::Block, p) {}
}; };
using BlockPtr = std::unique_ptr<BlockStmt>; using BlockPtr = std::unique_ptr<BlockStmt>;
@ -210,6 +211,7 @@ struct VarDeclStmt : Stmt {
bool hasType; // `int x = 5` vs `x := 5` bool hasType; // `int x = 5` vs `x := 5`
Type type; Type type;
std::string name; std::string name;
Pos namePos;
ExprPtr init; // may be null only when hasType ExprPtr init; // may be null only when hasType
VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {} VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {}
}; };
@ -236,12 +238,14 @@ struct WhileStmt : Stmt {
}; };
struct ForStmt : Stmt { struct ForStmt : Stmt {
std::string var; std::string var;
Pos varPos;
ExprPtr from, to; // for var in from..to (to is exclusive) ExprPtr from, to; // for var in from..to (to is exclusive)
BlockPtr body; BlockPtr body;
ForStmt(Pos p) : Stmt(StmtKind::For, p) {} ForStmt(Pos p) : Stmt(StmtKind::For, p) {}
}; };
struct ForEachStmt : Stmt { struct ForEachStmt : Stmt {
std::string var; std::string var;
Pos varPos;
ExprPtr list; // for var in list (an array or a str) ExprPtr list; // for var in list (an array or a str)
BlockPtr body; BlockPtr body;
ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {} ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {}
@ -266,11 +270,13 @@ struct DeferStmt : Stmt { // defer <statement>: runs when the block ends
struct Param { struct Param {
Type type; Type type;
std::string name; std::string name;
Pos pos; Pos pos; // the type
Pos namePos; // the name
}; };
struct FnDecl { struct FnDecl {
Pos pos; Pos pos;
Pos namePos;
std::string name; std::string name;
Type ret; // Void when there is no `-> type` Type ret; // Void when there is no `-> type`
std::vector<Param> params; std::vector<Param> params;
@ -282,10 +288,12 @@ struct Field {
std::string name; std::string name;
ExprPtr init; // default value, may be null ExprPtr init; // default value, may be null
Pos pos; Pos pos;
Pos namePos;
}; };
struct StructDecl { struct StructDecl {
Pos pos; Pos pos;
Pos namePos;
std::string name; std::string name;
std::vector<Field> fields; std::vector<Field> fields;
}; };

69
src/builtins_doc.h Normal file
View file

@ -0,0 +1,69 @@
#pragma once
// One description of every built-in function and method, shared by the language server
// (hover, completion, signature help).
struct BuiltinDoc {
const char *name;
const char *signature;
const char *doc;
};
inline const BuiltinDoc kBuiltins[] = {
{"print", "print(a, b, ...)", "Prints the values separated by spaces, then a new line. Works with every type."},
{"input", "input(str question = \"\") -> str", "Shows the question and reads one line typed by the user, without the newline."},
{"read_file", "read_file(str path) -> str", "The whole file as text. Stops the program if it can't be read."},
{"write_file", "write_file(str path, str text) -> bool", "Writes (replaces) the file. true if it worked."},
{"file_exists", "file_exists(str path) -> bool", "Is there a file at this path?"},
{"delete_file", "delete_file(str path) -> bool", "Deletes the file. true if it worked."},
{"shell", "shell(str command) -> int", "Runs a command in the system's shell and gives back its exit code."},
{"exit", "exit(int code)", "Ends the program right away with this exit code."},
{"addr", "addr(x) -> ptr[T]", "A pointer to a variable, field or element. addr(function) gives C a callback."},
{"new", "new(value) -> ptr[T]", "Puts a copy of the value on the heap. Give it back with free(...)."},
{"free", "free(ptr p)", "Gives back memory from new(...) (or from C), freeing what the value owns."},
{"str", "str(x) -> str", "Turns a number, bool, char, []u8 or C char pointer into text."},
{"int", "int(x) -> int", "Converts to a whole number: cuts off fractions, parses text (stops the program if it isn't a number)."},
{"float", "float(x) -> float", "Converts to a decimal number; parses text."},
{"char", "char(int n) -> char", "The character with this code."},
{"bool", "bool(int n) -> bool", "true for any number but 0."},
{"ptr", "ptr(x) -> ptr", "An untyped pointer: from a typed pointer, or from a number (an address or offset for C)."},
};
inline const BuiltinDoc kArrayMethods[] = {
{"len", ".len -> int", "The number of elements."},
{"ptr", ".ptr -> ptr[T]", "The address of the first element, for C."},
{"push", "push(T x)", "Adds x at the end."},
{"pop", "pop() -> T", "Removes the last element and gives it back."},
{"insert", "insert(int i, T x)", "Puts x at position i, moving the rest."},
{"remove", "remove(int i) -> T", "Removes position i and gives it back."},
{"clear", "clear()", "Removes everything."},
{"resize", "resize(int n)", "Makes the array n long; new elements are zero."},
{"find", "find(T x) -> int", "The index of x, or -1."},
{"contains", "contains(T x) -> bool", "Is x in the array?"},
{"sort", "sort()", "Sorts in place (numbers, chars, str)."},
{"reverse", "reverse()", "Reverses in place."},
{"slice", "slice(int start, int end) -> []T", "A new array with the elements start to end-1."},
{"join", "join(str sep) -> str", "For []str: one text with sep between the parts."},
};
inline const BuiltinDoc kStrMethods[] = {
{"len", ".len -> int", "The number of bytes."},
{"ptr", ".ptr -> ptr[char]", "The address of the text, for C (valid while the str lives)."},
{"sub", "sub(int start, int end) -> str", "The part from start to end-1."},
{"find", "find(str t) -> int", "Where t starts, or -1."},
{"contains", "contains(str t) -> bool", "Does the text contain t?"},
{"starts_with", "starts_with(str t) -> bool", "Does the text start with t?"},
{"ends_with", "ends_with(str t) -> bool", "Does the text end with t?"},
{"split", "split(str sep) -> []str", "The parts between each sep; split(\"\") gives single characters."},
{"trim", "trim() -> str", "Without spaces and newlines at both ends."},
{"upper", "upper() -> str", "With a-z changed to A-Z."},
{"lower", "lower() -> str", "With A-Z changed to a-z."},
{"replace", "replace(str from, str to) -> str", "Every from replaced by to."},
{"repeat", "repeat(int n) -> str", "The text n times."},
{"bytes", "bytes() -> []u8", "The bytes of the text."},
};
inline const char *kKeywords[] = {"fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true",
"false", "null", "import", "link", "struct", "defer"};
inline const char *kTypeNames[] = {"int", "float", "bool", "char", "str", "ptr", "i8", "i16", "i32", "i64",
"u8", "u16", "u32", "u64", "f32", "f64"};

View file

@ -3,6 +3,7 @@
// codegen_builtins.cpp; the C calling convention in abi.cpp. // codegen_builtins.cpp; the C calling convention in abi.cpp.
#include "codegen_impl.h" #include "codegen_impl.h"
#include "builtins_doc.h"
using namespace llvm; using namespace llvm;
@ -81,6 +82,21 @@ void Codegen::declareStructs() {
structStore.push_back(std::move(s)); structStore.push_back(std::move(s));
} }
for (auto &s : structStore) resolveStruct(s.get()); for (auto &s : structStore) resolveStruct(s.get());
if (g_index)
for (auto &s : structStore) {
StructIndex si{s->module, s->name, {}, -1, 0, 0};
if (s->decl) {
si.file = s->decl->namePos.file;
si.line = s->decl->namePos.line;
si.col = s->decl->namePos.col;
note(s->decl->namePos, s->name.size(), structHover(s.get()), s->decl->namePos);
}
for (auto &f : s->fields) {
si.fields.push_back({f.name, f.type.show()});
if (s->decl) note(f.pos, f.name.size(), "```finch\n" + f.type.show() + " " + f.name + "\n```\nfield of `" + s->name + "`", f.pos);
}
g_index->structs.push_back(si);
}
} }
void Codegen::declareFns() { void Codegen::declareFns() {
@ -147,6 +163,12 @@ void Codegen::define(Fn &fn) {
const FnDecl &d = *fn.decl; const FnDecl &d = *fn.decl;
curFn = &fn; curFn = &fn;
curModule = fn.module; curModule = fn.module;
if (g_index) {
FnInfo fi{fn.module, d.name, signature(fn), "", {}, d.namePos.file, d.namePos.line, d.namePos.col, d.body->end.line};
for (size_t i = 0; i < fn.params.size(); i++) fi.params.push_back(fn.params[i].show() + " " + d.params[i].name);
g_index->fns.push_back(fi);
note(d.namePos, d.name.size(), "```finch\n" + signature(fn) + "\n```", d.namePos);
}
Function *f = fn.llvm; Function *f = fn.llvm;
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", f)); b.SetInsertPoint(BasicBlock::Create(ctx, "entry", f));
if (di) { if (di) {
@ -168,8 +190,8 @@ void Codegen::define(Fn &fn) {
const Param &p = d.params[i]; const Param &p = d.params[i];
const FType &t = fn.params[i]; const FType &t = fn.params[i];
arg.setName(p.name); arg.setName(p.name);
if (owning(t) && !fn.borrowParam[i]) addVar(p.pos, p.name, t, copyValue(&arg, t), true, false, i + 1); // changed inside: own a copy if (owning(t) && !fn.borrowParam[i]) addVar(p.namePos, p.name, t, copyValue(&arg, t), true, false, i + 1); // changed inside: own a copy
else addVar(p.pos, p.name, t, &arg, false, false, i + 1); else addVar(p.namePos, p.name, t, &arg, false, false, i + 1);
i++; i++;
} }
blockBody(*d.body); blockBody(*d.body);
@ -256,7 +278,13 @@ Var &Codegen::addVar(Pos p, const std::string &name, const FType &t, Value *init
v.readonly = readonly; v.readonly = readonly;
v.owned = owned && owning(t); v.owned = owned && owning(t);
v.order = ++varOrder; v.order = ++varOrder;
v.pos = p;
if (v.owned) scopes.back().cleanups.push_back({false, name}); if (v.owned) scopes.back().cleanups.push_back({false, name});
if (g_index && name[0] != '$' && curFn) {
const FnDecl &d = *curFn->decl;
g_index->vars.push_back({name, t.show(), p.file, p.line, p.col, d.pos.file, d.pos.line, d.body->end.line});
note(p, name.size(), "```finch\n" + t.show() + " " + name + "\n```", p);
}
if (di && name[0] != '$') { if (di && name[0] != '$') {
DILocalVariable *dv = argNo ? di->createParameterVariable(diFn, name, argNo, diFiles[p.file], p.line, diType(t)) DILocalVariable *dv = argNo ? di->createParameterVariable(diFn, name, argNo, diFiles[p.file], p.line, diType(t))
: di->createAutoVariable(diFn, name, diFiles[p.file], p.line, diType(t)); : di->createAutoVariable(diFn, name, diFiles[p.file], p.line, diType(t));
@ -319,13 +347,13 @@ void Codegen::varDecl(const VarDeclStmt &s) {
Pos ip = s.init->pos; Pos ip = s.init->pos;
if (v.type.kind == FType::Void) failAt(ip.file, ip.line, ip.col, "this gives back nothing, so it can't be stored"); if (v.type.kind == FType::Void) failAt(ip.file, ip.line, ip.col, "this gives back nothing, so it can't be stored");
if (v.type.kind == FType::Null) failAt(ip.file, ip.line, ip.col, "null needs a type, like: ptr[int] " + s.name + " = null"); if (v.type.kind == FType::Null) failAt(ip.file, ip.line, ip.col, "null needs a type, like: ptr[int] " + s.name + " = null");
addVar(p, s.name, v.type, own(v), true); addVar(s.namePos, s.name, v.type, own(v), true);
return; return;
} }
FType t = resolve(s.type, p); FType t = resolve(s.type, p);
if (t.kind == FType::Void) failAt(p.file, p.line, p.col, "a variable can't have the type 'nothing'"); if (t.kind == FType::Void) failAt(p.file, p.line, p.col, "a variable can't have the type 'nothing'");
Value_ v = s.init ? coerce(exprWant(*s.init, t), t, s.init->pos, "'" + s.name + "'") : defaultValue(t, p); Value_ v = s.init ? coerce(exprWant(*s.init, t), t, s.init->pos, "'" + s.name + "'") : defaultValue(t, p);
addVar(p, s.name, t, own(v), true); addVar(s.namePos, s.name, t, own(v), true);
} }
void Codegen::assign(const AssignStmt &s) { void Codegen::assign(const AssignStmt &s) {
@ -431,7 +459,7 @@ void Codegen::forStmt(const ForStmt &s) {
Value *to = coerce(expr(*s.to), FType::I64, s.to->pos, "the end of a range").v; Value *to = coerce(expr(*s.to), FType::I64, s.to->pos, "the end of a range").v;
pushScope(); pushScope();
Var &iv = addVar(s.pos, s.var, FType::I64, from, false, true); Var &iv = addVar(s.varPos, s.var, FType::I64, from, false, true);
iv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed"; iv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed";
Value *i = iv.slot; Value *i = iv.slot;
@ -503,7 +531,7 @@ void Codegen::forEachStmt(const ForEachStmt &s) {
Value *elem = b.CreateLoad(ty(et), elemAddr); Value *elem = b.CreateLoad(ty(et), elemAddr);
loops.push_back({step, end, scopes.size()}); loops.push_back({step, end, scopes.size()});
pushScope(); pushScope();
Var &xv = addVar(s.pos, s.var, et, copyElems ? copyValue(elem, et) : elem, copyElems, true); Var &xv = addVar(s.varPos, s.var, et, copyElems ? copyValue(elem, et) : elem, copyElems, true);
xv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed (to change the list, loop with for i in 0..list.len and use list[i])"; xv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed (to change the list, loop with for i in 0..list.len and use list[i])";
block(*s.body); block(*s.body);
popScope(); popScope();
@ -655,6 +683,8 @@ Value_ Codegen::expr(const Expr &e) {
if (!lookup(v.name)) { if (!lookup(v.name)) {
auto c = cimports.consts.find(v.name); auto c = cimports.consts.find(v.name);
if (c != cimports.consts.end()) { if (c != cimports.consts.end()) {
note(e.pos, v.name.size(), "C constant `" + v.name + " = " +
(c->second.type.isFloat() ? std::to_string(c->second.f) : std::to_string(c->second.i)) + "`");
if (c->second.type.isFloat()) return {ConstantFP::get(b.getDoubleTy(), c->second.f), FType::F64, true}; if (c->second.type.isFloat()) return {ConstantFP::get(b.getDoubleTy(), c->second.f), FType::F64, true};
return {b.getInt64(c->second.i), FType::I64, true}; return {b.getInt64(c->second.i), FType::I64, true};
} }
@ -704,7 +734,10 @@ LRef Codegen::ref(const Expr &e, bool forWrite) {
switch (e.kind) { switch (e.kind) {
case ExprKind::Var: { case ExprKind::Var: {
auto &v = static_cast<const VarExpr &>(e); auto &v = static_cast<const VarExpr &>(e);
if (Var *var = lookup(v.name)) return {true, {var->slot, var->type}, {}}; if (Var *var = lookup(v.name)) {
note(e.pos, v.name.size(), "```finch\n" + var->type.show() + " " + v.name + "\n```", var->pos);
return {true, {var->slot, var->type}, {}};
}
if (cimports.globals.count(v.name)) return {true, {cGlobal(v.name), resolve(cimports.globals.at(v.name).type, e.pos)}, {}}; if (cimports.globals.count(v.name)) return {true, {cGlobal(v.name), resolve(cimports.globals.at(v.name).type, e.pos)}, {}};
return {false, {}, expr(e)}; return {false, {}, expr(e)};
} }
@ -753,6 +786,10 @@ LRef Codegen::member(const MemberExpr &m, bool forWrite) {
} }
if (t.kind == FType::Array || t.kind == FType::Str || t.kind == FType::Fixed) { if (t.kind == FType::Array || t.kind == FType::Str || t.kind == FType::Fixed) {
if (m.field == "len" || m.field == "ptr")
note(Pos{p.line, p.col + 1, p.file}, m.field.size(),
m.field == "len" ? "`.len -> int`: the number of " + std::string(t.kind == FType::Str ? "bytes" : "elements")
: "`.ptr`: the address of the first element, for C");
if (m.field != "len" && m.field != "ptr") if (m.field != "len" && m.field != "ptr")
failAt(p.file, p.line, p.col, (t.kind == FType::Str ? "a str" : "an array") + std::string(" has .len and .ptr, not '.") + m.field + "'"); failAt(p.file, p.line, p.col, (t.kind == FType::Str ? "a str" : "an array") + std::string(" has .len and .ptr, not '.") + m.field + "'");
if (forWrite) failAt(p.file, p.line, p.col, "." + m.field + " can't be changed directly" + (t.kind == FType::Array ? " (use resize, push or pop)" : "")); if (forWrite) failAt(p.file, p.line, p.col, "." + m.field + " can't be changed directly" + (t.kind == FType::Array ? " (use resize, push or pop)" : ""));
@ -786,6 +823,8 @@ LRef Codegen::member(const MemberExpr &m, bool forWrite) {
failAt(p.file, p.line, p.col, "the struct " + t.show() + " has no field '" + m.field + "' (it has: " + names + ")"); failAt(p.file, p.line, p.col, "the struct " + t.show() + " has no field '" + m.field + "' (it has: " + names + ")");
} }
const StructInfo::F &f = s->fields[idx]; const StructInfo::F &f = s->fields[idx];
note(Pos{p.line, p.col + 1, p.file}, m.field.size(),
"```finch\n" + f.type.show() + " " + m.field + "\n```\nfield of `" + s->name + "`", f.pos);
unsigned llvmIdx = f.llvmIndex; unsigned llvmIdx = f.llvmIndex;
if (obj.isPlace) return {true, {b.CreateStructGEP(s->llvm, obj.pl.addr, llvmIdx), f.type}, {}}; if (obj.isPlace) return {true, {b.CreateStructGEP(s->llvm, obj.pl.addr, llvmIdx), f.type}, {}};
// a field of a temporary struct: take it (copying if it owns memory), then drop the rest // a field of a temporary struct: take it (copying if it owns memory), then drop the rest
@ -1086,6 +1125,33 @@ Value_ Codegen::binary(const BinaryExpr &e) {
} }
} }
// ---------- language server index ----------
void Codegen::note(Pos at, size_t len, const std::string &hover, Pos def) {
if (!g_index || at.line <= 0) return;
SymRef r{at.file, at.line, at.col, (int)len, hover};
if (def.line > 0) {
r.defFile = def.file;
r.defLine = def.line;
r.defCol = def.col;
}
g_index->refs.push_back(r);
}
std::string Codegen::signature(const Fn &fn) {
std::string s = "fn " + (fn.module.empty() ? "" : fn.module + ".") + fn.decl->name + "(";
for (size_t i = 0; i < fn.params.size(); i++) s += (i ? ", " : "") + fn.params[i].show() + " " + fn.decl->params[i].name;
s += ")";
if (fn.ret.kind != FType::Void) s += " -> " + fn.ret.show();
return s;
}
std::string Codegen::structHover(StructInfo *s) {
std::string h = "```finch\nstruct " + s->name + " {\n";
for (auto &f : s->fields) h += " " + f.type.show() + " " + f.name + "\n";
return h + "}\n```" + (s->isC ? "\nfrom C" : "");
}
// ---------- debug info ---------- // ---------- debug info ----------
void Codegen::setLoc(Pos p) { void Codegen::setLoc(Pos p) {

View file

@ -1,6 +1,7 @@
// Code generation, part 3: calls, built-in functions, conversions and methods. // Code generation, part 3: calls, built-in functions, conversions and methods.
#include "codegen_impl.h" #include "codegen_impl.h"
#include "builtins_doc.h"
using namespace llvm; using namespace llvm;
@ -161,9 +162,23 @@ Value *Codegen::cGlobal(const std::string &name) {
// ---------- calls ---------- // ---------- calls ----------
static std::string docHover(const BuiltinDoc &d) {
return "```finch\n" + std::string(d.signature) + "\n```\n" + d.doc;
}
static std::string cSignature(const CFunc &f) {
std::string s = f.ret.show() + " " + f.name + "(";
for (size_t i = 0; i < f.params.size(); i++) s += (i ? ", " : "") + f.params[i].show();
if (f.variadic) s += f.params.empty() ? "..." : ", ...";
return s + ")";
}
Value_ Codegen::call(const CallExpr &c) { Value_ Codegen::call(const CallExpr &c) {
const std::string &n = c.callee; const std::string &n = c.callee;
Pos p = c.pos; Pos p = c.pos;
if (g_index)
for (const BuiltinDoc &d : kBuiltins)
if (n == d.name) note(p, n.size(), docHover(d));
auto one = [&](const char *what) -> const Expr & { auto one = [&](const char *what) -> const Expr & {
checkArgs(c.args, c.argNames, 1, p, n); checkArgs(c.args, c.argNames, 1, p, n);
(void)what; (void)what;
@ -266,9 +281,16 @@ Value_ Codegen::call(const CallExpr &c) {
} }
ModuleScope &m = modules[curModule]; ModuleScope &m = modules[curModule];
if (auto it = m.fns.find(n); it != m.fns.end()) return callFinch(it->second, c.args, c.argNames, p, n); if (auto it = m.fns.find(n); it != m.fns.end()) {
if (StructInfo *s = findStruct("", n, false)) return construct(s, c.args, c.argNames, p); note(p, n.size(), "```finch\n" + signature(it->second) + "\n```", it->second.decl->namePos);
return callFinch(it->second, c.args, c.argNames, p, n);
}
if (StructInfo *s = findStruct("", n, false)) {
note(p, n.size(), structHover(s), s->decl ? s->decl->namePos : Pos{});
return construct(s, c.args, c.argNames, p);
}
if (auto cf = cimports.fns.find(n); cf != cimports.fns.end()) { if (auto cf = cimports.fns.find(n); cf != cimports.fns.end()) {
note(p, n.size(), "C function from `" + cf->second.header + "`\n```c\n" + cSignature(cf->second) + "\n```");
checkArgs(c.args, c.argNames, cf->second.params.size(), p, n, cf->second.variadic); checkArgs(c.args, c.argNames, cf->second.params.size(), p, n, cf->second.variadic);
return callC(cf->second, c.args, p); return callC(cf->second, c.args, p);
} }
@ -468,10 +490,16 @@ Value_ Codegen::method(const MethodExpr &m) {
if (!lookup(mn) && modules[curModule].imports.count(mn)) { if (!lookup(mn) && modules[curModule].imports.count(mn)) {
auto mi = modules.find(mn); auto mi = modules.find(mn);
if (mi == modules.end()) failAt(p.file, p.line, p.col, "the module '" + mn + "' was not found"); if (mi == modules.end()) failAt(p.file, p.line, p.col, "the module '" + mn + "' was not found");
if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end()) note(m.obj->pos, mn.size(), "module `" + mn + "`");
Pos namePos{p.line, p.col + 1, p.file};
if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end()) {
note(namePos, m.name.size(), "```finch\n" + signature(it->second) + "\n```", it->second.decl->namePos);
return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name); return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name);
if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end()) }
if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end()) {
note(namePos, m.name.size(), structHover(it->second), it->second->decl->namePos);
return construct(it->second, m.args, m.argNames, p); return construct(it->second, m.args, m.argNames, p);
}
failAt(p.file, p.line, p.col, "the module '" + mn + "' has no function or struct named '" + m.name + "'"); failAt(p.file, p.line, p.col, "the module '" + mn + "' has no function or struct named '" + m.name + "'");
} }
if (!lookup(mn) && !cimports.globals.count(mn) && !cimports.consts.count(mn)) if (!lookup(mn) && !cimports.globals.count(mn) && !cimports.consts.count(mn))
@ -481,6 +509,14 @@ Value_ Codegen::method(const MethodExpr &m) {
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"}; static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
LRef r = ref(*m.obj, changing.count(m.name) > 0); LRef r = ref(*m.obj, changing.count(m.name) > 0);
FType t = r.isPlace ? r.pl.type : r.val.type; FType t = r.isPlace ? r.pl.type : r.val.type;
if (g_index && (t.kind == FType::Array || t.kind == FType::Str)) {
auto look = [&](const BuiltinDoc *first, const BuiltinDoc *last) {
for (; first != last; ++first)
if (m.name == first->name) note(Pos{p.line, p.col + 1, p.file}, m.name.size(), docHover(*first));
};
if (t.kind == FType::Str) look(std::begin(kStrMethods), std::end(kStrMethods));
else look(std::begin(kArrayMethods), std::end(kArrayMethods));
}
if (t.kind == FType::Array) { if (t.kind == FType::Array) {
Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : r.val; Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : r.val;
if (!r.isPlace) { if (!r.isPlace) {

View file

@ -3,6 +3,7 @@
#include "codegen.h" #include "codegen.h"
#include "error.h" #include "error.h"
#include "index.h"
#include <llvm/IR/Constants.h> #include <llvm/IR/Constants.h>
#include <llvm/IR/DIBuilder.h> #include <llvm/IR/DIBuilder.h>
@ -29,6 +30,7 @@ struct StructInfo {
const Expr *init = nullptr; // default value const Expr *init = nullptr; // default value
bool hidden = false; // C union/bitfield padding: has no name in Finch bool hidden = false; // C union/bitfield padding: has no name in Finch
unsigned llvmIndex = 0; // C structs carry explicit padding between fields unsigned llvmIndex = 0; // C structs carry explicit padding between fields
Pos pos; // Finch structs: where the field is declared
}; };
std::vector<F> fields; std::vector<F> fields;
llvm::StructType *llvm = nullptr; llvm::StructType *llvm = nullptr;
@ -55,6 +57,7 @@ struct Var {
bool owned = false; // dropped when its scope ends bool owned = false; // dropped when its scope ends
int order = 0; // declaration order, for defer int order = 0; // declaration order, for defer
bool moved = false; // returned out of the function: not dropped on that path bool moved = false; // returned out of the function: not dropped on that path
Pos pos; // where it was declared
}; };
struct Cleanup { struct Cleanup {
@ -259,6 +262,11 @@ public:
Value_ emitCCall(const CFunc &f, llvm::Function *fn, std::vector<Value_> args, Pos p); Value_ emitCCall(const CFunc &f, llvm::Function *fn, std::vector<Value_> args, Pos p);
llvm::Function *cThunk(Fn &fn, Pos p); llvm::Function *cThunk(Fn &fn, Pos p);
// ---------- language server index ----------
void note(Pos at, size_t len, const std::string &hover, Pos def = Pos{});
std::string signature(const Fn &fn);
std::string structHover(StructInfo *s);
// ---------- debug info ---------- // ---------- debug info ----------
void setLoc(Pos p); void setLoc(Pos p);
llvm::DIType *diType(const FType &t); llvm::DIType *diType(const FType &t);

View file

@ -99,6 +99,7 @@ void Codegen::resolveStruct(StructInfo *s) {
nf.type = resolveT(f.type, f.pos, true); nf.type = resolveT(f.type, f.pos, true);
if (nf.type.kind == FType::Void) failAt(f.pos.file, f.pos.line, f.pos.col, "a field can't have the type 'nothing'"); if (nf.type.kind == FType::Void) failAt(f.pos.file, f.pos.line, f.pos.col, "a field can't have the type 'nothing'");
nf.init = f.init.get(); nf.init = f.init.get();
nf.pos = f.namePos;
nf.llvmIndex = elems.size(); nf.llvmIndex = elems.size();
elems.push_back(ty(nf.type)); elems.push_back(ty(nf.type));
s->owning |= owning(nf.type); s->owning |= owning(nf.type);

View file

@ -13,8 +13,16 @@ struct SourceFile {
inline std::vector<SourceFile> g_files; inline std::vector<SourceFile> g_files;
inline int g_curFile = 0; // the file the lexer/parser is working on inline int g_curFile = 0; // the file the lexer/parser is working on
// The language server needs errors as values: it shows them in the editor and keeps running.
struct FinchError {
int file, line, col;
std::string msg;
};
inline bool g_throwErrors = false;
// Finch stops at the first error and shows exactly where it is. // Finch stops at the first error and shows exactly where it is.
[[noreturn]] inline void failAt(int file, int line, int col, const std::string &msg) { [[noreturn]] inline void failAt(int file, int line, int col, const std::string &msg) {
if (g_throwErrors) throw FinchError{file, line, col, msg};
const SourceFile &f = g_files.at(file); const SourceFile &f = g_files.at(file);
std::fprintf(stderr, "%s:%d:%d: error: %s\n", f.path.c_str(), line, col, msg.c_str()); std::fprintf(stderr, "%s:%d:%d: error: %s\n", f.path.c_str(), line, col, msg.c_str());

39
src/index.h Normal file
View file

@ -0,0 +1,39 @@
#pragma once
// What the compiler learns about a program while checking it, for the language server:
// what each name is, its type, and where it was defined.
#include <string>
#include <vector>
struct SymRef { // a name in the source
int file, line, col, len;
std::string hover; // markdown shown on mouse-over
int defFile = -1, defLine = 0, defCol = 0;
};
struct VarInfo { // a variable or parameter, for completion
std::string name, type;
int file, line, col;
int fnFile, fnLine, fnEnd; // the function it lives in
};
struct FnInfo {
std::string module, name, signature, doc;
std::vector<std::string> params;
int file, line, col, endLine;
};
struct StructIndex {
std::string module, name;
std::vector<std::pair<std::string, std::string>> fields; // name, type
int file, line, col;
};
struct SemIndex {
std::vector<SymRef> refs;
std::vector<VarInfo> vars;
std::vector<FnInfo> fns;
std::vector<StructIndex> structs;
};
inline SemIndex *g_index = nullptr; // set by the language server; null when compiling

View file

@ -147,6 +147,7 @@ private:
StructDecl s; StructDecl s;
s.pos = pos(); s.pos = pos();
expect(Tok::Struct); expect(Tok::Struct);
s.namePos = pos();
s.name = expect(Tok::Ident, "a struct name").text; s.name = expect(Tok::Ident, "a struct name").text;
if (isTypeName(s.name)) fail(s.pos.line, s.pos.col, "'" + s.name + "' is a built-in type name, pick another"); if (isTypeName(s.name)) fail(s.pos.line, s.pos.col, "'" + s.name + "' is a built-in type name, pick another");
Pos open = pos(); Pos open = pos();
@ -157,6 +158,7 @@ private:
f.pos = pos(); f.pos = pos();
if (!atDeclaration()) unexpected("(a field looks like: int x or str name = \"default\")"); if (!atDeclaration()) unexpected("(a field looks like: int x or str name = \"default\")");
f.type = type(); f.type = type();
f.namePos = pos();
f.name = expect(Tok::Ident, "a field name").text; f.name = expect(Tok::Ident, "a field name").text;
for (const Field &other : s.fields) for (const Field &other : s.fields)
if (other.name == f.name) fail(f.pos.line, f.pos.col, "the field '" + f.name + "' is listed twice"); if (other.name == f.name) fail(f.pos.line, f.pos.col, "the field '" + f.name + "' is listed twice");
@ -175,6 +177,7 @@ private:
fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }"); fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }");
expect(Tok::Fn, "'fn', 'struct', 'import' or 'link' (every function starts with fn)"); expect(Tok::Fn, "'fn', 'struct', 'import' or 'link' (every function starts with fn)");
fn.namePos = pos();
fn.name = expect(Tok::Ident, "a function name").text; fn.name = expect(Tok::Ident, "a function name").text;
expect(Tok::LParen); expect(Tok::LParen);
parenDepth++; parenDepth++;
@ -183,6 +186,7 @@ private:
Param p; Param p;
p.pos = pos(); p.pos = pos();
p.type = type(); p.type = type();
p.namePos = pos();
p.name = expect(Tok::Ident, "a parameter name").text; p.name = expect(Tok::Ident, "a parameter name").text;
fn.params.push_back(p); fn.params.push_back(p);
} while (accept(Tok::Comma)); } while (accept(Tok::Comma));
@ -207,6 +211,7 @@ private:
endOfStatement(); endOfStatement();
} }
parenDepth = saved; parenDepth = saved;
b->end = pos();
next(); next();
return b; return b;
} }
@ -229,12 +234,14 @@ private:
} }
case Tok::For: { case Tok::For: {
next(); next();
Pos varPos = pos();
std::string var = expect(Tok::Ident, "a loop variable name").text; std::string var = expect(Tok::Ident, "a loop variable name").text;
expect(Tok::In); expect(Tok::In);
ExprPtr first = expr(); ExprPtr first = expr();
if (accept(Tok::DotDot)) { // for i in 0..10 if (accept(Tok::DotDot)) { // for i in 0..10
auto s = std::make_unique<ForStmt>(p); auto s = std::make_unique<ForStmt>(p);
s->var = var; s->var = var;
s->varPos = varPos;
s->from = std::move(first); s->from = std::move(first);
s->to = expr(); s->to = expr();
s->body = block(); s->body = block();
@ -242,6 +249,7 @@ private:
} }
auto s = std::make_unique<ForEachStmt>(p); // for x in list auto s = std::make_unique<ForEachStmt>(p); // for x in list
s->var = var; s->var = var;
s->varPos = varPos;
s->list = std::move(first); s->list = std::move(first);
s->body = block(); s->body = block();
return s; return s;
@ -272,6 +280,7 @@ private:
auto s = std::make_unique<VarDeclStmt>(p); auto s = std::make_unique<VarDeclStmt>(p);
s->hasType = true; s->hasType = true;
s->type = type(); s->type = type();
s->namePos = pos();
s->name = expect(Tok::Ident, "a variable name").text; s->name = expect(Tok::Ident, "a variable name").text;
if (accept(Tok::Assign)) s->init = expr(); if (accept(Tok::Assign)) s->init = expr();
return s; return s;
@ -281,6 +290,7 @@ private:
if (at(Tok::Ident) && peekTok().kind == Tok::Declare) { if (at(Tok::Ident) && peekTok().kind == Tok::Declare) {
auto s = std::make_unique<VarDeclStmt>(p); auto s = std::make_unique<VarDeclStmt>(p);
s->hasType = false; s->hasType = false;
s->namePos = pos();
s->name = next().text; s->name = next().text;
next(); next();
s->init = expr(); s->init = expr();