Skip to content

Commit 55f138b

Browse files
authoredFeb 18, 2025
feat(completion): show self type fields/methods in completion list (#262)
With auto-insertion of `self.` prefix on select Fixes #70
1 parent 8358c48 commit 55f138b

File tree

4 files changed

+110
-14
lines changed

4 files changed

+110
-14
lines changed
 

‎server/src/e2e/suite/testcases/completion/methods.test

+47
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,50 @@ fun test() {
1919
14 let Create variable
2020
14 not Negate expression
2121
14 repeat Create repeat loop
22+
23+
========================================================================
24+
Completion of struct field of self type
25+
========================================================================
26+
primitive Int;
27+
28+
struct Data {
29+
active: Bool;
30+
}
31+
32+
extends fun some(self: Data) {
33+
return <caret>
34+
}
35+
------------------------------------------------------------------------
36+
5 self Data
37+
9 self.active: of Data
38+
13 false
39+
13 initOf Contract(params) StateInit
40+
13 null
41+
13 true
42+
2 some(self: Data)
43+
21 Data{}
44+
45+
========================================================================
46+
Completion of struct method of self type
47+
========================================================================
48+
primitive Int;
49+
50+
struct Data {
51+
active: Bool;
52+
}
53+
54+
extends fun other(self: Data) {}
55+
56+
extends fun some(self: Data) {
57+
return <caret>
58+
}
59+
------------------------------------------------------------------------
60+
5 self Data
61+
9 self.active: of Data
62+
13 false
63+
13 initOf Contract(params) StateInit
64+
13 null
65+
13 true
66+
2 other(self: Data)
67+
2 some(self: Data)
68+
21 Data{}

‎server/src/e2e/suite/testcases/completion/variables.test

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ extends fun move(self: Point, x: Int, y: Int) {
8282
13 null
8383
13 return;
8484
13 true
85+
2 move(self: Point, x: Int, y: Int)
8586
21 Point{}
8687
14 do
8788
14 foreach
@@ -114,6 +115,7 @@ extends fun scale(self: Point, factor: Int) {
114115
13 null
115116
13 return;
116117
13 true
118+
2 scale(self: Point, factor: Int)
117119
21 Point{}
118120
14 do
119121
14 foreach

‎server/src/psi/Decls.ts

+8
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,14 @@ export class Fun extends NamedNode {
291291
return first.name() === "self"
292292
}
293293

294+
public selfParam(): NamedNode | null {
295+
const params = this.parameters()
296+
if (params.length === 0) return null
297+
const first = params[0]
298+
if (first.name() !== "self") return null
299+
return first
300+
}
301+
294302
public signaturePresentation(): string {
295303
const parametersNode = this.node.childForFieldName("parameters")
296304
if (!parametersNode) return ""

‎server/src/psi/Reference.ts

+53-14
Original file line numberDiff line numberDiff line change
@@ -315,21 +315,25 @@ export class Reference {
315315
}
316316
}
317317

318-
// inside a trait/contract, when we write `foo`, we want to automatically complete it
319-
// with `self.foo` if there are any methods/fields/constants with the same name
320-
const ownerNode = parentOfType(this.element.node, "contract_body", "trait_body")
321-
if (ownerNode !== null && state.get("completion")) {
322-
const constructor = ownerNode.type === "contract_body" ? Contract : Trait
323-
const parent = ownerNode.parent
324-
if (!parent) return true
325-
326-
const owner = new constructor(parent, this.element.file)
327-
const typeConstructor = ownerNode.type === "contract_body" ? ContractTy : TraitTy
328-
const ownerTy = new typeConstructor(owner.name(), owner)
329-
const expr = new Expression(this.element.node, this.element.file)
318+
if (state.get("completion")) {
319+
const ownerNode = parentOfType(
320+
this.element.node,
321+
"contract_body",
322+
"trait_body",
323+
"global_function",
324+
)
325+
326+
// inside a trait/contract, when we write `foo`, we want to automatically complete it
327+
// with `self.foo` if there are any methods/fields/constants with the same name
328+
if (ownerNode?.type === "contract_body" || ownerNode?.type === "trait_body") {
329+
if (!this.processContractTraitSelfCompletion(ownerNode, state, proc)) return false
330+
}
330331

331-
const newState = state.withValue("prefix", "self.")
332-
if (!this.processType(expr, ownerTy, proc, newState)) return false
332+
// inside extends function, when we write `foo`, we want to automatically complete it
333+
// with `self.foo` if there are any methods/fields/constants with the same name
334+
if (ownerNode?.type === "global_function") {
335+
if (!this.processExtendsMethodSelfCompletion(ownerNode, state, proc)) return false
336+
}
333337
}
334338

335339
if (this.element.node.type === "tvm_instruction") {
@@ -369,6 +373,41 @@ export class Reference {
369373
return this.processAllEntities(proc, state)
370374
}
371375

376+
private processContractTraitSelfCompletion(
377+
ownerNode: SyntaxNode,
378+
state: ResolveState,
379+
proc: ScopeProcessor,
380+
): boolean {
381+
const constructor = ownerNode.type === "contract_body" ? Contract : Trait
382+
const parent = ownerNode.parent
383+
if (!parent) return true
384+
385+
const owner = new constructor(parent, this.element.file)
386+
const typeConstructor = ownerNode.type === "contract_body" ? ContractTy : TraitTy
387+
const ownerTy = new typeConstructor(owner.name(), owner)
388+
const expr = new Expression(this.element.node, this.element.file)
389+
390+
const newState = state.withValue("prefix", "self.")
391+
return this.processType(expr, ownerTy, proc, newState)
392+
}
393+
394+
private processExtendsMethodSelfCompletion(
395+
ownerNode: SyntaxNode,
396+
state: ResolveState,
397+
proc: ScopeProcessor,
398+
): boolean {
399+
const func = new Fun(ownerNode, this.element.file)
400+
const selfParam = func.selfParam()
401+
if (selfParam === null) return true // skip if we are not in extends function
402+
403+
const selfTy = TypeInferer.inferType(selfParam)
404+
if (!selfTy) return true
405+
const expr = new Expression(this.element.node, this.element.file)
406+
407+
const newState = state.withValue("prefix", "self.")
408+
return this.processType(expr, selfTy, proc, newState)
409+
}
410+
372411
private resolveInstanceInitField(
373412
parent: SyntaxNode,
374413
proc: ScopeProcessor,

0 commit comments

Comments
 (0)