Skip to content

Commit

Permalink
Implement fix and intention for actual struct fields expansion
Browse files Browse the repository at this point in the history
  • Loading branch information
shevtsiv committed Aug 19, 2019
1 parent d5240d0 commit b0eac1c
Show file tree
Hide file tree
Showing 13 changed files with 1,077 additions and 1 deletion.
38 changes: 38 additions & 0 deletions src/main/kotlin/org/rust/ide/annotator/RsErrorAnnotator.kt
Expand Up @@ -85,11 +85,49 @@ class RsErrorAnnotator : RsAnnotatorBase(), HighlightRangeExtension {
override fun visitSelfParameter(o: RsSelfParameter) = checkParamAttrs(holder, o)
override fun visitValueParameter(o: RsValueParameter) = checkParamAttrs(holder, o)
override fun visitVariadic(o: RsVariadic) = checkParamAttrs(holder, o)
override fun visitPatStruct(o: RsPatStruct) = checkRsPatStruct(holder, o)
override fun visitPatTupleStruct(o: RsPatTupleStruct) = checkRsPatTupleStruct(holder, o)
}

element.accept(visitor)
}

private fun checkRsPatStruct(holder: AnnotationHolder, patStruct: RsPatStruct) {
val declaration = patStruct.path.reference.deepResolve() as? RsFieldsOwner ?: return
val declarationFieldNames = declaration.fields.map { it.name }
val bodyFields = patStruct.patFieldList
val extraFields = bodyFields.filter { it.kind.fieldName !in declarationFieldNames }
val bodyFieldNames = bodyFields.map { it.kind.fieldName }
val missingFields = declaration.fields.filter { it.name !in bodyFieldNames && !it.queryAttributes.hasCfgAttr() }
extraFields.forEach {
RsDiagnostic.ExtraFieldInStructPattern(it).addToHolder(holder)
}
if (missingFields.isNotEmpty() && patStruct.dotdot == null) {
if (declaration.elementType == RsElementTypes.ENUM_VARIANT) {
RsDiagnostic.MissingFieldsInEnumVariantPattern(patStruct, declaration.text).addToHolder(holder)
} else {
RsDiagnostic.MissingFieldsInStructPattern(patStruct, declaration.text).addToHolder(holder)
}
}
}

private fun checkRsPatTupleStruct(holder: AnnotationHolder, patTupleStruct: RsPatTupleStruct) {
val declaration = patTupleStruct.path.reference.deepResolve() as? RsFieldsOwner ?: return
val bodyFields = patTupleStruct.childrenOfType<RsPatIdent>()
if (bodyFields.size < declaration.fields.size && patTupleStruct.dotdot == null) {
if (declaration.elementType == RsElementTypes.ENUM_VARIANT) {
RsDiagnostic.MissingFieldsInEnumVariantTuplePattern(patTupleStruct, declaration.text).addToHolder(holder)
} else {
RsDiagnostic.MissingFieldsInTupleStructPattern(patTupleStruct, declaration.text).addToHolder(holder)
}
} else if (bodyFields.size > declaration.fields.size) {
RsDiagnostic.ExtraFieldInTupleStructPattern(
patTupleStruct,
bodyFields.size ,
declaration.fields.size
).addToHolder(holder)
}
}

private fun checkTraitType(holder: AnnotationHolder, traitType: RsTraitType) {
if (!traitType.isImpl) return
Expand Down
@@ -0,0 +1,34 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.ide.annotator.fixes

import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.rust.ide.utils.expandStructFields
import org.rust.ide.utils.expandTupleStructFields
import org.rust.lang.core.psi.RsPatStruct
import org.rust.lang.core.psi.RsPatTupleStruct
import org.rust.lang.core.psi.RsPsiFactory

class AddStructFieldsPatFix(
element: PsiElement
) : LocalQuickFixAndIntentionActionOnPsiElement(element) {
override fun getText() = "Add missing fields"

override fun getFamilyName() = text

override fun invoke(project: Project, file: PsiFile, editor: Editor?, pat: PsiElement, endElement: PsiElement) {
val factory = RsPsiFactory(project)
if (pat is RsPatStruct) {
expandStructFields(factory, pat)
} else if (pat is RsPatTupleStruct) {
expandTupleStructFields(factory, editor, pat)
}
}
}
@@ -0,0 +1,43 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.ide.intentions

import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import org.rust.ide.utils.expandStructFields
import org.rust.ide.utils.expandTupleStructFields
import org.rust.lang.core.psi.*
import org.rust.lang.core.psi.ext.elementType

class AddStructFieldsPatIntention : RsElementBaseIntentionAction<AddStructFieldsPatIntention.Context>() {
override fun getText() = "Replace .. with actual fields"

override fun getFamilyName() = text

data class Context(
val structBody: RsPat
)

override fun findApplicableContext(project: Project, editor: Editor, element: PsiElement): Context? {
return if (element.elementType == RsElementTypes.DOTDOT
&& (element.context is RsPatStruct || element.context is RsPatTupleStruct)) {
Context(element.context as RsPat)
} else {
null
}
}

override fun invoke(project: Project, editor: Editor, ctx: Context) {
val factory = RsPsiFactory(project)
val structBody = ctx.structBody
if (structBody is RsPatStruct) {
expandStructFields(factory, structBody)
} else if (structBody is RsPatTupleStruct) {
expandTupleStructFields(factory, editor, structBody)
}
}
}
101 changes: 101 additions & 0 deletions src/main/kotlin/org/rust/ide/utils/StructFieldsExpander.kt
@@ -0,0 +1,101 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.ide.utils

import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.rust.lang.core.psi.*
import org.rust.lang.core.psi.ext.*
import org.rust.lang.core.resolve.ref.deepResolve
import org.rust.openapiext.buildAndRunTemplate
import org.rust.openapiext.createSmartPointer

fun expandStructFields(factory: RsPsiFactory, patStruct: RsPatStruct) {
val declaration = patStruct.path.reference.deepResolve() as? RsFieldsOwner ?: return
val hasTrailingComma = patStruct.rbrace.getPrevNonCommentSibling()?.elementType == RsElementTypes.COMMA
patStruct.dotdot?.delete()
val existingFields = patStruct.patFieldList
val bodyFieldNames = existingFields.map { it.kind.fieldName }.toSet()
val missingFields = declaration.fields
.filter { it.name !in bodyFieldNames && !it.queryAttributes.hasCfgAttr() }
.map { factory.createPatField(it.name!!) }

if (existingFields.isEmpty()) {
addFieldsToPat(factory, patStruct, missingFields, hasTrailingComma)
return
}

val fieldPositions = declaration.fields.withIndex().associate { it.value.name!! to it.index }
var insertedFieldsAmount = 0
for (missingField in missingFields) {
val missingFieldPosition = fieldPositions[missingField.kind.fieldName]!!
for (existingField in existingFields) {
val existingFieldPosition = fieldPositions[existingField.kind.fieldName] ?: continue
if (missingFieldPosition < existingFieldPosition) {
patStruct.addBefore(missingField, existingField)
patStruct.addAfter(factory.createComma(), existingField.getPrevNonCommentSibling())
insertedFieldsAmount++
break
}
}
}
addFieldsToPat(factory, patStruct, missingFields.drop(insertedFieldsAmount), hasTrailingComma)
}

fun expandTupleStructFields(factory: RsPsiFactory, editor: Editor?, patTuple: RsPatTupleStruct) {
val declaration = patTuple.path.reference.deepResolve() as? RsFieldsOwner ?: return
val hasTrailingComma = patTuple.rparen.getPrevNonCommentSibling()?.elementType == RsElementTypes.COMMA
val bodyFields = patTuple.childrenOfType<RsPatIdent>()
val missingFieldsAmount = declaration.fields.size - bodyFields.size
addFieldsToPat(factory, patTuple, createTupleStructMissingFields(factory, missingFieldsAmount), hasTrailingComma)
patTuple.dotdot?.delete()
editor?.buildAndRunTemplate(patTuple, patTuple.childrenOfType<RsPatBinding>().map { it.createSmartPointer() })
}

private fun createTupleStructMissingFields(factory: RsPsiFactory, amount: Int): List<RsPatBinding> {
val missingFields = ArrayList<RsPatBinding>(amount)
for (i in 0 until amount) {
missingFields.add(factory.createPatBinding("_$i"))
}
return missingFields
}

private fun addFieldsToPat(factory: RsPsiFactory, pat: RsPat, fields: List<PsiElement>, hasTrailingComma: Boolean) {
var anchor = determineOrCreateAnchor(factory, pat)
for (missingField in fields) {
pat.addAfter(missingField, anchor)
// Do not insert comma if we are in the middle of pattern
// since it will cause double comma in patterns with a trailing comma.
if (fields.last() == missingField) {
if (anchor.nextSibling?.getNextNonCommentSibling()?.elementType != RsElementTypes.DOTDOT) {
pat.addAfter(factory.createComma(), anchor.nextSibling)
}
} else {
pat.addAfter(factory.createComma(), anchor.nextSibling)
}
anchor = anchor.nextSibling.nextSibling as LeafPsiElement
}
if (!hasTrailingComma) {
anchor.delete()
}
}

private fun determineOrCreateAnchor(factory: RsPsiFactory, pat: RsPat): PsiElement {
val dots = pat.childrenOfType<LeafPsiElement>().firstOrNull { it.elementType == RsElementTypes.DOTDOT }
if (dots != null) {
// Picking dots prev sibling as anchor allows as to fill the pattern starting from dots position
// instead of filling pattern starting from the end.
return dots.getPrevNonCommentSibling()!! as LeafPsiElement
}
val lastElementInBody = pat.lastChild.getPrevNonCommentSibling()!!
return if (lastElementInBody !is LeafPsiElement) {
pat.addAfter(factory.createComma(), lastElementInBody)
lastElementInBody.nextSibling
} else {
lastElementInBody
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/org/rust/lang/core/psi/RsPsiFactory.kt
Expand Up @@ -387,6 +387,14 @@ class RsPsiFactory(
?.firstChild as RsPatBinding?
?: error("Failed to create pat element")

fun createPatField(name: String): RsPatField =
createFromText("""
struct Foo { bar: i32 }
fn baz(foo: Foo) {
let Foo { $name } = foo;
}
""") ?: error("Failed to create pat field")

fun createPatStruct(struct: RsStructItem): RsPatStruct {
val structName = struct.name ?: error("Failed to create pat struct")
val pad = if (struct.namedFields.isEmpty()) "" else " "
Expand Down
69 changes: 68 additions & 1 deletion src/main/kotlin/org/rust/lang/utils/RsDiagnostic.kt
Expand Up @@ -1104,10 +1104,77 @@ sealed class RsDiagnostic(
"nested `impl Trait` is not allowed"
)
}

class MissingFieldsInEnumVariantTuplePattern(pat: RsPat, private val declaration: String) : RsDiagnostic(pat) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0023,
"Enum variant pattern does not correspond to its declaration: `$declaration`",
fixes = listOf(AddStructFieldsPatFix(element))
)
}
}
class MissingFieldsInEnumVariantPattern(pat: RsPat, private val declaration: String) : RsDiagnostic(pat) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0027,
"Enum variant pattern does not correspond to its declaration: `$declaration`",
fixes = listOf(AddStructFieldsPatFix(element))
)
}
}

class MissingFieldsInStructPattern(pat: RsPat, private val declaration: String) : RsDiagnostic(pat) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0027,
"Struct pattern does not correspond to its declaration: `$declaration`",
fixes = listOf(AddStructFieldsPatFix(element))
)
}
}

class MissingFieldsInTupleStructPattern(pat: RsPat, private val declaration: String) : RsDiagnostic(pat) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0023,
"Tuple struct pattern does not correspond to its declaration: `$declaration`",
fixes = listOf(AddStructFieldsPatFix(element))
)
}
}

class ExtraFieldInStructPattern(private val extraField: RsPatField) : RsDiagnostic(extraField) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0026,
"Extra field found in the struct pattern: `${extraField.kind.fieldName}`"
)
}
}

class ExtraFieldInTupleStructPattern(
patTupleStruct: RsPatTupleStruct,
private val extraFieldsAmount: Int,
private val expectedAmount: Int
) : RsDiagnostic(patTupleStruct) {
override fun prepare(): PreparedAnnotation {
return PreparedAnnotation(
ERROR,
E0023,
"Extra fields found in the tuple struct pattern: Expected $expectedAmount, found $extraFieldsAmount"
)
}
}
}

enum class RsErrorCode {
E0004, E0040, E0046, E0050, E0060, E0061, E0069, E0081, E0084,
E0004, E0023, E0026, E0027, E0040, E0046, E0050, E0060, E0061, E0069, E0081, E0084,
E0106, E0107, E0118, E0120, E0121, E0124, E0132, E0133, E0184, E0185, E0186, E0198, E0199,
E0200, E0201, E0202, E0261, E0262, E0263, E0267, E0268, E0277,
E0308, E0322, E0328, E0379, E0384,
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/META-INF/core.xml
Expand Up @@ -690,6 +690,10 @@
<className>org.rust.ide.intentions.ConvertToTupleIntention</className>
<category>Rust</category>
</intentionAction>
<intentionAction>
<className>org.rust.ide.intentions.AddStructFieldsPatIntention</className>
<category>Rust</category>
</intentionAction>

<!-- Run Configurations -->

Expand Down
@@ -0,0 +1,4 @@
struct Bar { baz: i32, quux: i32, eggs:i32 }
fn foo(bar: Bar) {
let Bar { baz, quux, eggs } = bar;
}
@@ -0,0 +1,4 @@
struct Bar { baz: i32, quux: i32, eggs:i32 }
fn foo(bar: Bar) {
let Bar { baz, .. } = bar;
}
@@ -0,0 +1,5 @@
<html>
<body>
This intention expands actual struct fields instead of `..`.
</body>
</html>

0 comments on commit b0eac1c

Please sign in to comment.