Merge branch 'PY-16282'

This commit is contained in:
Ilya.Kazakevich
2015-06-25 16:34:45 +03:00
4 changed files with 119 additions and 64 deletions
@@ -15,6 +15,7 @@
*/
package com.jetbrains.python.psi.types;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
@@ -31,12 +32,6 @@ import java.util.*;
*/
public class TypeEvalContext {
/**
* Contexts are cached here to prevent useless recreations (and cache loss)
*/
@NotNull
private static final TypeEvalContextCache CACHE = new TypeEvalContextCache();
public static class Key {
private static final Key INSTANCE = new Key();
@@ -94,7 +89,7 @@ public class TypeEvalContext {
* the analyzed code was called or may be called. Since this is basically guesswork, the results should be used only for code completion.
*/
public static TypeEvalContext codeCompletion(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(true, true, true, origin));
return getContextFromCache(project, new TypeEvalContext(true, true, true, origin));
}
/**
@@ -105,7 +100,7 @@ public class TypeEvalContext {
* For code completion see {@link TypeEvalContext#codeCompletion(Project, PsiFile)}.
*/
public static TypeEvalContext userInitiated(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(true, true, false, origin));
return getContextFromCache(project, new TypeEvalContext(true, true, false, origin));
}
/**
@@ -115,7 +110,7 @@ public class TypeEvalContext {
* Inspections should not create a new type evaluation context. They should re-use the context of the inspection session.
*/
public static TypeEvalContext codeAnalysis(@NotNull final Project project, @Nullable final PsiFile origin) {
return CACHE.getContext(project, new TypeEvalContext(false, false, false, origin));
return getContextFromCache(project, new TypeEvalContext(false, false, false, origin));
}
/**
@@ -128,7 +123,7 @@ public class TypeEvalContext {
public static TypeEvalContext codeInsightFallback(@Nullable final Project project) {
final TypeEvalContext anchor = new TypeEvalContext(false, false, false, null);
if (project != null) {
return CACHE.getContext(project, anchor);
return getContextFromCache(project, anchor);
}
return anchor;
}
@@ -139,7 +134,20 @@ public class TypeEvalContext {
* Should be used only when normal code insight context is not enough for getting good results.
*/
public static TypeEvalContext deepCodeInsight(@NotNull final Project project) {
return CACHE.getContext(project, new TypeEvalContext(false, true, false, null));
return getContextFromCache(project, new TypeEvalContext(false, true, false, null));
}
/**
* Moves context through cache returning one from cache (if exists).
*
* @param project current project
* @param context context to fetch from cache
* @return context to use
* @see TypeEvalContextCache#getContext(TypeEvalContext)
*/
@NotNull
private static TypeEvalContext getContextFromCache(@NotNull final Project project, @NotNull final TypeEvalContext context) {
return ServiceManager.getService(project, TypeEvalContextCache.class).getContext(context);
}
public TypeEvalContext withTracing() {
@@ -191,7 +199,7 @@ public class TypeEvalContext {
}
}
final PyType type = element.getType(this, Key.INSTANCE);
assertValid(type, element);
assertValid(type, element);
synchronized (myEvaluated) {
myEvaluated.put(element, type);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2015 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.
@@ -15,72 +15,25 @@
*/
package com.jetbrains.python.psi.types;
import com.intellij.openapi.project.Project;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiModificationTracker.SERVICE;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
/**
* Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change.
* Class is thread safe.
* See {@link #getContext(com.intellij.openapi.project.Project, TypeEvalContext)}
*
* Project service to cache {@link TypeEvalContext}
* @author Ilya.Kazakevich
*/
class TypeEvalContextCache {
/**
* Cache itself.
*/
@NotNull
private final Map<TypeEvalConstraints, TypeEvalContext> myCache = new HashMap<TypeEvalConstraints, TypeEvalContext>();
/**
* Current PSI modification count
*/
private long myModificationCount = -1;
/**
* Lock to sync
*/
@NotNull
private final Object myLock = new Object();
public interface TypeEvalContextCache {
/**
* Returns context from cache (if exist) or returns the one you provided (and puts it into cache).
* To use this method, do the following:
* <ol>
* <li>Instantiate {@link com.jetbrains.python.psi.types.TypeEvalContext} you want to use</li>
* <li>Instantiate {@link TypeEvalContext} you want to use</li>
* <li>Pass its instance here as argument</li>
* <li>Use result</li>
* </ol>
*
* @param project project is required for caching engine
* @param standard context you want to use. Just instantiate it and pass here.
* @return context from cache (the one equals by constraints to yours or the one you provided)
*/
@NotNull
TypeEvalContext getContext(@NotNull final Project project, @NotNull final TypeEvalContext standard) {
final PsiModificationTracker tracker = SERVICE.getInstance(project);
synchronized (myLock) {
final long currentCount = tracker.getModificationCount();
if (currentCount == myModificationCount) {
// Cache is valid, use it
final TypeEvalContext valueFromCache = myCache.get(standard.getConstraints());
if (valueFromCache != null) {
// We have element in cache, return it
return valueFromCache;
}
}
else {
// Cache is invalid, flush it and store current count
myCache.clear();
myModificationCount = currentCount;
}
// We do not have value in cache (or cache is invalid), put it
myCache.put(standard.getConstraints(), standard);
return standard;
}
}
TypeEvalContext getContext(@NotNull TypeEvalContext standard);
}
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2014 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.jetbrains.python.psi.types;
import com.intellij.openapi.project.Project;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions.
* Class is thread safe.
* See {@link #getContext(TypeEvalContext)}
*
* @author Ilya.Kazakevich
*/
final class TypeEvalContextCacheImpl implements TypeEvalContextCache {
/**
* Producer to create map to store cache
*/
@NotNull
private static final MapCreator MAP_CREATOR = new MapCreator();
/**
* Lock to sync
*/
@NotNull
private final Object myLock = new Object();
/**
* {@link CachedValue} to store/create map [constraints, context]
*/
private final CachedValue<Map<TypeEvalConstraints, TypeEvalContext>> myCachedMapStorage;
TypeEvalContextCacheImpl(@NotNull final Project project) {
final CachedValuesManager manager = CachedValuesManager.getManager(project);
myCachedMapStorage = manager.createCachedValue(MAP_CREATOR, false);
}
@NotNull
@Override
public TypeEvalContext getContext(@NotNull final TypeEvalContext standard) {
// Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created.
synchronized (myLock) {
final Map<TypeEvalConstraints, TypeEvalContext> map = myCachedMapStorage.getValue();
final TypeEvalContext context = map.get(standard.getConstraints());
if (context != null) {
return context;
}
map.put(standard.getConstraints(), standard);
return standard;
}
}
/**
* Provider that creates map to store cache. Map depends on PSI modification
*/
private static final class MapCreator implements CachedValueProvider<Map<TypeEvalConstraints, TypeEvalContext>> {
@Nullable
@Override
public Result<Map<TypeEvalConstraints, TypeEvalContext>> compute() {
// This method is called if cache is empty. Create new map for it.
final HashMap<TypeEvalConstraints, TypeEvalContext> map = new HashMap<TypeEvalConstraints, TypeEvalContext>();
return new Result<Map<TypeEvalConstraints, TypeEvalContext>>(map, PsiModificationTracker.MODIFICATION_COUNT);
}
}
}
+4
View File
@@ -86,6 +86,10 @@
<projectService serviceInterface="com.jetbrains.python.psi.PyElementGenerator"
serviceImplementation="com.jetbrains.python.psi.impl.PyElementGeneratorImpl"/>
<projectService serviceInterface="com.jetbrains.python.psi.types.TypeEvalContextCache"
serviceImplementation="com.jetbrains.python.psi.types.TypeEvalContextCacheImpl"
/>
<applicationService serviceInterface="com.jetbrains.python.packaging.PyPackageService"
serviceImplementation="com.jetbrains.python.packaging.PyPackageService"/>
<applicationService serviceInterface="com.jetbrains.python.module.PyModuleService"