Skip to content

Commit

Permalink
feat(all): initial support for a destruct statement (#355)
Browse files Browse the repository at this point in the history
Fixes #251
Fixes #250
  • Loading branch information
i582 authored Feb 23, 2025
1 parent 06cfe11 commit adfd0f0
Show file tree
Hide file tree
Showing 17 changed files with 8,318 additions and 6,568 deletions.
7 changes: 7 additions & 0 deletions server/src/completion/CompletionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class CompletionContext {
public isInitOfName: boolean = false
public afterFieldType: boolean = false
public insideImport: boolean = false
public inDestruct: boolean = false

public contextTy: Ty | null = null

Expand Down Expand Up @@ -186,6 +187,12 @@ export class CompletionContext {
this.insideImport = true
}

if (parent.type === "destruct_bind") {
this.inDestruct = true
this.isExpression = false
this.isStatement = false
}

if (
parent.type === "let_statement" &&
parent.childForFieldName("name")?.equals(this.element.node)
Expand Down
4 changes: 4 additions & 0 deletions server/src/completion/ReferenceCompletionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export class ReferenceCompletionProcessor implements ScopeProcessor {
)
}

if (this.ctx.inDestruct) {
return node instanceof Field
}

// for non types context things like traits and primitives are prohibited
if (node instanceof Trait || node instanceof Primitive) return false
// but since structs and messages can be created like `Foo{}` we allow them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class ReferenceCompletionProvider implements CompletionProvider {

const kind = this.processFields(processor, state, ctx)

// process usual autocompletion for only non instance expressions
// process usual autocompletion for only non-instance expressions
if (kind === CompletionKind.ALL) {
this.ref.processResolveVariants(processor, state.withValue("completion", "true"))
}
Expand Down
53 changes: 53 additions & 0 deletions server/src/e2e/suite/testcases/inlayHints/destruct.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
========================================================================
Simple destruct with single variable
========================================================================
primitive Int;

struct Example {
number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number } = get42();
}
------------------------------------------------------------------------
primitive Int;

struct Example {
number: Int/* as int257 */ = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number/* : Int */ } = get42();
}

========================================================================
Simple destruct with single renamed variable
========================================================================
primitive Int;

struct Example {
number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number: other } = get42();
}
------------------------------------------------------------------------
primitive Int;

struct Example {
number: Int/* as int257 */ = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number: other/* : Int */ } = get42();
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,78 @@ fun foo() {
3 1:8 to 1:9 Variable 'x' is never used (tact)
3 2:8 to 2:9 Variable 'y' is never used (tact)
3 4:8 to 4:9 Variable 'w' is never used (tact)

========================================================================
Unused variable from destruct inspection
========================================================================
primitive Int;

struct Example {
number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number } = get42();
}
------------------------------------------------------------------------
3 9:18 to 9:24 Variable 'number' is never used (tact)

========================================================================
Unused variables from destruct inspection
========================================================================
primitive Int;

struct Example {
number: Int = 0;
other: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number, other } = get42();
}
------------------------------------------------------------------------
3 10:18 to 10:24 Variable 'number' is never used (tact)
3 10:26 to 10:31 Variable 'other' is never used (tact)

========================================================================
Unused variables from destruct inspection 2
========================================================================
primitive Int;

struct Example {
number: Int = 0;
other: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number, other } = get42();
dump(number);
}
------------------------------------------------------------------------
3 10:26 to 10:31 Variable 'other' is never used (tact)

========================================================================
Unused variables from destruct inspection ok
========================================================================
primitive Int;

struct Example {
number: Int = 0;
other: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number, other } = get42();
dump(number);
dump(other);
}
------------------------------------------------------------------------
no issues
36 changes: 36 additions & 0 deletions server/src/e2e/suite/testcases/references/destruct.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
========================================================================
Simple destruct with single variable for field
========================================================================
primitive Int;

struct Example {
<caret>number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number } = get42();
number;
}
------------------------------------------------------------------------
References: []
Scope: GlobalSearchScope

========================================================================
Simple destruct with single renamed variable
========================================================================
primitive Int;

struct Example {
<caret>number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { number: other } = get42();
}
------------------------------------------------------------------------
References: [9:18]
Scope: GlobalSearchScope
39 changes: 39 additions & 0 deletions server/src/e2e/suite/testcases/resolve/destruct.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
========================================================================
Simple destruct with single variable
========================================================================
primitive Int;

struct Example {
number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { <caret>number } = get42();
<caret>number;
}
------------------------------------------------------------------------
9:18 -> 9:18 resolved
10:4 -> 9:18 resolved

========================================================================
Simple destruct with single renamed variable
========================================================================
primitive Int;

struct Example {
number: Int = 0;
}

fun get42(): Example { return Example{} }

fun foo() {
let Example { <caret>number: other } = get42();
<caret>number;
<caret>other;
}
------------------------------------------------------------------------
9:18 -> 3:4 resolved
10:4 unresolved
11:4 -> 9:26 resolved
31 changes: 31 additions & 0 deletions server/src/inlays/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,37 @@ export function collect(
return true
}

if (type === "destruct_bind" && hints.types) {
const name = n.childForFieldName("name")
if (!name) return true

const field = Reference.findDestructField(n, file, name.text)
if (!field) return true

const fieldName = field.nameNode()
if (!fieldName) return true
const type = TypeInferer.inferType(fieldName)
if (!type) return true

// let Foo { name: otherName } = foo()
// ^^^^^^^^^
// or
// let Foo { name } = foo()
// ^^^^
const target = n.childForFieldName("bind") ?? n.childForFieldName("name")
if (!target) return true

result.push({
kind: InlayHintKind.Type,
label: typeHintParts(type),
position: {
line: target.endPosition.row,
character: target.endPosition.column,
},
})
return true
}

if ((type === "field" || type === "storage_variable") && hints.showExplicitTLBIntType) {
const field = new Field(n, file)
const typeNode = field.typeNode()
Expand Down
14 changes: 14 additions & 0 deletions server/src/inspections/UnusedVariableInspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ export class UnusedVariableInspection extends UnusedInspection implements Inspec

protected checkFile(file: File, diagnostics: lsp.Diagnostic[]): void {
RecursiveVisitor.visit(file.rootNode, node => {
if (node.type === "destruct_bind") {
// let Foo { name: otherName } = foo()
// ^^^^^^^^^
// or
// let Foo { name } = foo()
// ^^^^
const target = node.childForFieldName("bind") ?? node.childForFieldName("name")
this.checkUnused(target, file, diagnostics, {
kind: "Variable",
code: "unused-variable",
})
return
}

if (node.type !== "let_statement") {
return
}
Expand Down
Loading

0 comments on commit adfd0f0

Please sign in to comment.