Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/main/java/org/apache/groovy/internal/util/Predicate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.groovy.internal.util;

import org.apache.groovy.lang.annotation.Incubating;

/**
* Backport of Java8 Function.
* INTERNAL USE ONLY.
*/
@Incubating
public interface Predicate<T> {
boolean test(T t);
}
165 changes: 156 additions & 9 deletions src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

import antlr.RecognitionException;
import antlr.TokenStreamException;
import groovy.lang.Tuple2;
import groovy.transform.stc.IncorrectTypeHintException;
import org.apache.groovy.internal.util.Predicate;
import org.apache.groovy.util.SystemUtil;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.antlr.AntlrParserPlugin;
Expand Down Expand Up @@ -168,7 +170,7 @@ public static void extractPlaceholders(ClassNode node, Map<GenericsTypeName, Gen
if (parameterized == null || parameterized.length == 0) return;
GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes();
if (redirectGenericsTypes == null ||
(node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */ ) {
(node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */) {
redirectGenericsTypes = parameterized;
}
if (redirectGenericsTypes.length != parameterized.length) {
Expand Down Expand Up @@ -690,14 +692,19 @@ public static GenericsType[] applyGenericsContextToPlaceHolders(Map<String, Clas
* If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode, ClassNode)}
*/
public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) {
return findParameterizedType(genericsClass, actualType, false);
}

// Backported from 3.0.0
private static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) {
if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
return findParameterizedType(genericsClass, actualType);
return findParameterizedType(genericsClass, actualType, tryToFindExactType);
}

SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), new EvictableCache.ValueProvider<ParameterizedTypeCacheKey, SoftReference<ClassNode>>() {
@Override
public SoftReference<ClassNode> provide(ParameterizedTypeCacheKey key) {
return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType()));
return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType));
}
});

Expand All @@ -713,6 +720,11 @@ public SoftReference<ClassNode> provide(ParameterizedTypeCacheKey key) {
* @return the parameterized type
*/
public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType) {
return findParameterizedType(genericsClass, actualType, false);
}

// Backported from 3.0.0
private static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType, boolean tryToFindExactType) {
ClassNode parameterizedType = null;

if (null == genericsClass.getGenericsTypes()) {
Expand All @@ -724,12 +736,18 @@ public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode
List<ClassNode> classNodeList = new LinkedList<>(getAllSuperClassesAndInterfaces(actualType));
classNodeList.add(0, actualType);

LinkedList<ClassNode> parameterizedTypeCandidateList = new LinkedList<>();

for (ClassNode cn : classNodeList) {
if (cn == genericsClass) {
continue;
}

if (!genericsClass.equals(cn.redirect())) {
if (tryToFindExactType && null != cn.getGenericsTypes() && hasNonPlaceHolders(cn)) {
parameterizedTypeCandidateList.add(cn);
}

if (!(genericsClass.equals(cn.redirect()))) {
continue;
}

Expand All @@ -739,9 +757,33 @@ public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode
}
}

if (null == parameterizedType) {
if (!parameterizedTypeCandidateList.isEmpty()) {
parameterizedType = parameterizedTypeCandidateList.getLast();
}
}

return parameterizedType;
}

/**
* Backported from 3.0.0.
*
* Check whether the ClassNode has non generics placeholders, aka not placeholder
*
* @param parameterizedType the class node
* @return the result
* @since 2.5.9
*/
private static boolean hasNonPlaceHolders(ClassNode parameterizedType) {
return checkPlaceHolders(parameterizedType, new Predicate<GenericsType>() {
@Override
public boolean test(GenericsType genericsType) {
return !genericsType.isPlaceholder();
}
});
}

private static boolean isGenericsTypeArraysLengthEqual(GenericsType[] declaringGenericsTypes, GenericsType[] actualGenericsTypes) {
return null != actualGenericsTypes && declaringGenericsTypes.length == actualGenericsTypes.length;
}
Expand Down Expand Up @@ -793,18 +835,123 @@ public static void clearParameterizedTypeCache() {
* so we need actual types: T: String, S: Long
*/
public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver) {
ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver);
return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false).getFirst();
}

/**
* Backported from 3.0.0
*
* The method is similar with {@link GenericsUtils#makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)},
* The main difference is that the method will try to map all placeholders found to the relevant exact types,
* but the other will not try even if the parameterized type has placeholders
*
* @param declaringClass the generics class node declaring the generics types
* @param actualReceiver the sub-class class node
* @return the placeholder-to-actualtype mapping
* @since 2.5.9
*/
public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver) {
List<ClassNode> parameterizedTypeList = new LinkedList<>();

Map<GenericsType, GenericsType> result = makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver, parameterizedTypeList);

return connectGenericsTypes(result);
}

private static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver, List<ClassNode> parameterizedTypeList) {
Tuple2<Map<GenericsType, GenericsType>, ClassNode> resultAndParameterizedTypeTuple = doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true);
ClassNode parameterizedType = resultAndParameterizedTypeTuple.getSecond();
Map<GenericsType, GenericsType> result = resultAndParameterizedTypeTuple.getFirst();

if (hasPlaceHolders(parameterizedType) && !parameterizedTypeList.contains(parameterizedType)) {
parameterizedTypeList.add(parameterizedType);
result.putAll(makeDeclaringAndActualGenericsTypeMapOfExactType(parameterizedType, actualReceiver, parameterizedTypeList));
}

return connectGenericsTypes(result);
}

private static Tuple2<Map<GenericsType, GenericsType>, ClassNode> doMakeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver, boolean tryToFindExactType) {
ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType);

if (null == parameterizedType) {
return new Tuple2<>(Collections.<GenericsType,GenericsType>emptyMap(), parameterizedType);
}

Map<GenericsType, GenericsType> result = new LinkedHashMap<>();

result.putAll(makePlaceholderAndParameterizedTypeMap(declaringClass));
result.putAll(makePlaceholderAndParameterizedTypeMap(parameterizedType));

result = connectGenericsTypes(result);

return new Tuple2<>(result, parameterizedType);
}

private static Map<GenericsType, GenericsType> connectGenericsTypes(Map<GenericsType, GenericsType> genericsTypeMap) {
Map<GenericsType, GenericsType> result = new LinkedHashMap<>();

outer:
for (Map.Entry<GenericsType, GenericsType> entry : genericsTypeMap.entrySet()) {
GenericsType key = entry.getKey();
GenericsType value = entry.getValue();

if (value.isPlaceholder()) {
for (Map.Entry<GenericsType, GenericsType> genericsTypeMapEntry : genericsTypeMap.entrySet()) {
GenericsType genericsTypeMapEntryValue = genericsTypeMapEntry.getValue();
if (!genericsTypeMapEntryValue.isPlaceholder() && (genericsTypeMapEntry.getKey().getName().equals(value.getName()))) {
result.put(key, genericsTypeMapEntryValue); // connected to actual type
continue outer;
}
}
}

result.put(key, value);
}

return result;
}

private static boolean hasPlaceHolders(ClassNode parameterizedType) {
return checkPlaceHolders(parameterizedType, new Predicate<GenericsType>() {
@Override
public boolean test(GenericsType genericsType) {
return genericsType.isPlaceholder();
}
});
}

private static boolean checkPlaceHolders(ClassNode parameterizedType, Predicate<GenericsType> p) {
if (null == parameterizedType) return false;

GenericsType[] genericsTypes = parameterizedType.getGenericsTypes();

if (null == genericsTypes) return false;

for (GenericsType genericsType : genericsTypes) {
if (p.test(genericsType)) {
return true;
}
}

return false;
}

private static Map<GenericsType, GenericsType> makePlaceholderAndParameterizedTypeMap(ClassNode declaringClass) {
if (null == declaringClass) {
return Collections.emptyMap();
}

Map<GenericsType, GenericsType> result = new LinkedHashMap<>();

ClassNode redirectDeclaringClass = declaringClass.redirect();
GenericsType[] declaringGenericsTypes = declaringClass.getGenericsTypes();
GenericsType[] actualGenericsTypes = parameterizedType.getGenericsTypes();
GenericsType[] redirectDeclaringGenericsTypes = redirectDeclaringClass.getGenericsTypes();

Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
for (int i = 0, n = declaringGenericsTypes.length; i < n; i++) {
result.put(declaringGenericsTypes[i], actualGenericsTypes[i]);
if (null != declaringGenericsTypes && null != redirectDeclaringGenericsTypes) {
for (int i = 0, n = declaringGenericsTypes.length; i < n; i++) {
result.put(redirectDeclaringGenericsTypes[i], declaringGenericsTypes[i]);
}
}

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
import static org.codehaus.groovy.ast.tools.GenericsUtils.findActualTypeByGenericsPlaceholderName;
import static org.codehaus.groovy.ast.tools.GenericsUtils.makeDeclaringAndActualGenericsTypeMap;
import static org.codehaus.groovy.ast.tools.GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType;
import static org.codehaus.groovy.ast.tools.GenericsUtils.toGenericTypesString;
import static org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode;
import static org.codehaus.groovy.ast.tools.WideningCategories.isBigDecCategory;
Expand Down Expand Up @@ -654,7 +655,7 @@ public void visitVariableExpression(VariableExpression vexp) {
ClassNode actualType =
findActualTypeByGenericsPlaceholderName(
fieldNode.getOriginType().getUnresolvedName(),
makeDeclaringAndActualGenericsTypeMap(fieldNode.getDeclaringClass(), typeCheckingContext.getEnclosingClassNode())
makeDeclaringAndActualGenericsTypeMapOfExactType(fieldNode.getDeclaringClass(), typeCheckingContext.getEnclosingClassNode())
);

if (null != actualType) {
Expand Down
24 changes: 24 additions & 0 deletions src/test/groovy/bugs/groovy9204/Four.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 groovy.bugs.groovy9204;

import java.util.LinkedList;

public class Four extends Two<LinkedList> {
}
47 changes: 47 additions & 0 deletions src/test/groovy/bugs/groovy9204/Groovy9204.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 groovy.bugs.groovy9204

class Groovy9204 extends GroovyTestCase {
void testGenerics() {
assertScript '''
package groovy.bugs.groovy9204
@groovy.transform.CompileStatic
class ArrayListTest extends Three {
def test() {
field = new ArrayList()
field.add("hello")
field[0]
}
}

@groovy.transform.CompileStatic
class LinkedListTest extends Four {
def test() {
field = new LinkedList()
field.addFirst("hello")
field[0]
}
}

assert new ArrayListTest().test() == 'hello'
assert new LinkedListTest().test() == 'hello'
'''
}
}
25 changes: 25 additions & 0 deletions src/test/groovy/bugs/groovy9204/One.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 groovy.bugs.groovy9204;

import java.util.List;

public class One<T extends List> {
protected T field;
}
Loading