[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.
This commit is contained in:
Daniil Ovchinnikov
2019-03-13 10:50:13 +03:00
parent 8584b46bad
commit 3bdea4ec65
8 changed files with 119 additions and 66 deletions
@@ -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
@@ -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;
}
@@ -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) {
@@ -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<GrMethodCall> {
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<GrCallTypeCalculator> = 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? {
@@ -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<GrUnaryExpression> {
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
}
}
@@ -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)
}
}
+1
View File
@@ -156,6 +156,7 @@
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.WithTraitsTypeCalculator"/>
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.DgmCallTypeCalculator"/>
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.DgmIteratorCallTypeCalculator"/>
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.DgmNextPreviousCallTypeCalculator"/>
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.ClosureMethodsCallTypeCalculator"/>
<callTypeCalculator implementation="org.jetbrains.plugins.groovy.lang.typing.ObjectClassTypeCalculator"/>
@@ -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<String, Integer> m) { <caret>org.codehaus.groovy.runtime.DefaultGroovyMethods.iterator(m) }', GrMethodCall,
'java.util.Iterator<java.util.Map.Entry<java.lang.String,java.lang.Integer>>'
}
@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
}
}