Skip to content

Commit

Permalink
Merge #3792
Browse files Browse the repository at this point in the history
3792: MACRO&RES: delegate references from macro call bodies to expansions r=Undin a=vlad20012

Depends on #3640
Closes #2898

This makes work most of navigations actions inside macro calls
(go-to-declaration, go-to-implementation, go-to-type-declaration, etc)
and also rename (!), find usages, usage highlighting and more.

Quick demo:
![Peek 2019-05-05 18-34](https://user-images.githubusercontent.com/3221931/57196351-d441b700-6f64-11e9-89b6-19034b1dc7db.gif)

This works only if new macro expansion engine is enabled (see #3628 for details).

Co-authored-by: vlad20012 <beskvlad@gmail.com>
  • Loading branch information
bors[bot] and vlad20012 committed Jul 4, 2019
2 parents 1358d88 + b2e0a0c commit cadefbc
Show file tree
Hide file tree
Showing 26 changed files with 357 additions and 63 deletions.
15 changes: 12 additions & 3 deletions src/main/grammars/RustParser.bnf
Expand Up @@ -798,7 +798,7 @@ private ScalarTypeReferenceInner ::= ArrayType
TypeReference ::= TypeReferenceInner {
implements = "org.rust.lang.core.macros.RsExpandedElement"
stubClass = "org.rust.lang.core.stubs.RsPlaceholderStub"
extends = "org.rust.lang.core.psi.ext.RsStubbedElementImpl<?>"
mixin = "org.rust.lang.core.psi.ext.RsTypeReferenceImplMixin"
elementTypeFactory = "org.rust.lang.core.stubs.StubImplementationsKt.factory"
}

Expand Down Expand Up @@ -1303,8 +1303,17 @@ private SpecialExprContextMacroNames ::= "try" | "await" | "dbg" | "format" | "f

private MacroHead ::= AttrsAndVis &(PathWithoutTypes '!') (SpecialMacro | CommonMacro)

MacroArgument ::= <<any_braces CompactTT>>
// `CompactTT` differs from `TT` in that it does not create an additional `TT` nodes for each unpaired token
MacroArgument ::= <<any_braces MacroArgumentTT>>
MacroArgumentTT ::= (<<any_braces MacroArgumentTT>> | MacroBodyIdent | MacroBodyQuoteIdent | <<unpairedToken>>)*
MacroBodyIdent ::= identifier {
implements = "org.rust.lang.core.psi.ext.RsReferenceElementBase"
mixin = "org.rust.lang.core.psi.ext.RsMacroBodyIdentMixin"
}
MacroBodyQuoteIdent ::= QUOTE_IDENTIFIER {
implements = "org.rust.lang.core.psi.ext.RsReferenceElementBase"
mixin = "org.rust.lang.core.psi.ext.RsMacroBodyQuoteIdentMixin"
}

CompactTT ::= (<<any_braces CompactTT>> | <<unpairedToken>>)*

// Used only manually in (external) macro matching code
Expand Down
Expand Up @@ -10,11 +10,9 @@ import com.intellij.codeInsight.TargetElementUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.util.BitUtil
import org.rust.lang.core.macros.findExpansionElements
import org.rust.lang.core.macros.findNavigationTargetIfMacroExpansion
import org.rust.lang.core.psi.RsBinaryExpr
import org.rust.lang.core.psi.RsBinaryOp
import org.rust.lang.core.psi.RsMethodCall
import org.rust.lang.core.psi.RsPath
import org.rust.lang.core.psi.*
import org.rust.lang.core.psi.ext.*
import org.rust.lang.core.resolve.ref.RsReference
import org.rust.lang.core.resolve.ref.deepResolve
Expand Down Expand Up @@ -70,4 +68,23 @@ class RsTargetElementEvaluator : TargetElementEvaluatorEx2() {
*/
override fun getGotoDeclarationTarget(element: PsiElement, navElement: PsiElement?): PsiElement? =
element.findNavigationTargetIfMacroExpansion()

/**
* Used to get parent named element when [element] is a name identifier
*
* Note that if this method returns null, it means "use default logic"
*/
override fun getNamedElement(element: PsiElement): PsiElement? {
// This hack enables some actions (e.g. "find usages") when the [element] is inside a macro
// call and this element expands to name identifier of some named element.
if (element.elementType == RsElementTypes.IDENTIFIER && element.parent is RsMacroBodyIdent) {
val delegate = element.findExpansionElements()?.firstOrNull() ?: return null
val delegateParent = delegate.parent
if (delegateParent is RsNameIdentifierOwner && delegateParent.nameIdentifier == delegate) {
return delegateParent
}
}

return null
}
}
Expand Up @@ -11,15 +11,16 @@ import com.intellij.refactoring.RefactoringActionHandler
import org.rust.ide.refactoring.extractFunction.RsExtractFunctionHandler
import org.rust.ide.refactoring.introduceParameter.RsIntroduceParameterHandler
import org.rust.ide.refactoring.introduceVariable.RsIntroduceVariableHandler
import org.rust.lang.core.macros.isExpandedFromMacro
import org.rust.lang.core.psi.RsPatBinding
import org.rust.lang.core.psi.ext.RsNameIdentifierOwner

class RsRefactoringSupportProvider : RefactoringSupportProvider() {
override fun isInplaceRenameAvailable(element: PsiElement, context: PsiElement?): Boolean =
element is RsPatBinding
element is RsPatBinding && !element.isExpandedFromMacro

override fun isMemberInplaceRenameAvailable(element: PsiElement, context: PsiElement?): Boolean =
element is RsNameIdentifierOwner
element is RsNameIdentifierOwner && !element.isExpandedFromMacro

override fun getIntroduceVariableHandler(): RefactoringActionHandler = RsIntroduceVariableHandler()

Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/org/rust/ide/search/RsUsageTypeProvider.kt
Expand Up @@ -9,6 +9,7 @@ import com.intellij.psi.PsiElement
import com.intellij.usages.UsageTarget
import com.intellij.usages.impl.rules.UsageType
import com.intellij.usages.impl.rules.UsageTypeProviderEx
import org.rust.lang.core.macros.findExpansionElements
import org.rust.lang.core.psi.*

object RsUsageTypeProvider : UsageTypeProviderEx {
Expand Down Expand Up @@ -41,7 +42,8 @@ object RsUsageTypeProvider : UsageTypeProviderEx {
override fun getUsageType(element: PsiElement?): UsageType? = getUsageType(element, UsageTarget.EMPTY_ARRAY)

override fun getUsageType(element: PsiElement?, targets: Array<out UsageTarget>): UsageType? {
val parent = element?.goUp<RsPath>() ?: return null
val refinedElement = element?.findExpansionElements()?.firstOrNull()?.parent ?: element
val parent = refinedElement?.goUp<RsPath>() ?: return null
if (parent is RsBaseType) {
return when (val context = parent.goUp<RsBaseType>()) {
is RsTypeReference -> {
Expand Down
16 changes: 16 additions & 0 deletions src/main/kotlin/org/rust/lang/core/macros/MacroExpansionManager.kt
Expand Up @@ -6,6 +6,7 @@
package org.rust.lang.core.macros

import com.intellij.AppTopics
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.*
import com.intellij.openapi.application.impl.LaterInvocator
Expand All @@ -14,6 +15,8 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileDocumentManagerListener
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.progress.*
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
import com.intellij.openapi.progress.impl.ProgressManagerImpl
Expand Down Expand Up @@ -559,6 +562,10 @@ private class MacroExpansionServiceImplInner(
private fun processChangedMacros(workspaceOnly: Boolean) {
MACRO_LOG.trace("processChangedMacros")
if (!isExpansionModeNew) return

// Fixes inplace rename when the renamed element is referenced from a macro call body
if (isTemplateActiveInAnyEditor()) return

class ProcessModifiedMacrosTask(private val workspaceOnly: Boolean) : MacroExpansionTaskBase(
project,
storage,
Expand All @@ -579,6 +586,15 @@ private class MacroExpansionServiceImplInner(
taskQueue.run(task)
}

private fun isTemplateActiveInAnyEditor(): Boolean {
val tm = TemplateManager.getInstance(project)
for (editor in FileEditorManager.getInstance(project).allEditors) {
if (editor is TextEditor && tm.getActiveTemplate(editor.editor) != null) return true
}

return false
}

fun ensureUpToDate() {
if (!isExpansionModeNew) return
ProgressManager.checkCanceled()
Expand Down
17 changes: 14 additions & 3 deletions src/main/kotlin/org/rust/lang/core/psi/RsPsiImplUtil.kt
Expand Up @@ -5,9 +5,11 @@

package org.rust.lang.core.psi

import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.psi.util.PsiTreeUtil
import org.rust.lang.core.macros.findMacroCallExpandedFrom
import org.rust.lang.core.psi.ext.*

/**
Expand Down Expand Up @@ -36,7 +38,7 @@ object RsPsiImplUtil {
*/
fun getParameterUseScope(element: RsElement): SearchScope? {
val owner = element.contextStrict<RsGenericDeclaration>()
if (owner != null) return LocalSearchScope(owner)
if (owner != null) return localOrMacroSearchScope(owner)

return null
}
Expand Down Expand Up @@ -83,7 +85,7 @@ object RsPsiImplUtil {
is RsForeignModItem -> getTopLevelDeclarationUseScope(element, owner.containingMod, restrictedVis)

// In this case `owner` is function or code block, i.e. it's local scope
else -> LocalSearchScope(owner)
else -> localOrMacroSearchScope(owner)
}
}

Expand All @@ -98,12 +100,21 @@ object RsPsiImplUtil {
is RsVisibility.Restricted -> visibility.inMod
}

if (!restrictedMod.hasChildModules()) return LocalSearchScope(containingMod)
if (!restrictedMod.hasChildModules()) return localOrMacroSearchScope(containingMod)

// TODO restrict scope to [restrictedMod]. We can't use `DirectoryScope` b/c file from any
// directory can be included via `#[path]` attribute.
return null
}

/**
* If the [scope] is inside a macro expansion, we can't use it as a local search scope
* because elements inside the scope can be referenced from the macro call body via
* [org.rust.lang.core.resolve.ref.RsMacroBodyReferenceDelegateImpl]. We use the macro
* call as a search scope in this case
*/
fun localOrMacroSearchScope(scope: PsiElement): LocalSearchScope =
LocalSearchScope(scope.findMacroCallExpandedFrom() ?: scope)
}

private fun RsMod.hasChildModules(): Boolean =
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/org/rust/lang/core/psi/ext/PsiElement.kt
Expand Up @@ -26,6 +26,10 @@ val PsiElement.ancestors: Sequence<PsiElement> get() = generateSequence(this) {
if (it is PsiFile) null else it.parent
}

val PsiElement.contexts: Sequence<PsiElement> get() = generateSequence(this) {
if (it is PsiFile) null else it.context
}

val PsiElement.ancestorPairs: Sequence<Pair<PsiElement, PsiElement>> get() {
val parent = this.parent ?: return emptySequence()
return generateSequence(Pair(this, parent)) { (_, parent) ->
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/rust/lang/core/psi/ext/RsMacroBinding.kt
Expand Up @@ -7,18 +7,18 @@ package org.rust.lang.core.psi.ext

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope
import org.rust.lang.core.psi.RsMacro
import org.rust.lang.core.psi.RsMacroBinding
import org.rust.lang.core.psi.RsPsiImplUtil.localOrMacroSearchScope

abstract class RsMacroBindingImplMixin(node: ASTNode) : RsNamedElementImpl(node), RsMacroBinding {

override fun getNameIdentifier(): PsiElement? = metaVarIdentifier

override fun getUseScope(): SearchScope {
val owner = contextStrict<RsMacro>() ?: error("Macro binding outside of a macro")
return LocalSearchScope(owner)
return localOrMacroSearchScope(owner)
}
}

Expand Down
19 changes: 19 additions & 0 deletions src/main/kotlin/org/rust/lang/core/psi/ext/RsMacroBodyIdent.kt
@@ -0,0 +1,19 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.lang.core.psi.ext

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import org.rust.lang.core.psi.RsMacroBodyIdent
import org.rust.lang.core.resolve.ref.RsMacroBodyReferenceDelegateImpl
import org.rust.lang.core.resolve.ref.RsReference

abstract class RsMacroBodyIdentMixin(node: ASTNode) : RsElementImpl(node), RsMacroBodyIdent {
override val referenceNameElement: PsiElement
get() = identifier

override fun getReference(): RsReference? = RsMacroBodyReferenceDelegateImpl(this)
}
@@ -0,0 +1,19 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.lang.core.psi.ext

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import org.rust.lang.core.psi.RsMacroBodyQuoteIdent
import org.rust.lang.core.resolve.ref.RsMacroBodyReferenceDelegateImpl
import org.rust.lang.core.resolve.ref.RsReference

abstract class RsMacroBodyQuoteIdentMixin(node: ASTNode) : RsElementImpl(node), RsMacroBodyQuoteIdent {
override val referenceNameElement: PsiElement
get() = quoteIdentifier

override fun getReference(): RsReference? = RsMacroBodyReferenceDelegateImpl(this)
}
3 changes: 1 addition & 2 deletions src/main/kotlin/org/rust/lang/core/psi/ext/RsPatBinding.kt
Expand Up @@ -8,7 +8,6 @@ package org.rust.lang.core.psi.ext
import com.intellij.lang.ASTNode
import com.intellij.navigation.ItemPresentation
import com.intellij.psi.PsiElement
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.SearchScope
import com.intellij.psi.util.PsiTreeUtil
import org.rust.ide.icons.RsIcons
Expand Down Expand Up @@ -81,7 +80,7 @@ abstract class RsPatBindingImplMixin(node: ASTNode) : RsNamedElementImpl(node),
RsLambdaExpr::class.java
)

if (owner != null) return LocalSearchScope(owner)
if (owner != null) return RsPsiImplUtil.localOrMacroSearchScope(owner)

return super.getUseScope()
}
Expand Down
15 changes: 13 additions & 2 deletions src/main/kotlin/org/rust/lang/core/psi/ext/RsReferenceElement.kt
Expand Up @@ -9,16 +9,27 @@ import com.intellij.psi.PsiElement
import org.rust.lang.core.psi.unescapedText
import org.rust.lang.core.resolve.ref.RsReference

interface RsWeakReferenceElement : RsElement {

/**
* Provides basic methods for reference implementation ([org.rust.lang.core.resolve.ref.RsReferenceBase]).
* This interface should not be used in any analysis.
*/
interface RsReferenceElementBase : RsElement {
val referenceNameElement: PsiElement?

@JvmDefault
val referenceName: String? get() = referenceNameElement?.unescapedText
}

/**
* Marks an element that optionally can have a reference.
*/
interface RsWeakReferenceElement : RsReferenceElementBase {
override fun getReference(): RsReference?
}

/**
* Marks an element that has a reference.
*/
interface RsReferenceElement : RsWeakReferenceElement {

override val referenceNameElement: PsiElement
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/org/rust/lang/core/psi/ext/RsTypeReference.kt
Expand Up @@ -5,9 +5,23 @@

package org.rust.lang.core.psi.ext

import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.stubs.IStubElementType
import com.intellij.psi.util.PsiTreeUtil
import org.rust.lang.core.macros.RsExpandedElement
import org.rust.lang.core.psi.RsTypeReference
import org.rust.lang.core.stubs.RsPlaceholderStub


val RsTypeReference.typeElement: RsTypeElement?
get() = PsiTreeUtil.getStubChildOfType(this, RsTypeElement::class.java)

abstract class RsTypeReferenceImplMixin : RsStubbedElementImpl<RsPlaceholderStub>, RsTypeReference {

constructor(node: ASTNode) : super(node)

constructor(stub: RsPlaceholderStub, nodeType: IStubElementType<*, *>) : super(stub, nodeType)

override fun getContext(): PsiElement? = RsExpandedElement.getContextImpl(this)
}
Expand Up @@ -584,7 +584,7 @@ fun processLabelResolveVariants(label: RsLabel, processor: RsResolveProcessor):

fun processLifetimeResolveVariants(lifetime: RsLifetime, processor: RsResolveProcessor): Boolean {
if (lifetime.isPredefined) return false
loop@ for (scope in lifetime.ancestors) {
loop@ for (scope in lifetime.contexts) {
val lifetimeParameters = when (scope) {
is RsGenericDeclaration -> scope.lifetimeParameters
is RsWhereClause -> scope.wherePredList.mapNotNull { it.forLifetimes }.flatMap { it.lifetimeParameterList }
Expand Down
@@ -0,0 +1,35 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package org.rust.lang.core.resolve.ref

import com.intellij.psi.PsiElement
import org.rust.lang.core.macros.findExpansionElements
import org.rust.lang.core.psi.ext.RsElement
import org.rust.lang.core.psi.ext.RsReferenceElementBase
import org.rust.lang.core.psi.ext.ancestors

class RsMacroBodyReferenceDelegateImpl(
element: RsReferenceElementBase
) : RsReferenceBase<RsReferenceElementBase>(element) {
override val RsReferenceElementBase.referenceAnchor: PsiElement?
get() = element.referenceNameElement

private val delegates: List<RsReference>
get() {
return element.findExpansionElements()?.mapNotNull { delegated ->
delegated.ancestors
.mapNotNull { it.reference }
.firstOrNull() as? RsReference
}.orEmpty()
}

override fun isReferenceTo(element: PsiElement): Boolean {
return delegates.any { it.isReferenceTo(element) }
}

override fun multiResolve(): List<RsElement> =
delegates.flatMap { it.multiResolve() }.distinct()
}

0 comments on commit cadefbc

Please sign in to comment.