diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java index db1f7ead06..e36d2faa2d 100644 --- a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/contexts/CompilationUnitContext.java @@ -21,6 +21,11 @@ package com.github.javaparser.symbolsolver.javaparsermodel.contexts; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.ImportDeclaration; import com.github.javaparser.ast.Node; @@ -36,9 +41,6 @@ import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration; import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserInterfaceDeclaration; import com.github.javaparser.symbolsolver.resolution.SymbolSolver; -import java.util.LinkedList; -import java.util.List; -import java.util.stream.Collectors; /** * @author Federico Tomassetti @@ -47,6 +49,12 @@ public class CompilationUnitContext extends AbstractJavaParserContext> resolvedStaticImport = ThreadLocal.withInitial(() -> new ArrayList()); + /// /// Static methods /// @@ -83,16 +91,20 @@ public SymbolReference solveSymbol(String na if (importDecl.isStatic()) { if (importDecl.isAsterisk()) { String qName = importDecl.getNameAsString(); - ResolvedTypeDeclaration importedType = typeSolver.solveType(qName); - - // avoid infinite recursion - if (!isAncestorOf(importedType)) { - SymbolReference ref = - new SymbolSolver(typeSolver).solveSymbolInType(importedType, name); - if (ref.isSolved()) { - return ref; - } - } + // Try to resolve the name in from declarations imported with asterisks only if + // they have not already been analysed, otherwise this can lead to an infinite + // loop via circular dependencies. + if (!isAlreadyResolved(qName)) { + resolvedStaticImport.get().add(qName); + ResolvedTypeDeclaration importedType = typeSolver.solveType(qName); + + SymbolReference ref = new SymbolSolver(typeSolver) + .solveSymbolInType(importedType, name); + if (ref.isSolved()) { + resolvedStaticImport.remove(); // clear the search history + return ref; + } + } } else { String whole = importDecl.getNameAsString(); @@ -108,9 +120,16 @@ public SymbolReference solveSymbol(String na } } + // Clear of the search history because we don't want this context to be reused + // in another search. + resolvedStaticImport.remove(); return SymbolReference.unsolved(); } + private boolean isAlreadyResolved(String qName) { + return resolvedStaticImport.get().contains(qName); + } + @Override public SymbolReference solveType(String name, List typeArguments) { @@ -306,20 +325,16 @@ public SymbolReference solveMethod( && this.wrappedNode.getTypes().stream() .anyMatch(it -> it.getName().getIdentifier().equals(toSimpleName(importString)))) { // We are using a static import on a type defined in this file. It means the value was not found - // at - // a lower level so this will fail + // at a lower level so this will fail return SymbolReference.unsolved(); } - ResolvedTypeDeclaration ref = typeSolver.solveType(importString); - // avoid infinite recursion - if (!isAncestorOf(ref)) { - SymbolReference method = - MethodResolutionLogic.solveMethodInType(ref, name, argumentsTypes, true); - if (method.isSolved()) { - return method; - } - } + ResolvedTypeDeclaration ref = typeSolver.solveType(importString); + SymbolReference method = MethodResolutionLogic.solveMethodInType(ref, + name, argumentsTypes, true); + if (method.isSolved()) { + return method; + } } else { String qName = importDecl.getNameAsString(); @@ -382,7 +397,4 @@ private String getMember(String qName) { return memberName; } - private boolean isAncestorOf(ResolvedTypeDeclaration descendant) { - return descendant.toAst().filter(node -> wrappedNode.isAncestorOf(node)).isPresent(); - } } diff --git a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java index 30e45be9c1..e987357cd5 100644 --- a/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java +++ b/javaparser-symbol-solver-core/src/main/java/com/github/javaparser/symbolsolver/javaparsermodel/declarations/JavaParserTypeAdapter.java @@ -98,10 +98,7 @@ public boolean isAssignableBy(ResolvedType type) { } /** - * This method is deprecated because it receives the TypesSolver as a parameter. - * Eventually we would like to remove all usages of TypeSolver as a parameter. - * - * Also, resolution should move out of declarations, so that they are pure declarations and the resolution should + * Resolution should move out of declarations, so that they are pure declarations and the resolution should * work for JavaParser, Reflection and Javassist classes in the same way and not be specific to the three * implementations. */ diff --git a/javaparser-symbol-solver-testing/src/test/java/com/github/javaparser/symbolsolver/Issue4450Test.java b/javaparser-symbol-solver-testing/src/test/java/com/github/javaparser/symbolsolver/Issue4450Test.java new file mode 100755 index 0000000000..15752318a8 --- /dev/null +++ b/javaparser-symbol-solver-testing/src/test/java/com/github/javaparser/symbolsolver/Issue4450Test.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013-2024 The JavaParser Team. + * + * This file is part of JavaParser. + * + * JavaParser can be used either under the terms of + * a) the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * b) the terms of the Apache License + * + * You should have received a copy of both licenses in LICENCE.LGPL and + * LICENCE.APACHE. Please refer to those files for details. + * + * JavaParser is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + */ + +package com.github.javaparser.symbolsolver; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import com.github.javaparser.ParserConfiguration; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.expr.NameExpr; +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver; + +import java.io.IOException; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +public class Issue4450Test extends AbstractSymbolResolutionTest { + + @Test + public void test() throws IOException { + ParserConfiguration config = new ParserConfiguration(); + Path issueResourcesPath = adaptPath("src/test/resources/issue4450"); + JavaParserTypeSolver jpts = new JavaParserTypeSolver(issueResourcesPath); + CombinedTypeSolver cts = new CombinedTypeSolver(); + cts.add(new ReflectionTypeSolver(false)); + cts.add(jpts); + config.setSymbolResolver(new JavaSymbolSolver(cts)); + StaticJavaParser.setConfiguration(config); + StaticJavaParser.setConfiguration(config); + CompilationUnit cu = StaticJavaParser.parse(issueResourcesPath.resolve("a/RefCycleClass.java")); + + // We shouldn't throw a mismatched symbol + assertDoesNotThrow(() -> cu.findAll(NameExpr.class).stream() + .map(NameExpr::resolve) + .findAny().get()); + + cu.findAll(NameExpr.class).forEach(expr -> { + expr.resolve().getName(); + }); + } + +} diff --git a/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefClass2.java b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefClass2.java new file mode 100755 index 0000000000..9a3976fcb9 --- /dev/null +++ b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefClass2.java @@ -0,0 +1,6 @@ +package a; + +public class RefClass2 { + public static Runnable unknownName; + public Runnable nonStatic; +} diff --git a/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClass.java b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClass.java new file mode 100755 index 0000000000..1d72998ba5 --- /dev/null +++ b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClass.java @@ -0,0 +1,16 @@ +package a; + +import static a.RefCycleEnum.*; +import static a.RefClass2.*; + +public class RefCycleClass { + + public static int value1Value = 1; + + private RefCycleEnum theEnum; + + public void runMe() { + unknownName.run(); + RefCycleEnum myEnum = Value1; + } +} diff --git a/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClassFailure.java b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClassFailure.java new file mode 100755 index 0000000000..79b0129d30 --- /dev/null +++ b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleClassFailure.java @@ -0,0 +1,9 @@ +package a; + +import static a.RefClass2.*; + +public class RefCycleClassFailure { + public void runMe() { + nonStatic.run(); + } +} \ No newline at end of file diff --git a/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleEnum.java b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleEnum.java new file mode 100755 index 0000000000..9eb81b6691 --- /dev/null +++ b/javaparser-symbol-solver-testing/src/test/resources/issue4450/a/RefCycleEnum.java @@ -0,0 +1,13 @@ +package a; + +import static a.RefCycleClass.*; + +public enum RefCycleEnum { + Value1(value1Value); + + private RefCycleEnum(int value) { + Underlying = value; + } + + public final int Underlying; +}