diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/CompletionMemory.kt b/java/java-impl/src/com/intellij/codeInsight/completion/CompletionMemory.kt new file mode 100644 index 000000000000..f0490e8b741a --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/completion/CompletionMemory.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2017 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.completion + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.RangeMarker +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.Segment +import com.intellij.openapi.util.TextRange +import com.intellij.psi.* +import java.util.* + +/** + * @author peter + */ +object CompletionMemory { + private val LAST_CHOSEN_METHODS = Key.create>("COMPLETED_METHODS") + private val CHOSEN_METHODS = Key.create>("CHOSEN_METHODS") + + @JvmStatic + fun registerChosenMethod(method: PsiMethod, call: PsiCall) { + val nameRange = getAnchorRange(call) ?: return + val document = call.containingFile.viewProvider.document ?: return + addToMemory(document, createChosenMethodMarker(document, CompletionUtil.getOriginalOrSelf(method), nameRange)) + } + + private fun getAnchorRange(call: PsiCall) = when (call) { + is PsiMethodCallExpression -> call.methodExpression.referenceNameElement?.textRange + is PsiNewExpression -> call.classOrAnonymousClassReference?.referenceNameElement?.textRange + else -> null + } + + private fun addToMemory(document: Document, marker: RangeMarker) { + val completedMethods = LinkedList() + document.getUserData(LAST_CHOSEN_METHODS)?.let { completedMethods.addAll(it.filter { it.isValid && !haveSameRange(it, marker) }) } + while (completedMethods.size > 10) { + completedMethods.removeAt(0) + } + document.putUserData(LAST_CHOSEN_METHODS, completedMethods) + completedMethods.add(marker) + } + + private fun createChosenMethodMarker(document: Document, method: PsiMethod, nameRange: TextRange): RangeMarker { + val marker = document.createRangeMarker(nameRange.startOffset, nameRange.endOffset) + marker.putUserData(CHOSEN_METHODS, SmartPointerManager.getInstance(method.project).createSmartPsiElementPointer(method)) + return marker + } + + @JvmStatic + fun getChosenMethod(call: PsiCall): PsiMethod? { + val range = getAnchorRange(call) ?: return null + val completedMethods = call.containingFile.viewProvider.document?.getUserData(LAST_CHOSEN_METHODS) + val marker = completedMethods?.let { it.find { m -> haveSameRange(m, range) } } + return marker?.getUserData(CHOSEN_METHODS)?.element + } + + private fun haveSameRange(s1: Segment, s2: Segment) = s1.startOffset == s2.startOffset && s1.endOffset == s2.endOffset + +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java index cdcd0a043d18..a46c9c1e059d 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/ConstructorInsertHandler.java @@ -24,6 +24,7 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; @@ -243,8 +244,17 @@ public class ConstructorInsertHandler implements InsertHandler 0 : hasConstructorParameters(psiClass, place); + RangeMarker refEnd = context.getDocument().createRangeMarker(context.getTailOffset(), context.getTailOffset()); + JavaCompletionUtil.insertParentheses(context, delegate, false, hasParams, forAnonymous); + if (constructor != null) { + PsiCallExpression call = JavaMethodCallElement.findCallAtOffset(context, refEnd.getStartOffset()); + if (call != null) { + CompletionMemory.registerChosenMethod(constructor, call); + } + } + return true; } diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java index 21a494ed31f8..89a08f670a49 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java +++ b/java/java-impl/src/com/intellij/codeInsight/completion/JavaMethodCallElement.java @@ -168,21 +168,29 @@ public class JavaMethodCallElement extends LookupItem implements Type importOrQualify(document, file, method, startOffset); } - final PsiType type = method.getReturnType(); - if (context.getCompletionChar() == '!' && type != null && PsiType.BOOLEAN.isAssignableFrom(type)) { - context.setAddCompletionChar(false); - context.commitDocument(); - final int offset = context.getOffset(refStart); - final PsiMethodCallExpression methodCall = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethodCallExpression.class, false); - if (methodCall != null) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EXCLAMATION_FINISH); - document.insertString(methodCall.getTextRange().getStartOffset(), "!"); - } + PsiCallExpression methodCall = findCallAtOffset(context, context.getOffset(refStart)); + if (methodCall != null) { + CompletionMemory.registerChosenMethod(method, methodCall); + handleNegation(context, document, method, methodCall); } startArgumentLiveTemplate(context, method); } + static PsiCallExpression findCallAtOffset(InsertionContext context, int offset) { + context.commitDocument(); + return PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), offset, PsiCallExpression.class, false); + } + + private static void handleNegation(InsertionContext context, Document document, PsiMethod method, PsiCallExpression methodCall) { + PsiType type = method.getReturnType(); + if (context.getCompletionChar() == '!' && type != null && PsiType.BOOLEAN.isAssignableFrom(type)) { + context.setAddCompletionChar(false); + FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EXCLAMATION_FINISH); + document.insertString(methodCall.getTextRange().getStartOffset(), "!"); + } + } + private void importOrQualify(Document document, PsiFile file, PsiMethod method, int startOffset) { if (willBeImported()) { final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiReferenceExpression.class, false); diff --git a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java index c325f29d9091..64313fe643b9 100644 --- a/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/hint/api/impls/MethodParameterInfoHandler.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.AnnotationTargetUtil; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.CodeInsightSettings; +import com.intellij.codeInsight.completion.CompletionMemory; import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator; @@ -37,7 +38,6 @@ import com.intellij.psi.scope.processor.MethodResolverProcessor; import com.intellij.psi.scope.util.PsiScopesUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.MethodSignatureUtil; -import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -137,7 +137,12 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc Object[] candidates = context.getObjectsToView(); PsiExpression[] args = o.getExpressions(); - PsiElement realResolve = null; + PsiCall call = getCall(o); + PsiElement realResolve = call != null ? call.resolveMethod() : null; + + PsiMethod chosenMethod = CompletionMemory.getChosenMethod(call); + CandidateInfo chosenInfo = null; + CandidateInfo completeMatch = null; for (int i = 0; i < candidates.length; i++) { CandidateInfo candidate = (CandidateInfo)candidates[i]; @@ -202,18 +207,24 @@ public class MethodParameterInfoHandler implements ParameterInfoHandlerWithTabAc } context.setUIComponentEnabled(i, enabled); - if (candidates.length > 1 && - enabled && - parms.length == args.length && - isAssignableParametersBeforeGivenIndex(parms, args, args.length, substitutor)) { - if (realResolve == null) { - PsiCall call = getCall(o); - if (call != null) realResolve = call.resolveMethod(); - if (realResolve == null) realResolve = PsiUtilCore.NULL_PSI_ELEMENT; + if (candidates.length > 1 && enabled) { + if (chosenMethod == method) { + chosenInfo = candidate; + } + + if (parms.length == args.length && realResolve == method && + isAssignableParametersBeforeGivenIndex(parms, args, args.length, substitutor)) { + completeMatch = candidate; } - if (realResolve == PsiUtilCore.NULL_PSI_ELEMENT || realResolve == method) context.setHighlightedParameter(candidate); } } + + if (chosenInfo != null) { + context.setHighlightedParameter(chosenInfo); + } + else if (completeMatch != null) { + context.setHighlightedParameter(completeMatch); + } } private static PsiSubstitutor getCandidateInfoSubstitutor(CandidateInfo candidate) { diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/ParameterInfoTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/ParameterInfoTest.java index 5f7e03c73b47..480c1810787b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/ParameterInfoTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/ParameterInfoTest.java @@ -19,8 +19,12 @@ import com.intellij.JavaTestUtil; import com.intellij.codeInsight.hint.ParameterInfoComponent; import com.intellij.codeInsight.hint.api.impls.AnnotationParameterInfoHandler; import com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; import com.intellij.lang.parameterInfo.CreateParameterInfoContext; import com.intellij.lang.parameterInfo.ParameterInfoUIContextEx; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.infos.MethodCandidateInfo; @@ -29,6 +33,7 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; import com.intellij.testFramework.utils.parameterInfo.MockCreateParameterInfoContext; import com.intellij.testFramework.utils.parameterInfo.MockParameterInfoUIContext; import com.intellij.testFramework.utils.parameterInfo.MockUpdateParameterInfoContext; +import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -82,10 +87,18 @@ public class ParameterInfoTest extends LightCodeInsightFixtureTestCase { assertEquals(2, itemsToShow.length); assertTrue(itemsToShow[0] instanceof MethodCandidateInfo); ParameterInfoComponent.createContext(itemsToShow, getEditor(), handler, -1); + MockUpdateParameterInfoContext updateParameterInfoContext = updateParameterInfo(handler, list, itemsToShow); + assertTrue(updateParameterInfoContext.isUIComponentEnabled(0) || updateParameterInfoContext.isUIComponentEnabled(1)); + } + + @NotNull + private MockUpdateParameterInfoContext updateParameterInfo(MethodParameterInfoHandler handler, + PsiExpressionList list, + Object[] itemsToShow) { MockUpdateParameterInfoContext updateParameterInfoContext = new MockUpdateParameterInfoContext(getEditor(), getFile(), itemsToShow); updateParameterInfoContext.setParameterOwner(list); handler.updateParameterInfo(list, updateParameterInfoContext); - assertTrue(updateParameterInfoContext.isUIComponentEnabled(0) || updateParameterInfoContext.isUIComponentEnabled(1)); + return updateParameterInfoContext; } public void testStopAtAccessibleStaticCorrectCandidate() { @@ -132,20 +145,34 @@ public class ParameterInfoTest extends LightCodeInsightFixtureTestCase { } private String parameterPresentation(int parameterIndex) { + return parameterPresentation(0, parameterIndex); + } + + private String parameterPresentation(int lineIndex, int parameterIndex) { MethodParameterInfoHandler handler = new MethodParameterInfoHandler(); - CreateParameterInfoContext context = new MockCreateParameterInfoContext(getEditor(), getFile()); + CreateParameterInfoContext context = createContext(); PsiExpressionList list = handler.findElementForParameterInfo(context); assertNotNull(list); Object[] itemsToShow = context.getItemsToShow(); assertNotNull(itemsToShow); - assertEquals(1, itemsToShow.length); - assertTrue(itemsToShow[0] instanceof MethodCandidateInfo); - PsiMethod method = ((MethodCandidateInfo)itemsToShow[0]).getElement(); + assertTrue(itemsToShow[lineIndex] instanceof MethodCandidateInfo); + PsiMethod method = ((MethodCandidateInfo)itemsToShow[lineIndex]).getElement(); ParameterInfoUIContextEx parameterContext = ParameterInfoComponent.createContext(itemsToShow, getEditor(), handler, parameterIndex); - PsiSubstitutor substitutor = ((MethodCandidateInfo)itemsToShow[0]).getSubstitutor(); + PsiSubstitutor substitutor = ((MethodCandidateInfo)itemsToShow[lineIndex]).getSubstitutor(); return MethodParameterInfoHandler.updateMethodPresentation(method, substitutor, parameterContext); } + private CreateParameterInfoContext createContext() { + int caretOffset = getEditor().getCaretModel().getOffset(); + PsiExpressionList argList = PsiTreeUtil.findElementOfClassAtOffset(getFile(), caretOffset, PsiExpressionList.class, false); + return new MockCreateParameterInfoContext(getEditor(), getFile()) { + @Override + public int getParameterListStart() { + return argList == null ? caretOffset : argList.getTextRange().getStartOffset(); + } + }; + } + public void testAnnotationWithGenerics() { myFixture.configureByFile(getTestName(false) + ".java"); String text = annoParameterPresentation(); @@ -189,4 +216,57 @@ public class ParameterInfoTest extends LightCodeInsightFixtureTestCase { myFixture.configureByText("a.java", "class C {\n void m(@TA String s) { }\n void t() { m(\"test\"); }\n}"); assertEquals("@TA String s", parameterPresentation(-1)); } + + public void testHighlightMethodJustChosenInCompletion() { + myFixture.configureByText("a.java", "class Foo {" + + "{ bar }" + + "void bar(boolean a);" + + "void bar(String a);" + + "void bar(int a);" + + "void bar2(int a);" + + "}"); + LookupElement[] elements = myFixture.completeBasic(); + assertEquals("(int a)", LookupElementPresentation.renderElement(elements[1]).getTailText()); + myFixture.getLookup().setCurrentItem(elements[1]); + myFixture.type('\n'); + + assertEquals("boolean a", parameterPresentation(0, -1)); + assertEquals("String a", parameterPresentation(1, -1)); + assertEquals("int a", parameterPresentation(2, -1)); + + checkHighlighted(2); + } + + public void testHighlightConstructorJustChosenInCompletion() { + Registry.get("java.completion.show.constructors").setValue(true); + Disposer.register(getTestRootDisposable(), () -> Registry.get("java.completion.show.constructors").setValue(false)); + + myFixture.addClass("class Bar {" + + "Bar(boolean a);" + + "Bar(String a);" + + "Bar(int a);" + + "}; " + + "class Bar2 {}"); + myFixture.configureByText("a.java", "class Foo {{ new Bar }}"); + LookupElement[] elements = myFixture.completeBasic(); + assertEquals("(String a) (default package)", LookupElementPresentation.renderElement(elements[2]).getTailText()); + myFixture.getLookup().setCurrentItem(elements[2]); + myFixture.type('\n'); + myFixture.checkResult("class Foo {{ new Bar() }}"); + + assertEquals("boolean a", parameterPresentation(0, -1)); + assertEquals("String a", parameterPresentation(1, -1)); + assertEquals("int a", parameterPresentation(2, -1)); + + checkHighlighted(1); + } + + private void checkHighlighted(int lineIndex) { + MethodParameterInfoHandler handler = new MethodParameterInfoHandler(); + CreateParameterInfoContext context = createContext(); + PsiExpressionList list = handler.findElementForParameterInfo(context); + Object[] itemsToShow = context.getItemsToShow(); + assertEquals(itemsToShow[lineIndex], updateParameterInfo(handler, list, itemsToShow).getHighlightedParameter()); + } + } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.groovy index 95881d244c99..acbd9ced0b83 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/NormalCompletionOrderingTest.groovy @@ -201,17 +201,20 @@ class NormalCompletionOrderingTest extends CompletionSortingTestCase { void testFqnStats() { myFixture.addClass("public interface Baaaaaaar {}") + myFixture.addClass("package boo; public interface Baaaaaaar {}") myFixture.addClass("package zoo; public interface Baaaaaaar {}") configureSecondCompletion() final LookupImpl lookup = getLookup() - assertEquals("Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.getItems().get(0)).getQualifiedName()) - assertEquals("zoo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.getItems().get(1)).getQualifiedName()) - incUseCount(lookup, 1) + assertEquals("Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[0]).qualifiedName) + assertEquals("boo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[1]).qualifiedName) + assertEquals("zoo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[2]).qualifiedName) + incUseCount(lookup, 2) - assertEquals("zoo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.getItems().get(0)).getQualifiedName()) - assertEquals("Baaaaaaar", ((JavaPsiClassReferenceElement)lookup.getItems().get(1)).getQualifiedName()) + assertEquals("Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[0]).qualifiedName) // same package + assertEquals("zoo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[1]).qualifiedName) + assertEquals("boo.Baaaaaaar", ((JavaPsiClassReferenceElement) lookup.items[2]).qualifiedName) } void testSkipLifted() { @@ -738,4 +741,21 @@ class ContainerUtil extends ContainerUtilRt { assertPreferredItems 0, 'someMethod' } + void testPreferImportedClassesAmongstSameNamed() { + myFixture.addClass('package foo; public class String {}') + + // use foo.String + myFixture.configureByText 'a.java', 'class Foo { Stri }' + myFixture.completeBasic() + myFixture.lookup.currentItem = myFixture.lookupElements.find { it.lookupString == 'String' && LookupElementPresentation.renderElement(it).tailText.contains('foo') } + myFixture.type('\n') + myFixture.checkResult 'import foo.String;\n\nclass Foo { String\n}' + + // assert non-imported String is not preselected when completing in another file + myFixture.configureByText 'b.java', 'class Bar { Stri }' + myFixture.completeBasic() + myFixture.assertPreferredCompletionItems 0, 'String', 'String' + assert LookupElementPresentation.renderElement(myFixture.lookupElements[0]).tailText.contains('java.lang') + } + } diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/IntentionTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/IntentionTest.kt index 1e9d0dfa5020..7c0892fbbd66 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/IntentionTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/inlays/IntentionTest.kt @@ -16,10 +16,33 @@ package com.intellij.codeInsight.daemon.inlays import com.intellij.codeInsight.daemon.impl.ParameterHintsPresentationManager +import com.intellij.codeInsight.hints.settings.ParameterNameHintsSettings +import com.intellij.openapi.editor.ex.EditorSettingsExternalizable import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase import org.assertj.core.api.Assertions.assertThat +import org.jdom.Element class BlackListMethodIntentionTest : LightCodeInsightFixtureTestCase() { + + private var isParamHintsEnabledBefore = false + private lateinit var stateBefore: Element + + override fun setUp() { + super.setUp() + + val settings = EditorSettingsExternalizable.getInstance() + isParamHintsEnabledBefore = settings.isShowParameterNameHints + settings.isShowParameterNameHints = true + + stateBefore = ParameterNameHintsSettings.getInstance().state + } + + override fun tearDown() { + EditorSettingsExternalizable.getInstance().isShowParameterNameHints = isParamHintsEnabledBefore + ParameterNameHintsSettings.getInstance().loadState(stateBefore) + + super.tearDown() + } fun `test add to blacklist by alt enter`() { myFixture.configureByText("a.java", """ diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsWeigher.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsWeigher.java index 02955b43167e..a11bf8939d28 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsWeigher.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/StatisticsWeigher.java @@ -19,7 +19,7 @@ import com.intellij.codeInsight.lookup.Classifier; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Factory; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.psi.statistics.StatisticsInfo; @@ -27,9 +27,8 @@ import com.intellij.psi.statistics.StatisticsManager; import com.intellij.util.ProcessingContext; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.FlatteningIterator; -import gnu.trove.THashSet; -import gnu.trove.TObjectHashingStrategy; +import com.intellij.util.containers.JBIterable; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -81,25 +80,16 @@ public class StatisticsWeigher extends CompletionWeigher { public Iterable classify(@NotNull Iterable source, @NotNull final ProcessingContext context) { checkPrefixChanged(context); - final Collection> byWeight = buildMapByWeight(source).descendingMap().values(); - List initialList = getInitialNoStatElements(source, context); + Iterable rest = withoutInitial(source, initialList); + Collection> byWeight = buildMapByWeight(rest).descendingMap().values(); - //noinspection unchecked - final THashSet initialSet = new THashSet<>(initialList, TObjectHashingStrategy.IDENTITY); - final Condition notInInitialList = element -> !initialSet.contains(element); + return JBIterable.from(initialList).append(JBIterable.from(byWeight).flatten(group -> myNext.classify(group, context))); + } - return ContainerUtil.concat(initialList, new Iterable() { - @Override - public Iterator iterator() { - return new FlatteningIterator, LookupElement>(byWeight.iterator()) { - @Override - protected Iterator createValueIterator(List group) { - return myNext.classify(ContainerUtil.findAll(group, notInInitialList), context).iterator(); - } - }; - } - }); + private static Iterable withoutInitial(Iterable allItems, List initial) { + Set initialSet = ContainerUtil.newIdentityTroveSet(initial); + return JBIterable.from(allItems).filter(element -> !initialSet.contains(element)); } private List getInitialNoStatElements(Iterable source, ProcessingContext context) { @@ -116,18 +106,42 @@ public class StatisticsWeigher extends CompletionWeigher { } private TreeMap> buildMapByWeight(Iterable source) { + MultiMap byName = buildMapByLookupString(source); + + Set> toSort = ContainerUtil.newIdentityTroveSet(); TreeMap> map = new TreeMap<>(); for (LookupElement element : source) { - final int weight = getWeight(element).getScalar(); - List list = map.get(weight); - if (list == null) { - map.put(weight, list = new SmartList<>()); - } + Collection sameNamedGroup = byName.get(element.getLookupString()); + int weight = getMaxWeight(sameNamedGroup); + List list = ContainerUtil.getOrCreate(map, weight, (Factory>)() -> new SmartList<>()); list.add(element); + if (sameNamedGroup.size() > 1 && weight != getScalarWeight(element)) { + toSort.add(list); + } + } + for (List group : toSort) { + Collections.sort(group, Comparator.comparing(this::getScalarWeight).reversed()); } return map; } + @NotNull + private static MultiMap buildMapByLookupString(Iterable elements) { + MultiMap byName = new MultiMap<>(); + for (LookupElement element : elements) { + byName.putValue(element.getLookupString(), element); + } + return byName; + } + + private int getMaxWeight(Collection group) { + return group.stream().mapToInt(this::getScalarWeight).max().orElse(0); + } + + private int getScalarWeight(LookupElement e) { + return getWeight(e).getScalar(); + } + private StatisticsComparable getWeight(LookupElement t) { StatisticsComparable w = myWeights.get(t); if (w == null) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java index 15b9755d8437..128830e0cb17 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/ParameterHintsPassFactory.java @@ -105,12 +105,15 @@ public class ParameterHintsPassFactory extends AbstractProjectComponent implemen if (isDebug) { System.out.println(System.nanoTime() + ": [HintsPass] Traversing started"); + System.out.println(System.nanoTime() + ": [HintsPass] Matchers:"); + blackList.forEach(blackListItem -> System.out.println(" " + blackListItem)); } SyntaxTraverser.psiTraverser(myFile).forEach(element -> process(element, provider, matchers)); if (isDebug) { - System.out.println(System.nanoTime() + ": [HintsPass] Traversing ended"); + System.out.println(System.nanoTime() + ": [HintsPass] Traversing ended, hints to apply:"); + myAnnotations.forEach((i, s) -> System.out.println(" " + i + " " + s)); } } @@ -139,12 +142,15 @@ public class ParameterHintsPassFactory extends AbstractProjectComponent implemen if (info == null || !isMatchedByAny(info, blackListMatchers)) { hints.forEach((h) -> myAnnotations.put(h.getOffset(), h.getText())); } + else if (isDebug) { + System.out.println("Method Info : " + info + " is matched by something"); + } } @Override public void doApplyInformationToEditor() { if (isDebug) { - System.out.println(System.nanoTime() + ": hints addition started"); + System.out.println(System.nanoTime() + ": hints addition started, total hints: " + myAnnotations.size()); } assert myDocument != null; diff --git a/platform/platform-impl/src/com/intellij/diagnostic/LogEventException.java b/platform/platform-impl/src/com/intellij/diagnostic/LogEventException.java index 86678704d98e..000ed3745b55 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/LogEventException.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/LogEventException.java @@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.ExceptionWithAttachments; import com.intellij.openapi.diagnostic.IdeaLoggingEvent; import com.intellij.psi.impl.DebugUtil; +import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; /** @@ -45,7 +46,9 @@ public class LogEventException extends RuntimeException implements ExceptionWith public Attachment[] getAttachments() { Object data = myLogMessage.getData(); if (data instanceof LogMessageEx) { - return ((LogMessageEx)data).getAllAttachments().toArray(Attachment.EMPTY_ARRAY); + Attachment[] attachments = ((LogMessageEx)data).getAllAttachments().toArray(Attachment.EMPTY_ARRAY); + Throwable throwable = myLogMessage.getThrowable(); + return throwable == null ? attachments : ArrayUtil.prepend(new Attachment("details.trace", throwable), attachments); } return Attachment.EMPTY_ARRAY; } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java index b5ab730f5840..622b4ea5f779 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImpl.java @@ -28,9 +28,9 @@ import com.intellij.openapi.command.UndoConfirmationPolicy; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; -import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.ex.DocumentEx; +import com.intellij.openapi.editor.ex.PrioritizedDocumentListener; import com.intellij.openapi.editor.impl.EditorFactoryImpl; import com.intellij.openapi.editor.impl.TrailingSpacesStripper; import com.intellij.openapi.extensions.Extensions; @@ -179,24 +179,31 @@ public class FileDocumentManagerImpl extends FileDocumentManager implements Virt } if (!(file instanceof LightVirtualFile || file.getFileSystem() instanceof NonPhysicalFileSystem)) { - document.addDocumentListener( - new DocumentAdapter() { - @Override - public void documentChanged(DocumentEvent e) { - final Document document = e.getDocument(); - myUnsavedDocuments.add(document); - final Runnable currentCommand = CommandProcessor.getInstance().getCurrentCommand(); - Project project = currentCommand == null ? null : CommandProcessor.getInstance().getCurrentCommandProject(); - String lineSeparator = CodeStyleFacade.getInstance(project).getLineSeparator(); - document.putUserData(LINE_SEPARATOR_KEY, lineSeparator); + document.addDocumentListener(new PrioritizedDocumentListener() { + @Override + public void beforeDocumentChange(DocumentEvent event) { + } - // avoid documents piling up during batch processing - if (areTooManyDocumentsInTheQueue(myUnsavedDocuments)) { - saveAllDocumentsLater(); - } + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public void documentChanged(DocumentEvent e) { + final Document document = e.getDocument(); + myUnsavedDocuments.add(document); + final Runnable currentCommand = CommandProcessor.getInstance().getCurrentCommand(); + Project project = currentCommand == null ? null : CommandProcessor.getInstance().getCurrentCommandProject(); + String lineSeparator = CodeStyleFacade.getInstance(project).getLineSeparator(); + document.putUserData(LINE_SEPARATOR_KEY, lineSeparator); + + // avoid documents piling up during batch processing + if (areTooManyDocumentsInTheQueue(myUnsavedDocuments)) { + saveAllDocumentsLater(); } } - ); + }); } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImplTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImplTest.java index 4675324396c5..8ef609d67b53 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImplTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/fileEditor/impl/FileDocumentManagerImplTest.java @@ -20,6 +20,9 @@ import com.intellij.mock.MockVirtualFile; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter; @@ -45,6 +48,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicInteger; public class FileDocumentManagerImplTest extends PlatformTestCase { private FileDocumentManagerImpl myDocumentManager; @@ -593,4 +597,32 @@ public class FileDocumentManagerImplTest extends PlatformTestCase { FileDocumentManager.getInstance().saveAllDocuments(); } + + public void testDocumentUnsavedInsideChangeListener() throws IOException { + VirtualFile file = createFile("a.txt", "a"); + FileDocumentManager manager = FileDocumentManager.getInstance(); + Document document = manager.getDocument(file); + assertFalse(manager.isDocumentUnsaved(document)); + + AtomicInteger invoked = new AtomicInteger(); + DocumentListener listener = new DocumentListener() { + @Override + public void beforeDocumentChange(DocumentEvent e) { + assertFalse(manager.isDocumentUnsaved(document)); + } + + @Override + public void documentChanged(DocumentEvent event) { + invoked.incrementAndGet(); + assertTrue(manager.isDocumentUnsaved(document)); + } + }; + document.addDocumentListener(listener, getTestRootDisposable()); + EditorFactory.getInstance().getEventMulticaster().addDocumentListener(listener, getTestRootDisposable()); + + WriteCommandAction.runWriteCommandAction(myProject, () -> document.insertString(0, "b")); + + assertTrue(manager.isDocumentUnsaved(document)); + assertEquals(2, invoked.get()); + } } diff --git a/platform/testFramework/src/com/intellij/testFramework/utils/parameterInfo/MockUpdateParameterInfoContext.java b/platform/testFramework/src/com/intellij/testFramework/utils/parameterInfo/MockUpdateParameterInfoContext.java index a1bfed07dc5b..f7483be2b572 100644 --- a/platform/testFramework/src/com/intellij/testFramework/utils/parameterInfo/MockUpdateParameterInfoContext.java +++ b/platform/testFramework/src/com/intellij/testFramework/utils/parameterInfo/MockUpdateParameterInfoContext.java @@ -59,6 +59,10 @@ public class MockUpdateParameterInfoContext implements UpdateParameterInfoContex myHighlightedParameter = parameter; } + public Object getHighlightedParameter() { + return myHighlightedParameter; + } + public void setCurrentParameter(int index) { myCurrentParameter = index; } diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestBase.java b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestBase.java index 33e6698951ff..c6a08d3efd5f 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestBase.java +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/framework/GuiTestBase.java @@ -41,6 +41,7 @@ import org.jdom.xpath.XPath; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.runner.RunWith; @@ -94,9 +95,7 @@ public abstract class GuiTestBase { @Before public void setUp() throws Exception { - if (!canRunGuiTests()) { - return; - } + Assume.assumeTrue(canRunGuiTests()); Application application = ApplicationManager.getApplication(); assertNotNull(application); // verify that we are using the IDE's ClassLoader. diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/TreeInplaceEditor.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/TreeInplaceEditor.java index 2822bc259d47..f50f7c1a8d11 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/TreeInplaceEditor.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/TreeInplaceEditor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -263,7 +263,10 @@ public abstract class TreeInplaceEditor implements AWTEventListener { return; } } - cancelEditing(); + + if (id != MouseEvent.MOUSE_RELEASED) { // do not cancel on release outside of the component + cancelEditing(); + } } @Nullable diff --git a/plugins/git4idea/src/git4idea/GitTaskHandler.java b/plugins/git4idea/src/git4idea/GitTaskHandler.java index 1375ccfa0d68..790d4b1d918b 100644 --- a/plugins/git4idea/src/git4idea/GitTaskHandler.java +++ b/plugins/git4idea/src/git4idea/GitTaskHandler.java @@ -27,6 +27,7 @@ import git4idea.validators.GitRefNameValidator; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -77,23 +78,18 @@ public class GitTaskHandler extends DvcsTaskHandler { @Override protected Iterable getAllBranches(@NotNull GitRepository repository) { GitBranchesCollection branches = repository.getBranches(); - List list = ContainerUtil.map(branches.getLocalBranches(), new Function() { - @Override - public TaskInfo fun(GitBranch branch) { - return new TaskInfo(branch.getName(), Collections.singleton(repository.getPresentableUrl())); - } - }); - list.addAll(ContainerUtil.map(branches.getRemoteBranches(), new Function() { - @Override - public TaskInfo fun(GitBranch branch) { - return new TaskInfo(branch.getName(), Collections.singleton(repository.getPresentableUrl())) { - @Override - public boolean isRemote() { - return true; - } - }; - } - })); + List list = new ArrayList<>(ContainerUtil.map(branches.getLocalBranches(), + (Function)branch -> new TaskInfo(branch.getName(), + Collections.singleton( + repository + .getPresentableUrl())))); + list.addAll(ContainerUtil.map(branches.getRemoteBranches(), + (Function)branch -> new TaskInfo(branch.getName(), Collections.singleton(repository.getPresentableUrl())) { + @Override + public boolean isRemote() { + return true; + } + })); return list; } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java index f18932687d09..078e1d611032 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/settings/GradleExtensionsSettings.java @@ -114,10 +114,6 @@ public class GradleExtensionsSettings implements PersistentStateComponent -This inspection reports any instances of Groovy statements witch attempts to put GString object as a key to map. +This inspection reports any instances of Groovy statements which attempts to put GString object as a key to map. In a general way GString objects are mutable and probably should not be used as keys.

-Also, GString entry could no be accessed with java.lang.String object with same value. Example: +Also, GString entry could not be accessed with java.lang.String object with same value. Example:

     def map = [:]
diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java
index cc5c8e156e8f..6836173a12c3 100644
--- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java
+++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/codeInspection/changeToOperator/ChangeToOperatorInspection.java
@@ -58,9 +58,11 @@ public class ChangeToOperatorInspection extends BaseInspection {
         Transformation transformation = TRANSFORMATIONS.get(methodName);
         if (transformation == null) return;
 
+        PsiElement highlightElement = getHighlightElement(methodCall);
+        if (highlightElement == null) return;
         if (transformation.couldApply(methodCall, getOptions())) {
           registerError(
-            methodCall,
+            highlightElement,
             message("replace.with.operator.message", methodName),
             new LocalQuickFix[]{getFix(transformation, methodName)},
             GENERIC_ERROR_OR_WARNING
@@ -82,7 +84,9 @@ public class ChangeToOperatorInspection extends BaseInspection {
 
       @Override
       protected void doFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) throws IncorrectOperationException {
-        PsiElement call = descriptor.getPsiElement();
+        PsiElement call = descriptor.getPsiElement().getParent();
+        if (call == null) return;
+        call = call.getParent();
         if (!(call instanceof GrMethodCall)) return;
         GrMethodCall methodCall = (GrMethodCall) call;
         GrExpression invokedExpression = methodCall.getInvokedExpression();
@@ -95,6 +99,14 @@ public class ChangeToOperatorInspection extends BaseInspection {
     };
   }
 
+  @Nullable
+  public PsiElement getHighlightElement(@NotNull GrMethodCall methodCall) {
+    GrExpression invokedExpression = methodCall.getInvokedExpression();
+    if (!(invokedExpression instanceof GrReferenceExpression)) return null;
+
+    return  ((GrReferenceExpression)invokedExpression).getReferenceNameElement();
+  }
+
   @Nullable
   public String getMethodName(@NotNull GrMethodCall methodCall) {
     GrExpression invokedExpression = methodCall.getInvokedExpression();
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy
index 943cd00376a1..a41e3a75a1af 100644
--- a/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/inspections/GrChangeToOperatorTest.groovy
@@ -282,25 +282,27 @@ class Operators {
   final String DECLARATIONS = 'def (Operators a, Operators b) = [null, null]\n'
 
   private void doTest(String before, String after = null) {
-    fixture.with {
-      configureByText '_.groovy', "$DECLARATIONS$before"
-      moveCaret()
-      def intentions = filterAvailableIntentions('Replace ')
-      if (after) {
-        assert intentions: before
-        launchAction intentions.first()
-        checkResult "$DECLARATIONS$after"
-      }
-      else {
-        assert !intentions
+    Closeable closeCaret =  {fixture.editor.caretModel.moveToOffset(0)}
+
+    closeCaret.withCloseable {
+      fixture.with {
+        configureByText '_.groovy', "$DECLARATIONS$before"
+        moveCaret()
+        def intentions = filterAvailableIntentions('Replace ')
+        if (after) {
+          assert intentions: before
+          launchAction intentions.first()
+          checkResult "$DECLARATIONS$after"
+        }
+        else {
+          assert !intentions
+        }
       }
     }
   }
 
   private void moveCaret() {
-    def statement =
-
-   (fixture.file as GroovyFile).statements.last()
+    def statement = (fixture.file as GroovyFile).statements.last()
     GrMethodCall call = null
 
     if (statement instanceof GrMethodCall) {
diff --git a/python/psi-api/src/com/jetbrains/python/psi/Property.java b/python/psi-api/src/com/jetbrains/python/psi/Property.java
index 1d3084dc02ef..dcba8ecd2af2 100644
--- a/python/psi-api/src/com/jetbrains/python/psi/Property.java
+++ b/python/psi-api/src/com/jetbrains/python/psi/Property.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -72,5 +72,5 @@ public interface Property {
    * Get the return type of the property getter.
    */
   @Nullable
-  PyType getType(@NotNull TypeEvalContext context);
+  PyType getType(@Nullable PyExpression receiver, @NotNull TypeEvalContext context);
 }
diff --git a/python/src/com/jetbrains/python/inspections/PyStatementEffectInspection.java b/python/src/com/jetbrains/python/inspections/PyStatementEffectInspection.java
index b0d8896c48e8..40f5b676ffda 100644
--- a/python/src/com/jetbrains/python/inspections/PyStatementEffectInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyStatementEffectInspection.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -98,12 +98,15 @@ public class PyStatementEffectInspection extends PyInspection {
         }
       }
       else if (expression instanceof PyBinaryExpression) {
-        PyBinaryExpression binary = (PyBinaryExpression)expression;
+        final PyBinaryExpression binary = (PyBinaryExpression)expression;
+
+        final PyElementType operator = binary.getOperator();
+        if (PyTokenTypes.COMPARISON_OPERATIONS.contains(operator)) return false;
+
         final PyExpression leftExpression = binary.getLeftExpression();
         final PyExpression rightExpression = binary.getRightExpression();
         if (hasEffect(leftExpression) || hasEffect(rightExpression)) return true;
 
-        final PyElementType operator = binary.getOperator();
         String method = operator == null ? null : operator.getSpecialMethodName();
         if (method != null) {
           // maybe the op is overridden and may produce side effects, like cout << "hello"
diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
index 8fd6b672f542..fe1867516cfc 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2016 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -926,7 +926,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla
 
     @Nullable
     @Override
-    public PyType getType(@NotNull TypeEvalContext context) {
+    public PyType getType(@Nullable PyExpression receiver, @NotNull TypeEvalContext context) {
       if (mySite instanceof PyTargetExpressionImpl) {
         final PyType targetDocStringType = ((PyTargetExpressionImpl)mySite).getTypeFromDocString();
         if (targetDocStringType != null) {
@@ -939,7 +939,7 @@ public class PyClassImpl extends PyBaseElementImpl implements PyCla
         if (!(callable instanceof StubBasedPsiElement) && !context.maySwitchToAST(callable)) {
           return null;
         }
-        return context.getReturnType(callable);
+        return callable.getCallType(receiver, Collections.emptyMap(), context);
       }
       return null;
     }
diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
index 5915ff454435..664f08e0c56a 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -289,10 +289,10 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
       Property property = pyClass.findProperty(name, true, context);
       if (property != null) {
         if (classType.isDefinition()) {
-          return Ref.create(PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY));
+          return Ref.create(PyBuiltinCache.getInstance(pyClass).getObjectType(PyNames.PROPERTY));
         }
         if (AccessDirection.of(this) == AccessDirection.READ) {
-          final PyType type = property.getType(context);
+          final PyType type = property.getType(getQualifier(), context);
           if (type != null) {
             return Ref.create(type);
           }
diff --git a/python/testData/inspections/PyStatementEffectInspection/comparison.py b/python/testData/inspections/PyStatementEffectInspection/comparison.py
new file mode 100644
index 000000000000..944e277bea87
--- /dev/null
+++ b/python/testData/inspections/PyStatementEffectInspection/comparison.py
@@ -0,0 +1,3 @@
+status == 1
+status == {}
+status == dict()
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java b/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java
index 4733134eee6b..7c4dd51ae6c1 100644
--- a/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java
+++ b/python/testSrc/com/jetbrains/python/PyClassicPropertyTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -95,7 +95,7 @@ public class PyClassicPropertyTest extends PyTestCase {
     accessor = p.getGetter();
     assertFalse(accessor.isDefined());
 
-    final PyType codeInsightType = p.getType(TypeEvalContext.codeInsightFallback(myClass.getProject()));
+    final PyType codeInsightType = p.getType(null, TypeEvalContext.codeInsightFallback(myClass.getProject()));
     assertNull(codeInsightType);
 
     accessor = p.getSetter();
diff --git a/python/testSrc/com/jetbrains/python/PyTypeTest.java b/python/testSrc/com/jetbrains/python/PyTypeTest.java
index fb5991ef601e..6703a7775b36 100644
--- a/python/testSrc/com/jetbrains/python/PyTypeTest.java
+++ b/python/testSrc/com/jetbrains/python/PyTypeTest.java
@@ -1621,6 +1621,19 @@ public class PyTypeTest extends PyTestCase {
            "    expr = foo");
   }
 
+  // PY-22037
+  public void testAncestorPropertyReturnsSelf() {
+    doTest("Child",
+           "class Master(object):\n" +
+           "    @property\n" +
+           "    def me(self):\n" +
+           "        return self\n" +
+           "class Child(Master):\n" +
+           "    pass\n" +
+           "child = Child()\n" +
+           "expr = child.me");
+  }
+
   private static List getTypeEvalContexts(@NotNull PyExpression element) {
     return ImmutableList.of(TypeEvalContext.codeAnalysis(element.getProject(), element.getContainingFile()).withTracing(),
                             TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()).withTracing());
diff --git a/python/testSrc/com/jetbrains/python/inspections/PyStatementEffectInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyStatementEffectInspectionTest.java
index 2b687f293924..a891256a33ee 100644
--- a/python/testSrc/com/jetbrains/python/inspections/PyStatementEffectInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/PyStatementEffectInspectionTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2015 JetBrains s.r.o.
+ * Copyright 2000-2017 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.
@@ -31,8 +31,12 @@ public class PyStatementEffectInspectionTest extends PyInspectionTestCase {
     doTest(LanguageLevel.PYTHON35);
   }
 
+  public void testComparison() {
+    doTest();
+  }
+
   private void doTest(@NotNull LanguageLevel level) {
-    runWithLanguageLevel(level, () -> doTest());
+    runWithLanguageLevel(level, this::doTest);
   }
 
   @NotNull