Skip to content

Commit

Permalink
Fix for #1588 and #1595: update single method context GenericsMapper
Browse files Browse the repository at this point in the history
- do not show type vars in javadoc display
  • Loading branch information
eric-milles committed Sep 30, 2024
1 parent aaf9d90 commit b40cd6a
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ public void testMap18() {
String contents =
"def xxx = ['item'].collectEntries {str -> /*...*/}\n";

assertType(contents, "str", "java.lang.String");
assertType(contents, "xxx", "java.util.Map<java.lang.Object,java.lang.Object>");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2023 the original author or authors.
* Copyright 2009-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,7 +32,6 @@
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.tools.GenericsUtils;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
Expand Down Expand Up @@ -226,32 +225,12 @@ public static GenericsMapper gatherGenerics(final List<ClassNode> argumentTypes,
//--------------------------------------------------------------------------

/** Keeps track of all type parameterization up the type hierarchy. */
private final Deque<Map<String, ClassNode>> allGenerics = new LinkedList<>();
final Deque<Map<String, ClassNode>> allGenerics = new LinkedList<>();

protected boolean hasGenerics() {
return !allGenerics.isEmpty() && !allGenerics.getLast().isEmpty();
}

protected GenericsMapper fillPlaceholders(final GenericsType[] typeParameters) {
for (Map.Entry<String, ClassNode> name2Type : allGenerics.getLast().entrySet()) {
ClassNode type = name2Type.getValue(); if (type.isGenericsPlaceHolder()) {
for (GenericsType tp : typeParameters) {
if (type.getUnresolvedName().equals(tp.getName())) { // unresolved
// replace type parameter "T" with type "Number" or "Object" or whatever its erasure type is
type = type.hasMultiRedirect() ? type.asGenericsType().getUpperBounds()[0] : type.redirect();
if (type.isGenericsPlaceHolder()) type = type.getSuperClass();
// remove nested references to this or other type parameters
if (GenericsUtils.hasUnresolvedGenerics(type))
type = type.getPlainNodeReference();
name2Type.setValue(type);
break;
}
}
}
}
return this;
}

/**
* Finds the type of a parameter name in the highest level of the type hierarchy currently analyzed.
*
Expand Down Expand Up @@ -357,7 +336,7 @@ protected static void saveParameterType(final Map<String, ClassNode> map, final
if (old != null && !old.equals(val) && !old.equals(VariableScope.OBJECT_CLASS_NODE) && weak) {
val = /*WideningCategories.lowestUpperBound(*/old/*, val)*/;
}
map.put(key, val);
map.put(key, GroovyUtils.getWrapperTypeIfPrimitive(val));
}

protected static void tryResolveMethodT(final GenericsType unresolved, final Map<String, ClassNode> resolved, final Parameter[] parameters, final List<ClassNode> argumentTypes) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ protected TypeLookupResult findType(final Expression node, final ClassNode decla
} else if (cexp.isEmptyStringExpression() || VariableScope.STRING_CLASS_NODE.equals(nodeType)) {
return new TypeLookupResult(VariableScope.STRING_CLASS_NODE, null, node, confidence, scope);
} else if (ClassHelper.isNumberType(nodeType) || VariableScope.BIG_DECIMAL_CLASS.equals(nodeType) || VariableScope.BIG_INTEGER_CLASS.equals(nodeType)) {
return new TypeLookupResult(GroovyUtils.getWrapperTypeIfPrimitive(nodeType), null, null, confidence, scope);
return new TypeLookupResult(nodeType, null, null, confidence, scope);
} else {
return new TypeLookupResult(nodeType, null, null, TypeConfidence.UNKNOWN, scope);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1994,7 +1994,7 @@ private void visitUnaryExpression(final Expression node, final Expression operan
ClassNode operandType = primaryTypeStack.removeLast();
// infer the type of the (possibly overloaded) operator
String associatedMethod = findUnaryOperatorName(operation);
if (associatedMethod != null && !operandType.isDerivedFrom(VariableScope.NUMBER_CLASS_NODE)) {
if (associatedMethod != null && !ClassHelper.getWrapper(operandType).isDerivedFrom(VariableScope.NUMBER_CLASS_NODE)) {
scope.setMethodCallArgumentTypes(Collections.emptyList());
TypeLookupResult result = lookupExpressionType(GeneralUtils.constX(associatedMethod), operandType, false, scope);

Expand Down Expand Up @@ -2442,7 +2442,7 @@ private List<ClassNode> getMethodCallArgumentTypes(ASTNode node) {
} else if (expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression()) {
types.add(VariableScope.NULL_TYPE); // sentinel for wildcard matching
} else if (ClassHelper.isNumberType(exprType) || VariableScope.BIG_DECIMAL_CLASS.equals(exprType) || VariableScope.BIG_INTEGER_CLASS.equals(exprType)) {
types.add(GroovyUtils.getWrapperTypeIfPrimitive(exprType));
types.add(exprType);
} else if (expression instanceof GStringExpression || (expression instanceof ConstantExpression && ((ConstantExpression) expression).isEmptyStringExpression())) {
types.add(VariableScope.STRING_CLASS_NODE);
} else if (expression instanceof BooleanExpression || (expression instanceof ConstantExpression && (((ConstantExpression) expression).isTrueExpression() || ((ConstantExpression) expression).isFalseExpression()))) {
Expand Down Expand Up @@ -3219,7 +3219,7 @@ private static Parameter findTargetParameter(final Expression arg, final MethodC
}

/**
* @return the method name associated with this unary operator
* @return the method name associated with the unary operator
*/
private static String findUnaryOperatorName(final String text) {
switch (text.charAt(0)) {
Expand All @@ -3235,8 +3235,6 @@ private static String findUnaryOperatorName(final String text) {
return "negative";
case '~':
return "bitwiseNegate";
case ']':
return "putAt";
}
return null;
}
Expand Down Expand Up @@ -3561,10 +3559,10 @@ private static List<IMember> membersOf(final IType type, final boolean isScript)
return members;
}

private static void resetType(final GenericsType gt, final ClassNode t) {
ClassNode type = ClassHelper.isPrimitiveType(t) ? ClassHelper.getWrapper(t) : t;
gt.setName(type.getName());
gt.setType(type);
private static void resetType(final GenericsType g, final ClassNode c) {
ClassNode type = GroovyUtils.getWrapperTypeIfPrimitive(c);
g.setName(type.getName());
g.setType(type);
}

//--------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;

import groovy.transform.stc.ClosureParams;
Expand All @@ -34,6 +35,7 @@
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
Expand Down Expand Up @@ -202,7 +204,43 @@ public TypeLookupResult resolveTypeParameterization(final ClassNode objExprType,
ClassNode targetType = null;
GenericsMapper mapper;

if (scope.getEnclosingNode() instanceof MethodPointerExpression) {
if (!(scope.getEnclosingNode() instanceof MethodPointerExpression)) {
if (scope.getMethodCallArgumentTypes() != null) argumentTypes.addAll(scope.getMethodCallArgumentTypes());
mapper = GenericsMapper.gatherGenerics(argumentTypes, objectType, method, scope.getMethodCallGenericsTypes());
method = VariableScope.resolveTypeParameterization(mapper, method);

BiFunction<String, ClassNode, ClassNode> finder = mapper::findParameter;
Predicate<GenericsType> unresolved = (tp) -> (finder.apply(tp.getName(), null) == null);

if (scope.getMethodCallGenericsTypes() == null &&
Arrays.stream(GroovyUtils.getGenericsTypes(method)).anyMatch(unresolved)) {
if (testEnclosingAssignment(scope, rhs ->
(rhs instanceof StaticMethodCallExpression && rhs == scope.getCurrentNode()) ||
(rhs instanceof MethodCallExpression && ((MethodCallExpression) rhs).getMethod() == scope.getCurrentNode())
)) {
// maybe the assign target type can help resolve type parameters of method
targetType = scope.getEnclosingAssignment().getLeftExpression().getType();

GenericsMapper gm = GenericsMapper.gatherGenerics(singletonList(targetType), objectType, returnTypeStub(method));
if (gm.hasGenerics()) {
for (GenericsType tp : method.getGenericsTypes()) {
if (unresolved.test(tp) && gm.findParameter(tp.getName(), null) != null) {
mapper.allGenerics.getLast().put(tp.getName(), gm.findParameter(tp.getName(), null));
}
}
}
}

// do not return method type parameters; "def <T> T m()" returns Object if "T" unknown
for (GenericsType tp : method.getGenericsTypes()) {
if (unresolved.test(tp)) {
mapper.allGenerics.getLast().put(tp.getName(), erasure(tp));
}
}

method = VariableScope.resolveTypeParameterization(mapper, (MethodNode) declaration);
}
} else { // method pointer or reference
if (!isStatic && !method.isStatic()) {
// apply type arguments from the object expression to the referenced method
mapper = GenericsMapper.gatherGenerics(argumentTypes, objectType, method);
Expand Down Expand Up @@ -242,49 +280,34 @@ public TypeLookupResult resolveTypeParameterization(final ClassNode objExprType,

mapper = GenericsMapper.gatherGenerics(singletonList(returnType), declaringType, returnTypeStub(method));
method = VariableScope.resolveTypeParameterization(mapper, method);
} else {
if (ClassHelper.isSAMType(targetType)) {
ClassNode[] pt = GenericsUtils.parameterizeSAM(targetType).getV1();
if (isStatic && !method.isStatic()) { // GROOVY-10734, GROOVY-11259
objectType = pt[0]; pt = Arrays.copyOfRange(pt, 1, pt.length);
}
// use parameter types of SAM as "argument types" for referenced method to help resolve type parameters
mapper = GenericsMapper.gatherGenerics(Arrays.asList(pt), objectType, method);
method = VariableScope.resolveTypeParameterization(mapper, method);

mapper = GenericsMapper.gatherGenerics(targetType);
method = VariableScope.resolveTypeParameterization(mapper, method);
} else if (ClassHelper.isSAMType(targetType)) {
ClassNode[] pt = GenericsUtils.parameterizeSAM(targetType).getV1();
if (isStatic && !method.isStatic()) { // GROOVY-10734, GROOVY-11259
objectType = pt[0]; pt = Arrays.copyOfRange(pt, 1, pt.length);
}
// use parameter types of SAM as "argument types" for referenced method to help resolve type parameters
mapper = GenericsMapper.gatherGenerics(Arrays.asList(pt), objectType, method);
method = VariableScope.resolveTypeParameterization(mapper, method);

mapper = GenericsMapper.gatherGenerics(targetType);
method = VariableScope.resolveTypeParameterization(mapper, method);
}
}
} else {
if (scope.getMethodCallArgumentTypes() != null) argumentTypes.addAll(scope.getMethodCallArgumentTypes());
mapper = GenericsMapper.gatherGenerics(argumentTypes, objectType, method, scope.getMethodCallGenericsTypes());
method = VariableScope.resolveTypeParameterization(mapper, method);

if (scope.getMethodCallGenericsTypes() == null && GenericsUtils.hasUnresolvedGenerics(method.getReturnType()) &&
(argumentTypes.size() == (isGroovy ? 1 : 0) || false/*return type placeholder(s) not in parameters*/) &&
testEnclosingAssignment(scope, rhs ->
(rhs instanceof StaticMethodCallExpression && rhs == scope.getCurrentNode()) ||
(rhs instanceof MethodCallExpression && ((MethodCallExpression) rhs).getMethod() == scope.getCurrentNode())
)) {
// maybe the assign target type can help resolve type parameters of method
targetType = scope.getEnclosingAssignment().getLeftExpression().getType();

mapper = GenericsMapper.gatherGenerics(singletonList(targetType), declaringType, returnTypeStub(method));
method = VariableScope.resolveTypeParameterization(mapper, method);
// do not return method type parameters; "def <T> T m()" returns Object if "T" unknown
if (method.getGenericsTypes() != null && GenericsUtils.hasUnresolvedGenerics(method.getReturnType())) {
mapper = GenericsMapper.gatherGenerics(GroovyUtils.getParameterTypes(method.getParameters()), objectType, method);
for (GenericsType tp : method.getGenericsTypes()) {
if (mapper.findParameter(tp.getName(), null) == null) {
mapper.allGenerics.getLast().put(tp.getName(), erasure(tp));
}
}
method = VariableScope.resolveTypeParameterization(mapper, (MethodNode) declaration);
}
}

ClassNode returnType = method.getReturnType();
// do not return method type parameters; "def <T> T m()" returns Object if "T" unknown
if (method.getGenericsTypes() != null && scope.getMethodCallGenericsTypes() == null &&
GenericsUtils.hasUnresolvedGenerics(returnType) && (mapper = GenericsMapper.gatherGenerics(returnType)).hasGenerics()) {
returnType = VariableScope.resolveTypeParameterization(mapper.fillPlaceholders(method.getGenericsTypes()), VariableScope.clone(returnType.redirect()));
}

if (method != declaration || returnType != method.getReturnType()) {
return new TypeLookupResult(returnType, declaringType, method, this);
if (method != declaration) {
return new TypeLookupResult(method.getReturnType(), declaringType, method, this);
}
}
}
Expand All @@ -293,6 +316,21 @@ public TypeLookupResult resolveTypeParameterization(final ClassNode objExprType,

//--------------------------------------------------------------------------

private static ClassNode erasure(GenericsType tp) {
ClassNode cn = tp.getType().redirect();
if (tp.getType().getGenericsTypes() != null) {
tp = tp.getType().getGenericsTypes()[0];
}
if (tp.getUpperBounds() != null) {
cn = tp.getUpperBounds()[0];
}
if (GenericsUtils.hasUnresolvedGenerics(cn)) {
// deal with T extends Comparable<T>
cn = cn.getPlainNodeReference();
}
return cn;
}

private static MethodNode returnTypeStub(final MethodNode node) {
MethodNode stub = new MethodNode("", 0, VariableScope.VOID_CLASS_NODE, new Parameter[] {new Parameter(node.getReturnType(), "")}, null, null);
stub.setDeclaringClass(node.getDeclaringClass());
Expand Down
Loading

0 comments on commit b40cd6a

Please sign in to comment.