Open UFOMelkor opened 1 year ago
Like you correctly guessed, the 1 and 2 can be implemented with TypeProvider. Sadly, the way plugin is designed, using TypeProvider is not possible without introducing severe performance issues: https://github.com/klesun/deep-assoc-completion/issues/33#issuecomment-377044882
The 3 and 4 would be good doable improvements though. The heuristic resolution of arrow functions was not polished much. I don't have time to implement those improvements, but I can provide guidance in filing an MR.
The arrow functions are handled somewhere around here: https://github.com/klesun/deep-assoc-completion/blob/274b5054ab65ecd23d51d1ab405c646d2988c589/src/org/klesun/deep_assoc_completion/resolvers/ClosRes.java#L116
I experimented a little with a TypeProvider. I'm neither a Java/Kotlin expert nor have I written a plugin yet; therefore, the result is definitely not ready for any production like usage and might include multiple errors or unwanted side effects. For my current project, the providers helps with all four problems. I'll try to look deeper into it in the next weeks, but in the event that I'm distracted, I put my current code here.
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.jetbrains.php.PhpIndex
import com.jetbrains.php.lang.psi.elements.Function
import com.jetbrains.php.lang.psi.elements.Method
import com.jetbrains.php.lang.psi.elements.MethodReference
import com.jetbrains.php.lang.psi.elements.Parameter;
import com.jetbrains.php.lang.psi.elements.ParameterList
import com.jetbrains.php.lang.psi.elements.PhpExpression
import com.jetbrains.php.lang.psi.elements.PhpNamedElement;
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider4;
import java.util.Collections
class ClosureArgumentsProvider : PhpTypeProvider4 {
override fun getKey(): Char {
return 'Ⅶ';
}
override fun getType(element: PsiElement?): PhpType? {
if (element !is Parameter) {
return null;
}
val params = element.parent;
val func = params.parent;
val expression = func.parent;
val methodReference = expression.parent.parent;
if (params !is ParameterList || func !is Function || ! func.isClosure || expression !is PhpExpression || methodReference !is MethodReference) {
return null;
}
if (element.typeDeclaration != null || element.docTag != null) {
return null;
}
val signature = methodReference.signature;
return PhpType().add("#" + this.key + signature);
}
private fun completeFromElement(method: Method): PhpType? {
val type = PhpType().add(method.getParameter(0)?.type);
val types = type.typesWithParametrisedParts.filter { name -> name != "\\callable" };
val collectedTypes: MutableList<String> = ArrayList();
val callableRegex = Regex("\\\\callable<#ᤓᤓ([^,]+),([^>]+)>");
types.forEach { eachType ->
val match = callableRegex.matchEntire(eachType);
if (match != null) {
collectedTypes.add(match.groupValues.elementAt(1));
} else {
return null;
}
}
if (collectedTypes.isEmpty()) {
return null;
}
val newType = PhpType();
collectedTypes.forEach { each -> newType.add(each) }
return newType;
}
override fun complete(signature: String?, project: Project?): PhpType? {
if (project == null || signature == null) {
return null;
}
if (signature.indexOf("#" + this.key) != 0) {
return null;
}
val phpIndex = PhpIndex.getInstance(project);
val sig = signature.substringAfter(this.key);
try {
val bySignature = phpIndex.getBySignature(sig);
if (bySignature.isEmpty()) {
val elements: List<Collection<PhpNamedElement>> = sig.substringAfter("#" + this.key).split("|").map { each -> phpIndex.getBySignature(each) };
val flatElements: Collection<Method> = elements.flatten().filterIsInstance<Method>();
val result = PhpType();
flatElements.map { each -> this.completeFromElement(each) }.forEach { each -> result.add(each) };
if (result.size() > 0) {
return result;
}
return null;
}
val firstElement = bySignature.elementAt(0);
if (firstElement !is Method) {
return null;
}
return this.completeFromElement(firstElement);
} catch (e: IndexNotReadyException) {
return null;
}
}
override fun getBySignature(
signature: String?,
visited: MutableSet<String>?,
debth: Int,
project: Project?
): MutableCollection<out PhpNamedElement> {
return Collections.emptyList();
}
}
Cool. Enabling TypeProvider can not be merged into the main branch due to reasons I mentioned above, but you can create a fork of the plugin with TypeProvider enabled if you think someone may benefit from it like your project did.
I don't know whether this is out of scope for this plugin, but it is AFAIK currently the only one detecting the type of untyped closure parameters. I add a little script further below as an example.
To show what I am talking about at first, let me show a little screenshot.
The plugin automatically detects that the argument of the closure must be of type
WordBuilder
, provides autocompletion and allows Go To Declaration or Usages.IMHO, four things are missing to complete this feature.
Find usage
. As you can see, only the usages ofok()
are found but not forwithLetter()
->withWord()
is detected,withLetter()
is not.Without deeper knowledge, I would guess most of this would be possible with a TypeProvider.