Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/generated/stdlib.ts

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions compiler/structured.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9940,21 +9940,25 @@ export class Structured {
return fn;
}

/** A name for a function kept as a value: its own, or what holds it where it is written. */
/**
* A name for a function kept as a value: its own, or what holds it where it is written. Read from the file the
* function is written in, which need not be the body's: a handler written in main.ts reaches a library's body.
*/
private functionName(fn: FnValue): string {
const { ts } = this;
const sf = fn.decl.getSourceFile();
if (fn.name) return fn.name;
const p = fn.decl.parent;
if (p && (ts.isVariableDeclaration(p) || ts.isPropertyAssignment(p) || ts.isPropertyDeclaration(p)) && ts.isIdentifier(p.name)) return p.name.text;
if (p && ts.isBinaryExpression(p) && p.right === fn.decl) return p.left.getText(this.body.sf);
if (p && ts.isBinaryExpression(p) && p.right === fn.decl) return p.left.getText(sf);
// `return (x) => x + k`: the function its maker returns.
for (let up: TS.Node | undefined = p; up; up = up.parent) {
if (ts.isReturnStatement(up) || (ts.isArrowFunction(up) && up.body === fn.decl)) continue;
if ((ts.isFunctionDeclaration(up) || ts.isMethodDeclaration(up)) && up.name) return `the function ${up.name.getText(this.body.sf)} returns`;
if ((ts.isFunctionDeclaration(up) || ts.isMethodDeclaration(up)) && up.name) return `the function ${up.name.getText(sf)} returns`;
if (ts.isArrowFunction(up) && ts.isVariableDeclaration(up.parent) && ts.isIdentifier(up.parent.name)) return `the function ${up.parent.name.text} returns`;
if (!ts.isParenthesizedExpression(up) && !ts.isBlock(up) && !ts.isReturnStatement(up)) break;
}
if (p && ts.isCallExpression(p) && p.arguments.includes(fn.decl as TS.Expression)) return `the function handed to ${p.expression.getText(this.body.sf)}`;
if (p && ts.isCallExpression(p) && p.arguments.includes(fn.decl as TS.Expression)) return `the function handed to ${p.expression.getText(sf)}`;
return "the function";
}

Expand Down Expand Up @@ -9982,15 +9986,16 @@ export class Structured {
for (const p of fn.parameters) {
const k = this.valueKind(this.c.checker.getTypeAtLocation(p.name));
if (p.dotDotDotToken || !ts.isIdentifier(p.name) || !k) {
this.c.error(p, `${name} is kept as a value, so it is called through its number, and its parameters are numbers, booleans, texts, units or other functions — ${p.name.getText(this.body.sf)} is not one of those.`);
this.c.error(p, `${name} is kept as a value, so it is called through its number, and its parameters are numbers, booleans, texts, units or other functions — ${p.name.getText(p.getSourceFile())} is not one of those.`);
return null;
}
params.push(k);
}
const returned = this.c.checker.getReturnTypeOfSignature(signature);
const result: ValueKind | "void" = (returned.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined)) !== 0 ? "void" : this.valueKind(returned) ?? "void";
if (result === "void" && (returned.flags & (ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never)) === 0) {
this.c.error(node, `${name} is kept as a value, so it is called through its number, and it returns a number, a boolean, a text, a unit, another function or nothing — not ${this.c.checker.typeToString(returned)}.`);
// (Said at the function itself: where it is kept may be a library's line, far from the code to change.)
this.c.error(fn.decl, `${name} is kept as a value, so it is called through its number, and it returns a number, a boolean, a text, a unit, another function or nothing — not ${this.c.checker.typeToString(returned)}.`);
return null;
}
const target = fn.target as Body;
Expand Down
2 changes: 1 addition & 1 deletion stdlib/signals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* const waveCleared = new Signal<number>();
*
* program(async () => {
* waveCleared.on((wave) => setResources(P1, "add", 100 * wave, "ore"));
* waveCleared.on((wave) => { setResources(P1, "add", 100 * wave, "ore"); }); // braces: a handler gives back no action
* waveCleared.once((wave) => print(`First wave down!`));
* const id = waveCleared.on((wave) => print(`Wave ${wave} cleared`));
* …
Expand Down
4 changes: 4 additions & 0 deletions tests/function-values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,8 @@ describe("what a function kept as a value may do, and not", () => {
expect(messages("let f: () => Promise<void> = async () => { await sleep(frames(1)); }; await f();").join("\n")).toMatch(/async/);
expect(messages("let f = (xs: number[]) => xs.length; let n = 0; n = f([1]);").join("\n")).toMatch(/its parameters are numbers, booleans, texts, units or other functions/);
});
it("give back an action: said at the function, by the name of what it was handed to, also when a library keeps it", () => {
const r = compileScript(ts, { "main.ts": `import { Signal } from "trigscript/signals";\nprogram(() => {\n const cleared = new Signal<number>();\n cleared.on((wave) => setResources(P1, "add", wave, "ore"));\n cleared.emit(1);\n});` }, NAMES, { lib: LIB });
expect(r.diagnostics.map((d) => [d.file, d.line, d.message.slice(0, 54)])).toEqual([["main.ts", 4, "the function handed to cleared.on is kept as a value, "]]);
});
});
Loading