blob: ef1c02efd61fb02083dfd021ca0b6f8371d3373b [file] [log] [blame]
package org.checkerframework.framework.util.dependenttypes;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.Format;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.javacutil.BugInCF;
/**
* Helper class for creating dependent type annotation error strings.
*
* <p><b>IMPORTANT:</b> This is not an Exception. It is a regular class that is returned, not
* thrown. The errors are not thrown so that they are only reported once rather than every time the
* annotation is parsed. See {@link DependentTypesHelper} for more details.
*/
public class DependentTypesError {
/// Static fields
/** How elements of this class are formatted. */
@SuppressWarnings("InlineFormatString") // https://github.com/google/error-prone/issues/1650
private static final String FORMAT_STRING = "[error for expression: %s; error: %s]";
/** Regular expression for unparsing string representations of this class (gross). */
private static final Pattern ERROR_PATTERN =
Pattern.compile("\\[error for expression: (.*); error: (.*)\\]");
/**
* Returns whether or not the given expression string is an error. That is, whether it is a string
* that was generated by this class.
*
* @param expression expression string to test
* @return whether or not the given expressions string is an error
*/
public static boolean isExpressionError(String expression) {
return expression.startsWith("[error");
}
/** How to format warnings about use of formal parameter name. */
public static final @Format({ConversionCategory.INT, ConversionCategory.GENERAL}) String
FORMAL_PARAM_NAME_STRING = "Use \"#%d\" rather than \"%s\"";
/** Matches warnings about use of formal parameter name. */
private static final Pattern FORMAL_PARAM_NAME_PATTERN =
Pattern.compile("^'([a-zA-Z_$][a-zA-Z0-9_$]*)' because (Use \"#\\d+\" rather than \"\\1\")$");
/// Instance fields
/** The expression that is unparsable or otherwise problematic. */
public final String expression;
/** An error message about that expression. */
public final String error;
/// Constructors and methods
/**
* Create a DependentTypesError for the given expression and error message.
*
* @param expression the incorrect Java expression
* @param error an error message about the expression
*/
public DependentTypesError(String expression, String error) {
this.expression = expression;
this.error = error;
}
/**
* Create a DependentTypesError for the given expression and exception.
*
* @param expression the incorrect Java expression
* @param e wraps an error message about the expression
*/
public DependentTypesError(String expression, JavaExpressionParseException e) {
this.expression = expression;
this.error = e.getDiagMessage().getArgs()[0].toString();
}
/**
* Create a DependentTypesError by parsing a printed one.
*
* @param formattedError the toString() representation of a DependentTypesError
*/
public static DependentTypesError unparse(String formattedError) {
Matcher matcher = ERROR_PATTERN.matcher(formattedError);
if (matcher.matches()) {
assert matcher.groupCount() == 2;
return new DependentTypesError(matcher.group(1), matcher.group(2));
} else {
throw new BugInCF("Cannot unparse: " + formattedError);
}
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DependentTypesError that = (DependentTypesError) o;
return expression.equals(that.expression) && error.equals(that.error);
}
@Override
public int hashCode() {
return Objects.hash(expression, error);
}
@Override
public String toString() {
return String.format(FORMAT_STRING, expression, error);
}
/**
* Like toString, but uses better formatting sometimes. Use this only for the final output,
* because of the design that hides error messages in toString().
*/
public String format() {
Matcher m = FORMAL_PARAM_NAME_PATTERN.matcher(error);
if (m.matches()) {
return m.group(2);
}
return toString();
}
}