Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
========================================================================
unpackFromSlice correct signature for type alias
========================================================================
type SnakeString = slice

fun SnakeString.unpackFromSlice(mutate s: slice) {
}
------------------------------------------------------------------------
no issues

========================================================================
packToBuilder correct signature for type alias
========================================================================
type SnakeString = slice

fun SnakeString.packToBuilder(self, mutate b: builder) {
}
------------------------------------------------------------------------
no issues

========================================================================
unpackFromSlice for regular struct, should warn
========================================================================
struct RegularStruct {
value: int
}

fun RegularStruct.unpackFromSlice(mutate s: slice) {
}
------------------------------------------------------------------------
3 1:4 to 1:14 Field 'value' is never used (tolk)
3 4:18 to 4:33 Method 'unpackFromSlice' is never used, note, special `unpackFromSlice` method can be used only for type aliases to change serialization/deserialization logic (tolk)

========================================================================
packToBuilder for regular struct, should warn
========================================================================
struct RegularStruct {
value: int
}

fun RegularStruct.packToBuilder(self, mutate b: builder) {
}
------------------------------------------------------------------------
3 1:4 to 1:14 Field 'value' is never used (tolk)
3 4:18 to 4:31 Method 'packToBuilder' is never used, note, special `packToBuilder` method can be used only for type aliases to change serialization/deserialization logic (tolk)

========================================================================
static packToBuilder, should warn
========================================================================
type SnakeString = slice

fun SnakeString.packToBuilder(mutate b: builder) {
}
------------------------------------------------------------------------
3 2:16 to 2:29 Method 'packToBuilder' is never used, if you want custom serialization/deserialization logic, check signature of `packToBuilder` method, it should be like: `fun Type.packToBuilder(self, mutate b: builder)` (tolk)

========================================================================
unpackFromSlice with extra parameters, should warn
========================================================================
type SnakeString = slice

fun SnakeString.unpackFromSlice(mutate s: slice, extra: int) {
}
------------------------------------------------------------------------
3 2:16 to 2:31 Method 'unpackFromSlice' is never used, if you want custom serialization/deserialization logic, check signature of `unpackFromSlice` method, it should be like: `fun Type.unpackFromSlice(mutate s: slice)` (tolk)

========================================================================
packToBuilder with extra parameters, should warn
========================================================================
type SnakeString = slice

fun SnakeString.packToBuilder(self, mutate b: builder, extra: int) {
}
------------------------------------------------------------------------
3 2:16 to 2:29 Method 'packToBuilder' is never used, if you want custom serialization/deserialization logic, check signature of `packToBuilder` method, it should be like: `fun Type.packToBuilder(self, mutate b: builder)` (tolk)

========================================================================
Multiple type aliases with correct special methods
========================================================================
type String1 = slice
type String2 = slice

fun String1.unpackFromSlice(mutate s: slice) {}
fun String1.packToBuilder(self, mutate b: builder) {}
fun String2.unpackFromSlice(mutate s: slice) {}
fun String2.packToBuilder(self, mutate b: builder) {}
------------------------------------------------------------------------
no issues

========================================================================
Mixed correct and incorrect special methods
========================================================================
type String1 = slice
type String2 = slice

fun String1.unpackFromSlice(mutate s: slice) {} // correct
fun String1.packToBuilder(mutate b: builder) {} // wrong: missing self
fun String2.unpackFromSlice(s: slice, o: int) {} // wrong: extra argument
fun String2.packToBuilder(self, mutate b: builder) {} // correct
------------------------------------------------------------------------
3 4:12 to 4:25 Method 'packToBuilder' is never used, if you want custom serialization/deserialization logic, check signature of `packToBuilder` method, it should be like: `fun Type.packToBuilder(self, mutate b: builder)` (tolk)
3 5:12 to 5:27 Method 'unpackFromSlice' is never used, if you want custom serialization/deserialization logic, check signature of `unpackFromSlice` method, it should be like: `fun Type.unpackFromSlice(mutate s: slice)` (tolk)
4 changes: 3 additions & 1 deletion server/src/languages/tolk/inspections/UnusedInspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export abstract class UnusedInspection {
severity?: lsp.DiagnosticSeverity
code?: string
rangeNode?: SyntaxNode | null
additionalText?: string | null
skipIf?: () => boolean
},
): void {
Expand All @@ -41,7 +42,8 @@ export abstract class UnusedInspection {
diagnostics.push({
severity: options.severity ?? lsp.DiagnosticSeverity.Hint,
range,
message: `${options.kind} '${node.text}' is never used`,
message:
`${options.kind} '${node.text}' is never used` + (options.additionalText ?? ""),
source: "tolk",
code: options.code ?? "unused",
tags: [lsp.DiagnosticTag.Unnecessary],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import {
Enum,
Func,
GlobalVariable,
InstanceMethod,
MethodBase,
StaticMethod,
Struct,
TypeAlias,
} from "@server/languages/tolk/psi/Decls"
import {inferenceOf} from "@server/languages/tolk/type-inference"
import {FuncTy, TypeAliasTy} from "@server/languages/tolk/types/ty"

const IMPLICITLY_USED_FUNCTIONS: Set<string> = new Set([
"onInternalMessage",
Expand All @@ -23,8 +28,6 @@ const IMPLICITLY_USED_FUNCTIONS: Set<string> = new Set([
"main",
])

const IMPLICITLY_USED_METHODS: Set<string> = new Set(["unpackFromSlice", "packToBuilder"])

export class UnusedTopLevelDeclarationInspection extends UnusedInspection implements Inspection {
public readonly id: "unused-top-level-declaration" = InspectionIds.UNUSED_TOP_LEVEL_DECLARATION

Expand Down Expand Up @@ -74,7 +77,30 @@ export class UnusedTopLevelDeclarationInspection extends UnusedInspection implem

private inspectMethod(fun: Func, diagnostics: lsp.Diagnostic[]): void {
const name = fun.name()
if (IMPLICITLY_USED_METHODS.has(name)) {
if (this.isUnpackFromSlice(fun) || this.isPackToBuilder(fun)) {
return
}

const isSpecialMethod = name === "packToBuilder" || name === "unpackFromSlice"
if (isSpecialMethod) {
if (this.isMethodOfAlias(fun)) {
const expectedSignature =
name === "packToBuilder" ? "self, mutate b: builder" : "mutate s: slice"

this.checkUnused(fun.nameIdentifier(), fun.file, diagnostics, {
kind: "Method",
code: "unused-method",
additionalText: `, if you want custom serialization/deserialization logic, check signature of \`${name}\` method, it should be like: \`fun Type.${name}(${expectedSignature})\``,
rangeNode: fun.nameIdentifier(),
})
} else {
this.checkUnused(fun.nameIdentifier(), fun.file, diagnostics, {
kind: "Method",
code: "unused-method",
additionalText: `, note, special \`${name}\` method can be used only for type aliases to change serialization/deserialization logic`,
rangeNode: fun.nameIdentifier(),
})
}
return
}

Expand Down Expand Up @@ -140,4 +166,50 @@ export class UnusedTopLevelDeclarationInspection extends UnusedInspection implem
})
}
}

private isMethodOfAlias(fun: Func): boolean {
if (!(fun instanceof MethodBase)) return false

const inference = inferenceOf(fun.node, fun.file)
if (!inference) return false

const receiverTy = inference.typeOf(fun.receiverTypeNode())
if (!receiverTy) return false

return receiverTy instanceof TypeAliasTy
}

private isUnpackFromSlice(fun: Func): boolean {
return this.isMethod(fun, "unpackFromSlice", true, "slice")
}

private isPackToBuilder(fun: Func): boolean {
return this.isMethod(fun, "packToBuilder", false, "builder")
}

private isMethod(fun: Func, name: string, isStatic: boolean, expectedParams: string): boolean {
const hasName = fun.name() === name
if (!hasName) return false

if (!(fun instanceof MethodBase)) return false
if (isStatic && fun instanceof InstanceMethod) return false
if (!isStatic && fun instanceof StaticMethod) return false

const inference = inferenceOf(fun.node, fun.file)
if (!inference) return hasName

const actualFuncTy = inference.typeOf(fun.node)
if (!actualFuncTy || !(actualFuncTy instanceof FuncTy)) return hasName

const receiverTy = inference.typeOf(fun.receiverTypeNode())
if (!receiverTy) return false

const params =
fun instanceof InstanceMethod ? actualFuncTy.params.slice(1) : actualFuncTy.params

return (
receiverTy instanceof TypeAliasTy &&
params.map(it => it.name()).join(", ") === expectedParams
)
}
}
3 changes: 2 additions & 1 deletion server/src/languages/tolk/type-inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2586,7 +2586,8 @@ export class SinkExpression {
export class InferenceResult {
public constructor(public ctx: InferenceContext) {}

public typeOf(node: SyntaxNode): Ty | null {
public typeOf(node: SyntaxNode | null): Ty | null {
if (!node) return null
return this.ctx.getType(node)
}

Expand Down
Loading