bring back whole-method type inference for groovy

This commit is contained in:
peter
2012-09-03 18:59:21 +02:00
parent d50e24dd3a
commit ca49b5342f
4 changed files with 88 additions and 63 deletions
@@ -15,6 +15,7 @@
*/
package org.jetbrains.plugins.groovy.lang.psi.dataFlow;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiIntersectionType;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiType;
@@ -57,6 +58,16 @@ public class DFAType {
Mixin negate() {
return new Mixin(ID, myType, myCondition, !myNegated);
}
@Override
public String toString() {
return "Mixin{" +
"ID=" + ID +
", myType=" + myType +
", myCondition=" + myCondition +
", myNegated=" + myNegated +
'}';
}
}
private final PsiType primary;
@@ -68,6 +79,10 @@ public class DFAType {
}
public void addMixin(@Nullable PsiType mixin, ConditionInstruction instruction) {
if (mixin == null) {
return;
}
mixins.add(new Mixin(mixin, instruction, false));
}
@@ -136,7 +151,7 @@ public class DFAType {
}
private static boolean eq(PsiType t1, PsiType t2) {
return !TypeConversionUtil.erasure(t1).equals(TypeConversionUtil.erasure(t2));
return Comparing.equal(TypeConversionUtil.erasure(t1), TypeConversionUtil.erasure(t2));
}
@Nullable
@@ -153,4 +168,12 @@ public class DFAType {
}
return type;
}
@Override
public String toString() {
return "DFAType{" +
"primary=" + primary +
", mixins=" + mixins +
'}';
}
}
@@ -18,10 +18,8 @@ package org.jetbrains.plugins.groovy.lang.psi.dataFlow.types;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiIntersectionType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.*;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -31,7 +29,10 @@ import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.*;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.InstanceOfInstruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.MixinTypeInstruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ArgumentInstruction;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAEngine;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAType;
@@ -53,9 +54,9 @@ public class TypeInferenceHelper {
private static final Logger LOG = Logger.getInstance(TypeInferenceHelper.class);
private static final ThreadLocal<InferenceContext> ourInferenceContext = new ThreadLocal<InferenceContext>();
private static <T> T doInference(TypeDfaState bindings, Computable<T> computation) {
private static <T> T doInference(Map<String, PsiType> bindings, Computable<T> computation) {
InferenceContext old = ourInferenceContext.get();
ourInferenceContext.set(new InferenceContext.PartialContext(bindings.getBindings()));
ourInferenceContext.set(new InferenceContext.PartialContext(bindings));
try {
return computation.compute();
}
@@ -70,7 +71,7 @@ public class TypeInferenceHelper {
}
@Nullable
public static PsiType getInferredType(@NotNull final GrReferenceExpression refExpr) {
public static PsiType getInferredTypeOld(@NotNull final GrReferenceExpression refExpr) {
return RecursionManager.doPreventingRecursion(refExpr, true, new NullableComputable<PsiType>() {
@Override
public PsiType compute() {
@@ -92,7 +93,7 @@ public class TypeInferenceHelper {
}
@Nullable
public static PsiType getInferredType(@NotNull PsiElement place, String variableName) {
public static PsiType getInferredTypeOld(@NotNull PsiElement place, String variableName) {
final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(place);
if (scope == null) return null;
@@ -106,7 +107,7 @@ public class TypeInferenceHelper {
@Nullable
public static PsiType getInferredTypeNew(@NotNull final GrReferenceExpression refExpr) {
public static PsiType getInferredType(@NotNull final GrReferenceExpression refExpr) {
final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(refExpr);
if (scope == null) return null;
@@ -114,7 +115,7 @@ public class TypeInferenceHelper {
}
@Nullable
public static PsiType getInferredTypeNew(@NotNull PsiElement place, String variableName) {
public static PsiType getInferredType(@NotNull PsiElement place, String variableName) {
final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(place);
if (scope == null) return null;
@@ -344,22 +345,25 @@ public class TypeInferenceHelper {
@Nullable
private static List<Map<String, PsiType>> performTypeDfa(GrControlFlowOwner owner, Instruction[] flow) {
final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner);
final TypeDfaInstance dfaInstance = new TypeDfaInstance(owner, flow);
final TypesSemilattice semilattice = new TypesSemilattice(owner.getManager());
List<TypeDfaState> states = new DFAEngine<TypeDfaState>(flow, dfaInstance, semilattice).performDFAWithTimeout();
return states == null ? null : ContainerUtil.map(states, new Function<TypeDfaState, Map<String, PsiType>>() {
@Override
public Map<String, PsiType> fun(TypeDfaState state) {
return state.getBindings();
}
});
if (states == null) return null;
List<Map<String, PsiType>> result = ContainerUtil.newArrayList();
for (int i = 0; i < states.size(); i++) {
result.add(states.get(i).getBindings(flow[i]));
}
return result;
}
static class TypeDfaInstance implements DfaInstance<TypeDfaState> {
private final PsiElement myScope;
private final GrControlFlowOwner myScope;
private final Instruction[] myFlow;
TypeDfaInstance(PsiElement scope) {
TypeDfaInstance(GrControlFlowOwner scope, Instruction[] flow) {
myScope = scope;
myFlow = flow;
}
public void fun(final TypeDfaState state, final Instruction instruction) {
@@ -371,16 +375,19 @@ public class TypeInferenceHelper {
}
}
private static void handleMixin(final TypeDfaState state, final MixinTypeInstruction instruction) {
private void handleMixin(final TypeDfaState state, final MixinTypeInstruction instruction) {
final String varName = instruction.getVariableName();
state.putType(varName, doInference(state, new Computable<PsiType>() {
if (varName == null) return;
state.putType(varName, doInference(state.getBindings(instruction), new NullableComputable<DFAType>() {
@Override
public PsiType compute() {
PsiType original = state.getBindings().get(varName);
final PsiType mixin = instruction.inferMixinType();
if (mixin == null) return original;
if (original == null) return mixin;
return PsiIntersectionType.createIntersection(original, mixin);
public DFAType compute() {
ReadWriteVariableInstruction originalInstr = instruction.getInstructionToMixin(myFlow);
assert originalInstr != null && !originalInstr.isWrite();
DFAType original = state.getVariableType(varName).negate(originalInstr);
original.addMixin(instruction.inferMixinType(), instruction.getConditionInstruction());
return original;
}
}));
}
@@ -388,10 +395,10 @@ public class TypeInferenceHelper {
private void handleVariableWrite(TypeDfaState state, ReadWriteVariableInstruction instruction) {
final PsiElement element = instruction.getElement();
if (element != null && instruction.isWrite()) {
state.putType(instruction.getVariableName(), doInference(state, new Computable<PsiType>() {
state.putType(instruction.getVariableName(), doInference(state.getBindings(instruction), new Computable<DFAType>() {
@Override
public PsiType compute() {
return TypesUtil.boxPrimitiveType(getInitializerType(element), myScope.getManager(), myScope.getResolveScope());
public DFAType compute() {
return DFAType.create(TypesUtil.boxPrimitiveType(getInitializerType(element), myScope.getManager(), myScope.getResolveScope()));
}
}));
}
@@ -17,12 +17,14 @@ package org.jetbrains.plugins.groovy.lang.psi.dataFlow.types;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAType;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.Semilattice;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
@@ -51,7 +53,7 @@ public class TypesSemilattice implements Semilattice<TypeDfaState> {
}
class TypeDfaState {
private final Map<String, PsiType> myVarTypes;
private final Map<String, DFAType> myVarTypes;
TypeDfaState() {
myVarTypes = ContainerUtil.newHashMap();
@@ -62,13 +64,13 @@ class TypeDfaState {
}
void joinState(TypeDfaState another, PsiManager manager) {
for (Map.Entry<String, PsiType> entry : another.myVarTypes.entrySet()) {
for (Map.Entry<String, DFAType> entry : another.myVarTypes.entrySet()) {
final String name = entry.getKey();
final PsiType t1 = entry.getValue();
final DFAType t1 = entry.getValue();
if (myVarTypes.containsKey(name)) {
final PsiType t2 = myVarTypes.get(name);
final DFAType t2 = myVarTypes.get(name);
if (t1 != null && t2 != null) {
myVarTypes.put(name, TypesUtil.getLeastUpperBound(t1, t2, manager));
myVarTypes.put(name, DFAType.create(t1, t2, manager));
}
else {
myVarTypes.put(name, null);
@@ -78,36 +80,30 @@ class TypeDfaState {
}
boolean contentsEqual(TypeDfaState another) {
if (myVarTypes.size() != another.myVarTypes.size()) {
return false;
}
for (String name : myVarTypes.keySet()) {
if (!areTypesErasureEqual(another.myVarTypes, name)) {
return false;
}
}
return true;
return myVarTypes.equals(another.myVarTypes);
}
private boolean areTypesErasureEqual(Map<String, PsiType> another, String name) {
if (!another.containsKey(name)) return false;
final PsiType t1 = myVarTypes.get(name);
final PsiType t2 = another.get(name);
if (t1 == null || t2 == null) {
if (t1 != null || t2 != null) return false;
}
else {
if (!TypeConversionUtil.erasure(t1).equals(TypeConversionUtil.erasure(t2))) return false;
}
return true;
@NotNull
DFAType getVariableType(String variableName) {
DFAType type = myVarTypes.get(variableName);
return type == null ? DFAType.create(null) : type;
}
Map<String, PsiType> getBindings() {
return myVarTypes;
Map<String, PsiType> getBindings(Instruction instruction) {
HashMap<String,PsiType> map = ContainerUtil.newHashMap();
for (Map.Entry<String, DFAType> entry : myVarTypes.entrySet()) {
DFAType value = entry.getValue();
map.put(entry.getKey(), value == null ? null : value.negate(instruction).getResultType());
}
return map;
}
void putType(String variableName, PsiType type) {
void putType(String variableName, DFAType type) {
myVarTypes.put(variableName, type);
}
@Override
public String toString() {
return "TypeDfaState{" + myVarTypes + '}';
}
}
@@ -179,7 +179,6 @@ def bar(File file) { file.path }
}
public void "test using SSA variables in a for loop"() {
//todo RecursionManager.assertOnRecursionPrevention(testRootDisposable)
def text = """
def foo(List<String> list, SomeClass sc) {
List<String> result
@@ -190,7 +189,7 @@ ${
bar2(s, result, sc)
bar3(foo:s, bar:result, sc)
sc.someMethod(s)
''' * 2
''' * 20
}
}
}