Skip to content

Commit 4e9522e

Browse files
authored
feat(tolk): support lambdas from Tolk 1.2 (#202)
- Parsing - Type inference - Inlay hints for parameters without explicit type
1 parent 85aae33 commit 4e9522e

File tree

12 files changed

+495
-8
lines changed

12 files changed

+495
-8
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
========================================================================
2+
Lambda param type
3+
========================================================================
4+
fun measure(cb: (int) -> int) {}
5+
6+
fun foo() {
7+
measure(fun (x) {
8+
})
9+
}
10+
------------------------------------------------------------------------
11+
fun measure(cb: (int) -> int)/* : void */ {}
12+
13+
fun foo()/* : void */ {
14+
measure(/* cb: */fun (x/* : int */) {
15+
})
16+
}
17+
18+
========================================================================
19+
Lambda param types
20+
========================================================================
21+
fun measure(cb: (int, slice) -> int) {}
22+
23+
fun foo() {
24+
measure(fun (
25+
x,
26+
y,
27+
) {
28+
})
29+
}
30+
------------------------------------------------------------------------
31+
fun measure(cb: (int, slice) -> int)/* : void */ {}
32+
33+
fun foo()/* : void */ {
34+
measure(/* cb: */fun (
35+
x/* : int */,
36+
y/* : slice */,
37+
) {
38+
})
39+
}
40+
41+
========================================================================
42+
Lambda with generic param type
43+
========================================================================
44+
fun measure<T>(cb: (T) -> void): T {}
45+
46+
fun foo() {
47+
val a = measure(fun (a: int) {
48+
})
49+
}
50+
------------------------------------------------------------------------
51+
fun measure<T>(cb: (T) -> void): T {}
52+
53+
fun foo()/* : void */ {
54+
val a/* : int */ = measure(/* cb: */fun (a: int) {
55+
})
56+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
========================================================================
2+
Lambda parameter without type
3+
========================================================================
4+
fun test() {
5+
fun (<caret>a) {
6+
a;
7+
};
8+
}
9+
------------------------------------------------------------------------
10+
References: [2:8]
11+
Scope: LocalSearchScope:
12+
{
13+
a;
14+
}
15+
16+
========================================================================
17+
Lambda parameter with type
18+
========================================================================
19+
fun test() {
20+
fun (<caret>a: int) {
21+
a;
22+
};
23+
}
24+
------------------------------------------------------------------------
25+
References: [2:8]
26+
Scope: LocalSearchScope:
27+
{
28+
a;
29+
}
30+
31+
========================================================================
32+
Nested lambda parameter
33+
========================================================================
34+
fun test() {
35+
fun (<caret>a: int) {
36+
fun (a: int) {
37+
a;
38+
};
39+
};
40+
}
41+
------------------------------------------------------------------------
42+
References: []
43+
Scope: LocalSearchScope:
44+
{
45+
fun (a: int) {
46+
a;
47+
};
48+
}
49+
50+
========================================================================
51+
Nested lambda parameter 2
52+
========================================================================
53+
fun test() {
54+
fun (a: int) {
55+
fun (<caret>a: int) {
56+
a;
57+
};
58+
};
59+
}
60+
------------------------------------------------------------------------
61+
References: [3:12]
62+
Scope: LocalSearchScope:
63+
{
64+
a;
65+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
========================================================================
2+
Lambda parameter without type
3+
========================================================================
4+
fun test() {
5+
fun (a) {
6+
<caret>a;
7+
};
8+
}
9+
------------------------------------------------------------------------
10+
2:8 -> 1:9 resolved
11+
12+
========================================================================
13+
Lambda parameter with type
14+
========================================================================
15+
fun test() {
16+
fun (a: int) {
17+
<caret>a;
18+
};
19+
}
20+
------------------------------------------------------------------------
21+
2:8 -> 1:9 resolved
22+
23+
========================================================================
24+
Nested lambda parameter
25+
========================================================================
26+
fun test() {
27+
fun (a: int) {
28+
fun (a: int) {
29+
<caret>a;
30+
};
31+
};
32+
}
33+
------------------------------------------------------------------------
34+
3:12 -> 2:13 resolved
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
========================================================================
2+
Lambda param type
3+
========================================================================
4+
fun measure(cb: (int) -> int) {}
5+
6+
fun foo() {
7+
measure(fun (x) {
8+
//! ^ int
9+
})
10+
}
11+
------------------------------------------------------------------------
12+
ok
13+
14+
========================================================================
15+
Lambda param types
16+
========================================================================
17+
fun measure(cb: (int, slice) -> int) {}
18+
19+
fun foo() {
20+
measure(fun (
21+
x,
22+
//! ^ int
23+
y,
24+
//! ^ slice
25+
) {
26+
})
27+
}
28+
------------------------------------------------------------------------
29+
ok
30+
31+
========================================================================
32+
Lambda auto return type
33+
========================================================================
34+
fun measure<T>(cb: () -> T): T {}
35+
36+
fun foo() {
37+
val a = measure(fun () {
38+
//! ^ int
39+
return 10
40+
})
41+
}
42+
------------------------------------------------------------------------
43+
ok
44+
45+
========================================================================
46+
Lambda nullable auto return type
47+
========================================================================
48+
fun measure<T>(cb: (int) -> T): T {}
49+
50+
fun foo() {
51+
val a = measure(fun (a) {
52+
//! ^ int?
53+
if (a > 10) {
54+
return null
55+
}
56+
return 10
57+
})
58+
}
59+
------------------------------------------------------------------------
60+
ok
61+
62+
========================================================================
63+
Lambda with never auto return type
64+
========================================================================
65+
fun measure<T>(cb: (int) -> T): T {}
66+
67+
fun foo() {
68+
val a = measure(fun (a) {
69+
//! ^ never
70+
if (a > 10) {
71+
throw 20
72+
}
73+
throw 10
74+
})
75+
}
76+
------------------------------------------------------------------------
77+
ok
78+
79+
========================================================================
80+
Lambda with generic param type
81+
========================================================================
82+
fun measure<T>(cb: (T) -> void): T {}
83+
84+
fun foo() {
85+
val a = measure(fun (a: int) {
86+
//! ^ int
87+
})
88+
}
89+
------------------------------------------------------------------------
90+
ok
91+
92+
========================================================================
93+
Lambda with generic param type with struct
94+
========================================================================
95+
struct S {
96+
x: int
97+
}
98+
99+
fun measure<T>(cb: (T) -> void): T {}
100+
101+
fun foo() {
102+
val a = measure(fun (a: S) {
103+
//! ^ S
104+
a
105+
//! ^ S
106+
.x;
107+
//! ^ int
108+
})
109+
}
110+
------------------------------------------------------------------------
111+
ok

server/src/languages/tolk/inlays/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {constantValueHint} from "@server/languages/tolk/inlays/constant-value-hi
1515
import {getMethodId} from "@server/languages/tolk/inlays/get-method-id"
1616
import {FunctionBase} from "@server/languages/tolk/psi/Decls"
1717
import {typeOf} from "@server/languages/tolk/type-inference"
18+
import {lambdaParametersHints} from "@server/languages/tolk/inlays/lambda-parameters-hints"
1819

1920
export function collectTolkInlays(
2021
file: TolkFile,
@@ -38,6 +39,11 @@ export function collectTolkInlays(
3839
return true
3940
}
4041

42+
if (type === "lambda_expression" && hints.parameters) {
43+
lambdaParametersHints(n, file, result)
44+
return true
45+
}
46+
4147
if (type === "var_declaration" && hints.types) {
4248
variableDeclarationTypeHint(n, file, result)
4349
return true
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright © 2025 TON Core
3+
import type {Node as SyntaxNode} from "web-tree-sitter"
4+
import {InlayHint, InlayHintKind} from "vscode-languageserver-types"
5+
6+
import {Lambda} from "@server/languages/tolk/psi/TolkNode"
7+
import type {TolkFile} from "@server/languages/tolk/psi/TolkFile"
8+
import {typeOf} from "@server/languages/tolk/type-inference"
9+
import {UnknownTy} from "@server/languages/func/types/ty"
10+
11+
export function lambdaParametersHints(node: SyntaxNode, file: TolkFile, result: InlayHint[]): void {
12+
const lambda = new Lambda(node, file)
13+
const parameters = lambda.parameters()
14+
for (const parameter of parameters) {
15+
if (parameter.typeNode() !== null) continue
16+
const parameterType = typeOf(parameter.node, parameter.file)
17+
if (parameterType === null || parameterType instanceof UnknownTy) continue
18+
19+
result.push({
20+
kind: InlayHintKind.Parameter,
21+
label: [
22+
{
23+
value: ": ",
24+
},
25+
{
26+
value: parameterType.name(),
27+
},
28+
],
29+
position: {
30+
line: parameter.node.endPosition.row,
31+
character: parameter.node.endPosition.column,
32+
},
33+
})
34+
}
35+
}

server/src/languages/tolk/psi/Reference.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,11 +590,12 @@ export class Reference {
590590
}
591591
}
592592

593-
// process parameters of function
593+
// process parameters of a function
594594
const isFunction =
595595
descendant.type === "function_declaration" ||
596596
descendant.type === "method_declaration" ||
597-
descendant.type === "get_method_declaration"
597+
descendant.type === "get_method_declaration" ||
598+
descendant.type === "lambda_expression"
598599

599600
if (isFunction && (!this.forTypes || this.element.node.text === "self")) {
600601
const rawParameters = descendant.childForFieldName("parameters")

server/src/languages/tolk/psi/Referent.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,10 @@ export class Referent extends BaseReferent<NamedNode> {
173173
if (
174174
grand?.type === "function_declaration" ||
175175
grand?.type === "method_declaration" ||
176-
grand?.type === "get_method_declaration"
176+
grand?.type === "get_method_declaration" ||
177+
grand?.type === "lambda_expression"
177178
) {
178-
// search in function body
179+
// search in the function body
179180
return Referent.localSearchScope(grand.lastChild)
180181
}
181182
}

server/src/languages/tolk/psi/TolkNode.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {parentOfType} from "@server/psi/utils"
88
import {extractCommentsDoc} from "@server/psi/comments"
99
import {typeOf} from "@server/languages/tolk/type-inference"
1010

11+
import {Parameter} from "@server/languages/tolk/psi/Decls"
12+
1113
import {TolkFile} from "./TolkFile"
1214

1315
export class TolkNode extends BaseNode {
@@ -269,3 +271,15 @@ export class VarDeclaration extends NamedNode {
269271
return this.node.childForFieldName("redef") !== null
270272
}
271273
}
274+
275+
export class Lambda extends TolkNode {
276+
public parameters(): Parameter[] {
277+
const parametersNode = this.node.childForFieldName("parameters")
278+
if (!parametersNode) return []
279+
280+
return parametersNode.children
281+
.filter(value => value?.type === "parameter_declaration")
282+
.filter(value => value !== null)
283+
.map(value => new Parameter(value, this.file))
284+
}
285+
}

0 commit comments

Comments
 (0)