From daf1900aa32b2a3495723673a1e5f6a4d6197fcc Mon Sep 17 00:00:00 2001 From: Andrey Pfau Date: Wed, 10 Jul 2024 20:30:58 +0400 Subject: [PATCH] Support Tact 1.4.0, closes #180 --- src/main/grammar/TactLexer.flex | 5 +- src/main/grammar/TactParser.bnf | 60 ++++++++--- .../tact/diagnostics/TactDiagnostic.kt | 19 +++- .../psi/impl/TactCallExpressionImplMixin.kt | 1 + .../tact/psi/impl/TactCatchParameterMixin.kt | 22 ++++ .../tact/psi/impl/TactForEachKeyMixin.kt | 22 ++++ .../tact/psi/impl/TactForEachValueMixin.kt | 22 ++++ .../tact/resolve/TactTypeReference.kt | 10 +- .../org/ton/intellij/tact/type/TactTy.kt | 16 ++- .../ton/intellij/tact/type/TactTyInference.kt | 9 +- .../tact/type/TactTypeInferenceWalker.kt | 102 +++++++++++++----- 11 files changed, 227 insertions(+), 61 deletions(-) create mode 100644 src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCatchParameterMixin.kt create mode 100644 src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachKeyMixin.kt create mode 100644 src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachValueMixin.kt diff --git a/src/main/grammar/TactLexer.flex b/src/main/grammar/TactLexer.flex index 5f3efc6..3f8f51b 100644 --- a/src/main/grammar/TactLexer.flex +++ b/src/main/grammar/TactLexer.flex @@ -139,6 +139,9 @@ ESCAPE_SEQUENCE=\\\\ // backslash "*=" { return TIMESLET; } "/=" { return DIVLET; } "%=" { return MODLET; } + "&=" { return ANDLET; } + "|=" { return ORLET; } + "^=" { return XORLET; } "==" { return EQEQ; } "!=" { return EXCLEQ; } ">=" { return GTEQ; } @@ -185,7 +188,7 @@ ESCAPE_SEQUENCE=\\\\ // backslash "catch" { return CATCH_KEYWORD; } "foreach" { return FOREACH_KEYWORD; } "in" { return IN_KEYWORD; } - "bounced" { return zzBlockDepth == 1 && zzContractScope ? BOUNCED_KEYWORD : IDENTIFIER; } + "bounced" { return BOUNCED_KEYWORD; } "init" { return zzBlockDepth == 1 && zzParenDepth == 0 ? INIT_KEYWORD : IDENTIFIER; } "get" { return zzBlockDepth <= 1 ? GET_KEYWORD : IDENTIFIER; } "@interface" { return INTERFACE_MACRO; } diff --git a/src/main/grammar/TactParser.bnf b/src/main/grammar/TactParser.bnf index 00caf73..72ee1ba 100644 --- a/src/main/grammar/TactParser.bnf +++ b/src/main/grammar/TactParser.bnf @@ -63,6 +63,9 @@ TIMESLET = '*=' DIVLET = '/=' MODLET = '%=' + ANDLET = '&=' + ORLET = '|=' + XORLET = '^=' IF_KEYWORD = 'if' ELSE_KEYWORD = 'else' @@ -174,7 +177,7 @@ As ::= 'as' IDENTIFIER { } // Field -Field ::= IDENTIFIER TypeAscription As? Assigment? ';' { +Field ::= IDENTIFIER TypeAscription As? Assigment? { pin=1 implements = [ "org.ton.intellij.tact.psi.TactNameIdentifierOwner" @@ -225,7 +228,7 @@ Message ::= 'message' MessageId? IDENTIFIER BlockFields { MessageId ::= '(' INTEGER_LITERAL ')' { pin=1 } -BlockFields ::= '{' Field* '}' {pin=1} +BlockFields ::= '{' [<>] '}' {pin=1} // Contract WithClause ::= 'with' <> {pin=1} @@ -245,7 +248,7 @@ private ContractItem_with_recover ::= !('}' | <>) ContractItem { // recoverWhile=ContractItem_recover } private ContractItem_recover ::= !('}' | Item_first | IDENTIFIER) -private ContractItem ::= Field +private ContractItem ::= StorageVariable | Constant | ContractInit | ReceiveFunction @@ -253,6 +256,8 @@ private ContractItem ::= Field | ExternalFunction | Function +private StorageVariable ::= Field ';' + ContractAttribute ::= '@interface' StringId {pin=1} ContractInit ::= 'init' FunctionParameters Block { pin=1 @@ -276,7 +281,7 @@ Trait ::= ContractAttribute* 'trait' IDENTIFIER WithClause? TraitBody { TraitBody ::= '{' TraitItem* '}' { pin = 1 } -private TraitItem ::= Field +private TraitItem ::= StorageVariable | Constant | ReceiveFunction | BouncedFunction @@ -365,28 +370,28 @@ private BlockItem ::= !'}' Statement { } private BlockItem_recover ::= !('}' | Statement_first | Item_first | ';') -LetStatement ::= 'let' IDENTIFIER TypeAscription '=' Expression ';' { +LetStatement ::= 'let' (IDENTIFIER|'_') TypeAscription? '=' Expression ';' { pin = 1 rightAssociative=true implements=[ "org.ton.intellij.tact.psi.TactNamedElement" ] - mixin="org.ton.intellij.tact.psi.impl.TactLetStatementImplMixin" + mixin="org.ton.intellij.tact.psi.impl.TactLetStatementImplMixin" } private TypeAscription ::= ':' Type { pin = 1 } BlockStatement ::= Block -ReturnStatement ::= 'return' Expression? ';' { pin = 1 } -ExpressionStatement ::= Expression ';' { +ReturnStatement ::= 'return' Expression? ';'? { pin = 1 } +ExpressionStatement ::= Expression (';' | &'}') { pin=1 } AssignStatement ::= Expression AssignBinOp Expression ';' { rightAssociative = true } -AssignBinOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '%=' { +AssignBinOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' { name = "operator" } @@ -396,12 +401,28 @@ WhileStatement ::= 'while' Condition Block {pin=1} RepeatStatement ::= 'repeat' Condition Block {pin=1} UntilStatement ::= 'do' Block 'until' Condition ';' {pin=1} TryStatement ::= 'try' Block CatchClause? {pin=1} -CatchClause ::= 'catch' '(' IDENTIFIER ')' Block {pin=1} -ForEachStatement ::= 'foreach' '(' IDENTIFIER ',' IDENTIFIER 'in' Expression ')' Block {pin=1} - -Condition ::= '(' Expression ')' { - pin=1 +CatchClause ::= 'catch' '(' CatchParameter ')' Block {pin=1} +CatchParameter ::= IDENTIFIER { + mixin = "org.ton.intellij.tact.psi.impl.TactCatchParameterMixin" + implements = [ + "org.ton.intellij.tact.psi.TactNamedElement" + ] +} +ForEachStatement ::= 'foreach' '(' ForEachKey ',' ForEachValue 'in' Expression ')' Block {pin=1} +ForEachKey ::= IDENTIFIER { + mixin = "org.ton.intellij.tact.psi.impl.TactForEachKeyMixin" + implements = [ + "org.ton.intellij.tact.psi.TactNamedElement" + ] } +ForEachValue ::= IDENTIFIER { + mixin = "org.ton.intellij.tact.psi.impl.TactForEachValueMixin" + implements = [ + "org.ton.intellij.tact.psi.TactNamedElement" + ] +} + +Condition ::= Expression // Expressions Expression ::= TernaryExpression @@ -497,10 +518,14 @@ FieldExpression ::= IDENTIFIER !'(' { ParenExpression ::= '(' ParenExpressionItem ')' {pin=1} private ParenExpressionItem ::= Expression -StructExpression ::= IDENTIFIER '{' [<>] '}' { - pin=2 +StructExpression ::= IDENTIFIER '{' StructExpressionFields '}' { mixin = "org.ton.intellij.tact.psi.impl.TactStructExpressionImplMixin" } + +private StructExpressionFields ::= <> { + +} + StructExpressionField ::= IDENTIFIER (':' Expression)? {pin=1} private StructExpressionField_with_recover ::= !('}') StructExpressionField { pin=1 @@ -523,4 +548,5 @@ InitOfExpression ::= 'initOf' IDENTIFIER '(' [<> ( ',' <> )* -private meta trailing_comma_separated_list ::= <> (',' (<> | &')'))* +private meta trailing_comma_separated_list ::= <> (',' (<> | &')' | &'}'))* +private meta trailing_semicolon_separated_list ::= <> (';' (<> | &'}'))* diff --git a/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt b/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt index b158bbf..0bca0f3 100644 --- a/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt +++ b/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt @@ -7,6 +7,7 @@ import org.ton.intellij.tact.inspections.TactLocalInspectionTool import org.ton.intellij.tact.inspections.TactTypeCheckInspection import org.ton.intellij.tact.psi.TactNamedElement import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyRef import org.ton.intellij.util.PreparedAnnotation sealed class TactDiagnostic( @@ -48,7 +49,23 @@ sealed class TactDiagnostic( override fun prepare(): PreparedAnnotation = PreparedAnnotation( ProblemHighlightType.GENERIC_ERROR, "Type mismatch", - "expected `$expectedTy`, found `$actualTy`", + buildString { + val expectedName = expectedTy.toString() + val actualName = actualTy.toString() + if (expectedTy is TactTyRef && actualTy is TactTyRef && expectedName == actualName) { + append("expected: `") + append(expectedName) + append("` [") + append(expectedTy.item.containingFile.virtualFile.path) + append("], found: `") + append(actualName) + append("` [") + append(actualTy.item.containingFile.virtualFile.path) + append("]") + } else { + append("expected `$expectedName`, found `$actualName`") + } + }, fixes = buildList { } 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 52bb91f..0e5406c 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 @@ -30,6 +30,7 @@ abstract class TactCallExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement "get", "set", + "del", "asCell" -> { val parent = parent if (parent is TactDotExpression) { diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCatchParameterMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCatchParameterMixin.kt new file mode 100644 index 0000000..b02a532 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCatchParameterMixin.kt @@ -0,0 +1,22 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactCatchParameter +import org.ton.intellij.tact.psi.TactPsiFactory +import javax.swing.Icon + +abstract class TactCatchParameterMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactCatchParameter { + override fun getName(): String = identifier.text + + override fun setName(name: String): PsiElement { + (identifier).replace(TactPsiFactory(project).createIdentifier(name)) + return this + } + + override fun getTextOffset(): Int = identifier.textOffset + + override fun getIcon(flags: Int): Icon = TactIcons.VARIABLE +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachKeyMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachKeyMixin.kt new file mode 100644 index 0000000..5ce7d31 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachKeyMixin.kt @@ -0,0 +1,22 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactForEachKey +import org.ton.intellij.tact.psi.TactPsiFactory +import javax.swing.Icon + +abstract class TactForEachKeyMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactForEachKey { + override fun getName(): String? = identifier.text + + override fun setName(name: String): PsiElement { + identifier.replace(TactPsiFactory(project).createIdentifier(name)) + return this + } + + override fun getTextOffset(): Int = identifier.textOffset + + override fun getIcon(flags: Int): Icon = TactIcons.VARIABLE +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachValueMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachValueMixin.kt new file mode 100644 index 0000000..3d21ef6 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactForEachValueMixin.kt @@ -0,0 +1,22 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactForEachValue +import org.ton.intellij.tact.psi.TactPsiFactory +import javax.swing.Icon + +abstract class TactForEachValueMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactForEachValue { + override fun getName(): String? = identifier.text + + override fun setName(name: String): PsiElement { + identifier.replace(TactPsiFactory(project).createIdentifier(name)) + return this + } + + override fun getTextOffset(): Int = identifier.textOffset + + override fun getIcon(flags: Int): Icon = TactIcons.VARIABLE +} 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 3b9ebdf..fe7ae23 100644 --- a/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt @@ -3,18 +3,12 @@ package org.ton.intellij.tact.resolve import com.intellij.openapi.util.TextRange import org.ton.intellij.tact.psi.TactElement import org.ton.intellij.tact.psi.TactTypeDeclarationElement -import org.ton.intellij.tact.stub.index.TactTypesIndex +import org.ton.intellij.tact.type.TactTy class TactTypeReference(element: T, range: TextRange) : TactReferenceBase( element, range ) { override fun multiResolve(): Collection { - val currentFile = element.containingFile - val result = TactTypesIndex.findElementsByName(element.project, value) - val localType = result.find { it.containingFile == currentFile } - if (localType != null) { - return listOf(localType) - } - return listOf(result.firstOrNull() ?: return emptyList()) + return listOf(TactTy.searchDeclarations(element, value).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 bdb9d7f..b6159f4 100644 --- a/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt @@ -1,6 +1,6 @@ package org.ton.intellij.tact.type -import com.intellij.openapi.project.Project +import org.ton.intellij.tact.psi.TactElement import org.ton.intellij.tact.psi.TactReferencedType import org.ton.intellij.tact.psi.TactType import org.ton.intellij.tact.psi.TactTypeDeclarationElement @@ -12,10 +12,18 @@ interface TactTy { fun isAssignable(other: TactTy): Boolean companion object { - fun search(project: Project, name: String): List { - return TactTypesIndex.findElementsByName(project, name).map { - it.declaredTy + fun search(element: TactElement, name: String): List { + return searchDeclarations(element, name).map { it.declaredTy } + } + + fun searchDeclarations(element: TactElement, name: String): List { + val types = TactTypesIndex.findElementsByName(element.project, name) + val currentFile = element.containingFile + val localType = types.find { it.containingFile == currentFile } + if (localType != null) { + return listOf(localType) } + return types.toList() } } } diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt index 699cfa9..400b95c 100644 --- a/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt @@ -116,13 +116,20 @@ class TactInferenceContext( } } } - + is TactForEachStatement -> { + scope.forEachKey?.let { variableCandidates.add(it) } + scope.forEachValue?.let { variableCandidates.add(it) } + } is TactFunctionLike -> { scope.functionParameters?.functionParameterList?.forEach { param -> variableCandidates.add(param) } return@treeWalkUp false } + + is TactCatchClause -> { + scope.catchParameter?.let { variableCandidates.add(it) } + } } true } 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 02ccc8e..f64a41d 100644 --- a/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt @@ -37,8 +37,8 @@ class TactTypeInferenceWalker( private fun TactStatement.inferType() { when (this) { is TactLetStatement -> { - val variableTy = type?.ty val expressionTy = expression?.inferType() + val variableTy = type?.ty ?: expressionTy if (variableTy != null && expressionTy != null && !expressionTy.isAssignable(variableTy)) { ctx.reportTypeMismatch(this, variableTy, expressionTy) } @@ -116,9 +116,55 @@ class TactTypeInferenceWalker( } block?.inferType() } + + is TactForEachStatement -> { + val expression = expression + val expressionTy = expression?.inferType() + if (expressionTy != null) { + if (expressionTy !is TactTyMap) { + ctx.reportTypeMismatch(expression, object : TactTy { + override fun toString(): String = "map" + override fun isAssignable(other: TactTy): Boolean = false + }, expressionTy) + } else { + val keyTy = expressionTy.key + val key = forEachKey + val keyName = key?.name + if (keyName != null) { + addVariable(keyName, key, keyTy) + } + + val valueTy = expressionTy.value + val value = forEachValue + val valueName = value?.name + if (valueName != null) { + addVariable(valueName, value, valueTy) + } + } + } + block?.inferType() + } + + is TactTryStatement -> { + block?.inferType() + catchClause?.inferType() + } } } + private fun TactCatchClause.inferType(): TactTy? { + val parameter = catchParameter + val name = parameter?.name + val paramTy = parameter?.inferType() + if (name != null && paramTy != null) { + addVariable(name, parameter, paramTy) + } + block?.inferType() + return TactTyVoid + } + + private fun TactCatchParameter.inferType(): TactTy? = TactTy.search(this, "Int").firstOrNull() + private fun TactExpression.inferType(): TactTy? { ProgressManager.checkCanceled() var ty = ctx.getExprTy(this) @@ -182,21 +228,21 @@ class TactTypeInferenceWalker( var exprTy: TactTy? = null when (operator.text) { ">", ">=", "<", "<=" -> { - binTy = TactTy.search(project, "Bool").firstOrNull() - exprTy = TactTy.search(project, "Int").firstOrNull() + binTy = TactTy.search(this, "Bool").firstOrNull() + exprTy = TactTy.search(this, "Int").firstOrNull() } "==", "!=" -> { - binTy = TactTy.search(project, "Bool").firstOrNull() + binTy = TactTy.search(this, "Bool").firstOrNull() } "||", "&&" -> { - binTy = TactTy.search(project, "Bool").firstOrNull() + binTy = TactTy.search(this, "Bool").firstOrNull() exprTy = binTy } ">>", "<<", "&", "|", "+", "-", "*", "/", "%" -> { - binTy = TactTy.search(project, "Int").firstOrNull() + binTy = TactTy.search(this, "Int").firstOrNull() exprTy = binTy } } @@ -261,7 +307,7 @@ class TactTypeInferenceWalker( val name = right.identifier.text if (name == "toCell" && (leftType.item is TactStruct || leftType.item is TactMessage)) { - return TactTy.search(project, "Cell").firstOrNull() + return TactTy.search(this, "Cell").firstOrNull() } val function = TactFunctionIndex.findElementsByName(project, name).find { @@ -287,21 +333,16 @@ class TactTypeInferenceWalker( 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) - } + "set" -> return TactTyVoid + "del" -> return TactTy.search(this, "Bool").firstOrNull() + "isEmpty" -> return TactTy.search(this, "Bool").firstOrNull() + "get" -> return leftType.value.let { + if (it is TactTyNullable) it + else TactTyNullable(it) } - "asCell" -> { - return TactTy.search(project, "Cell").firstOrNull()?.let { - TactTyNullable(it) - } + "asCell" -> return TactTy.search(this, "Cell").firstOrNull()?.let { + TactTyNullable(it) } } } @@ -319,7 +360,10 @@ class TactTypeInferenceWalker( } return when (val candidate = candidates.firstOrNull()) { is TactFunctionParameter -> candidate.type?.ty - is TactLetStatement -> candidate.type?.ty + is TactLetStatement -> candidate.type?.ty ?: candidate.expression?.inferType() + is TactForEachKey -> (candidate.parentOfType()?.expression?.inferType() as? TactTyMap)?.key + is TactForEachValue -> (candidate.parentOfType()?.expression?.inferType() as? TactTyMap)?.value + is TactCatchParameter -> candidate.inferType() else -> null } } @@ -331,14 +375,14 @@ class TactTypeInferenceWalker( if (isStaticCall()) { when (name) { - "ton" -> return TactTy.search(project, "Int").firstOrNull() - "pow" -> return TactTy.search(project, "Int").firstOrNull() + "ton" -> return TactTy.search(this, "Int").firstOrNull() + "pow" -> return TactTy.search(this, "Int").firstOrNull() "require" -> return TactTyVoid - "address" -> return TactTy.search(project, "Address").firstOrNull() - "cell" -> return TactTy.search(project, "Cell").firstOrNull() + "address" -> return TactTy.search(this, "Address").firstOrNull() + "cell" -> return TactTy.search(this, "Cell").firstOrNull() "dump" -> return TactTyVoid "emptyMap" -> return TactTyNull - "sha256" -> return TactTy.search(project, "Int").firstOrNull() + "sha256" -> return TactTy.search(this, "Int").firstOrNull() } } @@ -383,15 +427,15 @@ class TactTypeInferenceWalker( } private fun TactStringExpression.inferType(): TactTy? { - return TactTyRef(TactTypesIndex.findElementsByName(project, "String").firstOrNull() ?: return null) + return TactTy.search(this, "String").firstOrNull() } private fun TactIntegerExpression.inferType(): TactTy? { - return TactTyRef(TactTypesIndex.findElementsByName(project, "Int").firstOrNull() ?: return null) + return TactTy.search(this, "Int").firstOrNull() } private fun TactBooleanExpression.inferType(): TactTy? { - return TactTyRef(TactTypesIndex.findElementsByName(project, "Bool").firstOrNull() ?: return null) + return TactTy.search(this, "Bool").firstOrNull() } }