diff --git a/CHANGELOG.md b/CHANGELOG.md index 7125aa9..822227f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # TON Plugin for the IntelliJ IDEs Changelog +## [2.2.0] + +### Added + +- Full support latest Tact 1.2.0 version ([#166](https://github.com/ton-blockchain/intellij-ton/issues/166)) + +### Fixed + +- A bug with comment/uncomment already commented + lines ([#169](https://github.com/ton-blockchain/intellij-ton/issues/169)) +- Function parameter name hints for var arguments of same + name ([#167](https://github.com/ton-blockchain/intellij-ton/issues/167)) + ## [2.1.0] ### Added diff --git a/README.md b/README.md index 942b065..50e856b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - FunC - Fift -- Tact (Only syntax highlighting, more complex support is planned in the future release) +- Tact - TL-B Schemas --- diff --git a/build.gradle.kts b/build.gradle.kts index a14876c..e678422 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,10 +21,10 @@ version = pluginVersion println("pluginVersion=$version") plugins { - kotlin("jvm") version "1.9.0" - id("org.jetbrains.intellij") version "1.16.0" - id("org.jetbrains.grammarkit") version "2022.3.2" - id("org.jetbrains.changelog") version "1.3.1" + kotlin("jvm") version "1.9.22" + id("org.jetbrains.intellij") version "1.17.3" + id("org.jetbrains.grammarkit") version "2022.3.2.2" + id("org.jetbrains.changelog") version "2.2.0" idea } @@ -43,7 +43,6 @@ allprojects { } dependencies { - implementation(kotlin("stdlib-jdk8")) implementation("me.alllex.parsus:parsus-jvm:0.6.1") implementation("com.github.andreypfau.tlb:tlb-jvm:54070d9405") } @@ -122,6 +121,20 @@ tasks { buildSearchableOptions { enabled = prop("enableBuildSearchableOptions").toBoolean() } + configurations.runtimeClasspath.get().forEach { + println(it) + } + jar { + from({ + configurations.runtimeClasspath.get().filter { file -> + !file.nameWithoutExtension.startsWith("kotlin-stdlib") && + !file.nameWithoutExtension.startsWith("annotations") + }.map { + if (it.isDirectory) it + else zipTree(it) + } + }) + } } fun prop(name: String, default: (() -> String?)? = null) = extra.properties[name] as? String @@ -130,7 +143,7 @@ fun prop(name: String, default: (() -> String?)? = null) = extra.properties[name fun generateParser(language: String, suffix: String = "", config: GenerateParserTask.() -> Unit = {}) = task("generate${language.capitalized()}Parser${suffix.capitalized()}") { sourceFile.set(file("src/main/grammar/${language}Parser.bnf")) - targetRoot.set("src/gen") + targetRootOutputDir.set(file("src/gen")) pathToParser.set("/org/ton/intellij/${language.lowercase()}/parser/${language}Parser.java") pathToPsiRoot.set("/org/ton/intellij/${language.lowercase()}/psi") purgeOldFiles.set(true) @@ -139,7 +152,6 @@ fun generateParser(language: String, suffix: String = "", config: GenerateParser fun generateLexer(language: String) = task("generate${language}Lexer") { sourceFile.set(file("src/main/grammar/${language}Lexer.flex")) - targetDir.set("src/gen/org/ton/intellij/${language.lowercase()}/lexer") - targetClass.set("_${language}Lexer") + targetOutputDir.set(file("src/gen/org/ton/intellij/${language.lowercase()}/lexer")) purgeOldFiles.set(true) } diff --git a/gradle.properties b/gradle.properties index 71dc48d..f461e42 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.jvmargs=-Xmx4096m pluginGroup=org.ton -pluginVersion=2.1.0 +pluginVersion=2.2.0 publishChannel=release publishToken=token enableBuildSearchableOptions=false @@ -11,4 +11,4 @@ ideaVersion=231-EAP-SNAPSHOT # https://plugins.jetbrains.com/plugin/227-psiviewer/versions psiViewerPluginVersion=231-SNAPSHOT kotlin.stdlib.default.dependency=false -buildNumber=8 +buildNumber=1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a595206..48c0a02 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/grammar/TactLexer.flex b/src/main/grammar/TactLexer.flex index bd63d0b..493fb45 100644 --- a/src/main/grammar/TactLexer.flex +++ b/src/main/grammar/TactLexer.flex @@ -26,6 +26,11 @@ import static org.ton.intellij.tact.psi.TactElementTypes.*; * Dedicated nested-comment level counter */ private int zzNestedCommentLevel = 0; + + private int zzBlockDepth = 0; + private int zzParenDepth = 0; + private boolean zzStructScope = false; + private boolean zzContractScope = false; %} %{ @@ -55,6 +60,7 @@ import static org.ton.intellij.tact.psi.TactElementTypes.*; %type IElementType %s IN_BLOCK_COMMENT +%s IN_NAME_ATTRIBUTE %unicode @@ -62,20 +68,39 @@ EOL=\R WHITE_SPACE=\s+ WHITE_SPACE=[ \t\n\x0B\f\r]+ -INTEGER_LITERAL=(0[xX][0-9a-fA-F][0-9a-fA-F_]*)|([0-9]+) +NON_ZERO_DIGIT=[1-9] +HEX_DIGIT=[0-9a-fA-F] +DIGIT=[0-9] +BIN_DIGIT=[01] +OCT_DIGIT=[0-7] +INTEGER_LITERAL_DEC = ({NON_ZERO_DIGIT}(_?{DIGIT})*) | 0{DIGIT}* +INTEGER_LITERAL_HEX = 0[xX] {HEX_DIGIT} (_?{HEX_DIGIT})* +INTEGER_LITERAL_BIN = 0[bB] {BIN_DIGIT} (_?{BIN_DIGIT})* +INTEGER_LITERAL_OCT = 0[oO] {OCT_DIGIT} (_?{OCT_DIGIT})* +INTEGER_LITERAL= {INTEGER_LITERAL_HEX} | {INTEGER_LITERAL_BIN} | {INTEGER_LITERAL_OCT} | {INTEGER_LITERAL_DEC} STRING_LITERAL=(\"([^\"\r\n\\]|\\.)*\") -IDENTIFIER=[:letter:]\w* +IDENTIFIER=[a-zA-Z_][a-zA-Z0-9_]* +FUNC_IDENTIFIER=[a-zA-Z_][a-zA-Z0-9_?!:&']* %% { {WHITE_SPACE} { return WHITE_SPACE; } - "{" { return LBRACE; } - "}" { return RBRACE; } + "/*" { yybegin(IN_BLOCK_COMMENT); yypushback(2); } + "//".* { return LINE_COMMENT; } + + "{" { zzBlockDepth++; return LBRACE; } + "}" { + if (zzBlockDepth-- == 0) { + zzStructScope = false; + zzContractScope = false; + } + return RBRACE; + } "[" { return LBRACK; } "]" { return RBRACK; } - "(" { return LPAREN; } - ")" { return RPAREN; } + "(" { zzParenDepth++; return LPAREN; } + ")" { zzParenDepth--; return RPAREN; } ":" { return COLON; } ";" { return SEMICOLON; } "," { return COMMA; } @@ -93,6 +118,11 @@ IDENTIFIER=[:letter:]\w* "=" { return EQ; } "?" { return Q; } "!" { return EXCL; } + "+=" { return PLUSLET; } + "-=" { return MINUSLET; } + "*=" { return TIMESLET; } + "/=" { return DIVLET; } + "%=" { return MODLET; } "==" { return EQEQ; } "!=" { return EXCLEQ; } ">=" { return GTEQ; } @@ -119,29 +149,27 @@ IDENTIFIER=[:letter:]\w* "const" { return CONST_KEYWORD; } "fun" { return FUN_KEYWORD; } "initOf" { return INIT_OF_KEYWORD; } - "get" { return GET_KEYWORD; } "as" { return AS_KEYWORD; } "abstract" { return ABSTRACT_KEYWORD; } "import" { return IMPORT_KEYWORD; } - "struct" { return STRUCT_KEYWORD; } - "message" { return MESSAGE_KEYWORD; } - "contract" { return CONTRACT_KEYWORD; } + "struct" { zzStructScope = true; return STRUCT_KEYWORD; } + "message" { return zzBlockDepth == 0 ? MESSAGE_KEYWORD : IDENTIFIER; } + "contract" { zzContractScope = true; return CONTRACT_KEYWORD; } "trait" { return TRAIT_KEYWORD; } "with" { return WITH_KEYWORD; } - "init" { return INIT_KEYWORD; } "receive" { return RECEIVE_KEYWORD; } - "bounced" { return BOUNCED_KEYWORD; } "external" { return EXTERNAL_KEYWORD; } "true" { return BOOLEAN_LITERAL; } "false" { return BOOLEAN_LITERAL; } "null" { return NULL_LITERAL; } - "intOf" { return INT_OF_KEYWORD; } + "primitive" { return PRIMITIVE_KEYWORD; } + "self" { return SELF_KEYWORD; } + "map" { return MAP_KEYWORD; } + "bounced" { return zzBlockDepth == 1 && zzContractScope ? BOUNCED_KEYWORD : IDENTIFIER; } + "init" { return zzBlockDepth == 1 && zzParenDepth == 0 ? INIT_KEYWORD : IDENTIFIER; } + "get" { return zzBlockDepth <= 1 ? GET_KEYWORD : IDENTIFIER; } "@interface" { return INTERFACE_MACRO; } - "@name" { return NAME_MACRO; } - - "/*" { yybegin(IN_BLOCK_COMMENT); yypushback(2); } - "////".* { return LINE_COMMENT; } - "//".* { return LINE_COMMENT; } + "@name" { yybegin(IN_NAME_ATTRIBUTE); yypushback(5); } {INTEGER_LITERAL} { return INTEGER_LITERAL; } {STRING_LITERAL} { return STRING_LITERAL; } @@ -162,4 +190,12 @@ IDENTIFIER=[:letter:]\w* [^] { } } + { + "@name" { return NAME_MACRO; } + "(" { zzParenDepth++; return LPAREN; } + ")" { zzParenDepth--; yybegin(YYINITIAL); return RPAREN; } + {FUNC_IDENTIFIER} { yybegin(YYINITIAL); return FUNC_IDENTIFIER; } + [^] { yybegin(YYINITIAL); yypushback(1); } +} + [^] { return BAD_CHARACTER; } diff --git a/src/main/grammar/TactParser.bnf b/src/main/grammar/TactParser.bnf index 7933f5c..da366ba 100644 --- a/src/main/grammar/TactParser.bnf +++ b/src/main/grammar/TactParser.bnf @@ -17,6 +17,8 @@ extends(".*Type")=Type extends(".*Expression")=Expression extends(".*Statement")=Statement + elementType(".+BinExpression") = BinExpression + elementType(".+BinOp") = BinOp generateTokenAccessors=true @@ -55,6 +57,11 @@ OROR = '||' ANDAND = '&&' EXCLEXCL = '!!' + PLUSLET = '+=' + MINUSLET = '-=' + TIMESLET = '*=' + DIVLET = '/=' + MODLET = '%=' IF_KEYWORD = 'if' ELSE_KEYWORD = 'else' @@ -73,9 +80,10 @@ CONST_KEYWORD = 'const' FUN_KEYWORD = 'fun' INIT_OF_KEYWORD = 'initOf' - GET_KEYWORD = 'get' AS_KEYWORD = 'as' ABSTRACT_KEYWORD = 'abstract' + PRIMITIVE_KEYWORD = 'primitive' + MAP_KEYWORD = 'map' IMPORT_KEYWORD = 'import' STRUCT_KEYWORD = 'struct' @@ -85,10 +93,10 @@ WITH_KEYWORD = 'with' INIT_KEYWORD = 'init' RECEIVE_KEYWORD = 'receive' - BOUNCED_KEYWORD = 'bounced' EXTERNAL_KEYWORD = 'external' - - INT_OF_KEYWORD = 'intOf' + GET_KEYWORD = 'get' + SELF_KEYWORD = 'self' + BOUNCED_KEYWORD = 'bounced' INTERFACE_MACRO = '@interface' NAME_MACRO = '@name' @@ -99,28 +107,51 @@ BOOLEAN_LITERAL = 'regexp:(true|false)' NULL_LITERAL = 'null' IDENTIFIER = 'regexp:\p{Alpha}\w*' + FUNC_IDENTIFIER = 'regexp:\p{Alpha}\w*' BLOCK_COMMENT = 'regexp:/\*(\*(?!/)|[^*])*\*/' LINE_COMMENT = 'regexp://(.*)' ] } -File ::= RootItem_with_recover* -private RootItem ::= Import | Struct | Message | Contract | Trait | Function | Constant -private RootItem_with_recover ::= !<> RootItem { +File ::= ProgramItem_with_recover* +private ProgramItem ::= Struct + | Message + | Contract + | Primitive + | Function + | Import + | Trait + | Constant +private ProgramItem_with_recover ::= !<> ProgramItem { pin=1 - recoverWhile=RootItem_recover +// recoverWhile=RootItem_recover } private RootItem_recover ::= !Item_first -private Item_first ::= 'import' | 'struct' | 'message' | '@interface' | 'contract' | 'trait' | 'get' | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' | 'fun' | '@name' | 'native' | 'const' | 'init' | 'receive' | 'bounced' | 'external' +private Item_first ::= 'import' | 'struct' | 'message' | '@interface' | 'contract' | 'trait' | 'get' | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' | 'fun' | '@name' | 'native' | 'const' | 'receive' | 'bounced' | 'external' Import ::= 'import' STRING_LITERAL ';' {pin=1} +Primitive ::= 'primitive' IDENTIFIER !'?' ';' { + pin=2 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactTypeDeclarationElement" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactPrimitiveImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactPrimitiveStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" +} + // Type Type ::= BouncedType | MapType | ReferencedType -ReferencedType ::= IDENTIFIER +ReferencedType ::= IDENTIFIER '?'? { + pin=1 + mixin = "org.ton.intellij.tact.psi.impl.TactReferencedTypeImplMixin" +} + BouncedType ::= 'bounced' '<' <> '>' {pin=1} -MapType ::= "map" '<' MapTypeItem ',' MapTypeItem '>' { +MapType ::= 'map' '<' MapTypeItem ',' MapTypeItem '>' { pin=1 } MapTypeItem ::= ReferencedType As? { @@ -131,8 +162,14 @@ As ::= 'as' IDENTIFIER { } // Field -Field ::= IDENTIFIER ':' Type As? Assigment? ';' { +Field ::= IDENTIFIER TypeAscription As? Assigment? ';' { pin=1 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactFieldImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactFieldStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" } private Assigment ::= '=' Expression { pin = 1 @@ -140,13 +177,39 @@ private Assigment ::= '=' Expression { // Constant ConstantAttribute ::= 'virtual' | 'override' | 'abstract' -Constant ::= ConstantAttribute* !'fun' 'const' IDENTIFIER ':' Type Assigment? ';' { - pin = 3 +Constant ::= ConstantAttribute* !'fun' 'const' IDENTIFIER TypeAscription Assigment? ';' { + pin = 4 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactAbstractable" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactConstantImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactConstantStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" } // Struct -Struct ::= 'struct' IDENTIFIER BlockFields {pin=1} -Message ::= 'message' MessageId? IDENTIFIER BlockFields {pin=1} +Struct ::= 'struct' IDENTIFIER BlockFields { + pin = 2 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactTypeDeclarationElement" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactStructImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactStructStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" +} + +Message ::= 'message' MessageId? IDENTIFIER BlockFields { + pin = 1 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactTypeDeclarationElement" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactMessageImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactMessageStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" +} MessageId ::= '(' INTEGER_LITERAL ')' { pin=1 } @@ -156,11 +219,18 @@ BlockFields ::= '{' Field* '}' {pin=1} WithClause ::= 'with' <> {pin=1} Contract ::= ContractAttribute* 'contract' IDENTIFIER WithClause? ContractBody { pin = 2 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactTypeDeclarationElement" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactContractImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactContractStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" } ContractBody ::= '{' ContractItem_with_recover* '}' {pin=1} private ContractItem_with_recover ::= !('}' | <>) ContractItem { pin=1 - recoverWhile=ContractItem_recover +// recoverWhile=ContractItem_recover } private ContractItem_recover ::= !('}' | Item_first | IDENTIFIER) private ContractItem ::= Field @@ -174,12 +244,25 @@ private ContractItem ::= Field ContractAttribute ::= '@interface' StringId {pin=1} ContractInit ::= 'init' FunctionParameters Block { pin=1 + implements=[ + "org.ton.intellij.tact.psi.TactFunctionLike" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactContractInitImplMixin" } // Trait -Trait ::= ContractAttribute* 'trait' IDENTIFIER WithClause? TraitBody +Trait ::= ContractAttribute* 'trait' IDENTIFIER WithClause? TraitBody { + pin = 2 + implements = [ + "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactTypeDeclarationElement" + ] + mixin = "org.ton.intellij.tact.psi.impl.TactTraitImplMixin" + stubClass = "org.ton.intellij.tact.stub.TactTraitStub" + elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" +} TraitBody ::= '{' TraitItem* '}' { - pin=1 + pin = 1 } private TraitItem ::= Field | Constant @@ -189,45 +272,68 @@ private TraitItem ::= Field | Function // Function -FunctionAttribute ::= 'get' | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' -Function ::= (NativeFunctionHead | FunctionAttribute*) 'fun' IDENTIFIER FunctionParameters FunctionType? FunctionBody { - pin = 3 +FunctionAttribute ::= "get" | 'mutates' | 'extends' | 'virtual' | 'override' | 'inline' | 'abstract' +Function ::= (NativeFunctionHead | FunctionAttribute*) (!'native' 'fun')? IDENTIFIER FunctionParameters TypeAscription? FunctionBody { implements = [ "org.ton.intellij.tact.psi.TactNameIdentifierOwner" + "org.ton.intellij.tact.psi.TactInferenceContextOwner" + "org.ton.intellij.tact.psi.TactFunctionLike" + "org.ton.intellij.tact.psi.TactAbstractable" ] -// extends = "org.ton.intellij.tact.psi.TactStubbedElementImpl" - mixin = "org.ton.intellij.tact.psi.TactFunctionImplMixin" + mixin = "org.ton.intellij.tact.psi.impl.TactFunctionImplMixin" stubClass = "org.ton.intellij.tact.stub.TactFunctionStub" elementTypeFactory = "org.ton.intellij.tact.stub.TactStubKt.factory" + pin=3 } private NativeFunctionHead ::= NameAttribute FunctionAttribute* 'native' { +// pin=2 +} + +NameAttribute ::= '@name' '(' FUNC_IDENTIFIER ')' { + pin=1 +} + +FunctionParameters ::= '(' [ (SelfParameter ',' <>) | (SelfParameter) | <> ] ')' {pin=1} +FunctionParameter ::= IDENTIFIER TypeAscription { pin=1 + implements=[ + "org.ton.intellij.tact.psi.TactNamedElement" + ] + mixin="org.ton.intellij.tact.psi.impl.TactFunctionParameterImplMixin" } -NameAttribute ::= '@name' FunctionId -FunctionId ::= '(' IDENTIFIER ')' {pin=2} +SelfParameter ::= 'self' TypeAscription {pin=1} -FunctionParameters ::= '(' [ <> ] ')' {pin=1} -FunctionParameter ::= IDENTIFIER ':' Type {pin=1} -private FunctionType ::= ':' Type { pin = 1 } private FunctionBody ::= ';' | Block ReceiveFunction ::= 'receive' (StringId|FunctionParameters) Block { pin=1 + implements=[ + "org.ton.intellij.tact.psi.TactFunctionLike" + ] + mixin="org.ton.intellij.tact.psi.impl.TactReceiveFunctionImplMixin" } BouncedFunction ::= 'bounced' FunctionParameters Block { pin=1 + implements=[ + "org.ton.intellij.tact.psi.TactFunctionLike" + ] + mixin="org.ton.intellij.tact.psi.impl.TactBouncedFunctionImplMixin" } ExternalFunction ::= 'external' (StringId|FunctionParameters) Block { pin=1 + implements=[ + "org.ton.intellij.tact.psi.TactFunctionLike" + ] + mixin="org.ton.intellij.tact.psi.impl.TactExternalFunctionImplMixin" } StringId ::= '(' STRING_LITERAL ')' {pin=2} // Statements -Statement ::= LetStatement +Statement ::= AssignStatement + | LetStatement | BlockStatement | ReturnStatement | ExpressionStatement - | AssignStatement | ConditionStatement | WhileStatement | RepeatStatement @@ -240,20 +346,37 @@ private Statement_first ::= 'let' | '{' | 'return' | Expression_first | 'if' | ' Block ::= '{' BlockItem* '}' {pin=1} private BlockItem ::= !'}' Statement { pin=1 - recoverWhile=BlockItem_recover +// recoverWhile=BlockItem_recover } private BlockItem_recover ::= !('}' | Statement_first | Item_first | ';') -LetStatement ::= 'let' IDENTIFIER ':' Type '=' 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" } +private TypeAscription ::= ':' Type { + pin = 1 +} + BlockStatement ::= Block -ReturnStatement ::= 'return' Expression ';' { pin = 1 } -ExpressionStatement ::= Expression ';' -AssignStatement ::= LValue ('='|'+='|'-='|'*='|'/='|'%=') Expression ';' -ConditionStatement ::= 'if' Condition Block ConditionElse? {pin=1} -ConditionElse ::= 'else' (Block | ConditionStatement) {pin=1} +ReturnStatement ::= 'return' Expression? ';' { pin = 1 } +ExpressionStatement ::= Expression ';' { + pin=1 +} +AssignStatement ::= Expression AssignBinOp Expression ';' { + rightAssociative = true +} + +AssignBinOp ::= '=' | '+=' | '-=' | '*=' | '/=' | '%=' { + name = "operator" +} + +ConditionStatement ::= 'if' Condition Block ElseBranch? {pin=1} +ElseBranch ::= 'else' (Block | ConditionStatement) {pin=1} WhileStatement ::= 'while' Condition Block {pin=1} RepeatStatement ::= 'repeat' Condition Block {pin=1} UntilStatement ::= 'do' Block 'until' Condition ';' {pin=1} @@ -262,29 +385,29 @@ Condition ::= '(' Expression ')' { pin=1 } -private LValue ::= IDENTIFIER '.' LValue - | IDENTIFIER - // Expressions Expression ::= TernaryExpression - | OrExpression - | AndExpression - | CompareExpression - | BinaryExpression - | AddExpression - | MulExpression + | BoolOrBinExpression + | BoolAndBinExpression + | CompBinExpression + | RelCompBinExpression + | BitShiftBinExpression + | BitAndBinExpression + | BitOrBinExpression + | AddBinExpression + | MulBinExpression | UnaryExpression | NotNullExpression - | CallExpression - | FieldExpression - | StaticCallExpression + | DotExpression | ParenExpression | StructExpression | IntegerExpression | BooleanExpression - | ReferenceExpression + | CallExpression + | ReferenceExpression !'(' | NullExpression - | IntOfExpression + | SelfExpression + | InitOfExpression | StringExpression { // extends = 'org.ton.intellij.tact.psi.TactStubbedElementImpl' // stubClass = "com.intellij.psi.stubs.StubBase" @@ -299,37 +422,84 @@ TernaryExpression ::= Expression '?' Expression ':' Expression { elseBranch="/Expression[2]" ] } -OrExpression ::= Expression '||' Expression -AndExpression ::= Expression '&&' Expression -CompareExpression ::= Expression ('!='|'=='|'>'|'>='|'<'|'<=') Expression -BinaryExpression ::= Expression ('>>'|'<<'|'&'|'|') Expression -AddExpression ::= Expression (('+' !'+') | ('-' !'-')) Expression -MulExpression ::= Expression ('*'|'/'|'%') Expression + +fake BinExpression ::= Expression BinOp Expression { + methods=[ + left="/Expression[0]" + right="/Expression[1]" + ] +} +fake BinOp ::= '||' | '&&' + | '!=' | '==' + | '>' | '>=' | '<' | '<=' + | '>>' | '<<' + | '&' | '|' + | '+' | '-' + | '*' | '/' | '%' + +BoolOrBinExpression ::= Expression BoolOrBinOp Expression +BoolAndBinExpression ::= Expression BoolAndBinOp Expression +CompBinExpression ::= Expression CompBinOp Expression +RelCompBinExpression ::= Expression RelCompBinOp Expression +BitShiftBinExpression ::= Expression BitShiftBinOp Expression +BitAndBinExpression ::= Expression BitAndBinOp Expression +BitOrBinExpression ::= Expression BitOrBinOp Expression +AddBinExpression ::= Expression AddBinOp Expression +MulBinExpression ::= Expression MulBinOp Expression + +BoolOrBinOp ::= '||' { name = "operator" } +BoolAndBinOp ::= '&&' { name = "operator" } +CompBinOp ::= '==' | '!=' { name = "operator" } +RelCompBinOp ::= '>' | '>=' | '<' | '<=' { name = "operator" } +BitShiftBinOp ::= '>>' | '<<' { name = "operator" } +BitAndBinOp ::= '&' { name = "operator" } +BitOrBinOp ::= '|' { name = "operator" } +AddBinOp ::= ('+' !'+') | ('-' !'-') { name = "operator" } +MulBinOp ::= '*' | '/' | '%' { name = "operator" } + UnaryExpression ::= ('-'|'+'|'!') Expression NotNullExpression ::= Expression '!!' -CallExpression ::= Expression '.' IDENTIFIER '(' [<>] ')' -FieldExpression ::= Expression '.' IDENTIFIER !'(' +DotExpression ::= Expression '.' CallOrField +private CallOrField ::= () (CallExpression | FieldExpression) { + pin=1 +} +CallExpression ::= IDENTIFIER '(' [<>] ')' { + pin=2 + mixin="org.ton.intellij.tact.psi.impl.TactCallExpressionImplMixin" +} +FieldExpression ::= IDENTIFIER !'(' { + pin=2 + mixin="org.ton.intellij.tact.psi.impl.TactFieldExpressionImplMixin" +} + StaticCallExpression ::= IDENTIFIER '(' [<>] ')' ParenExpression ::= '(' ParenExpressionItem ')' {pin=1} private ParenExpressionItem ::= Expression StructExpression ::= IDENTIFIER '{' [<>] '}' { pin=2 + mixin = "org.ton.intellij.tact.psi.impl.TactStructExpressionImplMixin" } StructExpressionField ::= IDENTIFIER ':' Expression {pin=1} private StructExpressionField_with_recover ::= !('}') StructExpressionField { pin=1 - recoverWhile=StructExpressionField_recover +// recoverWhile=StructExpressionField_recover } private StructExpressionField_recover ::= !(IDENTIFIER | ',' | '}') IntegerExpression ::= INTEGER_LITERAL BooleanExpression ::= BOOLEAN_LITERAL -ReferenceExpression ::= IDENTIFIER +ReferenceExpression ::= IDENTIFIER { + mixin = "org.ton.intellij.tact.psi.impl.TactReferenceExpressionImplMixin" +} NullExpression ::= 'null' -IntOfExpression ::= 'intOf' IDENTIFIER '(' [<>] ')' {pin=1} +SelfExpression ::= 'self' +InitOfExpression ::= 'initOf' IDENTIFIER '(' [<>] ')' { + pin = 1 + mixin = "org.ton.intellij.tact.psi.impl.TactInitOfExpressionImplMixin" +} StringExpression ::= STRING_LITERAL private meta comma_separated_list ::= <> ( ',' <> )* diff --git a/src/main/kotlin/org/ton/intellij/blueprint/action/InstallBlueprintAction.kt b/src/main/kotlin/org/ton/intellij/blueprint/action/InstallBlueprintAction.kt index 19a632b..82ddca5 100644 --- a/src/main/kotlin/org/ton/intellij/blueprint/action/InstallBlueprintAction.kt +++ b/src/main/kotlin/org/ton/intellij/blueprint/action/InstallBlueprintAction.kt @@ -32,7 +32,7 @@ class InstallBlueprintAction( val parent = packageJson.parent notification?.hideBalloon() - ProgressManager.getInstance().run(object : Task.Backgroundable(project, "") { + ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Installing TON-Blueprint") { override fun run(indicator: ProgressIndicator) { val listener = InstallNodeModuleQuickFix.createListener(project, packageJson, BLUEPRINT_PKG) val extraOptions = InstallNodeModuleQuickFix.buildExtraOptions(project, true) diff --git a/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt b/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt index 82901de..bc4d2a4 100644 --- a/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt +++ b/src/main/kotlin/org/ton/intellij/blueprint/cli/TonBlueprintProjectGenerator.kt @@ -93,31 +93,24 @@ class TonBlueprintProjectGenerator( override fun createPeer(): ProjectGeneratorPeer { return object : NpmPackageGeneratorPeer() { private lateinit var sampleCode: JBCheckBox - private var newCamelCaseCodeStyle: JBCheckBox? = null +// private var newCamelCaseCodeStyle: JBCheckBox? = null override fun createPanel(): JPanel { return super.createPanel().apply { sampleCode = JBCheckBox("Add sample code").also { add(it) } - if (projectType == ProjectType.FUNC) { - newCamelCaseCodeStyle = JBCheckBox("Use new camelCase code style").also { - add(it) - } - } } } override fun buildUI(settingsStep: SettingsStep) { super.buildUI(settingsStep) settingsStep.addSettingsComponent(sampleCode) - newCamelCaseCodeStyle?.let { settingsStep.addSettingsComponent(it) } } override fun getSettings(): Settings { return super.getSettings().apply { putUserData(ADD_SAMPLE_CODE, sampleCode.isSelected) - putUserData(ADD_NEW_CAMEL_CASE_CODE_STYLE, newCamelCaseCodeStyle?.isSelected ?: false) } } } diff --git a/src/main/kotlin/org/ton/intellij/func/FuncLanguage.kt b/src/main/kotlin/org/ton/intellij/func/FuncLanguage.kt index b397465..8f64a2d 100644 --- a/src/main/kotlin/org/ton/intellij/func/FuncLanguage.kt +++ b/src/main/kotlin/org/ton/intellij/func/FuncLanguage.kt @@ -5,4 +5,6 @@ import com.intellij.lang.Language object FuncLanguage : Language( "func", "func", "text/func", "text/x-func", "application/x-func" -), InjectableLanguage +), InjectableLanguage { + override fun isCaseSensitive(): Boolean = false +} diff --git a/src/main/kotlin/org/ton/intellij/func/action/file/FuncToCamelCaseAction.kt b/src/main/kotlin/org/ton/intellij/func/action/file/FuncToCamelCaseAction.kt new file mode 100644 index 0000000..cf1aa3e --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/func/action/file/FuncToCamelCaseAction.kt @@ -0,0 +1,342 @@ +package org.ton.intellij.func.action.file + +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.psi.util.PsiTreeUtil +import org.ton.intellij.func.psi.FuncFile +import org.ton.intellij.func.psi.FuncFunction +import org.ton.intellij.func.psi.FuncPsiFactory +import org.ton.intellij.func.psi.FuncReferenceExpression + +class FuncToCamelCaseAction : AnAction() { + override fun getActionUpdateThread(): ActionUpdateThread { + return ActionUpdateThread.BGT + } + + override fun update(e: AnActionEvent) { + val file = e.getData(CommonDataKeys.PSI_FILE) + e.presentation.isEnabledAndVisible = file is FuncFile + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.project ?: return + val file = e.getData(CommonDataKeys.PSI_FILE) as? FuncFile ?: return + + WriteCommandAction.runWriteCommandAction(project) { + PsiTreeUtil.collectElementsOfType(file, FuncFunction::class.java).forEach { + val name = it.name + if (name != null) { + val newName = transformString(name, 1) + if (newName != name) { + it.identifier.replace(FuncPsiFactory[project].createIdentifier(newName)) + } + } + } + PsiTreeUtil.collectElementsOfType(file, FuncReferenceExpression::class.java).forEach { + val name = it.name + if (name != null) { + val newName = transformString(name, 1) + if (newName != name) { + it.identifier.replace(FuncPsiFactory[project].createIdentifier(newName)) + } + } + } + } + } +} + +private val snakeMarks: String = "~_?!:'" +private val builtins = listOf( + "divmod", "~divmod", "moddiv", "~moddiv", "muldiv", "muldivc", "muldivr", "muldivmod", + "true", "false", "null", "nil", "Nil", "throw", "at", + "touch", "~touch", "touch2", "~touch2", "~dump", "~strdump", + "run_method0", "run_method1", "run_method2", "run_method3", "->" +) +private val snakeReplaceMap = mapOf( + // MAIN + "receiveInternalMessage" to "recv_internal", + "receiveExternalMessage" to "recv_external", + // CHECKS + "isSliceEmpty" to "slice_empty?", + "isSliceDataEmpty" to "slice_data_empty?", + "isSliceRefsEmpty" to "slice_refs_empty?", + "isDictEmpty" to "dict_empty?", + "isCellNull" to "cell_null?", + "isAddrNone" to "addr_none?", + "isWorkchainsEqual" to "workchains_equal?", + "isWorkchainMatch" to "workchain_match?", + "isBasechainAddr" to "basechain_addr?", + "isMasterchainAddr" to "masterchain_addr?", + // QUIET FUNCS + "tryComputeDataSize" to "compute_data_size?", + "trySliceComputeDataSize" to "slice_compute_data_size?", + "isSliceBeginsWith" to "slice_begins_with?", + // DICTS + // load + "tryLoadDict" to "load_dict?", + "~tryLoadDict" to "~load_dict?", + "tryPreloadDict" to "preload_dict?", + // get + "tryDictGet" to "dict_get?", + "tryUdictGet" to "udict_get?", + "tryIdictGet" to "idict_get?", + "tryDictGetRef" to "dict_get_ref?", + "tryUdictGetRef" to "udict_get_ref?", + "tryIdictGetRef" to "idict_get_ref?", + // setget + "tryDictSetGet" to "dict_set_get?", + "tryUdictSetGet" to "udict_set_get?", + "tryIdictSetGet" to "idict_set_get?", + "~tryDictSetGet" to "~dict_set_get?", + "~tryUdictSetGet" to "~udict_set_get?", + "~tryIdictSetGet" to "~idict_set_get?", + "tryDictSetGetRef" to "dict_set_get_ref?", + "tryUdictSetGetRef" to "udict_set_get_ref?", + "tryIdictSetGetRef" to "idict_set_get_ref?", + "~tryDictSetGetRef" to "~dict_set_get_ref?", + "~tryUdictSetGetRef" to "~udict_set_get_ref?", + "~tryIdictSetGetRef" to "~idict_set_get_ref?", + "tryDictSetGetBuilder" to "dict_set_get_builder?", + "tryUdictSetGetBuilder" to "udict_set_get_builder?", + "tryIdictSetGetBuilder" to "idict_set_get_builder?", + "~tryDictSetGetBuilder" to "~dict_set_get_builder?", + "~tryUdictSetGetBuilder" to "~udict_set_get_builder?", + "~tryIdictSetGetBuilder" to "~idict_set_get_builder?", + // delete + "tryDictDelete" to "dict_delete?", + "tryUdictDelete" to "udict_delete?", + "tryIdictDelete" to "idict_delete?", + "tryDictDeleteGet" to "dict_delete_get?", + "tryUdictDeleteGet" to "udict_delete_get?", + "tryIdictDeleteGet" to "idict_delete_get?", + "~tryDictDeleteGet" to "~dict_delete_get?", + "~tryUdictDeleteGet" to "~udict_delete_get?", + "~tryIdictDeleteGet" to "~idict_delete_get?", + "tryDictDeleteGetRef" to "dict_delete_get_ref?", + "tryUdictDeleteGetRef" to "udict_delete_get_ref?", + "tryIdictDeleteGetRef" to "idict_delete_get_ref?", + "~tryDictDeleteGetRef" to "~dict_delete_get_ref?", + "~tryUdictDeleteGetRef" to "~udict_delete_get_ref?", + "~tryIdictDeleteGetRef" to "~idict_delete_get_ref?", + "tryDictDeleteGetMin" to "dict_delete_get_min?", + "tryUdictDeleteGetMin" to "udict_delete_get_min?", + "tryIdictDeleteGetMin" to "idict_delete_get_min?", + "~tryDictDeleteGetMin" to "~dict_delete_get_min?", + "~tryUdictDeleteGetMin" to "~udict_delete_get_min?", + "~tryIdictDeleteGetMin" to "~idict_delete_get_min?", + "tryDictDeleteGetMax" to "dict_delete_get_max?", + "tryUdictDeleteGetMax" to "udict_delete_get_max?", + "tryIdictDeleteGetMax" to "idict_delete_get_max?", + "~tryDictDeleteGetMax" to "~dict_delete_get_max?", + "~tryUdictDeleteGetMax" to "~udict_delete_get_max?", + "~tryIdictDeleteGetMax" to "~idict_delete_get_max?", + "tryDictDeleteGetMinRef" to "dict_delete_get_min_ref?", + "tryUdictDeleteGetMinRef" to "udict_delete_get_min_ref?", + "tryIdictDeleteGetMinRef" to "idict_delete_get_min_ref?", + "~tryDictDeleteGetMinRef" to "~dict_delete_get_min_ref?", + "~tryUdictDeleteGetMinRef" to "~udict_delete_get_min_ref?", + "~tryIdictDeleteGetMinRef" to "~idict_delete_get_min_ref?", + "tryDictDeleteGetMaxRef" to "dict_delete_get_max_ref?", + "tryUdictDeleteGetMaxRef" to "udict_delete_get_max_ref?", + "tryIdictDeleteGetMaxRef" to "idict_delete_get_max_ref?", + "~tryDictDeleteGetMaxRef" to "~dict_delete_get_max_ref?", + "~tryUdictDeleteGetMaxRef" to "~udict_delete_get_max_ref?", + "~tryIdictDeleteGetMaxRef" to "~idict_delete_get_max_ref?", + // add + "tryDictAdd" to "dict_add?", + "tryUdictAdd" to "udict_add?", + "tryIdictAdd" to "idict_add?", + "tryDictAddBuilder" to "dict_add_builder?", + "tryUdictAddBuilder" to "udict_add_builder?", + "tryIdictAddBuilder" to "idict_add_builder?", + "tryDictAddRef" to "dict_add_ref?", + "tryUdictAddRef" to "udict_add_ref?", + "tryIdictAddRef" to "idict_add_ref?", + "tryDictAddGet" to "dict_add_get?", + "tryUdictAddGet" to "udict_add_get?", + "tryIdictAddGet" to "idict_add_get?", + "~tryDictAddGet" to "~dict_add_get?", + "~tryUdictAddGet" to "~udict_add_get?", + "~tryIdictAddGet" to "~idict_add_get?", + "tryDictAddGetRef" to "dict_add_get_ref?", + "tryUdictAddGetRef" to "udict_add_get_ref?", + "tryIdictAddGetRef" to "idict_add_get_ref?", + "~tryDictAddGetRef" to "~dict_add_get_ref?", + "~tryUdictAddGetRef" to "~udict_add_get_ref?", + "~tryIdictAddGetRef" to "~idict_add_get_ref?", + "tryDictAddGetBuilder" to "dict_add_get_builder?", + "tryUdictAddGetBuilder" to "udict_add_get_builder?", + "tryIdictAddGetBuilder" to "idict_add_get_builder?", + "~tryDictAddGetBuilder" to "~dict_add_get_builder?", + "~tryUdictAddGetBuilder" to "~udict_add_get_builder?", + "~tryIdictAddGetBuilder" to "~idict_add_get_builder?", + // replace + "tryDictReplace" to "dict_replace?", + "tryUdictReplace" to "udict_replace?", + "tryIdictReplace" to "idict_replace?", + "tryDictReplaceBuilder" to "dict_replace_builder?", + "tryUdictReplaceBuilder" to "udict_replace_builder?", + "tryIdictReplaceBuilder" to "idict_replace_builder?", + "tryDictReplaceRef" to "dict_replace_ref?", + "tryUdictReplaceRef" to "udict_replace_ref?", + "tryIdictReplaceRef" to "idict_replace_ref?", + "tryDictReplaceGet" to "dict_replace_get?", + "tryUdictReplaceGet" to "udict_replace_get?", + "tryIdictReplaceGet" to "idict_replace_get?", + "~tryDictReplaceGet" to "~dict_replace_get?", + "~tryUdictReplaceGet" to "~udict_replace_get?", + "~tryIdictReplaceGet" to "~idict_replace_get?", + "tryDictReplaceGetRef" to "dict_replace_get_ref?", + "tryUdictReplaceGetRef" to "udict_replace_get_ref?", + "tryIdictReplaceGetRef" to "idict_replace_get_ref?", + "~tryDictReplaceGetRef" to "~dict_replace_get_ref?", + "~tryUdictReplaceGetRef" to "~udict_replace_get_ref?", + "~tryIdictReplaceGetRef" to "~idict_replace_get_ref?", + "tryDictReplaceGetBuilder" to "dict_replace_get_builder?", + "tryUdictReplaceGetBuilder" to "udict_replace_get_builder?", + "tryIdictReplaceGetBuilder" to "idict_replace_get_builder?", + "~tryDictReplaceGetBuilder" to "~dict_replace_get_builder?", + "~tryUdictReplaceGetBuilder" to "~udict_replace_get_builder?", + "~tryIdictReplaceGetBuilder" to "~idict_replace_get_builder?", + // get min/max + "tryDictGetMin" to "dict_get_min?", + "tryUdictGetMin" to "udict_get_min?", + "tryIdictGetMin" to "idict_get_min?", + "tryDictGetMinRef" to "dict_get_min_ref?", + "tryUdictGetMinRef" to "udict_get_min_ref?", + "tryIdictGetMinRef" to "idict_get_min_ref?", + "tryDictGetMax" to "dict_get_max?", + "tryUdictGetMax" to "udict_get_max?", + "tryIdictGetMax" to "idict_get_max?", + "tryDictGetMaxRef" to "dict_get_max_ref?", + "tryUdictGetMaxRef" to "udict_get_max_ref?", + "tryIdictGetMaxRef" to "idict_get_max_ref?", + // get next/prev + "tryDictGetNext" to "dict_get_next?", + "tryUdictGetNext" to "udict_get_next?", + "tryIdictGetNext" to "idict_get_next?", + "tryDictGetNexteq" to "dict_get_nexteq?", + "tryUdictGetNexteq" to "udict_get_nexteq?", + "tryIdictGetNexteq" to "idict_get_nexteq?", + "tryDictGetPrev" to "dict_get_prev?", + "tryUdictGetPrev" to "udict_get_prev?", + "tryIdictGetPrev" to "idict_get_prev?", + "tryDictGetPreveq" to "dict_get_preveq?", + "tryUdictGetPreveq" to "udict_get_preveq?", + "tryIdictGetPreveq" to "idict_get_preveq?", + // pfx + "tryPfxdictGet" to "pfxdict_get?", + "tryPfxdictSet" to "pfxdict_set?", + "tryPfxdictDelete" to "pfxdict_delete?" +) +private val camelReplaceMap = snakeReplaceMap.entries.associate { (key, value) -> value to key } + +fun isSnakeCase(inputStr: String): Boolean { + return inputStr.all { it.isLowerCase() || it.isDigit() || snakeMarks.contains(it) } +} + +fun isCamelCase(inputStr: String): Boolean { + val sanitizedStr = inputStr.filter { it.isLetterOrDigit() } + return sanitizedStr.firstOrNull()?.isLowerCase() ?: false || sanitizedStr.any { it.isUpperCase() } +} + +private fun snakeToCamel(inputStr: String): String { + val words = inputStr.split("_") + val camelCaseWords = listOf(words[0].toLowerCase()) + words.drop(1).map { it.capitalize() } + return camelCaseWords.joinToString("") +} + +private fun camelToSnake(inputStr: String): String { + return inputStr.replace(Regex("([a-z0-9])([A-Z])"), "$1_$2").toLowerCase() +} + +private fun transformQuestionMark(inputStr: String): String { + return if (!inputStr.endsWith("?")) { + inputStr + } else if ("_" in inputStr) { + inputStr.dropLast(1) + } else { + "is_${inputStr.dropLast(1)}" + } +} + +private fun transformExclamationMark(inputStr: String): String { + return if (!inputStr.endsWith("!")) { + inputStr + } else { + "force_${inputStr.dropLast(1)}" + } +} + +private fun transformApostrophe(inputStr: String): String { + return if (!inputStr.endsWith("'")) { + inputStr + } else { + "modified_${inputStr.dropLast(1)}" + } +} + +private fun transformIsWord(inputStr: String): String { + return if (!(inputStr.startsWith("is") && inputStr.count { it.isUpperCase() } == 1)) { + inputStr + } else { + "${inputStr.substring(2)}?" + } +} + +private fun transformForceWord(inputStr: String): String { + return if (!(inputStr.startsWith("force") && inputStr[5].isUpperCase())) { + inputStr + } else { + "${inputStr.substring(5)}!" + } +} + +private fun transformModifiedWord(inputStr: String): String { + return if (!(inputStr.startsWith("modified") && inputStr[8].isUpperCase())) { + inputStr + } else { + "${inputStr.substring(8)}'" + } +} + +private fun transformStringToCamelCase(inputStr: String): String { + if (!isSnakeCase(inputStr) || inputStr in builtins) { + return inputStr + } + if (inputStr in camelReplaceMap.keys) { + return camelReplaceMap[inputStr] ?: inputStr + } + + var result = transformQuestionMark(inputStr) + result = transformExclamationMark(result) + result = transformApostrophe(result) + result = snakeToCamel(result) + return result +} + +private fun transformStringToSnakeCase(inputStr: String): String { + if (!isCamelCase(inputStr) || inputStr in builtins) { + return inputStr + } + if (inputStr in snakeReplaceMap.keys) { + return snakeReplaceMap[inputStr] ?: inputStr + } + + var result = transformIsWord(inputStr) + result = transformForceWord(result) + result = transformModifiedWord(result) + result = camelToSnake(result) + return result +} + +fun transformString(inputStr: String, mode: Int): String { + return when (mode) { + 1 -> transformStringToCamelCase(inputStr) + 2 -> transformStringToSnakeCase(inputStr) + else -> inputStr + } +} diff --git a/src/main/kotlin/org/ton/intellij/func/ide/FuncCommenter.kt b/src/main/kotlin/org/ton/intellij/func/ide/FuncCommenter.kt index 9b6ea5c..0943d18 100644 --- a/src/main/kotlin/org/ton/intellij/func/ide/FuncCommenter.kt +++ b/src/main/kotlin/org/ton/intellij/func/ide/FuncCommenter.kt @@ -5,6 +5,7 @@ import com.intellij.codeInsight.generation.CommenterDataHolder import com.intellij.codeInsight.generation.SelfManagingCommenter import com.intellij.codeInsight.generation.SelfManagingCommenterUtil import com.intellij.lang.CodeDocumentationAwareCommenter +import com.intellij.lang.Commenter import com.intellij.openapi.editor.Document import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiComment @@ -18,7 +19,7 @@ data class CommentHolder(val file: PsiFile) : CommenterDataHolder() { fun useSpaceAfterLineComment(): Boolean = CodeStyle.getLanguageSettings(file, FuncLanguage).LINE_COMMENT_ADD_SPACE } -class FuncCommenter : CodeDocumentationAwareCommenter, SelfManagingCommenter { +class FuncCommenter : Commenter, CodeDocumentationAwareCommenter, SelfManagingCommenter { override fun isDocumentationComment(element: PsiComment?) = false override fun getDocumentationCommentTokenType(): IElementType? = null override fun getDocumentationCommentLinePrefix(): String? = null @@ -96,7 +97,7 @@ class FuncCommenter : CodeDocumentationAwareCommenter, SelfManagingCommenter val param = params.getOrNull(index + offset) ?: return result val paramName = param.name - if (paramName != null) { + if (paramName != null && !(arg is FuncReferenceExpression && arg.name == paramName)) { result.add(InlayInfo(paramName, arg.textOffset)) } } diff --git a/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt b/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt index 1a43579..cdf2cde 100644 --- a/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt +++ b/src/main/kotlin/org/ton/intellij/func/inspection/FuncFunctionCallInspection.kt @@ -10,17 +10,18 @@ class FuncFunctionCallInspection : FuncInspectionBase() { holder: ProblemsHolder, session: LocalInspectionToolSession, ) = object : FuncVisitor() { -// override fun visitSpecialApplyExpression(o: FuncSpecialApplyExpression) { -// super.visitSpecialApplyExpression(o) -// val function = o.left.reference?.resolve() as? FuncFunction ?: return -// holder.check(function, o.right ?: return, true) -// } -// -// override fun visitApplyExpression(o: FuncApplyExpression) { -// super.visitApplyExpression(o) -// val function = o.left.reference?.resolve() as? FuncFunction ?: return -// holder.check(function, o.right ?: return, false) -// } + override fun visitSpecialApplyExpression(o: FuncSpecialApplyExpression) { + super.visitSpecialApplyExpression(o) + val function = o.left.reference?.resolve() as? FuncFunction ?: return + holder.check(function, o.right ?: return, true) + } + + override fun visitApplyExpression(o: FuncApplyExpression) { + super.visitApplyExpression(o) + if (o.parent is FuncSpecialApplyExpression) return + val function = o.left.reference?.resolve() as? FuncFunction ?: return + holder.check(function, o.right ?: return, false) + } } private fun ProblemsHolder.check(function: FuncFunction, argument: FuncExpression, isMethodCall: Boolean) { diff --git a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt index 3804c24..5de19ac 100644 --- a/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt +++ b/src/main/kotlin/org/ton/intellij/func/parser/FuncParserDefinition.kt @@ -58,7 +58,7 @@ class FuncParserDefinition : ParserDefinition { val WHITESPACES = TokenSet.create(TokenType.WHITE_SPACE) val STRING_LITERALS = TokenSet.create(FuncElementTypes.STRING_LITERAL) - val WHITE_SPACE_OR_COMMENT_BIT_SET = TokenSet.orSet(FUNC_COMMENTS, WHITESPACES); + val WHITE_SPACE_OR_COMMENT_BIT_SET = TokenSet.orSet(FUNC_COMMENTS, WHITESPACES) val OPERATORS = TokenSet.create( FuncElementTypes.PLUS, FuncElementTypes.MINUS, diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt index 9bae0ca..0f5a538 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncFile.kt @@ -4,12 +4,8 @@ import com.intellij.extapi.psi.PsiFileBase import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.FileViewProvider -import com.intellij.psi.PsiElement -import com.intellij.psi.stubs.StubElement -import com.intellij.psi.tree.IElementType import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager -import com.intellij.util.ArrayFactory import org.ton.intellij.func.FuncFileType import org.ton.intellij.func.FuncLanguage import org.ton.intellij.func.stub.FuncFileStub @@ -17,6 +13,7 @@ import org.ton.intellij.func.stub.type.FuncConstVarStubElementType import org.ton.intellij.func.stub.type.FuncFunctionStubElementType import org.ton.intellij.func.stub.type.FuncGlobalVarStubElementType import org.ton.intellij.func.stub.type.FuncIncludeDefinitionStubElementType +import org.ton.intellij.util.getChildrenByType import org.ton.intellij.util.recursionGuard //private fun processFile(context: FuncElement, file: FuncFile) { @@ -151,11 +148,3 @@ private val INCLUDE_COMPARE: Comparator = it.stringLiteral?.rawString?.text?.lowercase() } ) - -private fun getChildrenByType( - stub: StubElement, - elementType: IElementType, - f: ArrayFactory, -): List { - return stub.getChildrenByType(elementType, f).toList() as List -} diff --git a/src/main/kotlin/org/ton/intellij/func/psi/FuncTokenType.kt b/src/main/kotlin/org/ton/intellij/func/psi/FuncTokenType.kt index 8f87755..87e135a 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/FuncTokenType.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/FuncTokenType.kt @@ -11,9 +11,9 @@ import org.ton.intellij.util.tokenSetOf open class FuncTokenType(val name: String) : IElementType(name, FuncLanguage) -val FUNC_REGULAR_COMMENTS = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT) -val FUNC_DOC_COMMENTS = tokenSetOf(EOL_DOC_COMMENT, BLOCK_DOC_COMMENT) -val FUNC_COMMENTS = TokenSet.orSet(FUNC_REGULAR_COMMENTS, FUNC_DOC_COMMENTS) +val FUNC_REGULAR_COMMENTS get() = tokenSetOf(BLOCK_COMMENT, EOL_COMMENT) +val FUNC_DOC_COMMENTS get() = tokenSetOf(EOL_DOC_COMMENT, BLOCK_DOC_COMMENT) +val FUNC_COMMENTS get() = TokenSet.orSet(FUNC_REGULAR_COMMENTS, FUNC_DOC_COMMENTS) val FUNC_KEYWORDS = tokenSetOf( FuncElementTypes.RETURN_KEYWORD, diff --git a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionParameterMixin.kt b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionParameterMixin.kt index 3f3b976..b204e33 100644 --- a/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionParameterMixin.kt +++ b/src/main/kotlin/org/ton/intellij/func/psi/impl/FuncFunctionParameterMixin.kt @@ -4,9 +4,13 @@ import com.intellij.lang.ASTNode import com.intellij.psi.stubs.IStubElementType import org.ton.intellij.func.psi.FuncFunctionParameter import org.ton.intellij.func.stub.FuncFunctionParameterStub +import org.ton.intellij.tact.TactIcons +import javax.swing.Icon abstract class FuncFunctionParameterMixin : FuncNamedElementImpl, FuncFunctionParameter { constructor(node: ASTNode) : super(node) constructor(stub: FuncFunctionParameterStub, stubType: IStubElementType<*, *>) : super(stub, stubType) + + override fun getIcon(flags: Int): Icon = TactIcons.PARAMETER } diff --git a/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt index 5e8d256..8440adb 100644 --- a/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt +++ b/src/main/kotlin/org/ton/intellij/func/type/infer/TypeInferenceWaler.kt @@ -177,7 +177,18 @@ class FuncTypeInferenceWalker( val lhs = expressions[0] val rhs = expressions.getOrNull(1) - if (lhs is FuncPrimitiveTypeExpression || lhs is FuncHoleTypeExpression) { + fun FuncExpression.isTypeExpression(): Boolean { + return when (this) { + is FuncPrimitiveTypeExpression, + is FuncHoleTypeExpression -> true + + is FuncTupleExpression -> expressionList.all { it.isTypeExpression() } + is FuncTensorExpression -> expressionList.all { it.isTypeExpression() } + else -> false + } + } + + if (lhs.isTypeExpression()) { variableDeclarationState = true } val lhsTy = lhs?.inferType() diff --git a/src/main/kotlin/org/ton/intellij/tact/TactBundle.kt b/src/main/kotlin/org/ton/intellij/tact/TactBundle.kt new file mode 100644 index 0000000..1925879 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/TactBundle.kt @@ -0,0 +1,14 @@ +package org.ton.intellij.tact + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.PropertyKey + +const val TACT_BUNDLE = "messages.TactBundle" + +object TactBundle : DynamicBundle(TACT_BUNDLE) { + fun message(@PropertyKey(resourceBundle = TACT_BUNDLE) key: String, vararg params: Any): String = + getMessage(key, *params) + + fun messagePointer(@PropertyKey(resourceBundle = TACT_BUNDLE) key: String, vararg params: Any) = + getLazyMessage(key, *params) +} diff --git a/src/main/kotlin/org/ton/intellij/tact/TactFileType.kt b/src/main/kotlin/org/ton/intellij/tact/TactFileType.kt index 4cd6091..7435720 100644 --- a/src/main/kotlin/org/ton/intellij/tact/TactFileType.kt +++ b/src/main/kotlin/org/ton/intellij/tact/TactFileType.kt @@ -4,7 +4,7 @@ import com.intellij.openapi.fileTypes.LanguageFileType import com.intellij.openapi.vfs.VirtualFile object TactFileType : LanguageFileType(TactLanguage) { - override fun getName() = "tact" + override fun getName() = "Tact" override fun getDescription() = "Tact files" override fun getDefaultExtension() = "tact" override fun getIcon() = TactIcons.FILE diff --git a/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt b/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt index c76e318..d6f0767 100644 --- a/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt +++ b/src/main/kotlin/org/ton/intellij/tact/TactIcons.kt @@ -11,4 +11,12 @@ object TactIcons { val VARIABLE = AllIcons.Nodes.Variable val GLOBAL_VARIABLE = AllIcons.Nodes.Gvariable val RECURSIVE_CALL = AllIcons.Gutter.RecursiveMethod + val IMPLEMENTED_METHOD = AllIcons.Gutter.ImplementedMethod + val IMPLEMENTING_METHOD = AllIcons.Gutter.ImplementingMethod + val OVERRIDING_METHOD = AllIcons.Gutter.OverridingMethod + val CONTRACT = AllIcons.Nodes.Controller + val TRAIT = AllIcons.Nodes.Interface + val STRUCT = AllIcons.Nodes.Record + val MESSAGE = AllIcons.Nodes.Record + val PRIMITIVE = AllIcons.Nodes.Type } diff --git a/src/main/kotlin/org/ton/intellij/tact/TactLanguage.kt b/src/main/kotlin/org/ton/intellij/tact/TactLanguage.kt index 90ce8fa..49a752a 100644 --- a/src/main/kotlin/org/ton/intellij/tact/TactLanguage.kt +++ b/src/main/kotlin/org/ton/intellij/tact/TactLanguage.kt @@ -4,5 +4,9 @@ import com.intellij.lang.InjectableLanguage import com.intellij.lang.Language object TactLanguage : Language( - "tact", "tact", "text/tact", "text/x-tact", "application/x-tact" -), InjectableLanguage + "Tact", "tact", "text/tact", "text/x-tact", "application/x-tact" +), InjectableLanguage { + override fun isCaseSensitive(): Boolean = false + + override fun getDisplayName(): String = "Tact" +} diff --git a/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt b/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt index d8529b3..19f9ea3 100644 --- a/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt +++ b/src/main/kotlin/org/ton/intellij/tact/annotator/TactHighlightingAnnotator.kt @@ -11,6 +11,7 @@ import org.ton.intellij.tact.psi.TactConstant import org.ton.intellij.tact.psi.TactElement import org.ton.intellij.tact.psi.TactElementTypes.IDENTIFIER import org.ton.intellij.tact.psi.TactField +import org.ton.intellij.tact.psi.TactFieldExpression import org.ton.intellij.tact.psi.TactFunction class TactHighlightingAnnotator : Annotator { @@ -40,7 +41,13 @@ class TactHighlightingAnnotator : Annotator { } fun colorFor(element: TactElement) = when (element) { -// is TactNativeFunction -> TactColor.FUNCTION_STATIC + is TactFieldExpression -> { + if (element.reference?.resolve() != null) { + TactColor.FIELD + } else { + null + } + } is TactFunction -> TactColor.FUNCTION_DECLARATION is TactField -> TactColor.FIELD is TactConstant -> TactColor.CONSTANT diff --git a/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt b/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt new file mode 100644 index 0000000..b158bbf --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/diagnostics/TactDiagnostic.kt @@ -0,0 +1,79 @@ +package org.ton.intellij.tact.diagnostics + +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement +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.util.PreparedAnnotation + +sealed class TactDiagnostic( + val element: PsiElement, + val endElement: PsiElement = element +) { + abstract fun prepare(): PreparedAnnotation + + abstract fun canApply(localInspectionTool: TactLocalInspectionTool): Boolean + + fun addToHolder(holder: ProblemsHolder) { + val prepared = prepare() + val fixes = prepared.fixes.map { it.fix }.toTypedArray() + if (element == endElement) { + holder.registerProblem( + element, + prepared.fullDescription, + prepared.severity, + *fixes + ) + } else { + val descriptor = holder.manager.createProblemDescriptor( + element, + endElement, + prepared.fullDescription, + prepared.severity, + holder.isOnTheFly, + *fixes + ) + holder.registerProblem(descriptor) + } + } + + class TypeError( + element: PsiElement, + val expectedTy: TactTy, + val actualTy: TactTy + ) : TactDiagnostic(element) { + override fun prepare(): PreparedAnnotation = PreparedAnnotation( + ProblemHighlightType.GENERIC_ERROR, + "Type mismatch", + "expected `$expectedTy`, found `$actualTy`", + fixes = buildList { + + } + ) + + override fun canApply(localInspectionTool: TactLocalInspectionTool): Boolean { + return localInspectionTool is TactTypeCheckInspection + } + } + + class VariableAlreadyExists( + element: PsiElement, + val definition: TactNamedElement + ) : TactDiagnostic(element) { + override fun prepare(): PreparedAnnotation = PreparedAnnotation( + ProblemHighlightType.GENERIC_ERROR, + "Variable already exists", + "`${definition.name}` is already defined in this scope", + fixes = buildList { + + } + ) + + override fun canApply(localInspectionTool: TactLocalInspectionTool): Boolean { + return false + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/TactFindUsagesProvider.kt b/src/main/kotlin/org/ton/intellij/tact/ide/TactFindUsagesProvider.kt new file mode 100644 index 0000000..483a354 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/TactFindUsagesProvider.kt @@ -0,0 +1,41 @@ +package org.ton.intellij.tact.ide + +import com.intellij.lang.HelpID +import com.intellij.lang.cacheBuilder.DefaultWordsScanner +import com.intellij.lang.cacheBuilder.WordsScanner +import com.intellij.lang.findUsages.FindUsagesProvider +import com.intellij.psi.ElementDescriptionUtil +import com.intellij.psi.PsiElement +import com.intellij.usageView.UsageViewLongNameLocation +import com.intellij.usageView.UsageViewShortNameLocation +import org.ton.intellij.tact.parser.TactLexer +import org.ton.intellij.tact.psi.TACT_COMMENTS +import org.ton.intellij.tact.psi.TactElementTypes +import org.ton.intellij.util.tokenSetOf + +class TactFindUsagesProvider : FindUsagesProvider { + override fun getWordsScanner() = DefaultWordsScanner( + TactLexer(), + tokenSetOf(TactElementTypes.IDENTIFIER), + TACT_COMMENTS, + tokenSetOf(TactElementTypes.STRING_LITERAL) + ) + + override fun canFindUsagesFor(psiElement: PsiElement): Boolean { + return true + } + + override fun getHelpId(psiElement: PsiElement): String? { + return HelpID.FIND_OTHER_USAGES + } + + override fun getType(element: PsiElement): String { + return element.toString() + } + + override fun getDescriptiveName(element: PsiElement): String = + ElementDescriptionUtil.getElementDescription(element, UsageViewLongNameLocation.INSTANCE) + + override fun getNodeText(element: PsiElement, useFullName: Boolean): String = + ElementDescriptionUtil.getElementDescription(element, UsageViewShortNameLocation.INSTANCE) +} diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionContributor.kt b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionContributor.kt new file mode 100644 index 0000000..7b8e51f --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionContributor.kt @@ -0,0 +1,16 @@ +package org.ton.intellij.tact.ide.completion + +import com.intellij.codeInsight.completion.CompletionContributor +import com.intellij.codeInsight.completion.CompletionType + +class TactCompletionContributor : CompletionContributor() { + init { + extend(TactTypeCompletionProvider()) + extend(TactReferenceCompletionProvider()) + extend(TactDotCompletionProvider()) + } + + fun extend(provider: TactCompletionProvider) { + extend(CompletionType.BASIC, provider.elementPattern, provider) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionProvider.kt new file mode 100644 index 0000000..0083b52 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactCompletionProvider.kt @@ -0,0 +1,10 @@ +package org.ton.intellij.tact.ide.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionProvider +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiElement + +abstract class TactCompletionProvider : CompletionProvider() { + abstract val elementPattern: ElementPattern +} 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 new file mode 100644 index 0000000..2f2caed --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactDotCompletionProvider.kt @@ -0,0 +1,76 @@ +package org.ton.intellij.tact.ide.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.ElementPattern +import com.intellij.psi.PsiElement +import com.intellij.util.ProcessingContext +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.index.TactFunctionIndex +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.tact.type.selfInferenceResult +import org.ton.intellij.tact.type.selfType +import org.ton.intellij.tact.type.ty +import org.ton.intellij.util.ancestorStrict +import org.ton.intellij.util.processAllKeys +import org.ton.intellij.util.psiElement + +class TactDotCompletionProvider : TactCompletionProvider() { + override val elementPattern: ElementPattern = + psiElement().withSuperParent(2, TactDotExpression::class.java) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + val project = parameters.position.project + val right = parameters.position.parent + if (right !is TactFieldExpression && right !is TactCallExpression) return + val dotExpression = right.parent as? TactDotExpression ?: return + val left = dotExpression.expressionList.getOrNull(0) ?: return + val inference = dotExpression.ancestorStrict()?.selfInferenceResult ?: return + + val leftTy = inference.getExprTy(left) + if (leftTy is TactTyRef) { + leftTy.item.members.forEach { member -> + var builder = LookupElementBuilder.createWithIcon(member) + val typeText = when (member) { + is TactConstant -> member.type?.ty.toString() + is TactFunction -> member.type?.ty.toString() + else -> null + } + if (typeText != null) { + builder = builder.withTypeText(typeText) + } + builder.withInsertHandler { context, item -> + if (member is TactFunction) { + context.editor.document.insertString(context.editor.caretModel.offset, "()") + context.editor.caretModel.moveToOffset(context.editor.caretModel.offset + 2) + context.commitDocument() + } + } + result.addElement(builder) + } + } + processAllKeys(TactFunctionIndex.KEY, project) { name -> + val function = TactFunctionIndex.findElementsByName(project, name).find { + it.selfType?.isAssignable(leftTy) == true + } + if (function != null) { + result.addElement( + LookupElementBuilder.createWithIcon(function) + .withTypeText(function.selfType?.toString() ?: "") + .withInsertHandler { context, item -> + context.editor.document.insertString(context.editor.caretModel.offset, "()") + context.editor.caretModel.moveToOffset(context.editor.caretModel.offset + 2) + context.commitDocument() + } + ) + } + + true + } + } +} 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 new file mode 100644 index 0000000..5a63638 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactReferenceCompletionProvider.kt @@ -0,0 +1,43 @@ +package org.ton.intellij.tact.ide.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.util.ProcessingContext +import org.ton.intellij.tact.psi.TactInferenceContextOwner +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.util.ancestorStrict +import org.ton.intellij.util.processAllKeys + +class TactReferenceCompletionProvider : TactCompletionProvider() { + override val elementPattern = psiElement().withParent(TactReferenceExpression::class.java) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + val position = parameters.position.parent as? TactReferenceExpression ?: return + val inferenceContextOwner = position.ancestorStrict() ?: return + val project = position.project + val lookup = TactLookup(project, inferenceContextOwner) + + lookup.ctx.collectVariableCandidates(position).distinctBy { it.name }.forEach { + result.addElement(LookupElementBuilder.createWithIcon(it)) + } + + processAllKeys(TactFunctionIndex.KEY, project) { key -> + TactFunctionIndex.findElementsByName(project, key).asSequence() + .filter { !it.isGet } + .distinctBy { it.name } + .forEach { + result.addElement(LookupElementBuilder.createWithIcon(it)) + } + true + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactTypeCompletionProvider.kt b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactTypeCompletionProvider.kt new file mode 100644 index 0000000..b33d4fd --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/ide/completion/TactTypeCompletionProvider.kt @@ -0,0 +1,31 @@ +package org.ton.intellij.tact.ide.completion + +import com.intellij.codeInsight.completion.CompletionParameters +import com.intellij.codeInsight.completion.CompletionResultSet +import com.intellij.codeInsight.lookup.LookupElementBuilder +import com.intellij.patterns.PlatformPatterns.psiElement +import com.intellij.util.ProcessingContext +import org.ton.intellij.tact.psi.TactReferencedType +import org.ton.intellij.tact.stub.index.TactTypesIndex +import org.ton.intellij.util.processAllKeys + +class TactTypeCompletionProvider : TactCompletionProvider() { + override val elementPattern = psiElement().withParent(TactReferencedType::class.java) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + val position = parameters.position + val project = position.project + processAllKeys(TactTypesIndex.KEY, project) { key -> + TactTypesIndex.findElementsByName(project, key).forEach { + result.addElement(LookupElementBuilder.createWithIcon(it)) + return@forEach + } + Int + true + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/inspections/TactDiagnosticBasedInspection.kt b/src/main/kotlin/org/ton/intellij/tact/inspections/TactDiagnosticBasedInspection.kt new file mode 100644 index 0000000..4e015ee --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/inspections/TactDiagnosticBasedInspection.kt @@ -0,0 +1,21 @@ +package org.ton.intellij.tact.inspections + +import com.intellij.codeInspection.ProblemsHolder +import org.ton.intellij.tact.psi.TactFunctionLike +import org.ton.intellij.tact.psi.TactVisitor +import org.ton.intellij.tact.type.selfInferenceResult + +abstract class TactDiagnosticBasedInspection : TactLocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : TactVisitor() { + override fun visitFunctionLike(o: TactFunctionLike) { + o.selfInferenceResult.diagnostics.forEach { + if (it.canApply(this@TactDiagnosticBasedInspection)) { + it.addToHolder(holder) + } + } + super.visitFunctionLike(o) + } + } +} + +class TactTypeCheckInspection : TactDiagnosticBasedInspection() diff --git a/src/main/kotlin/org/ton/intellij/tact/inspections/TactLocalInspectionTool.kt b/src/main/kotlin/org/ton/intellij/tact/inspections/TactLocalInspectionTool.kt new file mode 100644 index 0000000..18b4b3a --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/inspections/TactLocalInspectionTool.kt @@ -0,0 +1,26 @@ +package org.ton.intellij.tact.inspections + +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.ton.intellij.tact.psi.TactFile + +abstract class TactLocalInspectionTool : LocalInspectionTool() { + override fun buildVisitor( + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession + ): PsiElementVisitor { + val file = session.file + return if (file is TactFile && isApplicableTo(file)) { + buildVisitor(holder, isOnTheFly) + } else { + PsiElementVisitor.EMPTY_VISITOR + } + } + + protected fun isApplicableTo(file: TactFile): Boolean { + return 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 new file mode 100644 index 0000000..e20dde2 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/inspections/TactUnresolvedReferenceInspection.kt @@ -0,0 +1,41 @@ +package org.ton.intellij.tact.inspections + +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.ton.intellij.tact.TactBundle +import org.ton.intellij.tact.psi.TactCallExpression +import org.ton.intellij.tact.psi.TactReferenceExpression +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) + reference.resolve() ?: return holder.registerProblem(o) + } + + override fun visitCallExpression(o: TactCallExpression) { + val reference = o.reference ?: return holder.registerProblem(o) + reference.resolve() ?: return holder.registerProblem(o) + } + } + + private fun ProblemsHolder.registerProblem( + element: TactReferenceExpression, + ) { + val referenceName = element.identifier.text + val description = TactBundle.message("inspection.message.unresolved.reference", referenceName) + val highlightedElement = element.identifier + registerProblem(highlightedElement, description, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + } + + private fun ProblemsHolder.registerProblem( + element: TactCallExpression, + ) { + val referenceName = element.identifier.text + val description = TactBundle.message("inspection.message.unresolved.reference", referenceName) + val highlightedElement = element.identifier + registerProblem(highlightedElement, description, ProblemHighlightType.LIKE_UNKNOWN_SYMBOL) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/linemarkers/TactImplLineMarkerProvider.kt b/src/main/kotlin/org/ton/intellij/tact/linemarkers/TactImplLineMarkerProvider.kt new file mode 100644 index 0000000..6cc0ffd --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/linemarkers/TactImplLineMarkerProvider.kt @@ -0,0 +1,48 @@ +package org.ton.intellij.tact.linemarkers + +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder +import com.intellij.psi.PsiElement +import com.intellij.psi.util.elementType +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.* +import org.ton.intellij.util.ancestorStrict +import javax.swing.Icon + +class TactImplLineMarkerProvider : RelatedItemLineMarkerProvider() { + override fun collectNavigationMarkers( + element: PsiElement, + result: MutableCollection> + ) { + if (element.elementType != TactElementTypes.IDENTIFIER) return + val abstractable = element.parent as? TactAbstractable ?: return + val superItem = abstractable.superItem ?: return + val superTrait = superItem.ancestorStrict() ?: return + + val icon: Icon + val action: String + val name = superTrait.name ?: return + val type = when (superItem) { + is TactFunction -> "method" + is TactConstant -> "constant" + else -> return + } + + if (superItem.isAbstract) { + icon = TactIcons.OVERRIDING_METHOD + action = "Overrides" + } else { + icon = TactIcons.IMPLEMENTING_METHOD + action = "Implements" + } + + val marker = NavigationGutterIconBuilder + .create(icon) + .setTarget(superItem) + .setTooltipText("$action $type in `$name`") + .createLineMarkerInfo(element) + + result.add(marker) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt b/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt index eaa96c1..f11295e 100644 --- a/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt +++ b/src/main/kotlin/org/ton/intellij/tact/parser/TactParserDefinition.kt @@ -14,7 +14,7 @@ import org.ton.intellij.tact.psi.TactFile import org.ton.intellij.tact.stub.TactFileStub class TactParserDefinition : ParserDefinition { - override fun createLexer(project: Project) = FlexAdapter(org.ton.intellij.tact.parser._TactLexer()) + override fun createLexer(project: Project) = TactLexer() override fun createParser(project: Project?) = TactParser() @@ -28,3 +28,5 @@ class TactParserDefinition : ParserDefinition { override fun createFile(viewProvider: FileViewProvider) = TactFile(viewProvider) } + +class TactLexer : FlexAdapter(org.ton.intellij.tact.parser._TactLexer()) diff --git a/src/main/kotlin/org/ton/intellij/tact/project/TactLibrary.kt b/src/main/kotlin/org/ton/intellij/tact/project/TactLibrary.kt new file mode 100644 index 0000000..c7f509d --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/project/TactLibrary.kt @@ -0,0 +1,58 @@ +package org.ton.intellij.tact.project + +import com.intellij.navigation.ItemPresentation +import com.intellij.openapi.project.BaseProjectDirectories.Companion.getBaseDirectories +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.AdditionalLibraryRootsProvider +import com.intellij.openapi.roots.SyntheticLibrary +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.findDirectory +import com.intellij.openapi.vfs.findFile +import org.ton.intellij.tact.TactIcons +import javax.swing.Icon + +class TactLibrary( + private val name: String, + private val sourceRoots: Set, + private val version: String? = null +) : SyntheticLibrary(), ItemPresentation { + override fun equals(other: Any?): Boolean = other is TactLibrary && other.sourceRoots == sourceRoots + + override fun hashCode(): Int = sourceRoots.hashCode() + + override fun getPresentableText(): String = if (version != null) "$name $version" else name + + override fun getIcon(unused: Boolean): Icon = TactIcons.FILE + + override fun getSourceRoots(): Collection = sourceRoots +} + +class TactAdditionalLibraryRootsProvider : AdditionalLibraryRootsProvider() { + override fun getAdditionalProjectLibraries(project: Project): Collection { + val stdlibLibrary = makeStdlibLibrary(project) ?: return emptyList() + return listOf(stdlibLibrary) + } +} + +private fun makeStdlibLibrary(project: Project): TactLibrary? { + val sourceRoots = mutableSetOf() + + val projectFile = project.getBaseDirectories().firstOrNull() ?: return null + val stdlibSrc = projectFile.findDirectory("node_modules/@tact-lang/compiler/stdlib") + if (stdlibSrc != null) { + sourceRoots.addAll(stdlibSrc.children) + val packageJson = stdlibSrc.parent.findFile("package.json") + if (packageJson != null) { + val version = packageJson.inputStream.bufferedReader().use { reader -> + val regex = """"version":\s*"([^"]+)"""".toRegex() + for (line in reader.readText().lines()) { + val match = regex.find(line) ?: continue + return@use match.groupValues[1] + } + null + } + return TactLibrary("stdlib", sourceRoots, version) + } + } + return TactLibrary("stdlib", sourceRoots) +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactAbstractable.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactAbstractable.kt new file mode 100644 index 0000000..0ec2d58 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactAbstractable.kt @@ -0,0 +1,37 @@ +package org.ton.intellij.tact.psi + +import org.ton.intellij.util.parentOfType +import javax.swing.Icon + +interface TactAbstractable : TactNameIdentifierOwner { + val isAbstract: Boolean + + override fun getIcon(flags: Int): Icon +} + +val TactAbstractable.superItem: TactAbstractable? + get() { + val name = name ?: return null + val owner = parentOfType() ?: return null + + val filter = when (this) { + is TactFunction -> { + { element: TactNamedElement -> element is TactFunction && element.name == name } + } + + is TactConstant -> { + { element: TactNamedElement -> element is TactConstant && element.name == name } + } + + else -> error("unreachable") + } + for (superTrait in owner.superTraits) { + for (member in superTrait.members) { + if (filter(member)) { + return member as TactAbstractable + } + } + } + + return null + } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactFunctionLike.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactFunctionLike.kt new file mode 100644 index 0000000..fe49b0c --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactFunctionLike.kt @@ -0,0 +1,5 @@ +package org.ton.intellij.tact.psi + +interface TactFunctionLike : TactInferenceContextOwner { + val functionParameters: TactFunctionParameters? +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactInferenceContextOwner.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactInferenceContextOwner.kt new file mode 100644 index 0000000..e7bba72 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactInferenceContextOwner.kt @@ -0,0 +1,5 @@ +package org.ton.intellij.tact.psi + +interface TactInferenceContextOwner : TactElement { + val body: TactBlock? +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt index 2b56fea..ae22600 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactTokenType.kt @@ -42,9 +42,11 @@ val TACT_KEYWORDS = tokenSetOf( CONTRACT_KEYWORD, TRAIT_KEYWORD, WITH_KEYWORD, - INIT_KEYWORD, RECEIVE_KEYWORD, - BOUNCED_KEYWORD, EXTERNAL_KEYWORD, - INT_OF_KEYWORD + PRIMITIVE_KEYWORD, + SELF_KEYWORD, + INIT_KEYWORD, + BOUNCED_KEYWORD, + MAP_KEYWORD ) diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactTypeDeclarationElement.kt b/src/main/kotlin/org/ton/intellij/tact/psi/TactTypeDeclarationElement.kt new file mode 100644 index 0000000..7fccb8b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/TactTypeDeclarationElement.kt @@ -0,0 +1,11 @@ +package org.ton.intellij.tact.psi + +import org.ton.intellij.tact.type.TactTy + +interface TactTypeDeclarationElement : TactNamedElement { + val declaredTy: TactTy + + val superTraits: Sequence get() = emptySequence() + + val members: Sequence +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactBouncedFunctionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactBouncedFunctionImplMixin.kt new file mode 100644 index 0000000..3e1d3dc --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactBouncedFunctionImplMixin.kt @@ -0,0 +1,13 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import org.ton.intellij.tact.psi.TactBlock +import org.ton.intellij.tact.psi.TactBouncedFunction +import org.ton.intellij.tact.psi.TactInferenceContextOwner + +abstract class TactBouncedFunctionImplMixin( + node: ASTNode +) : ASTWrapperPsiElement(node), TactBouncedFunction, TactInferenceContextOwner { + override val body: TactBlock? get() = block +} 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 new file mode 100644 index 0000000..495a9cc --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactCallExpressionImplMixin.kt @@ -0,0 +1,13 @@ +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.resolve.TactFieldReference + +abstract class TactCallExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactCallExpression { + override fun getReference(): PsiReference { + return TactFieldReference(this, identifier.textRangeInParent) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactConstantImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactConstantImplMixin.kt new file mode 100644 index 0000000..016e8d3 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactConstantImplMixin.kt @@ -0,0 +1,23 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiReference +import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactConstant +import org.ton.intellij.tact.psi.TactNamedElementImpl +import org.ton.intellij.tact.stub.TactConstantStub +import javax.swing.Icon + +abstract class TactConstantImplMixin : TactNamedElementImpl, TactConstant { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactConstantStub, type: IStubElementType<*, *>) : super(stub, type) + + override val isAbstract: Boolean + get() = greenStub?.isAbstract ?: constantAttributeList.any { it.abstractKeyword != null } + + override fun getReference(): PsiReference? = super.getReference() + + override fun getIcon(flags: Int): Icon = TactIcons.CONSTANT +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractImplMixin.kt new file mode 100644 index 0000000..1fec79b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractImplMixin.kt @@ -0,0 +1,85 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.TactConstantStub +import org.ton.intellij.tact.stub.TactContractStub +import org.ton.intellij.tact.stub.TactFieldStub +import org.ton.intellij.tact.stub.TactFunctionStub +import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.util.getChildrenByType +import org.ton.intellij.util.recursionGuard +import javax.swing.Icon + +abstract class TactContractImplMixin : TactNamedElementImpl, TactContract { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactContractStub, type: IStubElementType<*, *>) : super(stub, type) + + override val declaredTy: TactTy + get() = TactTyRef(this) + + override val superTraits: Sequence + get() = recursionGuard(this, { + sequence { + withClause?.typeList?.forEach { + val typeDeclaration = (it.reference?.resolve() as? TactTypeDeclarationElement) ?: return@forEach + yield(typeDeclaration) + yieldAll(typeDeclaration.superTraits) + } + } + }, memoize = false) ?: emptySequence() + + val constants: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.CONSTANT, TactConstantStub.Type.ARRAY_FACTORY) + } else { + contractBody?.constantList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + val fields: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FIELD, TactFieldStub.Type.ARRAY_FACTORY) + } else { + contractBody?.fieldList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + val functions: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FUNCTION, TactFunctionStub.Type.ARRAY_FACTORY) + } else { + contractBody?.functionList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + override val members: Sequence + get() = recursionGuard(this, { + sequence { + yieldAll(constants.asSequence()) + yieldAll(fields.asSequence()) + yieldAll(functions.asSequence()) + + superTraits.forEach { + yieldAll(it.members) + } + } + }, memoize = false) ?: emptySequence() + + override fun getIcon(flags: Int): Icon = TactIcons.CONTRACT +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractInitImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractInitImplMixin.kt new file mode 100644 index 0000000..ce327f2 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactContractInitImplMixin.kt @@ -0,0 +1,12 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import org.ton.intellij.tact.psi.TactBlock +import org.ton.intellij.tact.psi.TactContractInit +import org.ton.intellij.tact.psi.TactInferenceContextOwner + +abstract class TactContractInitImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactContractInit, + TactInferenceContextOwner { + override val body: TactBlock? get() = block +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactExternalFunctionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactExternalFunctionImplMixin.kt new file mode 100644 index 0000000..3fcf8da --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactExternalFunctionImplMixin.kt @@ -0,0 +1,14 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import org.ton.intellij.tact.psi.TactBlock +import org.ton.intellij.tact.psi.TactExternalFunction +import org.ton.intellij.tact.psi.TactInferenceContextOwner +import org.ton.intellij.tact.psi.TactReceiveFunction + +abstract class TactExternalFunctionImplMixin( + node: ASTNode +) : ASTWrapperPsiElement(node), TactExternalFunction, TactInferenceContextOwner { + override val body: TactBlock? get() = block +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldExpressionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldExpressionImplMixin.kt new file mode 100644 index 0000000..6aa7a05 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldExpressionImplMixin.kt @@ -0,0 +1,15 @@ +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.TactFieldExpression +import org.ton.intellij.tact.psi.TactStructExpression +import org.ton.intellij.tact.resolve.TactFieldReference +import org.ton.intellij.tact.resolve.TactTypeReference + +abstract class TactFieldExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactFieldExpression { + override fun getReference(): PsiReference { + return TactFieldReference(this, identifier.textRangeInParent) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldImplMixin.kt new file mode 100644 index 0000000..dfc18c7 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFieldImplMixin.kt @@ -0,0 +1,22 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiReference +import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.psi.TactField +import org.ton.intellij.tact.psi.TactNamedElementImpl +import org.ton.intellij.tact.stub.TactFieldStub + +abstract class TactFieldImplMixin : TactNamedElementImpl, TactField { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactFieldStub, type: IStubElementType<*, *>) : super(stub, type) + + override fun getReference(): PsiReference? { + return super.getReference() + } + + override fun toString(): String { + return "TactField(name=$name)" + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunction.kt similarity index 63% rename from src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt rename to src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunction.kt index 692ddfb..ab8dfd2 100644 --- a/src/main/kotlin/org/ton/intellij/tact/psi/TactFunction.kt +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunction.kt @@ -1,14 +1,30 @@ -package org.ton.intellij.tact.psi +package org.ton.intellij.tact.psi.impl import com.intellij.lang.ASTNode import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactFunction +import org.ton.intellij.tact.psi.TactInferenceContextOwner +import org.ton.intellij.tact.psi.TactNamedElementImpl import org.ton.intellij.tact.stub.TactFunctionStub import org.ton.intellij.util.greenStub +import javax.swing.Icon -abstract class TactFunctionImplMixin : TactNamedElementImpl, TactFunction { +abstract class TactFunctionImplMixin : TactNamedElementImpl, TactFunction, TactInferenceContextOwner { constructor(node: ASTNode) : super(node) constructor(stub: TactFunctionStub, type: IStubElementType<*, *>) : super(stub, type) + + override val body get() = block + + override val isAbstract: Boolean + get() = greenStub?.isAbstract ?: functionAttributeList.any { it.abstractKeyword != null } + + override fun getIcon(flags: Int): Icon = TactIcons.FUNCTION + + override fun toString(): String { + return "TactFunction(name=$name)" + } } val TactFunction.isNative get() = greenStub?.isNative ?: (nativeKeyword != null) @@ -18,4 +34,3 @@ val TactFunction.isExtends get() = greenStub?.isExtends ?: functionAttributeList val TactFunction.isVirtual get() = greenStub?.isVirtual ?: functionAttributeList.any { it.virtualKeyword != null } val TactFunction.isOverride get() = greenStub?.isOverride ?: functionAttributeList.any { it.overrideKeyword != null } val TactFunction.isInline get() = greenStub?.isInline ?: functionAttributeList.any { it.inlineKeyword != null } -val TactFunction.isAbstract get() = greenStub?.isAbstract ?: functionAttributeList.any { it.abstractKeyword != null } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunctionParameterImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunctionParameterImplMixin.kt new file mode 100644 index 0000000..943799a --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactFunctionParameterImplMixin.kt @@ -0,0 +1,18 @@ +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.psi.TactFunctionParameter +import org.ton.intellij.tact.psi.TactPsiFactory + +abstract class TactFunctionParameterImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactFunctionParameter { + 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 +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactInitOfExpressionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactInitOfExpressionImplMixin.kt new file mode 100644 index 0000000..8244769 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactInitOfExpressionImplMixin.kt @@ -0,0 +1,13 @@ +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.TactInitOfExpression +import org.ton.intellij.tact.resolve.TactTypeReference + +abstract class TactInitOfExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactInitOfExpression { + override fun getReference(): PsiReference? { + return TactTypeReference(this, identifier?.textRangeInParent ?: return null) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactLetStatementImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactLetStatementImplMixin.kt new file mode 100644 index 0000000..cc6a231 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactLetStatementImplMixin.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.TactLetStatement +import org.ton.intellij.tact.psi.TactPsiFactory +import javax.swing.Icon + +abstract class TactLetStatementImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactLetStatement { + 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 ?: super.getTextOffset() + + override fun getIcon(flags: Int): Icon = TactIcons.VARIABLE +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactMessageImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactMessageImplMixin.kt new file mode 100644 index 0000000..dd49776 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactMessageImplMixin.kt @@ -0,0 +1,41 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.TactFieldStub +import org.ton.intellij.tact.stub.TactMessageStub +import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.util.getChildrenByType +import javax.swing.Icon + +abstract class TactMessageImplMixin : TactNamedElementImpl, TactMessage { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactMessageStub, type: IStubElementType<*, *>) : super(stub, type) + + override val declaredTy: TactTy + get() = TactTyRef(this) + + val fields: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FIELD, TactFieldStub.Type.ARRAY_FACTORY) + } else { + blockFields?.fieldList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + + override val members: Sequence + get() = fields.asSequence() + + override fun getIcon(flags: Int): Icon = TactIcons.MESSAGE + +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactPrimitiveImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactPrimitiveImplMixin.kt new file mode 100644 index 0000000..5258658 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactPrimitiveImplMixin.kt @@ -0,0 +1,24 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.TactNamedElement +import org.ton.intellij.tact.psi.TactNamedElementImpl +import org.ton.intellij.tact.psi.TactPrimitive +import org.ton.intellij.tact.stub.TactPrimitiveStub +import org.ton.intellij.tact.type.TactTyRef +import javax.swing.Icon + +abstract class TactPrimitiveImplMixin : TactNamedElementImpl, TactPrimitive { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactPrimitiveStub, type: IStubElementType<*, *>) : super(stub, type) + + override val declaredTy + get() = TactTyRef(this) + + override val members: Sequence get() = emptySequence() + + override fun getIcon(flags: Int): Icon = TactIcons.PRIMITIVE +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReceiveFunctionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReceiveFunctionImplMixin.kt new file mode 100644 index 0000000..75e7b47 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReceiveFunctionImplMixin.kt @@ -0,0 +1,13 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.extapi.psi.ASTWrapperPsiElement +import com.intellij.lang.ASTNode +import org.ton.intellij.tact.psi.TactBlock +import org.ton.intellij.tact.psi.TactInferenceContextOwner +import org.ton.intellij.tact.psi.TactReceiveFunction + +abstract class TactReceiveFunctionImplMixin( + node: ASTNode +) : ASTWrapperPsiElement(node), TactReceiveFunction, TactInferenceContextOwner { + override val body: TactBlock? get() = block +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferenceExpressionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferenceExpressionImplMixin.kt new file mode 100644 index 0000000..ac67cc1 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferenceExpressionImplMixin.kt @@ -0,0 +1,15 @@ +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.TactReferenceExpression +import org.ton.intellij.tact.resolve.TactFieldReference + +abstract class TactReferenceExpressionImplMixin( + node: ASTNode +) : ASTWrapperPsiElement(node), TactReferenceExpression { + override fun getReference(): PsiReference { + return TactFieldReference(this, identifier.textRangeInParent) + } +} 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 new file mode 100644 index 0000000..cc43f28 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactReferencedTypeImplMixin.kt @@ -0,0 +1,25 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiReference +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.TactTyNullable + +abstract class TactReferencedTypeImplMixin( + node: ASTNode +) : TactTypeImpl(node), TactReferencedType { + override fun getReference(): PsiReference? = TactTypeReference(this, identifier.textRangeInParent) +} + +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 + } diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructExpressionImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructExpressionImplMixin.kt new file mode 100644 index 0000000..6fcf876 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructExpressionImplMixin.kt @@ -0,0 +1,13 @@ +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.TactStructExpression +import org.ton.intellij.tact.resolve.TactTypeReference + +abstract class TactStructExpressionImplMixin(node: ASTNode) : ASTWrapperPsiElement(node), TactStructExpression { + override fun getReference(): PsiReference { + return TactTypeReference(this, identifier.textRangeInParent) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructImplMixin.kt new file mode 100644 index 0000000..c48765b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactStructImplMixin.kt @@ -0,0 +1,38 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.TactFieldStub +import org.ton.intellij.tact.stub.TactStructStub +import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.util.getChildrenByType +import javax.swing.Icon + +abstract class TactStructImplMixin : TactNamedElementImpl, TactStruct { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactStructStub, type: IStubElementType<*, *>) : super(stub, type) + + override val declaredTy: TactTy get() = TactTyRef(this) + + val fields: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FIELD, TactFieldStub.Type.ARRAY_FACTORY) + } else { + blockFields?.fieldList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + override val members: Sequence + get() = fields.asSequence() + + override fun getIcon(flags: Int): Icon = TactIcons.STRUCT +} diff --git a/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactTraitImplMixin.kt b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactTraitImplMixin.kt new file mode 100644 index 0000000..e4ded6e --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/psi/impl/TactTraitImplMixin.kt @@ -0,0 +1,96 @@ +package org.ton.intellij.tact.psi.impl + +import com.intellij.lang.ASTNode +import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import org.ton.intellij.tact.TactIcons +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.TactConstantStub +import org.ton.intellij.tact.stub.TactFieldStub +import org.ton.intellij.tact.stub.TactFunctionStub +import org.ton.intellij.tact.stub.TactTraitStub +import org.ton.intellij.tact.stub.index.TactTypesIndex +import org.ton.intellij.tact.type.TactTy +import org.ton.intellij.tact.type.TactTyRef +import org.ton.intellij.util.getChildrenByType +import org.ton.intellij.util.recursionGuard +import javax.swing.Icon + +abstract class TactTraitImplMixin : TactNamedElementImpl, TactTrait { + constructor(node: ASTNode) : super(node) + + constructor(stub: TactTraitStub, type: IStubElementType<*, *>) : super(stub, type) + + override val declaredTy: TactTy + get() = TactTyRef(this) + + override val superTraits: Sequence + get() = recursionGuard(this, { + sequence { + withClause?.typeList?.forEach { + val typeDeclaration = (it.reference?.resolve() as? TactTypeDeclarationElement) ?: return@forEach + yield(typeDeclaration) + yieldAll(typeDeclaration.superTraits) + } + if (name != "BaseTrait") { + val baseTrait = TactTypesIndex.findElementsByName(project, "BaseTrait") + .filterIsInstance() + .firstOrNull { it.containingFile.name == "base.tact" } + if (baseTrait != null) { + yield(baseTrait) + } + } + } + }, memoize = false) ?: emptySequence() + + val constants: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.CONSTANT, TactConstantStub.Type.ARRAY_FACTORY) + } else { + traitBody?.constantList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + val fields: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FIELD, TactFieldStub.Type.ARRAY_FACTORY) + } else { + traitBody?.fieldList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + val functions: List + get() = CachedValuesManager.getCachedValue(this) { + val stub = stub + val functions = if (stub != null) { + getChildrenByType(stub, TactElementTypes.FUNCTION, TactFunctionStub.Type.ARRAY_FACTORY) + } else { + traitBody?.functionList ?: emptyList() + } + CachedValueProvider.Result.create(functions, this) + } + + override val members: Sequence + get() = recursionGuard(this, { + sequence { + yieldAll(constants.asSequence()) + yieldAll(fields.asSequence()) + yieldAll(functions.asSequence()) + + superTraits.forEach { + yieldAll(it.members) + } + } + }, memoize = false) ?: emptySequence() + + override fun toString(): String = "TactTrait(name=$name)" + + override fun getIcon(flags: Int): Icon = TactIcons.TRAIT +} diff --git a/src/main/kotlin/org/ton/intellij/tact/resolve/TactFieldReference.kt b/src/main/kotlin/org/ton/intellij/tact/resolve/TactFieldReference.kt new file mode 100644 index 0000000..1fb825b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactFieldReference.kt @@ -0,0 +1,16 @@ +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.TactInferenceContextOwner +import org.ton.intellij.tact.type.selfInferenceResult +import org.ton.intellij.util.ancestorStrict + +class TactFieldReference(element: T, range: TextRange) : TactReferenceBase( + element, range +) { + override fun multiResolve(): Collection { + val inference = element.ancestorStrict()?.selfInferenceResult + return inference?.getResolvedRefs(element)?.mapNotNull { it.element as? TactElement } ?: emptyList() + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt b/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt index e3f42a7..0f9de71 100644 --- a/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactReference.kt @@ -1,7 +1,11 @@ package org.ton.intellij.tact.resolve import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.openapi.util.TextRange import com.intellij.psi.* +import com.intellij.psi.impl.source.resolve.ResolveCache +import org.apache.xml.resolver.apps.resolver +import org.ton.intellij.func.psi.impl.FuncReference import org.ton.intellij.tact.psi.TactElement interface TactReference : PsiPolyVariantReference { @@ -9,14 +13,21 @@ interface TactReference : PsiPolyVariantReference { override fun resolve(): TactElement? - fun multiResolve(): List + fun multiResolve(): Collection } -abstract class TactReferenceBase(element: T) : PsiPolyVariantReferenceBase(element), TactReference { +abstract class TactReferenceBase(element: T, range: TextRange) : + PsiPolyVariantReferenceBase(element, range), TactReference { + private val resolver = ResolveCache.PolyVariantResolver> { t, incompleteCode -> + if (!myElement.isValid) return@PolyVariantResolver ResolveResult.EMPTY_ARRAY + multiResolve().map(::PsiElementResolveResult).toTypedArray() + } + override fun resolve(): TactElement? = super.resolve() as? TactElement override fun multiResolve(incompleteCode: Boolean): Array { - return multiResolve().map(::PsiElementResolveResult).toTypedArray() + if (!myElement.isValid) return ResolveResult.EMPTY_ARRAY + return ResolveCache.getInstance(myElement.project).resolveWithCaching(this, resolver, false, incompleteCode) } override fun getVariants(): Array = LookupElement.EMPTY_ARRAY diff --git a/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt b/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt new file mode 100644 index 0000000..b989932 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/resolve/TactTypeReference.kt @@ -0,0 +1,22 @@ +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 + +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.asSequence() + .filterIsInstance() + .find { it.containingFile == currentFile } + if (localType != null) { + return listOf(localType) + } + return listOf(result.firstOrNull() as? TactTypeDeclarationElement ?: return emptyList()) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactConstantStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactConstantStub.kt new file mode 100644 index 0000000..685cf9a --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactConstantStub.kt @@ -0,0 +1,74 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.ArrayFactory +import com.intellij.util.BitUtil +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactConstant +import org.ton.intellij.tact.psi.impl.TactConstantImpl +import org.ton.intellij.tact.stub.index.indexConstant +import org.ton.intellij.util.BitFlagsBuilder + +class TactConstantStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, + val flags: Byte, +) : TactNamedStub(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?, flags: Byte) : this( + parent, + elementType, + StringRef.fromString(name), + flags + ) + + val isAbstract get() = BitUtil.isSet(flags, Flags.ABSTRACT) + val isOverride get() = BitUtil.isSet(flags, Flags.OVERRIDE) + val isVirtual get() = BitUtil.isSet(flags, Flags.VIRTUAL) + + override fun toString(): String { + return "${javaClass.simpleName}(name=$name, flags=$flags, isAbstract=$isAbstract, isOverride=$isOverride, isVirtual=$isVirtual)" + } + + object Type : TactStubElementType("CONSTANT") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactConstantStub { + return TactConstantStub(parentStub, this, dataStream.readName(), dataStream.readByte()) + } + + override fun serialize(stub: TactConstantStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + dataStream.writeByte(stub.flags.toInt()) + } + + override fun createPsi(stub: TactConstantStub): TactConstant { + return TactConstantImpl(stub, this) + } + + override fun createStub(psi: TactConstant, parentStub: StubElement<*>): TactConstantStub { + var flags = 0.toByte() + flags = BitUtil.set(flags, Flags.ABSTRACT, psi.isAbstract) + psi.constantAttributeList.forEach { + if (it.virtualKeyword != null) { + flags = BitUtil.set(flags, Flags.VIRTUAL, true) + } + if (it.overrideKeyword != null) { + flags = BitUtil.set(flags, Flags.OVERRIDE, true) + } + } + return TactConstantStub(parentStub, this, psi.name, flags) + } + + override fun indexStub(stub: TactConstantStub, sink: IndexSink) = sink.indexConstant(stub) + + val EMPTY_ARRAY = emptyArray() + val ARRAY_FACTORY: ArrayFactory = ArrayFactory { + if (it == 0) EMPTY_ARRAY else arrayOfNulls(it) + } + } + + private object Flags : BitFlagsBuilder(Limit.BYTE) { + val ABSTRACT = nextBitMask().toByte() + val OVERRIDE = nextBitMask().toByte() + val VIRTUAL = nextBitMask().toByte() + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactContractStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactContractStub.kt new file mode 100644 index 0000000..71abf9d --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactContractStub.kt @@ -0,0 +1,45 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactContract +import org.ton.intellij.tact.psi.impl.TactContractImpl +import org.ton.intellij.tact.stub.index.indexContract +import org.ton.intellij.tact.stub.index.indexMessage +import org.ton.intellij.tact.stub.index.indexStruct + +class TactContractStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, +) : TactNamedStub(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?) : this( + parent, + elementType, + StringRef.fromString(name), + ) + + override fun toString(): String { + return "${javaClass.simpleName}(name=$name)" + } + + object Type : TactStubElementType("CONTRACT") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactContractStub { + return TactContractStub(parentStub, this, dataStream.readName()) + } + + override fun serialize(stub: TactContractStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + } + + override fun createPsi(stub: TactContractStub): TactContract { + return TactContractImpl(stub, this) + } + + override fun createStub(psi: TactContract, parentStub: StubElement<*>): TactContractStub { + return TactContractStub(parentStub, this, psi.name) + } + + override fun indexStub(stub: TactContractStub, sink: IndexSink) = sink.indexContract(stub) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactFieldStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactFieldStub.kt new file mode 100644 index 0000000..0db6842 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactFieldStub.kt @@ -0,0 +1,49 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.ArrayFactory +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactField +import org.ton.intellij.tact.psi.impl.TactFieldImpl +import org.ton.intellij.tact.stub.index.indexField + +class TactFieldStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, +) : TactNamedStub(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?) : this( + parent, + elementType, + StringRef.fromString(name), + ) + + override fun toString(): String { + return "${javaClass.simpleName}(name=$name)" + } + + object Type : TactStubElementType("FIELD") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactFieldStub { + return TactFieldStub(parentStub, this, dataStream.readName()) + } + + override fun serialize(stub: TactFieldStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + } + + override fun createPsi(stub: TactFieldStub): TactField { + return TactFieldImpl(stub, this) + } + + override fun createStub(psi: TactField, parentStub: StubElement<*>): TactFieldStub { + return TactFieldStub(parentStub, this, psi.name) + } + + override fun indexStub(stub: TactFieldStub, sink: IndexSink) = sink.indexField(stub) + + val EMPTY_ARRAY = emptyArray() + val ARRAY_FACTORY: ArrayFactory = ArrayFactory { + if (it == 0) EMPTY_ARRAY else arrayOfNulls(it) + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactFunctionStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactFunctionStub.kt new file mode 100644 index 0000000..922d022 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactFunctionStub.kt @@ -0,0 +1,88 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.ArrayFactory +import com.intellij.util.BitUtil +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactFunction +import org.ton.intellij.tact.psi.impl.* +import org.ton.intellij.tact.stub.index.indexFunction +import org.ton.intellij.util.BitFlagsBuilder + +class TactFunctionStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, + val flags: Int, +) : TactNamedStub(parent, elementType, name) { + constructor( + parent: StubElement<*>, elementType: IStubElementType<*, *>, + name: String?, + flags: Int, + ) : this( + parent, + elementType, + StringRef.fromString(name), + flags + ) + + val isNative get() = BitUtil.isSet(flags, Flags.NATIVE) + val isGet get() = BitUtil.isSet(flags, Flags.GET) + val isMutates get() = BitUtil.isSet(flags, Flags.MUTATES) + val isExtends get() = BitUtil.isSet(flags, Flags.EXTENDS) + val isVirtual get() = BitUtil.isSet(flags, Flags.VIRTUAL) + val isOverride get() = BitUtil.isSet(flags, Flags.OVERRIDE) + val isInline get() = BitUtil.isSet(flags, Flags.INLINE) + val isAbstract get() = BitUtil.isSet(flags, Flags.ABSTRACT) + + + object Type : TactStubElementType("FUNCTION") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactFunctionStub { + return TactFunctionStub(parentStub, this, dataStream.readName(), dataStream.readInt()) + } + + override fun serialize(stub: TactFunctionStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + dataStream.writeInt(stub.flags) + } + + override fun createPsi(stub: TactFunctionStub): TactFunction { + return TactFunctionImpl(stub, this) + } + + override fun createStub(psi: TactFunction, parentStub: StubElement<*>): TactFunctionStub { + var flags = 0 + flags = BitUtil.set(flags, Flags.NATIVE, psi.isNative) + flags = BitUtil.set(flags, Flags.GET, psi.isGet) + flags = BitUtil.set(flags, Flags.MUTATES, psi.isMutates) + flags = BitUtil.set(flags, Flags.EXTENDS, psi.isExtends) + flags = BitUtil.set(flags, Flags.VIRTUAL, psi.isVirtual) + flags = BitUtil.set(flags, Flags.OVERRIDE, psi.isOverride) + flags = BitUtil.set(flags, Flags.INLINE, psi.isInline) + flags = BitUtil.set(flags, Flags.ABSTRACT, psi.isAbstract) + return TactFunctionStub(parentStub, this, psi.name, flags) + } + + override fun indexStub(stub: TactFunctionStub, sink: IndexSink) = sink.indexFunction(stub) + + val EMPTY_ARRAY = emptyArray() + val ARRAY_FACTORY: ArrayFactory = ArrayFactory { + if (it == 0) EMPTY_ARRAY else arrayOfNulls(it) + } + } + + private object Flags : BitFlagsBuilder(Limit.INT) { + val NATIVE = nextBitMask() + val GET = nextBitMask() + val MUTATES = nextBitMask() + val EXTENDS = nextBitMask() + val VIRTUAL = nextBitMask() + val OVERRIDE = nextBitMask() + val INLINE = nextBitMask() + val ABSTRACT = nextBitMask() + } + + override fun toString(): String { + return "TactFunctionStub(name=${name}, flags=$flags, isNative=$isNative, isGet=$isGet, isMutates=$isMutates, isExtends=$isExtends, isVirtual=$isVirtual, isOverride=$isOverride, isInline=$isInline, isAbstract=$isAbstract)" + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactMessageStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactMessageStub.kt new file mode 100644 index 0000000..2675c0b --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactMessageStub.kt @@ -0,0 +1,67 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactMessage +import org.ton.intellij.tact.psi.impl.TactMessageImpl +import org.ton.intellij.tact.stub.index.indexMessage + +class TactMessageStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, + val hasMessageId: Boolean, + val messageId: Int, +) : TactNamedStub(parent, elementType, name) { + constructor( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: String?, + hasMessageId: Boolean, + messageId: Int + ) : this( + parent, + elementType, + StringRef.fromString(name), + hasMessageId, + messageId + ) + + override fun toString(): String { + return "TactMessageStub(name=$name, hasMessageId=$hasMessageId, messageId=$messageId)" + } + + object Type : TactStubElementType("MESSAGE") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactMessageStub { + return TactMessageStub( + parentStub, + this, + dataStream.readName(), + dataStream.readBoolean(), + dataStream.readInt() + ) + } + + override fun serialize(stub: TactMessageStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + dataStream.writeBoolean(stub.hasMessageId) + dataStream.writeInt(stub.messageId) + } + + override fun createPsi(stub: TactMessageStub): TactMessage { + return TactMessageImpl(stub, this) + } + + override fun createStub(psi: TactMessage, parentStub: StubElement<*>): TactMessageStub { + val messageId = psi.messageId + val messageIdValueString = messageId?.integerLiteral?.text?.replace("_", "") + val messageIdValue = messageIdValueString?.toIntOrNull() + ?: messageIdValueString?.toIntOrNull(16) + ?: messageIdValueString?.toIntOrNull(2) + ?: 0 + return TactMessageStub(parentStub, this, psi.name, messageId != null, messageIdValue) + } + + override fun indexStub(stub: TactMessageStub, sink: IndexSink) = sink.indexMessage(stub) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactPrimitiveStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactPrimitiveStub.kt new file mode 100644 index 0000000..efc3e8e --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactPrimitiveStub.kt @@ -0,0 +1,43 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactPrimitive +import org.ton.intellij.tact.psi.impl.TactPrimitiveImpl +import org.ton.intellij.tact.stub.index.indexPrimitive + +class TactPrimitiveStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, +) : TactNamedStub(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?) : this( + parent, + elementType, + StringRef.fromString(name), + ) + + override fun toString(): String { + return "${javaClass.simpleName}(name=$name)" + } + + object Type : TactStubElementType("PRIMITIVE") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactPrimitiveStub { + return TactPrimitiveStub(parentStub, this, dataStream.readName()) + } + + override fun serialize(stub: TactPrimitiveStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + } + + override fun createPsi(stub: TactPrimitiveStub): TactPrimitive { + return TactPrimitiveImpl(stub, this) + } + + override fun createStub(psi: TactPrimitive, parentStub: StubElement<*>): TactPrimitiveStub { + return TactPrimitiveStub(parentStub, this, psi.name) + } + + override fun indexStub(stub: TactPrimitiveStub, sink: IndexSink) = sink.indexPrimitive(stub) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactStructStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactStructStub.kt new file mode 100644 index 0000000..baef547 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactStructStub.kt @@ -0,0 +1,40 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactStruct +import org.ton.intellij.tact.psi.impl.TactStructImpl +import org.ton.intellij.tact.stub.index.indexMessage +import org.ton.intellij.tact.stub.index.indexStruct + +class TactStructStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, +) : TactNamedStub(parent, elementType, name) { + constructor(parent: StubElement<*>, elementType: IStubElementType<*, *>, name: String?) : this( + parent, + elementType, + StringRef.fromString(name), + ) + + object Type : TactStubElementType("STRUCT") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactStructStub { + return TactStructStub(parentStub, this, dataStream.readName()) + } + + override fun serialize(stub: TactStructStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + } + + override fun createPsi(stub: TactStructStub): TactStruct { + return TactStructImpl(stub, this) + } + + override fun createStub(psi: TactStruct, parentStub: StubElement<*>): TactStructStub { + return TactStructStub(parentStub, this, psi.name) + } + + override fun indexStub(stub: TactStructStub, sink: IndexSink) = sink.indexStruct(stub) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt index a2fb8a4..993839a 100644 --- a/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactStub.kt @@ -4,13 +4,11 @@ import com.intellij.psi.PsiFile import com.intellij.psi.StubBuilder import com.intellij.psi.stubs.* import com.intellij.psi.tree.IStubFileElementType -import com.intellij.util.BitUtil import com.intellij.util.io.StringRef import org.ton.intellij.tact.TactLanguage -import org.ton.intellij.tact.psi.* -import org.ton.intellij.tact.psi.impl.TactFunctionImpl -import org.ton.intellij.tact.stub.index.indexFunction -import org.ton.intellij.util.BitFlagsBuilder +import org.ton.intellij.tact.psi.TactElement +import org.ton.intellij.tact.psi.TactFile +import org.ton.intellij.tact.psi.TactNamedElement abstract class TactNamedStub( parent: StubElement<*>?, @@ -43,7 +41,7 @@ class TactFileStub( override fun getType() = Type object Type : IStubFileElementType(TactLanguage) { - private const val STUB_VERSION = 2 + private const val STUB_VERSION = 7 override fun getStubVersion(): Int = STUB_VERSION @@ -72,79 +70,13 @@ class TactFileStub( fun factory(name: String): TactStubElementType<*, *> { return when (name) { "FUNCTION" -> TactFunctionStub.Type + "MESSAGE" -> TactMessageStub.Type + "STRUCT" -> TactStructStub.Type + "TRAIT" -> TactTraitStub.Type + "CONTRACT" -> TactContractStub.Type + "PRIMITIVE" -> TactPrimitiveStub.Type + "FIELD" -> TactFieldStub.Type + "CONSTANT" -> TactConstantStub.Type else -> error("Unknown element type: $name") } } - -class TactFunctionStub( - parent: StubElement<*>, - elementType: IStubElementType<*, *>, - name: StringRef?, - val flags: Int, -) : TactNamedStub(parent, elementType, name) { - constructor( - parent: StubElement<*>, elementType: IStubElementType<*, *>, - name: String?, - flags: Int, - ) : this( - parent, - elementType, - StringRef.fromString(name), - flags - ) - - val isNative get() = BitUtil.isSet(flags, Flags.NATIVE) - val isGet get() = BitUtil.isSet(flags, Flags.GET) - val isMutates get() = BitUtil.isSet(flags, Flags.MUTATES) - val isExtends get() = BitUtil.isSet(flags, Flags.EXTENDS) - val isVirtual get() = BitUtil.isSet(flags, Flags.VIRTUAL) - val isOverride get() = BitUtil.isSet(flags, Flags.OVERRIDE) - val isInline get() = BitUtil.isSet(flags, Flags.INLINE) - val isAbstract get() = BitUtil.isSet(flags, Flags.ABSTRACT) - - - object Type : TactStubElementType("FUNCTION") { - override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactFunctionStub { - return TactFunctionStub(parentStub, this, dataStream.readName(), dataStream.readInt()) - } - - override fun serialize(stub: TactFunctionStub, dataStream: StubOutputStream) { - dataStream.writeName(stub.name) - dataStream.writeInt(stub.flags) - } - - override fun createPsi(stub: TactFunctionStub): TactFunction { - return TactFunctionImpl(stub, this) - } - - override fun createStub(psi: TactFunction, parentStub: StubElement<*>): TactFunctionStub { - var flags = 0 - flags = BitUtil.set(flags, Flags.NATIVE, psi.isNative) - flags = BitUtil.set(flags, Flags.GET, psi.isGet) - flags = BitUtil.set(flags, Flags.MUTATES, psi.isMutates) - flags = BitUtil.set(flags, Flags.EXTENDS, psi.isExtends) - flags = BitUtil.set(flags, Flags.VIRTUAL, psi.isVirtual) - flags = BitUtil.set(flags, Flags.OVERRIDE, psi.isOverride) - flags = BitUtil.set(flags, Flags.INLINE, psi.isInline) - flags = BitUtil.set(flags, Flags.ABSTRACT, psi.isAbstract) - return TactFunctionStub(parentStub, this, psi.name, flags) - } - - override fun indexStub(stub: TactFunctionStub, sink: IndexSink) = sink.indexFunction(stub) - } - - private object Flags : BitFlagsBuilder(Limit.INT) { - val NATIVE = nextBitMask() - val GET = nextBitMask() - val MUTATES = nextBitMask() - val EXTENDS = nextBitMask() - val VIRTUAL = nextBitMask() - val OVERRIDE = nextBitMask() - val INLINE = nextBitMask() - val ABSTRACT = nextBitMask() - } - - override fun toString(): String { - return "TactFunctionStub(name=${name}, flags=$flags, isNative=$isNative, isGet=$isGet, isMutates=$isMutates, isExtends=$isExtends, isVirtual=$isVirtual, isOverride=$isOverride, isInline=$isInline, isAbstract=$isAbstract)" - } -} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/TactTraitStub.kt b/src/main/kotlin/org/ton/intellij/tact/stub/TactTraitStub.kt new file mode 100644 index 0000000..41f1806 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/TactTraitStub.kt @@ -0,0 +1,54 @@ +package org.ton.intellij.tact.stub + +import com.intellij.psi.stubs.* +import com.intellij.util.io.StringRef +import org.ton.intellij.tact.psi.TactTrait +import org.ton.intellij.tact.psi.impl.TactTraitImpl +import org.ton.intellij.tact.stub.index.indexMessage +import org.ton.intellij.tact.stub.index.indexStruct +import org.ton.intellij.tact.stub.index.indexTrait + +class TactTraitStub( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: StringRef?, + val withClause: List, +) : TactNamedStub(parent, elementType, name) { + constructor( + parent: StubElement<*>, + elementType: IStubElementType<*, *>, + name: String?, + withClause: List + ) : this( + parent, + elementType, + StringRef.fromString(name), + withClause.map { StringRef.fromString(it) } + ) + + object Type : TactStubElementType("TRAIT") { + override fun deserialize(dataStream: StubInputStream, parentStub: StubElement<*>): TactTraitStub { + return TactTraitStub( + parentStub, + this, + dataStream.readName(), + List(dataStream.readVarInt()) { dataStream.readName() }) + } + + override fun serialize(stub: TactTraitStub, dataStream: StubOutputStream) { + dataStream.writeName(stub.name) + dataStream.writeVarInt(stub.withClause.size) + stub.withClause.forEach { dataStream.writeName(it?.string) } + } + + override fun createPsi(stub: TactTraitStub): TactTrait { + return TactTraitImpl(stub, this) + } + + override fun createStub(psi: TactTrait, parentStub: StubElement<*>): TactTraitStub { + return TactTraitStub(parentStub, this, psi.name, psi.withClause?.typeList?.map { it.text } ?: emptyList()) + } + + override fun indexStub(stub: TactTraitStub, sink: IndexSink) = sink.indexTrait(stub) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt index 22bca1a..8413bf2 100644 --- a/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/StubIndexing.kt @@ -1,15 +1,59 @@ package org.ton.intellij.tact.stub.index import com.intellij.psi.stubs.IndexSink -import org.ton.intellij.tact.stub.TactFunctionStub -import org.ton.intellij.tact.stub.TactNamedStub +import org.ton.intellij.tact.stub.* fun IndexSink.indexFunction(stub: TactFunctionStub) { - indexNamedStub(stub) + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactFunctionIndex.KEY, it) + } +} + +fun IndexSink.indexMessage(stub: TactMessageStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactTypesIndex.KEY, it) + } +} + +fun IndexSink.indexStruct(stub: TactStructStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactTypesIndex.KEY, it) + } +} + +fun IndexSink.indexTrait(stub: TactTraitStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactTypesIndex.KEY, it) + } +} + +fun IndexSink.indexContract(stub: TactContractStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactTypesIndex.KEY, it) + } +} + +fun IndexSink.indexPrimitive(stub: TactPrimitiveStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactTypesIndex.KEY, it) + } +} + +fun IndexSink.indexField(stub: TactFieldStub) { + stub.name?.let { + occurrence(TactNamedElementIndex.KEY, it) + } } -private fun IndexSink.indexNamedStub(stub: TactNamedStub<*>) { +fun IndexSink.indexConstant(stub: TactConstantStub) { stub.name?.let { occurrence(TactNamedElementIndex.KEY, it) + occurrence(TactConstantIndex.KEY, it) } } diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/TactConstantIndex.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactConstantIndex.kt new file mode 100644 index 0000000..c7d74cc --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactConstantIndex.kt @@ -0,0 +1,30 @@ +package org.ton.intellij.tact.stub.index + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import org.ton.intellij.tact.psi.TactConstant +import org.ton.intellij.tact.stub.TactFileStub +import org.ton.intellij.util.checkCommitIsNotInProgress +import org.ton.intellij.util.getElements + +class TactConstantIndex : StringStubIndexExtension() { + override fun getVersion(): Int = TactFileStub.Type.stubVersion + + override fun getKey(): StubIndexKey = KEY + + companion object { + val KEY = + StubIndexKey.createIndexKey("org.ton.intellij.tact.stub.index.TactConstantIndex") + + fun findElementsByName( + project: Project, + target: String, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project) + ): Collection { + checkCommitIsNotInProgress(project) + return getElements(KEY, target, project, scope) + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/TactFunctionIndex.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactFunctionIndex.kt new file mode 100644 index 0000000..4a48c68 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactFunctionIndex.kt @@ -0,0 +1,30 @@ +package org.ton.intellij.tact.stub.index + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import org.ton.intellij.tact.psi.TactFunction +import org.ton.intellij.tact.stub.TactFileStub +import org.ton.intellij.util.checkCommitIsNotInProgress +import org.ton.intellij.util.getElements + +class TactFunctionIndex : StringStubIndexExtension() { + override fun getVersion(): Int = TactFileStub.Type.stubVersion + + override fun getKey(): StubIndexKey = KEY + + companion object { + val KEY = + StubIndexKey.createIndexKey("org.ton.intellij.tact.stub.index.TactFunctionIndex") + + fun findElementsByName( + project: Project, + target: String, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project) + ): Collection { + checkCommitIsNotInProgress(project) + return getElements(KEY, target, project, scope) + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/stub/index/TactTypesIndex.kt b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactTypesIndex.kt new file mode 100644 index 0000000..8657472 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/stub/index/TactTypesIndex.kt @@ -0,0 +1,30 @@ +package org.ton.intellij.tact.stub.index + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey +import org.ton.intellij.tact.psi.TactTypeDeclarationElement +import org.ton.intellij.tact.stub.TactFileStub +import org.ton.intellij.util.checkCommitIsNotInProgress +import org.ton.intellij.util.getElements + +class TactTypesIndex : StringStubIndexExtension() { + override fun getVersion(): Int = TactFileStub.Type.stubVersion + + override fun getKey(): StubIndexKey = KEY + + companion object { + val KEY = + StubIndexKey.createIndexKey("org.ton.intellij.tact.stub.index.TactTypesIndex") + + fun findElementsByName( + project: Project, + target: String, + scope: GlobalSearchScope = GlobalSearchScope.allScope(project) + ): Collection { + checkCommitIsNotInProgress(project) + return getElements(KEY, target, project, scope) + } + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactLookup.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactLookup.kt new file mode 100644 index 0000000..b74cd43 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactLookup.kt @@ -0,0 +1,13 @@ +package org.ton.intellij.tact.type + +import com.intellij.openapi.project.Project +import org.ton.intellij.tact.psi.TactElement + +class TactLookup( + private val project: Project, + context: TactElement? = null +) { + val ctx by lazy(LazyThreadSafetyMode.NONE) { + TactInferenceContext(project, this) + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt new file mode 100644 index 0000000..e056736 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTy.kt @@ -0,0 +1,95 @@ +package org.ton.intellij.tact.type + +import com.intellij.openapi.project.Project +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.stub.index.TactTypesIndex + +interface TactTy { + override fun toString(): String + + fun isAssignable(other: TactTy): Boolean + + companion object { + fun search(project: Project, name: String): List { + return TactTypesIndex.findElementsByName(project, name).map { + it.declaredTy + } + } + } +} + +val TactType.ty: TactTy? + get() { + val type = reference?.resolve() as? TactTypeDeclarationElement ?: return null + val declaredTy = type.declaredTy + if (this is TactReferencedType && this.q != null) { + return TactTyNullable(declaredTy) + } + return declaredTy + } + +sealed interface TactTyRuntime : TactTy + +data object TactTyUnknown : TactTy { + override fun toString(): String { + return "???" + } + + override fun isAssignable(other: TactTy): Boolean { + return false + } +} + +data class TactTyRef( + val item: TactTypeDeclarationElement +) : TactTy { + override fun toString(): String = item.name ?: item.toString() + + override fun isAssignable(other: TactTy): Boolean { + return other is TactTyRef && item == other.item + } +} + +data class TactTyNullable( + val inner: TactTy +) : TactTy { + override fun toString(): String = "$inner?" + + override fun isAssignable(other: TactTy): Boolean { + return (other is TactTyNullable && inner.isAssignable(other.inner)) || inner.isAssignable(other) + } +} + +data class TactTyMap( + val key: TactTy, + val value: TactTy +) : TactTy { + 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) + } +} + +data class TactBounced( + val inner: TactTy +) : TactTy, TactTyRuntime { + override fun toString(): String = "bounced<$inner>" + + override fun isAssignable(other: TactTy): Boolean { + return other is TactBounced && inner.isAssignable(other.inner) + } +} + +object TactTyVoid : TactTy { + override fun toString(): String = "" + override fun isAssignable(other: TactTy): Boolean = false +} + +object TactTyNull : TactTy, TactTyRuntime { + override fun toString(): String = "" + + override fun isAssignable(other: TactTy): Boolean = other == TactTyNull || other is TactTyNullable +} diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt new file mode 100644 index 0000000..699cfa9 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTyInference.kt @@ -0,0 +1,139 @@ +package org.ton.intellij.tact.type + +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementResolveResult +import com.intellij.psi.util.* +import com.intellij.util.containers.OrderedSet +import org.ton.intellij.tact.diagnostics.TactDiagnostic +import org.ton.intellij.tact.psi.* +import org.ton.intellij.tact.stub.index.TactConstantIndex +import org.ton.intellij.util.processAllKeys +import org.ton.intellij.util.recursionGuard +import java.util.* + +private val TACT_INFERENCE_KEY: Key> = Key.create("TACT_INFERENCE_KEY") + +val TactInferenceContextOwner.selfInferenceResult: TactInferenceResult + get() { + return CachedValuesManager.getCachedValue(this, TACT_INFERENCE_KEY) { + val inferred = inferTypesIn(this) + CachedValueProvider.Result.create(inferred, PsiModificationTracker.MODIFICATION_COUNT) + } + } + +fun inferTypesIn(element: TactInferenceContextOwner): TactInferenceResult { + val lookup = TactLookup(element.project, element) + return recursionGuard(element, { lookup.ctx.infer(element) }, memoize = false) + ?: error("Can not run nested type inference") +} + +interface TactInferenceData { + val diagnostics: List + + fun getExprTy(expr: TactExpression): TactTy? + + fun getResolvedRefs(element: PsiElement): OrderedSet +} + +data class TactInferenceResult( + val exprTypes: Map, + val resolvedRefs: Map>, + override val diagnostics: List = emptyList() +) : TactInferenceData { + val timestamp = System.nanoTime() + + override fun getExprTy(expr: TactExpression): TactTy = + exprTypes[expr] ?: TactTyUnknown + + override fun getResolvedRefs(element: PsiElement): OrderedSet { + return resolvedRefs[element] ?: EMPTY_RESOLVED_SET + } +} + + +private val EMPTY_RESOLVED_SET = OrderedSet() + +class TactInferenceContext( + val project: Project, + val lookup: TactLookup +) : TactInferenceData { + private val resolvedTypes = HashMap() + private val exprTypes = HashMap() + private val resolvedRefs = HashMap>() + override val diagnostics = ArrayList() + + override fun getExprTy(expr: TactExpression): TactTy? { + return exprTypes[expr] + } + + fun setExprTy(expr: TactExpression, ty: TactTy) { + exprTypes[expr] = ty + } + + fun isTypeInferred(expr: TactExpression): Boolean { + return exprTypes.containsKey(expr) + } + + override fun getResolvedRefs(element: PsiElement): OrderedSet { + return resolvedRefs[element] ?: EMPTY_RESOLVED_SET + } + + fun setResolvedRefs(element: PsiElement, refs: OrderedSet) { + resolvedRefs[element] = refs + } + + fun addDiagnostic(diagnostic: TactDiagnostic) { + if (diagnostic.element.containingFile.isPhysical) { + diagnostics.add(diagnostic) + } + } + + fun reportTypeMismatch(element: PsiElement, expected: TactTy, actual: TactTy) { + addDiagnostic(TactDiagnostic.TypeError(element, expected, actual)) + } + + fun infer(element: TactInferenceContextOwner): TactInferenceResult { + element.body?.let { block -> + val walker = TactTypeInferenceWalker(this, element.selfType ?: TactTyUnknown) + walker.walk(block) + } + return TactInferenceResult(exprTypes, resolvedRefs, diagnostics) + } + + public fun collectVariableCandidates(element: TactReferenceExpression): Collection { + val variableCandidates = LinkedList() + PsiTreeUtil.treeWalkUp(element, null) { scope, prevParent -> + when (scope) { + is TactBlock -> { + scope.statementList.forEach { stmt -> + if (stmt == prevParent) return@forEach + when (stmt) { + is TactLetStatement -> { + variableCandidates.add(stmt) + } + } + } + } + + is TactFunctionLike -> { + scope.functionParameters?.functionParameterList?.forEach { param -> + variableCandidates.add(param) + } + return@treeWalkUp false + } + } + true + } + processAllKeys(TactConstantIndex.KEY, element.project) { key -> + TactConstantIndex.findElementsByName(element.project, key).forEach { + if (it.parent is TactFile) { + variableCandidates.add(it) + } + } + true + } + return variableCandidates + } +} diff --git a/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt b/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt new file mode 100644 index 0000000..6b03cf3 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/tact/type/TactTypeInferenceWalker.kt @@ -0,0 +1,324 @@ +package org.ton.intellij.tact.type + +import com.intellij.openapi.progress.ProgressManager +import com.intellij.psi.PsiElementResolveResult +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.ty +import org.ton.intellij.tact.stub.index.TactFunctionIndex +import org.ton.intellij.tact.stub.index.TactTypesIndex +import org.ton.intellij.util.ancestorStrict +import org.ton.intellij.util.parentOfType + +class TactTypeInferenceWalker( + val ctx: TactInferenceContext, + private val returnTy: TactTy +) { + private val variables = HashMap>() + + fun walk(block: TactBlock) { + block.inferType() + } + + private fun addVariable(name: String, element: TactNamedElement, ty: TactTy) { + variables[name] = element to ty + } + + private fun TactBlock.inferType() { + val statements = statementList + for (statement in statements) { + statement.inferType() + } + } + + private fun TactStatement.inferType() { + when (this) { + is TactLetStatement -> { + val variableTy = type?.ty + val expressionTy = expression?.inferType() + if (variableTy != null && expressionTy != null && !expressionTy.isAssignable(variableTy)) { + ctx.reportTypeMismatch(this, variableTy, expressionTy) + } + val name = name + val definedVariable = variables[name] + if (name != null) { + if (definedVariable != null) { + ctx.addDiagnostic(TactDiagnostic.VariableAlreadyExists(this, definedVariable.first)) + } else if (variableTy != null) { + addVariable(name, this, variableTy) + } + } + } + + is TactAssignStatement -> { + val expressions = expressionList + val lValue = expressions.getOrNull(0) + val rValue = expressions.getOrNull(1) + + val lValueTy = lValue?.inferType() + val rValueTy = rValue?.inferType() + if (lValueTy != null && rValueTy != null && !lValueTy.isAssignable(rValueTy)) { + ctx.reportTypeMismatch(this, lValueTy, rValueTy) + } + } + + is TactExpressionStatement -> expression.inferType() + is TactConditionStatement -> { + val conditionTy = condition?.expression?.inferType() + if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { + ctx.reportTypeMismatch(this, object : TactTy { + override fun toString(): String = "Bool" + override fun isAssignable(other: TactTy): Boolean = false + }, conditionTy) + } + block?.inferType() + elseBranch?.conditionStatement?.inferType() + elseBranch?.block?.inferType() + } + + is TactReturnStatement -> expression?.inferType() + is TactWhileStatement -> { + val conditionTy = condition?.expression?.inferType() + if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { + ctx.reportTypeMismatch(this, object : TactTy { + override fun toString(): String = "Bool" + override fun isAssignable(other: TactTy): Boolean = false + }, conditionTy) + } + block?.inferType() + } + + is TactUntilStatement -> { + block?.inferType() + val conditionTy = condition?.expression?.inferType() + if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { + ctx.reportTypeMismatch(this, object : TactTy { + override fun toString(): String = "Bool" + override fun isAssignable(other: TactTy): Boolean = false + }, conditionTy) + } + } + + is TactRepeatStatement -> { + val conditionTy = condition?.expression?.inferType() + if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Int")) { + ctx.reportTypeMismatch(this, object : TactTy { + override fun toString(): String = "Int" + override fun isAssignable(other: TactTy): Boolean = false + }, conditionTy) + } + block?.inferType() + } + } + } + + private fun TactExpression.inferType(): TactTy? { + ProgressManager.checkCanceled() + var ty = ctx.getExprTy(this) + if (ty != null) { + return ty + } + ty = when (this) { + is TactTernaryExpression -> inferType() + is TactBinExpression -> inferType() + is TactParenExpression -> inferType() + is TactDotExpression -> inferType() + is TactReferenceExpression -> inferType() + is TactCallExpression -> inferType() + is TactInitOfExpression -> inferType() + is TactStructExpression -> inferType() + is TactSelfExpression -> inferType() + is TactUnaryExpression -> inferType() + is TactNotNullExpression -> inferType() + is TactStringExpression -> inferType() + is TactIntegerExpression -> inferType() + is TactBooleanExpression -> inferType() + else -> null + } + if (ty != null) { + ctx.setExprTy(this, ty) + } + return ty + } + + private fun TactTernaryExpression.inferType(): TactTy? { + val conditionTy = condition.inferType() + val thenTy = thenBranch?.inferType() + val elseTy = elseBranch?.inferType() + if (conditionTy != null && (conditionTy !is TactTyRef || conditionTy.item.name != "Bool")) { + ctx.reportTypeMismatch(this, object : TactTy { + override fun toString(): String = "Bool" + override fun isAssignable(other: TactTy): Boolean = false + }, conditionTy) + } + if (thenTy != null && elseTy != null) { + if (thenTy.isAssignable(elseTy)) { + return thenTy + } else if (elseTy.isAssignable(thenTy)) { + return elseTy + } else { + ctx.reportTypeMismatch(this, thenTy, elseTy) + } + } + return null + } + + private fun TactBinExpression.inferType(): TactTy? { + return expressionList.map { + it.inferType() + }.firstOrNull() + } + + private fun TactSelfExpression.inferType(): TactTy? = getParentFunction()?.selfType + private fun TactParenExpression.inferType(): TactTy? = expression?.inferType() + private fun TactUnaryExpression.inferType(): TactTy? = expression?.inferType() + + private fun TactInitOfExpression.inferType(): TactTy? { + expressionList.forEach { + it.inferType() + } + val stateInit = TactTypesIndex.findElementsByName(project, "StateInit") + .filterIsInstance() + .firstOrNull { + it.containingFile.name == "contract.tact" + } + return stateInit?.declaredTy + } + + private fun TactDotExpression.inferType(): TactTy? { + val expressions = expressionList + val left = expressions.getOrNull(0) + val right = expressions.getOrNull(1) + + val leftType = left?.inferType() + + when (right) { + is TactFieldExpression -> { + if (leftType is TactTyRef) { + val name = right.identifier.text + val members = leftType.item.members + val resolvedField = members.find { it.name == name } + if (resolvedField != null) { + ctx.setResolvedRefs(right, OrderedSet(listOf(PsiElementResolveResult(resolvedField)))) + return when (resolvedField) { + is TactField -> resolvedField.type?.ty + is TactConstant -> resolvedField.type?.ty + else -> null + } + } + } + } + + is TactCallExpression -> { + var returnTy: TactTy? = null + if (leftType is TactTyRef) { + val name = right.identifier.text + + val function = TactFunctionIndex.findElementsByName(project, name).find { + it.selfType?.isAssignable(leftType) == true + } + + 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 + } + } + } + right.expressionList.forEach { + it.inferType() + } + return returnTy + } + } + + return null + } + + private fun TactReferenceExpression.inferType(): TactTy? { + val referenceName = identifier.text + val candidates = ctx.collectVariableCandidates(this).filter { it.name == referenceName } + if (candidates.isNotEmpty()) { + ctx.setResolvedRefs(this, OrderedSet(listOf(PsiElementResolveResult(candidates.first())))) + } + return when (val candidate = candidates.firstOrNull()) { + is TactFunctionParameter -> candidate.type?.ty + is TactLetStatement -> candidate.type?.ty + else -> null + } + } + + private fun TactCallExpression.inferType(): TactTy? { + expressionList.forEach { it.inferType() } + + val name = identifier.text + var candidates = TactFunctionIndex.findElementsByName(project, name) + if (candidates.isNotEmpty()) { + if (candidates.size > 1) { + val newCandidates = candidates.filter { !it.isGet } + if (newCandidates.isNotEmpty()) { + candidates = newCandidates + } + } + + ctx.setResolvedRefs(this, OrderedSet(candidates.map { PsiElementResolveResult(it) })) + } + + var ty: TactTy? = null + for (candidate in candidates) { + ty = candidate.type?.ty + if (ty != null) { + break + } + } + + return ty + } + + private fun TactStructExpression.inferType(): TactTy? { + val type = (reference?.resolve() as? TactTypeDeclarationElement)?.declaredTy + structExpressionFieldList.forEach { field -> + field.expression?.inferType() + } + return type + } + + private fun TactNotNullExpression.inferType(): TactTy? { + val inferType = expression.inferType() + return if (inferType is TactTyNullable) { + inferType.inner + } else { + inferType + } + } + + private fun TactStringExpression.inferType(): TactTy? { + return TactTyRef(TactTypesIndex.findElementsByName(project, "String").firstOrNull() ?: return null) + } + + private fun TactIntegerExpression.inferType(): TactTy? { + return TactTyRef(TactTypesIndex.findElementsByName(project, "Int").firstOrNull() ?: return null) + } + + private fun TactBooleanExpression.inferType(): TactTy? { + return TactTyRef(TactTypesIndex.findElementsByName(project, "Bool").firstOrNull() ?: return null) + } +} + +fun TactElement.getParentFunction(): TactInferenceContextOwner? { + return ancestorStrict() +} + +val TactInferenceContextOwner.selfType: TactTy? + get() = parentOfType()?.declaredTy ?: if (this is TactFunction) { + functionParameters?.selfParameter?.let { + it.type?.ty + } + } else null diff --git a/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt b/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt index 2b5d5b6..dcabc08 100644 --- a/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt +++ b/src/main/kotlin/org/ton/intellij/tlb/TlbLanguage.kt @@ -3,4 +3,8 @@ package org.ton.intellij.tlb import com.intellij.lang.InjectableLanguage import com.intellij.lang.Language -object TlbLanguage : Language("tlb", "text/tlb", "text/tl-b"), InjectableLanguage +object TlbLanguage : + Language("tlb", "text/tlb", "text/x-tlb", "text/tl-b", "text/x-tl-b", "application/x-tlb", "application/x-tl-b"), + InjectableLanguage { + override fun isCaseSensitive(): Boolean = false +} diff --git a/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt b/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt index 6312835..e9d5f9f 100644 --- a/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt +++ b/src/main/kotlin/org/ton/intellij/util/IntellijUtils.kt @@ -22,6 +22,7 @@ import com.intellij.psi.stubs.StubIndexKey import com.intellij.psi.tree.IElementType import com.intellij.psi.tree.TokenSet import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.ArrayFactory fun tokenSetOf(vararg tokens: IElementType) = TokenSet.create(*tokens) @@ -116,3 +117,12 @@ val PsiElement.prevVisibleOrNewLine: PsiElement? .filterNot { it is PsiComment || it is PsiErrorElement } .filter { it !is PsiWhiteSpace || it.textContains('\n') } .firstOrNull() + + +public fun getChildrenByType( + stub: StubElement, + elementType: IElementType, + f: ArrayFactory, +): List { + return stub.getChildrenByType(elementType, f).toList() as List +} diff --git a/src/main/kotlin/org/ton/intellij/util/PreparedAnnotation.kt b/src/main/kotlin/org/ton/intellij/util/PreparedAnnotation.kt new file mode 100644 index 0000000..7c8c0c6 --- /dev/null +++ b/src/main/kotlin/org/ton/intellij/util/PreparedAnnotation.kt @@ -0,0 +1,29 @@ +package org.ton.intellij.util + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.util.InspectionMessage +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.openapi.util.NlsContexts.Tooltip +import com.intellij.openapi.util.TextRange +import com.intellij.xml.util.XmlStringUtil.escapeString + +class PreparedAnnotation( + val severity: ProblemHighlightType, + @Suppress("UnstableApiUsage") @InspectionMessage val header: String, + @Suppress("UnstableApiUsage") @Tooltip val description: String = "", + val fixes: List = emptyList(), + val textAttributes: TextAttributesKey? = null, +) { + val fullDescription: String get() = "${escapeString(header)}
${escapeString(description)}" +} + +data class QuickFixWithRange( + val fix: LocalQuickFix, + val availabilityRange: TextRange?, +) + +private fun listOfFixes(vararg fixes: LocalQuickFix?): List = + fixes.mapNotNull { if (it == null) null else QuickFixWithRange(it, null) } + +private fun List.toQuickFixInfo(): List = map { QuickFixWithRange(it, null) } diff --git a/src/main/resources/META-INF/func.xml b/src/main/resources/META-INF/func.xml index 43bc86d..7c002e5 100644 --- a/src/main/resources/META-INF/func.xml +++ b/src/main/resources/META-INF/func.xml @@ -109,6 +109,10 @@ + + + @@ -116,5 +120,14 @@ text="FunC File" description="Create new FunC file"> + + + + + + + + diff --git a/src/main/resources/META-INF/tact.xml b/src/main/resources/META-INF/tact.xml index 49c994d..d5274fa 100644 --- a/src/main/resources/META-INF/tact.xml +++ b/src/main/resources/META-INF/tact.xml @@ -1,29 +1,56 @@ + messages.TactBundle + - - - - - - + - - + + + + + - + + + + + + + + + + + diff --git a/src/main/resources/META-INF/tlb.xml b/src/main/resources/META-INF/tlb.xml index 600d3bf..b39c866 100644 --- a/src/main/resources/META-INF/tlb.xml +++ b/src/main/resources/META-INF/tlb.xml @@ -20,7 +20,7 @@ implementationClass="org.ton.intellij.tlb.ide.TlbFindUsagesProvider"/> - + diff --git a/src/main/resources/icons/blueprint.svg b/src/main/resources/icons/blueprint.svg index a9ed8e8..fdce331 100644 --- a/src/main/resources/icons/blueprint.svg +++ b/src/main/resources/icons/blueprint.svg @@ -1,30 +1,26 @@ - - + + + + - - - + + + - - - + + + - - - + + + - - - - - diff --git a/src/main/resources/icons/tact.svg b/src/main/resources/icons/tact.svg index 09e6323..7187692 100644 --- a/src/main/resources/icons/tact.svg +++ b/src/main/resources/icons/tact.svg @@ -1,19 +1,19 @@ - - - + + + - - - + + - - + + - - - + + + diff --git a/src/main/resources/icons/tact_dark.svg b/src/main/resources/icons/tact_dark.svg index 4394936..a805381 100644 --- a/src/main/resources/icons/tact_dark.svg +++ b/src/main/resources/icons/tact_dark.svg @@ -1,19 +1,19 @@ - - - + + + - - - + + - - + + - - - + + + diff --git a/src/main/resources/icons/ton_symbol.svg b/src/main/resources/icons/ton_symbol.svg new file mode 100644 index 0000000..18fff33 --- /dev/null +++ b/src/main/resources/icons/ton_symbol.svg @@ -0,0 +1,6 @@ + + + + diff --git a/src/main/resources/inspectionDescriptions/TactUnresolvedReference.html b/src/main/resources/inspectionDescriptions/TactUnresolvedReference.html new file mode 100644 index 0000000..b5c1ff1 --- /dev/null +++ b/src/main/resources/inspectionDescriptions/TactUnresolvedReference.html @@ -0,0 +1,5 @@ + + +Reports unresolved references. + + diff --git a/src/main/resources/messages/TactBundle.properties b/src/main/resources/messages/TactBundle.properties new file mode 100644 index 0000000..dfac6e8 --- /dev/null +++ b/src/main/resources/messages/TactBundle.properties @@ -0,0 +1,4 @@ +tact=Tact +inspection.tact.type.check.display.name=Type checker +inspection.tact.unresolved.reference.display.name=Unresolved reference +inspection.message.unresolved.reference=Unresolved reference: `{0}`