Skip to content

Commit 213432f

Browse files
author
Trinketer22
committed
Basic impure inspection
1 parent 0ca8dba commit 213432f

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright © 2025 TON Core
3+
4+
import * as lsp from "vscode-languageserver"
5+
6+
import type { FuncFile } from "@server/languages/func/psi/FuncFile"
7+
import { Node } from 'web-tree-sitter'
8+
import { UnusedInspection } from "./UnusedInspection"
9+
import { Inspection, InspectionIds } from "./Inspection"
10+
import { RecursiveVisitor } from "@server/visitor/visitor";
11+
import { Func } from "@server/languages/func/psi/Decls";
12+
import { convertTy, typeOf } from "@server/languages/func/types/infer";
13+
import { asLspRange } from "@server/utils/position"
14+
import { closestNamedSibling, parentOfType, parentOfTypeWithCb } from "@server/psi/utils"
15+
import { Referent } from "@server/languages/func/psi/Referent"
16+
17+
18+
export class UnusedImpureInspection extends UnusedInspection implements Inspection {
19+
public readonly id: "unused-impure" = InspectionIds.UNUSED_IMPURE;
20+
21+
protected checkFile(file: FuncFile, diagnostics: lsp.Diagnostic[]): void {
22+
const impureMap: Map<string, Func> = new Map();
23+
// Populate impure functions map
24+
file.getFunctions().forEach(f => {
25+
if (!f.isImpure) {
26+
impureMap.set(f.name(true), f);
27+
}
28+
});
29+
RecursiveVisitor.visit(file.rootNode, (node): boolean => {
30+
if (node.type == "function_application") {
31+
const funcIdentifier = node.children.find(n => n?.type === "identifier")
32+
if (funcIdentifier) {
33+
const droppableDef = impureMap.get(funcIdentifier.text);
34+
if (droppableDef && this.checkCallWillDrop(node, droppableDef, file)) {
35+
const range = asLspRange(node);
36+
diagnostics.push({
37+
severity: lsp.DiagnosticSeverity.Error,
38+
range,
39+
message: "This call will be dropped due to lack of impure specifier!",
40+
source: "func"
41+
})
42+
}
43+
}
44+
}
45+
return true;
46+
})
47+
}
48+
49+
private checkCallWillDrop(node: Node, definition: Func, file: FuncFile) {
50+
const returnExp = definition.returnType();
51+
if (returnExp !== null) {
52+
// If return type of a function is empty tensor - check no more.
53+
if (returnExp.node.text == '()') {
54+
return true;
55+
}
56+
}
57+
const expressionParent = parentOfTypeWithCb<{ parent: Node, origin: Node }>(node,
58+
(parent, origin) => {
59+
return { parent, origin }
60+
},
61+
"expression_statement",
62+
"return_statement",
63+
"function_application",
64+
"method_call",
65+
"if_statement",
66+
"while_statement",
67+
"do_while_statement",
68+
"repeat_statement"
69+
);
70+
// If call is in the block_statement of any kind, it will be a child of expression_statement
71+
// Otherwise it is in condition block of if/while/do while
72+
// Or in arguments clause of other function_application/method_call
73+
if (!expressionParent || expressionParent.parent.type !== "expression_statement") {
74+
return false;
75+
}
76+
77+
// We are in the expression expression_statement
78+
// Closest previous sibling got to be lvalue expression
79+
// (identifier/tensor_expression/tuple_expression)
80+
const lValue = closestNamedSibling(expressionParent.origin, 'prev', (sibling) => sibling.type !== "comment");
81+
// If no lvalue, non-impure call will drop
82+
if (!lValue) {
83+
return true;
84+
}
85+
// If no identifiers referenced in lvalue, means those are whole type and will be dropped
86+
const affectedIdentifiers = lValue.descendantsOfType("identifier");
87+
88+
for (let refValue of affectedIdentifiers) {
89+
if (!refValue) {
90+
continue;
91+
}
92+
const references = new Referent(refValue, file).findReferences({}) // we need at least one reference
93+
// Has to be referenced in call, conditional or return statement;
94+
for (let ref of references) {
95+
const parent = parentOfType(ref.node,
96+
"expression_statement", // But don't go above expression_statement
97+
"function_application",
98+
"method_call",
99+
"if_statement",
100+
"while_statement",
101+
"do_while_statement",
102+
"repeat_statement"
103+
)
104+
if (parent && parent.type !== "expression_statement") {
105+
return false;
106+
}
107+
}
108+
}
109+
110+
return true;
111+
}
112+
}
113+

server/src/languages/func/psi/Decls.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export class FunctionBase extends NamedNode {
105105
const methodId = specifiers?.children.find(it => it?.type === "method_id") ?? null
106106
return methodId !== null
107107
}
108+
public get isImpure(): boolean {
109+
const specifiers = this.node.childForFieldName("specifiers");
110+
return Boolean(specifiers?.children.find(it => it?.type === "impure"));
111+
}
108112

109113
public get hasExplicitMethodId(): boolean {
110114
// check for

0 commit comments

Comments
 (0)