From 3bdea4ec65556b61b0ffb6ac6dbeea8eaf30bce3 Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov Date: Tue, 12 Mar 2019 19:52:53 +0300 Subject: [PATCH] [groovy] fix DGM#next/#previous handling (IDEA-208834) 1. Add DgmNextPreviousCallTypeCalculator which doesn't care how the method was invoked, either via extension method call, or explicit static DGM call, or via --/++. 2. Use binary numeric operators logic in next/previous calculator. 3. Expose mechanism for extracting return type from arbitrary resolve result. 4. Use aforementioned mechanism in DefaultUnaryExpressionTypeCalculator. --- .../expressions/GrUnaryExpression.java | 4 +- .../arithmetic/GrUnaryExpressionImpl.java | 6 +-- .../GrBinaryExpressionUtil.java | 25 ++++------- .../typing/DefaultMethodCallTypeCalculator.kt | 29 +++++++----- .../DefaultUnaryExpressionTypeCalculator.kt | 45 +++++-------------- .../DgmNextPreviousCallTypeCalculator.kt | 39 ++++++++++++++++ plugins/groovy/src/META-INF/plugin.xml | 1 + .../lang/resolve/TypeInsightTest.groovy | 36 +++++++++++++++ 8 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DgmNextPreviousCallTypeCalculator.kt diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrUnaryExpression.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrUnaryExpression.java index 27bbb058afbd..2ea70d414c81 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrUnaryExpression.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrUnaryExpression.java @@ -6,13 +6,13 @@ import com.intellij.psi.PsiType; import com.intellij.psi.tree.IElementType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.groovy.lang.psi.api.GroovyReference; +import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyCallReference; public interface GrUnaryExpression extends GrExpression { @NotNull @Override - GroovyReference getReference(); + GroovyCallReference getReference(); /** * @return type of this expression with regard to whether this expression is prefix or postfix diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/arithmetic/GrUnaryExpressionImpl.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/arithmetic/GrUnaryExpressionImpl.java index 5dcd8fd5b887..b0e713f51258 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/arithmetic/GrUnaryExpressionImpl.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/arithmetic/GrUnaryExpressionImpl.java @@ -9,11 +9,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor; -import org.jetbrains.plugins.groovy.lang.psi.api.GroovyReference; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.GrExpressionImpl; +import org.jetbrains.plugins.groovy.lang.resolve.api.GroovyCallReference; import org.jetbrains.plugins.groovy.lang.resolve.references.GrUnaryOperatorReference; import org.jetbrains.plugins.groovy.lang.typing.GrTypeCalculator; @@ -21,7 +21,7 @@ import java.util.Objects; public class GrUnaryExpressionImpl extends GrExpressionImpl implements GrUnaryExpression { - private final GroovyReference myReference = new GrUnaryOperatorReference(this); + private final GroovyCallReference myReference = new GrUnaryOperatorReference(this); public GrUnaryExpressionImpl(@NotNull ASTNode node) { super(node); @@ -29,7 +29,7 @@ public class GrUnaryExpressionImpl extends GrExpressionImpl implements GrUnaryEx @NotNull @Override - public GroovyReference getReference() { + public GroovyCallReference getReference() { return myReference; } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/binaryCalculators/GrBinaryExpressionUtil.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/binaryCalculators/GrBinaryExpressionUtil.java index 0fdd0d57a828..eff00a5abdf9 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/binaryCalculators/GrBinaryExpressionUtil.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/binaryCalculators/GrBinaryExpressionUtil.java @@ -1,22 +1,10 @@ -/* - * 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. - */ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.binaryCalculators; import com.intellij.psi.CommonClassNames; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiType; +import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrOperatorExpression; import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; @@ -32,9 +20,14 @@ public class GrBinaryExpressionUtil { public static PsiType getDefaultNumericResultType(PsiType ltype, PsiType rtype, GrOperatorExpression e) { int lRank = getTypeRank(ltype); int rRank = getTypeRank(rtype); + return getDefaultNumericResultType(lRank, rRank, e); + } + + @Nullable + public static PsiType getDefaultNumericResultType(int lRank, int rRank, PsiElement context) { int resultRank = getResultTypeRank(lRank, rRank); String fqn = getTypeFqn(resultRank); - return fqn == null ? null : createTypeByFQClassName(fqn, e); + return fqn == null ? null : createTypeByFQClassName(fqn, context); } private static int getResultTypeRank(int lRank, int rRank) { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultMethodCallTypeCalculator.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultMethodCallTypeCalculator.kt index 2e37f64f50f0..1671dd6e9a9a 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultMethodCallTypeCalculator.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultMethodCallTypeCalculator.kt @@ -7,6 +7,7 @@ import com.intellij.psi.* import org.jetbrains.plugins.groovy.lang.psi.api.GroovyMethodResult import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod @@ -17,29 +18,35 @@ import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUt import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil.getSmartReturnType import org.jetbrains.plugins.groovy.lang.resolve.api.Argument +import org.jetbrains.plugins.groovy.lang.resolve.api.Arguments import org.jetbrains.plugins.groovy.lang.resolve.impl.getArguments class DefaultMethodCallTypeCalculator : GrTypeCalculator { override fun getType(expression: GrMethodCall): PsiType? { + val results = expression.multiResolve(false) + if (results.isEmpty()) { + return null + } + val arguments = expression.getArguments() var type: PsiType? = null - for (result in expression.multiResolve(false)) { - type = TypesUtil.getLeastUpperBoundNullable(type, getTypeFromResult(result, expression), expression.manager) + for (result in results) { + type = TypesUtil.getLeastUpperBoundNullable(type, getTypeFromResult(result, arguments, expression), expression.manager) } return type } } -private fun getTypeFromResult(result: GroovyResolveResult, expression: GrMethodCall): PsiType? { - val baseType = getBaseTypeFromResult(result, expression).devoid(expression) ?: return null +fun getTypeFromResult(result: GroovyResolveResult, arguments: Arguments?, context: GrExpression): PsiType? { + val baseType = getBaseTypeFromResult(result, arguments, context).devoid(context) ?: return null val substitutor = if (baseType !is GrLiteralClassType && hasGenerics(baseType)) result.substitutor else PsiSubstitutor.EMPTY - return TypesUtil.substituteAndNormalizeType(baseType, substitutor, result.spreadState, expression) + return TypesUtil.substituteAndNormalizeType(baseType, substitutor, result.spreadState, context) } -private fun getBaseTypeFromResult(result: GroovyResolveResult, expression: GrMethodCall): PsiType? { +private fun getBaseTypeFromResult(result: GroovyResolveResult, arguments: Arguments?, context: PsiElement): PsiType? { return when { - result.isInvokedOnProperty -> getTypeFromPropertyCall(result.element, expression) - result is GroovyMethodResult -> getTypeFromCandidate(result, expression) + result.isInvokedOnProperty -> getTypeFromPropertyCall(result.element, arguments, context) + result is GroovyMethodResult -> getTypeFromCandidate(result, context) else -> null } } @@ -54,7 +61,7 @@ private fun getTypeFromCandidate(result: GroovyMethodResult, context: PsiElement private val ep: ExtensionPointName = ExtensionPointName.create("org.intellij.groovy.callTypeCalculator") -private fun getTypeFromPropertyCall(element: PsiElement?, expression: GrMethodCall): PsiType? { +private fun getTypeFromPropertyCall(element: PsiElement?, arguments: Arguments?, context: PsiElement): PsiType? { val type = when (element) { // TODO introduce property concept, resolve into it and get its type is GrField -> element.typeGroovy is GrMethod -> element.inferredReturnType @@ -66,8 +73,8 @@ private fun getTypeFromPropertyCall(element: PsiElement?, expression: GrMethodCa if (type !is GrClosureType) { return null } - val argumentTypes = expression.getArguments()?.map(Argument::type)?.toTypedArray() - return GrClosureSignatureUtil.getReturnType(type.signatures, argumentTypes, expression) + val argumentTypes = arguments?.map(Argument::type)?.toTypedArray() + return GrClosureSignatureUtil.getReturnType(type.signatures, argumentTypes, context) } fun PsiType?.devoid(context: PsiElement): PsiType? { diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultUnaryExpressionTypeCalculator.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultUnaryExpressionTypeCalculator.kt index 440f5aec5401..6985d249fa44 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultUnaryExpressionTypeCalculator.kt +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DefaultUnaryExpressionTypeCalculator.kt @@ -1,48 +1,25 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.plugins.groovy.lang.typing -import com.intellij.psi.CommonClassNames.JAVA_LANG_NUMBER -import com.intellij.psi.PsiMethod import com.intellij.psi.PsiType -import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryExpression -import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod -import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil -import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil -import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil -import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.isNumericType class DefaultUnaryExpressionTypeCalculator : GrTypeCalculator { override fun getType(expression: GrUnaryExpression): PsiType? { - val resolveResult = PsiImplUtil.extractUniqueResult(expression.reference.multiResolve(false)) - - if (isIncDecNumber(resolveResult)) { - return expression.operand?.type - } - - val substituted = ResolveUtil.extractReturnTypeFromCandidate(resolveResult, expression, PsiType.EMPTY_ARRAY) - if (substituted != null) { - return substituted - } - - val operand = expression.operand ?: return null - val type = operand.type - return if (TypesUtil.isNumericType(type)) type else null + return getTypeFromOperator(expression) + ?: getNumericType(expression) } - //hack for DGM.next(Number):Number - private fun isIncDecNumber(result: GroovyResolveResult): Boolean { - val element = result.element as? PsiMethod ?: return false + private fun getTypeFromOperator(expression: GrUnaryExpression): PsiType? { + val reference = expression.reference + val result = reference.advancedResolve() + return getTypeFromResult(result, reference.arguments, expression) + } - val method = (element as? GrGdkMethod)?.staticMethod ?: element - - val name = method.name - if ("next" != name && "previous" != name) return false - - if (!PsiUtil.isDGMMethod(method)) return false - - val parameter = method.parameterList.parameters.singleOrNull() ?: return false - return parameter.type.equalsToText(JAVA_LANG_NUMBER) + private fun getNumericType(expression: GrUnaryExpression): PsiType? { + val type = expression.operand?.type + return if (isNumericType(type)) type else null } } diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DgmNextPreviousCallTypeCalculator.kt b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DgmNextPreviousCallTypeCalculator.kt new file mode 100644 index 000000000000..3703974a5292 --- /dev/null +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/typing/DgmNextPreviousCallTypeCalculator.kt @@ -0,0 +1,39 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.groovy.lang.typing + +import com.intellij.psi.CommonClassNames +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiType +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypeConstants.INTEGER_RANK +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypeConstants.getTypeRank +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil.isNumericType +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.binaryCalculators.GrBinaryExpressionUtil.getDefaultNumericResultType +import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames.DEFAULT_GROOVY_METHODS +import org.jetbrains.plugins.groovy.lang.resolve.api.Arguments + +class DgmNextPreviousCallTypeCalculator : GrCallTypeCalculator { + + override fun getType(receiver: PsiType?, method: PsiMethod, arguments: Arguments?, context: PsiElement): PsiType? { + if (!isNextPrevious(method)) { + return null + } + val type = arguments?.firstOrNull()?.type + if (!isNumericType(type)) { + return null + } + return getDefaultNumericResultType(getTypeRank(type), INTEGER_RANK, context) + } + + private fun isNextPrevious(method: PsiMethod): Boolean { + val name = method.name + if ("next" != name && "previous" != name) { + return false + } + if (method.containingClass?.qualifiedName != DEFAULT_GROOVY_METHODS) { + return false + } + val parameter = method.parameterList.parameters.singleOrNull() ?: return false + return parameter.type.equalsToText(CommonClassNames.JAVA_LANG_NUMBER) + } +} diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml index a538c0491468..a680f4ed5217 100644 --- a/plugins/groovy/src/META-INF/plugin.xml +++ b/plugins/groovy/src/META-INF/plugin.xml @@ -156,6 +156,7 @@ + diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInsightTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInsightTest.groovy index 9b694a3d34d8..3f24bea83fc3 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInsightTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInsightTest.groovy @@ -7,6 +7,8 @@ import org.jetbrains.plugins.groovy.util.GroovyLatestTest import org.jetbrains.plugins.groovy.util.TypingTest import org.junit.Test +import static com.intellij.psi.CommonClassNames.* + @CompileStatic class TypeInsightTest extends GroovyLatestTest implements TypingTest { @@ -21,4 +23,38 @@ class TypeInsightTest extends GroovyLatestTest implements TypingTest { typingTest 'def usage(Map m) { org.codehaus.groovy.runtime.DefaultGroovyMethods.iterator(m) }', GrMethodCall, 'java.util.Iterator>' } + + @Test + void 'number next previous'() { + expressionTypeTest '1.next()', JAVA_LANG_INTEGER + expressionTypeTest '((byte)1).next()', JAVA_LANG_INTEGER + expressionTypeTest '((short)1).next()', JAVA_LANG_INTEGER + expressionTypeTest '1l.next()', JAVA_LANG_LONG + expressionTypeTest '1f.next()', JAVA_LANG_DOUBLE + expressionTypeTest '1d.next()', JAVA_LANG_DOUBLE + + expressionTypeTest '1.previous()', JAVA_LANG_INTEGER + expressionTypeTest '((byte)1).previous()', JAVA_LANG_INTEGER + expressionTypeTest '((short)1).previous()', JAVA_LANG_INTEGER + expressionTypeTest '1l.previous()', JAVA_LANG_LONG + expressionTypeTest '1f.previous()', JAVA_LANG_DOUBLE + expressionTypeTest '1d.previous()', JAVA_LANG_DOUBLE + } + + @Test + void 'number inc dec'() { + expressionTypeTest '++1', JAVA_LANG_INTEGER + expressionTypeTest '++((byte)1)', JAVA_LANG_INTEGER + expressionTypeTest '++((short)1)', JAVA_LANG_INTEGER + expressionTypeTest '++1l', JAVA_LANG_LONG + expressionTypeTest '++1f', JAVA_LANG_DOUBLE + expressionTypeTest '++1d', JAVA_LANG_DOUBLE + + expressionTypeTest '--1', JAVA_LANG_INTEGER + expressionTypeTest '--((byte)1)', JAVA_LANG_INTEGER + expressionTypeTest '--((short)1)', JAVA_LANG_INTEGER + expressionTypeTest '--1l', JAVA_LANG_LONG + expressionTypeTest '--1f', JAVA_LANG_DOUBLE + expressionTypeTest '--1d', JAVA_LANG_DOUBLE + } }