Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(completion): show self type fields/methods in completion list #262

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions server/src/e2e/suite/testcases/completion/methods.test
Original file line number Diff line number Diff line change
@@ -19,3 +19,50 @@ fun test() {
14 let Create variable
14 not Negate expression
14 repeat Create repeat loop

========================================================================
Completion of struct field of self type
========================================================================
primitive Int;

struct Data {
active: Bool;
}

extends fun some(self: Data) {
return <caret>
}
------------------------------------------------------------------------
5 self Data
9 self.active: of Data
13 false
13 initOf Contract(params) StateInit
13 null
13 true
2 some(self: Data)
21 Data{}

========================================================================
Completion of struct method of self type
========================================================================
primitive Int;

struct Data {
active: Bool;
}

extends fun other(self: Data) {}

extends fun some(self: Data) {
return <caret>
}
------------------------------------------------------------------------
5 self Data
9 self.active: of Data
13 false
13 initOf Contract(params) StateInit
13 null
13 true
2 other(self: Data)
2 some(self: Data)
21 Data{}
2 changes: 2 additions & 0 deletions server/src/e2e/suite/testcases/completion/variables.test
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ extends fun move(self: Point, x: Int, y: Int) {
13 null
13 return;
13 true
2 move(self: Point, x: Int, y: Int)
21 Point{}
14 do
14 foreach
@@ -114,6 +115,7 @@ extends fun scale(self: Point, factor: Int) {
13 null
13 return;
13 true
2 scale(self: Point, factor: Int)
21 Point{}
14 do
14 foreach
8 changes: 8 additions & 0 deletions server/src/psi/Decls.ts
Original file line number Diff line number Diff line change
@@ -291,6 +291,14 @@ export class Fun extends NamedNode {
return first.name() === "self"
}

public selfParam(): NamedNode | null {
const params = this.parameters()
if (params.length === 0) return null
const first = params[0]
if (first.name() !== "self") return null
return first
}

public signaturePresentation(): string {
const parametersNode = this.node.childForFieldName("parameters")
if (!parametersNode) return ""
67 changes: 53 additions & 14 deletions server/src/psi/Reference.ts
Original file line number Diff line number Diff line change
@@ -315,21 +315,25 @@ export class Reference {
}
}

// inside a trait/contract, when we write `foo`, we want to automatically complete it
// with `self.foo` if there are any methods/fields/constants with the same name
const ownerNode = parentOfType(this.element.node, "contract_body", "trait_body")
if (ownerNode !== null && state.get("completion")) {
const constructor = ownerNode.type === "contract_body" ? Contract : Trait
const parent = ownerNode.parent
if (!parent) return true

const owner = new constructor(parent, this.element.file)
const typeConstructor = ownerNode.type === "contract_body" ? ContractTy : TraitTy
const ownerTy = new typeConstructor(owner.name(), owner)
const expr = new Expression(this.element.node, this.element.file)
if (state.get("completion")) {
const ownerNode = parentOfType(
this.element.node,
"contract_body",
"trait_body",
"global_function",
)

// inside a trait/contract, when we write `foo`, we want to automatically complete it
// with `self.foo` if there are any methods/fields/constants with the same name
if (ownerNode?.type === "contract_body" || ownerNode?.type === "trait_body") {
if (!this.processContractTraitSelfCompletion(ownerNode, state, proc)) return false
}

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

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

private processContractTraitSelfCompletion(
ownerNode: SyntaxNode,
state: ResolveState,
proc: ScopeProcessor,
): boolean {
const constructor = ownerNode.type === "contract_body" ? Contract : Trait
const parent = ownerNode.parent
if (!parent) return true

const owner = new constructor(parent, this.element.file)
const typeConstructor = ownerNode.type === "contract_body" ? ContractTy : TraitTy
const ownerTy = new typeConstructor(owner.name(), owner)
const expr = new Expression(this.element.node, this.element.file)

const newState = state.withValue("prefix", "self.")
return this.processType(expr, ownerTy, proc, newState)
}

private processExtendsMethodSelfCompletion(
ownerNode: SyntaxNode,
state: ResolveState,
proc: ScopeProcessor,
): boolean {
const func = new Fun(ownerNode, this.element.file)
const selfParam = func.selfParam()
if (selfParam === null) return true // skip if we are not in extends function

const selfTy = TypeInferer.inferType(selfParam)
if (!selfTy) return true
const expr = new Expression(this.element.node, this.element.file)

const newState = state.withValue("prefix", "self.")
return this.processType(expr, selfTy, proc, newState)
}

private resolveInstanceInitField(
parent: SyntaxNode,
proc: ScopeProcessor,