diff --git a/build.gradle b/build.gradle index c1a890e..05cc6a1 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,11 @@ repositories { mavenCentral() } +dependencies { + testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.0" + testImplementation "org.junit.jupiter:junit-jupiter-engine:5.7.0" +} + // Add the generated source files into the build sourceSets.main.java.srcDirs 'src/main/gen' idea.module.generatedSourceDirs.add(file('src/main/gen')) @@ -67,4 +72,10 @@ patchPluginXml { 0.1.0
Initial release of the LALRPOP plugin """ +} + +test { + useJUnitPlatform() + + maxHeapSize = "1G" } \ No newline at end of file diff --git a/src/main/grammars/LalrpopParser.bnf b/src/main/grammars/LalrpopParser.bnf index 3c06613..9442527 100644 --- a/src/main/grammars/LalrpopParser.bnf +++ b/src/main/grammars/LalrpopParser.bnf @@ -11,6 +11,8 @@ elementTypeHolderClass="com.mdrobnak.lalrpop.psi.LpElementTypes" elementTypeClass="com.mdrobnak.lalrpop.psi.LpElementType" tokenTypeClass="com.mdrobnak.lalrpop.psi.LpTokenType" + + generateTokenAccessors=true } grammar ::= SHEBANG_ATTRIBUTE* use_stmt* @@ -99,7 +101,7 @@ alternative ::= annotation* symbol+ (IF cond)? action? } action ::= action_type CODE { - implements = "com.intellij.psi.PsiLanguageInjectionHost" + implements = ["com.intellij.psi.PsiLanguageInjectionHost" "com.mdrobnak.lalrpop.psi.LpResolveType"] mixin = "com.mdrobnak.lalrpop.psi.ext.LpActionMixin" } @@ -168,7 +170,7 @@ type_of_symbol ::= POUND symbol POUND { implements = "com.mdrobnak.lalrpop.psi.LpResolveType" mixin = "com.mdrobnak.lalrpop.psi.ext.LpTypeOfSymbolMixin" } -rust_reference ::= AND lifetime_rule? MUT? type_ref { +rust_reference ::= AND LIFETIME? MUT? type_ref { implements = "com.mdrobnak.lalrpop.psi.LpResolveType" mixin = "com.mdrobnak.lalrpop.psi.ext.LpRustReferenceMixin" } @@ -188,9 +190,7 @@ return_type ::= RSINGLEARROW type_ref type_generic_arguments ::= LESSTHAN <> GREATERTHAN -private type_ref_or_lifetime ::= type_ref | lifetime_rule - -lifetime_rule ::= LIFETIME +type_ref_or_lifetime ::= type_ref | LIFETIME extern_token ::= EXTERN LBRACE associated_type* enum_token associated_type* RBRACE | EXTERN LBRACE associated_type* RBRACE { @@ -215,9 +215,7 @@ enum_token ::= ENUM type_ref LBRACE <> RBRACE { mixin = "com.mdrobnak.lalrpop.psi.ext.LpEnumTokenMixin" } -associated_type ::= TYPE associated_type_name EQUALS type_ref SEMICOLON - -associated_type_name ::= ID +associated_type ::= TYPE ID EQUALS type_ref SEMICOLON conversion ::= terminal USER_ACTION CODE diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/injectors/LpRustActionCodeInjector.kt b/src/main/kotlin/com/mdrobnak/lalrpop/injectors/LpRustActionCodeInjector.kt index 83e6e1c..34e4cae 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/injectors/LpRustActionCodeInjector.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/injectors/LpRustActionCodeInjector.kt @@ -17,55 +17,18 @@ import org.rust.lang.RsLanguage */ class LpRustActionCodeInjector : MultiHostInjector { override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) { - if (!context.isValid || context !is LpActionImpl) { + if (!context.isValid || context !is LpAction) { return } - val codeNode = context.code ?: return + val codeNode = context.code val imports = context.containingFile.importCode - val nonterminal = context.parentOfType()!! - val alternative = context.parentOfType()!! - val typeResolutionContext = context.containingFile.lalrpopTypeResolutionContext() - - val inputs = alternative.selected.map { (it as LpSymbolImpl).getSelectedType(typeResolutionContext) } - val returnType = context.actionType.returnType(nonterminal.resolveType(typeResolutionContext, listOf()), typeResolutionContext) - - val grammarDecl = PsiTreeUtil.findChildOfType(context.containingFile, LpGrammarDecl::class.java) - - val grammarParams = grammarDecl?.grammarParams - val grammarParametersString = - grammarParams?.grammarParamList?.joinToString(separator = "") { "${it.name}: ${it.typeRef.text}," } - ?: "" - - val grammarTypeParams = grammarDecl?.grammarTypeParams - val genericParameters = nonterminal.nonterminalName.nonterminalParams?.nonterminalParamList - - fun addNullableIterators(a: List?, b: List?): List = (a ?: listOf()) + (b ?: listOf()) - fun List.join(): String = - if (this.isEmpty()) "" else this.joinToString(prefix = "<", postfix = ">", separator = ", ") { it } - - val grammarTypeParamsString = addNullableIterators( - grammarTypeParams?.typeParamList?.map { it.text }, - genericParameters?.map { it.text } - ).join() - - val arguments = inputs.mapIndexed { index, it -> - when (it) { - is LpSelectedType.WithName -> (if (it.isMutable) "mut " else "") + it.name + ": " + it.type - is LpSelectedType.WithoutName -> "__intellij_lalrpop_noname_$index: " + it.type - } - }.joinToString(", ") - - val grammarWhereClauses = grammarDecl?.grammarWhereClauses - val grammarWhereClausesString = - grammarWhereClauses?.grammarWhereClauseList?.joinToString(prefix = "where ", separator = ", ") { it.text } - ?: "" + val rustFunctionHeader = context.actionCodeFunctionHeader(true) val prefix = "mod __intellij_lalrpop {\n" + "$imports\n" + - "fn __intellij_lalrpop $grammarTypeParamsString ($grammarParametersString $arguments) -> $returnType" + - " $grammarWhereClausesString {\n" + "$rustFunctionHeader {\n" val suffix = "\n}\n}" @@ -78,5 +41,5 @@ class LpRustActionCodeInjector : MultiHostInjector { } override fun elementsToInjectIn(): List> = - listOf(LpActionImpl::class.java) + listOf(LpAction::class.java) } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/inspections/CannotInferNonterminalTypeInspection.kt b/src/main/kotlin/com/mdrobnak/lalrpop/inspections/CannotInferNonterminalTypeInspection.kt index e53089b..1cc60e4 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/inspections/CannotInferNonterminalTypeInspection.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/inspections/CannotInferNonterminalTypeInspection.kt @@ -1,10 +1,16 @@ package com.mdrobnak.lalrpop.inspections import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElementVisitor +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.LpNonterminal import com.mdrobnak.lalrpop.psi.LpVisitor +import com.mdrobnak.lalrpop.psi.ext.setType +import com.mdrobnak.lalrpop.psi.getContextAndResolveType object CannotInferNonterminalTypeInspection : LocalInspectionTool() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { @@ -17,9 +23,20 @@ object CannotInferNonterminalTypeInspection : LocalInspectionTool() { holder.registerProblem( nonterminal, "Cannot infer type of nonterminal", - ) // TODO Quickfix: get from the rust plugin if available. + InferFromRustPluginQuickFix, + ) } } } } -} \ No newline at end of file +} + +object InferFromRustPluginQuickFix : LocalQuickFix { + override fun getFamilyName(): String = "Get from action code" + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + (descriptor.psiElement as LpNonterminal).apply { + setType(getContextAndResolveType(LpMacroArguments.identity(nonterminalName.nonterminalParams))) + } + } +} diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/inspections/WrongInferredTypeInspection.kt b/src/main/kotlin/com/mdrobnak/lalrpop/inspections/WrongInferredTypeInspection.kt index 3b73040..c1e5586 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/inspections/WrongInferredTypeInspection.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/inspections/WrongInferredTypeInspection.kt @@ -7,18 +7,17 @@ import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileFactory import com.intellij.psi.util.PsiTreeUtil -import com.jetbrains.rd.util.string.printToString import com.mdrobnak.lalrpop.injectors.findModuleDefinition +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.LpNonterminal import com.mdrobnak.lalrpop.psi.LpVisitor import com.mdrobnak.lalrpop.psi.ext.importCode +import com.mdrobnak.lalrpop.psi.ext.rustGenericUnitStructs import com.mdrobnak.lalrpop.psi.util.lalrpopTypeResolutionContext import org.rust.lang.RsLanguage import org.rust.lang.core.macros.RsExpandedElement import org.rust.lang.core.macros.setContext -import org.rust.lang.core.psi.RsModDeclItem import org.rust.lang.core.psi.RsTypeAlias -import org.rust.lang.core.psi.ext.childOfType import org.rust.lang.core.psi.ext.childrenOfType import org.rust.lang.core.resolve.ImplLookup @@ -34,13 +33,9 @@ object WrongInferredTypeInspection : LocalInspectionTool() { ): PsiElementVisitor { return object : LpVisitor() { override fun visitNonterminal(nonterminal: LpNonterminal) { - // TODO: idea to implement this inspection for lalrpop macros: - // https://github.com/Mcat12/intellij-lalrpop/pull/22#discussion_r542538737 - if (nonterminal.nonterminalName.nonterminalParams != null) return - val context = nonterminal.containingFile.lalrpopTypeResolutionContext() - val explicitType = nonterminal.typeRef?.resolveType(context, listOf()) + val explicitType = nonterminal.typeRef?.resolveType(context, LpMacroArguments()) // LALRPOP will automatically supply action code of (), so we don't need to worry about inferring the wrong type. // https://github.com/lalrpop/lalrpop/blob/8a96e9646b3d00c2226349efed832c4c25631c53/lalrpop/src/normalize/lower/mod.rs#L351-L354 @@ -48,8 +43,10 @@ object WrongInferredTypeInspection : LocalInspectionTool() { var seenType = explicitType + val unitStructsGenerics by lazy { nonterminal.rustGenericUnitStructs() } + nonterminal.alternatives.alternativeList.filter { it.action == null }.forEach { alternative -> - val alternativeType = alternative.resolveType(context, listOf()) + val alternativeType = alternative.resolveType(context, LpMacroArguments()) if (seenType == null) { seenType = alternativeType @@ -60,6 +57,7 @@ object WrongInferredTypeInspection : LocalInspectionTool() { nonterminal.project, nonterminal.containingFile, nonterminal.containingFile.importCode, + unitStructsGenerics, seenType!!, alternativeType ) @@ -74,9 +72,24 @@ object WrongInferredTypeInspection : LocalInspectionTool() { } } - fun sameTypes(project: Project, lalrpopFile: PsiFile, importCode: String, type1: String, type2: String): Boolean { + fun sameTypes( + project: Project, + lalrpopFile: PsiFile, + importCode: String, + unitStructsGenerics: String, + type1: String, + type2: String + ): Boolean { val file = PsiFileFactory.getInstance(project) - .createFileFromText(RsLanguage, "mod __intellij_lalrpop {\n$importCode\ntype T1 = $type1;\ntype T2 = $type2;\n}") + .createFileFromText( + RsLanguage, + "mod __intellij_lalrpop {\n" + + " $importCode\n" + + " $unitStructsGenerics\n" + + " type T1 = $type1;\n" + + " type T2 = $type2;\n" + + "}" + ) val aliases = PsiTreeUtil.findChildrenOfType(file, RsTypeAlias::class.java) if (aliases.size != 2) return false diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/intentions/AddExplicitTypeIntention.kt b/src/main/kotlin/com/mdrobnak/lalrpop/intentions/AddExplicitTypeIntention.kt new file mode 100644 index 0000000..885fa4c --- /dev/null +++ b/src/main/kotlin/com/mdrobnak/lalrpop/intentions/AddExplicitTypeIntention.kt @@ -0,0 +1,34 @@ +package com.mdrobnak.lalrpop.intentions + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.intellij.psi.util.parentOfType +import com.mdrobnak.lalrpop.LpLanguage +import com.mdrobnak.lalrpop.psi.LpMacroArguments +import com.mdrobnak.lalrpop.psi.LpNonterminal +import com.mdrobnak.lalrpop.psi.ext.setType +import com.mdrobnak.lalrpop.psi.getContextAndResolveType + +class AddExplicitTypeIntention : IntentionAction { + override fun startInWriteAction(): Boolean = true + + override fun getText(): String = "Add explicit type" + + override fun getFamilyName(): String = "Add explicit type" + + override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean { + if (editor == null || file == null || file.language != LpLanguage) return false + + return file.findElementAt(editor.caretModel.primaryCaret.offset)?.parentOfType(withSelf = true) != null + } + + override fun invoke(project: Project, editor: Editor?, file: PsiFile?) { + if (editor == null || file == null || file.language != LpLanguage) return + + file.findElementAt(editor.caretModel.primaryCaret.offset)?.parentOfType(withSelf = true)?.apply { + setType(getContextAndResolveType(LpMacroArguments.identity(nonterminalName.nonterminalParams))) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpElementFactory.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpElementFactory.kt index aeda401..2bca55f 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpElementFactory.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpElementFactory.kt @@ -43,4 +43,19 @@ class LpElementFactory(val project: Project) { fun createWhitespace(s: String): PsiWhiteSpace = createFromText("${s}grammar;") ?: error("Failed to create whitespace '$s'") + + /** + * Returns a pair where the first element corresponds to the ':' and the second to the type_ref. + * Used with `nonterminal.addRangeAfter(pair.first, pair.second, nonterminal.name)` to add the type to a nonterminal + * where a type doesn't exist already. + */ + fun createNonterminalType(type: String): Pair { + val nonterminal = createFromText("grammar;\n Nonterminal: $type = {};") ?: error("Failed to create nonterminal, type = $type") + + val typeRef = nonterminal.typeRef!! + val whitespace = typeRef.prevSibling + val colon = whitespace.prevSibling + + return colon to typeRef + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpResolveType.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpResolveType.kt index 38c8f70..bf5d8b0 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpResolveType.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpResolveType.kt @@ -1,8 +1,46 @@ package com.mdrobnak.lalrpop.psi +import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.mdrobnak.lalrpop.psi.util.lalrpopTypeResolutionContext +import org.rust.lang.core.macros.setContext +import org.rust.lang.core.psi.RsPsiFactory +import org.rust.lang.core.psi.RsTypeParameterList +import org.rust.lang.core.psi.ext.RsElement +import org.rust.lang.core.types.Substitution +import org.rust.lang.core.types.infer.RsInferenceContext +import org.rust.lang.core.types.toTypeSubst +import org.rust.lang.core.types.ty.TyTypeParameter +import org.rust.lang.core.types.ty.TyUnit +import org.rust.lang.core.types.type -data class NonterminalGenericArgument(val rustType: String, var name: String) +data class LpMacroArgument(val rustType: String, val name: String) +data class LpMacroArguments(val arguments: List = listOf()) : List by arguments { + fun getSubstitution( + params: RsTypeParameterList?, + project: Project, + inferenceContext: RsInferenceContext, + expandedElementContext: RsElement + ): Substitution = + params?.typeParameterList?.map { param -> + TyTypeParameter.named(param) to (arguments.find { arg -> arg.name == param.identifier.text }?.rustType?.let { + RsPsiFactory(project).createType(it).let { typeRef -> + typeRef.setContext(expandedElementContext) + + inferenceContext.fullyResolve(typeRef.type) + } + } ?: TyUnit) + }.orEmpty().toMap().toTypeSubst() + + companion object { + fun identity(params: LpNonterminalParams?): LpMacroArguments = + LpMacroArguments(params?.nonterminalParamList?.map { + val name = it.name!! + LpMacroArgument(name, name) + }.orEmpty()) + } +} data class LpTypeResolutionContext( val locationType: String = "usize", @@ -17,16 +55,23 @@ interface LpResolveType : PsiElement { /** * Returns a string of the rust type the node that implements this would resolve to. * + * @param context the type resolution context of this file * @param arguments the list of arguments, only used in the case of a nonterminal ref; for example in * Nonterminal: SomeType = { * Rule1 Rule2 => SomeType::new(<>), * } * * And referenced with Nonterminal in another symbol, the list of arguments should be the resolved types of - * "A" and "B", in this order. + * "A" and "B", not necessarily in this order. + * + * @see LpMacroArguments.identity + * + * @see PsiFile.lalrpopTypeResolutionContext + * @see LpResolveType.getContextAndResolveType */ - fun resolveType(context: LpTypeResolutionContext, arguments: List): String + fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String -// fun resolveType(arguments: List): String = -// this.resolveType(this.containingFile.lalrpopTypeResolutionContext(), arguments) } + +fun LpResolveType.getContextAndResolveType(arguments: LpMacroArguments): String = + this.resolveType(this.containingFile.lalrpopTypeResolutionContext(), arguments) diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpSelectedType.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpSelectedType.kt index 18063e8..848685a 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpSelectedType.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/LpSelectedType.kt @@ -1,6 +1,6 @@ package com.mdrobnak.lalrpop.psi sealed class LpSelectedType { - data class WithName(val isMutable: Boolean, val name: String, val type: String): LpSelectedType() + data class WithName(val name: String, val type: String, val isMutable: Boolean = false): LpSelectedType() data class WithoutName(val type: String): LpSelectedType() } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAction.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAction.kt index 1694f41..e54b95d 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAction.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAction.kt @@ -3,26 +3,89 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.LiteralTextEscaper -import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFileFactory import com.intellij.psi.PsiLanguageInjectionHost import com.intellij.psi.impl.source.tree.LeafElement -import com.intellij.psi.util.elementType -import com.mdrobnak.lalrpop.psi.LpAction -import com.mdrobnak.lalrpop.psi.LpAlternative -import com.mdrobnak.lalrpop.psi.LpElementTypes +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.parentOfType +import com.mdrobnak.lalrpop.injectors.findModuleDefinition +import com.mdrobnak.lalrpop.psi.* import com.mdrobnak.lalrpop.psi.util.lalrpopTypeResolutionContext +import org.rust.ide.presentation.renderInsertionSafe +import org.rust.lang.RsLanguage +import org.rust.lang.core.macros.RsExpandedElement +import org.rust.lang.core.macros.setContext +import org.rust.lang.core.psi.RsFunction +import org.rust.lang.core.psi.ext.RsElement +import org.rust.lang.core.psi.ext.block +import org.rust.lang.core.psi.ext.childrenOfType +import org.rust.lang.core.resolve.ImplLookup +import org.rust.lang.core.types.infer.substitute val LpAction.alternativeParent: LpAlternative - get() = this.parent as LpAlternative + get() = this.parentOfType()!! -abstract class LpActionMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpAction { - val code: PsiElement? - get() = if (lastChild?.elementType == LpElementTypes.CODE) { - lastChild - } else { - null +val Int.lalrpopNoNameParameterByIndex + get() = "__intellij_lalrpop_noname_$this" + +/** + * The action code function header / definition (fn __intellij_lalrpop (params) where where_clauses), + * without the opening brace. + * + * @param withReturnType add the return type? You usually want this to be true, but during type resolution of a nonterminal + * like A = B => do_something(<>);, to use the return type we should know it first, but to know it we must + * infer it with the rust plugin, but to infer it we need the header, and this would lead to infinite indirect recursion. + */ +fun LpAction.actionCodeFunctionHeader(withReturnType: Boolean = true): String { + val alternative = parentOfType()!! + val nonterminal = parentOfType()!! + + val typeResolutionContext = containingFile.lalrpopTypeResolutionContext() + + val inputs = alternative.selectedTypesInContext(typeResolutionContext) + + val arrowReturnType = + if (withReturnType) + " -> " + actionType.returnType( + nonterminal.resolveType( + typeResolutionContext, + LpMacroArguments.identity(nonterminal.nonterminalName.nonterminalParams) + ), + typeResolutionContext + ) + else "" + + val grammarDecl = this.containingFile.lalrpopFindGrammarDecl() + + val grammarParams = grammarDecl.grammarParams + val grammarParametersString = + grammarParams?.grammarParamList?.joinToString(separator = "") { "${it.name}: ${it.typeRef.text}," } + ?: "" + + val grammarTypeParams = grammarDecl.grammarTypeParams + val genericParameters = nonterminal.nonterminalName.nonterminalParams?.nonterminalParamList + + val genericParamsString = + (grammarTypeParams?.typeParamList?.map { it.text }.orEmpty() + genericParameters?.map { it.text }.orEmpty()) + .let { if (it.isEmpty()) "" else it.joinToString(prefix = "<", postfix = ">", separator = ", ") } + + val arguments = inputs.mapIndexed { index, it -> + when (it) { + is LpSelectedType.WithName -> (if (it.isMutable) "mut " else "") + it.name + ": " + it.type + is LpSelectedType.WithoutName -> "${index.lalrpopNoNameParameterByIndex}: " + it.type } + }.joinToString(", ") + + val grammarWhereClauses = grammarDecl.grammarWhereClauses + val grammarWhereClausesString = + grammarWhereClauses?.grammarWhereClauseList?.joinToString(prefix = "where ", separator = ", ") { it.text } + ?: "" + return "fn __intellij_lalrpop $genericParamsString ($grammarParametersString $arguments) $arrowReturnType\n" + + "$grammarWhereClausesString\n" +} + +abstract class LpActionMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpAction { override fun isValidHost(): Boolean = true override fun updateText(text: String): PsiLanguageInjectionHost { @@ -36,6 +99,51 @@ abstract class LpActionMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpActi val context = this.containingFile.lalrpopTypeResolutionContext() return LpActionLiteralTextEscaper( this, - this.alternativeParent.selected.map { (it as LpSymbolMixin).getSelectedType(context) }) + this.alternativeParent.selectedTypesInContext( + context, + resolveTypes = false // no need to know the types of the selected symbols for expanding `<>`s + ) + ) + } + + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { + val importCode = this.containingFile.importCode + + val code = actionCodeEscape(code.text, this.alternativeParent.selectedTypesInContext(context)) + + val fnCode = actionCodeFunctionHeader(false) + + "{\n" + + " $code\n" + + "}\n" + val genericUnitStructs = this.alternativeParent.nonterminalParent.rustGenericUnitStructs() + + val fileText = "mod __intellij_lalrpop {\n$importCode\n $genericUnitStructs\n $fnCode \n}" + val file = PsiFileFactory.getInstance(project) + .createFileFromText(RsLanguage, fileText) + + val fn = PsiTreeUtil.findChildOfType(file, RsFunction::class.java) ?: return "()" + val block = fn.block ?: return "()" + val expr = block.expr ?: return "()" + + val moduleDefinition = findModuleDefinition(project, this.containingFile) ?: return "()" + + for (child in file.childrenOfType()) { + child.setContext(moduleDefinition) + } + + val ctx = ImplLookup.relativeTo(fn).ctx + + val inferenceResult = ctx.infer(fn) + val inferredGenericType = inferenceResult.getExprType(expr) + + val maybeConcreteType = inferredGenericType.substitute( + arguments.getSubstitution( + fn.typeParameterList, project, ctx, + fn.parent as RsElement + ) + ) + + return actionType.nonterminalTypeFromReturn(maybeConcreteType) + .renderInsertionSafe(fn, includeTypeArguments = true, includeLifetimeArguments = true) } } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaper.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaper.kt index a1d8990..30a32d1 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaper.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaper.kt @@ -15,6 +15,31 @@ import java.util.* */ private data class Mapping(val sourceRange: TextRange, val targetRange: TextRange, val context: Context) +private fun actionCodeEscapeWithMappings(actionCode: String, evalOfAngleBracketsExpression: List): Pair, String> { + // get all the text ranges with <> from rust + val mappings = actionCode.findAllMappings("<>", evalOfAngleBracketsExpression) + + // replace them all with their replacements, in reverse order + val result = mappings.foldRight(actionCode) { mapping, acc -> + mapping.sourceRange.replace(acc, evalOfAngleBracketsExpression.replacement(mapping.context)) + } + + return mappings to result +} + +/** + * Get the action code, roughly like what the rust plugin will see after lalrpop does it's magic; + * mostly resolves <> and replaces them with something based on evalOfAngleBracketsExpressions and + * where they are in the action code (parentheses / brackets / braces). + * + * @param actionCode The action code in the source lalrpop file + * @param evalOfAngleBracketsExpression The list of names for "selected types" + * + * @return The final rust form the action code will have. + */ +fun actionCodeEscape(actionCode: String, evalOfAngleBracketsExpression: List): String = + actionCodeEscapeWithMappings(actionCode, evalOfAngleBracketsExpression).second + class LpActionLiteralTextEscaper(action: LpAction, private val evalOfAngleBracketsExpression: List) : LiteralTextEscaper(action) { @@ -23,17 +48,12 @@ class LpActionLiteralTextEscaper(action: LpAction, private val evalOfAngleBracke override fun decode(rangeInsideHost: TextRange, outChars: StringBuilder): Boolean { // get the text from the host val txt = this.myHost.text.substring(rangeInsideHost.startOffset, rangeInsideHost.endOffset) - // get all the text ranges with <> from rust - mappings = txt.findAllMappings("<>", evalOfAngleBracketsExpression) - - // replace them all with their replacements, in reverse order - val result = mappings.foldRight(txt) { mapping, acc -> - mapping.sourceRange.replace(acc, evalOfAngleBracketsExpression.replacement(mapping.context)) - } + val mappingsAndResult = actionCodeEscapeWithMappings(txt, evalOfAngleBracketsExpression) + mappings = mappingsAndResult.first // add the result string to the builder - outChars.append(result) + outChars.append(mappingsAndResult.second) // can never fail so just return true return true @@ -58,6 +78,14 @@ class LpActionLiteralTextEscaper(action: LpAction, private val evalOfAngleBracke /** * Returns a list of the mappings within `this` for `text` (= "<>") where the `<>` should be * replaced by `replacements`, via the "replacement" function declared below on the `replacements` list. + * + * @param text "<>" + * @param replacements The list of replacements; used to compute the final length and set up the ranges + * + * @return The list of mappings + * + * @see Mapping + * @see lengthFor */ private fun String.findAllMappings(text: String, replacements: List): List { var prevIndex = -text.length @@ -115,7 +143,7 @@ private fun List.replacement(context: Context): String = Context.Braces -> "${it.name}: ${it.name}" } is LpSelectedType.WithoutName -> { - "__intellij_lalrpop_noname_$index" + index.lalrpopNoNameParameterByIndex } } }.joinToString(separator = ", ") diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionType.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionType.kt index 0dfab70..e9b95a2 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionType.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionType.kt @@ -4,12 +4,47 @@ import com.intellij.psi.util.elementType import com.mdrobnak.lalrpop.psi.LpActionType import com.mdrobnak.lalrpop.psi.LpElementTypes import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext +import org.rust.ide.presentation.render import org.rust.lang.core.psi.ext.childrenWithLeaves +import org.rust.lang.core.psi.ext.qualifiedName +import org.rust.lang.core.types.ty.Ty +import org.rust.lang.core.types.ty.TyAdt +/** + * The type an action of this type returns, given the nonterminal type. + * + * @param nonterminalType The type of the nonterminal this action belongs to. + * @param context The type resolution context of this file. + * + * @return String-version of the rust type + */ fun LpActionType.returnType(nonterminalType: String, context: LpTypeResolutionContext): String { return when (this.childrenWithLeaves.first().elementType) { LpElementTypes.USER_ACTION, LpElementTypes.LOOKAHEAD_ACTION, LpElementTypes.LOOKBEHIND_ACTION -> nonterminalType LpElementTypes.FALLIBLE_ACTION -> "::std::result::Result<$nonterminalType, ${context.parseError}>" else -> throw IllegalStateException("Child other than =>, =>@L, =>@R, or =>? in an action_type rule") } +} + +/** + * The inverse of LpActionType.returnType. Given the return type of the action code, return the type the nonterminal has to be. + * + * @param ty Inferred type of action code + * + * @return The type the nonterminal has to be + * + * @see LpActionType.returnType + */ +fun LpActionType.nonterminalTypeFromReturn(ty: Ty): Ty { + return when (this.childrenWithLeaves.first().elementType) { + LpElementTypes.USER_ACTION, LpElementTypes.LOOKAHEAD_ACTION, LpElementTypes.LOOKBEHIND_ACTION -> ty + LpElementTypes.FALLIBLE_ACTION -> + ty.typeParameterValues.let { + if ((ty as? TyAdt)?.item?.qualifiedName != "core::result::Result") { + error("Inferred type from fallible action code(${ty.render()}) is not Result") + } + it.typeByName("T") + } + else -> throw IllegalStateException("Child other than =>, =>@L, =>@R, or =>? in an action_type rule") + } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAlternative.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAlternative.kt index 5f65b9c..3d331d5 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAlternative.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpAlternative.kt @@ -2,20 +2,35 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode -import com.mdrobnak.lalrpop.psi.LpAlternative -import com.mdrobnak.lalrpop.psi.LpSymbol -import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.intellij.psi.util.parentOfType +import com.mdrobnak.lalrpop.psi.* import com.mdrobnak.lalrpop.psi.util.computeType +import com.mdrobnak.lalrpop.psi.util.lalrpopTypeResolutionContext import com.mdrobnak.lalrpop.psi.util.selected val LpAlternative.selected: List - get() { - return this.children.filterIsInstance().selected - } + get() = this.symbolList.selected + +/** + * The selected types, resolved with the 'context' resolution context, and + * + * @param context The type resolution context + * @param resolveTypes Whether to resolve types or not. Some calls to this(like in 'LpAction.createLiteralTextEscaper'), + * don't need to know about the types, but are only interested in names. + * + * @see LpTypeResolutionContext + * @see LpAction.createLiteralTextEscaper + */ +fun LpAlternative.selectedTypesInContext( + context: LpTypeResolutionContext = containingFile.lalrpopTypeResolutionContext(), + resolveTypes: Boolean = true +): List = + this.selected.map { it.getSelectedType(context, resolveTypes) } + +val LpAlternative.nonterminalParent: LpNonterminal + get() = this.parentOfType()!! abstract class LpAlternativeMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpAlternative { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { - return this.symbolList.computeType(context, arguments) - } + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = + this.action?.resolveType(context, arguments) ?: this.symbolList.computeType(context, arguments) } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpArray.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpArray.kt index b77c477..d853123 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpArray.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpArray.kt @@ -4,9 +4,9 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpArray import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpArrayMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpArray { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = "[" + this.typeRef.resolveType(context, arguments) + "]" } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynFn.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynFn.kt index 217bda9..ecc7b51 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynFn.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynFn.kt @@ -4,10 +4,10 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpDynFn import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpDynFnMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpDynFn { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { val forAllTypeParams = this.forall.typeParamList val forAllString = if (forAllTypeParams.isEmpty()) { forAllTypeParams.joinToString(prefix = "for<", separator = ", ", postfix = ">") { it.text } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynTrait.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynTrait.kt index ea7847e..7f4fac6 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynTrait.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpDynTrait.kt @@ -4,10 +4,10 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpDynTrait import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpDynTraitMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpDynTrait { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { return "dyn " + this.rustType.resolveType(context, arguments) } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExprSymbol.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExprSymbol.kt index 42b3e0c..39e163c 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExprSymbol.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExprSymbol.kt @@ -3,12 +3,11 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpExprSymbol -import com.mdrobnak.lalrpop.psi.LpResolveType import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.util.computeType abstract class LpExprSymbolMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpExprSymbol { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = this.symbolList.computeType(context, arguments) } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExternToken.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExternToken.kt index 7c7bff7..7cf1fb1 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExternToken.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpExternToken.kt @@ -5,21 +5,22 @@ import com.intellij.lang.ASTNode import com.intellij.lang.folding.FoldingDescriptor import com.intellij.openapi.editor.Document import com.mdrobnak.lalrpop.psi.LpExternToken +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext import com.mdrobnak.lalrpop.psi.util.braceMatcherFoldDescriptors fun LpExternToken.resolveErrorType(): String? = - this.associatedTypeList.find { it.associatedTypeName.text == "Error" }?.typeRef?.resolveType( - LpTypeResolutionContext(), listOf() + this.associatedTypeList.find { it.id.text == "Error" }?.typeRef?.resolveType( + LpTypeResolutionContext(), LpMacroArguments() ) fun LpExternToken.resolveLocationType(): String? = - this.associatedTypeList.find { it.associatedTypeName.text == "Location" }?.typeRef?.resolveType( - LpTypeResolutionContext(), listOf() + this.associatedTypeList.find { it.id.text == "Location" }?.typeRef?.resolveType( + LpTypeResolutionContext(), LpMacroArguments() ) fun LpExternToken.resolveTokenType(): String? = - this.enumToken?.typeRef?.resolveType(LpTypeResolutionContext(), listOf()) + this.enumToken?.typeRef?.resolveType(LpTypeResolutionContext(), LpMacroArguments()) abstract class LpExternTokenMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpExternToken { override fun getFoldRegions(document: Document, quick: Boolean): List = diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpGrammarDecl.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpGrammarDecl.kt new file mode 100644 index 0000000..25d02ef --- /dev/null +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpGrammarDecl.kt @@ -0,0 +1,11 @@ +package com.mdrobnak.lalrpop.psi.ext + +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import com.mdrobnak.lalrpop.psi.LpGrammarDecl + +fun PsiFile.lalrpopFindGrammarDecl(): LpGrammarDecl = PsiTreeUtil.findChildOfType(this, LpGrammarDecl::class.java)!! + +fun LpGrammarDecl.typeParamsRustUnitStructs(): String = + this.grammarTypeParams?.typeParamList?.filter { it.id != null } + ?.joinToString(separator = "\n", postfix = "\n") { "struct ${it.id!!.text}();" } ?: "" \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminal.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminal.kt index 13a0af9..c31d537 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminal.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminal.kt @@ -5,25 +5,53 @@ import com.intellij.lang.ASTNode import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiModificationTracker +import com.mdrobnak.lalrpop.psi.LpElementFactory +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.LpNonterminal import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument + +fun LpNonterminal.setType(type: String) { + val typePsi = LpElementFactory(project).createNonterminalType(type) + val typeRef = typeRef + + if (typeRef != null) + typeRef.replace(typePsi.second) + else + addRangeAfter(typePsi.first, typePsi.second, nonterminalName) +} + +fun LpNonterminal.rustGenericUnitStructs(): String = + this.containingFile.lalrpopFindGrammarDecl().typeParamsRustUnitStructs() + + this.nonterminalName.nonterminalParams?.nonterminalParamList?.joinToString( + separator = "\n", + postfix = "\n" + ) { "struct ${it.id.text}();" } + abstract class LpNonterminalMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpNonterminal { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = if (this.nonterminalName.nonterminalParams != null) { internallyResolveType(context, arguments) } else { // Isn't a lalrpop macro and therefore can be cached CachedValuesManager.getCachedValue(this) { return@getCachedValue CachedValueProvider.Result( - internallyResolveType(context, listOf()), + internallyResolveType(context, LpMacroArguments()), PsiModificationTracker.MODIFICATION_COUNT ) } } - private fun internallyResolveType(context: LpTypeResolutionContext, arguments: List): String = - this.typeRef?.resolveType(context, arguments) ?: this.alternatives.alternativeList.firstOrNull { it.action == null } - ?.resolveType(context, arguments) ?: "()" + private fun internallyResolveType( + context: LpTypeResolutionContext, + arguments: LpMacroArguments + ): String = + // get directly from the type_ref if available + this.typeRef?.resolveType(context, arguments) ?: + // or try to infer from the first alternative that doesn't have action code + this.alternatives.alternativeList.firstOrNull { it.action == null }?.resolveType(context, arguments) ?: + // or as a last resort try to infer from the action code with intellij-rust + this.alternatives.alternativeList.firstOrNull()?.resolveType(context, arguments) ?: + // or if we get here, it means the nonterminal looks like `A = {};`, e.g. there are no alternatives and no type_ref + "()" } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalParam.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalParam.kt index f10af5f..43023b2 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalParam.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalParam.kt @@ -4,13 +4,14 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement import com.intellij.psi.TokenType +import com.intellij.psi.util.parentOfType import com.mdrobnak.lalrpop.psi.LpElementFactory import com.mdrobnak.lalrpop.psi.LpElementTypes import com.mdrobnak.lalrpop.psi.LpNonterminalParam import com.mdrobnak.lalrpop.psi.LpNonterminalParams import org.toml.lang.psi.ext.elementType -open class LpNonterminalParamMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpNonterminalParam { +abstract class LpNonterminalParamMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpNonterminalParam { override fun getNameIdentifier(): PsiElement { return node.findChildByType(LpElementTypes.ID)!!.psi } @@ -27,12 +28,7 @@ open class LpNonterminalParamMixin(node: ASTNode) : ASTWrapperPsiElement(node), override fun delete() { // if it is alone, just delete the parent `<...>` - when (val parent = this.parent) { - is LpNonterminalParams -> if (parent.nonterminalParamList.size == 1) { - parent.delete() - return - } - } + this.parentOfType()?.let { if (it.nonterminalParamList.size == 1) it.delete() } // on refactoring(safe-delete), also delete the comma that follows this param if (this.nextSibling?.elementType == LpElementTypes.COMMA) this.nextSibling.delete() diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalRef.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalRef.kt index 6b1b42c..fec4aa3 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalRef.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpNonterminalRef.kt @@ -29,28 +29,28 @@ abstract class LpNonterminalRefMixin(node: ASTNode) : ASTWrapperPsiElement(node) return LpNonterminalReference(this) } - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { return when (val ref = this.reference.resolve()) { is LpNonterminalName -> { val nonterminalParams = ref.nonterminalParams - ?: return ref.nonterminalParent.resolveType(context, arguments) + ?: return ref.nonterminalParent.resolveType(context, LpMacroArguments()) val nonterminal = ref.nonterminalParent - val nonterminalArguments = this.arguments - if (nonterminalArguments != null) { - nonterminal.resolveType(context, - nonterminalArguments.symbolList.zip(nonterminalParams.nonterminalParamList).map { - NonterminalGenericArgument(it.first.resolveType(context, arguments), it.second.text) - }) - } else { - nonterminal.resolveType(context, nonterminalParams.nonterminalParamList.map { - NonterminalGenericArgument("()", it.text) - }) - } + this.arguments?.let { nonterminalArguments -> + nonterminal.resolveType( + context, + LpMacroArguments( + nonterminalArguments.symbolList.zip(nonterminalParams.nonterminalParamList).map { + LpMacroArgument(it.first.resolveType(context, arguments), it.second.id.text) + }) + ) + } ?: nonterminal.resolveType(context, LpMacroArguments(nonterminalParams.nonterminalParamList.map { + LpMacroArgument("()", it.id.text) + })) } is LpNonterminalParam -> { - arguments.find { it.name == ref.text }?.rustType ?: ref.text + arguments.find { it.name == ref.id.text }?.rustType ?: ref.text } else -> "()" } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpParenthesesExprSymbol.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpParenthesesExprSymbol.kt index 2b00e27..1e27d4b 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpParenthesesExprSymbol.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpParenthesesExprSymbol.kt @@ -4,10 +4,9 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpParenthesesExprSymbol import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpParenthesesExprSymbolMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpParenthesesExprSymbol { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { - return this.exprSymbol.resolveType(context, arguments) - } + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = + exprSymbol.resolveType(context, arguments) } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpPathId.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpPathId.kt index 1896636..6f7d5d0 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpPathId.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpPathId.kt @@ -3,16 +3,16 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.intellij.psi.PsiReference +import com.intellij.psi.util.parentOfType +import com.mdrobnak.lalrpop.psi.LpMacroArguments +import com.mdrobnak.lalrpop.psi.LpNonterminal import com.mdrobnak.lalrpop.psi.LpPathId import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument import com.mdrobnak.lalrpop.resolve.LpPathIdReference -open class LpPathIdMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpPathId { +abstract class LpPathIdMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpPathId { override fun getReference(): PsiReference? = LpPathIdReference(this) - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { - val id = this.text - return arguments.find { it.name == id }?.rustType ?: id - } + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = + this.id.text.let { id -> arguments.find { it.name == id }?.rustType ?: id } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpQuotedLiteral.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpQuotedLiteral.kt index f31f45b..162f735 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpQuotedLiteral.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpQuotedLiteral.kt @@ -23,5 +23,5 @@ abstract class LpQuotedLiteralMixin(node: ASTNode) : ASTWrapperPsiElement(node), return LiteralTextEscaper.createSimple(this) } - fun isRegex(): Boolean = firstChild.elementType == LpElementTypes.REGEX_LITERAL + fun isRegex(): Boolean = regexLiteral != null } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustReference.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustReference.kt index 628bb51..4e73d6e 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustReference.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustReference.kt @@ -5,21 +5,18 @@ import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpElementTypes import com.mdrobnak.lalrpop.psi.LpRustReference import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments import org.rust.lang.core.psi.ext.childrenWithLeaves import org.rust.lang.core.psi.ext.elementType -val LpRustReference.lifetime: String? - get() = this.lifetimeRule?.text - val LpRustReference.lifetimeOrInfer: String - get() = this.lifetime ?: "'_" + get() = this.lifetime?.text ?: "'_" val LpRustReference.isRefMut: Boolean - get() = this.childrenWithLeaves.find { it.elementType == LpElementTypes.MUT } != null + get() = this.mut != null abstract class LpRustReferenceMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpRustReference { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { return "&${this.lifetimeOrInfer} ${if (this.isRefMut) "mut" else ""} ${this.typeRef.resolveType(context, arguments)}" } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustType.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustType.kt index d1a8755..d136850 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustType.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpRustType.kt @@ -2,19 +2,22 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.LpRustType +import com.mdrobnak.lalrpop.psi.LpTypeRef import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument abstract class LpRustTypeMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpRustType { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { - val path = this.path + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { return (path.pathId?.resolveType(context, arguments) ?: path.text) + - when (val genericArguments = this.typeGenericArguments) { - null -> "" - else -> (genericArguments.lifetimeRuleList.map { it.text } + - genericArguments.typeRefList.map { it.resolveType(context, arguments) }) - .joinToString(prefix = "<", separator = ", ", postfix = ">") { it } - } + (this.typeGenericArguments?.let { genericArguments -> + genericArguments.typeRefOrLifetimeList + .joinToString(prefix = "<", separator = ", ", postfix = ">") { + when (val child = it.firstChild) { + is LpTypeRef -> child.resolveType(context, arguments) + else -> child.text // lifetime + } + } + } ?: "") } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol.kt index ded96a6..273e907 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol.kt @@ -2,39 +2,43 @@ package com.mdrobnak.lalrpop.psi.ext import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode +import com.intellij.psi.util.parentOfType import com.mdrobnak.lalrpop.psi.* -import org.rust.lang.core.psi.ext.childrenWithLeaves -import org.rust.lang.core.psi.ext.elementType val LpSymbol.isExplicitlySelected: Boolean - get() = this.childrenWithLeaves.first().elementType == LpElementTypes.LESSTHAN + get() = this.lessthan != null val LpSymbol.isNamed: Boolean get() = this.symbolName != null val LpSymbol.isMutable: Boolean - get() = this.symbolName?.childrenWithLeaves?.any { it.elementType == LpElementTypes.MUT } ?: false + get() = this.symbolName?.mut != null val LpSymbol.symbolNameString: String? - get() = this.symbolName?.childrenWithLeaves?.find { it.elementType == LpElementTypes.ID }?.text + get() = this.symbolName?.id?.text fun LpSymbol.removeName() { this.symbolName?.delete() } -abstract class LpSymbolMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpSymbol { - fun getSelectedType(context: LpTypeResolutionContext): LpSelectedType { - val isMutable = this.isMutable - val name = this.symbolNameString - val type = this.resolveType(context, listOf()) - - return if (name != null) { - LpSelectedType.WithName(isMutable, name, type) - } else { - LpSelectedType.WithoutName(type) - } +fun LpSymbol.getSelectedType(context: LpTypeResolutionContext, resolveTypes: Boolean = true): LpSelectedType { + val isMutable = this.isMutable + val name = this.symbolNameString + val type = if (resolveTypes) + this.resolveType( + context, + LpMacroArguments.identity(this.parentOfType()?.nonterminalName?.nonterminalParams) + ) + else "" + + return if (name != null) { + LpSelectedType.WithName(name, type, isMutable) + } else { + LpSelectedType.WithoutName(type) } +} - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = +abstract class LpSymbolMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpSymbol { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = symbol0.resolveType(context, arguments) } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol0.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol0.kt index 6e99712..31c627d 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol0.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol0.kt @@ -4,10 +4,10 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpSymbol0 import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpSymbol0Mixin(node: ASTNode) : ASTWrapperPsiElement(node), LpSymbol0 { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = repeatOpList.fold(symbol1.resolveType(context, arguments)) { tp, repeatOp -> repeatOp.switch( question = "::std::option::Option<$tp>", diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol1.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol1.kt index 6e8183d..023dc8b 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol1.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpSymbol1.kt @@ -5,13 +5,13 @@ import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpElementTypes import com.mdrobnak.lalrpop.psi.LpSymbol1 import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.util.computeType import org.rust.lang.core.psi.ext.childrenWithLeaves import org.rust.lang.core.psi.ext.elementType abstract class LpSymbol1Mixin(node: ASTNode) : ASTWrapperPsiElement(node), LpSymbol1 { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String { + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { val nonterminalRef = this.nonterminalRef if (nonterminalRef != null) { return nonterminalRef.resolveType(context, arguments) diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTuple.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTuple.kt index a5d6c44..f8b862c 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTuple.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTuple.kt @@ -4,10 +4,10 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpTuple import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpTupleMixin(node: ASTNode): ASTWrapperPsiElement(node), LpTuple { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = this.typeRefList.joinToString(prefix = "(", separator = ", ", postfix = ")") { it.resolveType(context, arguments) } diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeOfSymbol.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeOfSymbol.kt index e538b9a..773b2bf 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeOfSymbol.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeOfSymbol.kt @@ -4,8 +4,8 @@ import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.lang.ASTNode import com.mdrobnak.lalrpop.psi.LpTypeOfSymbol import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments abstract class LpTypeOfSymbolMixin(node: ASTNode): ASTWrapperPsiElement(node), LpTypeOfSymbol { - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = this.symbol.resolveType(context, arguments) + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = this.symbol.resolveType(context, arguments) } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeRef.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeRef.kt index b6a2500..87fe66a 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeRef.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpTypeRef.kt @@ -10,7 +10,7 @@ import com.intellij.psi.util.parentOfType import com.mdrobnak.lalrpop.psi.LpResolveType import com.mdrobnak.lalrpop.psi.LpTypeRef import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments /** * If this type ref is not a child of another type ref. @@ -34,7 +34,7 @@ abstract class LpTypeRefMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpTyp return SimpleMultiLineTextEscaper(this) } - override fun resolveType(context: LpTypeResolutionContext, arguments: List): String = + override fun resolveType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String = when (val child = firstChild) { // all children of a TypeRef in the AST: // LpTuple, LpArray, LpTypeOfSymbol, LpRustReference, LpRustType, LpDynTrait, LpDynFn diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpUseStmt.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpUseStmt.kt index 3b1a45e..1b5bf9e 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpUseStmt.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/ext/LpUseStmt.kt @@ -16,9 +16,6 @@ val PsiFile.importCode: String get() = this.childrenOfType().joinToString(separator = "\n") { it.text } abstract class LpUseStmtMixin(node: ASTNode) : ASTWrapperPsiElement(node), LpUseStmt { - val importCode: PsiElement - get() = findChildByType(LpElementTypes.IMPORT_CODE)!! - override fun isValidHost(): Boolean = true override fun updateText(text: String): PsiLanguageInjectionHost { diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/psi/util/LpPsiUtil.kt b/src/main/kotlin/com/mdrobnak/lalrpop/psi/util/LpPsiUtil.kt index 237695c..cf001a8 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/psi/util/LpPsiUtil.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/psi/util/LpPsiUtil.kt @@ -8,7 +8,7 @@ import com.intellij.psi.util.PsiTreeUtil import com.mdrobnak.lalrpop.psi.LpExternToken import com.mdrobnak.lalrpop.psi.LpSymbol import com.mdrobnak.lalrpop.psi.LpTypeResolutionContext -import com.mdrobnak.lalrpop.psi.NonterminalGenericArgument +import com.mdrobnak.lalrpop.psi.LpMacroArguments import com.mdrobnak.lalrpop.psi.ext.isExplicitlySelected import com.mdrobnak.lalrpop.psi.ext.resolveErrorType import com.mdrobnak.lalrpop.psi.ext.resolveLocationType @@ -21,7 +21,7 @@ val List.selected: List this } -fun List.computeType(context: LpTypeResolutionContext, arguments: List): String { +fun List.computeType(context: LpTypeResolutionContext, arguments: LpMacroArguments): String { val sel = selected return sel.joinToString( prefix = if (sel.size != 1) "(" else "", diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpNonterminalReference.kt b/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpNonterminalReference.kt index ac5b000..d652428 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpNonterminalReference.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpNonterminalReference.kt @@ -11,11 +11,9 @@ import com.mdrobnak.lalrpop.psi.LpNonterminalRef class LpNonterminalReference(element: LpNonterminalRef) : PsiReferenceBase(element, TextRange.allOf(element.text)) { override fun resolve(): PsiElement? = - LpResolveUtil.findNonterminal(element.containingFile, element.text).firstOrNull() ?: when (val thisNonTerminal = - PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java)) { - null -> null - else -> LpResolveUtil.findNonterminalParameter(thisNonTerminal, element.text).firstOrNull() - } + LpResolveUtil.findNonterminal(element.containingFile, element.text).firstOrNull() + ?: PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java) + ?.let { LpResolveUtil.findNonterminalParameter(it, element.text).firstOrNull() } override fun handleElementRename(newElementName: String): PsiElement { val newNode = LpElementFactory(element.project).createIdentifier(newElementName) @@ -24,10 +22,8 @@ class LpNonterminalReference(element: LpNonterminalRef) : } override fun getVariants(): Array { - return (LpResolveUtil.findNonterminals(element.containingFile) + when (val thisNonTerminal = - PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java)) { - null -> listOf() - else -> LpResolveUtil.findNonterminalParams(thisNonTerminal) - }).toTypedArray() + return (LpResolveUtil.findNonterminals(element.containingFile) + + PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java) + ?.let { LpResolveUtil.findNonterminalParams(it) }.orEmpty()).toTypedArray() } } \ No newline at end of file diff --git a/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpPathIdReference.kt b/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpPathIdReference.kt index 50b342f..ba40df4 100644 --- a/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpPathIdReference.kt +++ b/src/main/kotlin/com/mdrobnak/lalrpop/resolve/LpPathIdReference.kt @@ -11,10 +11,8 @@ import com.mdrobnak.lalrpop.psi.LpPathId class LpPathIdReference(element: LpPathId) : PsiReferenceBase(element, TextRange.allOf(element.text)) { override fun resolve(): PsiElement? = - when (val thisNonTerminal = PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java)) { - null -> null - else -> LpResolveUtil.findNonterminalParameter(thisNonTerminal, element.text).firstOrNull() - } + PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java) + ?.let { LpResolveUtil.findNonterminalParameter(it, element.text).firstOrNull() } override fun handleElementRename(newElementName: String): PsiElement { val newNode = LpElementFactory(element.project).createIdentifier(newElementName) @@ -23,8 +21,7 @@ class LpPathIdReference(element: LpPathId) : } override fun getVariants(): Array = - when (val thisNonTerminal = PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java)) { - null -> arrayOf() - else -> LpResolveUtil.findNonterminalParams(thisNonTerminal).toTypedArray() - } + PsiTreeUtil.getParentOfType(element, LpNonterminal::class.java)?.let { + LpResolveUtil.findNonterminalParams(it) + }.orEmpty().toTypedArray() } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7c834a0..f675e12 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -84,5 +84,10 @@ enabledByDefault="true" level="ERROR"/> + + com.mdrobnak.lalrpop.intentions.AddExplicitTypeIntention + LALRPOP + + \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/after.lalrpop.template b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/after.lalrpop.template new file mode 100644 index 0000000..5960b09 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/after.lalrpop.template @@ -0,0 +1,5 @@ +grammar; + +A = (); + +B: () = A; \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/before.lalrpop.template b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/before.lalrpop.template new file mode 100644 index 0000000..9106609 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/before.lalrpop.template @@ -0,0 +1,5 @@ +grammar; + +A = (); + +B = A; \ No newline at end of file diff --git a/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/description.html b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/description.html new file mode 100644 index 0000000..5257ef1 --- /dev/null +++ b/src/main/resources/intentionDescriptions/AddExplicitTypeIntention/description.html @@ -0,0 +1,9 @@ + + +

+ Infers the type of a nonterminal and sets the result on said nonterminal. +

+ +

+ + \ No newline at end of file diff --git a/src/test/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaperTest.kt b/src/test/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaperTest.kt new file mode 100644 index 0000000..f105b13 --- /dev/null +++ b/src/test/kotlin/com/mdrobnak/lalrpop/psi/ext/LpActionLiteralTextEscaperTest.kt @@ -0,0 +1,61 @@ +package com.mdrobnak.lalrpop.psi.ext + +import com.mdrobnak.lalrpop.psi.LpSelectedType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class LpActionLiteralTextEscaperTest { + @Test + fun `no replacements`() { + assertEquals("", actionCodeEscape("", listOf())) + assertEquals("X::Y::new()", actionCodeEscape("X::Y::new()", listOf())) + } + + @Test + fun `simple inside-parentheses replacements`() { + assertEquals( + "X::new(p1, p2, p3)", + actionCodeEscape( + "X::new(<>)", listOf( + LpSelectedType.WithName(name = "p1", type = ""), + LpSelectedType.WithName(name = "p2", type = ""), + LpSelectedType.WithName(name = "p3", type = "") + ) + ) + ) + assertEquals( + "X::new(a, b, c, p1, p2, p3)", + actionCodeEscape( + "X::new(a, b, c, <>)", listOf( + LpSelectedType.WithName(name = "p1", type = ""), + LpSelectedType.WithName(name = "p2", type = ""), + LpSelectedType.WithName(name = "p3", type = "") + ) + ) + ) + } + + @Test + fun `simple inside-braces replacements`() { + assertEquals( + "X {p1: p1, p2: p2, p3: p3}", + actionCodeEscape( + "X {<>}", listOf( + LpSelectedType.WithName(name = "p1", type = ""), + LpSelectedType.WithName(name = "p2", type = ""), + LpSelectedType.WithName(name = "p3", type = "") + ) + ) + ) + assertEquals( + "X {a, b, c, p1: p1, p2: p2, p3: p3}", + actionCodeEscape( + "X {a, b, c, <>}", listOf( + LpSelectedType.WithName(name = "p1", type = ""), + LpSelectedType.WithName(name = "p2", type = ""), + LpSelectedType.WithName(name = "p3", type = "") + ) + ) + ) + } +} \ No newline at end of file