Skip to content

Commit

Permalink
refactor: typed errors (#1210)
Browse files Browse the repository at this point in the history
* refactor: split parser errors

* refactor: improve error messages and update snapshots
  • Loading branch information
verytactical authored Dec 18, 2024
1 parent 45a006b commit b26e2e1
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 234 deletions.
2 changes: 2 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"ignore": [
"src/grammar/ast.ts",
"src/prettyPrinter.ts",
"src/error/display-to-json.ts",
"src/grammar/src-info.ts",
".github/workflows/tact*.yml"
],
"ignoreDependencies": ["@tact-lang/ton-abi"]
Expand Down
67 changes: 67 additions & 0 deletions src/error/display-to-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* Render error message to JSON for tests
*/

import { throwInternalCompilerError } from "../errors";
import { SrcInfo } from "../grammar";
import { srcInfoEqual } from "../grammar/src-info";
import { ErrorDisplay } from "./display";

export type ErrorJson = ErrorSub | ErrorText | ErrorLink | ErrorAt;

export type ErrorText = { kind: "text"; text: string };
export type ErrorSub = { kind: "sub"; parts: string[]; subst: ErrorJson[] };
export type ErrorLink = { kind: "link"; text: string; loc: SrcInfo };
export type ErrorAt = { kind: "at"; body: ErrorJson; loc: SrcInfo };

export const errorJsonEqual = (left: ErrorJson, right: ErrorJson): boolean => {
switch (left.kind) {
case "link": {
return (
left.kind === right.kind &&
left.text === right.text &&
srcInfoEqual(left.loc, right.loc)
);
}
case "at": {
return (
left.kind === right.kind &&
errorJsonEqual(left.body, right.body) &&
srcInfoEqual(left.loc, right.loc)
);
}
case "text": {
return left.kind === right.kind && left.text === right.text;
}
case "sub": {
if (left.kind !== right.kind) {
return false;
}
if (left.parts.length !== right.parts.length) {
return false;
}
if (left.parts.some((part, index) => part != right.parts[index])) {
return false;
}
if (left.subst.length !== right.subst.length) {
return false;
}
return left.subst.every((leftChild, index) => {
const rightChild = right.subst[index];
if (typeof rightChild === "undefined") {
throwInternalCompilerError(
"Impossible: by this moment array lengths must match",
);
}
return errorJsonEqual(leftChild, rightChild);
});
}
}
};

export const displayToJson: ErrorDisplay<ErrorJson> = {
text: (text) => ({ kind: "text", text }),
sub: (parts, ...subst) => ({ kind: "sub", parts: [...parts], subst }),
link: (text, loc) => ({ kind: "link", text, loc }),
at: (loc, body) => ({ kind: "at", body, loc }),
};
24 changes: 24 additions & 0 deletions src/error/display-to-string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Render error message to string for compiler CLI
*/

import { ErrorDisplay } from "./display";
import { locationStr } from "../errors";

export const displayToString: ErrorDisplay<string> = {
text: (text) => text,
sub: (parts, ...subst) => {
const [head, ...tail] = parts;
if (!head) {
return "";
}
return tail.reduce((acc, part, index) => {
const sub = subst[index];
return acc + sub + part;
}, head);
},
link: (text, _loc) => text,
at: (loc, body) => {
return `${locationStr(loc)}${body}\n${loc.interval.getLineAndColumnMessage()}`;
},
};
19 changes: 19 additions & 0 deletions src/error/display.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Describes DSL for displaying errors
*/

import { SrcInfo } from "../grammar";

export interface ErrorDisplay<T> {
// Specify main error location
at: (loc: SrcInfo, body: T) => T;

// Regular string
text: (text: string) => T;

// Text with substitutions
sub: (text: TemplateStringsArray, ...subst: T[]) => T;

// Reference some code location
link: (text: string, loc: SrcInfo) => T;
}
45 changes: 4 additions & 41 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { MatchResult } from "ohm-js";
import path from "path";
import { cwd } from "process";
import { AstFuncId, AstId, AstTypeId } from "./grammar/ast";
import { ItemOrigin, SrcInfo } from "./grammar";
import { getSrcInfoFromOhm } from "./grammar/src-info";
import { SrcInfo } from "./grammar";

export class TactError extends Error {
readonly loc?: SrcInfo;
Expand All @@ -13,19 +11,8 @@ export class TactError extends Error {
}
}

export class TactParseError extends TactError {
constructor(message: string, loc: SrcInfo) {
super(message, loc);
}
}

export class TactSyntaxError extends TactError {
constructor(message: string, loc: SrcInfo) {
super(message, loc);
}
}

/// This will be split at least into two categories: typechecking and codegen errors
// Any regular compilation error shown to user:
// parsing, typechecking, code generation
export class TactCompilationError extends TactError {
constructor(message: string, loc?: SrcInfo) {
super(message, loc);
Expand All @@ -46,7 +33,7 @@ export class TactConstEvalError extends TactCompilationError {
}
}

function locationStr(sourceInfo: SrcInfo): string {
export function locationStr(sourceInfo: SrcInfo): string {
if (sourceInfo.file) {
const loc = sourceInfo.interval.getLineAndColumn() as {
lineNum: number;
Expand All @@ -59,28 +46,6 @@ function locationStr(sourceInfo: SrcInfo): string {
}
}

export function throwParseError(
matchResult: MatchResult,
path: string,
origin: ItemOrigin,
): never {
const interval = matchResult.getInterval();
const source = getSrcInfoFromOhm(interval, path, origin);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const message = `Parse error: expected ${(matchResult as any).getExpectedText()}\n`;
throw new TactParseError(
`${locationStr(source)}${message}\n${interval.getLineAndColumnMessage()}`,
source,
);
}

export function throwSyntaxError(message: string, source: SrcInfo): never {
throw new TactSyntaxError(
`Syntax error: ${locationStr(source)}${message}\n${source.interval.getLineAndColumnMessage()}`,
source,
);
}

export function throwCompilationError(
message: string,
source?: SrcInfo,
Expand Down Expand Up @@ -128,8 +93,6 @@ export function idTextErr(

export type TactErrorCollection =
| Error
| TactParseError
| TactSyntaxError
| TactCompilationError
| TactInternalCompilerError
| TactConstEvalError;
Loading

0 comments on commit b26e2e1

Please sign in to comment.