Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INT & ANN: Implement intention and fix for struct fields expansion instead of .. #3996

Merged
merged 1 commit into from Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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) {
shevtsiv marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this patFieldList return _ patterns as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this patFieldList return _ patterns as well?

Yes, this patFieldList also includes underscores, but the patTupleStruct.childrenOfType<RsPatIdent>() that was used below does not do this. That's why _ were considered wrong fields.

Thank you for the report!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but the patTupleStruct.childrenOfType() that was used below does not do this.

Oh! I didn't catch that. Thanks for your fix!

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>