org.codehaus.groovy.control.ResolveVisitor Maven / Gradle / Ivy
Show all versions of groovy Show documentation
/*
* 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.control;
import groovy.lang.Tuple2;
import org.apache.groovy.ast.tools.ExpressionUtils;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.DynamicVariable;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.GenericsType.GenericsTypeName;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.VariableScope;
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
import org.codehaus.groovy.ast.expr.BinaryExpression;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.DeclarationExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.ast.expr.SpreadMapExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.CatchStatement;
import org.codehaus.groovy.ast.stmt.ForStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.runtime.memoize.UnlimitedConcurrentCache;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.transform.trait.Traits;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import org.objectweb.asm.Opcodes;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import static groovy.lang.Tuple.tuple;
import static org.codehaus.groovy.ast.ClassHelper.isObjectType;
import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
/**
* Visitor to resolve types and convert VariableExpression to
* ClassExpressions if needed. The ResolveVisitor will try to
* find the Class for a ClassExpression and prints an error if
* it fails to do so. Constructions like C[], foo as C, (C) foo
* will force creation of a ClassExpression for C
*
* Note: the method to start the resolving is {@link #startResolving(ClassNode,SourceUnit)}.
*/
public class ResolveVisitor extends ClassCodeExpressionTransformer {
// note: BigInteger and BigDecimal are also imported by default
// `java.util` is used much frequently than other two java packages(`java.io` and `java.net`), so place java.util before the two packages
public static final String[] DEFAULT_IMPORTS = {"java.lang.", "java.util.", "java.io.", "java.net.", "groovy.lang.", "groovy.util."};
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final String QUESTION_MARK = "?";
private final CompilationUnit compilationUnit;
private ClassNodeResolver classNodeResolver;
private SourceUnit source;
private ClassNode currentClass;
private ImportNode currentImport;
private MethodNode currentMethod;
private VariableScope currentScope;
private Map genericParameterNames = new HashMap<>();
private final Set fieldTypesChecked = new HashSet<>();
private boolean checkingVariableTypeInDeclaration;
private boolean inClosure, inPropertyExpression;
private boolean isTopLevelProperty = true;
/*package*/ int phase; // sub-divide visit
/**
* A ConstructedNestedClass consists of an outer class and a name part, denoting a
* nested class with an unknown number of levels down. This allows resolve tests to
* skip this node for further inner class searches and combinations with imports, since
* the outer class we know is already resolved.
*/
private static class ConstructedNestedClass extends ClassNode {
final ClassNode knownEnclosingType;
public ConstructedNestedClass(final ClassNode outer, final String inner) {
super(outer.getName() + "$" + inner.replace('.', '$'), Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
this.knownEnclosingType = outer;
this.isPrimaryNode = false;
}
@Override
public String getUnresolvedName() {
// outer class (aka knownEnclosingType) may have aliased name that should be reflected here too
return super.getUnresolvedName().replace(knownEnclosingType.getName(), knownEnclosingType.getUnresolvedName());
}
@Override
public boolean hasPackageName() {
if (redirect() != this) return super.hasPackageName();
return knownEnclosingType.hasPackageName();
}
@Override
public String setName(final String name) {
if (redirect() != this) {
return super.setName(name);
} else {
throw new GroovyBugError("ConstructedNestedClass#setName should not be called");
}
}
}
/**
* we use ConstructedClassWithPackage to limit the resolving the compiler
* does when combining package names and class names. The idea
* that if we use a package, then we do not want to replace the
* '.' with a '$' for the package part, only for the class name
* part. There is also the case of a imported class, so this logic
* can't be done in these cases...
*/
private static class ConstructedClassWithPackage extends ClassNode {
final String prefix;
String className;
public ConstructedClassWithPackage(final String pkg, final String name) {
super(pkg + name, Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
isPrimaryNode = false;
this.prefix = pkg;
this.className = name;
}
@Override
public String getName() {
if (redirect() != this)
return super.getName();
return prefix + className;
}
@Override
public boolean hasPackageName() {
if (redirect() != this)
return super.hasPackageName();
return className.indexOf('.') != -1;
}
@Override
public String setName(final String name) {
if (redirect() != this) {
return super.setName(name);
} else {
throw new GroovyBugError("ConstructedClassWithPackage#setName should not be called");
}
}
}
/**
* we use LowerCaseClass to limit the resolving the compiler
* does for vanilla names starting with a lower case letter. The idea
* that if we use a vanilla name with a lower case letter, that this
* is in most cases no class. If it is a class the class needs to be
* imported explicitly. The effect is that in an expression like
* "def foo = bar" we do not have to use a loadClass call to check the
* name foo and bar for being classes. Instead we will ask the module
* for an alias for this name which is much faster.
*/
private static class LowerCaseClass extends ClassNode {
final String className;
public LowerCaseClass(final String name) {
super(name, Opcodes.ACC_PUBLIC, ClassHelper.OBJECT_TYPE);
isPrimaryNode = false;
this.className = name;
}
@Override
public String getName() {
if (redirect() != this)
return super.getName();
return className;
}
@Override
public boolean hasPackageName() {
if (redirect() != this)
return super.hasPackageName();
return false;
}
@Override
public String setName(final String name) {
if (redirect() != this) {
return super.setName(name);
} else {
throw new GroovyBugError("LowerCaseClass#setName should not be called");
}
}
}
@SuppressWarnings("serial")
static class Interrupt extends CompilationFailedException {
private Interrupt(final ProcessingUnit unit) {
super(Phases.SEMANTIC_ANALYSIS, unit);
}
}
//--------------------------------------------------------------------------
public ResolveVisitor(final CompilationUnit compilationUnit) {
this.compilationUnit = compilationUnit;
}
public void setClassNodeResolver(final ClassNodeResolver classNodeResolver) {
this.classNodeResolver = classNodeResolver;
}
public void startResolving(final ClassNode node, final SourceUnit source) {
this.source = source;
visitClass(node);
}
@Override
protected SourceUnit getSourceUnit() {
return source;
}
@Override
public void visitMethod(final MethodNode node) {
super.visitMethod(node);
visitGenericsTypeAnnotations(node);
visitTypeAnnotations(node.getReturnType());
}
@Override
protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
VariableScope oldScope = currentScope;
currentScope = node.getVariableScope();
Map oldPNames = genericParameterNames;
genericParameterNames = node.isStatic() && !Traits.isTrait(node.getDeclaringClass())
? new HashMap<>() : new HashMap<>(genericParameterNames);
resolveGenericsHeader(node.getGenericsTypes());
Parameter[] paras = node.getParameters();
for (Parameter p : paras) {
p.setInitialExpression(transform(p.getInitialExpression()));
resolveOrFail(p.getType(), p.getType());
visitAnnotations(p);
}
resolveOrFail(node.getReturnType(), node);
if (node.getExceptions() != null) {
for (ClassNode t : node.getExceptions()) {
resolveOrFail(t, node);
}
}
MethodNode oldCurrentMethod = currentMethod;
currentMethod = node;
super.visitConstructorOrMethod(node, isConstructor);
currentMethod = oldCurrentMethod;
genericParameterNames = oldPNames;
currentScope = oldScope;
}
@Override
public void visitField(final FieldNode node) {
ClassNode t = node.getType();
if (!fieldTypesChecked.contains(node)) {
resolveOrFail(t, node);
}
super.visitField(node);
}
@Override
public void visitProperty(final PropertyNode node) {
Map oldPNames = genericParameterNames;
if (node.isStatic() && !Traits.isTrait(node.getDeclaringClass())) {
genericParameterNames = new HashMap<>();
}
ClassNode t = node.getType();
resolveOrFail(t, node);
super.visitProperty(node);
fieldTypesChecked.add(node.getField());
genericParameterNames = oldPNames;
}
private void resolveOrFail(final ClassNode type, final ASTNode node) {
resolveOrFail(type, "", node);
}
private void resolveOrFail(final ClassNode type, final String msg, final ASTNode node) {
resolveOrFail(type, msg, node, false);
}
private void resolveOrFail(final ClassNode type, final String msg, final ASTNode node, final boolean preferImports) {
visitTypeAnnotations(type);
if (preferImports) {
resolveGenericsTypes(type.getGenericsTypes());
if (resolveAliasFromModule(type)) return;
}
if (resolve(type)) return;
if (resolveToInner(type)) return;
addError("unable to resolve class " + type.toString(false) + msg, node);
}
protected boolean resolveToInner(final ClassNode type) {
// we do not do our name mangling to find an inner class
// if the type is a ConstructedClassWithPackage, because in this case we
// are resolving the name at a different place already
if (type instanceof ConstructedClassWithPackage) return false;
if (type instanceof ConstructedNestedClass) return false;
// GROOVY-8715
ClassNode t = type;
while (t.isArray()) {
t = t.getComponentType();
}
String name = t.getName(), temp = name;
while (temp.lastIndexOf('.') != -1) {
temp = replaceLastPointWithDollar(temp);
t.setName(temp);
if (resolve(t, true, false, false)) {
return true;
}
}
t.setName(name);
return false;
}
protected boolean resolve(final ClassNode type) {
return resolve(type, true, true, true);
}
protected boolean resolve(final ClassNode type, final boolean testModuleImports, final boolean testDefaultImports, final boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
GenericsType genericsType = genericParameterNames.get(new GenericsTypeName(typeName));
if (genericsType != null) {
type.setRedirect(genericsType.getType());
type.setGenericsTypes(new GenericsType[]{genericsType});
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return (!type.hasPackageName() && resolveNestedClass(type)) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
(testDefaultImports && !type.hasPackageName() && resolveFromDefaultImports(type)) ||
resolveToOuter(type) ||
(testStaticInnerClasses && type.hasPackageName() && resolveFromStaticInnerClasses(type));
}
protected boolean resolveNestedClass(final ClassNode type) {
if (type instanceof ConstructedNestedClass || type instanceof ConstructedClassWithPackage) return false;
ClassNode cn = currentClass;
Set cycleCheck = new HashSet<>();
// GROOVY-4043: for type "X", try "A$X" with each type in the class hierarchy (except for Object)
for (; cn != null && cycleCheck.add(cn) && !isObjectType(cn); cn = cn.getSuperClass()) {
if (setRedirect(type, cn)) return true;
}
// Another case we want to check here is if we are in a
// nested class A$B$C and want to access B without
// qualifying it by A.B. A alone will work, since that
// is the qualified (minus package) name of that class
// anyway.
List outerClasses = currentClass.getOuterClasses();
if (!outerClasses.isEmpty()) {
// Since we have B and want to get A we start with the most
// outer class, put them together and then see if that does
// already exist. In case of B from within A$B we are done
// after the first step already. In case of for example
// A.B.C.D.E.F and accessing E from F we test A$E=failed,
// A$B$E=failed, A$B$C$E=fail, A$B$C$D$E=success.
for (ListIterator it = outerClasses.listIterator(outerClasses.size()); it.hasPrevious(); ) {
cn = it.previous();
if (setRedirect(type, cn)) return true;
}
}
return false;
}
private boolean setRedirect(final ClassNode type, final ClassNode classToCheck) {
String typeName = type.getName();
Predicate resolver = (ClassNode maybeOuter) -> {
if (!typeName.equals(maybeOuter.getName())) {
ClassNode maybeNested = new ConstructedNestedClass(maybeOuter, typeName);
if (resolveFromCompileUnit(maybeNested) || resolveToOuter(maybeNested)) {
type.setRedirect(maybeNested);
return true;
}
}
return false;
};
if (resolver.test(classToCheck)) {
if (currentClass != classToCheck && !currentClass.getOuterClasses().contains(classToCheck) && !isVisibleNestedClass(type.redirect(), currentClass)) {
type.setRedirect(null);
} else {
return true;
}
}
if (classToCheck.getInterfaces().length > 0) {
for (ClassNode face : classToCheck.getAllInterfaces()) {
if (resolver.test(face)) {
return true;
}
}
}
return false;
}
private static String replaceLastPointWithDollar(final String name) {
int lastPointIndex = name.lastIndexOf('.');
return name.substring(0, lastPointIndex) + "$" + name.substring(lastPointIndex + 1);
}
protected boolean resolveFromStaticInnerClasses(final ClassNode type) {
// a class consisting of a vanilla name can never be
// a static inner class, because at least one dot is
// required for this. Example: foo.bar -> foo$bar
if (!(type instanceof LowerCaseClass || type instanceof ConstructedNestedClass)) {
if (type instanceof ConstructedClassWithPackage) {
// we replace '.' only in the className part
// with '$' to find an inner class. The case that
// the package is really a class is handled elsewhere
ConstructedClassWithPackage tmp = (ConstructedClassWithPackage) type;
String savedName = tmp.className;
tmp.className = replaceLastPointWithDollar(savedName);
if (resolve(tmp, false, true, true)) {
type.setRedirect(tmp.redirect());
return true;
}
tmp.className = savedName;
} else {
String savedName = type.getName();
type.setName(replaceLastPointWithDollar(savedName));
if (resolve(type, false, true, true)) return true;
type.setName(savedName);
}
}
return false;
}
protected boolean resolveFromDefaultImports(final ClassNode type) {
// we do not resolve a vanilla name starting with a lower case letter
// try to resolve against a default import, because we know that the
// default packages do not contain classes like these
if (!(type instanceof LowerCaseClass)) {
String typeName = type.getName();
Set packagePrefixSet = DEFAULT_IMPORT_CLASS_AND_PACKAGES_CACHE.get(typeName);
if (packagePrefixSet != null) {
// if the type name was resolved before, we can try the successfully resolved packages first, which are much less and very likely successful to resolve.
// As a result, we can avoid trying other default import packages and further resolving, which can improve the resolving performance to some extent.
if (resolveFromDefaultImports(type, packagePrefixSet.toArray(EMPTY_STRING_ARRAY))) {
return true;
}
}
if (resolveFromDefaultImports(type, DEFAULT_IMPORTS)) {
return true;
}
if ("BigInteger".equals(typeName)) {
type.setRedirect(ClassHelper.BigInteger_TYPE);
return true;
}
if ("BigDecimal".equals(typeName)) {
type.setRedirect(ClassHelper.BigDecimal_TYPE);
return true;
}
}
return false;
}
private static final Map> DEFAULT_IMPORT_CLASS_AND_PACKAGES_CACHE = new UnlimitedConcurrentCache<>();
static {
DEFAULT_IMPORT_CLASS_AND_PACKAGES_CACHE.putAll(VMPluginFactory.getPlugin().getDefaultImportClasses(DEFAULT_IMPORTS));
}
protected boolean resolveFromDefaultImports(final ClassNode type, final String[] packagePrefixes) {
String typeName = type.getName();
for (String packagePrefix : packagePrefixes) {
// We limit the inner class lookups here by using ConstructedClassWithPackage.
// This way only the name will change, the packagePrefix will
// not be included in the lookup. The case where the
// packagePrefix is really a class is handled elsewhere.
// WARNING: This code does not expect a class that has a static
// inner class in DEFAULT_IMPORTS
ConstructedClassWithPackage tmp = new ConstructedClassWithPackage(packagePrefix, typeName);
if (resolve(tmp, false, false, false)) {
type.setRedirect(tmp.redirect());
if (DEFAULT_IMPORTS == packagePrefixes) { // Only the non-cached type and packages should be cached
Set packagePrefixSet = DEFAULT_IMPORT_CLASS_AND_PACKAGES_CACHE.computeIfAbsent(typeName, key -> new HashSet<>(2));
packagePrefixSet.add(packagePrefix);
}
return true;
}
}
return false;
}
protected boolean resolveFromCompileUnit(final ClassNode type) {
// look into the compile unit if there is a class with that name
CompileUnit compileUnit = currentClass.getCompileUnit();
if (compileUnit == null) return false;
ClassNode cuClass = compileUnit.getClass(type.getName());
if (cuClass != null) {
if (type != cuClass) type.setRedirect(cuClass);
return true;
}
return false;
}
private void ambiguousClass(final ClassNode type, final ClassNode iType, final String name) {
if (type.getName().equals(iType.getName())) {
addError("reference to " + name + " is ambiguous, both class " + type.getName() + " and " + iType.getName() + " match", type);
} else {
type.setRedirect(iType);
}
}
private boolean resolveAliasFromModule(final ClassNode type) {
// In case of getting a ConstructedClassWithPackage here we do not do checks for partial
// matches with imported classes. The ConstructedClassWithPackage is already a constructed
// node and any subclass resolving will then take place elsewhere
if (type instanceof ConstructedClassWithPackage) return false;
ModuleNode module = currentClass.getModule();
if (module == null) return false;
String name = type.getName();
// check module node imports aliases
// the while loop enables a check for inner classes which are not fully imported,
// but visible as the surrounding class is imported and the inner class is public/protected static
String pname = name;
int index = name.length();
/*
* we have a name foo.bar and an import foo.foo. This means foo.bar is possibly
* foo.foo.bar rather than foo.bar. This means to cut at the dot in foo.bar and
* foo for import
*/
do {
pname = name.substring(0, index);
ClassNode aliasedNode = null;
ImportNode importNode = module.getImport(pname);
if (importNode != null && importNode != currentImport) {
aliasedNode = importNode.getType();
}
if (aliasedNode == null) {
importNode = module.getStaticImports().get(pname);
if (importNode != null && importNode != currentImport) {
// static alias only for inner classes and must be at end of chain
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), importNode.getFieldName());
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
type.setRedirect(tmp.redirect());
return true;
}
}
}
if (aliasedNode != null) {
if (pname.length() == name.length()) {
// full match
// We can compare here by length, because pname is always
// a substring of name, so same length means they are equal.
type.setRedirect(aliasedNode);
return true;
} else {
//partial match
// At this point we know that we have a match for pname. This may
// mean, that name[pname.length()..<-1] is a static inner class.
// For this the rest of the name does not need any dots in its name.
// It is either completely a inner static class or it is not.
// Since we do not want to have useless lookups we create the name
// completely and use a ConstructedClassWithPackage to prevent lookups against the package.
String className = aliasedNode.getNameWithoutPackage() + "$" + name.substring(pname.length() + 1).replace('.', '$');
ConstructedClassWithPackage tmp = new ConstructedClassWithPackage(aliasedNode.getPackageName() + ".", className);
if (resolve(tmp, true, true, false)) {
type.setRedirect(tmp.redirect());
return true;
}
}
}
index = pname.lastIndexOf('.');
} while (index != -1);
return false;
}
protected boolean resolveFromModule(final ClassNode type, final boolean testModuleImports) {
if (type instanceof ConstructedNestedClass) return false;
// we decided if we have a vanilla name starting with a lower case
// letter that we will not try to resolve this name against .*
// imports. Instead a full import is needed for these.
// resolveAliasFromModule will do this check for us. This method
// does also check the module contains a class in the same package
// of this name. This check is not done for vanilla names starting
// with a lower case letter anymore
if (type instanceof LowerCaseClass) {
return resolveAliasFromModule(type);
}
String name = type.getName();
ModuleNode module = currentClass.getModule();
if (module == null) return false;
boolean newNameUsed = false;
// we add a package if there is none yet and the module has one. But we
// do not add that if the type is a ConstructedClassWithPackage. The code in ConstructedClassWithPackage
// hasPackageName() will return true if ConstructedClassWithPackage#className has no dots.
// but since the prefix may have them and the code there does ignore that
// fact. We check here for ConstructedClassWithPackage.
if (!type.hasPackageName() && module.hasPackageName() && !(type instanceof ConstructedClassWithPackage)) {
type.setName(module.getPackageName() + name);
newNameUsed = true;
}
// look into the module node if there is a class with that name
List moduleClasses = module.getClasses();
for (ClassNode mClass : moduleClasses) {
if (mClass.getName().equals(type.getName())) {
if (mClass != type) type.setRedirect(mClass);
return true;
}
}
if (newNameUsed) type.setName(name);
if (testModuleImports) {
if (resolveAliasFromModule(type)) return true;
if (module.hasPackageName()) {
// check package this class is defined in. The usage of ConstructedClassWithPackage here
// means, that the module package will not be involved when the
// compiler tries to find an inner class.
ClassNode tmp = new ConstructedClassWithPackage(module.getPackageName(), name);
if (resolve(tmp, false, false, false)) {
ambiguousClass(type, tmp, name);
return true;
}
}
// check static imports for static inner types
for (ImportNode importNode : module.getStaticImports().values()) { // this may be fully redundant with resolveAliasFromModule
if (importNode.getFieldName().equals(name)) {
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
type.setRedirect(tmp.redirect());
return true;
}
}
}
for (ImportNode importNode : module.getStaticStarImports().values()) {
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
ambiguousClass(type, tmp, name);
return true;
}
}
// check star imports ("import foo.*" or "import foo.Bar.*")
for (ImportNode importNode : module.getStarImports()) {
if (importNode.getType() != null) {
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
ambiguousClass(type, tmp, name);
return true;
}
} else {
ClassNode tmp = new ConstructedClassWithPackage(importNode.getPackageName(), name);
if (resolve(tmp, false, false, true)) {
ambiguousClass(type, tmp, name);
return true;
}
}
}
}
return false;
}
protected boolean resolveToOuter(final ClassNode type) {
String name = type.getName();
if (classNodeResolver == null)
classNodeResolver = new ClassNodeResolver();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
} else if (!currentClass.getModule().hasPackageName() || name.indexOf('.') != -1) {
ClassNodeResolver.LookupResult lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr != null) {
if (lr.isSourceUnit()) {
currentClass.getCompileUnit().addClassNodeToCompile(type, lr.getSourceUnit());
throw new Interrupt(compilationUnit); // GROOVY-10300, et al.: restart resolve
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
}
return false;
}
@Override
public Expression transform(final Expression exp) {
if (exp == null) return null;
Expression ret;
if (exp instanceof VariableExpression) {
ret = transformVariableExpression((VariableExpression) exp);
} else if (exp.getClass() == PropertyExpression.class) {
ret = transformPropertyExpression((PropertyExpression) exp);
} else if (exp instanceof DeclarationExpression) {
ret = transformDeclarationExpression((DeclarationExpression) exp);
} else if (exp instanceof BinaryExpression) {
ret = transformBinaryExpression((BinaryExpression) exp);
} else if (exp instanceof MethodCallExpression) {
ret = transformMethodCallExpression((MethodCallExpression) exp);
} else if (exp instanceof ClosureExpression) {
ret = transformClosureExpression((ClosureExpression) exp);
} else if (exp instanceof ConstructorCallExpression) {
ret = transformConstructorCallExpression((ConstructorCallExpression) exp);
} else if (exp instanceof AnnotationConstantExpression) {
ret = transformAnnotationConstantExpression((AnnotationConstantExpression) exp);
} else {
resolveOrFail(exp.getType(), exp);
ret = exp.transformExpression(this);
}
if (ret != null && ret != exp) {
ret.setSourcePosition(exp);
}
return ret;
}
private static String lookupClassName(final PropertyExpression pe) {
boolean doInitialClassTest = true;
StringBuilder name = new StringBuilder(32);
// this loop builds a name from right to left each name part separated by "."
for (Expression expr = pe; expr != null && name != null; expr = ((PropertyExpression) expr).getObjectExpression()) {
if (expr instanceof VariableExpression) {
VariableExpression ve = (VariableExpression) expr;
// stop at super and this
if (ve.isSuperExpression() || ve.isThisExpression()) {
return null;
}
String varName = ve.getName();
Tuple2 classNameInfo = makeClassName(doInitialClassTest, name, varName);
name = classNameInfo.getV1();
doInitialClassTest = classNameInfo.getV2();
break;
}
// anything other than PropertyExpressions or
// VariableExpressions will stop resolving
if (expr.getClass() != PropertyExpression.class) {
return null;
}
String property = ((PropertyExpression) expr).getPropertyAsString();
// the class property stops resolving, dynamic property names too
if (property == null || property.equals("class")) {
return null;
}
Tuple2 classNameInfo = makeClassName(doInitialClassTest, name, property);
name = classNameInfo.getV1();
doInitialClassTest = classNameInfo.getV2();
}
if (name == null || name.length() == 0) return null;
return name.toString();
}
private static Tuple2 makeClassName(final boolean doInitialClassTest, final StringBuilder name, final String varName) {
if (doInitialClassTest) {
// we are at the first name part. This is the right most part.
// If this part is in lower case, then we do not need a class
// check. other parts of the property expression will be tested
// by a different method call to this method, so foo.Bar.bar
// can still be resolved to the class foo.Bar and the static
// field bar.
if (!testVanillaNameForClass(varName)) {
return tuple(null, Boolean.TRUE);
} else {
return tuple(new StringBuilder(varName), Boolean.FALSE);
}
}
name.insert(0, varName + ".");
return tuple(name, Boolean.FALSE);
}
// iterate from the inner most to the outer and check for classes
// this check will ignore a .class property, for Example Integer.class will be
// a PropertyExpression with the ClassExpression of Integer as objectExpression
// and class as property
private static Expression correctClassClassChain(final PropertyExpression pe) {
LinkedList stack = new LinkedList();
ClassExpression found = null;
for (Expression it = pe; it != null; it = ((PropertyExpression) it).getObjectExpression()) {
if (it instanceof ClassExpression) {
found = (ClassExpression) it;
break;
} else if (!(it.getClass() == PropertyExpression.class)) {
return pe;
}
stack.addFirst(it);
}
if (found == null) return pe;
if (stack.isEmpty()) return pe;
Object stackElement = stack.removeFirst();
if (!(stackElement.getClass() == PropertyExpression.class)) return pe;
PropertyExpression classPropertyExpression = (PropertyExpression) stackElement;
String propertyNamePart = classPropertyExpression.getPropertyAsString();
if (propertyNamePart == null || !propertyNamePart.equals("class")) return pe;
found.setSourcePosition(classPropertyExpression);
if (stack.isEmpty()) return found;
stackElement = stack.removeFirst();
if (!(stackElement.getClass() == PropertyExpression.class)) return pe;
PropertyExpression classPropertyExpressionContainer = (PropertyExpression) stackElement;
classPropertyExpressionContainer.setObjectExpression(found);
return pe;
}
protected Expression transformPropertyExpression(PropertyExpression pe) {
boolean itlp = isTopLevelProperty;
boolean ipe = inPropertyExpression;
Expression objectExpression = pe.getObjectExpression();
inPropertyExpression = true;
isTopLevelProperty = (objectExpression.getClass() != PropertyExpression.class);
objectExpression = transform(objectExpression);
// we handle the property part as if it were not part of the property
inPropertyExpression = false;
Expression property = transform(pe.getProperty());
isTopLevelProperty = itlp;
inPropertyExpression = ipe;
boolean spreadSafe = pe.isSpreadSafe();
PropertyExpression old = pe;
pe = new PropertyExpression(objectExpression, property, pe.isSafe());
pe.setSpreadSafe(spreadSafe);
pe.setSourcePosition(old);
String className = lookupClassName(pe);
if (className != null) {
ClassNode type = ClassHelper.make(className);
if (resolve(type)) {
return new ClassExpression(type);
}
}
if (objectExpression instanceof ClassExpression && pe.getPropertyAsString() != null) {
// possibly an inner class (or inherited inner class)
ClassExpression ce = (ClassExpression) objectExpression;
ClassNode classNode = ce.getType();
while (classNode != null) {
ClassNode type = new ConstructedNestedClass(classNode, pe.getPropertyAsString());
if (resolve(type, false, false, false)) {
if (classNode == ce.getType() || isVisibleNestedClass(type, ce.getType())) {
return new ClassExpression(type);
}
}
classNode = classNode.getSuperClass();
}
}
Expression ret = pe;
checkThisAndSuperAsPropertyAccess(pe);
if (isTopLevelProperty) ret = correctClassClassChain(pe);
return ret;
}
private static boolean isVisibleNestedClass(final ClassNode innerType, final ClassNode outerType) {
int modifiers = innerType.getModifiers();
return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)
|| (!Modifier.isPrivate(modifiers) && Objects.equals(innerType.getPackageName(), outerType.getPackageName()));
}
private boolean directlyImplementsTrait(final ClassNode trait) {
ClassNode[] interfaces = currentClass.getInterfaces();
if (interfaces == null) {
return currentClass.getSuperClass().equals(trait);
}
for (ClassNode node : interfaces) {
if (node.equals(trait)) {
return true;
}
}
return currentClass.getSuperClass().equals(trait);
}
private void checkThisAndSuperAsPropertyAccess(final PropertyExpression expression) {
if (expression.isImplicitThis()) return;
String prop = expression.getPropertyAsString();
if (prop == null) return;
if (!prop.equals("this") && !prop.equals("super")) return;
ClassNode type = expression.getObjectExpression().getType();
if (expression.getObjectExpression() instanceof ClassExpression) {
if (!(currentClass instanceof InnerClassNode) && !Traits.isTrait(type)) {
addError("The usage of 'Class.this' and 'Class.super' is only allowed in nested/inner classes.", expression);
return;
}
if (currentScope != null && !currentScope.isInStaticContext() && Traits.isTrait(type) && "super".equals(prop) && directlyImplementsTrait(type)) {
return;
}
ClassNode iterType = currentClass;
while (iterType != null) {
if (iterType.equals(type)) break;
iterType = iterType.getOuterClass();
}
if (iterType == null) {
addError("The class '" + type.getName() + "' needs to be an outer class of '" +
currentClass.getName() + "' when using '.this' or '.super'.", expression);
}
if (!Modifier.isStatic(currentClass.getModifiers())) return;
if (currentScope != null && !currentScope.isInStaticContext()) return;
addError("The usage of 'Class.this' and 'Class.super' within static nested class '" +
currentClass.getName() + "' is not allowed in a static context.", expression);
}
}
protected Expression transformVariableExpression(final VariableExpression ve) {
visitAnnotations(ve);
Variable v = ve.getAccessedVariable();
if(!(v instanceof DynamicVariable) && !checkingVariableTypeInDeclaration) {
/*
* GROOVY-4009: when a normal variable is simply being used, there is no need to try to
* resolve its type. Variable type resolve should proceed only if the variable is being declared.
*/
return ve;
}
if (v instanceof DynamicVariable) {
String name = ve.getName();
ClassNode t = ClassHelper.make(name);
// asking isResolved here allows to check if a primitive
// type name like "int" was used to make t. In such a case
// we have nothing left to do.
boolean isClass = t.isResolved();
if (!isClass) {
// It was no primitive type, so next we see if the name,
// which is a vanilla name, starts with a lower case letter.
// In that case we change it to a LowerCaseClass to let the
// compiler skip the resolving at several places in this class.
if (Character.isLowerCase(name.charAt(0))) {
t = new LowerCaseClass(name);
}
isClass = resolve(t);
}
if (isClass) {
// the name is a type so remove it from the scoping
// as it is only a classvariable, it is only in
// referencedClassVariables, but must be removed
// for each parentscope too
for (VariableScope scope = currentScope; scope != null && !scope.isRoot(); scope = scope.getParent()) {
if (scope.removeReferencedClassVariable(ve.getName()) == null) break;
}
return new ClassExpression(t);
}
}
resolveOrFail(ve.getType(), ve);
ClassNode origin = ve.getOriginType();
if (origin != ve.getType()) resolveOrFail(origin, ve);
return ve;
}
private static boolean testVanillaNameForClass(final String name) {
if (name == null || name.length() == 0) return false;
return !Character.isLowerCase(name.charAt(0));
}
protected Expression transformBinaryExpression(final BinaryExpression be) {
Expression left = transform(be.getLeftExpression());
if (be.getOperation().isA(Types.ASSIGNMENT_OPERATOR) && left instanceof ClassExpression) {
ClassExpression ce = (ClassExpression) left;
String error = "you tried to assign a value to the class '" + ce.getType().getName() + "'";
if (ce.getType().isScript()) {
error += ". Do you have a script with this name?";
}
addError(error, be.getLeftExpression());
return be;
}
if (left instanceof ClassExpression && be.getOperation().isOneOf(
new int[]{Types.ARRAY_EXPRESSION, Types.SYNTH_LIST, Types.SYNTH_MAP})) {
if (be.getRightExpression() instanceof ListExpression) {
ListExpression list = (ListExpression) be.getRightExpression();
if (list.getExpressions().isEmpty()) {
return new ClassExpression(left.getType().makeArray());
} else {
// maybe we have C[k1:v1, k2:v2] -> should become (C)([k1:v1, k2:v2])
boolean map = true;
for (Expression expression : list.getExpressions()) {
if (!(expression instanceof MapEntryExpression)) {
map = false;
break;
}
}
if (map) {
MapExpression me = new MapExpression();
for (Expression expression : list.getExpressions()) {
me.addMapEntryExpression((MapEntryExpression) transform(expression));
}
me.setSourcePosition(list);
return CastExpression.asExpression(left.getType(), me);
}
}
} else if (be.getRightExpression() instanceof SpreadMapExpression) {
// we have C[*:map] -> should become (C) map
SpreadMapExpression mapExpression = (SpreadMapExpression) be.getRightExpression();
Expression right = transform(mapExpression.getExpression());
return CastExpression.asExpression(left.getType(), right);
}
if (be.getRightExpression() instanceof MapEntryExpression) {
// may be we have C[k1:v1] -> should become (C)([k1:v1])
MapExpression me = new MapExpression();
me.addMapEntryExpression((MapEntryExpression) transform(be.getRightExpression()));
me.setSourcePosition(be.getRightExpression());
return new CastExpression(left.getType(), me);
}
}
Expression right = transform(be.getRightExpression());
be.setLeftExpression(left);
be.setRightExpression(right);
return be;
}
protected Expression transformClosureExpression(final ClosureExpression ce) {
boolean oldInClosure = inClosure;
inClosure = true;
for (Parameter para : getParametersSafe(ce)) {
ClassNode t = para.getType();
resolveOrFail(t, ce);
visitAnnotations(para);
if (para.hasInitialExpression()) {
para.setInitialExpression(transform(para.getInitialExpression()));
}
visitAnnotations(para);
}
Statement code = ce.getCode();
if (code != null) code.visit(this);
inClosure = oldInClosure;
return ce;
}
protected Expression transformConstructorCallExpression(final ConstructorCallExpression cce) {
if (!cce.isUsingAnonymousInnerClass()) { // GROOVY-9642
ClassNode cceType = cce.getType();
resolveOrFail(cceType, cce);
if (cceType.isAbstract()) {
addError("You cannot create an instance from the abstract " + getDescription(cceType) + ".", cce);
}
}
return cce.transformExpression(this);
}
private static String getDescription(final ClassNode node) {
return (node.isInterface() ? "interface" : "class") + " '" + node.getName() + "'";
}
protected Expression transformMethodCallExpression(final MethodCallExpression mce) {
Expression args = transform(mce.getArguments());
Expression method = transform(mce.getMethod());
Expression object = transform(mce.getObjectExpression());
resolveGenericsTypes(mce.getGenericsTypes());
MethodCallExpression ret = new MethodCallExpression(object, method, args);
ret.setGenericsTypes(mce.getGenericsTypes());
ret.setMethodTarget(mce.getMethodTarget());
ret.setImplicitThis(mce.isImplicitThis());
ret.setSpreadSafe(mce.isSpreadSafe());
ret.setSafe(mce.isSafe());
return ret;
}
protected Expression transformDeclarationExpression(final DeclarationExpression de) {
visitAnnotations(de);
Expression oldLeft = de.getLeftExpression();
checkingVariableTypeInDeclaration = true;
Expression left = transform(oldLeft);
checkingVariableTypeInDeclaration = false;
if (left instanceof ClassExpression) {
ClassExpression ce = (ClassExpression) left;
addError("you tried to assign a value to the class " + ce.getType().getName(), oldLeft);
return de;
}
Expression right = transform(de.getRightExpression());
if (right == de.getRightExpression()) {
fixDeclaringClass(de);
return de;
}
DeclarationExpression newDeclExpr = new DeclarationExpression(left, de.getOperation(), right);
newDeclExpr.setDeclaringClass(de.getDeclaringClass());
newDeclExpr.addAnnotations(de.getAnnotations());
newDeclExpr.copyNodeMetaData(de);
fixDeclaringClass(newDeclExpr);
return newDeclExpr;
}
// TODO: get normal resolving to set declaring class
private void fixDeclaringClass(final DeclarationExpression newDeclExpr) {
if (newDeclExpr.getDeclaringClass() == null && currentMethod != null) {
newDeclExpr.setDeclaringClass(currentMethod.getDeclaringClass());
}
}
protected Expression transformAnnotationConstantExpression(final AnnotationConstantExpression ace) {
AnnotationNode an = (AnnotationNode) ace.getValue();
ClassNode type = an.getClassNode();
resolveOrFail(type, " for annotation", an);
for (Map.Entry member : an.getMembers().entrySet()) {
member.setValue(transform(member.getValue()));
}
return ace;
}
private void visitTypeAnnotations(final ClassNode node) {
visitAnnotations(node.getTypeAnnotations());
visitGenericsTypeAnnotations(node);
}
private void visitGenericsTypeAnnotations(final ClassNode node) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (node.isUsingGenerics() && genericsTypes != null) {
visitGenericsTypeAnnotations(genericsTypes);
}
}
private void visitGenericsTypeAnnotations(final MethodNode node) {
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes != null) {
visitGenericsTypeAnnotations(genericsTypes);
}
}
private void visitGenericsTypeAnnotations(final GenericsType[] genericsTypes) {
for (GenericsType gt : genericsTypes) {
visitTypeAnnotations(gt.getType());
}
}
@Override
protected void visitAnnotation(final AnnotationNode node) {
resolveOrFail(node.getClassNode(), " for annotation", node, true);
for (Map.Entry member : node.getMembers().entrySet()) {
Expression value = transformInlineConstants(transform(member.getValue()));
checkAnnotationMemberValue(value);
member.setValue(value);
}
}
// resolve constant-looking expressions statically (do here as they get transformed away later)
private static Expression transformInlineConstants(final Expression exp) {
if (exp instanceof AnnotationConstantExpression) {
ConstantExpression ce = (ConstantExpression) exp;
if (ce.getValue() instanceof AnnotationNode) {
// replicate a little bit of AnnotationVisitor here
// because we can't wait until later to do this
AnnotationNode an = (AnnotationNode) ce.getValue();
for (Map.Entry member : an.getMembers().entrySet()) {
member.setValue(transformInlineConstants(member.getValue()));
}
}
} else {
return ExpressionUtils.transformInlineConstants(exp);
}
return exp;
}
private void checkAnnotationMemberValue(final Expression newValue) {
if (newValue instanceof PropertyExpression) {
PropertyExpression pe = (PropertyExpression) newValue;
if (!(pe.getObjectExpression() instanceof ClassExpression)) {
addError("unable to find class '" + pe.getText() + "' for annotation attribute constant", pe.getObjectExpression());
}
} else if (newValue instanceof ListExpression) {
ListExpression le = (ListExpression) newValue;
for (Expression e : le.getExpressions()) {
checkAnnotationMemberValue(e);
}
}
}
@Override
public void visitClass(final ClassNode node) {
ClassNode oldNode = currentClass;
currentClass = node;
ModuleNode module = node.getModule();
if (!module.hasImportsResolved()) {
for (ImportNode importNode : module.getImports()) {
currentImport = importNode;
ClassNode type = importNode.getType();
if (resolve(type, false, false, true)) {
currentImport = null;
continue;
}
currentImport = null;
addError("unable to resolve class " + type.getName(), type);
}
for (ImportNode importNode : module.getStarImports()) {
if (importNode.getLineNumber() > 0) {
currentImport = importNode;
String importName = importNode.getPackageName();
importName = importName.substring(0, importName.length()-1);
ClassNode type = ClassHelper.makeWithoutCaching(importName);
if (resolve(type, false, false, true)) {
importNode.setType(type);
}
currentImport = null;
}
}
for (ImportNode importNode : module.getStaticImports().values()) {
ClassNode type = importNode.getType();
if (!resolve(type, true, true, true))
addError("unable to resolve class " + type.getName(), type);
}
for (ImportNode importNode : module.getStaticStarImports().values()) {
ClassNode type = importNode.getType();
if (!resolve(type, true, true, true))
addError("unable to resolve class " + type.getName(), type);
}
module.setImportsResolved(true);
}
//
if (!(node instanceof InnerClassNode) || Modifier.isStatic(node.getModifiers())) {
genericParameterNames = new HashMap<>();
}
resolveGenericsHeader(node.getGenericsTypes());
switch (phase) { // GROOVY-9866, GROOVY-10466
case 0:
case 1:
ClassNode sn = node.getUnresolvedSuperClass();
if (sn != null) {
resolveOrFail(sn, "", node, true);
}
for (ClassNode in : node.getInterfaces()) {
resolveOrFail(in, "", node, true);
}
if (sn != null) checkCyclicInheritance(node, sn);
for (ClassNode in : node.getInterfaces()) {
checkCyclicInheritance(node, in);
}
if (node.getGenericsTypes() != null) {
for (GenericsType gt : node.getGenericsTypes()) {
if (gt != null && gt.getUpperBounds() != null) {
for (ClassNode variant : gt.getUpperBounds()) {
if (variant.isGenericsPlaceHolder()) checkCyclicInheritance(variant, gt.getType());
}
}
}
}
case 2:
// VariableScopeVisitor visits anon. inner class body inline, so resolve now
for (Iterator it = node.getInnerClasses(); it.hasNext(); ) {
InnerClassNode cn = it.next();
if (cn.isAnonymous()) {
MethodNode enclosingMethod = cn.getEnclosingMethod();
if (enclosingMethod != null) {
resolveGenericsHeader(enclosingMethod.getGenericsTypes()); // GROOVY-6977
}
resolveOrFail(cn.getUnresolvedSuperClass(false), cn); // GROOVY-9642
}
}
if (phase == 1) break; // resolve other class headers before members, et al.
// initialize scopes/variables now that imports and super types are resolved
new VariableScopeVisitor(source).visitClass(node);
visitTypeAnnotations(node);
super.visitClass(node);
}
currentClass = oldNode;
}
private void addFatalError(final String text, final ASTNode node) {
source.addFatalError(text, node);
}
private void checkCyclicInheritance(final ClassNode node, final ClassNode type) {
if (type.redirect() == node || type.getOuterClasses().contains(node)) {
addFatalError("Cycle detected: the type " + node.getName() + " cannot extend/implement itself or one of its own member types", type);
} else if (type != ClassHelper.OBJECT_TYPE) {
Set done = new HashSet<>();
done.add(ClassHelper.OBJECT_TYPE);
done.add(null);
LinkedList todo = new LinkedList<>();
Collections.addAll(todo, type.getInterfaces());
todo.add(type.getUnresolvedSuperClass());
todo.add(type.getOuterClass());
do {
ClassNode next = todo.poll();
if (!done.add(next)) continue;
if (next.redirect() == node) {
ClassNode cn = type; while (cn.getOuterClass() != null) cn = cn.getOuterClass();
addFatalError("Cycle detected: a cycle exists in the type hierarchy between " + node.getName() + " and " + cn.getName(), type);
}
Collections.addAll(todo, next.getInterfaces());
todo.add(next.getUnresolvedSuperClass());
todo.add(next.getOuterClass());
} while (!todo.isEmpty());
}
}
@Override
public void visitCatchStatement(final CatchStatement cs) {
resolveOrFail(cs.getExceptionType(), cs);
if (ClassHelper.isDynamicTyped(cs.getExceptionType())) {
cs.getVariable().setType(ClassHelper.make(Exception.class));
}
super.visitCatchStatement(cs);
}
@Override
public void visitForLoop(final ForStatement forLoop) {
resolveOrFail(forLoop.getVariableType(), forLoop);
super.visitForLoop(forLoop);
}
@Override
public void visitBlockStatement(final BlockStatement block) {
VariableScope oldScope = currentScope;
currentScope = block.getVariableScope();
super.visitBlockStatement(block);
currentScope = oldScope;
}
private boolean resolveGenericsTypes(final GenericsType[] types) {
if (types == null) return true;
currentClass.setUsingGenerics(true);
boolean resolved = true;
for (GenericsType type : types) {
// attempt resolution on all types, so don't short-circuit and stop if we've previously failed
resolved = resolveGenericsType(type) && resolved;
}
return resolved;
}
private void resolveGenericsHeader(final GenericsType[] types) {
resolveGenericsHeader(types, null, 0);
}
private void resolveGenericsHeader(final GenericsType[] types, final GenericsType rootType, final int level) {
if (types == null) return;
currentClass.setUsingGenerics(true);
List> upperBoundsWithGenerics = new LinkedList<>();
List> upperBoundsToResolve = new LinkedList<>();
for (GenericsType type : types) {
if (level > 0 && type.getName().equals(rootType.getName())) {
continue;
}
String name = type.getName();
ClassNode typeType = type.getType();
GenericsTypeName gtn = new GenericsTypeName(name);
boolean isWildcardGT = QUESTION_MARK.equals(name);
boolean dealWithGenerics = (level == 0 || (level > 0 && genericParameterNames.get(gtn) != null));
if (type.getUpperBounds() != null) {
boolean nameAdded = false;
for (ClassNode upperBound : type.getUpperBounds()) {
if (upperBound == null) continue;
if (!isWildcardGT) {
if (!nameAdded || !resolve(typeType)) {
if (dealWithGenerics) {
type.setPlaceholder(true);
typeType.setRedirect(upperBound);
genericParameterNames.put(gtn, type);
nameAdded = true;
}
}
upperBoundsToResolve.add(tuple(upperBound, typeType));
}
if (upperBound.isUsingGenerics()) {
upperBoundsWithGenerics.add(tuple(upperBound, type));
}
}
} else if (!isWildcardGT) {
if (dealWithGenerics) {
type.setPlaceholder(true);
GenericsType last = genericParameterNames.put(gtn, type);
typeType.setRedirect(last != null ? last.getType().redirect() : ClassHelper.OBJECT_TYPE);
}
}
}
for (Tuple2 tp : upperBoundsToResolve) {
ClassNode upperBound = tp.getV1();
ClassNode classNode = tp.getV2();
resolveOrFail(upperBound, classNode);
}
for (Tuple2 tp : upperBoundsWithGenerics) {
ClassNode upperBound = tp.getV1();
GenericsType gt = tp.getV2();
resolveGenericsHeader(upperBound.getGenericsTypes(), 0 == level ? gt : rootType, level + 1);
}
}
private boolean resolveGenericsType(final GenericsType genericsType) {
if (genericsType.isResolved()) return true;
currentClass.setUsingGenerics(true);
ClassNode type = genericsType.getType();
visitTypeAnnotations(type); // JSR-308 support
GenericsType tp = genericParameterNames.get(new GenericsTypeName(type.getName()));
if (tp != null) {
ClassNode[] bounds = tp.getUpperBounds();
if (bounds != null && (bounds.length > 1 || (bounds[0].isRedirectNode()
&& bounds[0].redirect().getGenericsTypes() != null))) {
// GROOVY-10622: bounds are too complex for a redirect-only representation
//genericsType.setUpperBounds(bounds);
type.setGenericsPlaceHolder(true);
type.setRedirect(bounds[0]);
} else {
type.setRedirect(tp.getType());
}
genericsType.setPlaceholder(true);
} else {
ClassNode[] upperBounds = genericsType.getUpperBounds();
if (upperBounds != null) {
for (ClassNode upperBound : upperBounds) {
resolveOrFail(upperBound, genericsType);
type.setRedirect(upperBound);
resolveGenericsTypes(upperBound.getGenericsTypes());
}
} else if (genericsType.isWildcard()) {
type.setRedirect(ClassHelper.OBJECT_TYPE);
} else {
resolveOrFail(type, genericsType);
}
}
if (genericsType.getLowerBound() != null) {
resolveOrFail(genericsType.getLowerBound(), genericsType);
}
if (resolveGenericsTypes(type.getGenericsTypes())) {
genericsType.setResolved(genericsType.getType().isResolved());
}
return genericsType.isResolved();
}
}