diff --git a/build.gradle.kts b/build.gradle.kts index e678422..399855f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import org.gradle.configurationcache.extensions.capitalized +import org.jetbrains.changelog.Changelog import org.jetbrains.grammarkit.tasks.GenerateLexerTask import org.jetbrains.grammarkit.tasks.GenerateParserTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -113,9 +114,9 @@ tasks { sinceBuild.set("231") untilBuild.set("") changeNotes.set(provider { - changelog.run { + changelog.renderItem(changelog.run { getLatest() - }.toHTML() + }, Changelog.OutputType.HTML) }) } buildSearchableOptions { diff --git a/gradle.properties b/gradle.properties index f461e42..97b9f84 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx4096m pluginGroup=org.ton -pluginVersion=2.2.0 +pluginVersion=2.2.1 publishChannel=release publishToken=token enableBuildSearchableOptions=false diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactDotCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactDotCompletionProvider.kt index 2f2caed..b3934ae 100644 --- a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactDotCompletionProvider.kt +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactDotCompletionProvider.kt @@ -61,7 +61,7 @@ class TactDotCompletionProvider : TactCompletionProvider() { if (function != null) { result.addElement( LookupElementBuilder.createWithIcon(function) - .withTypeText(function.selfType?.toString() ?: "") + .withTypeText(function.type?.ty?.toString() ?: "") .withInsertHandler { context, item -> context.editor.document.insertString(context.editor.caretModel.offset, "()") context.editor.caretModel.moveToOffset(context.editor.caretModel.offset + 2) diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactReferenceCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactReferenceCompletionProvider.kt index 5a63638..9b5d493 100644 --- a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactReferenceCompletionProvider.kt +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactReferenceCompletionProvider.kt @@ -10,6 +10,7 @@ import org.ton.intellij.tact.psi.TactReferenceExpression import org.ton.intellij.tact.psi.impl.isGet import org.ton.intellij.tact.stub.index.TactFunctionIndex import org.ton.intellij.tact.type.TactLookup +import org.ton.intellij.tact.type.ty import org.ton.intellij.util.ancestorStrict import org.ton.intellij.util.processAllKeys @@ -34,8 +35,12 @@ class TactReferenceCompletionProvider : TactCompletionProvider() { TactFunctionIndex.findElementsByName(project, key).asSequence() .filter { !it.isGet } .distinctBy { it.name } - .forEach { - result.addElement(LookupElementBuilder.createWithIcon(it)) + .forEach { function -> + result.addElement( + LookupElementBuilder + .createWithIcon(function) + .withTypeText(function.type?.ty?.toString() ?: "") + ) } true } diff --git a/src/main/kotlin/org/ton/intellij/tact/inspections/TactUnresolvedReferenceInspection.kt b/src/main/kotlin/org/ton/intellij/tact/inspections/TactUnresolvedReferenceInspection.kt index e20dde2..f01029b 100644 --- a/src/main/kotlin/org/ton/intellij/tact/inspections/TactUnresolvedReferenceInspection.kt +++ b/src/main/kotlin/org/ton/intellij/tact/inspections/TactUnresolvedReferenceInspection.kt @@ -11,12 +11,12 @@ import org.ton.intellij.tact.psi.TactVisitor class TactUnresolvedReferenceInspection : TactLocalInspectionTool() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = object : TactVisitor() { override fun visitReferenceExpression(o: TactReferenceExpression) { - val reference = o.reference ?: return holder.registerProblem(o) + val reference = o.reference ?: return reference.resolve() ?: return holder.registerProblem(o) } override fun visitCallExpression(o: TactCallExpression) { - val reference = o.reference ?: return holder.registerProblem(o) + val reference = o.reference ?: return reference.resolve() ?: return holder.registerProblem(o) } } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCallExpressionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCallExpressionImplMixin.kt index 495a9cc..52bb91f 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCallExpressionImplMixin.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCallExpressionImplMixin.kt @@ -3,11 +3,66 @@ package org.ton.intellij.tact.psi.impl import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.PsiReference -import org.ton.intellij.tact.psi.TactCallExpression +import org.ton.intellij.tact.psi.* import org.ton.intellij.tact.resolve.TactFieldReference +import org.ton.intellij.tact.type.TactTyMap +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.tact.type.selfInferenceResult +import org.ton.intellij.util.ancestorStrict abstract class TactCallExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactCallExpression { - override fun getReference(): PsiReference { - return TactFieldReference(this, identifier.textRangeInParent) + override fun getReference(): PsiReference? { + val identifier = identifier + val ref = TactFieldReference(this, identifier.textRangeInParent) + when (identifier.text) { + "ton", + "pow", + "require", + "address", + "cell", + "dump", + "emptyMap", + "sha256" -> { + if (isStaticCall()) { + return null + } + } + + "get", + "set", + "asCell" -> { + val parent = parent + if (parent is TactDotExpression) { + val leftTy = parent.expressionList.firstOrNull()?.let { left -> + parent.ancestorStrict()?.selfInferenceResult?.getExprTy(left) + } + if (leftTy is TactTyMap) { + return null + } + } + } + + "toCell" -> { + val parent = parent + if (parent is TactDotExpression) { + val leftTy = parent.expressionList.firstOrNull()?.let { left -> + parent.ancestorStrict()?.selfInferenceResult?.getExprTy(left) + } + if (leftTy is TactTyRef && (leftTy.item is TactStruct || leftTy.item is TactMessage)) { + return null + } + } + } + } + return ref + } +} + +fun TactCallExpression.isStaticCall(): Boolean { + val parent = parent + return if (parent is TactDotExpression) { + parent.expressionList.firstOrNull() == this + } else { + true } } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferencedTypeImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferencedTypeImplMixin.kt index cc43f28..4c277d9 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferencedTypeImplMixin.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferencedTypeImplMixin.kt @@ -2,12 +2,15 @@ package org.ton.intellij.tact.psi.impl import com.intellij.lang.ASTNode import com.intellij.psi.PsiReference +import org.ton.intellij.tact.psi.TactMapType import org.ton.intellij.tact.psi.TactReferencedType import org.ton.intellij.tact.psi.TactType import org.ton.intellij.tact.psi.TactTypeDeclarationElement import org.ton.intellij.tact.resolve.TactTypeReference import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyMap import org.ton.intellij.tact.type.TactTyNullable +import org.ton.intellij.tact.type.TactTyUnknown abstract class TactReferencedTypeImplMixin( node: ASTNode @@ -16,10 +19,18 @@ abstract class TactReferencedTypeImplMixin( } val TactType.ty: TactTy? - get() { - val reference = reference ?: return null - val typeDeclaration = reference.resolve() as? TactTypeDeclarationElement ?: return null - val declaredTy = typeDeclaration.declaredTy - if (this is TactReferencedType && q != null) return TactTyNullable(declaredTy) - return declaredTy + get() = getType(this) + +private fun getType(tactType: TactType): TactTy? { + if (tactType is TactMapType) { + val mapTypeItemList = tactType.mapTypeItemList + val keyType = mapTypeItemList.getOrNull(0)?.referencedType?.ty ?: TactTyUnknown + val valueType = mapTypeItemList.getOrNull(1)?.referencedType?.ty ?: TactTyUnknown + return TactTyMap(keyType, valueType) } + val reference = tactType.reference ?: return null + val typeDeclaration = reference.resolve() as? TactTypeDeclarationElement ?: return null + val declaredTy = typeDeclaration.declaredTy + if (tactType is TactReferencedType && tactType.q != null) return TactTyNullable(declaredTy) + return declaredTy +} diff --git a/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt b/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt index b989932..3b9ebdf 100644 --- a/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt @@ -11,12 +11,10 @@ class TactTypeReference(element: T, range: TextRange) : TactRef override fun multiResolve(): Collection { val currentFile = element.containingFile val result = TactTypesIndex.findElementsByName(element.project, value) - val localType = result.asSequence() - .filterIsInstance() - .find { it.containingFile == currentFile } + val localType = result.find { it.containingFile == currentFile } if (localType != null) { return listOf(localType) } - return listOf(result.firstOrNull() as? TactTypeDeclarationElement ?: return emptyList()) + return listOf(result.firstOrNull() ?: return emptyList()) } } diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt index e056736..bdb9d7f 100644 --- a/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt @@ -69,7 +69,7 @@ data class TactTyMap( override fun toString(): String = "map<$key, $value>" override fun isAssignable(other: TactTy): Boolean { - return other is TactTyMap && key.isAssignable(other.key) && value.isAssignable(other.value) + return other is TactTyNull || (other is TactTyMap && key.isAssignable(other.key) && value.isAssignable(other.value)) } } diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt index 6b03cf3..02ccc8e 100644 --- a/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt @@ -6,6 +6,7 @@ import com.intellij.util.containers.OrderedSet import org.ton.intellij.tact.diagnostics.TactDiagnostic import org.ton.intellij.tact.psi.* import org.ton.intellij.tact.psi.impl.isGet +import org.ton.intellij.tact.psi.impl.isStaticCall import org.ton.intellij.tact.psi.impl.ty import org.ton.intellij.tact.stub.index.TactFunctionIndex import org.ton.intellij.tact.stub.index.TactTypesIndex @@ -60,15 +61,16 @@ class TactTypeInferenceWalker( val lValueTy = lValue?.inferType() val rValueTy = rValue?.inferType() if (lValueTy != null && rValueTy != null && !lValueTy.isAssignable(rValueTy)) { - ctx.reportTypeMismatch(this, lValueTy, rValueTy) + ctx.reportTypeMismatch(rValue, lValueTy, rValueTy) } } is TactExpressionStatement -> expression.inferType() is TactConditionStatement -> { + val condition = condition val conditionTy = condition?.expression?.inferType() if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { - ctx.reportTypeMismatch(this, object : TactTy { + ctx.reportTypeMismatch(condition, object : TactTy { override fun toString(): String = "Bool" override fun isAssignable(other: TactTy): Boolean = false }, conditionTy) @@ -80,9 +82,10 @@ class TactTypeInferenceWalker( is TactReturnStatement -> expression?.inferType() is TactWhileStatement -> { + val condition = condition val conditionTy = condition?.expression?.inferType() if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { - ctx.reportTypeMismatch(this, object : TactTy { + ctx.reportTypeMismatch(condition, object : TactTy { override fun toString(): String = "Bool" override fun isAssignable(other: TactTy): Boolean = false }, conditionTy) @@ -92,9 +95,10 @@ class TactTypeInferenceWalker( is TactUntilStatement -> { block?.inferType() + val condition = condition val conditionTy = condition?.expression?.inferType() if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { - ctx.reportTypeMismatch(this, object : TactTy { + ctx.reportTypeMismatch(condition, object : TactTy { override fun toString(): String = "Bool" override fun isAssignable(other: TactTy): Boolean = false }, conditionTy) @@ -102,9 +106,10 @@ class TactTypeInferenceWalker( } is TactRepeatStatement -> { + val condition = condition val conditionTy = condition?.expression?.inferType() if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Int")) { - ctx.reportTypeMismatch(this, object : TactTy { + ctx.reportTypeMismatch(condition, object : TactTy { override fun toString(): String = "Int" override fun isAssignable(other: TactTy): Boolean = false }, conditionTy) @@ -166,9 +171,45 @@ class TactTypeInferenceWalker( } private fun TactBinExpression.inferType(): TactTy? { - return expressionList.map { - it.inferType() - }.firstOrNull() + val expressions = expressionList + val operator = binOp + val left = expressions.getOrNull(0) + val right = expressions.getOrNull(1) + val leftTy = left?.inferType() + val rightTy = right?.inferType() + + var binTy: TactTy? = null + var exprTy: TactTy? = null + when (operator.text) { + ">", ">=", "<", "<=" -> { + binTy = TactTy.search(project, "Bool").firstOrNull() + exprTy = TactTy.search(project, "Int").firstOrNull() + } + + "==", "!=" -> { + binTy = TactTy.search(project, "Bool").firstOrNull() + } + + "||", "&&" -> { + binTy = TactTy.search(project, "Bool").firstOrNull() + exprTy = binTy + } + + ">>", "<<", "&", "|", "+", "-", "*", "/", "%" -> { + binTy = TactTy.search(project, "Int").firstOrNull() + exprTy = binTy + } + } + if (leftTy != null && rightTy != null && exprTy != null) { + if (!leftTy.isAssignable(exprTy)) { + ctx.reportTypeMismatch(this, exprTy, leftTy) + } + if (!rightTy.isAssignable(exprTy)) { + ctx.reportTypeMismatch(this, exprTy, rightTy) + } + } + + return binTy } private fun TactSelfExpression.inferType(): TactTy? = getParentFunction()?.selfType @@ -212,33 +253,61 @@ class TactTypeInferenceWalker( } is TactCallExpression -> { - var returnTy: TactTy? = null - if (leftType is TactTyRef) { - val name = right.identifier.text + right.expressionList.forEach { + it.inferType() + } + when (leftType) { + is TactTyRef -> { + val name = right.identifier.text - val function = TactFunctionIndex.findElementsByName(project, name).find { - it.selfType?.isAssignable(leftType) == true + if (name == "toCell" && (leftType.item is TactStruct || leftType.item is TactMessage)) { + return TactTy.search(project, "Cell").firstOrNull() + } + + val function = TactFunctionIndex.findElementsByName(project, name).find { + it.selfType?.isAssignable(leftType) == true + } + + if (function != null) { + ctx.setResolvedRefs(right, OrderedSet(listOf(PsiElementResolveResult(function)))) + return function.type?.ty + } else { + val members = leftType.item.members.toList() + val resolvedFunction = members.find { it.name == name } as? TactFunction + if (resolvedFunction != null) { + ctx.setResolvedRefs( + right, + OrderedSet(listOf(PsiElementResolveResult(resolvedFunction))) + ) + return resolvedFunction.type?.ty + } + } } - if (function != null) { - ctx.setResolvedRefs(right, OrderedSet(listOf(PsiElementResolveResult(function)))) - returnTy = function.type?.ty - } else { - val members = leftType.item.members.toList() - val resolvedFunction = members.find { it.name == name } as? TactFunction - if (resolvedFunction != null) { - ctx.setResolvedRefs(right, OrderedSet(listOf(PsiElementResolveResult(resolvedFunction)))) - returnTy = resolvedFunction.type?.ty + is TactTyMap -> { + val name = right.identifier.text + when (name) { + "set" -> { + return TactTyVoid + } + + "get" -> { + return leftType.value.let { + if (it is TactTyNullable) it + else TactTyNullable(it) + } + } + + "asCell" -> { + return TactTy.search(project, "Cell").firstOrNull()?.let { + TactTyNullable(it) + } + } } } } - right.expressionList.forEach { - it.inferType() - } - return returnTy } } - return null } @@ -259,6 +328,20 @@ class TactTypeInferenceWalker( expressionList.forEach { it.inferType() } val name = identifier.text + + if (isStaticCall()) { + when (name) { + "ton" -> return TactTy.search(project, "Int").firstOrNull() + "pow" -> return TactTy.search(project, "Int").firstOrNull() + "require" -> return TactTyVoid + "address" -> return TactTy.search(project, "Address").firstOrNull() + "cell" -> return TactTy.search(project, "Cell").firstOrNull() + "dump" -> return TactTyVoid + "emptyMap" -> return TactTyNull + "sha256" -> return TactTy.search(project, "Int").firstOrNull() + } + } + var candidates = TactFunctionIndex.findElementsByName(project, name) if (candidates.isNotEmpty()) { if (candidates.size > 1) {