Skip to content

Commit cca446d

Browse files
UnderscoreTudAPickledWalrusEfniliteModerocky
authored
Fix expression conversion (#7165)
* Fix expression conversion * Extract duplicate code into a separate helper method * improve conversion strategy * Add .sk to test file * Simplify conversion usage We need to use conversion whenever there are multiple return types. If the expression does not accept our supertype, then we can attempt to convert it, which will already handle safety checks for multiple return types * SimpleExpression: fix possible return type conversion This fixes SimpleExpression not converting possible return types that are not contained in the desired types array. For example, if an expression can return a Number or a String, and we want an Expression that is a Number or an World, it will now include converters for String->Number and String->World * Use safety checks of ConvertedExpression * Remove incorrect converter remake * Move logic from SimpleExpression to ConvertedExpression --------- Co-authored-by: APickledWalrus <apickledwalrus@gmail.com> Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com> Co-authored-by: Moderocky <admin@moderocky.com>
1 parent 3c94063 commit cca446d

File tree

4 files changed

+42
-51
lines changed

4 files changed

+42
-51
lines changed

src/main/java/ch/njol/skript/lang/SkriptParser.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,6 @@ private static <T> Variable<T> parseVariable(String expr, Class<? extends T>[] r
326326
}
327327
}
328328

329-
330329
@Nullable
331330
@SuppressWarnings({"unchecked", "rawtypes"})
332331
private <T> Expression<? extends T> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable LogEntry error, Class<? extends T>... types) {
@@ -368,9 +367,9 @@ private <T> Expression<? extends T> parseSingleExpr(boolean allowUnparsedLiteral
368367
if ((flags & PARSE_EXPRESSIONS) != 0) {
369368
Expression<?> parsedExpression = parseExpression(types, expr);
370369
if (parsedExpression != null) { // Expression/VariableString parsing success
370+
Class<?> parsedReturnType = parsedExpression.getReturnType();
371371
for (Class<? extends T> type : types) {
372-
// Check return type against everything that expression accepts
373-
if (parsedExpression.canReturn(type)) {
372+
if (type.isAssignableFrom(parsedReturnType)) {
374373
log.printLog();
375374
return (Expression<? extends T>) parsedExpression;
376375
}
@@ -540,13 +539,14 @@ private Expression<?> parseSingleExpr(boolean allowUnparsedLiteral, @Nullable Lo
540539
if ((flags & PARSE_EXPRESSIONS) != 0) {
541540
Expression<?> parsedExpression = parseExpression(types, expr);
542541
if (parsedExpression != null) { // Expression/VariableString parsing success
542+
Class<?> parsedReturnType = parsedExpression.getReturnType();
543543
for (int i = 0; i < types.length; i++) {
544544
Class<?> type = types[i];
545545
if (type == null) // Ignore invalid (null) types
546546
continue;
547547

548-
// Check return type against everything that expression accepts
549-
if (parsedExpression.canReturn(type)) {
548+
// Check return type against the expression's return type
549+
if (type.isAssignableFrom(parsedReturnType)) {
550550
if (!exprInfo.isPlural[i] && !parsedExpression.isSingle()) { // Wrong number of arguments
551551
if (context == ParseContext.COMMAND) {
552552
Skript.error(Commands.m_too_many_arguments.toString(exprInfo.classes[i].getName().getIndefiniteArticle(), exprInfo.classes[i].getName().toString()), ErrorQuality.SEMANTIC_ERROR);

src/main/java/ch/njol/skript/lang/util/ConvertedExpression.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import ch.njol.skript.lang.Expression;
2525
import ch.njol.skript.lang.SkriptParser.ParseResult;
2626
import ch.njol.skript.registrations.Classes;
27+
import ch.njol.skript.util.Utils;
2728
import ch.njol.util.Checker;
2829
import ch.njol.util.Kleenean;
2930
import ch.njol.util.coll.CollectionUtils;
@@ -96,24 +97,31 @@ public ConvertedExpression(Expression<? extends F> source, Class<T> to, Collecti
9697
@Nullable
9798
public static <F, T> ConvertedExpression<F, T> newInstance(Expression<F> from, Class<T>... to) {
9899
assert !CollectionUtils.containsSuperclass(to, from.getReturnType());
99-
// we track a list of converters that may work
100-
List<ConverterInfo<? super F, ? extends T>> converters = new ArrayList<>();
101-
for (Class<T> type : to) { // REMIND try more converters? -> also change WrapperExpression (and maybe ExprLoopValue)
102-
assert type != null;
103-
// casting <? super ? extends F> to <? super F> is wrong, but since the converter is only used for values returned by the expression
104-
// (which are instances of "<? extends F>") this won't result in any ClassCastExceptions.
105-
for (Class<? extends F> checking : from.possibleReturnTypes()) {
106-
//noinspection unchecked
107-
ConverterInfo<? super F, ? extends T> converter = (ConverterInfo<? super F, ? extends T>) Converters.getConverterInfo(checking, type);
108-
if (converter != null)
109-
converters.add(converter);
100+
101+
// we might be able to cast some (or all) of the possible return types to T
102+
// for possible return types that can't be directly cast, regular converters will be used
103+
List<ConverterInfo<? extends F, ? extends T>> infos = new ArrayList<>();
104+
for (Class<? extends F> type : from.possibleReturnTypes()) {
105+
if (CollectionUtils.containsSuperclass(to, type)) { // this type is of T, build a converter simply casting
106+
// noinspection unchecked - 'type' is a desired type in 'to'
107+
Class<T> toType = (Class<T>) type;
108+
infos.add(new ConverterInfo<>(type, toType, toType::cast, 0));
109+
} else { // this possible return type is not included in 'to'
110+
// build all converters for converting the possible return type into any of the types of 'to'
111+
for (Class<T> toType : to) {
112+
ConverterInfo<? extends F, T> converter = Converters.getConverterInfo(type, toType);
113+
if (converter != null)
114+
infos.add(converter);
115+
}
110116
}
111-
int size = converters.size();
112-
if (size == 1) // if there is only one info, there is no need to wrap it in a list
113-
return new ConvertedExpression<>(from, type, converters.get(0));
114-
if (size > 1)
115-
return new ConvertedExpression<>(from, type, converters, true);
116117
}
118+
if (!infos.isEmpty()) { // there are converters for (at least some of) the return types
119+
// a note: casting <? extends F> to <? super F> is wrong, but since the converter is used only for values
120+
// returned by the expression (which are instances of <? extends F>), this won't result in any CCEs
121+
// noinspection rawtypes, unchecked
122+
return new ConvertedExpression(from, Utils.getSuperType(infos.stream().map(ConverterInfo::getTo).toArray(Class[]::new)), infos, true);
123+
}
124+
117125
return null;
118126
}
119127

src/main/java/ch/njol/skript/lang/util/SimpleExpression.java

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,10 @@
3535
import org.jetbrains.annotations.NotNull;
3636
import org.jetbrains.annotations.Nullable;
3737
import org.skriptlang.skript.lang.converter.Converter;
38-
import org.skriptlang.skript.lang.converter.ConverterInfo;
3938

4039
import java.lang.reflect.Array;
41-
import java.util.ArrayList;
4240
import java.util.Arrays;
4341
import java.util.Iterator;
44-
import java.util.List;
4542

4643
/**
4744
* An implementation of the {@link Expression} interface. You should usually extend this class to make a new expression.
@@ -206,33 +203,6 @@ public <R> Expression<? extends R> getConvertedExpression(Class<R>... to) {
206203
// check whether this expression is already of type R
207204
if (CollectionUtils.containsSuperclass(to, getReturnType()))
208205
return (Expression<? extends R>) this;
209-
210-
// we might be to cast some of the possible return types to R
211-
List<ConverterInfo<? extends T, R>> infos = new ArrayList<>();
212-
for (Class<? extends T> type : this.possibleReturnTypes()) {
213-
if (CollectionUtils.containsSuperclass(to, type)) { // this type is of R
214-
// build a converter that for casting to R
215-
// safety check is present in the event that we do not get this type at runtime
216-
final Class<R> toType = (Class<R>) type;
217-
infos.add(new ConverterInfo<>(getReturnType(), toType, fromObject -> {
218-
if (toType.isInstance(fromObject))
219-
return (R) fromObject;
220-
return null;
221-
}, 0));
222-
}
223-
}
224-
int size = infos.size();
225-
if (size == 1) { // if there is only one info, there is no need to wrap it in a list
226-
ConverterInfo<? extends T, R> info = infos.get(0);
227-
//noinspection rawtypes
228-
return new ConvertedExpression(this, info.getTo(), info);
229-
}
230-
if (size > 1) {
231-
//noinspection rawtypes
232-
return new ConvertedExpression(this, Utils.getSuperType(infos.stream().map(ConverterInfo::getTo).toArray(Class[]::new)), infos, false);
233-
}
234-
235-
// attempt traditional conversion with proper converters
236206
return this.getConvertedExpr(to);
237207
}
238208

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
test "expressions sometimes dont convert":
2+
assert formatted ({_foo} + {_bar}) is not set with "formatting nothing shouldn't throw an error"
3+
4+
set {_foo} to "test"
5+
assert formatted ({_foo} + {_bar}) is not set with "formatting string + none shouldn't throw an error"
6+
7+
set {_foo} to 1
8+
set {_bar} to 2
9+
assert formatted ({_foo} + {_bar}) is not set with "formatting number + number shouldn't throw an error"
10+
11+
set {_foo} to "foo"
12+
set {_bar} to "bar"
13+
assert formatted ({_foo} + {_bar}) is "foobar" with "formatting strings doesn't work"

0 commit comments

Comments
 (0)