From e362f6d8a39ffecafac355cf205a4062ca3c07c1 Mon Sep 17 00:00:00 2001 From: Maxim Medvedev Date: Sat, 3 Sep 2011 16:56:55 +0400 Subject: [PATCH] ability to infer types of groovy vars in certain place --- .../template/macro/ArrayVariableMacro.java | 2 +- .../macro/IterableComponentTypeMacro.java | 2 + .../template/macro/IterableVariableMacro.java | 2 +- .../template/macro/VariableOfTypeMacro.java | 2 +- .../macro/VariableTypeCalculator.java | 47 +++++++ .../src/META-INF/LangExtensionPoints.xml | 1 + plugins/groovy/src/META-INF/plugin.xml | 1 + .../utils/ControlFlowUtils.java | 5 +- .../lang/psi/impl/TypeInferenceHelper.java | 124 ++++++++++++------ .../GroovyVariableTypeCalculator.java | 39 ++++++ 10 files changed, 178 insertions(+), 47 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/template/macro/VariableTypeCalculator.java create mode 100644 plugins/groovy/src/org/jetbrains/plugins/groovy/template/GroovyVariableTypeCalculator.java diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/ArrayVariableMacro.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/ArrayVariableMacro.java index 0168cf06f714..a68fb005a9e5 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/macro/ArrayVariableMacro.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/ArrayVariableMacro.java @@ -43,7 +43,7 @@ public class ArrayVariableMacro extends VariableTypeMacroBase { PsiElement place = file.findElementAt(offset); PsiVariable[] variables = MacroUtil.getVariablesVisibleAt(place, ""); for (PsiVariable variable : variables) { - PsiType type = variable.getType(); + PsiType type = VariableTypeCalculator.getVarTypeAt(variable, place); if (type instanceof PsiArrayType) { array.add(variable); } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableComponentTypeMacro.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableComponentTypeMacro.java index f4af2d4cd0a7..654c6374807f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableComponentTypeMacro.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableComponentTypeMacro.java @@ -50,6 +50,8 @@ public class IterableComponentTypeMacro implements Macro { PsiExpression expr = MacroUtil.resultToPsiExpression(result, context); if (expr == null) return null; PsiType type = expr.getType(); + + if (type instanceof PsiArrayType) { return new PsiTypeResult(((PsiArrayType)type).getComponentType(), project); } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableVariableMacro.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableVariableMacro.java index 58dbf2f15007..05721dfc5850 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableVariableMacro.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/IterableVariableMacro.java @@ -68,7 +68,7 @@ public class IterableVariableMacro extends VariableTypeMacroBase { final PsiElement parent = var.getParent(); if (parent instanceof PsiForeachStatement && parent == PsiTreeUtil.getParentOfType(place, PsiForeachStatement.class)) continue; - PsiType type = var.getType(); + PsiType type = VariableTypeCalculator.getVarTypeAt(var, place); if (type instanceof PsiArrayType || iterableType.isAssignableFrom(type)) { result.add(var); } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableOfTypeMacro.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableOfTypeMacro.java index e822b6313741..0672a1ea7b4a 100644 --- a/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableOfTypeMacro.java +++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableOfTypeMacro.java @@ -97,7 +97,7 @@ public class VariableOfTypeMacro implements Macro { } } - PsiType type1 = var.getType(); + PsiType type1 = VariableTypeCalculator.getVarTypeAt(var, place); if (type == null || type.isAssignableFrom(type1)) { array.add(var); } diff --git a/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableTypeCalculator.java b/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableTypeCalculator.java new file mode 100644 index 000000000000..106a887bd04d --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/template/macro/VariableTypeCalculator.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.template.macro; + +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiVariable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** +* @author Max Medvedev +*/ +public abstract class VariableTypeCalculator { + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create("com.intellij.variableTypeCalculator"); + + @Nullable + public abstract PsiType inferVarTypeAt(@NotNull PsiVariable var, @NotNull PsiElement place); + + /** + * @return inferred type of variable in the context of place + */ + @NotNull + public static PsiType getVarTypeAt(@NotNull PsiVariable var, @NotNull PsiElement place) { + for (VariableTypeCalculator calculator : EP_NAME.getExtensions()) { + final PsiType type = calculator.inferVarTypeAt(var, place); + if (type != null) return type; + } + + return var.getType(); + } +} diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index c6428f0e6a58..53c99f5ea95d 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -193,6 +193,7 @@ + diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index 19cfd264ae67..e555c1c5e08a 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -193,6 +193,7 @@ + () { @Override public PsiType compute() { - @SuppressWarnings("unchecked") GroovyPsiElement scope = - PsiTreeUtil.getParentOfType(refExpr, GrMethod.class, GrClosableBlock.class, GrClassInitializer.class, GroovyFileBase.class); - if (scope instanceof GrMethod) { - scope = ((GrMethod)scope).getBlock(); - } - else if (scope instanceof GrClassInitializer) { - scope = ((GrClassInitializer)scope).getBlock(); + final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(refExpr); + if (scope == null) return null; + + final Instruction[] flow = scope.getControlFlow(); + ReadWriteVariableInstruction instruction = findInstruction(refExpr, flow); + if (instruction == null) return null; + + if (instruction.isWrite()) { + return getInitializerType(refExpr); } - if (scope != null) { - final Instruction[] flow = ((GrControlFlowOwner)scope).getControlFlow(); - ReadWriteVariableInstruction instruction = findInstruction(refExpr, flow); - if (instruction == null) { - return null; - } - if (instruction.isWrite()) { - return getInitializerType(refExpr); - } - - final Pair>> pair = getDefUseMaps((GrControlFlowOwner)scope); - - final int varIndex = pair.first.getVarIndex(refExpr.getReferenceName()); - final TIntObjectHashMap allDefs = pair.second.get(instruction.num()); - final TIntHashSet varDefs = allDefs.get(varIndex); - if (varDefs != null) { - PsiType result = null; - for (int defIndex : varDefs.toArray()) { - PsiType defType = getDefinitionType(flow[defIndex]); - if (defType != null) { - defType = TypesUtil.boxPrimitiveType(defType, scope.getManager(), scope.getResolveScope()); - result = result == null ? defType : TypesUtil.getLeastUpperBound(result, defType, scope.getManager()); - } - } - return result; - } - } - return null; + return getInferredType(refExpr.getReferenceName(), instruction, flow, scope); } }); } + + @Nullable + public static PsiType getInferredType(@NotNull PsiElement place, String variableName) { + final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(place); + if (scope == null) return null; + + final Instruction[] flow = scope.getControlFlow(); + Instruction instruction = findInstructionAt(place, flow); + if (instruction == null) return null; + + return getInferredType(variableName, instruction, flow, scope); + } + + @Nullable + private static Instruction findInstructionAt(PsiElement place, Instruction[] flow) { + List applicable = new ArrayList(); + for (Instruction instruction : flow) { + final PsiElement element = instruction.getElement(); + if (element == null) continue; + + if (element == place) return instruction; + + if (PsiTreeUtil.isAncestor(element, place, true)) { + applicable.add(instruction); + } + } + if (applicable.size() == 0) return null; + + Collections.sort(applicable, new Comparator() { + @Override + public int compare(Instruction o1, Instruction o2) { + final TextRange t1 = o1.getElement().getTextRange(); + final TextRange t2 = o2.getElement().getTextRange(); + final int s1 = t1.getStartOffset(); + final int s2 = t2.getStartOffset(); + + if (s1 == s2) { + return t1.getEndOffset() - t2.getEndOffset(); + } + return s2 - s1; + } + }); + + return applicable.get(0); + } + + @Nullable + private static PsiType getInferredType(String varName, Instruction instruction, Instruction[] flow, GrControlFlowOwner scope) { + final Pair>> pair = getDefUseMaps(scope); + + final int varIndex = pair.first.getVarIndex(varName); + final TIntObjectHashMap allDefs = pair.second.get(instruction.num()); + final TIntHashSet varDefs = allDefs.get(varIndex); + if (varDefs == null) return null; + + PsiType result = null; + for (int defIndex : varDefs.toArray()) { + PsiType defType = getDefinitionType(flow[defIndex]); + if (defType != null) { + defType = TypesUtil.boxPrimitiveType(defType, scope.getManager(), scope.getResolveScope()); + result = result == null ? defType : TypesUtil.getLeastUpperBound(result, defType, scope.getManager()); + } + } + return result; + } private static Pair>> getDefUseMaps(final GrControlFlowOwner scope) { return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider>>>() { @@ -156,7 +196,7 @@ public class TypeInferenceHelper { @Nullable - public static ReadWriteVariableInstruction findInstruction(final GrReferenceExpression refExpr, final Instruction[] flow) { + private static ReadWriteVariableInstruction findInstruction(final GrReferenceExpression refExpr, final Instruction[] flow) { for (Instruction instruction : flow) { if (instruction instanceof ReadWriteVariableInstruction && instruction.getElement() == refExpr) { return (ReadWriteVariableInstruction)instruction; @@ -164,7 +204,7 @@ public class TypeInferenceHelper { } return null; } - + @Nullable public static PsiType getInitializerType(final PsiElement element) { if (element instanceof GrReferenceExpression && ((GrReferenceExpression) element).getQualifierExpression() == null) { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/template/GroovyVariableTypeCalculator.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/template/GroovyVariableTypeCalculator.java new file mode 100644 index 000000000000..5e45adfa70e8 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/template/GroovyVariableTypeCalculator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2011 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.plugins.groovy.template; + +import com.intellij.codeInsight.template.macro.VariableTypeCalculator; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiType; +import com.intellij.psi.PsiVariable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.GroovyFileType; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; +import org.jetbrains.plugins.groovy.lang.psi.impl.TypeInferenceHelper; + +/** + * @author Max Medvedev + */ +public class GroovyVariableTypeCalculator extends VariableTypeCalculator { + @Override + public PsiType inferVarTypeAt(@NotNull PsiVariable var, @NotNull PsiElement place) { + if (!(var instanceof GrVariable) || !(place.getLanguage() == GroovyFileType.GROOVY_LANGUAGE)) return null; + if (var instanceof GrField) return var.getType(); + + return TypeInferenceHelper.getInferredType(place, var.getName()); + } +}