From a319a63d025995645da8272b908f351cc224f484 Mon Sep 17 00:00:00 2001 From: Theron Wang Date: Fri, 9 Aug 2024 11:24:23 -0400 Subject: [PATCH] address CR comments --- .../specimin/JavaLangUtils.java | 9 +-- .../specimin/JavaParserUtil.java | 45 +++++++++++++ .../specimin/UnsolvedMethod.java | 7 +- .../specimin/UnsolvedSymbolVisitor.java | 67 ++++--------------- 4 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/checkerframework/specimin/JavaLangUtils.java b/src/main/java/org/checkerframework/specimin/JavaLangUtils.java index 8573a752..79eea5b2 100644 --- a/src/main/java/org/checkerframework/specimin/JavaLangUtils.java +++ b/src/main/java/org/checkerframework/specimin/JavaLangUtils.java @@ -316,7 +316,8 @@ public static String[] getTypesForOp(String binOp) { } /** - * Is a type primitive (int, char, boolean, etc.)? + * Is a type primitive (int, char, boolean, etc.)? This method returns false for boxed types + * (Integer, Character, Boolean, etc.) * * @param type the type to check * @return true iff the type is primitive @@ -326,12 +327,12 @@ public static boolean isPrimitive(String type) { } /** - * Converts a primitive to its object wrapper class (i.e. int --> Integer) + * Converts a primitive to its boxed type (i.e. int --> Integer) * * @param primitive the primitive type (int, boolean, char, etc.) - * @return the primitive as an object (int --> Integer, char --> Character) + * @return the boxed type (int --> Integer, char --> Character) */ - public static String getPrimitiveAsObject(String primitive) { + public static String getPrimitiveAsBoxedType(String primitive) { String converted = primitivesToObjects.get(primitive); if (converted == null) { diff --git a/src/main/java/org/checkerframework/specimin/JavaParserUtil.java b/src/main/java/org/checkerframework/specimin/JavaParserUtil.java index 0962d656..ec83983b 100644 --- a/src/main/java/org/checkerframework/specimin/JavaParserUtil.java +++ b/src/main/java/org/checkerframework/specimin/JavaParserUtil.java @@ -1,6 +1,8 @@ package org.checkerframework.specimin; +import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.AnnotationDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; @@ -12,10 +14,13 @@ import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.nodeTypes.NodeWithDeclaration; import com.github.javaparser.ast.type.ClassOrInterfaceType; +import com.github.javaparser.ast.type.Type; import com.github.javaparser.resolution.UnsolvedSymbolException; import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; import com.google.common.base.Splitter; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -200,6 +205,46 @@ public static Node getEnclosingClassLike(Node node) { return parent; } + /** + * Given a String of types (separated by commas), return a List of these types, with any + * primitives converted to their object counterparts. Use this instead of {@code .split(", ")} to + * properly handle generics. + * + * @param commaSeparatedTypes A string of comma separated types + * @return a list of strings representing the types in commaSeparatedTypes + */ + public static List getReferenceTypesFromCommaSeparatedString(String commaSeparatedTypes) { + if (commaSeparatedTypes == null || commaSeparatedTypes.isBlank()) { + return Collections.EMPTY_LIST; + } + + // Splitting them is simply to change primitives to objects so we do not + // get an error when parsing in StaticJavaParser (note that this array) + // may contain incomplete types like ["Map"] + String[] tokens = commaSeparatedTypes.split(","); + + for (int i = 0; i < tokens.length; i++) { + if (JavaLangUtils.isPrimitive(tokens[i].trim())) { + tokens[i] = JavaLangUtils.getPrimitiveAsBoxedType(tokens[i].trim()); + } + } + + // Parse as a generic type, then get the type arguments + // This way we can properly differentiate between commas within type arguments + // versus actual commas in javac error messages + Type parsed = StaticJavaParser.parseType("ToParse<" + String.join(", ", tokens) + ">"); + + List types = new ArrayList<>(); + NodeList typeArguments = parsed.asClassOrInterfaceType().getTypeArguments().orElse(null); + + if (typeArguments != null) { + for (Type typeArgument : typeArguments) { + types.add(typeArgument.toString()); + } + } + return types; + } + /** * Returns true iff the innermost enclosing class/interface is an enum. * diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java b/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java index fec73bc8..d99df9a5 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedMethod.java @@ -183,13 +183,12 @@ public boolean replaceParamWithObject(String incorrectTypeName) { } /** - * Given a correct method reference type, this method replaces the type at the given index with - * the corrected name + * Corrects the parameter's type at index {@code parameter} * * @param parameter The parameter (index) to replace - * @param correctName The type name to replace the parameter as + * @param correctName The type name to replace the parameter type as */ - public void correctMethodReferenceType(int parameter, String correctName) { + public void correctParameterType(int parameter, String correctName) { parameterList.set(parameter, correctName); } diff --git a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java index e180546c..dbe14a22 100644 --- a/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java +++ b/src/main/java/org/checkerframework/specimin/UnsolvedSymbolVisitor.java @@ -1634,7 +1634,8 @@ public boolean declaredInCurrentClass(MethodCallExpr method) { if (!methodDeclared.getName().asString().equals(method.getName().asString())) { continue; } - List methodTypesOfArguments = getArgumentTypesFromMethodCall(method, currentPackage); + List methodTypesOfArguments = + getArgumentTypesFromMethodCall(method, currentPackage); NodeList methodDeclaredParameters = methodDeclared.getParameters(); List methodDeclaredTypesOfParameters = new ArrayList<>(); for (Parameter parameter : methodDeclaredParameters) { @@ -2723,8 +2724,8 @@ private boolean isLambdaVoidReturn(LambdaExpr lambda) { * @param numberOfParams the number of parameters * @param isVoid true iff the method is void * @param pkgName the package in which a new functional interface should be created, if necessary - * @return the fully-qualified name of a functional interface that is in-scope and is a supertype - * of the given function, according to javac's arity-based typechecking rules for functions + * @return the fully-qualified name of a functional interface that is in-scope, matches the + * specified arity, and the specified voidness */ private String resolveFunctionalInterface(int numberOfParams, boolean isVoid, String pkgName) { // we need to run at least once more to solve the functional interface we're about to create @@ -3738,8 +3739,6 @@ public boolean updateTypeForSyntheticClasses( * the correct parameter types * @return true if any method argument was updated */ - @SuppressWarnings("argument") - // found: int, required: @Interned int public boolean updateMethodReferenceParameters(Map methodReferencesToCorrect) { boolean updated = false; for (String methodReference : methodReferencesToCorrect.keySet()) { @@ -3757,7 +3756,8 @@ public boolean updateMethodReferenceParameters(Map methodReferen List parameters = new ArrayList<>(); - for (String parameter : getReferenceTypesFromCommaSeparatedString(parametersAsString)) { + for (String parameter : + JavaParserUtil.getReferenceTypesFromCommaSeparatedString(parametersAsString)) { parameters.add(lookupFQNs(parameter.trim())); } @@ -3766,8 +3766,8 @@ public boolean updateMethodReferenceParameters(Map methodReferen resolveFunctionalInterfaceWithFullyQualifiedParameters( parameters, false, currentPackage); - for (int argument : argumentsToFix) { - method.correctMethodReferenceType(argument, fixed); + for (Integer argument : argumentsToFix) { + method.correctParameterType(argument.intValue(), fixed); updated = true; } } @@ -3784,8 +3784,6 @@ public boolean updateMethodReferenceParameters(Map methodReferen * the correct voidness (true if void, false if not) * @return true if any method was updated */ - @SuppressWarnings("argument") - // found: int, required: @Interned int public boolean updateMethodReferenceVoidness(Map methodReferencesToCorrect) { boolean updated = false; for (String methodReference : methodReferencesToCorrect.keySet()) { @@ -3796,13 +3794,14 @@ public boolean updateMethodReferenceVoidness(Map methodReferenc unsolvedMethodsToArguments.entrySet()) { Set argumentsToFix = method.getValue(); - for (int argument : argumentsToFix) { + for (Integer argument : argumentsToFix) { // 2nd phase: Since we've already corrected the parameter types, now // we should keep them and update voidness - String arg = method.getKey().getParameterList().get(argument); + String arg = method.getKey().getParameterList().get(argument.intValue()); String parametersAsString = arg.substring(arg.indexOf('<') + 1, arg.lastIndexOf('>')); - List parameters = getReferenceTypesFromCommaSeparatedString(parametersAsString); + List parameters = + JavaParserUtil.getReferenceTypesFromCommaSeparatedString(parametersAsString); // Remove the last element; in updateMethodReferenceParameters we assumed that it was // non-void parameters.remove(parameters.size() - 1); @@ -3811,7 +3810,7 @@ public boolean updateMethodReferenceVoidness(Map methodReferenc resolveFunctionalInterfaceWithFullyQualifiedParameters( parameters, methodReferencesToCorrect.get(methodReference), currentPackage); - method.getKey().correctMethodReferenceType(argument, fixed); + method.getKey().correctParameterType(argument.intValue(), fixed); updated = true; } @@ -3822,46 +3821,6 @@ public boolean updateMethodReferenceVoidness(Map methodReferenc return updated; } - /** - * Given a String of types (separated by commas), return a List of these types, with any - * primitives converted to their object counterparts. Use this instead of {@code .split(", ")} to - * properly handle generics. - * - * @param commaSeparatedTypes A string of comma separated types - * @return a list of strings representing the types in commaSeparatedTypes - */ - private List getReferenceTypesFromCommaSeparatedString(String commaSeparatedTypes) { - if (commaSeparatedTypes == null || commaSeparatedTypes.isBlank()) { - return new ArrayList<>(); - } - - // Splitting them is simply to change primitives to objects so we do not - // get an error when parsing in StaticJavaParser (note that this array) - // may contain incomplete types like ["Map"] - String[] tokens = commaSeparatedTypes.split(","); - - for (int i = 0; i < tokens.length; i++) { - if (JavaLangUtils.isPrimitive(tokens[i].trim())) { - tokens[i] = JavaLangUtils.getPrimitiveAsObject(tokens[i].trim()); - } - } - - // Parse as a generic type, then get the type arguments - // This way we can properly differentiate between commas within type arguments - // versus actual commas in javac error messages - Type parsed = StaticJavaParser.parseType("ToParse<" + String.join(", ", tokens) + ">"); - - List types = new ArrayList<>(); - NodeList typeArguments = parsed.asClassOrInterfaceType().getTypeArguments().orElse(null); - - if (typeArguments != null) { - for (Type typeArgument : typeArguments) { - types.add(typeArgument.toString()); - } - } - return types; - } - /** * Lookup the fully-qualified names of each type in the given string, and replace the simple type * names in the given string with their fully-qualified equivalents. Return the result.