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: Support "Create associated function" for path Self::func() #9335

Merged
merged 1 commit into from Sep 28, 2022
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
Expand Up @@ -51,7 +51,6 @@ class CreateFunctionIntention : RsElementBaseIntentionAction<CreateFunctionInten
open val isAsync: Boolean = callElement.isAtLeastEdition2018
abstract val arguments: RsValueArgumentList
abstract val returnType: ReturnType
open val implItem: RsImplItem? = null

open class Function(callExpr: RsCallExpr, name: String, val module: RsMod) : Context(name, callExpr) {
override val visibility: String = getVisibility(module, callExpr.containingMod)
Expand All @@ -65,11 +64,9 @@ class CreateFunctionIntention : RsElementBaseIntentionAction<CreateFunctionInten
callExpr: RsCallExpr,
name: String,
module: RsMod,
val item: RsStructOrEnumItemElement
) : Function(callExpr, name, module) {
override val implItem: RsImplItem?
get() = super.implItem
}
val item: RsStructOrEnumItemElement?,
val existingImpl: RsImplItem?,
) : Function(callExpr, name, module)

class Method(
val methodCall: RsMethodCall,
Expand Down Expand Up @@ -103,12 +100,23 @@ class CreateFunctionIntention : RsElementBaseIntentionAction<CreateFunctionInten
val target = getTargetItemForFunctionCall(path) ?: return null
val name = path.referenceName ?: return null

return if (target is CallableInsertionTarget.Item) {
text = "Create associated function `${target.item.name}::$name`"
Context.AssociatedFunction(functionCall, name, target.module, target.item)
} else {
text = "Create function `$name`"
Context.Function(functionCall, name, target.module)
return when (target) {
is RsMod -> {
text = "Create function `$name`"
Context.Function(functionCall, name, target)
}

is RsStructOrEnumItemElement -> {
text = "Create associated function `${target.name}::$name`"
Context.AssociatedFunction(functionCall, name, target.containingMod, target, existingImpl = null)
Copy link
Member

Choose a reason for hiding this comment

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

It looks like in the current usage, existingImpl and item are exclusive? It could be modeled by some enum, but it's probably overkill.

}

is RsImplItem -> {
text = "Create associated function `Self::$name`"
Context.AssociatedFunction(functionCall, name, target.containingMod, item = null, target)
}

else -> null
}
}
val methodCall = element.parentOfType<RsMethodCall>()
Expand Down Expand Up @@ -202,21 +210,25 @@ class CreateFunctionIntention : RsElementBaseIntentionAction<CreateFunctionInten
val sourceFunction = ctx.callElement.parentOfType<RsFunction>() ?: return null

return when (ctx) {
is Context.AssociatedFunction -> insertAssociatedFunction(ctx.item, function)
is Context.AssociatedFunction -> insertAssociatedFunction(ctx.item, ctx.existingImpl, function)
is Context.Function -> insertFunction(ctx.module, sourceFunction, function)
is Context.Method -> insertMethod(ctx.item, sourceFunction, function)
}
}

private fun insertAssociatedFunction(
item: RsStructOrEnumItemElement,
item: RsStructOrEnumItemElement?,
existingImpl: RsImplItem?,
function: RsFunction
): RsFunction? {
val psiFactory = RsPsiFactory(item.project)
val name = item.name ?: return null
val psiFactory = RsPsiFactory(function.project)

val newImpl = psiFactory.createInherentImplItem(name, item.typeParameterList, item.whereClause)
val impl = item.parent.addAfter(newImpl, item) as RsImplItem
val impl = existingImpl
?: run {
val name = item?.name ?: return null
val newImpl = psiFactory.createInherentImplItem(name, item.typeParameterList, item.whereClause)
item.parent.addAfter(newImpl, item) as RsImplItem
}

return impl.members?.let {
it.addBefore(function, it.rbrace) as RsFunction
Expand Down
Expand Up @@ -32,7 +32,7 @@ class CreateStructIntention : RsElementBaseIntentionAction<CreateStructIntention
if (structLiteral.path != path) return null
if (path.resolveStatus != PathResolveStatus.UNRESOLVED) return null

val target = getTargetModForStruct(path) ?: return null
val target = getWritablePathMod(path) ?: return null
val name = path.referenceName ?: return null

text = "Create struct `$name`"
Expand Down Expand Up @@ -101,8 +101,3 @@ class CreateStructIntention : RsElementBaseIntentionAction<CreateStructIntention
return factory.tryCreateStruct("${visibility}struct ${ctx.name}$suffix")
}
}

private fun getTargetModForStruct(path: RsPath): RsMod? = when {
path.qualifier != null -> getWritablePathTarget(path) as? RsMod
else -> path.containingMod
}
Expand Up @@ -34,8 +34,7 @@ class CreateTupleStructIntention : RsElementBaseIntentionAction<CreateTupleStruc
if (!functionCall.expr.isAncestorOf(path)) return null
if (path.resolveStatus != PathResolveStatus.UNRESOLVED) return null

val target = getTargetItemForFunctionCall(path) ?: return null
if (target !is CallableInsertionTarget.Module) return null
val targetMod = getWritablePathMod(path) ?: return null

val name = path.referenceName ?: return null
if (!name.isCamelCase()) return null
Expand All @@ -45,7 +44,7 @@ class CreateTupleStructIntention : RsElementBaseIntentionAction<CreateTupleStruc
if (expectedType !is TyUnknown) return null

text = "Create tuple struct `$name`"
return Context(name, functionCall, target.module)
return Context(name, functionCall, targetMod)
}
return null
}
Expand Down
38 changes: 18 additions & 20 deletions src/main/kotlin/org/rust/ide/intentions/createFromUsage/utils.kt
Expand Up @@ -7,6 +7,7 @@ package org.rust.ide.intentions.createFromUsage

import com.intellij.psi.PsiElement
import org.rust.cargo.project.workspace.PackageOrigin
import org.rust.lang.core.psi.RsImplItem
import org.rust.lang.core.psi.RsModItem
import org.rust.lang.core.psi.RsPath
import org.rust.lang.core.psi.RsStructItem
Expand All @@ -32,37 +33,34 @@ fun getVisibility(target: RsMod, source: RsMod): String = when {
else -> ""
}

fun getWritablePathTarget(path: RsPath): RsQualifiedNamedElement? {
private fun getWritablePathTarget(path: RsPath): RsQualifiedNamedElement? {
val item = path.qualifier?.reference?.resolve() as? RsQualifiedNamedElement
if (item?.containingCargoPackage?.origin != PackageOrigin.WORKSPACE) return null
if (!isUnitTestMode && !item.isWritable) return null
return item
}

sealed class CallableInsertionTarget {
abstract val module: RsMod

class Module(val target: RsMod) : CallableInsertionTarget() {
override val module: RsMod = target
}

class Item(val item: RsStructOrEnumItemElement) : CallableInsertionTarget() {
override val module: RsMod = item.containingMod
}
/**
* `foo::bar`
* ~~~~~~~~ [path]
* ~~~ returning mod
*/
fun getWritablePathMod(path: RsPath): RsMod? {
if (path.qualifier == null) return path.containingMod
return getWritablePathTarget(path) as? RsMod
}

/**
* Find either a module or an ADT which qualifies the passed path.
* Find either a module, or an ADT which qualifies the passed path.
*/
fun getTargetItemForFunctionCall(path: RsPath): CallableInsertionTarget? {
if (path.qualifier != null) {
return when (val item = getWritablePathTarget(path)) {
is RsMod -> CallableInsertionTarget.Module(item)
is RsStructOrEnumItemElement -> CallableInsertionTarget.Item(item)
else -> null
}
fun getTargetItemForFunctionCall(path: RsPath): RsElement? {
val qualifier = path.qualifier ?: return path.containingMod

if (qualifier.hasCself && !qualifier.hasColonColon) {
val impl = qualifier.reference?.resolve() as? RsImplItem
if (impl != null && impl.isAncestorOf(path)) return impl
}
return CallableInsertionTarget.Module(path.containingMod)
return getWritablePathTarget(path)
}

fun insertStruct(targetModule: RsMod, struct: RsStructItem, sourceFunction: RsElement): RsStructItem {
Expand Down
Expand Up @@ -551,6 +551,46 @@ class CreateFunctionIntentionTest : RsIntentionTestBase(CreateFunctionIntention:
}
""")

fun `test create associated method inside impl`() = doAvailableTest("""
struct S;
impl S {
fn foo() {
Self::bar/*caret*/();
}
}
""", """
struct S;
impl S {
fn foo() {
Self::bar();
}
fn bar() {
todo!()
}
}
""")

fun `test create associated method inside complex impl`() = doAvailableTest("""
struct S;
trait T<A> {}
impl<A> T<A> for (S, A) {
fn foo() {
Self::bar/*caret*/();
}
}
""", """
struct S;
trait T<A> {}
impl<A> T<A> for (S, A) {
fn foo() {
Self::bar();
}
fn bar() {
todo!()
}
}
""")

fun `test unavailable inside method arguments`() = doUnavailableTest("""
struct S;
fn foo(s: S) {
Expand Down