mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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:
+2
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+9
-16
@@ -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) {
|
||||
|
||||
+18
-11
@@ -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? {
|
||||
|
||||
+11
-34
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+39
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user