diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java index 0e16b3ce60..021a95f040 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java @@ -2780,7 +2780,7 @@ public void testTraitBinary() throws Exception { env.addGroovyClass(paths[1], "q", "DefaultNamed", "package q;\n" + - "public class DefaultNamed {\n" + + "class DefaultNamed {\n" + " public String name() { 'name' }\n" + "}\n"); @@ -2844,6 +2844,55 @@ public void testTraitBinary() throws Exception { executeClass(paths[0], "Runner", "new name", ""); } + @Test // https://github.com/groovy/groovy-eclipse/issues/733 + public void testTraitGenerics() throws Exception { + IPath[] paths = createSimpleProject("Project", true); + + env.addGroovyClass(paths[1], "p", "Event", + "package p\n" + + "class Event {\n" + + " Event(String id, T payload) {\n" + + " }\n" + + " Event setReplyTo(Object replyTo) {\n" + + " }\n" + + "}\n"); + + env.addGroovyClass(paths[1], "p", "Events", + "package p\n" + + "@groovy.transform.CompileStatic\n" + + "trait Events {\n" + + " def > Registration> on(Class key, Closure consumer) {\n" + + " }\n" + + "}\n" + + "interface Registration {}\n"); + + env.addGroovyClass(paths[1], "q", "Service", + "package q\n" + + "class Service implements p.Events {\n" + + "}\n"); + + env.addGroovyClass(paths[1], "q", "ServiceWrapper", + "package q\n" + + "class ServiceWrapper {\n" + + " Service service\n" + + "}\n"); + + fullBuild(paths[0]); + expectingNoProblems(); + + // modify the body of the wrapper + env.addGroovyClass(paths[1], "q", "ServiceWrapper", + "package q\n" + + "class ServiceWrapper {\n" + + " Service service\n" + + " def logger\n" + + "}\n"); + + incrementalBuild(paths[0]); + expectingCompiledClasses("q.ServiceWrapper"); + expectingNoProblems(); // not "Inconsistent classfile encountered: The undefined type parameter T is referenced from within Service" + } + @Test // see GroovyCompilationUnitDeclaration#processToPhase(int) public void testTraitGRE1776() throws Exception { IPath[] paths = createSimpleProject("Project", true); @@ -2856,7 +2905,7 @@ public void testTraitGRE1776() throws Exception { env.addGroovyClass(paths[1], "q", "MyClass", "package q\n" + "import p.MyTrait\n" + - "public class MyClass implements MyTrait {}\n"); + "class MyClass implements MyTrait {}\n"); incrementalBuild(paths[0]); expectingCompiledClasses("p.MyTrait", "p.MyTrait$Trait$Helper", "q.MyClass"); diff --git a/base/org.codehaus.groovy24/.checkstyle b/base/org.codehaus.groovy24/.checkstyle index 9ac919f5e0..47a5896dc4 100644 --- a/base/org.codehaus.groovy24/.checkstyle +++ b/base/org.codehaus.groovy24/.checkstyle @@ -35,6 +35,7 @@ + diff --git a/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/ClassNode.java b/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/ClassNode.java index d52d3c7604..3fd565f37c 100644 --- a/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/ClassNode.java +++ b/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/ClassNode.java @@ -187,7 +187,7 @@ public void setHasInconsistentHierarchy(boolean b) { private Map, Set>> transformInstances; // use this to synchronize access for the lazy init - protected Object lazyInitLock = new Object(); + protected final Object lazyInitLock = new Object(); // clazz!=null when resolved protected Class clazz; @@ -199,8 +199,7 @@ public void setHasInconsistentHierarchy(boolean b) { protected ClassNode componentType; // if not null this instance is handled as proxy // for the redirect - // GRECLIPSE private->protected - protected ClassNode redirect; + private ClassNode redirect; // flag if the classes or its members are annotated private boolean annotated; @@ -1311,7 +1310,7 @@ public String toString(boolean showRedirect) { ret.append(">"); } if (redirect != null && showRedirect) { - ret.append(" -> ").append(redirect().toString()); + ret.append(" -> ").append(redirect.toString()); } return ret.toString(); } @@ -1541,6 +1540,17 @@ public boolean isAnnotated() { return this.annotated; } + // GRECLIPSE add + public GenericsType asGenericsType() { + if (!isGenericsPlaceHolder()) { + return new GenericsType(this); + } else { + ClassNode upper = (redirect != null ? redirect : this); + return new GenericsType(this, new ClassNode[]{upper}, null); + } + } + // GRECLIPSE end + public GenericsType[] getGenericsTypes() { return genericsTypes; } diff --git a/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/tools/GenericsUtils.java b/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/tools/GenericsUtils.java new file mode 100644 index 0000000000..36d5a1ef10 --- /dev/null +++ b/base/org.codehaus.groovy24/src/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -0,0 +1,632 @@ +/* + * 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.codehaus.groovy.ast.tools; + +import groovyjarjarantlr.RecognitionException; +import groovyjarjarantlr.TokenStreamException; +import groovy.transform.stc.IncorrectTypeHintException; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.antlr.AntlrParserPlugin; +import org.codehaus.groovy.antlr.parser.GroovyLexer; +import org.codehaus.groovy.antlr.parser.GroovyRecognizer; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.syntax.ParserException; +import org.codehaus.groovy.syntax.Reduction; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; + +/** + * Utility methods to deal with generic types. + * + * @author Cedric Champeau + * @author Paul King + */ +public class GenericsUtils { + public static final GenericsType[] EMPTY_GENERICS_ARRAY = new GenericsType[0]; + public static final String JAVA_LANG_OBJECT = "java.lang.Object"; + + /** + * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a + * class uses generic type
<T,U,V>
(redirectGenericTypes), is used with actual type parameters + *
<java.lang.String, U,V>
, then a class or interface using generic types
<T,V>
+ * will be aligned to
<java.lang.String,V>
+ * @param redirectGenericTypes the type arguments or the redirect class node + * @param parameterizedTypes the actual type arguments used on this class node + * @param alignmentTarget the generic type arguments to which we want to align to + * @return aligned type arguments + * @deprecated You shouldn't call this method because it is inherently unreliable + */ + @Deprecated + public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) { + if (alignmentTarget==null) return EMPTY_GENERICS_ARRAY; + if (parameterizedTypes==null || parameterizedTypes.length==0) return alignmentTarget; + GenericsType[] generics = new GenericsType[alignmentTarget.length]; + for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) { + final GenericsType currentTarget = alignmentTarget[i]; + GenericsType match = null; + if (redirectGenericTypes!=null) { + for (int j = 0; j < redirectGenericTypes.length && match == null; j++) { + GenericsType redirectGenericType = redirectGenericTypes[j]; + if (redirectGenericType.isCompatibleWith(currentTarget.getType())) { + if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) { + // check if there's a potential better match + boolean skip = false; + for (int k=j+1; k extractPlaceholders(ClassNode cn) { + Map ret = new HashMap(); + extractPlaceholders(cn, ret); + return ret; + } + + /** + * For a given classnode, fills in the supplied map with the parameterized + * types it defines. + * @param node + * @param map + */ + public static void extractPlaceholders(ClassNode node, Map map) { + if (node == null) return; + + if (node.isArray()) { + extractPlaceholders(node.getComponentType(), map); + return; + } + + if (!node.isUsingGenerics() || !node.isRedirectNode()) return; + GenericsType[] parameterized = node.getGenericsTypes(); + if (parameterized == null || parameterized.length == 0) return; + GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes(); + if (redirectGenericsTypes==null) redirectGenericsTypes = parameterized; + for (int i = 0; i < redirectGenericsTypes.length; i++) { + GenericsType redirectType = redirectGenericsTypes[i]; + if (redirectType.isPlaceholder()) { + String name = redirectType.getName(); + if (!map.containsKey(name)) { + GenericsType value = parameterized[i]; + map.put(name, value); + if (value.isWildcard()) { + ClassNode lowerBound = value.getLowerBound(); + if (lowerBound!=null) { + extractPlaceholders(lowerBound, map); + } + ClassNode[] upperBounds = value.getUpperBounds(); + if (upperBounds!=null) { + for (ClassNode upperBound : upperBounds) { + extractPlaceholders(upperBound, map); + } + } + } else if (!value.isPlaceholder()) { + extractPlaceholders(value.getType(), map); + } + } + } + } + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + * @deprecated Use #parameterizeType instead + */ + @Deprecated + public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) { + return parameterizeType(hint, target); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + */ + public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) { + if (hint.isArray()) { + if (target.isArray()) { + return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); + } + return target; + } + if (!target.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { + ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); + if (!hint.equals(nextSuperClass)) { + Map genericsSpec = createGenericsSpec(hint); + extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); + ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); + return parameterizeType(result, target); + } + } + Map genericsSpec = createGenericsSpec(hint); + ClassNode targetRedirect = target.redirect(); + genericsSpec = createGenericsSpec(targetRedirect, genericsSpec); + extractSuperClassGenerics(hint, targetRedirect, genericsSpec); + return correctToGenericsSpecRecurse(genericsSpec, targetRedirect); + + } + + public static ClassNode nonGeneric(ClassNode type) { + if (type.isUsingGenerics()) { + final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName()); + nonGen.setRedirect(type); + nonGen.setGenericsTypes(null); + nonGen.setUsingGenerics(false); + return nonGen; + } + if (type.isArray() && type.getComponentType().isUsingGenerics()) { + return type.getComponentType().getPlainNodeReference().makeArray(); + } + return type; + } + + public static ClassNode newClass(ClassNode type) { + return type.getPlainNodeReference(); + } + + public static ClassNode makeClassSafe(Class klass) { + return makeClassSafeWithGenerics(ClassHelper.make(klass)); + } + + public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) { + GenericsType[] genericsTypes = new GenericsType[1]; + genericsTypes[0] = new GenericsType(genericsType); + return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes); + } + + public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) { + ClassNode plainNodeReference = newClass(type); + if (genericTypes != null && genericTypes.length > 0) { + plainNodeReference.setGenericsTypes(genericTypes); + if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true); + } + return plainNodeReference; + } + + public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) { + if (type.isArray()) { + return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray(); + } + GenericsType[] gtypes = new GenericsType[0]; + if (genericTypes != null) { + gtypes = new GenericsType[genericTypes.length]; + System.arraycopy(genericTypes, 0, gtypes, 0, gtypes.length); + } + return makeClassSafe0(type, gtypes); + } + + public static MethodNode correctToGenericsSpec(Map genericsSpec, MethodNode mn) { + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType()); + Parameter[] origParameters = mn.getParameters(); + Parameter[] newParameters = new Parameter[origParameters.length]; + for (int i = 0; i < origParameters.length; i++) { + Parameter origParameter = origParameters[i]; + newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, origParameter.getType()), origParameter.getName(), origParameter.getInitialExpression()); + } + return new MethodNode(mn.getName(), mn.getModifiers(), correctedType, newParameters, mn.getExceptions(), mn.getCode()); + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type) { + return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList()); + } + + /** + * @since 2.4.1 + */ + public static ClassNode[] correctToGenericsSpecRecurse(Map genericsSpec, ClassNode[] types) { + if (types==null || types.length==1) return types; + ClassNode[] newTypes = new ClassNode[types.length]; + boolean modified = false; + for (int i=0; i()); + modified = modified || (types[i]!=newTypes[i]); + } + if (!modified) return types; + return newTypes; + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type, List exclusions) { + if (type.isArray()) { + return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); + } + if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + if (type != null && type.isGenericsPlaceHolder() && type.getGenericsTypes() == null) { + ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); + placeholder.setGenericsPlaceHolder(true); + type = makeClassSafeWithGenerics(type, new GenericsType(placeholder)); + } + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + GenericsType[] oldgTypes = type.getGenericsTypes(); + GenericsType[] newgTypes = GenericsType.EMPTY_ARRAY; + if (oldgTypes != null) { + newgTypes = new GenericsType[oldgTypes.length]; + for (int i = 0; i < newgTypes.length; i++) { + GenericsType oldgType = oldgTypes[i]; + if (oldgType.isPlaceholder() ) { + if (genericsSpec.get(oldgType.getName())!=null) { + newgTypes[i] = new GenericsType(genericsSpec.get(oldgType.getName())); + } else { + newgTypes[i] = new GenericsType(ClassHelper.OBJECT_TYPE); + } + } else if (oldgType.isWildcard()) { + ClassNode oldLower = oldgType.getLowerBound(); + ClassNode lower = oldLower!=null?correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions):null; + ClassNode[] oldUpper = oldgType.getUpperBounds(); + ClassNode[] upper = null; + if (oldUpper!=null) { + upper = new ClassNode[oldUpper.length]; + for (int j = 0; j < oldUpper.length; j++) { + upper[j] = correctToGenericsSpecRecurse(genericsSpec,oldUpper[j], exclusions); + } + } + GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); + fixed.setName(oldgType.getName()); + fixed.setWildcard(true); + newgTypes[i] = fixed; + } else { + newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec,correctToGenericsSpec(genericsSpec, oldgType), exclusions)); + } + } + } + return makeClassSafeWithGenerics(type, newgTypes); + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, GenericsType type) { + ClassNode ret = null; + if (type.isPlaceholder()) { + String name = type.getName(); + ret = genericsSpec.get(name); + } + if (ret == null) ret = type.getType(); + return ret; + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, ClassNode type) { + if (type.isArray()) { + return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); + } + if (type.isGenericsPlaceHolder()) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + return type; + } + + public static Map createGenericsSpec(ClassNode current) { + return createGenericsSpec(current, Collections.EMPTY_MAP); + } + + public static Map createGenericsSpec(ClassNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret contains the type specs, what we now need is the type spec for the + // current class. To get that we first apply the type parameters to the + // current class and then use the type names of the current class to reset + // the map. Example: + // class A{} + // class B extends A {} + // first we have: T->Number + // we apply it to A -> A + // resulting in: V->Number,W->Long,X->String + + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + ClassNode[] spec = new ClassNode[sgts.length]; + for (int i = 0; i < spec.length; i++) { + spec[i] = correctToGenericsSpec(ret, sgts[i]); + } + GenericsType[] newGts = current.redirect().getGenericsTypes(); + if (newGts == null) return ret; + ret.clear(); + for (int i = 0; i < spec.length; i++) { + ret.put(newGts[i].getName(), spec[i]); + } + } + return ret; + } + + public static Map addMethodGenerics(MethodNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret starts with the original type specs, now add gts for the current method if any + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + for (GenericsType sgt : sgts) { + ret.put(sgt.getName(), sgt.getType()); + } + } + return ret; + } + + public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map spec) { + // TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections, + // but operates on ClassNodes instead of GenericsType + if (target == null || type == target) return; + if (type.isArray() && target.isArray()) { + extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); + } else if (type.isArray() && JAVA_LANG_OBJECT.equals(target.getName())) { + // Object is superclass of arrays but no generics involved + } else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { + // structural match route + if (target.isGenericsPlaceHolder()) { + spec.put(target.getGenericsTypes()[0].getName(), type); + } else { + extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); + } + } else { + // have first to find matching super class or interface + ClassNode superClass = getSuperClass(type, target); + + if (superClass != null) { + ClassNode corrected = getCorrectedClassNode(type, superClass, false); + extractSuperClassGenerics(corrected, target, spec); + } else { + // if we reach here, we have an unhandled case + throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this."); + } + } + } + + public static ClassNode getSuperClass(ClassNode type, ClassNode target) { + ClassNode superClass = ClassHelper.getNextSuperClass(type, target); + + if (superClass == null) { + if (ClassHelper.isPrimitiveType(type)) { + superClass = ClassHelper.getNextSuperClass(ClassHelper.getWrapper(type), target); + } + } + + return superClass; + } + + private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map spec) { + // if declaration does not provide generics, there is no connection to make + if (usage==null || declaration==null || declaration.length==0) return; + if (usage.length!=declaration.length) return; + + // both have generics + for (int i=0; i spec) { + if (usage==null || declaration==null || declaration.length==0) return; + // both have generics + for (int i=0; i")); + final GroovyRecognizer rn = GroovyRecognizer.make(lexer); + try { + rn.classOrInterfaceType(true); + final AtomicReference ref = new AtomicReference(); + AntlrParserPlugin plugin = new AntlrParserPlugin() { + @Override + public ModuleNode buildAST(final SourceUnit sourceUnit, final ClassLoader classLoader, final Reduction cst) throws ParserException { + ref.set(makeTypeWithArguments(rn.getAST())); + return null; + } + }; + plugin.buildAST(null, null, null); + ClassNode parsedNode = ref.get(); + // the returned node is DummyNode genericsSpec, GenericsType[] oldPlaceHolders) { + if (oldPlaceHolders==null || oldPlaceHolders.length==0) return oldPlaceHolders; + if (genericsSpec.isEmpty()) return oldPlaceHolders; + GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length]; + for (int i=0; i0) { + ClassNode[] upperCorrected = new ClassNode[upper.length]; + for (int j=0;j + diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/ClassNode.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/ClassNode.java index 26124b93c3..1a010127d8 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/ClassNode.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/ClassNode.java @@ -192,21 +192,20 @@ public void setHasInconsistentHierarchy(boolean b) { protected Class clazz; // only false when this classNode is constructed from a class // GRECLIPSE private->protected - protected volatile boolean lazyInitDone=true; + protected volatile boolean lazyInitDone = true; // not null if if the ClassNode is an array // GRECLIPSE private->protected - protected ClassNode componentType = null; + protected ClassNode componentType; // if not null this instance is handled as proxy // for the redirect - // GRECLIPSE private->protected - protected ClassNode redirect=null; + private ClassNode redirect; // flag if the classes or its members are annotated private boolean annotated; // type spec for generics // GRECLIPSE private->protected - protected GenericsType[] genericsTypes=null; - private boolean usesGenerics=false; + protected GenericsType[] genericsTypes; + private boolean usesGenerics; // if set to true the name getGenericsTypes consists // of 1 element describing the name of the placeholder @@ -909,6 +908,7 @@ public List getOuterClasses() { } while ((outer = outer.getOuterClass()) != null); return result; + // GRECLIPSE end } /** @@ -1324,7 +1324,7 @@ public String toString(boolean showRedirect) { ret.append(">"); } if (redirect != null && showRedirect) { - ret.append(" -> ").append(redirect().toString()); + ret.append(" -> ").append(redirect.toString()); } return ret.toString(); } @@ -1505,6 +1505,17 @@ public boolean isAnnotated() { return this.annotated; } + // GRECLIPSE add + public GenericsType asGenericsType() { + if (!isGenericsPlaceHolder()) { + return new GenericsType(this); + } else { + ClassNode upper = (redirect != null ? redirect : this); + return new GenericsType(this, new ClassNode[]{upper}, null); + } + } + // GRECLIPSE end + public GenericsType[] getGenericsTypes() { return genericsTypes; } diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/tools/GenericsUtils.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/tools/GenericsUtils.java new file mode 100644 index 0000000000..6c99338720 --- /dev/null +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -0,0 +1,860 @@ +/* + * 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.codehaus.groovy.ast.tools; + +import groovyjarjarantlr.RecognitionException; +import groovyjarjarantlr.TokenStreamException; +import groovy.transform.stc.IncorrectTypeHintException; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.antlr.AntlrParserPlugin; +import org.codehaus.groovy.antlr.parser.GroovyLexer; +import org.codehaus.groovy.antlr.parser.GroovyRecognizer; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.runtime.memoize.ConcurrentSoftCache; +import org.codehaus.groovy.runtime.memoize.EvictableCache; +import org.codehaus.groovy.syntax.ParserException; +import org.codehaus.groovy.syntax.Reduction; + +import java.io.StringReader; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; + +/** + * Utility methods to deal with generic types. + */ +public class GenericsUtils { + public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY; + public static final String JAVA_LANG_OBJECT = "java.lang.Object"; + + /** + * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a + * class uses generic type
<T,U,V>
(redirectGenericTypes), is used with actual type parameters + *
<java.lang.String, U,V>
, then a class or interface using generic types
<T,V>
+ * will be aligned to
<java.lang.String,V>
+ * + * @param redirectGenericTypes the type arguments or the redirect class node + * @param parameterizedTypes the actual type arguments used on this class node + * @param alignmentTarget the generic type arguments to which we want to align to + * @return aligned type arguments + * @deprecated You shouldn't call this method because it is inherently unreliable + */ + @Deprecated + public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) { + if (alignmentTarget == null) return EMPTY_GENERICS_ARRAY; + if (parameterizedTypes == null || parameterizedTypes.length == 0) return alignmentTarget; + GenericsType[] generics = new GenericsType[alignmentTarget.length]; + for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) { + final GenericsType currentTarget = alignmentTarget[i]; + GenericsType match = null; + if (redirectGenericTypes != null) { + for (int j = 0; j < redirectGenericTypes.length && match == null; j++) { + GenericsType redirectGenericType = redirectGenericTypes[j]; + if (redirectGenericType.isCompatibleWith(currentTarget.getType())) { + if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) { + // check if there's a potential better match + boolean skip = false; + for (int k = j + 1; k < redirectGenericTypes.length && !skip; k++) { + GenericsType ogt = redirectGenericTypes[k]; + if (ogt.isPlaceholder() && ogt.isCompatibleWith(currentTarget.getType()) && ogt.getName().equals(currentTarget.getName())) { + skip = true; + } + } + if (skip) continue; + } + match = parameterizedTypes[j]; + if (currentTarget.isWildcard()) { + // if alignment target is a wildcard type + // then we must make best effort to return a parameterized + // wildcard + ClassNode lower = currentTarget.getLowerBound() != null ? match.getType() : null; + ClassNode[] currentUpper = currentTarget.getUpperBounds(); + ClassNode[] upper = currentUpper != null ? new ClassNode[currentUpper.length] : null; + if (upper != null) { + for (int k = 0; k < upper.length; k++) { + upper[k] = currentUpper[k].isGenericsPlaceHolder() ? match.getType() : currentUpper[k]; + } + } + match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower); + match.setWildcard(true); + } + } + } + } + if (match == null) { + match = currentTarget; + } + generics[i] = match; + } + return generics; + } + + /** + * Generates a wildcard generic type in order to be used for checks against class nodes. + * See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}. + * + * @param types the type to be used as the wildcard upper bound + * @return a wildcard generics type + */ + public static GenericsType buildWildcardType(final ClassNode... types) { + ClassNode base = ClassHelper.makeWithoutCaching("?"); + GenericsType gt = new GenericsType(base, types, null); + gt.setWildcard(true); + return gt; + } + + public static Map extractPlaceholders(ClassNode cn) { + Map ret = new HashMap(); + extractPlaceholders(cn, ret); + return ret; + } + + /** + * For a given classnode, fills in the supplied map with the parameterized + * types it defines. + * + * @param node the class node to check + * @param map the generics type information collector + */ + public static void extractPlaceholders(ClassNode node, Map map) { + if (node == null) return; + + if (node.isArray()) { + extractPlaceholders(node.getComponentType(), map); + return; + } + + if (!node.isUsingGenerics() || !node.isRedirectNode()) return; + GenericsType[] parameterized = node.getGenericsTypes(); + if (parameterized == null || parameterized.length == 0) return; + GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes(); + if (redirectGenericsTypes == null || + (node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */ ) { + redirectGenericsTypes = parameterized; + } + if (redirectGenericsTypes.length != parameterized.length) { + throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" + + "\nExpected: " + node.getName() + toGenericTypesString(redirectGenericsTypes) + + "\nSupplied: " + node.getName() + toGenericTypesString(parameterized)); + } + + List valueList = new LinkedList<>(); + for (int i = 0; i < redirectGenericsTypes.length; i++) { + GenericsType redirectType = redirectGenericsTypes[i]; + if (redirectType.isPlaceholder()) { + String name = redirectType.getName(); + if (!map.containsKey(name)) { + GenericsType value = parameterized[i]; + map.put(name, value); + + valueList.add(value); + } + } + } + + for (GenericsType value : valueList) { + if (value.isWildcard()) { + ClassNode lowerBound = value.getLowerBound(); + if (lowerBound != null) { + extractPlaceholders(lowerBound, map); + } + ClassNode[] upperBounds = value.getUpperBounds(); + if (upperBounds != null) { + for (ClassNode upperBound : upperBounds) { + extractPlaceholders(upperBound, map); + } + } + } else if (!value.isPlaceholder()) { + extractPlaceholders(value.getType(), map); + } + } + } + + public static String toGenericTypesString(GenericsType[] genericsTypes) { + if (genericsTypes == null) return ""; + StringBuilder sb = new StringBuilder("<"); + for (int i = 0, n = genericsTypes.length; i < n; i++) { + sb.append(genericsTypes[i].toString()); + if (i < n - 1) { + sb.append(","); + } + } + sb.append("> "); + return sb.toString(); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + * @deprecated Use #parameterizeType instead + */ + @Deprecated + public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) { + return parameterizeType(hint, target); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + */ + public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) { + if (hint.isArray()) { + if (target.isArray()) { + return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); + } + return target; + } + if (!target.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { + ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); + if (!hint.equals(nextSuperClass)) { + Map genericsSpec = createGenericsSpec(hint); + extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); + ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); + return parameterizeType(result, target); + } + } + Map genericsSpec = createGenericsSpec(hint); + ClassNode targetRedirect = target.redirect(); + genericsSpec = createGenericsSpec(targetRedirect, genericsSpec); + extractSuperClassGenerics(hint, targetRedirect, genericsSpec); + return correctToGenericsSpecRecurse(genericsSpec, targetRedirect); + + } + + public static ClassNode nonGeneric(ClassNode type) { + if (type.isUsingGenerics()) { + final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName()); + nonGen.setRedirect(type); + nonGen.setGenericsTypes(null); + nonGen.setUsingGenerics(false); + return nonGen; + } + if (type.isArray() && type.getComponentType().isUsingGenerics()) { + return type.getComponentType().getPlainNodeReference().makeArray(); + } + return type; + } + + public static ClassNode newClass(ClassNode type) { + return type.getPlainNodeReference(); + } + + public static ClassNode makeClassSafe(Class klass) { + return makeClassSafeWithGenerics(ClassHelper.make(klass)); + } + + public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) { + GenericsType[] genericsTypes = new GenericsType[1]; + genericsTypes[0] = new GenericsType(genericsType); + return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes); + } + + public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) { + ClassNode plainNodeReference = newClass(type); + if (genericTypes != null && genericTypes.length > 0) { + plainNodeReference.setGenericsTypes(genericTypes); + if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true); + } + return plainNodeReference; + } + + public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) { + if (type.isArray()) { + return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray(); + } + GenericsType[] gtypes = GenericsType.EMPTY_ARRAY; + if (genericTypes != null) { + gtypes = new GenericsType[genericTypes.length]; + System.arraycopy(genericTypes, 0, gtypes, 0, gtypes.length); + } + return makeClassSafe0(type, gtypes); + } + + public static MethodNode correctToGenericsSpec(Map genericsSpec, MethodNode mn) { + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType()); + Parameter[] origParameters = mn.getParameters(); + Parameter[] newParameters = new Parameter[origParameters.length]; + for (int i = 0; i < origParameters.length; i++) { + Parameter origParameter = origParameters[i]; + newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, origParameter.getType()), origParameter.getName(), origParameter.getInitialExpression()); + } + return new MethodNode(mn.getName(), mn.getModifiers(), correctedType, newParameters, mn.getExceptions(), mn.getCode()); + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type) { + return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList()); + } + + /** + * @since 2.4.1 + */ + public static ClassNode[] correctToGenericsSpecRecurse(Map genericsSpec, ClassNode[] types) { + if (types == null || types.length == 1) return types; + ClassNode[] newTypes = new ClassNode[types.length]; + boolean modified = false; + for (int i = 0; i < types.length; i++) { + newTypes[i] = correctToGenericsSpecRecurse(genericsSpec, types[i], new ArrayList()); + modified = modified || (types[i] != newTypes[i]); + } + if (!modified) return types; + return newTypes; + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type, List exclusions) { + if (type.isArray()) { + return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); + } + if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + if (type != null && type.isGenericsPlaceHolder() && type.getGenericsTypes() == null) { + ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); + placeholder.setGenericsPlaceHolder(true); + type = makeClassSafeWithGenerics(type, new GenericsType(placeholder)); + } + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + GenericsType[] oldgTypes = type.getGenericsTypes(); + GenericsType[] newgTypes = GenericsType.EMPTY_ARRAY; + if (oldgTypes != null) { + newgTypes = new GenericsType[oldgTypes.length]; + for (int i = 0; i < newgTypes.length; i++) { + GenericsType oldgType = oldgTypes[i]; + if (oldgType.isPlaceholder()) { + if (genericsSpec.get(oldgType.getName()) != null) { + newgTypes[i] = new GenericsType(genericsSpec.get(oldgType.getName())); + } else { + newgTypes[i] = new GenericsType(ClassHelper.OBJECT_TYPE); + } + } else if (oldgType.isWildcard()) { + ClassNode oldLower = oldgType.getLowerBound(); + ClassNode lower = oldLower != null ? correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions) : null; + ClassNode[] oldUpper = oldgType.getUpperBounds(); + ClassNode[] upper = null; + if (oldUpper != null) { + upper = new ClassNode[oldUpper.length]; + for (int j = 0; j < oldUpper.length; j++) { + upper[j] = correctToGenericsSpecRecurse(genericsSpec, oldUpper[j], exclusions); + } + } + GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); + fixed.setName(oldgType.getName()); + fixed.setWildcard(true); + newgTypes[i] = fixed; + } else { + newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions)); + } + } + } + return makeClassSafeWithGenerics(type, newgTypes); + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, GenericsType type) { + ClassNode ret = null; + if (type.isPlaceholder()) { + String name = type.getName(); + ret = genericsSpec.get(name); + } + if (ret == null) ret = type.getType(); + return ret; + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, ClassNode type) { + if (type.isArray()) { + return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); + } + if (type.isGenericsPlaceHolder()) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + return type; + } + + public static Map createGenericsSpec(ClassNode current) { + return createGenericsSpec(current, Collections.EMPTY_MAP); + } + + public static Map createGenericsSpec(ClassNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret contains the type specs, what we now need is the type spec for the + // current class. To get that we first apply the type parameters to the + // current class and then use the type names of the current class to reset + // the map. Example: + // class A{} + // class B extends A {} + // first we have: T->Number + // we apply it to A -> A + // resulting in: V->Number,W->Long,X->String + + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + ClassNode[] spec = new ClassNode[sgts.length]; + for (int i = 0; i < spec.length; i++) { + spec[i] = correctToGenericsSpec(ret, sgts[i]); + } + GenericsType[] newGts = current.redirect().getGenericsTypes(); + if (newGts == null) return ret; + ret.clear(); + for (int i = 0; i < spec.length; i++) { + ret.put(newGts[i].getName(), spec[i]); + } + } + return ret; + } + + public static Map addMethodGenerics(MethodNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret starts with the original type specs, now add gts for the current method if any + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + for (GenericsType sgt : sgts) { + ret.put(sgt.getName(), sgt.getType()); + } + } + return ret; + } + + public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map spec) { + // TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections, + // but operates on ClassNodes instead of GenericsType + if (target == null || type == target) return; + if (type.isArray() && target.isArray()) { + extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); + } else if (type.isArray() && JAVA_LANG_OBJECT.equals(target.getName())) { + // Object is superclass of arrays but no generics involved + } else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { + // structural match route + if (target.isGenericsPlaceHolder()) { + spec.put(target.getGenericsTypes()[0].getName(), type); + } else { + extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); + } + } else { + // have first to find matching super class or interface + ClassNode superClass = getSuperClass(type, target); + + if (superClass != null) { + ClassNode corrected = getCorrectedClassNode(type, superClass, false); + extractSuperClassGenerics(corrected, target, spec); + } else { + // if we reach here, we have an unhandled case + throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this."); + } + } + } + + public static ClassNode getSuperClass(ClassNode type, ClassNode target) { + ClassNode superClass = ClassHelper.getNextSuperClass(type, target); + + if (superClass == null) { + if (ClassHelper.isPrimitiveType(type)) { + superClass = ClassHelper.getNextSuperClass(ClassHelper.getWrapper(type), target); + } + } + + return superClass; + } + + private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map spec) { + // if declaration does not provide generics, there is no connection to make + if (usage == null || declaration == null || declaration.length == 0) return; + if (usage.length != declaration.length) return; + + // both have generics + for (int i = 0; i < usage.length; i++) { + GenericsType ui = usage[i]; + GenericsType di = declaration[i]; + if (di.isPlaceholder()) { + spec.put(di.getName(), ui.getType()); + } else if (di.isWildcard()) { + if (ui.isWildcard()) { + extractSuperClassGenerics(ui.getLowerBound(), di.getLowerBound(), spec); + extractSuperClassGenerics(ui.getUpperBounds(), di.getUpperBounds(), spec); + } else { + ClassNode cu = ui.getType(); + extractSuperClassGenerics(cu, di.getLowerBound(), spec); + ClassNode[] upperBounds = di.getUpperBounds(); + if (upperBounds != null) { + for (ClassNode cn : upperBounds) { + extractSuperClassGenerics(cu, cn, spec); + } + } + } + } else { + extractSuperClassGenerics(ui.getType(), di.getType(), spec); + } + } + } + + private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map spec) { + if (usage == null || declaration == null || declaration.length == 0) return; + // both have generics + for (int i = 0; i < usage.length; i++) { + ClassNode ui = usage[i]; + ClassNode di = declaration[i]; + if (di.isGenericsPlaceHolder()) { + spec.put(di.getGenericsTypes()[0].getName(), di); + } else if (di.isUsingGenerics()) { + extractSuperClassGenerics(ui.getGenericsTypes(), di.getGenericsTypes(), spec); + } + } + } + + public static ClassNode[] parseClassNodesFromString( + final String option, + final SourceUnit sourceUnit, + final CompilationUnit compilationUnit, + final MethodNode mn, + final ASTNode usage) { + GroovyLexer lexer = new GroovyLexer(new StringReader("DummyNode<" + option + ">")); + final GroovyRecognizer rn = GroovyRecognizer.make(lexer); + try { + rn.classOrInterfaceType(true); + final AtomicReference ref = new AtomicReference(); + AntlrParserPlugin plugin = new AntlrParserPlugin() { + @Override + public ModuleNode buildAST(final SourceUnit sourceUnit, final ClassLoader classLoader, final Reduction cst) throws ParserException { + ref.set(makeTypeWithArguments(rn.getAST())); + return null; + } + }; + plugin.buildAST(null, null, null); + ClassNode parsedNode = ref.get(); + // the returned node is DummyNode genericsSpec, GenericsType[] oldPlaceHolders) { + if (oldPlaceHolders == null || oldPlaceHolders.length == 0) return oldPlaceHolders; + if (genericsSpec.isEmpty()) return oldPlaceHolders; + GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length]; + for (int i = 0; i < oldPlaceHolders.length; i++) { + GenericsType old = oldPlaceHolders[i]; + if (!old.isPlaceholder()) + throw new GroovyBugError("Given generics type " + old + " must be a placeholder!"); + ClassNode fromSpec = genericsSpec.get(old.getName()); + if (fromSpec != null) { + /* GRECLIPSE edit + if (fromSpec.isGenericsPlaceHolder()) { + ClassNode[] upper = new ClassNode[]{fromSpec.redirect()}; + newTypes[i] = new GenericsType(fromSpec, upper, null); + } else { + newTypes[i] = new GenericsType(fromSpec); + } + */ + newTypes[i] = fromSpec.asGenericsType(); + // GRECLIPSE end + } else { + ClassNode[] upper = old.getUpperBounds(); + ClassNode[] newUpper = upper; + if (upper != null && upper.length > 0) { + ClassNode[] upperCorrected = new ClassNode[upper.length]; + for (int j = 0; j < upper.length; j++) { + upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec, upper[j]); + } + upper = upperCorrected; + } + ClassNode lower = old.getLowerBound(); + ClassNode newLower = correctToGenericsSpecRecurse(genericsSpec, lower); + if (lower == newLower && upper == newUpper) { + newTypes[i] = oldPlaceHolders[i]; + } else { + ClassNode newPlaceHolder = ClassHelper.make(old.getName()); + GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower); + gt.setPlaceholder(true); + newTypes[i] = gt; + } + } + } + return newTypes; + } + + private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.getBoolean("groovy.enable.parameterized.type.cache"); + + /** + * Try to get the parameterized type from the cache. + * 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) { + if (!PARAMETERIZED_TYPE_CACHE_ENABLED) { + return findParameterizedType(genericsClass, actualType); + } + + SoftReference sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), new EvictableCache.ValueProvider>() { + @Override + public SoftReference provide(ParameterizedTypeCacheKey key) { + return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType())); + } + }); + + return null == sr ? null : sr.get(); + } + + /** + * Get the parameterized type by search the whole class hierarchy according to generics class and actual receiver. + * {@link #findParameterizedTypeFromCache(ClassNode, ClassNode)} is strongly recommended for better performance. + * + * @param genericsClass the generics class + * @param actualType the actual type + * @return the parameterized type + */ + public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType) { + ClassNode parameterizedType = null; + + if (null == genericsClass.getGenericsTypes()) { + return parameterizedType; + } + + GenericsType[] declaringGenericsTypes = genericsClass.getGenericsTypes(); + + List classNodeList = new LinkedList<>(getAllSuperClassesAndInterfaces(actualType)); + classNodeList.add(0, actualType); + + for (ClassNode cn : classNodeList) { + if (cn == genericsClass) { + continue; + } + + if (!genericsClass.equals(cn.redirect())) { + continue; + } + + if (isGenericsTypeArraysLengthEqual(declaringGenericsTypes, cn.getGenericsTypes())) { + parameterizedType = cn; + break; + } + } + + return parameterizedType; + } + + private static boolean isGenericsTypeArraysLengthEqual(GenericsType[] declaringGenericsTypes, GenericsType[] actualGenericsTypes) { + return null != actualGenericsTypes && declaringGenericsTypes.length == actualGenericsTypes.length; + } + + private static List getAllSuperClassesAndInterfaces(ClassNode actualReceiver) { + List superClassAndInterfaceList = new LinkedList<>(); + List allSuperClassNodeList = getAllUnresolvedSuperClasses(actualReceiver); + superClassAndInterfaceList.addAll(allSuperClassNodeList); + superClassAndInterfaceList.addAll(actualReceiver.getAllInterfaces()); + + for (ClassNode superClassNode : allSuperClassNodeList) { + superClassAndInterfaceList.addAll(superClassNode.getAllInterfaces()); + } + + return superClassAndInterfaceList; + } + + private static List getAllUnresolvedSuperClasses(ClassNode actualReceiver) { + List superClassNodeList = new LinkedList<>(); + + for (ClassNode cn = actualReceiver.getUnresolvedSuperClass(); null != cn && ClassHelper.OBJECT_TYPE != cn; cn = cn.getUnresolvedSuperClass()) { + superClassNodeList.add(cn); + } + + return superClassNodeList; + } + + private static final EvictableCache> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64); + + /** + * Clear the parameterized type cache + * It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675 + */ + public static void clearParameterizedTypeCache() { + PARAMETERIZED_TYPE_CACHE.clearAll(); + } + + /** + * map declaring generics type to actual generics type, e.g. GROOVY-7204: + * declaring generics types: T, S extends Serializable + * actual generics types : String, Long + * + * the result map is [ + * T: String, + * S: Long + * ] + * + * The resolved types can not help us to choose methods correctly if the argument is a string: T: Object, S: Serializable + * so we need actual types: T: String, S: Long + */ + public static Map makeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver) { + ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver); + + if (null == parameterizedType) { + return Collections.emptyMap(); + } + + GenericsType[] declaringGenericsTypes = declaringClass.getGenericsTypes(); + GenericsType[] actualGenericsTypes = parameterizedType.getGenericsTypes(); + + Map result = new LinkedHashMap<>(); + for (int i = 0, n = declaringGenericsTypes.length; i < n; i++) { + result.put(declaringGenericsTypes[i], actualGenericsTypes[i]); + } + + return result; + } + + /** + * Get the actual type according to the placeholder name + * + * @param placeholderName the placeholder name, e.g. T, E + * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode} + * @return the actual type + */ + public static ClassNode findActualTypeByGenericsPlaceholderName(String placeholderName, Map genericsPlaceholderAndTypeMap) { + for (Map.Entry entry : genericsPlaceholderAndTypeMap.entrySet()) { + GenericsType declaringGenericsType = entry.getKey(); + + if (placeholderName.equals(declaringGenericsType.getName())) { + return entry.getValue().getType().redirect(); + } + } + + return null; + } + + private static class ParameterizedTypeCacheKey { + private ClassNode genericsClass; + private ClassNode actualType; + + public ParameterizedTypeCacheKey(ClassNode genericsClass, ClassNode actualType) { + this.genericsClass = genericsClass; + this.actualType = actualType; + } + + public ClassNode getGenericsClass() { + return genericsClass; + } + + @SuppressWarnings("unused") + public void setGenericsClass(ClassNode genericsClass) { + this.genericsClass = genericsClass; + } + + public ClassNode getActualType() { + return actualType; + } + + @SuppressWarnings("unused") + public void setActualType(ClassNode actualType) { + this.actualType = actualType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ParameterizedTypeCacheKey cacheKey = (ParameterizedTypeCacheKey) o; + + return genericsClass == cacheKey.genericsClass && + actualType == cacheKey.actualType; + } + + @Override + public int hashCode() { + return Objects.hash(genericsClass, actualType); + } + } +} diff --git a/base/org.codehaus.groovy26/.checkstyle b/base/org.codehaus.groovy26/.checkstyle index 0f7c3a353c..617dd4c680 100644 --- a/base/org.codehaus.groovy26/.checkstyle +++ b/base/org.codehaus.groovy26/.checkstyle @@ -40,6 +40,7 @@ + diff --git a/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/ClassNode.java b/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/ClassNode.java index 8f03dc7987..1c0f7cc0fe 100644 --- a/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/ClassNode.java +++ b/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/ClassNode.java @@ -192,21 +192,20 @@ public void setHasInconsistentHierarchy(boolean b) { protected Class clazz; // only false when this classNode is constructed from a class // GRECLIPSE private->protected - protected volatile boolean lazyInitDone=true; + protected volatile boolean lazyInitDone = true; // not null if if the ClassNode is an array // GRECLIPSE private->protected - protected ClassNode componentType = null; + protected ClassNode componentType; // if not null this instance is handled as proxy // for the redirect - // GRECLIPSE private->protected - protected ClassNode redirect=null; + private ClassNode redirect; // flag if the classes or its members are annotated private boolean annotated; // type spec for generics // GRECLIPSE private->protected - protected GenericsType[] genericsTypes=null; - private boolean usesGenerics=false; + protected GenericsType[] genericsTypes; + private boolean usesGenerics; // if set to true the name getGenericsTypes consists // of 1 element describing the name of the placeholder @@ -913,6 +912,7 @@ public List getOuterClasses() { } while ((outer = outer.getOuterClass()) != null); return result; + // GRECLIPSE end } /** @@ -1359,7 +1359,7 @@ public String toString(boolean showRedirect) { ret.append(">"); } if (redirect != null && showRedirect) { - ret.append(" -> ").append(redirect().toString()); + ret.append(" -> ").append(redirect.toString()); } return ret.toString(); } @@ -1540,6 +1540,17 @@ public boolean isAnnotated() { return this.annotated; } + // GRECLIPSE add + public GenericsType asGenericsType() { + if (!isGenericsPlaceHolder()) { + return new GenericsType(this); + } else { + ClassNode upper = (redirect != null ? redirect : this); + return new GenericsType(this, new ClassNode[]{upper}, null); + } + } + // GRECLIPSE end + public GenericsType[] getGenericsTypes() { return genericsTypes; } diff --git a/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/tools/GenericsUtils.java b/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/tools/GenericsUtils.java new file mode 100644 index 0000000000..051008b855 --- /dev/null +++ b/base/org.codehaus.groovy26/src/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -0,0 +1,846 @@ +/* + * 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.codehaus.groovy.ast.tools; + +import groovyjarjarantlr.RecognitionException; +import groovyjarjarantlr.TokenStreamException; +import groovy.transform.stc.IncorrectTypeHintException; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.antlr.AntlrParserPlugin; +import org.codehaus.groovy.antlr.parser.GroovyLexer; +import org.codehaus.groovy.antlr.parser.GroovyRecognizer; +import org.codehaus.groovy.ast.ASTNode; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.GenericsType; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.ModuleNode; +import org.codehaus.groovy.ast.Parameter; +import org.codehaus.groovy.ast.stmt.EmptyStatement; +import org.codehaus.groovy.control.CompilationUnit; +import org.codehaus.groovy.control.ResolveVisitor; +import org.codehaus.groovy.control.SourceUnit; +import org.codehaus.groovy.runtime.memoize.ConcurrentSoftCache; +import org.codehaus.groovy.runtime.memoize.EvictableCache; +import org.codehaus.groovy.syntax.ParserException; +import org.codehaus.groovy.syntax.Reduction; + +import java.io.StringReader; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; + +/** + * Utility methods to deal with generic types. + */ +public class GenericsUtils { + public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY; + public static final String JAVA_LANG_OBJECT = "java.lang.Object"; + + /** + * Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a + * class uses generic type
<T,U,V>
(redirectGenericTypes), is used with actual type parameters + *
<java.lang.String, U,V>
, then a class or interface using generic types
<T,V>
+ * will be aligned to
<java.lang.String,V>
+ * + * @param redirectGenericTypes the type arguments or the redirect class node + * @param parameterizedTypes the actual type arguments used on this class node + * @param alignmentTarget the generic type arguments to which we want to align to + * @return aligned type arguments + * @deprecated You shouldn't call this method because it is inherently unreliable + */ + @Deprecated + public static GenericsType[] alignGenericTypes(final GenericsType[] redirectGenericTypes, final GenericsType[] parameterizedTypes, final GenericsType[] alignmentTarget) { + if (alignmentTarget == null) return EMPTY_GENERICS_ARRAY; + if (parameterizedTypes == null || parameterizedTypes.length == 0) return alignmentTarget; + GenericsType[] generics = new GenericsType[alignmentTarget.length]; + for (int i = 0, scgtLength = alignmentTarget.length; i < scgtLength; i++) { + final GenericsType currentTarget = alignmentTarget[i]; + GenericsType match = null; + if (redirectGenericTypes != null) { + for (int j = 0; j < redirectGenericTypes.length && match == null; j++) { + GenericsType redirectGenericType = redirectGenericTypes[j]; + if (redirectGenericType.isCompatibleWith(currentTarget.getType())) { + if (currentTarget.isPlaceholder() && redirectGenericType.isPlaceholder() && !currentTarget.getName().equals(redirectGenericType.getName())) { + // check if there's a potential better match + boolean skip = false; + for (int k = j + 1; k < redirectGenericTypes.length && !skip; k++) { + GenericsType ogt = redirectGenericTypes[k]; + if (ogt.isPlaceholder() && ogt.isCompatibleWith(currentTarget.getType()) && ogt.getName().equals(currentTarget.getName())) { + skip = true; + } + } + if (skip) continue; + } + match = parameterizedTypes[j]; + if (currentTarget.isWildcard()) { + // if alignment target is a wildcard type + // then we must make best effort to return a parameterized + // wildcard + ClassNode lower = currentTarget.getLowerBound() != null ? match.getType() : null; + ClassNode[] currentUpper = currentTarget.getUpperBounds(); + ClassNode[] upper = currentUpper != null ? new ClassNode[currentUpper.length] : null; + if (upper != null) { + for (int k = 0; k < upper.length; k++) { + upper[k] = currentUpper[k].isGenericsPlaceHolder() ? match.getType() : currentUpper[k]; + } + } + match = new GenericsType(ClassHelper.makeWithoutCaching("?"), upper, lower); + match.setWildcard(true); + } + } + } + } + if (match == null) { + match = currentTarget; + } + generics[i] = match; + } + return generics; + } + + /** + * Generates a wildcard generic type in order to be used for checks against class nodes. + * See {@link GenericsType#isCompatibleWith(org.codehaus.groovy.ast.ClassNode)}. + * + * @param types the type to be used as the wildcard upper bound + * @return a wildcard generics type + */ + public static GenericsType buildWildcardType(final ClassNode... types) { + ClassNode base = ClassHelper.makeWithoutCaching("?"); + GenericsType gt = new GenericsType(base, types, null); + gt.setWildcard(true); + return gt; + } + + public static Map extractPlaceholders(ClassNode cn) { + Map ret = new HashMap(); + extractPlaceholders(cn, ret); + return ret; + } + + /** + * For a given classnode, fills in the supplied map with the parameterized + * types it defines. + * + * @param node the class node to check + * @param map the generics type information collector + */ + public static void extractPlaceholders(ClassNode node, Map map) { + if (node == null) return; + + if (node.isArray()) { + extractPlaceholders(node.getComponentType(), map); + return; + } + + if (!node.isUsingGenerics() || !node.isRedirectNode()) return; + GenericsType[] parameterized = node.getGenericsTypes(); + if (parameterized == null || parameterized.length == 0) return; + GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes(); + if (redirectGenericsTypes == null || + (node.isGenericsPlaceHolder() && redirectGenericsTypes.length != parameterized.length) /* GROOVY-8609 */ ) { + redirectGenericsTypes = parameterized; + } + if (redirectGenericsTypes.length != parameterized.length) { + throw new GroovyBugError("Expected earlier checking to detect generics parameter arity mismatch" + + "\nExpected: " + node.getName() + toGenericTypesString(redirectGenericsTypes) + + "\nSupplied: " + node.getName() + toGenericTypesString(parameterized)); + } + + List valueList = new LinkedList<>(); + for (int i = 0; i < redirectGenericsTypes.length; i++) { + GenericsType redirectType = redirectGenericsTypes[i]; + if (redirectType.isPlaceholder()) { + String name = redirectType.getName(); + if (!map.containsKey(name)) { + GenericsType value = parameterized[i]; + map.put(name, value); + + valueList.add(value); + } + } + } + + for (GenericsType value : valueList) { + if (value.isWildcard()) { + ClassNode lowerBound = value.getLowerBound(); + if (lowerBound != null) { + extractPlaceholders(lowerBound, map); + } + ClassNode[] upperBounds = value.getUpperBounds(); + if (upperBounds != null) { + for (ClassNode upperBound : upperBounds) { + extractPlaceholders(upperBound, map); + } + } + } else if (!value.isPlaceholder()) { + extractPlaceholders(value.getType(), map); + } + } + } + + public static String toGenericTypesString(GenericsType[] genericsTypes) { + if (genericsTypes == null) return ""; + StringBuilder sb = new StringBuilder("<"); + for (int i = 0, n = genericsTypes.length; i < n; i++) { + sb.append(genericsTypes[i].toString()); + if (i < n - 1) { + sb.append(","); + } + } + sb.append("> "); + return sb.toString(); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + * @deprecated Use #parameterizeType instead + */ + @Deprecated + public static ClassNode parameterizeInterfaceGenerics(final ClassNode hint, final ClassNode target) { + return parameterizeType(hint, target); + } + + /** + * Interface class nodes retrieved from {@link org.codehaus.groovy.ast.ClassNode#getInterfaces()} + * or {@link org.codehaus.groovy.ast.ClassNode#getAllInterfaces()} are returned with generic type + * arguments. This method allows returning a parameterized interface given the parameterized class + * node which implements this interface. + * + * @param hint the class node where generics types are parameterized + * @param target the interface we want to parameterize generics types + * @return a parameterized interface class node + */ + public static ClassNode parameterizeType(final ClassNode hint, final ClassNode target) { + if (hint.isArray()) { + if (target.isArray()) { + return parameterizeType(hint.getComponentType(), target.getComponentType()).makeArray(); + } + return target; + } + if (!target.equals(hint) && implementsInterfaceOrIsSubclassOf(target, hint)) { + ClassNode nextSuperClass = ClassHelper.getNextSuperClass(target, hint); + if (!hint.equals(nextSuperClass)) { + Map genericsSpec = createGenericsSpec(hint); + extractSuperClassGenerics(hint, nextSuperClass, genericsSpec); + ClassNode result = correctToGenericsSpecRecurse(genericsSpec, nextSuperClass); + return parameterizeType(result, target); + } + } + Map genericsSpec = createGenericsSpec(hint); + ClassNode targetRedirect = target.redirect(); + genericsSpec = createGenericsSpec(targetRedirect, genericsSpec); + extractSuperClassGenerics(hint, targetRedirect, genericsSpec); + return correctToGenericsSpecRecurse(genericsSpec, targetRedirect); + + } + + public static ClassNode nonGeneric(ClassNode type) { + if (type.isUsingGenerics()) { + final ClassNode nonGen = ClassHelper.makeWithoutCaching(type.getName()); + nonGen.setRedirect(type); + nonGen.setGenericsTypes(null); + nonGen.setUsingGenerics(false); + return nonGen; + } + if (type.isArray() && type.getComponentType().isUsingGenerics()) { + return type.getComponentType().getPlainNodeReference().makeArray(); + } + return type; + } + + public static ClassNode newClass(ClassNode type) { + return type.getPlainNodeReference(); + } + + public static ClassNode makeClassSafe(Class klass) { + return makeClassSafeWithGenerics(ClassHelper.make(klass)); + } + + public static ClassNode makeClassSafeWithGenerics(Class klass, ClassNode genericsType) { + GenericsType[] genericsTypes = new GenericsType[1]; + genericsTypes[0] = new GenericsType(genericsType); + return makeClassSafeWithGenerics(ClassHelper.make(klass), genericsTypes); + } + + public static ClassNode makeClassSafe0(ClassNode type, GenericsType... genericTypes) { + ClassNode plainNodeReference = newClass(type); + if (genericTypes != null && genericTypes.length > 0) { + plainNodeReference.setGenericsTypes(genericTypes); + if (type.isGenericsPlaceHolder()) plainNodeReference.setGenericsPlaceHolder(true); + } + return plainNodeReference; + } + + public static ClassNode makeClassSafeWithGenerics(ClassNode type, GenericsType... genericTypes) { + if (type.isArray()) { + return makeClassSafeWithGenerics(type.getComponentType(), genericTypes).makeArray(); + } + GenericsType[] gtypes = GenericsType.EMPTY_ARRAY; + if (genericTypes != null) { + gtypes = new GenericsType[genericTypes.length]; + System.arraycopy(genericTypes, 0, gtypes, 0, gtypes.length); + } + return makeClassSafe0(type, gtypes); + } + + public static MethodNode correctToGenericsSpec(Map genericsSpec, MethodNode mn) { + ClassNode correctedType = correctToGenericsSpecRecurse(genericsSpec, mn.getReturnType()); + Parameter[] origParameters = mn.getParameters(); + Parameter[] newParameters = new Parameter[origParameters.length]; + for (int i = 0; i < origParameters.length; i++) { + Parameter origParameter = origParameters[i]; + newParameters[i] = new Parameter(correctToGenericsSpecRecurse(genericsSpec, origParameter.getType()), origParameter.getName(), origParameter.getInitialExpression()); + } + return new MethodNode(mn.getName(), mn.getModifiers(), correctedType, newParameters, mn.getExceptions(), mn.getCode()); + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type) { + return correctToGenericsSpecRecurse(genericsSpec, type, new ArrayList()); + } + + /** + * @since 2.4.1 + */ + public static ClassNode[] correctToGenericsSpecRecurse(Map genericsSpec, ClassNode[] types) { + if (types == null || types.length == 1) return types; + ClassNode[] newTypes = new ClassNode[types.length]; + boolean modified = false; + for (int i = 0; i < types.length; i++) { + newTypes[i] = correctToGenericsSpecRecurse(genericsSpec, types[i], new ArrayList()); + modified = modified || (types[i] != newTypes[i]); + } + if (!modified) return types; + return newTypes; + } + + public static ClassNode correctToGenericsSpecRecurse(Map genericsSpec, ClassNode type, List exclusions) { + if (type.isArray()) { + return correctToGenericsSpecRecurse(genericsSpec, type.getComponentType(), exclusions).makeArray(); + } + if (type.isGenericsPlaceHolder() && !exclusions.contains(type.getUnresolvedName())) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + if (type != null && type.isGenericsPlaceHolder() && type.getGenericsTypes() == null) { + ClassNode placeholder = ClassHelper.makeWithoutCaching(type.getUnresolvedName()); + placeholder.setGenericsPlaceHolder(true); + type = makeClassSafeWithGenerics(type, new GenericsType(placeholder)); + } + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + GenericsType[] oldgTypes = type.getGenericsTypes(); + GenericsType[] newgTypes = GenericsType.EMPTY_ARRAY; + if (oldgTypes != null) { + newgTypes = new GenericsType[oldgTypes.length]; + for (int i = 0; i < newgTypes.length; i++) { + GenericsType oldgType = oldgTypes[i]; + if (oldgType.isPlaceholder()) { + if (genericsSpec.get(oldgType.getName()) != null) { + newgTypes[i] = new GenericsType(genericsSpec.get(oldgType.getName())); + } else { + newgTypes[i] = new GenericsType(ClassHelper.OBJECT_TYPE); + } + } else if (oldgType.isWildcard()) { + ClassNode oldLower = oldgType.getLowerBound(); + ClassNode lower = oldLower != null ? correctToGenericsSpecRecurse(genericsSpec, oldLower, exclusions) : null; + ClassNode[] oldUpper = oldgType.getUpperBounds(); + ClassNode[] upper = null; + if (oldUpper != null) { + upper = new ClassNode[oldUpper.length]; + for (int j = 0; j < oldUpper.length; j++) { + upper[j] = correctToGenericsSpecRecurse(genericsSpec, oldUpper[j], exclusions); + } + } + GenericsType fixed = new GenericsType(oldgType.getType(), upper, lower); + fixed.setName(oldgType.getName()); + fixed.setWildcard(true); + newgTypes[i] = fixed; + } else { + newgTypes[i] = new GenericsType(correctToGenericsSpecRecurse(genericsSpec, correctToGenericsSpec(genericsSpec, oldgType), exclusions)); + } + } + } + return makeClassSafeWithGenerics(type, newgTypes); + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, GenericsType type) { + ClassNode ret = null; + if (type.isPlaceholder()) { + String name = type.getName(); + ret = genericsSpec.get(name); + } + if (ret == null) ret = type.getType(); + return ret; + } + + public static ClassNode correctToGenericsSpec(Map genericsSpec, ClassNode type) { + if (type.isArray()) { + return correctToGenericsSpec(genericsSpec, type.getComponentType()).makeArray(); + } + if (type.isGenericsPlaceHolder()) { + String name = type.getGenericsTypes()[0].getName(); + type = genericsSpec.get(name); + } + if (type == null) type = ClassHelper.OBJECT_TYPE; + return type; + } + + public static Map createGenericsSpec(ClassNode current) { + return createGenericsSpec(current, Collections.EMPTY_MAP); + } + + public static Map createGenericsSpec(ClassNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret contains the type specs, what we now need is the type spec for the + // current class. To get that we first apply the type parameters to the + // current class and then use the type names of the current class to reset + // the map. Example: + // class A{} + // class B extends A {} + // first we have: T->Number + // we apply it to A -> A + // resulting in: V->Number,W->Long,X->String + + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + ClassNode[] spec = new ClassNode[sgts.length]; + for (int i = 0; i < spec.length; i++) { + spec[i] = correctToGenericsSpec(ret, sgts[i]); + } + GenericsType[] newGts = current.redirect().getGenericsTypes(); + if (newGts == null) return ret; + ret.clear(); + for (int i = 0; i < spec.length; i++) { + ret.put(newGts[i].getName(), spec[i]); + } + } + return ret; + } + + public static Map addMethodGenerics(MethodNode current, Map oldSpec) { + Map ret = new HashMap(oldSpec); + // ret starts with the original type specs, now add gts for the current method if any + GenericsType[] sgts = current.getGenericsTypes(); + if (sgts != null) { + for (GenericsType sgt : sgts) { + ret.put(sgt.getName(), sgt.getType()); + } + } + return ret; + } + + public static void extractSuperClassGenerics(ClassNode type, ClassNode target, Map spec) { + // TODO: this method is very similar to StaticTypesCheckingSupport#extractGenericsConnections, + // but operates on ClassNodes instead of GenericsType + if (target == null || type == target) return; + if (type.isArray() && target.isArray()) { + extractSuperClassGenerics(type.getComponentType(), target.getComponentType(), spec); + } else if (type.isArray() && JAVA_LANG_OBJECT.equals(target.getName())) { + // Object is superclass of arrays but no generics involved + } else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) { + // structural match route + if (target.isGenericsPlaceHolder()) { + spec.put(target.getGenericsTypes()[0].getName(), type); + } else { + extractSuperClassGenerics(type.getGenericsTypes(), target.getGenericsTypes(), spec); + } + } else { + // have first to find matching super class or interface + ClassNode superClass = getSuperClass(type, target); + + if (superClass != null) { + ClassNode corrected = getCorrectedClassNode(type, superClass, false); + extractSuperClassGenerics(corrected, target, spec); + } else { + // if we reach here, we have an unhandled case + throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this."); + } + } + } + + public static ClassNode getSuperClass(ClassNode type, ClassNode target) { + ClassNode superClass = ClassHelper.getNextSuperClass(type, target); + + if (superClass == null) { + if (ClassHelper.isPrimitiveType(type)) { + superClass = ClassHelper.getNextSuperClass(ClassHelper.getWrapper(type), target); + } + } + + return superClass; + } + + private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map spec) { + // if declaration does not provide generics, there is no connection to make + if (usage == null || declaration == null || declaration.length == 0) return; + if (usage.length != declaration.length) return; + + // both have generics + for (int i = 0; i < usage.length; i++) { + GenericsType ui = usage[i]; + GenericsType di = declaration[i]; + if (di.isPlaceholder()) { + spec.put(di.getName(), ui.getType()); + } else if (di.isWildcard()) { + if (ui.isWildcard()) { + extractSuperClassGenerics(ui.getLowerBound(), di.getLowerBound(), spec); + extractSuperClassGenerics(ui.getUpperBounds(), di.getUpperBounds(), spec); + } else { + ClassNode cu = ui.getType(); + extractSuperClassGenerics(cu, di.getLowerBound(), spec); + ClassNode[] upperBounds = di.getUpperBounds(); + if (upperBounds != null) { + for (ClassNode cn : upperBounds) { + extractSuperClassGenerics(cu, cn, spec); + } + } + } + } else { + extractSuperClassGenerics(ui.getType(), di.getType(), spec); + } + } + } + + private static void extractSuperClassGenerics(ClassNode[] usage, ClassNode[] declaration, Map spec) { + if (usage == null || declaration == null || declaration.length == 0) return; + // both have generics + for (int i = 0; i < usage.length; i++) { + ClassNode ui = usage[i]; + ClassNode di = declaration[i]; + if (di.isGenericsPlaceHolder()) { + spec.put(di.getGenericsTypes()[0].getName(), di); + } else if (di.isUsingGenerics()) { + extractSuperClassGenerics(ui.getGenericsTypes(), di.getGenericsTypes(), spec); + } + } + } + + public static ClassNode[] parseClassNodesFromString( + final String option, + final SourceUnit sourceUnit, + final CompilationUnit compilationUnit, + final MethodNode mn, + final ASTNode usage) { + GroovyLexer lexer = new GroovyLexer(new StringReader("DummyNode<" + option + ">")); + final GroovyRecognizer rn = GroovyRecognizer.make(lexer); + try { + rn.classOrInterfaceType(true); + final AtomicReference ref = new AtomicReference(); + AntlrParserPlugin plugin = new AntlrParserPlugin() { + @Override + public ModuleNode buildAST(final SourceUnit sourceUnit, final ClassLoader classLoader, final Reduction cst) throws ParserException { + ref.set(makeTypeWithArguments(rn.getAST())); + return null; + } + }; + plugin.buildAST(null, null, null); + ClassNode parsedNode = ref.get(); + // the returned node is DummyNode genericsSpec, GenericsType[] oldPlaceHolders) { + if (oldPlaceHolders == null || oldPlaceHolders.length == 0) return oldPlaceHolders; + if (genericsSpec.isEmpty()) return oldPlaceHolders; + GenericsType[] newTypes = new GenericsType[oldPlaceHolders.length]; + for (int i = 0; i < oldPlaceHolders.length; i++) { + GenericsType old = oldPlaceHolders[i]; + if (!old.isPlaceholder()) + throw new GroovyBugError("Given generics type " + old + " must be a placeholder!"); + ClassNode fromSpec = genericsSpec.get(old.getName()); + if (fromSpec != null) { + /* GRECLIPSE edit + if (fromSpec.isGenericsPlaceHolder()) { + ClassNode[] upper = new ClassNode[]{fromSpec.redirect()}; + newTypes[i] = new GenericsType(fromSpec, upper, null); + } else { + newTypes[i] = new GenericsType(fromSpec); + } + */ + newTypes[i] = fromSpec.asGenericsType(); + // GRECLIPSE end + } else { + ClassNode[] upper = old.getUpperBounds(); + ClassNode[] newUpper = upper; + if (upper != null && upper.length > 0) { + ClassNode[] upperCorrected = new ClassNode[upper.length]; + for (int j = 0; j < upper.length; j++) { + upperCorrected[i] = correctToGenericsSpecRecurse(genericsSpec, upper[j]); + } + upper = upperCorrected; + } + ClassNode lower = old.getLowerBound(); + ClassNode newLower = correctToGenericsSpecRecurse(genericsSpec, lower); + if (lower == newLower && upper == newUpper) { + newTypes[i] = oldPlaceHolders[i]; + } else { + ClassNode newPlaceHolder = ClassHelper.make(old.getName()); + GenericsType gt = new GenericsType(newPlaceHolder, newUpper, newLower); + gt.setPlaceholder(true); + newTypes[i] = gt; + } + } + } + return newTypes; + } + + /** + * Try to get the parameterized type from the cache. + * 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) { + SoftReference sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), new EvictableCache.ValueProvider>() { + @Override + public SoftReference provide(ParameterizedTypeCacheKey key) { + return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType())); + } + }); + + return null == sr ? null : sr.get(); + } + + /** + * Get the parameterized type by search the whole class hierarchy according to generics class and actual receiver. + * {@link #findParameterizedTypeFromCache(ClassNode, ClassNode)} is strongly recommended for better performance. + * + * @param genericsClass the generics class + * @param actualType the actual type + * @return the parameterized type + */ + public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType) { + ClassNode parameterizedType = null; + + if (null == genericsClass.getGenericsTypes()) { + return parameterizedType; + } + + GenericsType[] declaringGenericsTypes = genericsClass.getGenericsTypes(); + + List classNodeList = new LinkedList<>(getAllSuperClassesAndInterfaces(actualType)); + classNodeList.add(0, actualType); + + for (ClassNode cn : classNodeList) { + if (cn == genericsClass) { + continue; + } + + if (!genericsClass.equals(cn.redirect())) { + continue; + } + + if (isGenericsTypeArraysLengthEqual(declaringGenericsTypes, cn.getGenericsTypes())) { + parameterizedType = cn; + break; + } + } + + return parameterizedType; + } + + private static boolean isGenericsTypeArraysLengthEqual(GenericsType[] declaringGenericsTypes, GenericsType[] actualGenericsTypes) { + return null != actualGenericsTypes && declaringGenericsTypes.length == actualGenericsTypes.length; + } + + private static List getAllSuperClassesAndInterfaces(ClassNode actualReceiver) { + List superClassAndInterfaceList = new LinkedList<>(); + List allSuperClassNodeList = getAllUnresolvedSuperClasses(actualReceiver); + superClassAndInterfaceList.addAll(allSuperClassNodeList); + superClassAndInterfaceList.addAll(actualReceiver.getAllInterfaces()); + + for (ClassNode superClassNode : allSuperClassNodeList) { + superClassAndInterfaceList.addAll(superClassNode.getAllInterfaces()); + } + + return superClassAndInterfaceList; + } + + private static List getAllUnresolvedSuperClasses(ClassNode actualReceiver) { + List superClassNodeList = new LinkedList<>(); + + for (ClassNode cn = actualReceiver.getUnresolvedSuperClass(); null != cn && ClassHelper.OBJECT_TYPE != cn; cn = cn.getUnresolvedSuperClass()) { + superClassNodeList.add(cn); + } + + return superClassNodeList; + } + + private static final EvictableCache> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64); + + /** + * map declaring generics type to actual generics type, e.g. GROOVY-7204: + * declaring generics types: T, S extends Serializable + * actual generics types : String, Long + * + * the result map is [ + * T: String, + * S: Long + * ] + * + * The resolved types can not help us to choose methods correctly if the argument is a string: T: Object, S: Serializable + * so we need actual types: T: String, S: Long + */ + public static Map makeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver) { + ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver); + + if (null == parameterizedType) { + return Collections.emptyMap(); + } + + GenericsType[] declaringGenericsTypes = declaringClass.getGenericsTypes(); + GenericsType[] actualGenericsTypes = parameterizedType.getGenericsTypes(); + + Map result = new LinkedHashMap<>(); + for (int i = 0, n = declaringGenericsTypes.length; i < n; i++) { + result.put(declaringGenericsTypes[i], actualGenericsTypes[i]); + } + + return result; + } + + /** + * Get the actual type according to the placeholder name + * + * @param placeholderName the placeholder name, e.g. T, E + * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode} + * @return the actual type + */ + public static ClassNode findActualTypeByGenericsPlaceholderName(String placeholderName, Map genericsPlaceholderAndTypeMap) { + for (Map.Entry entry : genericsPlaceholderAndTypeMap.entrySet()) { + GenericsType declaringGenericsType = entry.getKey(); + + if (placeholderName.equals(declaringGenericsType.getName())) { + return entry.getValue().getType().redirect(); + } + } + + return null; + } + + private static class ParameterizedTypeCacheKey { + private ClassNode genericsClass; + private ClassNode actualType; + + public ParameterizedTypeCacheKey(ClassNode genericsClass, ClassNode actualType) { + this.genericsClass = genericsClass; + this.actualType = actualType; + } + + public ClassNode getGenericsClass() { + return genericsClass; + } + + @SuppressWarnings("unused") + public void setGenericsClass(ClassNode genericsClass) { + this.genericsClass = genericsClass; + } + + public ClassNode getActualType() { + return actualType; + } + + @SuppressWarnings("unused") + public void setActualType(ClassNode actualType) { + this.actualType = actualType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ParameterizedTypeCacheKey cacheKey = (ParameterizedTypeCacheKey) o; + + return genericsClass == cacheKey.genericsClass && + actualType == cacheKey.actualType; + } + + @Override + public int hashCode() { + return Objects.hash(genericsClass, actualType); + } + } +} diff --git a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNode.java b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNode.java index aac7788ad5..9e99c87138 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNode.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/codehaus/jdt/groovy/internal/compiler/ast/JDTClassNode.java @@ -217,7 +217,7 @@ private void initializeMembers() { // the preceding code sets 'groovyDecl', later used to 'initializeProperties'. // From this point onward... the code is only about initializing fields, constructors and methods. - if (redirect != null) { + if (isRedirectNode()) { // The code in ClassNode seems set up to get field information *always* from the end of the 'redirect' chain. // So, the redirect target should be responsible for its own members initialisation.