Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple potential return types & defer expression type checking. #6624

Merged
merged 13 commits into from
May 31, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Set;
import java.util.List;
import java.util.Collection;

@Name("Arithmetic")
@Description("Arithmetic expressions, e.g. 1 + 2, (health of player - 2) / 3, etc.")
Expand Down Expand Up @@ -108,6 +110,7 @@ public PatternInfo(Operator operator, boolean leftGrouped, boolean rightGrouped)
private Operator operator;

private Class<? extends T> returnType;
private Collection<Class<?>> knownReturnTypes;

// A chain of expressions and operators, alternating between the two. Always starts and ends with an expression.
private final List<Object> chain = new ArrayList<>();
Expand Down Expand Up @@ -250,10 +253,12 @@ public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelaye
}
if (returnTypes == null) { // both are object; can't determine anything
returnType = (Class<? extends T>) Object.class;
knownReturnTypes = Arithmetics.getAllReturnTypes(operator);
} else if (returnTypes.length == 0) { // one of the classes is known but doesn't have any operations
return error(firstClass, secondClass);
} else {
returnType = (Class<? extends T>) Classes.getSuperClassInfo(returnTypes).getC();
knownReturnTypes = Set.of(returnTypes);
Moderocky marked this conversation as resolved.
Show resolved Hide resolved
}
} else if (returnType == null) { // lookup
OperationInfo<L, R, T> operationInfo = (OperationInfo<L, R, T>) Arithmetics.lookupOperationInfo(operator, firstClass, secondClass);
Expand Down Expand Up @@ -328,6 +333,21 @@ public Class<? extends T> getReturnType() {
return returnType;
}

@Override
public Class<? extends T>[] possibleReturnTypes() {
if (returnType == Object.class)
//noinspection unchecked
return knownReturnTypes.toArray(new Class[0]);
return super.possibleReturnTypes();
}

@Override
public boolean canReturn(Class<?> returnType) {
if (this.returnType == Object.class && knownReturnTypes.contains(returnType))
return true;
APickledWalrus marked this conversation as resolved.
Show resolved Hide resolved
return super.canReturn(returnType);
}

@Override
public boolean isSingle() {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,15 @@ public Expression<? extends T> simplify() {
public Object[] beforeChange(Expression<?> changed, @Nullable Object[] delta) {
return expr.beforeChange(changed, delta); // Forward to what we're wrapping
}


@Override
public Class<? extends T>[] possibleReturnTypes() {
return expr.possibleReturnTypes();
}

@Override
public boolean canReturn(Class<?> returnType) {
return expr.canReturn(returnType);
}

}
25 changes: 25 additions & 0 deletions src/main/java/ch/njol/skript/lang/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,31 @@ default Optional<T> getOptionalSingle(Event event) {
*/
Class<? extends T> getReturnType();

/**
* For expressions that might return multiple (incalculable at parse time) types,
* this provides a list of all possible types.
* Use cases include: expressions that depend on the return type of their input.
*
* @return A list of all possible types this might return
*/
default Class<? extends T>[] possibleReturnTypes() {
Moderocky marked this conversation as resolved.
Show resolved Hide resolved
//noinspection unchecked
return new Class[] {this.getReturnType()};
}

/**
* Whether this expression <b>might</b> return the following type.
* @param returnType The type to test
* @return true if the argument is within the bounds of the return types
*/
default boolean canReturn(Class<?> returnType) {
for (Class<?> type : this.possibleReturnTypes()) {
if (returnType.isAssignableFrom(type))
return true;
}
return false;
}

/**
* Returns true if this expression returns all possible values, false if it only returns some of them.
* <p>
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/ch/njol/skript/lang/ExpressionList.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class ExpressionList<T> implements Expression<T> {

protected final Expression<? extends T>[] expressions;
private final Class<T> returnType;
private final Class<?>[] possibleReturnTypes;
protected boolean and;
private final boolean single;

Expand All @@ -53,10 +54,19 @@ public ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType
this(expressions, returnType, and, null);
}

public ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType, Class<?>[] possibleReturnTypes, boolean and) {
Moderocky marked this conversation as resolved.
Show resolved Hide resolved
this(expressions, returnType, possibleReturnTypes, and, null);
}

protected ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType, boolean and, @Nullable ExpressionList<?> source) {
this(expressions, returnType, new Class[]{returnType}, and, source);
}

protected ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType, Class<?>[] possibleReturnTypes, boolean and, @Nullable ExpressionList<?> source) {
assert expressions != null;
this.expressions = expressions;
this.returnType = returnType;
this.possibleReturnTypes = possibleReturnTypes;
this.and = and;
if (and) {
single = false;
Expand Down Expand Up @@ -178,14 +188,20 @@ public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) {
return null;
returnTypes[i] = exprs[i].getReturnType();
}
return new ExpressionList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), and, this);
return new ExpressionList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), returnTypes, and, this);
}

@Override
public Class<T> getReturnType() {
return returnType;
}

@Override
public Class<? extends T>[] possibleReturnTypes() {
//noinspection unchecked
return (Class<? extends T>[]) possibleReturnTypes;
}

@Override
public boolean getAnd() {
return and;
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/ch/njol/skript/lang/LiteralList.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

/**
* A list of literals. Can contain {@link UnparsedLiteral}s.
*
*
* @author Peter Güttinger
*/
public class LiteralList<T> extends ExpressionList<T> implements Literal<T> {
Expand All @@ -35,10 +35,18 @@ public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, boolean
super(literals, returnType, and);
}

public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, Class<?>[] possibleReturnTypes, boolean and) {
super(literals, returnType, possibleReturnTypes, and);
}

public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, boolean and, LiteralList<?> source) {
super(literals, returnType, and, source);
}

public LiteralList(Literal<? extends T>[] literals, Class<T> returnType, Class<?>[] possibleReturnTypes, boolean and, LiteralList<?> source) {
super(literals, returnType, possibleReturnTypes, and, source);
}

@Override
public T[] getArray() {
return getArray(null);
Expand All @@ -64,7 +72,7 @@ public <R> Literal<? extends R> getConvertedExpression(final Class<R>... to) {
return null;
returnTypes[i] = exprs[i].getReturnType();
}
return new LiteralList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), and, this);
return new LiteralList<>(exprs, (Class<R>) Classes.getSuperClassInfo(returnTypes).getC(), returnTypes, and, this);
}

@Override
Expand Down
39 changes: 19 additions & 20 deletions src/main/java/ch/njol/skript/lang/SkriptParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@

import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
Expand All @@ -76,7 +75,7 @@
* Used for parsing my custom patterns.<br>
* <br>
* Note: All parse methods print one error at most xor any amount of warnings and lower level log messages. If the given string doesn't match any pattern then nothing is printed.
*
*
* @author Peter Güttinger
*/
public class SkriptParser {
Expand All @@ -102,7 +101,7 @@ public SkriptParser(String expr, int flags) {
* Constructs a new SkriptParser object that can be used to parse the given expression.
* <p>
* A SkriptParser can be re-used indefinitely for the given expression, but to parse a new expression a new SkriptParser has to be created.
*
*
* @param expr The expression to parse
* @param flags Some parse flags ({@link #PARSE_EXPRESSIONS}, {@link #PARSE_LITERALS})
* @param context The parse context
Expand Down Expand Up @@ -371,7 +370,7 @@ private <T> Expression<? extends T> parseSingleExpr(boolean allowUnparsedLiteral
if (parsedExpression != null) { // Expression/VariableString parsing success
for (Class<? extends T> type : types) {
// Check return type against everything that expression accepts
if (type.isAssignableFrom(parsedExpression.getReturnType())) {
if (parsedExpression.canReturn(type)) {
log.printLog();
return (Expression<? extends T>) parsedExpression;
}
Expand Down Expand Up @@ -541,17 +540,13 @@ private Expression<?> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo
if ((flags & PARSE_EXPRESSIONS) != 0) {
Expression<?> parsedExpression = parseExpression(types, expr);
if (parsedExpression != null) { // Expression/VariableString parsing success
Class<?> returnType = parsedExpression.getReturnType(); // Sometimes getReturnType does non-trivial costly operations
if (returnType == null)
throw new SkriptAPIException("Expression '" + expr + "' returned null for method Expression#getReturnType. Null is not a valid return.");

for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
if (type == null) // Ignore invalid (null) types
continue;

// Check return type against everything that expression accepts
if (type.isAssignableFrom(returnType)) {
if (parsedExpression.canReturn(type)) {
if (!exprInfo.isPlural[i] && !parsedExpression.isSingle()) { // Wrong number of arguments
if (context == ParseContext.COMMAND) {
Skript.error(Commands.m_too_many_arguments.toString(exprInfo.classes[i].getName().getIndefiniteArticle(), exprInfo.classes[i].getName().toString()), ErrorQuality.SEMANTIC_ERROR);
Expand Down Expand Up @@ -752,11 +747,15 @@ private <T> Expression<? extends T> parseExpressionList(ParseLogHandler log, Cla
exprReturnTypes[i] = parsedExpressions.get(i).getReturnType();

if (isLiteralList) {
Literal<T>[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]);
return new LiteralList<>(literals, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse());
//noinspection unchecked,SuspiciousToArrayCall
Literal<T>[] literals = parsedExpressions.toArray(new Literal[0]);
//noinspection unchecked
return new LiteralList<>(literals, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), exprReturnTypes, !and.isFalse());
} else {
Expression<T>[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]);
return new ExpressionList<>(expressions, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse());
//noinspection unchecked
Expression<T>[] expressions = parsedExpressions.toArray(new Expression[0]);
//noinspection unchecked
return new ExpressionList<>(expressions, (Class<T>) Classes.getSuperClassInfo(exprReturnTypes).getC(), exprReturnTypes, !and.isFalse());
}
}

Expand Down Expand Up @@ -885,10 +884,10 @@ public Expression<?> parseExpression(ExprInfo exprInfo) {

if (isLiteralList) {
Literal<?>[] literals = parsedExpressions.toArray(new Literal[parsedExpressions.size()]);
return new LiteralList(literals, Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse());
return new LiteralList(literals, Classes.getSuperClassInfo(exprReturnTypes).getC(), exprReturnTypes, !and.isFalse());
} else {
Expression<?>[] expressions = parsedExpressions.toArray(new Expression[parsedExpressions.size()]);
return new ExpressionList(expressions, Classes.getSuperClassInfo(exprReturnTypes).getC(), !and.isFalse());
return new ExpressionList(expressions, Classes.getSuperClassInfo(exprReturnTypes).getC(), exprReturnTypes, !and.isFalse());

}
} finally {
Expand Down Expand Up @@ -1024,7 +1023,7 @@ public static ParseResult parse(String text, String pattern) {

/**
* Finds the closing bracket of the group at <tt>start</tt> (i.e. <tt>start</tt> has to be <i>in</i> a group).
*
*
* @param pattern The string to search in
* @param closingBracket The bracket to look for, e.g. ')'
* @param openingBracket A bracket that opens another group, e.g. '('
Expand Down Expand Up @@ -1075,7 +1074,7 @@ private static int nextUnescaped(String pattern, char character, int from) {

/**
* Counts how often the given character occurs in the given string, ignoring any escaped occurrences of the character.
*
*
* @param haystack The string to search in
* @param needle The character to search for
* @return The number of unescaped occurrences of the given character
Expand Down Expand Up @@ -1110,7 +1109,7 @@ static int countUnescaped(String haystack, char needle, int start, int end) {

/**
* Find the next unescaped (i.e. single) double quote in the string.
*
*
* @param string The string to search in
* @param start Index after the starting quote
* @return Index of the end quote
Expand Down Expand Up @@ -1181,7 +1180,7 @@ public static String notOfType(ClassInfo<?>... types) {
* Returns the next character in the expression, skipping strings,
* variables and parentheses
* (unless {@code context} is {@link ParseContext#COMMAND} or {@link ParseContext#PARSE}).
*
*
* @param expr The expression to traverse.
* @param startIndex The index to start at.
* @return The next index (can be expr.length()), or -1 if
Expand Down Expand Up @@ -1299,7 +1298,7 @@ private ParseResult parse_i(String pattern) {

/**
* Validates a user-defined pattern (used in {@link ExprParse}).
*
*
* @param pattern The pattern string to validate
* @return The pattern with %codenames% and a boolean array that contains whether the expressions are plural or not
*/
Expand Down
Loading