• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wurstscript / WurstScript / 271

29 Sep 2025 12:12PM UTC coverage: 64.649% (+2.4%) from 62.222%
271

Pull #1096

circleci

Frotty
Merge branch 'perf-improvements' of https://github.com/wurstscript/WurstScript into perf-improvements
Pull Request #1096: Perf improvements

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.47
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/attributes/AttrFuncDef.java
1
package de.peeeq.wurstscript.attributes;
2

3
import com.google.common.collect.ImmutableCollection;
4
import com.google.common.collect.ImmutableList;
5
import com.google.common.collect.Lists;
6
import de.peeeq.wurstscript.WurstOperator;
7
import de.peeeq.wurstscript.ast.*;
8
import de.peeeq.wurstscript.attributes.names.FuncLink;
9
import de.peeeq.wurstscript.attributes.names.Visibility;
10
import de.peeeq.wurstscript.types.*;
11
import de.peeeq.wurstscript.utils.Utils;
12
import org.eclipse.jdt.annotation.Nullable;
13

14
import java.util.ArrayList;
15
import java.util.Collection;
16
import java.util.Collections;
17
import java.util.List;
18
import java.util.function.Predicate;
19
import java.util.stream.Collectors;
20

21

22
/**
23
 * this attribute find the variable definition for every variable reference
24
 */
25
public class AttrFuncDef {
×
26

27
    // TODO just use the attr function signature to get the def
28

29
    public final static String overloadingPlus = "op_plus";
30
    public final static String overloadingMinus = "op_minus";
31
    public final static String overloadingMult = "op_mult";
32
    public final static String overloadingDiv = "op_divReal";
33

34
    public static FuncLink calculate(final ExprFuncRef node) {
35

36
        Collection<FuncLink> funcs;
37
        if (node.getScopeName().length() > 0) {
1✔
38
            TypeDef typeDef = node.lookupType(node.getScopeName());
1✔
39
            if (typeDef == null) {
1✔
40
                node.addError("Could not find type " + node.getScopeName() + ".");
×
41
                return null;
×
42
            }
43
            WurstType receiverType = typeDef.attrTyp();
1✔
44
            funcs = node.lookupMemberFuncs(receiverType, node.getFuncName());
1✔
45
        } else {
1✔
46
            funcs = node.lookupFuncs(node.getFuncName());
1✔
47
        }
48
        if (funcs.size() == 0) {
1✔
49
            node.addError("Could not find a function with name " + node.getFuncName());
×
50
            return null;
×
51
        }
52
        funcs = filterInvisible(node.getFuncName(), node, funcs);
1✔
53
        if (funcs.size() == 1) {
1✔
54
            return Utils.getFirst(funcs);
1✔
55
        }
56
        if (funcs.size() > 1) {
×
57
            node.addError("Reference to function " + node.getFuncName() + " is ambiguous. Alternatives are:\n" + Utils.printAlternatives(funcs));
×
58
        }
59
        return Utils.getFirst(funcs);
×
60
    }
61

62

63
    public static @Nullable FuncLink calculate(ExprBinary node) {
64
        return getExtensionFunction(node.getLeft(), node.getRight(), node.getOp());
1✔
65
    }
66

67
    public static @Nullable FuncLink calculate(final ExprMemberMethod node) {
68

69
        Expr left = node.getLeft();
1✔
70
        WurstType leftType = left.attrTyp();
1✔
71
        String funcName = node.getFuncName();
1✔
72

73
        @Nullable FuncLink result = searchMemberFunc(node, leftType, funcName, argumentTypes(node));
1✔
74
        if (result == null) {
1✔
75
            node.addError("The method " + funcName + " is undefined for receiver of type " + leftType);
1✔
76
        }
77
        return result;
1✔
78
    }
79

80
    public static @Nullable FuncLink calculate(final ExprFunctionCall node) {
81
        FuncLink result = searchFunction(node.getFuncName(), node, argumentTypes(node));
1✔
82

83
        if (result == null) {
1✔
84
            String funcName = node.getFuncName();
1✔
85
            if (funcName.startsWith("InitTrig_")
1✔
86
                    && node.attrNearestFuncDef() != null
1✔
87
                    && node.attrNearestFuncDef().getName().equals("InitCustomTriggers")) {
1✔
88
                // ignore missing InitTrig functions
89
            } else {
90
                node.addError("Could not resolve reference to called function " + funcName);
1✔
91
            }
92
        }
93
        return result;
1✔
94
    }
95

96
    private static @Nullable FuncLink getExtensionFunction(Expr left, Expr right, WurstOperator op) {
97
        String funcName = op.getOverloadingFuncName();
1✔
98
        if (funcName == null || nativeOperator(op, left.attrTyp(), right.attrTyp(), left)) {
1✔
99
            return null;
1✔
100
        }
101
        return searchMemberFunc(left, left.attrTyp(), funcName, Collections.singletonList(right.attrTyp()));
1✔
102
    }
103

104

105
    /**
106
     * checks if operator is a native operator like for 1+2
107
     * TODO also check which operator is used?
108
     *
109
     * @param op
110
     * @param term
111
     */
112
    private static boolean nativeOperator(WurstOperator op, WurstType leftType, WurstType rightType, Element term) {
113
        return
1✔
114
                // numeric
115
                ((leftType.isSubtypeOf(WurstTypeInt.instance(), term) || leftType.isSubtypeOf(WurstTypeReal.instance(), term))
1✔
116
                        && (rightType.isSubtypeOf(WurstTypeInt.instance(), term) || rightType.isSubtypeOf(WurstTypeReal.instance(), term)))
1✔
117
                        // strings
118
                        || (op == WurstOperator.PLUS && leftType instanceof WurstTypeString && rightType instanceof WurstTypeString);
119
    }
120

121

122
    public static List<WurstType> argumentTypes(StmtCall node) {
123
        List<WurstType> result = Lists.newArrayList();
1✔
124
        for (Expr arg : node.getArgs()) {
1✔
125
            WurstType argType;
126
            if (arg instanceof ExprClosure) {
1✔
127
                // for closures, we only calculate the type, if all argument types are specified:
128
                ExprClosure closure = (ExprClosure) arg;
1✔
129
                if (closure.getShortParameters().stream().allMatch(p -> p.getTypOpt() instanceof TypeExpr)) {
1✔
130
                    argType = arg.attrTyp();
1✔
131
                } else {
132
                    WurstType expected = arg.attrExpectedTyp();
1✔
133

134
                    List<WurstType> paramTypes = new ArrayList<>();
1✔
135
                    boolean hasInferredType = false;
1✔
136
                    int pIndex = 0;
1✔
137
                    for (WShortParameter p : closure.getShortParameters()) {
1✔
138
                        if (p.getTypOpt() instanceof TypeExpr) {
1✔
139
                            paramTypes.add(p.getTypOpt().attrTyp());
×
140
                        } else {
141
                            WurstType pt = AttrVarDefType.getParameterTypeFromClosureType(p, pIndex, expected, false);
1✔
142
                            paramTypes.add(pt);
1✔
143
                            if (pt instanceof WurstTypeInfer) {
1✔
144
                                hasInferredType = true;
×
145
                            }
146
                        }
147
                        pIndex++;
1✔
148
                    }
1✔
149
                    if (hasInferredType) {
1✔
150
                        // if there are unknown parameter types, use an approximated function type for overloading resolution
151
                        WurstType resultType = WurstTypeInfer.instance();
×
152
                        argType = new WurstTypeClosure(paramTypes, resultType);
×
153
                    } else {
×
154
                        // if there are no unknown types for the argument, then it should be safe to directly calculate the type
155
                        argType = arg.attrTyp();
1✔
156
                    }
157
                }
158
            } else {
1✔
159
                argType = arg.attrTyp();
1✔
160
            }
161
            result.add(argType);
1✔
162
        }
1✔
163
        return result;
1✔
164
    }
165

166
    /** very simple calculation of argument types without using expected types in closures */
167
    public static List<WurstType> argumentTypesPre(StmtCall node) {
168
        List<WurstType> result = Lists.newArrayList();
1✔
169
        for (Expr arg : node.getArgs()) {
1✔
170
            WurstType argType;
171
            if (arg instanceof ExprClosure) {
1✔
172
                // for closures, we only calculate the type, if all argument types are specified:
173
                ExprClosure closure = (ExprClosure) arg;
1✔
174
                boolean b = true;
1✔
175
                for (WShortParameter wShortParameter : closure.getShortParameters()) {
1✔
176
                    if (!(wShortParameter.getTypOpt() instanceof TypeExpr)) {
1✔
177
                        b = false;
1✔
178
                        break;
1✔
179
                    }
180
                }
1✔
181
                if (b) {
1✔
182
                    argType = arg.attrTyp();
1✔
183
                } else {
184
                    List<WurstType> paramTypes = new ArrayList<>();
1✔
185
                    for (WShortParameter p : closure.getShortParameters()) {
1✔
186
                        if (p.getTypOpt() instanceof TypeExpr) {
1✔
187
                            paramTypes.add(p.getTypOpt().attrTyp());
×
188
                        } else {
189
                            paramTypes.add(WurstTypeInfer.instance());
1✔
190
                        }
191
                    }
1✔
192
                    WurstType resultType = WurstTypeInfer.instance();
1✔
193
                    argType = new WurstTypeClosure(paramTypes, resultType);
1✔
194
                }
195
            } else {
1✔
196
                argType = arg.attrTyp();
1✔
197
            }
198
            result.add(argType);
1✔
199
        }
1✔
200
        return result;
1✔
201
    }
202

203

204
    private static FuncLink searchFunction(String funcName, @Nullable FuncRef node, List<WurstType> argumentTypes) {
205
        if (node == null) {
1✔
206
            return null;
×
207
        }
208
        ImmutableCollection<FuncLink> funcs1 = node.lookupFuncs(funcName);
1✔
209
        if (funcs1.size() == 0) {
1✔
210
            if (funcName.startsWith("InitTrig_")) {
1✔
211
                // ignore error
212
                return null;
1✔
213
            }
214
            if (node instanceof Annotation) {
1✔
215
                node.addWarning("Annotation " + funcName + " is not defined.");
1✔
216
            } else {
217
                node.addError("Reference to function " + funcName + " could not be resolved.");
1✔
218
            }
219
            return null;
1✔
220
        } else if (funcs1.size() == 1) {
1✔
221
            return Utils.getFirst(funcs1);
1✔
222
        }
223
        // distinguish between annotation functions and others
224
        List<FuncLink> funcs = filterAnnotation(node, funcs1);
1✔
225
        if (funcs.size() == 1) {
1✔
226
            return Utils.getFirst(funcs);
1✔
227
        }
228

229
        // filter out the methods which are private somewhere else
230
        funcs = filterInvisible(funcName, node, funcs);
1✔
231
        if (funcs.size() == 1) {
1✔
232
            return Utils.getFirst(funcs);
1✔
233
        }
234

235
        funcs = filterByReceiverType(node, funcName, funcs);
1✔
236
        if (funcs.size() == 1) {
1✔
237
            return Utils.getFirst(funcs);
1✔
238
        }
239

240
        funcs = filterByParameters(node, argumentTypes, funcs);
1✔
241
        if (funcs.size() == 1) {
1✔
242
            return Utils.getFirst(funcs);
1✔
243
        }
244

245
        funcs = ignoreWithIfNotDefinedAnnotation(node, funcs);
1✔
246
        if (funcs.size() == 1) {
1✔
247
            return Utils.getFirst(funcs);
1✔
248
        }
249

250
        funcs = useLocalPackageIfPossible(node, funcs);
×
251
        if (funcs.size() == 1) {
×
252
            return Utils.getFirst(funcs);
×
253
        }
254

255
        node.addError("Call to function " + funcName + " is ambiguous. Alternatives are:\n"
×
256
                + Utils.printAlternatives(funcs));
×
257
        return Utils.getFirst(funcs);
×
258
    }
259

260
    static ImmutableList<FuncLink> filterAnnotation(FuncRef node, ImmutableCollection<FuncLink> funcs1) {
261
        Predicate<FuncLink> filter;
262
        if (node instanceof Annotation) {
1✔
263
            filter = f -> f.getDef().hasAnnotation("@annotation");
1✔
264
        } else {
265
            filter = f -> !f.getDef().hasAnnotation("@annotation");
1✔
266
        }
267
        ImmutableList<FuncLink> res = funcs1.stream()
1✔
268
                .filter(filter)
1✔
269
                .collect(Utils.toImmutableList());
1✔
270
        if (res.isEmpty()) {
1✔
271
            return ImmutableList.copyOf(funcs1);
×
272
        }
273
        return res;
1✔
274
    }
275

276
    private static List<FuncLink> ignoreWithIfNotDefinedAnnotation(FuncRef node, List<FuncLink> funcs) {
277
        return funcs.stream()
1✔
278
                .filter(fl -> !fl.hasIfNotDefinedAnnotation())
1✔
279
                .collect(Collectors.toList());
1✔
280
    }
281

282

283
    private static List<FuncLink> useLocalPackageIfPossible(FuncRef node,
284
                                                            List<FuncLink> funcs) {
285
        int localCount = 0;
×
286
        FuncLink local = null;
×
287
        PackageOrGlobal myPackage = node.attrNearestPackage();
×
288
        for (FuncLink n : funcs) {
×
289
            if (n.getDef().attrNearestPackage() == myPackage) {
×
290
                local = n;
×
291
                localCount++;
×
292
            }
293
        }
×
294
        if (localCount == 0) {
×
295
            return funcs;
×
296
        } else if (localCount == 1) {
×
297
            return ImmutableList.of(local);
×
298
        }
299
        List<FuncLink> result = Lists.newArrayList();
×
300
        for (FuncLink n : funcs) {
×
301
            if (n.getDef().attrNearestPackage() == myPackage) {
×
302
                result.add(n);
×
303
            }
304
        }
×
305
        return result;
×
306
    }
307

308

309
    private static @Nullable FuncLink searchMemberFunc(Expr node, WurstType leftType, String funcName, List<WurstType> argumentTypes) {
310
        Collection<FuncLink> funcs1 = node.lookupMemberFuncs(leftType, funcName);
1✔
311
        if (funcs1.size() == 0) {
1✔
312
            return null;
1✔
313
        }
314
        if (funcs1.size() == 1) {
1✔
315
            return Utils.getFirst(funcs1);
1✔
316
        }
317
        // filter out the methods which are private somewhere else
318
        List<FuncLink> funcs = filterInvisible(funcName, node, funcs1);
1✔
319
        if (funcs.size() == 1) {
1✔
320
            return Utils.getFirst(funcs);
1✔
321
        }
322

323
        // chose method with most specific receiver type
324
        funcs = filterByReceiverType(node, funcName, funcs);
1✔
325
        if (funcs.size() == 1) {
1✔
326
            return Utils.getFirst(funcs);
1✔
327
        }
328

329
        funcs = filterByParameters(node, argumentTypes, funcs);
1✔
330
        if (funcs.size() == 1) {
1✔
331
            return Utils.getFirst(funcs);
1✔
332
        }
333

334
        node.addError("Call to function " + funcName + " is ambiguous. Alternatives are:\n" + Utils.printAlternatives(funcs));
×
335
        return Utils.getFirst(funcs);
×
336
    }
337

338

339
    private static List<FuncLink> filterByParameters(Element node,
340
                                                     List<WurstType> argumentTypes, List<FuncLink> funcs) {
341
        // filter out methods with wrong number of params
342
        funcs = filterByParamaeterNumber(argumentTypes, funcs);
1✔
343
        if (funcs.size() == 1) {
1✔
344
            return funcs;
1✔
345
        }
346

347
        // filter out methods for which the arguments have wrong types
348
        funcs = filterByParameterTypes(node, argumentTypes, funcs);
1✔
349
        return funcs;
1✔
350
    }
351

352

353
    private static List<FuncLink> filterByParameterTypes(
354
            Element node, List<WurstType> argumentTypes, List<FuncLink> funcs3) {
355
        List<FuncLink> funcs4 = Lists.newArrayListWithCapacity(funcs3.size());
1✔
356
        nextFunc:
357
        for (FuncLink f : funcs3) {
1✔
358
            VariableBinding mapping = f.getVariableBinding();
1✔
359
            for (int i = 0; i < argumentTypes.size(); i++) {
1✔
360
                // TODO use matching here
361
                WurstType at = argumentTypes.get(i);
1✔
362
                WurstType pt = f.getParameterType(i);
1✔
363
                VariableBinding m2 = at.matchAgainstSupertype(pt, node, mapping, VariablePosition.RIGHT);
1✔
364
                if (m2 == null) {
1✔
365
                    continue nextFunc;
1✔
366
                }
367
                mapping = m2;
1✔
368
            }
369
            funcs4.add(f);
1✔
370
        }
1✔
371
        if (funcs4.size() == 0) {
1✔
372
            return ImmutableList.of(Utils.getFirst(funcs3));
×
373
        } else if (funcs4.size() == 1) {
1✔
374
            return ImmutableList.of(Utils.getFirst(funcs4));
1✔
375
        } else if (argumentTypes.stream().anyMatch(t -> t instanceof WurstTypeUnknown)) {
1✔
376
            // if some argument type could not be determined, we don't want errors here, just take the first one
377
            return ImmutableList.of(Utils.getFirst(funcs4));
×
378
        }
379
        return funcs4;
1✔
380
    }
381

382

383
    private static List<FuncLink> filterByParamaeterNumber(List<WurstType> argumentTypes, List<FuncLink> funcs2) {
384
        List<FuncLink> funcs3 = Lists.newArrayListWithCapacity(funcs2.size());
1✔
385
        for (FuncLink f : funcs2) {
1✔
386
            if (f.getParameterTypes().size() == argumentTypes.size()
1✔
387
                    || (f.getParameterTypes().size() == 1 && f.getParameterTypes().get(0) instanceof WurstTypeVararg)) {
1✔
388
                funcs3.add(f);
1✔
389
            }
390
        }
1✔
391
        if (funcs3.size() == 0) {
1✔
392
            return Collections.singletonList(Utils.getFirst(funcs2));
×
393
        }
394
        return funcs3;
1✔
395
    }
396

397

398
    private static List<FuncLink> filterInvisible(String funcName, Element node, Collection<FuncLink> funcs1) {
399
        if (node.attrSource().getFile().equals("<REPL>")) {
1✔
400
            // no filtering of invisible names in repl:
401
            return Lists.newArrayList(funcs1);
×
402
        }
403
        List<FuncLink> funcs2 = Lists.newArrayListWithCapacity(funcs1.size());
1✔
404
        for (FuncLink nl : funcs1) {
1✔
405
            if (!(nl.getVisibility() == Visibility.PRIVATE_OTHER
1✔
406
                    || nl.getVisibility() == Visibility.PROTECTED_OTHER)) {
1✔
407
                funcs2.add(nl);
1✔
408
            }
409
        }
1✔
410

411
        funcs2 = Utils.removedDuplicates(funcs2);
1✔
412

413
        if (funcs2.size() == 0) {
1✔
414
            node.addError("Function " + funcName + " is not visible here.");
×
415
            return ImmutableList.of(Utils.getFirst(funcs1));
×
416
        }
417
        return funcs2;
1✔
418
    }
419

420

421
    private static List<FuncLink> filterByReceiverType(Element node,
422
                                                       String funcName, List<FuncLink> funcs2) {
423
        List<FuncLink> funcs3 = Lists.newArrayListWithCapacity(funcs2.size());
1✔
424
        for (FuncLink f : funcs2) {
1✔
425
            boolean existsMoreSpecific = false;
1✔
426
            WurstType f_receiverType = f.getReceiverType();
1✔
427
            if (f_receiverType != null) {
1✔
428
                for (FuncLink g : funcs2) {
1✔
429
                    if (f != g) {
1✔
430
                        WurstType g_receiverType = g.getReceiverType();
1✔
431
                        if (g_receiverType != null
1✔
432
                                && g_receiverType.isSubtypeOf(f_receiverType, node)
1✔
433
                                && !g_receiverType.equalsType(f_receiverType, node)) {
1✔
434
                            existsMoreSpecific = true;
1✔
435
                            break;
1✔
436
                        }
437
                    }
438
                }
1✔
439
            }
440
            if (!existsMoreSpecific) {
1✔
441
                funcs3.add(f);
1✔
442
            }
443
        }
1✔
444

445
        if (funcs3.size() == 0) {
1✔
446
            node.addError("Function " + funcName + " dfopsdfmpso.");
×
447
            return ImmutableList.of(Utils.getFirst(funcs2));
×
448
        }
449
        return funcs3;
1✔
450
    }
451

452

453
    public static FunctionDefinition calculateDef(FuncRef e) {
454
        FuncLink f = e.attrFuncLink();
1✔
455
        return f == null ? null : f.getDef();
1✔
456
    }
457

458
    public static FunctionDefinition calculateDef(ExprBinary e) {
459
        FuncLink f = e.attrFuncLink();
1✔
460
        return f == null ? null : f.getDef();
1✔
461
    }
462

463
    public static FuncLink calculate(Annotation node) {
464
        List<WurstType> argumentTypes = node.getArgs().stream()
1✔
465
                .map(Expr::attrTyp)
1✔
466
                .collect(Collectors.toList());
1✔
467
        FuncLink result = searchFunction(node.getFuncName(), node, argumentTypes);
1✔
468
        if (result == null) {
1✔
469
            return null;
1✔
470
        }
471
        FunctionDefinition def = result.getDef();
1✔
472
        if (def != null && !def.hasAnnotation("@annotation")) {
1✔
473
            node.addWarning("The function " + def.getName() + " must be annotated with @annotation to be usable as an annotation.");
1✔
474
        }
475
        return result;
1✔
476
    }
477
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc