11package ch .njol .skript .lang .function ;
22
3-
43import ch .njol .skript .Skript ;
54import ch .njol .skript .SkriptAPIException ;
65import ch .njol .skript .classes .ClassInfo ;
98import ch .njol .skript .lang .KeyProviderExpression ;
109import ch .njol .skript .lang .KeyedValue ;
1110import ch .njol .skript .lang .SkriptParser ;
11+ import ch .njol .skript .lang .function .FunctionRegistry .Retrieval ;
12+ import ch .njol .skript .lang .function .FunctionRegistry .RetrievalResult ;
1213import ch .njol .skript .log .RetainingLogHandler ;
1314import ch .njol .skript .log .SkriptLogger ;
1415import ch .njol .skript .registrations .Classes ;
1516import ch .njol .skript .util .Contract ;
1617import ch .njol .skript .util .LiteralUtils ;
1718import ch .njol .util .StringUtils ;
1819import org .bukkit .event .Event ;
19- import org .skriptlang .skript .util .Executable ;
2020import org .jetbrains .annotations .Nullable ;
2121import org .skriptlang .skript .lang .converter .Converters ;
22-
22+ import org . skriptlang . skript . util . Executable ;
2323import java .util .*;
24+ import java .util .stream .Collectors ;
2425
2526/**
2627 * Reference to a {@link Function Skript function}.
2728 */
2829public 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