mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-16 17:35:02 +01:00
Detect unused variables
This commit is contained in:
parent
f164aad631
commit
9270f52e6b
33 changed files with 340 additions and 63 deletions
15
src/ast.ts
15
src/ast.ts
|
|
@ -134,9 +134,8 @@ export type ItemKindFunction<P extends Phase> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FunctionArg<P extends Phase> = {
|
export type FunctionArg<P extends Phase> = {
|
||||||
name: string;
|
ident: Ident;
|
||||||
type: Type<P>;
|
type: Type<P>;
|
||||||
span: Span;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ItemKindType<P extends Phase> = {
|
export type ItemKindType<P extends Phase> = {
|
||||||
|
|
@ -449,6 +448,8 @@ export type Resolution =
|
||||||
* ```
|
* ```
|
||||||
* When traversing resolutions, a stack of locals has to be kept.
|
* When traversing resolutions, a stack of locals has to be kept.
|
||||||
* It's similar to a De Bruijn index.
|
* It's similar to a De Bruijn index.
|
||||||
|
*
|
||||||
|
* You generally want to index the stack as stack[stack.len - 1 - res.idx].
|
||||||
*/
|
*/
|
||||||
index: number;
|
index: number;
|
||||||
}
|
}
|
||||||
|
|
@ -577,10 +578,9 @@ export function superFoldItem<From extends Phase, To extends Phase>(
|
||||||
): Item<To> {
|
): Item<To> {
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "function": {
|
case "function": {
|
||||||
const args = item.params.map(({ name, type, span }) => ({
|
const args = item.params.map(({ ident, type }) => ({
|
||||||
name,
|
ident,
|
||||||
type: folder.type(type),
|
type: folder.type(type),
|
||||||
span,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -620,10 +620,9 @@ export function superFoldItem<From extends Phase, To extends Phase>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "import": {
|
case "import": {
|
||||||
const args = item.params.map(({ name, type, span }) => ({
|
const args = item.params.map(({ ident, type }) => ({
|
||||||
name,
|
ident,
|
||||||
type: folder.type(type),
|
type: folder.type(type),
|
||||||
span,
|
|
||||||
}));
|
}));
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ export type Options = {
|
||||||
packageName: string;
|
packageName: string;
|
||||||
debug: Set<string>;
|
debug: Set<string>;
|
||||||
noOutput: boolean;
|
noOutput: boolean;
|
||||||
|
noStd: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parseArgs(hardcodedInput: string): Options {
|
export function parseArgs(hardcodedInput: string): Options {
|
||||||
|
|
@ -89,6 +90,7 @@ export function parseArgs(hardcodedInput: string): Options {
|
||||||
let packageName: string;
|
let packageName: string;
|
||||||
let debug = new Set<string>();
|
let debug = new Set<string>();
|
||||||
let noOutput = false;
|
let noOutput = false;
|
||||||
|
let noStd = false;
|
||||||
|
|
||||||
if (process.argv.length > 2) {
|
if (process.argv.length > 2) {
|
||||||
filename = process.argv[2];
|
filename = process.argv[2];
|
||||||
|
|
@ -117,6 +119,9 @@ export function parseArgs(hardcodedInput: string): Options {
|
||||||
if (process.argv.some((arg) => arg === "--no-output")) {
|
if (process.argv.some((arg) => arg === "--no-output")) {
|
||||||
noOutput = true;
|
noOutput = true;
|
||||||
}
|
}
|
||||||
|
if (process.argv.some((arg) => arg === "--no-std")) {
|
||||||
|
noStd = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
filename = "<hardcoded>";
|
filename = "<hardcoded>";
|
||||||
input = hardcodedInput;
|
input = hardcodedInput;
|
||||||
|
|
@ -136,5 +141,6 @@ export function parseArgs(hardcodedInput: string): Options {
|
||||||
packageName,
|
packageName,
|
||||||
debug,
|
debug,
|
||||||
noOutput,
|
noOutput,
|
||||||
|
noStd,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
18
src/error.ts
18
src/error.ts
|
|
@ -44,12 +44,18 @@ export class ErrorHandler {
|
||||||
private emitter = (msg: string) => globalThis.console.error(msg),
|
private emitter = (msg: string) => globalThis.console.error(msg),
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public emit(err: CompilerError): ErrorEmitted {
|
public emitError(err: CompilerError): ErrorEmitted {
|
||||||
renderError(this.emitter, err);
|
renderDiagnostic(this.emitter, err, (msg) => chalk.red(`error: ${msg}`));
|
||||||
this.errors.push(err);
|
this.errors.push(err);
|
||||||
return ERROR_EMITTED;
|
return ERROR_EMITTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public warn(err: CompilerError): void {
|
||||||
|
renderDiagnostic(this.emitter, err, (msg) =>
|
||||||
|
chalk.yellow(`warning: ${msg}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public hasErrors(): boolean {
|
public hasErrors(): boolean {
|
||||||
return this.errors.length > 0;
|
return this.errors.length > 0;
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +78,11 @@ export class CompilerError {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const console = {};
|
const console = {};
|
||||||
|
|
||||||
function renderError(emitter: Emitter, e: CompilerError) {
|
function renderDiagnostic(
|
||||||
|
emitter: Emitter,
|
||||||
|
e: CompilerError,
|
||||||
|
render_msg: (msg: string) => string,
|
||||||
|
) {
|
||||||
const { span } = e;
|
const { span } = e;
|
||||||
const { content } = span.file;
|
const { content } = span.file;
|
||||||
|
|
||||||
|
|
@ -88,7 +98,7 @@ function renderError(emitter: Emitter, e: CompilerError) {
|
||||||
}
|
}
|
||||||
const lineIdx = lineSpans.indexOf(line);
|
const lineIdx = lineSpans.indexOf(line);
|
||||||
const lineNo = lineIdx + 1;
|
const lineNo = lineIdx + 1;
|
||||||
emitter(chalk.red(`error: ${e.msg}`));
|
emitter(render_msg(e.msg));
|
||||||
emitter(` --> ${span.file.path ?? "<unknown>"}:${lineNo}`);
|
emitter(` --> ${span.file.path ?? "<unknown>"}:${lineNo}`);
|
||||||
|
|
||||||
emitter(`${lineNo} | ${spanToSnippet(content, line)}`);
|
emitter(`${lineNo} | ${spanToSnippet(content, line)}`);
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ function main() {
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
if (packageName !== "std") {
|
if (packageName !== "std" && !opts.noStd) {
|
||||||
gcx.pkgLoader(gcx, "std", Span.startOfFile(file));
|
gcx.pkgLoader(gcx, "std", Span.startOfFile(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ export function tokenize(handler: ErrorHandler, file: LoadedFile): LexerResult {
|
||||||
return { ok: true, tokens: tokenizeInner(file) };
|
return { ok: true, tokens: tokenizeInner(file) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof LexerError) {
|
if (e instanceof LexerError) {
|
||||||
const err: ErrorEmitted = handler.emit(e.inner);
|
const err: ErrorEmitted = handler.emitError(e.inner);
|
||||||
return { ok: false, err };
|
return { ok: false, err };
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ export const loadPkg: PkgLoader = (
|
||||||
return dummyErrorPkg(
|
return dummyErrorPkg(
|
||||||
pkgId,
|
pkgId,
|
||||||
name,
|
name,
|
||||||
gcx.error.emit(
|
gcx.error.emitError(
|
||||||
new CompilerError(`cycle detected loading extern module ${name}`, span),
|
new CompilerError(`cycle detected loading extern module ${name}`, span),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -118,7 +118,7 @@ export const loadPkg: PkgLoader = (
|
||||||
|
|
||||||
const file = loadModuleFile(".", name, span);
|
const file = loadModuleFile(".", name, span);
|
||||||
if (!file.ok) {
|
if (!file.ok) {
|
||||||
return dummyErrorPkg(pkgId, name, gcx.error.emit(file.err));
|
return dummyErrorPkg(pkgId, name, gcx.error.emitError(file.err));
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokens = tokenize(gcx.error, file.value);
|
const tokens = tokenize(gcx.error, file.value);
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ function parseItem(t: State): [State, Item<Parsed>] {
|
||||||
[t] = expectNext(t, ")");
|
[t] = expectNext(t, ")");
|
||||||
} else {
|
} else {
|
||||||
if (name.span.file.path === undefined) {
|
if (name.span.file.path === undefined) {
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`no known source file for statement, cannot load file relative to it`,
|
`no known source file for statement, cannot load file relative to it`,
|
||||||
name.span,
|
name.span,
|
||||||
|
|
@ -245,7 +245,7 @@ function parseItem(t: State): [State, Item<Parsed>] {
|
||||||
const file = loadModuleFile(name.span.file.path, name.ident, name.span);
|
const file = loadModuleFile(name.span.file.path, name.ident, name.span);
|
||||||
|
|
||||||
if (!file.ok) {
|
if (!file.ok) {
|
||||||
t.gcx.error.emit(file.err);
|
t.gcx.error.emitError(file.err);
|
||||||
contents = [];
|
contents = [];
|
||||||
} else {
|
} else {
|
||||||
const tokens = tokenize(t.gcx.error, file.value);
|
const tokens = tokenize(t.gcx.error, file.value);
|
||||||
|
|
@ -311,15 +311,19 @@ function parseFunctionSig(t: State): [State, FunctionSig] {
|
||||||
[t] = expectNext(t, "(");
|
[t] = expectNext(t, "(");
|
||||||
|
|
||||||
let params: FunctionArg<Parsed>[];
|
let params: FunctionArg<Parsed>[];
|
||||||
[t, params] = parseCommaSeparatedList(t, ")", (t) => {
|
[t, params] = parseCommaSeparatedList(
|
||||||
let name;
|
t,
|
||||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
")",
|
||||||
[t] = expectNext(t, ":");
|
(t): [State, FunctionArg<Parsed>] => {
|
||||||
let type;
|
let name;
|
||||||
[t, type] = parseType(t);
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
[t] = expectNext(t, ":");
|
||||||
|
let type;
|
||||||
|
[t, type] = parseType(t);
|
||||||
|
|
||||||
return [t, { name: name.ident, type, span: name.span }];
|
return [t, { ident: { name: name.ident, span: name.span }, type }];
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let colon;
|
let colon;
|
||||||
let returnType = undefined;
|
let returnType = undefined;
|
||||||
|
|
@ -732,7 +736,7 @@ function parseType(t: State): [State, Type<Parsed>] {
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new FatalParseError(
|
throw new FatalParseError(
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`unexpected token: \`${tok.kind}\`, expected type`,
|
`unexpected token: \`${tok.kind}\`, expected type`,
|
||||||
span,
|
span,
|
||||||
|
|
@ -799,7 +803,7 @@ function expectNext<T extends BaseToken>(
|
||||||
[t, tok] = maybeNextT(t);
|
[t, tok] = maybeNextT(t);
|
||||||
if (!tok) {
|
if (!tok) {
|
||||||
throw new FatalParseError(
|
throw new FatalParseError(
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`expected \`${kind}\`, found end of file`,
|
`expected \`${kind}\`, found end of file`,
|
||||||
Span.eof(t.file),
|
Span.eof(t.file),
|
||||||
|
|
@ -809,7 +813,7 @@ function expectNext<T extends BaseToken>(
|
||||||
}
|
}
|
||||||
if (tok.kind !== kind) {
|
if (tok.kind !== kind) {
|
||||||
throw new FatalParseError(
|
throw new FatalParseError(
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`expected \`${kind}\`, found \`${tok.kind}\``,
|
`expected \`${kind}\`, found \`${tok.kind}\``,
|
||||||
tok.span,
|
tok.span,
|
||||||
|
|
@ -824,7 +828,7 @@ function next(t: State): [State, Token] {
|
||||||
const [rest, next] = maybeNextT(t);
|
const [rest, next] = maybeNextT(t);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
throw new FatalParseError(
|
throw new FatalParseError(
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError("unexpected end of file", Span.eof(t.file)),
|
new CompilerError("unexpected end of file", Span.eof(t.file)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -841,7 +845,7 @@ function maybeNextT(t: State): [State, Token | undefined] {
|
||||||
|
|
||||||
function unexpectedToken(t: ParseState, token: Token, expected: string): never {
|
function unexpectedToken(t: ParseState, token: Token, expected: string): never {
|
||||||
throw new FatalParseError(
|
throw new FatalParseError(
|
||||||
t.gcx.error.emit(
|
t.gcx.error.emitError(
|
||||||
new CompilerError(`unexpected token, expected ${expected}`, token.span),
|
new CompilerError(`unexpected token, expected ${expected}`, token.span),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -875,7 +879,7 @@ function validateAst(ast: Pkg<Built>, gcx: GlobalContext) {
|
||||||
});
|
});
|
||||||
return expr;
|
return expr;
|
||||||
} else if (expr.kind === "let") {
|
} else if (expr.kind === "let") {
|
||||||
gcx.error.emit(
|
gcx.error.emitError(
|
||||||
new CompilerError("let is only allowed in blocks", expr.span),
|
new CompilerError("let is only allowed in blocks", expr.span),
|
||||||
);
|
);
|
||||||
return superFoldExpr(expr, this);
|
return superFoldExpr(expr, this);
|
||||||
|
|
@ -886,7 +890,7 @@ function validateAst(ast: Pkg<Built>, gcx: GlobalContext) {
|
||||||
const innerClass = binaryExprPrecedenceClass(inner.binaryKind);
|
const innerClass = binaryExprPrecedenceClass(inner.binaryKind);
|
||||||
|
|
||||||
if (ourClass !== innerClass) {
|
if (ourClass !== innerClass) {
|
||||||
gcx.error.emit(
|
gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`mixing operators without parentheses is not allowed. ${side} is ${inner.binaryKind}, which is different from ${expr.binaryKind}`,
|
`mixing operators without parentheses is not allowed. ${side} is ${inner.binaryKind}, which is different from ${expr.binaryKind}`,
|
||||||
expr.span,
|
expr.span,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ function printItem(item: Item<AnyPhase>): string {
|
||||||
|
|
||||||
function printFunction(func: ItemFunction<AnyPhase>): string {
|
function printFunction(func: ItemFunction<AnyPhase>): string {
|
||||||
const args = func.params
|
const args = func.params
|
||||||
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
.map(({ ident: name, type }) => `${name}: ${printType(type)}`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
|
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
|
||||||
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`;
|
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`;
|
||||||
|
|
@ -89,7 +89,7 @@ function printTypeDef(type: ItemType<AnyPhase>): string {
|
||||||
|
|
||||||
function printImportDef(def: ItemImport<AnyPhase>): string {
|
function printImportDef(def: ItemImport<AnyPhase>): string {
|
||||||
const args = def.params
|
const args = def.params
|
||||||
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
.map(({ ident: name, type }) => `${name}: ${printType(type)}`)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
const ret = def.returnType ? `: ${printType(def.returnType)}` : "";
|
const ret = def.returnType ? `: ${printType(def.returnType)}` : "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ function resolveModule(
|
||||||
contents.forEach((item) => {
|
contents.forEach((item) => {
|
||||||
const existing = items.get(item.name);
|
const existing = items.get(item.name);
|
||||||
if (existing !== undefined) {
|
if (existing !== undefined) {
|
||||||
cx.gcx.error.emit(
|
cx.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`item \`${item.name}\` has already been declared`,
|
`item \`${item.name}\` has already been declared`,
|
||||||
item.span,
|
item.span,
|
||||||
|
|
@ -164,7 +164,7 @@ function resolveModule(
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: "error",
|
kind: "error",
|
||||||
err: cx.gcx.error.emit(
|
err: cx.gcx.error.emitError(
|
||||||
new CompilerError(`cannot find ${ident.name}`, ident.span),
|
new CompilerError(`cannot find ${ident.name}`, ident.span),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
@ -179,18 +179,17 @@ function resolveModule(
|
||||||
|
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
case "function": {
|
case "function": {
|
||||||
const params = item.params.map(({ name, span, type }) => ({
|
const params = item.params.map(({ ident, type }) => ({
|
||||||
name,
|
ident,
|
||||||
span,
|
|
||||||
type: this.type(type),
|
type: this.type(type),
|
||||||
}));
|
}));
|
||||||
const returnType = item.returnType && this.type(item.returnType);
|
const returnType = item.returnType && this.type(item.returnType);
|
||||||
|
|
||||||
item.params.forEach(({ name }) => scopes.push(name));
|
item.params.forEach(({ ident: name }) => scopes.push(name.name));
|
||||||
const body = this.expr(item.body);
|
const body = this.expr(item.body);
|
||||||
const revParams = item.params.slice();
|
const revParams = item.params.slice();
|
||||||
revParams.reverse();
|
revParams.reverse();
|
||||||
revParams.forEach(({ name }) => popScope(name));
|
revParams.forEach(({ ident: name }) => popScope(name.name));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kind: "function",
|
kind: "function",
|
||||||
|
|
@ -289,7 +288,7 @@ function resolveModule(
|
||||||
let pathRes: Resolution;
|
let pathRes: Resolution;
|
||||||
|
|
||||||
if (typeof expr.field.value === "number") {
|
if (typeof expr.field.value === "number") {
|
||||||
const err: ErrorEmitted = cx.gcx.error.emit(
|
const err: ErrorEmitted = cx.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
"module contents cannot be indexed with a number",
|
"module contents cannot be indexed with a number",
|
||||||
expr.field.span,
|
expr.field.span,
|
||||||
|
|
@ -304,7 +303,7 @@ function resolveModule(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pathResItem === undefined) {
|
if (pathResItem === undefined) {
|
||||||
const err: ErrorEmitted = cx.gcx.error.emit(
|
const err: ErrorEmitted = cx.gcx.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`module ${module.name} has no item ${expr.field.value}`,
|
`module ${module.name} has no item ${expr.field.value}`,
|
||||||
expr.field.span,
|
expr.field.span,
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,5 @@ export function tyErrorFrom(prev: { err: ErrorEmitted }): Ty {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emitError(cx: TypeckCtx, err: CompilerError): ErrorEmitted {
|
export function emitError(cx: TypeckCtx, err: CompilerError): ErrorEmitted {
|
||||||
return cx.gcx.error.emit(err);
|
return cx.gcx.error.emitError(err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import { emitError } from "./base";
|
||||||
import { checkBody, exprError } from "./expr";
|
import { checkBody, exprError } from "./expr";
|
||||||
import { InferContext } from "./infer";
|
import { InferContext } from "./infer";
|
||||||
import { typeOfItem } from "./item";
|
import { typeOfItem } from "./item";
|
||||||
|
import { lintProgram } from "./lint";
|
||||||
|
|
||||||
export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
|
export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
|
||||||
const cx = {
|
const cx = {
|
||||||
|
|
@ -55,7 +56,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
|
||||||
cx,
|
cx,
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`import parameters must be I32 or Int`,
|
`import parameters must be I32 or Int`,
|
||||||
item.params[i].span,
|
item.params[i].ident.span,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -214,5 +215,7 @@ export function typeck(gcx: GlobalContext, ast: Pkg<Resolved>): Pkg<Typecked> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lintProgram(gcx, typecked);
|
||||||
|
|
||||||
return typecked;
|
return typecked;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ export class InferContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.error.emit(
|
this.error.emitError(
|
||||||
new CompilerError(
|
new CompilerError(
|
||||||
`cannot assign ${printTy(rhs)} to ${printTy(lhs)}`,
|
`cannot assign ${printTy(rhs)} to ${printTy(lhs)}`,
|
||||||
span,
|
span,
|
||||||
|
|
|
||||||
108
src/typeck/lint.ts
Normal file
108
src/typeck/lint.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
import {
|
||||||
|
Folder,
|
||||||
|
Ident,
|
||||||
|
Item,
|
||||||
|
Pkg,
|
||||||
|
Typecked,
|
||||||
|
foldAst,
|
||||||
|
mkDefaultFolder,
|
||||||
|
superFoldExpr,
|
||||||
|
superFoldItem,
|
||||||
|
} from "../ast";
|
||||||
|
import { GlobalContext } from "../context";
|
||||||
|
import { CompilerError } from "../error";
|
||||||
|
|
||||||
|
type LintContext = {
|
||||||
|
locals: LocalUsed[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type LocalUsed = {
|
||||||
|
ident: Ident;
|
||||||
|
used: boolean;
|
||||||
|
isParam: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function lintProgram(gcx: GlobalContext, ast: Pkg<Typecked>): void {
|
||||||
|
const cx: LintContext = {
|
||||||
|
locals: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const checker: Folder<Typecked, Typecked> = {
|
||||||
|
...mkDefaultFolder(),
|
||||||
|
itemInner(item: Item<Typecked>): Item<Typecked> {
|
||||||
|
switch (item.kind) {
|
||||||
|
case "function": {
|
||||||
|
const prev = cx.locals;
|
||||||
|
cx.locals = [
|
||||||
|
...item.params.map((param) => ({
|
||||||
|
ident: param.ident,
|
||||||
|
used: false,
|
||||||
|
isParam: true,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
superFoldItem(item, this);
|
||||||
|
checkLocalsUsed(gcx, 0, cx);
|
||||||
|
cx.locals = prev;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return superFoldItem(item, this);
|
||||||
|
},
|
||||||
|
expr(expr) {
|
||||||
|
switch (expr.kind) {
|
||||||
|
case "let": {
|
||||||
|
superFoldExpr(expr, this);
|
||||||
|
cx.locals.push({ ident: expr.name, used: false, isParam: false });
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
case "block": {
|
||||||
|
const prevLocalsLen = cx.locals.length;
|
||||||
|
superFoldExpr(expr, this);
|
||||||
|
checkLocalsUsed(gcx, prevLocalsLen, cx);
|
||||||
|
cx.locals.length = prevLocalsLen;
|
||||||
|
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
case "ident": {
|
||||||
|
const { res } = expr.value;
|
||||||
|
switch (res.kind) {
|
||||||
|
case "local": {
|
||||||
|
cx.locals[cx.locals.length - 1 - res.index].used = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return superFoldExpr(expr, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return superFoldExpr(expr, this);
|
||||||
|
},
|
||||||
|
ident(ident) {
|
||||||
|
return ident;
|
||||||
|
},
|
||||||
|
type(type) {
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foldAst(ast, checker);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLocalsUsed(
|
||||||
|
gcx: GlobalContext,
|
||||||
|
prevLength: number,
|
||||||
|
cx: LintContext,
|
||||||
|
) {
|
||||||
|
for (let i = prevLength; i < cx.locals.length; i++) {
|
||||||
|
const local = cx.locals[i];
|
||||||
|
if (!local.used && !local.ident.name.startsWith("_")) {
|
||||||
|
const kind = local.isParam ? "function parameter" : "variable";
|
||||||
|
gcx.error.warn(
|
||||||
|
new CompilerError(
|
||||||
|
`unused ${kind}: \`${local.ident.name}\``,
|
||||||
|
local.ident.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,5 +2,6 @@ import ("wasi_snapshot_preview1" "fd_write")
|
||||||
fd_write(fd: I32, ciovec_ptr: I32, ciovec_len: I32, out_ptr: I32): I32;
|
fd_write(fd: I32, ciovec_ptr: I32, ciovec_len: I32, out_ptr: I32): I32;
|
||||||
|
|
||||||
function print(s: String) = (
|
function print(s: String) = (
|
||||||
let s: (I32, I32) = ___transmute(s);
|
// TODO: do it
|
||||||
|
let _s: (I32, I32) = ___transmute(s);
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ function allocate(size: I32, align: I32): I32 = (
|
||||||
alignedPtr
|
alignedPtr
|
||||||
);
|
);
|
||||||
|
|
||||||
function deallocate(ptr: I32, size: I32) = (
|
function deallocate(_ptr: I32, _size: I32) = (
|
||||||
std.println("uwu deawwocate :3");
|
std.println("uwu deawwocate :3");
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod alloc;
|
mod alloc;
|
||||||
|
|
||||||
function memcpy(dst: I32, src: I32, n: I32) =
|
// The function parameters are not actually unused.
|
||||||
|
function memcpy(_dst: I32, _src: I32, _n: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
"local.get 2",
|
"local.get 2",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//@check-pass
|
//@check-pass
|
||||||
|
|
||||||
function dropping(a: I32) =
|
function dropping(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
"local.get 0",
|
"local.get 0",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
function a(a: I32) =
|
function a(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
function dropping(a: I32) =
|
function dropping(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
"meow meow",
|
"meow meow",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
function dropping(a: I32) =
|
function dropping(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
"local.get 0",
|
"local.get 0",
|
||||||
"drop",
|
"drop",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
function dropping(a: I32) = (
|
function dropping(_a: I32) = (
|
||||||
1;
|
1;
|
||||||
___asm(__locals(), "drop");
|
___asm(__locals(), "drop");
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
function a(a: I32) =
|
function a(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
"local.get 0 0",
|
"local.get 0 0",
|
||||||
"drop",
|
"drop",
|
||||||
);
|
);
|
||||||
|
|
||||||
function b(a: I32) =
|
function b(_a: I32) =
|
||||||
___asm(
|
___asm(
|
||||||
__locals(),
|
__locals(),
|
||||||
"local.get",
|
"local.get",
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,11 @@ error: cannot assign String to Int
|
||||||
--> $DIR/basic_recovery.nil:3
|
--> $DIR/basic_recovery.nil:3
|
||||||
3 | let b: Int = "";
|
3 | let b: Int = "";
|
||||||
^
|
^
|
||||||
|
warning: unused variable: `a`
|
||||||
|
--> $DIR/basic_recovery.nil:2
|
||||||
|
2 | let a: Int = "";
|
||||||
|
^
|
||||||
|
warning: unused variable: `b`
|
||||||
|
--> $DIR/basic_recovery.nil:3
|
||||||
|
3 | let b: Int = "";
|
||||||
|
^
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ function main() = (
|
||||||
singleArg("hi!");
|
singleArg("hi!");
|
||||||
manyArgs(1,2,3,4,5,6);
|
manyArgs(1,2,3,4,5,6);
|
||||||
|
|
||||||
let a: () = returnNothing();
|
let _a: () = returnNothing();
|
||||||
let b: () = returnExplicitUnit();
|
let _b: () = returnExplicitUnit();
|
||||||
let c: String = returnString();
|
let _c: String = returnString();
|
||||||
);
|
);
|
||||||
|
|
||||||
function noArgs() =;
|
function noArgs() =;
|
||||||
function singleArg(a: String) =;
|
function singleArg(_a: String) =;
|
||||||
function manyArgs(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) =;
|
function manyArgs(_a: Int, _b: Int, _c: Int, _d: Int, _e: Int, _f: Int) =;
|
||||||
|
|
||||||
function returnNothing() =;
|
function returnNothing() =;
|
||||||
function returnExplicitUnit(): () =;
|
function returnExplicitUnit(): () =;
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@ function main() = (
|
||||||
x();
|
x();
|
||||||
);
|
);
|
||||||
|
|
||||||
function x(a: Int) = ;
|
function x(_a: Int) = ;
|
||||||
22
ui-tests/lint/unused_vars.nil
Normal file
22
ui-tests/lint/unused_vars.nil
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
//@check-pass
|
||||||
|
|
||||||
|
function main() = (
|
||||||
|
let x = 0;
|
||||||
|
let _ok = 0;
|
||||||
|
let used = 0;
|
||||||
|
|
||||||
|
used;
|
||||||
|
);
|
||||||
|
|
||||||
|
function x() = (
|
||||||
|
let x = 0;
|
||||||
|
(
|
||||||
|
let x = 0;
|
||||||
|
call(x);
|
||||||
|
);
|
||||||
|
let y = x;
|
||||||
|
);
|
||||||
|
|
||||||
|
function call(_a: Int) = ;
|
||||||
|
|
||||||
|
function param(p: Int) = ;
|
||||||
12
ui-tests/lint/unused_vars.stderr
Normal file
12
ui-tests/lint/unused_vars.stderr
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
warning: unused variable: `x`
|
||||||
|
--> $DIR/unused_vars.nil:4
|
||||||
|
4 | let x = 0;
|
||||||
|
^
|
||||||
|
warning: unused variable: `y`
|
||||||
|
--> $DIR/unused_vars.nil:17
|
||||||
|
17 | let y = x;
|
||||||
|
^
|
||||||
|
warning: unused function parameter: `p`
|
||||||
|
--> $DIR/unused_vars.nil:22
|
||||||
|
22 | function param(p: Int) = ;
|
||||||
|
^
|
||||||
|
|
@ -2,3 +2,7 @@ error: type I32 does not take any generic arguments but 1 were passed
|
||||||
--> $DIR/generics_on_primitive.nil:2
|
--> $DIR/generics_on_primitive.nil:2
|
||||||
2 | let a: I32[I32] = 0;
|
2 | let a: I32[I32] = 0;
|
||||||
^^^
|
^^^
|
||||||
|
warning: unused variable: `a`
|
||||||
|
--> $DIR/generics_on_primitive.nil:2
|
||||||
|
2 | let a: I32[I32] = 0;
|
||||||
|
^
|
||||||
|
|
|
||||||
12
ui-tests/type/generics/generics_structs_in_args.stderr
Normal file
12
ui-tests/type/generics/generics_structs_in_args.stderr
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
warning: unused function parameter: `a`
|
||||||
|
--> $DIR/generics_structs_in_args.nil:11
|
||||||
|
11 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
|
warning: unused function parameter: `b`
|
||||||
|
--> $DIR/generics_structs_in_args.nil:11
|
||||||
|
11 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
|
warning: unused function parameter: `c`
|
||||||
|
--> $DIR/generics_structs_in_args.nil:11
|
||||||
|
11 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
12
ui-tests/type/generics/structs.stderr
Normal file
12
ui-tests/type/generics/structs.stderr
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
warning: unused function parameter: `a`
|
||||||
|
--> $DIR/structs.nil:9
|
||||||
|
9 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
|
warning: unused function parameter: `b`
|
||||||
|
--> $DIR/structs.nil:9
|
||||||
|
9 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
|
warning: unused function parameter: `c`
|
||||||
|
--> $DIR/structs.nil:9
|
||||||
|
9 | function test(a: A[I32], b: B[I32, Int, I32], c: C) = ;
|
||||||
|
^
|
||||||
|
|
@ -22,3 +22,51 @@ error: type () does not take any generic arguments but 1 were passed
|
||||||
--> $DIR/wrong_amount.nil:22
|
--> $DIR/wrong_amount.nil:22
|
||||||
22 | c3: C[I32],
|
22 | c3: C[I32],
|
||||||
^
|
^
|
||||||
|
warning: unused function parameter: `a1`
|
||||||
|
--> $DIR/wrong_amount.nil:9
|
||||||
|
9 | a1: A,
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `a2`
|
||||||
|
--> $DIR/wrong_amount.nil:10
|
||||||
|
10 | a2: A[],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `a3`
|
||||||
|
--> $DIR/wrong_amount.nil:11
|
||||||
|
11 | a3: A[I32],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `a4`
|
||||||
|
--> $DIR/wrong_amount.nil:12
|
||||||
|
12 | a4: A[I32, I32],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `b1`
|
||||||
|
--> $DIR/wrong_amount.nil:14
|
||||||
|
14 | b1: B,
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `b2`
|
||||||
|
--> $DIR/wrong_amount.nil:15
|
||||||
|
15 | b2: B[],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `b3`
|
||||||
|
--> $DIR/wrong_amount.nil:16
|
||||||
|
16 | b3: B[Int, Int],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `b4`
|
||||||
|
--> $DIR/wrong_amount.nil:17
|
||||||
|
17 | b4: B[Int, I32, Int],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `b5`
|
||||||
|
--> $DIR/wrong_amount.nil:18
|
||||||
|
18 | b5: B[Int, Int, Int, Int],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `c1`
|
||||||
|
--> $DIR/wrong_amount.nil:20
|
||||||
|
20 | c1: C,
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `c2`
|
||||||
|
--> $DIR/wrong_amount.nil:21
|
||||||
|
21 | c2: C[],
|
||||||
|
^^
|
||||||
|
warning: unused function parameter: `c3`
|
||||||
|
--> $DIR/wrong_amount.nil:22
|
||||||
|
22 | c3: C[I32],
|
||||||
|
^^
|
||||||
|
|
|
||||||
4
ui-tests/type/type_alias.stderr
Normal file
4
ui-tests/type/type_alias.stderr
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
warning: unused variable: `a`
|
||||||
|
--> $DIR/type_alias.nil:6
|
||||||
|
6 | let a: A = (0, 0);
|
||||||
|
^
|
||||||
24
ui-tests/type/type_assignments.stderr
Normal file
24
ui-tests/type/type_assignments.stderr
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
warning: unused variable: `a1`
|
||||||
|
--> $DIR/type_assignments.nil:15
|
||||||
|
15 | let a1: Int = a;
|
||||||
|
^^
|
||||||
|
warning: unused variable: `b1`
|
||||||
|
--> $DIR/type_assignments.nil:16
|
||||||
|
16 | let b1: I32 = b;
|
||||||
|
^^
|
||||||
|
warning: unused variable: `c1`
|
||||||
|
--> $DIR/type_assignments.nil:17
|
||||||
|
17 | let c1: String = c;
|
||||||
|
^^
|
||||||
|
warning: unused variable: `d1`
|
||||||
|
--> $DIR/type_assignments.nil:18
|
||||||
|
18 | let d1: Bool = d;
|
||||||
|
^^
|
||||||
|
warning: unused variable: `e1`
|
||||||
|
--> $DIR/type_assignments.nil:19
|
||||||
|
19 | let e1: CustomType = e;
|
||||||
|
^^
|
||||||
|
warning: unused variable: `f1`
|
||||||
|
--> $DIR/type_assignments.nil:20
|
||||||
|
20 | let f1: (Int, I32) = f;
|
||||||
|
^^
|
||||||
Loading…
Add table
Add a link
Reference in a new issue