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/resolving/documentation): add support for fromCell and fromSlice methods on structs and messages #233

Merged
merged 1 commit into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
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
18 changes: 17 additions & 1 deletion server/src/TypeInferer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ export class TypeInferer {
if (node.node.type === "type_identifier") {
const resolved = Reference.resolve(new NamedNode(node.node, node.file))
if (resolved === null) {
if (node.node.text === "K" || node.node.text === "V") {
if (
node.node.text === "K" ||
node.node.text === "V" ||
node.node.text === "S" ||
node.node.text === "M"
) {
return new PlaceholderTy(node.node.text, new NamedNode(node.node, node.file))
}
return null
Expand Down Expand Up @@ -224,12 +229,23 @@ export class TypeInferer {
const resolved = Reference.resolve(element)
if (resolved === null) return null
if (!(resolved instanceof Fun)) return null
const calledName = resolved.name()

const returnType = resolved.returnType()
if (returnType === null) return null
const type = this.inferTypeMaybeOption(returnType.node, returnType)
if (!type) return null

const qualifier = node.node.childForFieldName("object")

// Handle `Struct.fromCell()` calls
if (
qualifier &&
(calledName.startsWith("AnyStruct_") || calledName.startsWith("AnyMessage_"))
) {
return new Expression(qualifier, node.file).type()
}

// following code is completely garbage, we need to replace it with actual generics
if (
type instanceof PlaceholderTy ||
Expand Down
3 changes: 2 additions & 1 deletion server/src/completion/ReferenceCompletionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "@server/completion/WeightedCompletionItem"
import {StructTy} from "@server/types/BaseTy"
import {tactCodeBlock} from "@server/documentation/documentation"
import {trimPrefix} from "@server/utils/strings"

export class ReferenceCompletionProcessor implements ScopeProcessor {
public constructor(private readonly ctx: CompletionContext) {}
Expand Down Expand Up @@ -58,7 +59,7 @@ export class ReferenceCompletionProcessor implements ScopeProcessor {
if (!(node instanceof NamedNode)) return true

const prefix = state.get("prefix") ?? ""
const name = node.name()
const name = trimPrefix(trimPrefix(node.name(), "AnyMessage_"), "AnyStruct_")
if (name.endsWith("DummyIdentifier") || name === "AnyStruct" || name === "AnyMessage") {
return true
}
Expand Down
4 changes: 3 additions & 1 deletion server/src/documentation/documentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ export function generateDocFor(node: NamedNode): string | null {
const func = new Fun(astNode, node.file)
const doc = extractCommentsDoc(node)

const name = trimPrefix(trimPrefix(node.name(), "AnyMessage_"), "AnyStruct_")

return defaultResult(
`${func.modifiers()}fun ${node.name()}${func.signaturePresentation()}`,
`${func.modifiers()}fun ${name}${func.signaturePresentation()}`,
doc,
)
}
Expand Down
45 changes: 45 additions & 0 deletions server/src/e2e/suite/testcases/completion/fromCell.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
========================================================================
fromCell/fromSlice for Struct
========================================================================
primitive Int;

fun AnyStruct_fromCell(cell: Cell): S {}
fun AnyStruct_fromSlice(slice: Slice): S {}

struct Foo {}

fun test() {
Foo.<caret>;
}
------------------------------------------------------------------------
2 fromCell(cell: Cell): S
2 fromSlice(slice: Slice): S
14 call Use as function argument
14 do Create do-until loop
14 if Create if statement
14 let Create variable
14 not Negate expression
14 repeat Create repeat loop

========================================================================
fromCell/fromSlice for message
========================================================================
primitive Int;

fun AnyMessage_fromCell(cell: Cell): M {}
fun AnyMessage_fromSlice(slice: Slice): M {}

message Foo {}

fun test() {
Foo.<caret>;
}
------------------------------------------------------------------------
2 fromCell(cell: Cell): M
2 fromSlice(slice: Slice): M
14 call Use as function argument
14 do Create do-until loop
14 if Create if statement
14 let Create variable
14 not Negate expression
14 repeat Create repeat loop
35 changes: 35 additions & 0 deletions server/src/e2e/suite/testcases/documentation/fromCell.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
========================================================================
fromCell/fromSlice for Struct
========================================================================
primitive Int;

fun AnyStruct_fromCell(cell: Cell): S {}
fun AnyStruct_fromSlice(slice: Slice): S {}

struct Foo {}

fun test() {
Foo.<caret>fromCell();
}
------------------------------------------------------------------------
```tact
fun fromCell(cell: Cell): S
```

========================================================================
fromCell/fromSlice for message
========================================================================
primitive Int;

fun AnyMessage_fromCell(cell: Cell): M {}
fun AnyMessage_fromSlice(slice: Slice): M {}

message Foo {}

fun test() {
Foo.<caret>fromSlice();
}
------------------------------------------------------------------------
```tact
fun fromSlice(slice: Slice): M
```
31 changes: 31 additions & 0 deletions server/src/e2e/suite/testcases/resolve/fromCell.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
========================================================================
fromCell/fromSlice for Struct
========================================================================
primitive Int;

fun AnyStruct_fromCell(cell: Cell): S {}
fun AnyStruct_fromSlice(slice: Slice): S {}

struct Foo {}

fun test() {
Foo.<caret>fromCell();
}
------------------------------------------------------------------------
8:8 -> 2:4 resolved

========================================================================
fromCell/fromSlice for message
========================================================================
primitive Int;

fun AnyMessage_fromCell(cell: Cell): M {}
fun AnyMessage_fromSlice(slice: Slice): M {}

message Foo {}

fun test() {
Foo.<caret>fromSlice();
}
------------------------------------------------------------------------
8:8 -> 3:4 resolved
33 changes: 33 additions & 0 deletions server/src/e2e/suite/testcases/types/fromCell.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
========================================================================
fromCell/fromSlice for Struct
========================================================================
primitive Int;

fun AnyStruct_fromCell(cell: Cell): S {}
fun AnyStruct_fromSlice(slice: Slice): S {}

struct Foo {}

fun test() {
let f = Foo.<caret>;
// ^! Foo
}
------------------------------------------------------------------------
ok

========================================================================
fromCell/fromSlice for message
========================================================================
primitive Int;

fun AnyMessage_fromCell(cell: Cell): M {}
fun AnyMessage_fromSlice(slice: Slice): M {}

message Foo {}

fun test() {
let f = Foo.<caret>;
// ^! Foo
}
------------------------------------------------------------------------
ok
37 changes: 37 additions & 0 deletions server/src/psi/Reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,36 @@ export class Reference {
if (qualifierType === null) return true

if (qualifierType instanceof StructTy || qualifierType instanceof MessageTy) {
if (qualifier.node.type === "identifier") {
const resolved = Reference.resolve(new NamedNode(qualifier.node, qualifier.file))
if (resolved instanceof Struct || resolved instanceof Message) {
// found `Foo.fromCell` case
const prefix = resolved instanceof Struct ? "AnyStruct_" : "AnyMessage_"

const fromCellName = prefix + "fromCell"
const fromCell = index.elementByName(IndexKey.Funs, fromCellName)
if (fromCell) {
const newState = state.withValue(
"search-name",
prefix + this.element.name(),
)
if (!proc.execute(fromCell, newState)) return false
}

const fromSliceName = prefix + "fromSlice"
const fromSlice = index.elementByName(IndexKey.Funs, fromSliceName)
if (fromSlice) {
const newState = state.withValue(
"search-name",
prefix + this.element.name(),
)
if (!proc.execute(fromSlice, newState)) return false
}

return true
}
}

const nodeStruct = index.elementByName(IndexKey.Primitives, "AnyStruct")
if (nodeStruct) {
const structPrimitiveTy = new PrimitiveTy("AnyStruct", nodeStruct, null)
Expand Down Expand Up @@ -418,6 +448,13 @@ export class Reference {
public execute(node: Node, state: ResolveState): boolean {
if (!(node instanceof Fun)) return true
if (node.withSelf()) return true // don't add methods to unqualified completion
if (
node.name().startsWith("AnyStruct_") ||
node.name().startsWith("AnyMessage_")
) {
// this functions in fact static methods
return true
}
return proc.execute(node, state)
}
})()
Expand Down
6 changes: 6 additions & 0 deletions stubs/stubs.tact
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ primitive AnyStruct;
/// Pseudo-type that represents any Message.
primitive AnyMessage;

fun AnyStruct_fromCell(cell: Cell): S {}
fun AnyStruct_fromSlice(slice: Slice): S {}

fun AnyMessage_fromCell(cell: Cell): M {}
fun AnyMessage_fromSlice(slice: Slice): M {}

/// Extension function for any Struct.
///
/// Converts the Struct to a `Cell` and returns it.
Expand Down