Skip to content

Commit 9806eef

Browse files
authored
Function Overloading (#7757)
1 parent bf84d72 commit 9806eef

File tree

11 files changed

+1322
-63
lines changed

11 files changed

+1322
-63
lines changed

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@
6363

6464
# Documentation
6565
/src/main/java/ch/njol/skript/doc/ @Pikachu920 @AyhamAl-Ali @Efnilite @skriptlang/core-developers
66+
67+
# Functions
68+
/src/main/java/ch/njol/skript/lang/function @Efnilite @skriptlang/core-developers

src/main/java/ch/njol/skript/Skript.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,8 @@ public static void debug(final String info) {
17751775
* @see String#formatted(Object...)
17761776
*/
17771777
public static void debug(String message, Object... objects) {
1778+
if (!debug())
1779+
return;
17781780
debug(message.formatted(objects));
17791781
}
17801782

@@ -1803,6 +1805,17 @@ public static void error(final @Nullable String error) {
18031805
SkriptLogger.log(Level.SEVERE, error);
18041806
}
18051807

1808+
/**
1809+
* Sends an error message with formatted objects.
1810+
*
1811+
* @param message The message to send
1812+
* @param objects The objects to format the message with
1813+
* @see String#formatted(Object...)
1814+
*/
1815+
public static void error(String message, Object... objects) {
1816+
error(message.formatted(objects));
1817+
}
1818+
18061819
/**
18071820
* Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log
18081821
* errors with a specific {@link ErrorQuality}.

src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ch.njol.skript.lang.function;
22

33
import ch.njol.skript.ScriptLoader;
4+
import ch.njol.skript.Skript;
45
import ch.njol.skript.lang.Expression;
56
import ch.njol.skript.lang.ExpressionList;
67
import ch.njol.skript.lang.util.common.AnyNamed;
@@ -56,12 +57,15 @@ public DynamicFunctionReference(@NotNull String name, @Nullable Script source) {
5657
this.name = name;
5758
Function<? extends Result> function;
5859
if (source != null) {
60+
// will return the first function found that matches name.
61+
// TODO: add a way to specify param types
5962
//noinspection unchecked
60-
function = (Function<? extends Result>) Functions.getFunction(name, source.getConfig().getFileName());
63+
function = (Function<? extends Result>) FunctionRegistry.getRegistry().getFunction(source.getConfig().getFileName(), name).retrieved();
6164
} else {
6265
//noinspection unchecked
63-
function = (Function<? extends Result>) Functions.getFunction(name, null);
66+
function = (Function<? extends Result>) FunctionRegistry.getRegistry().getFunction(null, name).retrieved();
6467
}
68+
6569
this.resolved = function != null;
6670
this.function = new WeakReference<>(function);
6771
if (resolved) {

src/main/java/ch/njol/skript/lang/function/FunctionReference.java

Lines changed: 117 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ch.njol.skript.lang.function;
22

3-
43
import ch.njol.skript.Skript;
54
import ch.njol.skript.SkriptAPIException;
65
import ch.njol.skript.classes.ClassInfo;
@@ -9,24 +8,31 @@
98
import ch.njol.skript.lang.KeyProviderExpression;
109
import ch.njol.skript.lang.KeyedValue;
1110
import ch.njol.skript.lang.SkriptParser;
11+
import ch.njol.skript.lang.function.FunctionRegistry.Retrieval;
12+
import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult;
1213
import ch.njol.skript.log.RetainingLogHandler;
1314
import ch.njol.skript.log.SkriptLogger;
1415
import ch.njol.skript.registrations.Classes;
1516
import ch.njol.skript.util.Contract;
1617
import ch.njol.skript.util.LiteralUtils;
1718
import ch.njol.util.StringUtils;
1819
import org.bukkit.event.Event;
19-
import org.skriptlang.skript.util.Executable;
2020
import org.jetbrains.annotations.Nullable;
2121
import org.skriptlang.skript.lang.converter.Converters;
22-
22+
import org.skriptlang.skript.util.Executable;
2323
import java.util.*;
24+
import java.util.stream.Collectors;
2425

2526
/**
2627
* Reference to a {@link Function Skript function}.
2728
*/
2829
public class FunctionReference<T> implements Contract, Executable<Event, T[]> {
2930

31+
private static final String AMBIGUOUS_ERROR =
32+
"Skript cannot determine which function named '%s' to call. " +
33+
"The following functions were matched: %s. " +
34+
"Try clarifying the type of the arguments using the 'value within' expression.";
35+
3036
/**
3137
* Name of function that is called, for logging purposes.
3238
*/
@@ -85,8 +91,8 @@ public class FunctionReference<T> implements Contract, Executable<Event, T[]> {
8591
private Contract contract;
8692

8793
public FunctionReference(
88-
String functionName, @Nullable Node node, @Nullable String script,
89-
@Nullable Class<? extends T>[] returnTypes, Expression<?>[] params
94+
String functionName, @Nullable Node node, @Nullable String script,
95+
@Nullable Class<? extends T>[] returnTypes, Expression<?>[] params
9096
) {
9197
this.functionName = functionName;
9298
this.node = node;
@@ -99,17 +105,23 @@ public FunctionReference(
99105
public boolean validateParameterArity(boolean first) {
100106
if (!first && script == null)
101107
return false;
102-
Signature<?> sign = Functions.getSignature(functionName, script);
108+
109+
Signature<?> sign = getRegisteredSignature();
110+
103111
if (sign == null)
104112
return false;
113+
105114
// Not enough parameters
106115
return parameters.length >= sign.getMinParameters();
107116
}
108117

118+
private Class<?>[] parameterTypes;
119+
109120
/**
110121
* Validates this function reference. Prints errors if needed.
122+
*
111123
* @param first True if this is called while loading a script. False when
112-
* this is called when the function signature changes.
124+
* this is called when the function signature changes.
113125
* @return True if validation succeeded.
114126
*/
115127
public boolean validateFunction(boolean first) {
@@ -119,7 +131,7 @@ public boolean validateFunction(boolean first) {
119131
function = null;
120132
SkriptLogger.setNode(node);
121133
Skript.debug("Validating function " + functionName);
122-
Signature<?> sign = Functions.getSignature(functionName, script);
134+
Signature<?> sign = getRegisteredSignature();
123135

124136
// Check if the requested function exists
125137
if (sign == null) {
@@ -161,7 +173,7 @@ public boolean validateFunction(boolean first) {
161173
single = sign.single;
162174
} else if (single && !sign.single) {
163175
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible return type, but is still used in other script(s)."
164-
+ " These will continue to use the old version of the function until Skript restarts.");
176+
+ " These will continue to use the old version of the function until Skript restarts.");
165177
function = previousFunction;
166178
return false;
167179
}
@@ -253,6 +265,80 @@ public boolean validateFunction(boolean first) {
253265
return true;
254266
}
255267

268+
// attempt to get the types of the parameters for this function reference
269+
private void parseParameters() {
270+
if (parameterTypes != null) {
271+
return;
272+
}
273+
274+
parameterTypes = new Class<?>[parameters.length];
275+
for (int i = 0; i < parameters.length; i++) {
276+
Expression<?> parsed = LiteralUtils.defendExpression(parameters[i]);
277+
parameterTypes[i] = parsed.getReturnType();
278+
}
279+
}
280+
281+
/**
282+
* Attempts to get this function's signature.
283+
*/
284+
private Signature<?> getRegisteredSignature() {
285+
parseParameters();
286+
287+
if (Skript.debug()) {
288+
Skript.debug("Getting signature for '%s' with types %s",
289+
functionName, Arrays.toString(Arrays.stream(parameterTypes).map(Class::getSimpleName).toArray()));
290+
}
291+
292+
Retrieval<Signature<?>> attempt = FunctionRegistry.getRegistry().getSignature(script, functionName, parameterTypes);
293+
if (attempt.result() == RetrievalResult.EXACT) {
294+
return attempt.retrieved();
295+
}
296+
297+
// if we can't find a signature based on param types, try to match any function
298+
attempt = FunctionRegistry.getRegistry().getSignature(script, functionName);
299+
300+
if (attempt.result() == RetrievalResult.EXACT) {
301+
return attempt.retrieved();
302+
}
303+
304+
if (attempt.result() == RetrievalResult.AMBIGUOUS) {
305+
ambiguousError(attempt.conflictingArgs());
306+
}
307+
308+
return null;
309+
}
310+
311+
/**
312+
* Attempts to get this function's registered implementation.
313+
*/
314+
private Function<?> getRegisteredFunction() {
315+
parseParameters();
316+
317+
if (Skript.debug()) {
318+
Skript.debug("Getting function '%s' with types %s",
319+
functionName, Arrays.toString(Arrays.stream(parameterTypes).map(Class::getSimpleName).toArray()));
320+
}
321+
322+
Retrieval<Function<?>> attempt = FunctionRegistry.getRegistry().getFunction(script, functionName, parameterTypes);
323+
324+
if (attempt.result() == RetrievalResult.EXACT) {
325+
return attempt.retrieved();
326+
}
327+
328+
// if we can't find a signature based on param types, try to match any function
329+
attempt = FunctionRegistry.getRegistry().getFunction(script, functionName);
330+
331+
if (attempt.result() == RetrievalResult.EXACT) {
332+
return attempt.retrieved();
333+
}
334+
335+
if (attempt.result() == RetrievalResult.AMBIGUOUS) {
336+
ambiguousError(attempt.conflictingArgs());
337+
}
338+
339+
return null;
340+
}
341+
256342
public @Nullable Function<? extends T> getFunction() {
257343
return function;
258344
}
@@ -273,7 +359,7 @@ public boolean resetReturnValue() {
273359
// If needed, acquire the function reference
274360
if (function == null)
275361
//noinspection unchecked
276-
function = (Function<? extends T>) Functions.getFunction(functionName, script);
362+
function = (Function<? extends T>) getRegisteredFunction();
277363

278364
if (function == null) { // It might be impossible to resolve functions in some cases!
279365
Skript.error("Couldn't resolve call for '" + functionName + "'.");
@@ -369,6 +455,7 @@ public boolean isSingle(Expression<?>... arguments) {
369455

370456
/**
371457
* The contract is used in preference to the function for determining return type, etc.
458+
*
372459
* @return The contract determining this function's parse-time hints, potentially this reference
373460
*/
374461
public Contract getContract() {
@@ -391,7 +478,7 @@ public T[] execute(Event event, Object... arguments) {
391478
// If needed, acquire the function reference
392479
if (function == null)
393480
//noinspection unchecked
394-
function = (Function<? extends T>) Functions.getFunction(functionName, script);
481+
function = (Function<? extends T>) getRegisteredFunction();
395482

396483
if (function == null) { // It might be impossible to resolve functions in some cases!
397484
Skript.error("Couldn't resolve call for '" + functionName + "'.");
@@ -419,4 +506,23 @@ static Object[][] consign(Object... arguments) {
419506

420507
}
421508

509+
private void ambiguousError(Class<?>[][] conflictingArgs) {
510+
List<String> parts = new ArrayList<>();
511+
for (Class<?>[] args : conflictingArgs) {
512+
String argNames = Arrays.stream(args).map(arg -> {
513+
String name = Classes.getExactClassName(arg);
514+
515+
if (name == null) {
516+
return arg.getSimpleName();
517+
} else {
518+
return name.toLowerCase();
519+
}
520+
}).collect(Collectors.joining(", "));
521+
522+
parts.add("%s(%s)".formatted(functionName, argNames));
523+
}
524+
525+
Skript.error(AMBIGUOUS_ERROR, functionName, StringUtils.join(parts, ", ", " and "));
526+
}
527+
422528
}

0 commit comments

Comments
 (0)