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

wurstscript / WurstScript / 266

29 Sep 2025 09:10AM UTC coverage: 62.244% (+0.02%) from 62.222%
266

Pull #1096

circleci

Frotty
Optimize imports
Pull Request #1096: Perf improvements

17480 of 28083 relevant lines covered (62.24%)

0.62 hits per line

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

81.33
de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/validation/WurstValidator.java
1
package de.peeeq.wurstscript.validation;
2

3
import com.google.common.collect.*;
4
import de.peeeq.wurstscript.WLogger;
5
import de.peeeq.wurstscript.ast.*;
6
import de.peeeq.wurstscript.attributes.CofigOverridePackages;
7
import de.peeeq.wurstscript.attributes.CompileError;
8
import de.peeeq.wurstscript.attributes.ImplicitFuncs;
9
import de.peeeq.wurstscript.attributes.names.DefLink;
10
import de.peeeq.wurstscript.attributes.names.FuncLink;
11
import de.peeeq.wurstscript.attributes.names.NameLink;
12
import de.peeeq.wurstscript.attributes.names.VarLink;
13
import de.peeeq.wurstscript.gui.ProgressHelper;
14
import de.peeeq.wurstscript.types.*;
15
import de.peeeq.wurstscript.utils.Utils;
16
import de.peeeq.wurstscript.validation.controlflow.DataflowAnomalyAnalysis;
17
import de.peeeq.wurstscript.validation.controlflow.ReturnsAnalysis;
18
import io.vavr.Tuple2;
19
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
20
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
21
import org.eclipse.jdt.annotation.Nullable;
22

23
import java.util.*;
24
import java.util.stream.Collectors;
25

26
import static de.peeeq.wurstscript.attributes.SmallHelpers.superArgs;
27

28
/**
29
 * this class validates a wurstscript program
30
 * <p>
31
 * it has visit methods for different elements in the AST and checks whether
32
 * these are correct
33
 * <p>
34
 * the validation phase might not find all errors, code transformation and
35
 * optimization phases might detect other errors because they do a more
36
 * sophisticated analysis of the program
37
 * <p>
38
 * also note that many cases are already caught by the calculation of the
39
 * attributes
40
 */
41
public class WurstValidator {
42
    private enum Phase { LIGHT, HEAVY }
1✔
43
    private Phase phase = Phase.LIGHT;
1✔
44
    private boolean isHeavy() { return phase == Phase.HEAVY; }
1✔
45

46

47
    private final ArrayList<FunctionLike> heavyFunctions = new ArrayList<>();
1✔
48
    private final ArrayList<ExprStatementsBlock> heavyBlocks = new ArrayList<>();
1✔
49

50
    private final WurstModel prog;
51
    private int functionCount;
52
    private int visitedFunctions;
53
    private final Multimap<WScope, WScope> calledFunctions = HashMultimap.create();
1✔
54
    private @Nullable Element lastElement = null;
1✔
55
    private final HashSet<String> trveWrapperFuncs = new HashSet<>();
1✔
56
    private final HashMap<String, HashSet<FunctionCall>> wrapperCalls = new HashMap<>();
1✔
57

58
    public WurstValidator(WurstModel root) {
1✔
59
        this.prog = root;
1✔
60
    }
1✔
61

62
    public void validate(Collection<CompilationUnit> toCheck) {
63
        try {
64
            functionCount = countFunctions();
1✔
65
            visitedFunctions = 0;
1✔
66
            heavyFunctions.clear();
1✔
67
            heavyBlocks.clear();
1✔
68

69
            lightValidation(toCheck);
1✔
70

71
            heavyValidation();
1✔
72

73
            prog.getErrorHandler().setProgress("Post checks", 0.55);
1✔
74
            postChecks(toCheck);
1✔
75

76
        } catch (RuntimeException e) {
×
77
            WLogger.severe(e);
×
78
            Element le = lastElement;
×
79
            if (le != null) {
×
80
                le.addError("Encountered compiler bug near element " + Utils.printElement(le) + ":\n"
×
81
                    + Utils.printException(e));
×
82
            } else {
83
                throw e;
×
84
            }
85
        }
1✔
86
    }
1✔
87

88
    private void heavyValidation() {
89
        // ===== Phase 2: HEAVY (process only collected targets) =====
90
        phase = Phase.HEAVY;
1✔
91
        visitedFunctions = 0;
1✔
92
        prog.getErrorHandler().setProgress("Validation (control-flow + dataflow)", 0.5);
1✔
93

94
        // functions: returns + DFA + reachability walk inside the function body
95
        for (FunctionLike f : heavyFunctions) {
1✔
96
            // returns + DFA
97
            checkUninitializedVars(f);
1✔
98

99
            // reachability: walk only the function body statements
100
            Element body = (f instanceof FunctionImplementation)
1✔
101
                ? ((FunctionImplementation) f).getBody()
1✔
102
                : f; // closures use ExprStatementsBlock path below
1✔
103
            walkReachability(body);
1✔
104
        }
1✔
105

106
        // closure blocks collected for DFA
107
        for (ExprStatementsBlock b : heavyBlocks) {
1✔
108
            new DataflowAnomalyAnalysis(false).execute(b);
1✔
109
            walkReachability(b);
1✔
110
        }
1✔
111
    }
1✔
112

113
    private void lightValidation(Collection<CompilationUnit> toCheck) {
114
        // ===== Phase 1: LIGHT (all regular checks, collect heavy targets) =====
115
        phase = Phase.LIGHT;
1✔
116
        prog.getErrorHandler().setProgress("Validation (light)",
1✔
117
            ProgressHelper.getValidatorPercent(0, Math.max(1, functionCount)));
1✔
118

119
        for (CompilationUnit cu : toCheck) {
1✔
120
            walkTree(cu);
1✔
121
        }
1✔
122

123
        // Build CFG once for heavy phase (enables reachability/prev/next attrs)
124
        for (CompilationUnit cu : toCheck) {
1✔
125
            computeFlowAttributes(cu);
1✔
126
        }
1✔
127
    }
1✔
128

129
    /** Visit only statements under root and run checkReachability where applicable. */
130
    private void walkReachability(Element root) {
131
        // fast local traversal; no other checks
132
        Deque<Element> stack = new ArrayDeque<>();
1✔
133
        stack.push(root);
1✔
134
        while (!stack.isEmpty()) {
1✔
135
            Element e = stack.pop();
1✔
136
            if (e instanceof WStatement) {
1✔
137
                checkReachability((WStatement) e);
1✔
138
            }
139
            for (int i = e.size() - 1; i >= 0; i--) {
1✔
140
                stack.push(e.get(i));
1✔
141
            }
142
        }
1✔
143
    }
1✔
144

145

146

147
    /**
148
     * checks done after walking the tree
149
     */
150
    private void postChecks(Collection<CompilationUnit> toCheck) {
151
        checkUnusedImports(toCheck);
1✔
152
        ValidateGlobalsUsage.checkGlobalsUsage(toCheck);
1✔
153
        ValidateClassMemberUsage.checkClassMembers(toCheck);
1✔
154
        ValidateLocalUsage.checkLocalsUsage(toCheck);
1✔
155

156
        trveWrapperFuncs.forEach(wrapper -> {
1✔
157
            if (wrapperCalls.containsKey(wrapper)) {
1✔
158
                wrapperCalls.get(wrapper).forEach(call -> {
1✔
159
                    if (call.getArgs().size() > 1 && call.getArgs().get(1) instanceof ExprStringVal) {
1✔
160
                        ExprStringVal varName = (ExprStringVal) call.getArgs().get(1);
1✔
161
                        TRVEHelper.protectedVariables.add(varName.getValS());
1✔
162
                        WLogger.info("keep: " + varName.getValS());
1✔
163
                    } else {
1✔
164
                        call.addError("Map contains TriggerRegisterVariableEvent with non-constant arguments. Can't be optimized.");
×
165
                    }
166
                });
1✔
167
            }
168
        });
1✔
169
    }
1✔
170

171
    private void checkUnusedImports(Collection<CompilationUnit> toCheck) {
172
        for (CompilationUnit cu : toCheck) {
1✔
173
            for (WPackage p : cu.getPackages()) {
1✔
174
                checkUnusedImports(p);
1✔
175
            }
1✔
176
        }
1✔
177
    }
1✔
178

179
    private void checkUnusedImports(WPackage p) {
180
        Set<PackageOrGlobal> used = Sets.newLinkedHashSet();
1✔
181

182
        collectUsedPackages(used, p.getElements());
1✔
183

184
        // String usedToStr =
185
        // used.stream().map(Utils::printElement).sorted().collect(Collectors.joining(",
186
        // "));
187
        // System.out.println("used = " + usedToStr);
188

189
        // contributed packages for each import
190
        Map<WImport, Set<WPackage>> contributions = new HashMap<>();
1✔
191
        for (WImport imp : p.getImports()) {
1✔
192
            Set<WPackage> contributedPackages = contributedPackages(imp.attrImportedPackage(), used, new HashSet<>());
1✔
193
            contributions.put(imp, contributedPackages);
1✔
194
            // System.out.println( imp.getPackagename() + " contributes = " +
195
            // contributedPackages.stream().map(Utils::printElement).sorted().collect(Collectors.joining(",
196
            // ")));
197
        }
1✔
198

199
        // check for imports, which only contribute a subset of some other
200
        // import
201
        for (WImport imp : p.getImports()) {
1✔
202
            if (imp.attrImportedPackage() == null || imp.getIsPublic() || imp.getPackagename().equals("Wurst")) {
1✔
203
                continue;
1✔
204
            }
205
            Set<WPackage> impContributions = contributions.get(imp);
1✔
206
            if (impContributions.isEmpty()) {
1✔
207
                imp.addWarning("The import " + imp.getPackagename() + " is never used");
1✔
208
            } else {
209
                for (WImport imp2 : p.getImports()) {
1✔
210
                    if (imp == imp2) {
1✔
211
                        continue;
1✔
212
                    }
213
                    if (contributions.get(imp2).containsAll(impContributions)) {
1✔
214
                        imp.addWarning("The import " + imp.getPackagename()
1✔
215
                                + " can be removed, because it is already included in " + imp2.getPackagename() + ".");
1✔
216
                        break;
1✔
217
                    }
218

219
                }
1✔
220
            }
221
        }
1✔
222
    }
1✔
223

224
    private Set<WPackage> contributedPackages(WPackage p, Set<PackageOrGlobal> used, Set<WPackage> visited) {
225
        if (p == null) {
1✔
226
            return Collections.emptySet();
1✔
227
        }
228
        visited.add(p);
1✔
229
        Set<WPackage> result = new HashSet<>();
1✔
230
        if (used.contains(p)) {
1✔
231
            result.add(p);
1✔
232
        }
233
        for (WImport imp : p.getImports()) {
1✔
234
            WPackage imported = imp.attrImportedPackage();
1✔
235
            if (imp.getPackagename().equals("Wurst") || visited.contains(imported)) {
1✔
236
                continue;
1✔
237
            }
238
            if (imp.getIsPublic()) {
1✔
239
                result.addAll(contributedPackages(imported, used, visited));
1✔
240
            }
241
        }
1✔
242
        return result;
1✔
243
    }
244

245
    private WPackage getConfiguredPackage(Element e) {
246
        PackageOrGlobal p = e.attrNearestPackage();
1✔
247
        if(p instanceof WPackage) {
1✔
248
            if (p.getModel().attrConfigOverridePackages().containsValue(p)) {
1✔
249
                for(WPackage k : p.getModel().attrConfigOverridePackages().keySet()) {
1✔
250
                    if(p.getModel().attrConfigOverridePackages().get(k).equals(p)) {
1✔
251
                        return k;
1✔
252
                    }
253
                }
×
254
            }
255
        }
256
        return null;
×
257
    }
258

259
    private void collectUsedPackages(Set<PackageOrGlobal> used, Element root) {
260
        ArrayDeque<Object> stack = new ArrayDeque<>();
1✔
261
        // Push (element, visited=false). Boolean marks "post" processing.
262
        stack.push(Boolean.FALSE);
1✔
263
        stack.push(root);
1✔
264

265
        while (!stack.isEmpty()) {
1✔
266
            Element e = (Element) stack.pop();
1✔
267
            boolean visited = (Boolean) stack.pop();
1✔
268

269
            if (!visited) {
1✔
270
                // schedule post-visit
271
                stack.push(Boolean.TRUE);
1✔
272
                stack.push(e);
1✔
273

274
                // push children for pre-visit (so they’re processed before e)
275
                for (int i = e.size() - 1; i >= 0; i--) {
1✔
276
                    Element c = e.get(i);
1✔
277
                    stack.push(Boolean.FALSE);
1✔
278
                    stack.push(c);
1✔
279
                }
280
                continue;
1✔
281
            }
282

283
            // === post-order node work (same as your original) ===
284
            if (e instanceof FuncRef) {
1✔
285
                FuncRef fr = (FuncRef) e;
1✔
286
                FuncLink link = fr.attrFuncLink();
1✔
287
                if (link != null) {
1✔
288
                    used.add(link.getDef().attrNearestPackage());
1✔
289
                    if (link.getDef().attrHasAnnotation("@config")) {
1✔
290
                        WPackage configPackage = getConfiguredPackage(link.getDef());
1✔
291
                        if (configPackage != null) {
1✔
292
                            used.add(configPackage);
1✔
293
                        }
294
                    }
295
                }
296
            }
297

298
            if (e instanceof NameRef) {
1✔
299
                NameRef nr = (NameRef) e;
1✔
300
                NameLink def = nr.attrNameLink();
1✔
301
                if (def != null) {
1✔
302
                    used.add(def.getDef().attrNearestPackage());
1✔
303
                    if (def.getDef().attrHasAnnotation("@config")) {
1✔
304
                        WPackage configPackage = getConfiguredPackage(def.getDef());
1✔
305
                        if (configPackage != null) {
1✔
306
                            used.add(configPackage);
1✔
307
                        }
308
                    }
309
                }
310
            }
311

312
            if (e instanceof TypeRef) {
1✔
313
                TypeRef t = (TypeRef) e;
1✔
314
                TypeDef def = t.attrTypeDef();
1✔
315
                if (def != null) {
1✔
316
                    used.add(def.attrNearestPackage());
1✔
317
                }
318
            }
319

320
            if (e instanceof ExprBinary) {
1✔
321
                ExprBinary binop = (ExprBinary) e;
1✔
322
                FuncLink def = binop.attrFuncLink();
1✔
323
                if (def != null) {
1✔
324
                    used.add(def.getDef().attrNearestPackage());
1✔
325
                }
326
            }
327

328
            if (e instanceof Expr) {
1✔
329
                WurstType typ = ((Expr) e).attrTyp();
1✔
330
                if (typ instanceof WurstTypeNamedScope) {
1✔
331
                    WurstTypeNamedScope ns = (WurstTypeNamedScope) typ;
1✔
332
                    NamedScope def = ns.getDef();
1✔
333
                    if (def != null) {
1✔
334
                        used.add(def.attrNearestPackage());
1✔
335
                    }
336
                } else if (typ instanceof WurstTypeTuple) {
1✔
337
                    TupleDef def = ((WurstTypeTuple) typ).getTupleDef();
1✔
338
                    used.add(def.attrNearestPackage());
1✔
339
                }
340
            }
341

342
            if (e instanceof ModuleUse) {
1✔
343
                ModuleUse mu = (ModuleUse) e;
1✔
344
                @Nullable ModuleDef def = mu.attrModuleDef();
1✔
345
                if (def != null) {
1✔
346
                    used.add(def.attrNearestPackage());
1✔
347
                }
348
            }
349
        }
1✔
350
    }
1✔
351

352

353
    private void walkTree(Element root) {
354
        ArrayDeque<Element> stack = new ArrayDeque<>();
1✔
355
        stack.push(root);
1✔
356

357
        while (!stack.isEmpty()) {
1✔
358
            Element e = stack.pop();
1✔
359
            lastElement = e;
1✔
360
            check(e);
1✔
361
            lastElement = null;
1✔
362

363
            // left→right order: push in reverse
364
            for (int i = e.size() - 1; i >= 0; i--) {
1✔
365
                stack.push(e.get(i));
1✔
366
            }
367
        }
1✔
368
    }
1✔
369

370
    private void check(Element e) {
371
        try {
372
            if (e instanceof Annotation)
1✔
373
                checkAnnotation((Annotation) e);
1✔
374
            if (e instanceof AstElementWithTypeParameters)
1✔
375
                checkTypeParameters((AstElementWithTypeParameters) e);
1✔
376
            if (e instanceof AstElementWithNameId)
1✔
377
                checkName((AstElementWithNameId) e);
1✔
378
            if (e instanceof ClassDef) {
1✔
379
                checkAbstractMethods((ClassDef) e);
1✔
380
                visit((ClassDef) e);
1✔
381
            }
382
            if (e instanceof ClassOrModule)
1✔
383
                checkConstructorsUnique((ClassOrModule) e);
1✔
384
            if (e instanceof CompilationUnit)
1✔
385
                checkPackageName((CompilationUnit) e);
1✔
386
            if (e instanceof ConstructorDef) {
1✔
387
                checkConstructor((ConstructorDef) e);
1✔
388
                checkConstructorSuperCall((ConstructorDef) e);
1✔
389
            }
390
            if (e instanceof ExprBinary)
1✔
391
                visit((ExprBinary) e);
1✔
392
            if (e instanceof ExprClosure)
1✔
393
                checkClosure((ExprClosure) e);
1✔
394
            if (e instanceof ExprEmpty)
1✔
395
                checkExprEmpty((ExprEmpty) e);
1✔
396
            if (e instanceof ExprIntVal)
1✔
397
                checkIntVal((ExprIntVal) e);
1✔
398
            if (e instanceof ExprFuncRef)
1✔
399
                checkFuncRef((ExprFuncRef) e);
1✔
400
            if (e instanceof ExprFunctionCall) {
1✔
401
                checkBannedFunctions((ExprFunctionCall) e);
1✔
402
                visit((ExprFunctionCall) e);
1✔
403
            }
404
            if (e instanceof ExprMemberMethod)
1✔
405
                visit((ExprMemberMethod) e);
1✔
406
            if (e instanceof ExprMemberVar)
1✔
407
                checkMemberVar((ExprMemberVar) e);
1✔
408
            if (e instanceof ExprMemberArrayVar)
1✔
409
                checkMemberArrayVar((ExprMemberArrayVar) e);
1✔
410
            if (e instanceof ExprNewObject) {
1✔
411
                checkNewObj((ExprNewObject) e);
1✔
412
                visit((ExprNewObject) e);
1✔
413
            }
414
            if (e instanceof ExprNull)
1✔
415
                checkExprNull((ExprNull) e);
1✔
416
            if (e instanceof ExprVarAccess)
1✔
417
                visit((ExprVarAccess) e);
1✔
418
            if (e instanceof ExprVarArrayAccess)
1✔
419
                checkArrayAccess((ExprVarArrayAccess) e);
1✔
420
            if (e instanceof ExtensionFuncDef)
1✔
421
                visit((ExtensionFuncDef) e);
1✔
422
            if (e instanceof FuncDef)
1✔
423
                visit((FuncDef) e);
1✔
424
            if (e instanceof FuncRef)
1✔
425
                checkFuncRef((FuncRef) e);
1✔
426
            if (e instanceof FunctionLike)
1✔
427
                checkUninitializedVars((FunctionLike) e);
1✔
428
            if (e instanceof GlobalVarDef)
1✔
429
                visit((GlobalVarDef) e);
1✔
430
            if (e instanceof HasModifier)
1✔
431
                checkModifiers((HasModifier) e);
1✔
432
            if (e instanceof HasTypeArgs)
1✔
433
                checkTypeBinding((HasTypeArgs) e);
1✔
434
            if (e instanceof InterfaceDef)
1✔
435
                checkInterfaceDef((InterfaceDef) e);
1✔
436
            if (e instanceof LocalVarDef) {
1✔
437
                checkLocalShadowing((LocalVarDef) e);
1✔
438
                visit((LocalVarDef) e);
1✔
439
            }
440
            if (e instanceof Modifiers)
1✔
441
                visit((Modifiers) e);
1✔
442
            if (e instanceof ModuleDef)
1✔
443
                visit((ModuleDef) e);
1✔
444
            if (e instanceof NameDef) {
1✔
445
                nameDefsMustNotBeNamedAfterJassNativeTypes((NameDef) e);
1✔
446
                checkConfigOverride((NameDef) e);
1✔
447
            }
448
            if (e instanceof NameRef) {
1✔
449
                checkImplicitParameter((NameRef) e);
1✔
450
                checkNameRef((NameRef) e);
1✔
451
            }
452
            if (e instanceof StmtCall)
1✔
453
                checkCall((StmtCall) e);
1✔
454
            if (e instanceof ExprDestroy)
1✔
455
                visit((ExprDestroy) e);
1✔
456
            if (e instanceof StmtForRange)
1✔
457
                checkForRange((StmtForRange) e);
1✔
458
            if (e instanceof StmtIf)
1✔
459
                visit((StmtIf) e);
1✔
460
            if (e instanceof StmtReturn)
1✔
461
                visit((StmtReturn) e);
1✔
462
            if (e instanceof StmtSet)
1✔
463
                checkStmtSet((StmtSet) e);
1✔
464
            if (e instanceof StmtWhile)
1✔
465
                visit((StmtWhile) e);
1✔
466
            if (e instanceof SwitchStmt)
1✔
467
                checkSwitch((SwitchStmt) e);
1✔
468
            if (e instanceof TypeExpr)
1✔
469
                checkTypeExpr((TypeExpr) e);
1✔
470
            if (e instanceof TypeExprArray)
1✔
471
                checkCodeArrays((TypeExprArray) e);
1✔
472
            if (e instanceof TupleDef)
1✔
473
                checkTupleDef((TupleDef) e);
1✔
474
            if (e instanceof VarDef)
1✔
475
                checkVarDef((VarDef) e);
1✔
476
            if (e instanceof WImport)
1✔
477
                visit((WImport) e);
1✔
478
            if (e instanceof WPackage)
1✔
479
                checkPackage((WPackage) e);
1✔
480
            if (e instanceof WParameter) {
1✔
481
                checkParameter((WParameter) e);
1✔
482
                visit((WParameter) e);
1✔
483
            }
484
            if (e instanceof WScope)
1✔
485
                checkForDuplicateNames((WScope) e);
1✔
486
            if (e instanceof WStatement)
1✔
487
                checkReachability((WStatement) e);
1✔
488
            if (e instanceof WurstModel)
1✔
489
                checkForDuplicatePackages((WurstModel) e);
×
490
            if (e instanceof WStatements) {
1✔
491
                checkForInvalidStmts((WStatements) e);
1✔
492
                checkForEmptyBlocks((WStatements) e);
1✔
493
            }
494
            if (e instanceof StmtExitwhen)
1✔
495
                visit((StmtExitwhen) e);
1✔
496
        } catch (CyclicDependencyError cde) {
×
497
            cde.printStackTrace();
×
498
            Element element = cde.getElement();
×
499
            String attr = cde.getAttributeName().replaceFirst("^attr", "");
×
500
            WLogger.info(Utils.printElementWithSource(Optional.of(element))
×
501
                + " depends on itself when evaluating attribute "
502
                + attr);
503
            WLogger.info(cde);
×
504
            throw new CompileError(element.attrSource(),
×
505
                    Utils.printElement(element) + " depends on itself when evaluating attribute " + attr);
×
506
        }
1✔
507
    }
1✔
508

509
    private void checkAbstractMethods(ClassDef c) {
510
        ImmutableMultimap<String, DefLink> nameLinks = c.attrNameLinks();
1✔
511
        if (!c.attrIsAbstract()) {
1✔
512
            StringBuilder toImplement = new StringBuilder();
1✔
513
            // should have no abstract methods
514
            for (DefLink link : nameLinks.values()) {
1✔
515
                NameDef f = link.getDef();
1✔
516
                if (f.attrIsAbstract()) {
1✔
517
                    if (f.attrNearestStructureDef() == c) {
1✔
518
                        Element loc = f.getModifiers().stream()
1✔
519
                                .filter(m -> m instanceof ModAbstract)
1✔
520
                                .<Element>map(x -> x)
1✔
521
                                .findFirst()
1✔
522
                                .orElse(f);
1✔
523
                        loc.addError("Non-abstract class " + c.getName() + " cannot have abstract functions like " + f.getName());
×
524
                    } else if (link instanceof FuncLink) {
1✔
525
                        toImplement.append("\n    ");
1✔
526
                        toImplement.append(((FuncLink) link).printFunctionTemplate());
1✔
527
                    }
528
                }
529
            }
1✔
530
            if (toImplement.length() > 0) {
1✔
531
                c.addError("Non-abstract class " + c.getName() + " must implement the following functions:" + toImplement);
1✔
532
            }
533
        }
534
    }
1✔
535

536
    private void visit(StmtExitwhen exitwhen) {
537
        Element parent = exitwhen.getParent();
1✔
538
        while (!(parent instanceof FunctionDefinition)) {
1✔
539
            if (parent instanceof StmtForEach) {
1✔
540
                StmtForEach forEach = (StmtForEach) parent;
1✔
541
                if (forEach.getIn().tryGetNameDef().attrIsVararg()) {
1✔
542
                    exitwhen.addError("Cannot use break in vararg for each loops.");
×
543
                }
544
                return;
×
545
            } else if (parent instanceof LoopStatement) {
1✔
546
                return;
1✔
547
            }
548
            parent = parent.getParent();
1✔
549
        }
550
        exitwhen.addError("Break is not allowed outside of loop statements.");
×
551
    }
×
552

553
    private void checkTupleDef(TupleDef e) {
554
        checkTupleDefCycle(e, new ArrayList<>());
1✔
555

556
    }
1✔
557

558
    private boolean checkTupleDefCycle(TupleDef e, ArrayList<TupleDef> tuples) {
559
        if (tuples.contains(e)) {
1✔
560
            return true;
1✔
561
        }
562
        tuples.add(e);
1✔
563
        try {
564
            for (WParameter param : e.getParameters()) {
1✔
565
                WurstType t = param.getTyp().attrTyp();
1✔
566
                if (t instanceof WurstTypeTuple) {
1✔
567
                    WurstTypeTuple tt = (WurstTypeTuple) t;
1✔
568
                    TupleDef tDef = tt.getTupleDef();
1✔
569
                    if (checkTupleDefCycle(tDef, tuples)) {
1✔
570
                        param.addError("Parameter " + param.getName() + " is recursive. This is not allowed for tuples.");
×
571
                        return true;
×
572
                    }
573
                }
574
            }
1✔
575
            return false;
1✔
576
        } finally {
577
            tuples.remove(e);
1✔
578
        }
579
    }
580

581
    private void checkForInvalidStmts(WStatements stmts) {
582
        for (WStatement s : stmts) {
1✔
583
            if (s instanceof ExprVarAccess) {
1✔
584
                ExprVarAccess ev = (ExprVarAccess) s;
1✔
585
                s.addError("Use of variable " + ev.getVarName() + " is an incomplete statement.");
×
586
            }
587
        }
1✔
588
    }
1✔
589

590
    private void checkForEmptyBlocks(WStatements e) {
591
        Element parent = e.getParent();
1✔
592
        // some parent cases to ignore:
593
        if (parent instanceof OnDestroyDef
1✔
594
            || parent instanceof ConstructorDef
595
            || parent instanceof FunctionDefinition
596
            || parent instanceof SwitchDefaultCaseStatements
597
            || parent instanceof SwitchCase) {
598
            return;
1✔
599
        }
600
        if (parent instanceof ExprStatementsBlock) {
1✔
601
            // for blocks in closures, we have StartFunction and EndFunction statements, so must be > 2 to be nonempty
602
            if (e.size() > 2) {
1✔
603
                return;
1✔
604
            }
605
            parent.getParent().addWarning("This function has an empty body. Write 'skip' if you intend to leave it empty.");
1✔
606
            return;
1✔
607
        }
608
        if (!e.isEmpty()) {
1✔
609
            return;
1✔
610
        }
611
        if (Utils.isJassCode(parent)) {
1✔
612
            // no warnings in Jass code
613
            return;
1✔
614
        }
615

616
        if (parent instanceof StmtIf) {
1✔
617
            StmtIf stmtIf = (StmtIf) parent;
1✔
618
            if (e == stmtIf.getElseBlock() && stmtIf.getHasElse()) {
1✔
619
                parent.addWarning("This if-statement has an empty else-block.");
1✔
620
            } else if (e == stmtIf.getThenBlock()) {
1✔
621
                parent.addWarning("This if-statement has an empty then-block. Write 'skip' if you intend to leave it empty.");
1✔
622
            }
623
            return;
1✔
624
        }
625

626
        parent.addWarning("This statement (" + Utils.printElement(parent) + ") contains an empty block. Write 'skip' if you intend to leave it empty.");
1✔
627
    }
1✔
628

629
    private void checkName(AstElementWithNameId e) {
630
        String name = e.getNameId().getName();
1✔
631
        TypeDef def = e.lookupType(name, false);
1✔
632

633
        if (def != e && def instanceof NativeType) {
1✔
634
            e.addError(
1✔
635
                    "The name '" + name + "' is already used as a native type in " + Utils.printPos(def.getSource()));
1✔
636
        } else if (!e.attrSource().getFile().endsWith(".j")) {
1✔
637
            switch (name) {
1✔
638
                case "int":
639
                case "integer":
640
                case "real":
641
                case "code":
642
                case "boolean":
643
                case "string":
644
                case "handle":
645
                    e.addError("The name '" + name + "' is a built-in type and cannot be used here.");
×
646
            }
647
        }
648
    }
1✔
649

650
    private void checkConfigOverride(NameDef e) {
651
        if (!e.hasAnnotation("@config")) {
1✔
652
            return;
1✔
653
        }
654
        PackageOrGlobal nearestPackage = e.attrNearestPackage();
1✔
655
        if (!(nearestPackage instanceof WPackage)) {
1✔
656
            e.addError("Annotation @config can only be used in packages.");
×
657
            return;
×
658
        }
659
        WPackage configPackage = (WPackage) nearestPackage;
1✔
660
        if (!configPackage.getName().endsWith(CofigOverridePackages.CONFIG_POSTFIX)) {
1✔
661
            e.addError(
×
662
                    "Annotation @config can only be used in config packages (package name has to end with '_config').");
663
            return;
×
664
        }
665

666
        WPackage origPackage = CofigOverridePackages.getOriginalPackage(configPackage);
1✔
667
        if (origPackage == null) {
1✔
668
            return;
×
669
        }
670

671
        if (e instanceof GlobalVarDef) {
1✔
672
            GlobalVarDef v = (GlobalVarDef) e;
1✔
673
            NameLink origVar = origPackage.getElements().lookupVarNoConfig(v.getName(), false);
1✔
674
            if (origVar == null) {
1✔
675
                e.addError("Could not find var " + v.getName() + " in configured package.");
×
676
                return;
×
677
            }
678

679
            if (!v.attrTyp().equalsType(origVar.getTyp(), v)) {
1✔
680
                e.addError("Configured variable must have type " + origVar.getTyp() + " but the found type is "
1✔
681
                        + v.attrTyp() + ".");
1✔
682
                return;
×
683
            }
684

685
            if (!origVar.getDef().hasAnnotation("@configurable")) {
1✔
686
                e.addWarning("The configured variable " + v.getName() + " is not marked with @configurable.\n"
1✔
687
                        + "It is still possible to configure this var but it is not recommended.");
688
            }
689

690
        } else if (e instanceof FuncDef) {
1✔
691
            FuncDef funcDef = (FuncDef) e;
1✔
692
            Collection<FuncLink> funcs = origPackage.getElements().lookupFuncsNoConfig(funcDef.getName(), false);
1✔
693
            FuncDef configuredFunc = null;
1✔
694
            for (NameLink nameLink : funcs) {
1✔
695
                if (nameLink.getDef() instanceof FuncDef) {
1✔
696
                    FuncDef f = (FuncDef) nameLink.getDef();
1✔
697
                    if (equalSignatures(funcDef, f)) {
1✔
698
                        configuredFunc = f;
1✔
699
                        break;
1✔
700
                    }
701
                }
702
            }
1✔
703
            if (configuredFunc == null) {
1✔
704
                funcDef.addError("Could not find a function " + funcDef.getName()
×
705
                        + " with the same signature in the configured package.");
706
            } else {
707
                if (!configuredFunc.hasAnnotation("@configurable")) {
1✔
708
                    e.addWarning("The configured function " + funcDef.getName() + " is not marked with @configurable.\n"
1✔
709
                            + "It is still possible to configure this function but it is not recommended.");
710
                }
711
            }
712

713
        } else {
1✔
714
            e.addError("Configuring " + Utils.printElement(e) + " is not supported by Wurst.");
×
715
        }
716
    }
1✔
717

718
    private boolean equalSignatures(FuncDef f, FuncDef g) {
719
        if (f.getParameters().size() != g.getParameters().size()) {
1✔
720
            return false;
×
721
        }
722
        if (!f.attrReturnTyp().equalsType(g.attrReturnTyp(), f)) {
1✔
723
            return false;
×
724
        }
725
        for (int i = 0; i < f.getParameters().size(); i++) {
1✔
726
            if (!f.getParameters().get(i).attrTyp().equalsType(g.getParameters().get(i).attrTyp(), f)) {
1✔
727
                return false;
1✔
728
            }
729
        }
730

731
        return true;
1✔
732
    }
733

734
    private void checkExprEmpty(ExprEmpty e) {
735
        e.addError("Incomplete expression...");
1✔
736

737
    }
1✔
738

739
    private void checkMemberArrayVar(ExprMemberArrayVar e) {
740
        // TODO Auto-generated method stub
741

742
    }
1✔
743

744
    private void checkNameRef(NameRef e) {
745
        if (e.getVarName().isEmpty()) {
1✔
746
            e.addError("Missing variable name.");
×
747
        }
748
    }
1✔
749

750
    private void checkPackage(WPackage p) {
751
        checkForDuplicateImports(p);
1✔
752
        p.attrInitDependencies();
1✔
753
    }
1✔
754

755
    private void checkTypeExpr(TypeExpr e) {
756
        if (e instanceof TypeExprResolved) {
1✔
757
            return;
1✔
758
        }
759
        if (e.isModuleUseTypeArg()) {
1✔
760
            return;
×
761
        }
762

763
        TypeDef typeDef = e.attrTypeDef();
1✔
764
        // check that modules are not used as normal types
765
        if (e.attrTypeDef() instanceof ModuleDef) {
1✔
766
            ModuleDef md = (ModuleDef) e.attrTypeDef();
1✔
767
            checkModuleTypeUsedCorrectly(e, md);
1✔
768
        }
769

770
        if (typeDef instanceof TypeParamDef) { // references a type parameter
1✔
771
            TypeParamDef tp = (TypeParamDef) typeDef;
1✔
772
            checkTypeparamsUsedCorrectly(e, tp);
1✔
773
        }
774

775
    }
1✔
776

777
    /**
778
     * Checks that module types are only used in valid places
779
     */
780
    private void checkModuleTypeUsedCorrectly(TypeExpr e, ModuleDef md) {
781
        if (e instanceof TypeExprThis) {
1✔
782
            // thistype is allowed, because it is translated to a real type when used
783
            return;
×
784
        }
785
        if (e.getParent() instanceof TypeExprThis) {
1✔
786
            TypeExprThis parent = (TypeExprThis) e.getParent();
1✔
787
            if (parent.getScopeType() == e) {
1✔
788
                // ModuleName.thistype is allowed
789
                // TODO (maybe check here that it is a parent)
790
                return;
1✔
791
            }
792
        }
793
        if (e instanceof TypeExprSimple) {
1✔
794
            TypeExprSimple tes = (TypeExprSimple) e;
1✔
795
            if (tes.getScopeType() instanceof TypeExpr) {
1✔
796
                TypeExpr scopeType = (TypeExpr) tes.getScopeType();
×
797
                if (scopeType instanceof TypeExprThis
×
798
                        || scopeType.attrTypeDef() instanceof ModuleDef) {
×
799
                    // thistype.A etc. is allowed
800
                    return;
×
801
                }
802
            }
803
        }
804
        e.addError("Cannot use module type " + md.getName() + " in this context.");
×
805
    }
×
806

807
    /**
808
     * check that type parameters are used in correct contexts:
809
     */
810
    private void checkTypeparamsUsedCorrectly(TypeExpr e, TypeParamDef tp) {
811
        if (tp.isStructureDefTypeParam()) { // typeParamDef is for
1✔
812
            // structureDef
813
            if (tp.attrNearestStructureDef() instanceof ModuleDef) {
1✔
814
                // in modules we can also type-params in static contexts
815
                return;
1✔
816
            }
817

818
            if (!e.attrIsDynamicContext()) {
1✔
819
                e.addError("Type variables must not be used in static contexts.");
×
820
            }
821
        }
822
    }
1✔
823

824
    private void checkClosure(ExprClosure e) {
825
        WurstType expectedTyp = e.attrExpectedTypAfterOverloading();
1✔
826

827

828

829
        if (expectedTyp instanceof WurstTypeCode) {
1✔
830
            // TODO check if no vars are captured
831
            if (!e.attrCapturedVariables().isEmpty()) {
1✔
832
                for (Map.Entry<Element, VarDef> elem : e.attrCapturedVariables().entries()) {
1✔
833

834
                    elem.getKey().addError("Cannot capture local variable '" + elem.getValue().getName()
×
835
                        + "' in anonymous function. This is only possible with closures.");
836
                }
×
837
            }
838
        } else if (expectedTyp instanceof WurstTypeUnknown || expectedTyp instanceof WurstTypeClosure) {
1✔
839

840

841

842
            e.addError("Closures can only be used when a interface or class type is given.");
×
843

844

845

846
        } else if (!(expectedTyp instanceof WurstTypeClass
1✔
847
            || expectedTyp instanceof WurstTypeInterface)) {
848
            e.addError("Closures can only be used when a interface or class type is given, " + "but at this position a "
×
849
                + expectedTyp + " is expected.");
850
        }
851
        e.attrCapturedVariables();
1✔
852

853
        if (isHeavy() && e.getImplementation() instanceof ExprStatementsBlock) {
1✔
854
            ExprStatementsBlock block = (ExprStatementsBlock) e.getImplementation();
×
855
            new DataflowAnomalyAnalysis(false).execute(block);
×
856
        } else if (!isHeavy() && e.getImplementation() instanceof ExprStatementsBlock) {
1✔
857
            // Phase-1: collect closure blocks for Phase-2 DFA
858
            heavyBlocks.add((ExprStatementsBlock) e.getImplementation());
1✔
859
        }
860

861

862

863
        if (expectedTyp instanceof WurstTypeClass) {
1✔
864
            WurstTypeClass ct = (WurstTypeClass) expectedTyp;
1✔
865

866
            ClassDef cd = ct.getClassDef();
1✔
867
            if (cd.getConstructors().stream().noneMatch(constr -> constr.getParameters().isEmpty())) {
1✔
868

869

870

871

872

873

874
                e.addError("No default constructor for class " + ct
×
875
                    + " found, so it cannot be instantiated using an anonymous function.");
876
            }
877
        }
878

879
    }
1✔
880

881
    private void checkConstructorsUnique(ClassOrModule c) {
882
        List<ConstructorDef> constrs = c.getConstructors();
1✔
883

884
        for (int i = 0; i < constrs.size() - 1; i++) {
1✔
885
            ConstructorDef c1 = constrs.get(i);
1✔
886
            for (int j = i + 1; i < constrs.size(); i++) {
1✔
887
                ConstructorDef c2 = constrs.get(j);
1✔
888
                if (c1.getParameters().size() != c2.getParameters().size()) {
1✔
889
                    continue;
1✔
890
                }
891

892
                if (!parametersTypeDisjunct(c1.getParameters(), c2.getParameters())) {
1✔
893
                    c2.addError(
1✔
894
                            "Duplicate constructor, an other constructor with similar types is already defined in line "
895
                                    + c1.attrSource().getLine());
1✔
896
                }
897
            }
898
        }
899

900
    }
1✔
901

902
    private boolean parametersTypeDisjunct(WParameters params1, WParameters params2) {
903
        for (int i = 0; i < params1.size(); i++) {
1✔
904
            WurstType t1 = params1.get(i).attrTyp();
1✔
905
            WurstType t2 = params2.get(i).attrTyp();
1✔
906
            if (!t1.isSubtypeOf(t2, params1) && !t2.isSubtypeOf(t1, params2)) {
1✔
907
                return true;
1✔
908
            }
909
        }
910
        return false;
1✔
911
    }
912

913
    private void checkImplicitParameter(NameRef e) {
914
        e.attrImplicitParameter();
1✔
915
    }
1✔
916

917
    private void checkTypeParameters(AstElementWithTypeParameters e) {
918
        for (TypeParamDef ta : e.getTypeParameters()) {
1✔
919
            String name = ta.getName();
1✔
920
            if (name.indexOf('<') >= 0 || (!name.isEmpty() && name.charAt(0) == '#')) {
1✔
921
                ta.addError("Type parameter must be a simple name ");
×
922
            } else {
923
                checkTypeName(ta, name);
1✔
924
            }
925
            ta.attrTyp();
1✔
926
        }
1✔
927
    }
1✔
928

929
    private void checkExprNull(ExprNull e) {
930
        if (e.attrExpectedTyp() instanceof WurstTypeUnknown && !Utils.isJassCode(e)) {
1✔
931
            e.addError(
×
932
                    "Cannot use 'null' constant here because " + "the compiler cannot infer which kind of null it is.");
933
        }
934

935
    }
1✔
936

937
    private void checkForRange(StmtForRange e) {
938
        if (!(e.getLoopVar().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e))) {
1✔
939
            e.getLoopVar().addError("For-loop variable must be int.");
×
940
        }
941
        if (!(e.getTo().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e))) {
1✔
942
            e.getLoopVar().addError("For-loop target must be int.");
×
943
        }
944
        if (!(e.getStep().attrTyp().isSubtypeOf(WurstTypeInt.instance(), e))) {
1✔
945
            e.getLoopVar().addError("For-loop step must be int.");
×
946
        }
947
    }
1✔
948

949
    private void checkIntVal(ExprIntVal e) {
950
        // check range? ...
951
    }
1✔
952

953
    private int countFunctions() {
954
        final int[] functionCount = new int[1];
1✔
955
        prog.accept(new WurstModel.DefaultVisitor() {
1✔
956

957
            @Override
958
            public void visit(FuncDef f) {
959
                super.visit(f);
1✔
960
                functionCount[0]++;
1✔
961
            }
1✔
962
        });
963
        return functionCount[0];
1✔
964
    }
965

966
    private void checkStmtSet(StmtSet s) {
967
        NameLink nameLink = s.getUpdatedExpr().attrNameLink();
1✔
968
        if (nameLink == null) {
1✔
969
            s.getUpdatedExpr().addError("Could not find variable " + s.getUpdatedExpr().getVarName() + ".");
×
970
            return;
×
971
        }
972

973
        if (!(nameLink instanceof VarLink)) {
1✔
974
            s.getUpdatedExpr()
1✔
975
                    .addError("Invalid assignment. This is not a variable, this is a " + nameLink);
×
976
            return;
×
977
        }
978

979
        WurstType leftType = s.getUpdatedExpr().attrTyp();
1✔
980
        WurstType rightType = s.getRight().attrTyp();
1✔
981

982
        checkAssignment(Utils.isJassCode(s), s, leftType, rightType);
1✔
983

984
        checkIfAssigningToConstant(s.getUpdatedExpr());
1✔
985

986
        checkIfNoEffectAssignment(s);
1✔
987
    }
1✔
988

989
    private void checkIfNoEffectAssignment(StmtSet s) {
990
        if (refersToSameVar(s.getUpdatedExpr(), s.getRight())) {
1✔
991
            s.addWarning("The assignment to " + Utils.printElement(s.getUpdatedExpr().attrNameDef())
1✔
992
                    + " probably has no effect.");
993
        }
994

995
    }
1✔
996

997
    private boolean refersToSameVar(OptExpr a, OptExpr b) {
998
        if (a instanceof NoExpr && b instanceof NoExpr) {
1✔
999
            return true;
1✔
1000
        }
1001
        if (a instanceof ExprThis && b instanceof ExprThis) {
1✔
1002
            return true;
1✔
1003
        }
1004
        if (a instanceof NameRef && b instanceof NameRef) {
1✔
1005
            NameRef va = (NameRef) a;
1✔
1006
            NameRef vb = (NameRef) b;
1✔
1007
            NameLink nla = va.attrNameLink();
1✔
1008
            NameLink nlb = vb.attrNameLink();
1✔
1009
            if (nla != null && nlb != null && nla.getDef() == nlb.getDef()
1✔
1010
                    && refersToSameVar(va.attrImplicitParameter(), vb.attrImplicitParameter())) {
1✔
1011
                if (va instanceof AstElementWithIndexes && vb instanceof AstElementWithIndexes) {
1✔
1012
                    AstElementWithIndexes vai = (AstElementWithIndexes) va;
1✔
1013
                    AstElementWithIndexes vbi = (AstElementWithIndexes) vb;
1✔
1014

1015
                    for (int i = 0; i < vai.getIndexes().size() && i < vbi.getIndexes().size(); i++) {
1✔
1016
                        if (!refersToSameVar(vai.getIndexes().get(i), vbi.getIndexes().get(i))) {
1✔
1017
                            return false;
1✔
1018
                        }
1019
                    }
1020
                }
1021
                return true;
1✔
1022
            }
1023
        }
1024
        return false;
1✔
1025
    }
1026

1027
    private void checkIfAssigningToConstant(final LExpr left) {
1028
        left.match(new LExpr.MatcherVoid() {
1✔
1029

1030
            @Override
1031
            public void case_ExprVarArrayAccess(ExprVarArrayAccess e) {
1032

1033
            }
1✔
1034

1035
            @Override
1036
            public void case_ExprVarAccess(ExprVarAccess e) {
1037
                checkVarNotConstant(e, e.attrNameLink());
1✔
1038
            }
1✔
1039

1040
            @Override
1041
            public void case_ExprMemberVarDot(ExprMemberVarDot e) {
1042
                if (e.attrNameDef() instanceof WParameter) {
1✔
1043
                    // we have an assignment to a tuple variable
1044
                    // check whether left side is 'this' or a constant variable
1045
                    if (e.getLeft() instanceof ExprThis) {
1✔
1046
                        e.addError("Cannot change 'this'. Tuples are not classes.");
×
1047
                    } else if (e.getLeft() instanceof NameRef) {
1✔
1048
                        checkIfAssigningToConstant((NameRef) e.getLeft());
1✔
1049
                    } else {
1050
                        e.addError(
×
1051
                                "Ok, so you are trying to assign something to the return value of a function. This wont do nothing. Tuples are not classes.");
1052
                    }
1053
                }
1054
                checkVarNotConstant(e, e.attrNameLink());
1✔
1055
            }
1✔
1056

1057
            @Override
1058
            public void case_ExprMemberArrayVarDot(ExprMemberArrayVarDot e) {
1059

1060
            }
1✔
1061

1062
            @Override
1063
            public void case_ExprMemberArrayVarDotDot(ExprMemberArrayVarDotDot e) {
1064
                e.addError("Cannot assign to dot-dot-expression.");
×
1065
            }
×
1066

1067
            @Override
1068
            public void case_ExprMemberVarDotDot(ExprMemberVarDotDot e) {
1069
                e.addError("Cannot assign to dot-dot-expression.");
×
1070
            }
×
1071
        });
1072
    }
1✔
1073

1074
    private void checkVarNotConstant(NameRef left, @Nullable NameLink link) {
1075
        if (link == null) {
1✔
1076
            return;
×
1077
        }
1078
        NameDef var = link.getDef();
1✔
1079
        if (var != null && var.attrIsConstant()) {
1✔
1080
            if (var instanceof GlobalVarDef) {
1✔
1081
                GlobalVarDef glob = (GlobalVarDef) var;
1✔
1082
                if (glob.attrIsDynamicClassMember() && isInConstructor(left)) {
1✔
1083
                    // allow to assign constant members in constructor
1084
                    return;
1✔
1085
                }
1086
            }
1087
            left.addError("Cannot assign a new value to constant " + Utils.printElement(var));
×
1088
        }
1089
    }
1✔
1090

1091
    private boolean isInConstructor(Element e) {
1092
        while (e != null) {
1✔
1093
            if (e instanceof ConstructorDef) {
1✔
1094
                return true;
1✔
1095
            }
1096
            e = e.getParent();
1✔
1097
        }
1098
        return false;
1✔
1099
    }
1100

1101
    private void checkAssignment(boolean isJassCode, Element pos, WurstType leftType, WurstType rightType) {
1102
        if (!rightType.isSubtypeOf(leftType, pos)) {
1✔
1103
            if (isJassCode) {
1✔
1104
                if (leftType.isSubtypeOf(WurstTypeReal.instance(), pos)
×
1105
                        && rightType.isSubtypeOf(WurstTypeInt.instance(), pos)) {
×
1106
                    // special case: jass allows to assign an integer to a real
1107
                    // variable
1108
                    return;
×
1109
                }
1110
            }
1111
            pos.addError("Cannot assign " + rightType + " to " + leftType);
×
1112
        }
1113
        if (leftType instanceof WurstTypeNamedScope) {
1✔
1114
            WurstTypeNamedScope ns = (WurstTypeNamedScope) leftType;
1✔
1115
            if (ns.isStaticRef()) {
1✔
1116
                pos.addError("Missing variable name in variable declaration.\n" + "Cannot assign to " + leftType);
×
1117
            }
1118
        }
1119
        if (leftType instanceof WurstTypeArray) {
1✔
1120
            pos.addError("Missing array index for assignment to array variable.s");
×
1121
        }
1122
        if (rightType instanceof WurstTypeVoid) {
1✔
1123
            if (pos.attrNearestPackage() instanceof WPackage) {
×
1124
                WPackage pack = (WPackage) pos.attrNearestPackage();
×
1125
                if (pack != null && !pack.getName().equals("WurstREPL")) { // allow
×
1126
                    // assigning
1127
                    // nothing
1128
                    // to
1129
                    // a
1130
                    // variable
1131
                    // in
1132
                    // the
1133
                    // Repl
1134
                    pos.addError("Function or expression returns nothing. Cannot assign nothing to a variable.");
×
1135
                }
1136
            }
1137
        }
1138
    }
1✔
1139

1140
    private void visit(LocalVarDef s) {
1141
        checkVarName(s, false);
1✔
1142
        if (s.getInitialExpr() instanceof Expr) {
1✔
1143
            Expr initial = (Expr) s.getInitialExpr();
1✔
1144
            if ((s.getOptTyp() instanceof NoTypeExpr)) {
1✔
1145
                // TODO
1146
            } else {
1147
                if (initial instanceof ExprNewObject) {
1✔
1148
                    s.addWarning("Duplicated type information. Use 'var' or 'let' instead.");
1✔
1149
                }
1150
            }
1151
            WurstType leftType = s.attrTyp();
1✔
1152
            WurstType rightType = initial.attrTyp();
1✔
1153

1154
            checkAssignment(Utils.isJassCode(s), s, leftType, rightType);
1✔
1155
        } else if (s.getInitialExpr() instanceof ArrayInitializer) {
1✔
1156
            ArrayInitializer arInit = (ArrayInitializer) s.getInitialExpr();
1✔
1157
            checkArrayInit(s, arInit);
1✔
1158
        }
1159
        checkIfRead(s);
1✔
1160
    }
1✔
1161

1162
    private void checkArrayInit(VarDef def, ArrayInitializer arInit) {
1163
        WurstType leftType = def.attrTyp();
1✔
1164
        if (leftType instanceof WurstTypeArray) {
1✔
1165
            WurstTypeArray arT = (WurstTypeArray) leftType;
1✔
1166
            if (arT.getDimensions() > 1) {
1✔
1167
                def.addError("Array initializer can only be used with one-dimensional arrays.");
×
1168
            }
1169
            if (arT.getDimensions() == 1) {
1✔
1170
                int initialValues = arInit.getValues().size();
1✔
1171
                int size = arT.getSize(0);
1✔
1172
                if (size >= 0 && size != initialValues) {
1✔
1173
                    def.addError("Array variable " + def.getName() + " is an array of size " + size + ", but is initialized with " + initialValues + " values here.");
×
1174
                }
1175

1176
            }
1177
            WurstType baseType = arT.getBaseType();
1✔
1178
            for (Expr expr : arInit.getValues()) {
1✔
1179
                if (!expr.attrTyp().isSubtypeOf(baseType, expr)) {
1✔
1180
                    expr.addError("Expected expression of type " + baseType + " in array initialization, but found " + expr.attrTyp());
×
1181
                }
1182
            }
1✔
1183
        } else {
1✔
1184
            def.addError("Array initializer can only be used with array-variables, but " + Utils.printElement(def) + " has type " + leftType);
×
1185
        }
1186

1187
    }
1✔
1188

1189
    private void checkIfRead(VarDef s) {
1190
        if (s.getName().startsWith("_")) {
1✔
1191
            // variables starting with an underscore are not read
1192
            // (same convention as in Erlang)
1193
            return;
1✔
1194
        }
1195
        if (Utils.isJassCode(s)) {
1✔
1196
            return;
1✔
1197
        }
1198
        if (s.getParent() instanceof StmtForRange) {
1✔
1199
            // it is ok, when the variable of a for-statement is not used
1200
            return;
1✔
1201
        }
1202
        WScope f = s.attrNearestScope();
1✔
1203
        if (f != null && !f.attrReadVariables().contains(s)) {
1✔
1204
            s.addWarning("The " + Utils.printElement(s) + " is never read. If intentional, prefix with \"_\" to suppress this warning.");
1✔
1205
        }
1206
    }
1✔
1207

1208
    private void checkVarName(VarDef s, boolean isConstant) {
1209
        String varName = s.getName();
1✔
1210

1211
        if (!isValidVarnameStart(varName) // first letter not lower case
1✔
1212
                && !Utils.isJassCode(s) // not in jass code
1✔
1213
                && !varName.matches("[A-Z0-9_]+") // not a constant
1✔
1214
        ) {
1215
            s.addWarning("Variable names should start with a lower case character. (" + varName + ")");
1✔
1216
        }
1217
        if (varName.equals("handle")) {
1✔
1218
            s.addError("\"handle\" is not a valid variable name");
×
1219
        } else if (varName.equals("code")) {
1✔
1220
            s.addError("\"code\" is not a valid variable name");
×
1221
        }
1222

1223
    }
1✔
1224

1225
    private boolean isValidVarnameStart(String varName) {
1226
        return varName.length() > 0 && Character.isLowerCase(varName.charAt(0)) || varName.startsWith("_");
1✔
1227
    }
1228

1229
    private void visit(WParameter p) {
1230
        checkVarName(p, false);
1✔
1231
        if (p.attrIsVararg()) {
1✔
1232
            if (p.attrNearestFuncDef().getParameters().size() != 1) {
1✔
1233
                p.addError("Vararg functions may only have one parameter");
×
1234
            }
1235
        }
1236
        checkIfParameterIsRead(p);
1✔
1237
    }
1✔
1238

1239
    private void checkIfParameterIsRead(WParameter p) {
1240
        FunctionImplementation f = p.attrNearestFuncDef();
1✔
1241
        if (f != null) {
1✔
1242
            if (p.getParent().getParent() instanceof ExprClosure) {
1✔
1243
                // closures can ignore parameters
1244
                return;
×
1245
            }
1246
            if (f.attrIsOverride()) {
1✔
1247
                // if a function is overridden it is ok to ignore parameters
1248
                return;
1✔
1249
            }
1250
            if (f.attrIsAbstract()) {
1✔
1251
                // if a function is abstract, then parameter vars are not used
1252
                return;
1✔
1253
            }
1254
            if (f.attrHasAnnotation("compiletimenative")) {
1✔
1255
                return;
1✔
1256
            }
1257
        } else {
1258
            if (p.getParent().getParent() instanceof TupleDef) {
1✔
1259
                // ignore tuples
1260
                return;
1✔
1261
            }
1262
            if (p.getParent().getParent() instanceof NativeFunc) {
1✔
1263
                // ignore native functions
1264
                return;
1✔
1265
            }
1266
        }
1267

1268
        checkIfRead(p);
1✔
1269
    }
1✔
1270

1271
    private void visit(GlobalVarDef s) {
1272
        checkVarName(s, s.attrIsConstant());
1✔
1273
        if (s.getInitialExpr() instanceof Expr) {
1✔
1274
            Expr initial = (Expr) s.getInitialExpr();
1✔
1275
            WurstType leftType = s.attrTyp();
1✔
1276
            WurstType rightType = initial.attrTyp();
1✔
1277
            checkAssignment(Utils.isJassCode(s), s, leftType, rightType);
1✔
1278
        } else if (s.getInitialExpr() instanceof ArrayInitializer) {
1✔
1279
            checkArrayInit(s, (ArrayInitializer) s.getInitialExpr());
1✔
1280
        }
1281

1282
        if (s.attrTyp() instanceof WurstTypeArray && !s.attrIsStatic() && s.attrIsDynamicClassMember()) {
1✔
1283
            // s.addError("Array variables must be static.\n" +
1284
            // "Hint: use Lists for dynamic stuff.");
1285
        }
1286
    }
1✔
1287

1288
    private void visit(StmtIf stmtIf) {
1289
        WurstType condType = stmtIf.getCond().attrTyp();
1✔
1290
        if (!(condType instanceof WurstTypeBool)) {
1✔
1291
            stmtIf.getCond().addError("If condition must be a boolean but found " + condType);
1✔
1292
        }
1293
    }
1✔
1294

1295
    private void visit(StmtWhile stmtWhile) {
1296
        WurstType condType = stmtWhile.getCond().attrTyp();
1✔
1297
        if (!(condType instanceof WurstTypeBool)) {
1✔
1298
            stmtWhile.getCond().addError("While condition must be a boolean but found " + condType);
×
1299
        }
1300
    }
1✔
1301

1302
    private void visit(ExtensionFuncDef func) {
1303
        checkFunctionName(func);
1✔
1304
        func.getExtendedType().attrTyp();
1✔
1305
    }
1✔
1306

1307
    private void checkFunctionName(FunctionDefinition f) {
1308
        if (!Utils.isJassCode(f)) {
1✔
1309
            if (!isValidVarnameStart(f.getName())) {
1✔
1310
                f.addWarning("Function names should start with an lower case character.");
×
1311
            }
1312
        }
1313
    }
1✔
1314

1315
    private void checkReturn(FunctionLike func) {
1316
        if (!isHeavy()) return;
1✔
1317
        if (!func.attrHasEmptyBody()) {
1✔
1318
            new ReturnsAnalysis().execute(func);
1✔
1319
        } else { // no body, check if in interface:
1320
            if (func instanceof FunctionImplementation) {
1✔
1321
                FunctionImplementation funcDef = (FunctionImplementation) func;
1✔
1322
                if (funcDef.getReturnTyp() instanceof TypeExpr
1✔
1323
                        && !(func.attrNearestStructureDef() instanceof InterfaceDef)) {
1✔
1324
                    func.addError("Function " + funcDef.getName()
×
1325
                            + " is missing a body. Use the 'skip' statement to define an empty body.");
1326
                }
1327
            }
1328
        }
1329
    }
1✔
1330

1331
    private void checkReachability(WStatement s) {
1332
        if (!isHeavy()) return;
1✔
1333
        if (s.getParent() instanceof WStatements) {
1✔
1334
            WStatements stmts = (WStatements) s.getParent();
1✔
1335
            if (s.attrPreviousStatements().isEmpty()) {
1✔
1336
                if (s.attrListIndex() > 0 || !(stmts.getParent() instanceof TranslatedToImFunction
1✔
1337
                        || stmts.getParent() instanceof ExprStatementsBlock)) {
1✔
1338
                    if (Utils.isJassCode(s)) {
1✔
1339
                        // in jass this is just a warning, because
1340
                        // the shitty code emitted by jasshelper sometimes
1341
                        // contains unreachable code
1342
                        s.addWarning("Unreachable code");
×
1343
                    } else {
1344
                        if (mightBeAffectedBySwitchThatCoversAllCases(s)) {
1✔
1345
                            // fow backwards compatibility just use a warning when
1346
                            // switch statements that handle all cases are involved:
1347
                            s.addWarning("Unreachable code");
1✔
1348
                        } else {
1349
                            s.addError("Unreachable code");
×
1350
                        }
1351
                    }
1352
                }
1353
            }
1354
        }
1355
    }
1✔
1356

1357
    private boolean mightBeAffectedBySwitchThatCoversAllCases(WStatement s) {
1358
        boolean[] containsSwitchAr = { false };
1✔
1359
        s.attrNearestNamedScope().accept(new Element.DefaultVisitor() {
1✔
1360
            @Override
1361
            public void visit(SwitchStmt switchStmt) {
1362
                if (switchStmt.calculateHandlesAllCases()) {
1✔
1363
                    containsSwitchAr[0] = true;
1✔
1364
                }
1365
            }
1✔
1366
        });
1367
        return containsSwitchAr[0];
1✔
1368
    }
1369

1370
    private void visit(FuncDef func) {
1371
        visitedFunctions++;
1✔
1372
        func.getErrorHandler().setProgress(null, ProgressHelper.getValidatorPercent(visitedFunctions, functionCount));
1✔
1373

1374
        checkFunctionName(func);
1✔
1375
        if (func.attrIsAbstract()) {
1✔
1376
            if (!func.attrHasEmptyBody()) {
1✔
1377
                func.addError("Abstract function " + func.getName() + " must not have a body.");
×
1378
            }
1379
            if (func.attrIsPrivate()) {
1✔
1380
                func.addError("Abstract functions must not be private.");
×
1381
            }
1382
        }
1383
    }
1✔
1384

1385
    private void checkUninitializedVars(FunctionLike f) {
1386
        boolean isAbstract = false;
1✔
1387
        if (f instanceof FuncDef) {
1✔
1388
            FuncDef func = (FuncDef) f;
1✔
1389
            if (func.attrIsAbstract()) {
1✔
1390
                isAbstract = true;
1✔
1391
                if (isHeavy() && !func.attrHasEmptyBody()) {
1✔
1392
                    func.getBody().get(0)
×
1393
                        .addError("The abstract function " + func.getName() + " must not have any statements.");
×
1394
                }
1395
            }
1396
        }
1397
        if (isAbstract) return;
1✔
1398

1399
        if (!isHeavy()) {
1✔
1400
            // Phase-1: collect, but do not analyze.
1401
            heavyFunctions.add(f);
1✔
1402
            return;
1✔
1403
        }
1404

1405
        // Phase-2: actually run heavy analyses:
1406
        checkReturn(f);
1✔
1407

1408
        if (!f.getSource().getFile().endsWith("common.j")
1✔
1409
            && !f.getSource().getFile().endsWith("blizzard.j")
1✔
1410
            && !f.getSource().getFile().endsWith("war3map.j")) {
1✔
1411
            new DataflowAnomalyAnalysis(Utils.isJassCode(f)).execute(f);
1✔
1412
        }
1413
    }
1✔
1414

1415

1416

1417

1418

1419
    private void checkCall(StmtCall call) {
1420
        String funcName;
1421
        if (call instanceof FunctionCall) {
1✔
1422
            FunctionCall fcall = (FunctionCall) call;
1✔
1423
            funcName = fcall.getFuncName();
1✔
1424
            HashSet<FunctionCall> fcalls = wrapperCalls.computeIfAbsent(funcName, (String s) -> new HashSet<>());
1✔
1425
            fcalls.add(fcall);
1✔
1426
        } else if (call instanceof ExprNewObject) {
1✔
1427
            funcName = "constructor";
1✔
1428
        } else {
1429
            throw new Error("unhandled case: " + Utils.printElement(call));
×
1430
        }
1431

1432
        call.attrCallSignature().checkSignatureCompatibility(call.attrFunctionSignature(), funcName, call);
1✔
1433
    }
1✔
1434

1435

1436
    private static final Object2ObjectOpenHashMap<WurstType, Object2BooleanOpenHashMap<WurstType>> SUBTYPE_MEMO
1✔
1437
        = new Object2ObjectOpenHashMap<>();
1438
    // crude cap to avoid unbounded growth; tune as needed
1439
    private static final int SUBTYPE_MEMO_CAP = 16_384;
1440
    private static int SUBTYPE_MEMO_SIZE = 0;
1✔
1441

1442
    private static boolean isSubtypeCached(WurstType actual, WurstType expected, Annotation site) {
1443
        if (actual == expected) return true;
×
1444
        // quick structural equality before expensive check
1445
        if (actual.equals(expected)) return true;
×
1446

1447
        Object2BooleanOpenHashMap<WurstType> inner = SUBTYPE_MEMO.get(actual);
×
1448
        if (inner != null && inner.containsKey(expected)) {
×
1449
            return inner.getBoolean(expected);
×
1450
        }
1451

1452
        boolean res = actual.isSubtypeOf(expected, site);
×
1453

1454
        if (inner == null) {
×
1455
            inner = new Object2BooleanOpenHashMap<>();
×
1456
            SUBTYPE_MEMO.put(actual, inner);
×
1457
        }
1458
        if (!inner.containsKey(expected)) {
×
1459
            inner.put(expected, res);
×
1460
            if (++SUBTYPE_MEMO_SIZE > SUBTYPE_MEMO_CAP) {
×
1461
                // simple eviction policy: clear all when too big (cheap & safe)
1462
                SUBTYPE_MEMO.clear();
×
1463
                SUBTYPE_MEMO_SIZE = 0;
×
1464
            }
1465
        }
1466
        return res;
×
1467
    }
1468

1469
    private void checkAnnotation(Annotation a) {
1470
        FuncLink fl = a.attrFuncLink();
1✔
1471
        if (fl == null) return;
1✔
1472

1473
        // pull once; avoid repeated virtual calls and size reads
1474
        final var args = a.getArgs();
1✔
1475
        final int argCount = args.size();
1✔
1476

1477
        // pull all parameter types once (and reuse). If FuncLink can expose an array, even better.
1478
        final var paramTypes = fl.getParameterTypes();
1✔
1479
        final int paramCount = paramTypes.size();
1✔
1480

1481
        if (argCount < paramCount) {
1✔
1482
            a.addWarning("not enough arguments");
1✔
1483
            return;
1✔
1484
        } else if (argCount > paramCount) {
1✔
1485
            a.addWarning("too many arguments");
×
1486
            return;
×
1487
        }
1488

1489
        // same count; validate pairwise
1490
        for (int i = 0; i < argCount; i++) {
1✔
1491
            // avoid double indexing/attr calls
1492
            final var argExpr = args.get(i);
1✔
1493
            final WurstType actual = argExpr.attrTyp();
1✔
1494
            final WurstType expected = paramTypes.get(i);
1✔
1495

1496
            // fast path: == / equals handled inside isSubtypeCached too,
1497
            // but doing it here keeps it branch-predictable and avoids map lookups for exact matches
1498
            if (actual == expected || actual.equals(expected)) {
1✔
1499
                continue;
×
1500
            }
1501

1502
            if (!isSubtypeCached(actual, expected, a)) {
×
1503
                // build message only on miss
1504
                argExpr.addWarning("Expected " + expected + " but found " + actual + ".");
×
1505
            }
1506
        }
1507
    }
1✔
1508

1509

1510
    private void visit(ExprFunctionCall stmtCall) {
1511
        String funcName = stmtCall.getFuncName();
1✔
1512
        // calculating the exprType should reveal most errors:
1513
        stmtCall.attrTyp();
1✔
1514

1515
        checkFuncDefDeprecated(stmtCall);
1✔
1516

1517
        if (stmtCall.attrFuncLink() != null) {
1✔
1518
            FuncLink calledFunc = stmtCall.attrFuncLink();
1✔
1519
            if (calledFunc.getDef().attrIsDynamicClassMember()) {
1✔
1520
                if (!stmtCall.attrIsDynamicContext()) {
1✔
1521
                    stmtCall.addError("Cannot call dynamic function " + funcName + " from static context.");
×
1522
                }
1523
            }
1524
            if (calledFunc.getDef() instanceof ExtensionFuncDef) {
1✔
1525
                stmtCall.addError("Extension function " + funcName + " must be called with an explicit receiver.\n"
×
1526
                        + "Try to write this." + funcName + "(...) .");
1527
            }
1528
        }
1529

1530
        // special check for filter & condition:
1531
        if (Utils.oneOf(funcName, "Condition", "Filter") && !stmtCall.getArgs().isEmpty()) {
1✔
1532
            Expr firstArg = stmtCall.getArgs().get(0);
1✔
1533
            if (firstArg instanceof ExprFuncRef) {
1✔
1534
                ExprFuncRef exprFuncRef = (ExprFuncRef) firstArg;
1✔
1535
                FuncLink f = exprFuncRef.attrFuncLink();
1✔
1536
                if (f != null) {
1✔
1537
                    if (!(f.getReturnType() instanceof WurstTypeBool) && !(f.getReturnType() instanceof WurstTypeVoid)) {
1✔
1538
                        firstArg.addError("Functions passed to Filter or Condition must return boolean or nothing.");
×
1539
                    }
1540
                }
1541
            }
1542
        }
1543

1544
    }
1✔
1545

1546
    // private void checkParams(Element where, List<Expr> args,
1547
    // FunctionDefinition calledFunc) {
1548
    // if (calledFunc == null) {
1549
    // return;
1550
    // }
1551
    // List<PscriptType> parameterTypes = calledFunc.attrParameterTypes();
1552
    // checkParams(where, args, parameterTypes);
1553
    // }
1554

1555
    @Deprecated
1556
    private void checkParams(Element where, String preMsg, List<Expr> args, FunctionSignature sig) {
1557
        checkParams(where, preMsg, args, sig.getParamTypes());
1✔
1558
    }
1✔
1559

1560
    @Deprecated
1561
    private void checkParams(Element where, String preMsg, List<Expr> args, List<WurstType> parameterTypes) {
1562
        if (args.size() > parameterTypes.size()) {
1✔
1563
            where.addError(preMsg + "Too many parameters.");
×
1564

1565
        } else if (args.size() < parameterTypes.size()) {
1✔
1566
            where.addError(preMsg + "Missing parameters.");
×
1567
        } else {
1568
            for (int i = 0; i < args.size(); i++) {
1✔
1569

1570
                WurstType actual = args.get(i).attrTyp();
1✔
1571
                WurstType expected = parameterTypes.get(i);
1✔
1572
                // if (expected instanceof AstElementWithTypeArgs)
1573
                if (!actual.isSubtypeOf(expected, where)) {
1✔
1574
                    args.get(i).addError(
×
1575
                            preMsg + "Expected " + expected + " as parameter " + (i + 1) + " but  found " + actual);
1576
                }
1577
            }
1578
        }
1579
    }
1✔
1580

1581
    private void visit(ExprBinary expr) {
1582
        FuncLink def = expr.attrFuncLink();
1✔
1583
        if (def != null) {
1✔
1584
            FunctionSignature sig = FunctionSignature.fromNameLink(def);
1✔
1585
            CallSignature callSig = new CallSignature(expr.getLeft(), Collections.singletonList(expr.getRight()));
1✔
1586
            callSig.checkSignatureCompatibility(sig, "" + expr.getOp(), expr);
1✔
1587
        }
1588
    }
1✔
1589

1590
    private void visit(ExprMemberMethod stmtCall) {
1591
        // calculating the exprType should reveal all errors:
1592
        stmtCall.attrTyp();
1✔
1593
    }
1✔
1594

1595
    private void visit(ExprNewObject stmtCall) {
1596
        stmtCall.attrTyp();
1✔
1597
        stmtCall.attrConstructorDef();
1✔
1598
    }
1✔
1599

1600
    private void visit(Modifiers modifiers) {
1601
        boolean hasVis = false;
1✔
1602
        boolean isStatic = false;
1✔
1603
        for (Modifier m : modifiers) {
1✔
1604
            if (m instanceof VisibilityModifier) {
1✔
1605
                if (hasVis) {
1✔
1606
                    m.addError("Each element can only have one visibility modifier (public, private, ...)");
×
1607
                }
1608
                hasVis = true;
1✔
1609
            } else if (m instanceof ModStatic) {
1✔
1610
                if (isStatic) {
1✔
1611
                    m.addError("double static? - what r u trying to do?");
×
1612
                }
1613
                isStatic = true;
1✔
1614
            }
1615
        }
1✔
1616
    }
1✔
1617

1618
    private void visit(StmtReturn s) {
1619
        if (s.attrNearestExprStatementsBlock() != null) {
1✔
1620
            ExprStatementsBlock e = s.attrNearestExprStatementsBlock();
1✔
1621
            if (e.getReturnStmt() != s) {
1✔
1622
                s.addError("Return in a statements block can only be at the end.");
×
1623
                return;
×
1624
            }
1625
            if (s.getReturnedObj() instanceof Expr) {
1✔
1626
                Expr expr = (Expr) s.getReturnedObj();
1✔
1627
                if (expr.attrTyp().isVoid()) {
1✔
1628
                    s.addError("Cannot return void from statements block.");
×
1629
                }
1630
            } else {
1✔
1631
                s.addError("Cannot have empty return statement in statements block.");
×
1632
            }
1633
        } else {
1✔
1634
            FunctionImplementation func = s.attrNearestFuncDef();
1✔
1635
            if (func == null) {
1✔
1636
                s.addError("return statements can only be used inside functions");
×
1637
                return;
×
1638
            }
1639
            checkReturnInFunc(s, func);
1✔
1640
        }
1641
    }
1✔
1642

1643
    private void checkReturnInFunc(StmtReturn s, FunctionImplementation func) {
1644
        WurstType returnType = func.attrReturnTyp();
1✔
1645
        if (s.getReturnedObj() instanceof Expr) {
1✔
1646
            Expr returned = (Expr) s.getReturnedObj();
1✔
1647
            if (returnType.isSubtypeOf(WurstTypeVoid.instance(), s)) {
1✔
1648
                s.addError("Cannot return a value from a function which returns nothing");
×
1649
            } else {
1650
                WurstType returnedType = returned.attrTyp();
1✔
1651
                if (!returnedType.isSubtypeOf(returnType, s)) {
1✔
1652
                    s.addError("Cannot return " + returnedType + ", expected expression of type " + returnType);
×
1653
                }
1654
            }
1655
        } else { // empty return
1✔
1656
            if (!returnType.isSubtypeOf(WurstTypeVoid.instance(), s)) {
1✔
1657
                s.addError("Missing return value");
×
1658
            }
1659
        }
1660
    }
1✔
1661

1662
    private void visit(ClassDef classDef) {
1663
        checkTypeName(classDef, classDef.getName());
1✔
1664
        if (!(classDef.getExtendedClass() instanceof NoTypeExpr) && !(classDef.getExtendedClass().attrTyp() instanceof WurstTypeClass)) {
1✔
1665
            classDef.getExtendedClass().addError("Classes may only extend other classes.");
×
1666
        }
1667
        if (classDef.isInnerClass() && !classDef.attrIsStatic()) {
1✔
1668
            classDef.addError("At the moment only static inner classes are supported.");
×
1669
        }
1670
    }
1✔
1671

1672
    private void checkTypeName(Element source, String name) {
1673
        if (!Character.isUpperCase(name.charAt(0))) {
1✔
1674
            source.addWarning("Type names should start with upper case characters.");
×
1675
        }
1676
    }
1✔
1677

1678
    private void visit(ModuleDef moduleDef) {
1679
        checkTypeName(moduleDef, moduleDef.getName());
1✔
1680
        // calculate all functions to find possible errors
1681
        moduleDef.attrNameLinks();
1✔
1682
    }
1✔
1683

1684
    private void visit(ExprDestroy stmtDestroy) {
1685
        WurstType typ = stmtDestroy.getDestroyedObj().attrTyp();
1✔
1686
        if (typ instanceof WurstTypeModule) {
1✔
1687

1688
        } else if (typ instanceof WurstTypeClass) {
1✔
1689
            WurstTypeClass c = (WurstTypeClass) typ;
1✔
1690
            checkDestroyClass(stmtDestroy, c);
1✔
1691
        } else if (typ instanceof WurstTypeInterface) {
1✔
1692
            WurstTypeInterface i = (WurstTypeInterface) typ;
1✔
1693
            checkDestroyInterface(stmtDestroy, i);
1✔
1694
        } else {
1✔
1695
            stmtDestroy.addError("Cannot destroy objects of type " + typ);
×
1696
        }
1697
    }
1✔
1698

1699
    private void checkDestroyInterface(ExprDestroy stmtDestroy, WurstTypeInterface i) {
1700
        if (i.isStaticRef()) {
1✔
1701
            stmtDestroy.addError("Cannot destroy interface " + i);
×
1702
        }
1703
    }
1✔
1704

1705
    private void checkDestroyClass(ExprDestroy stmtDestroy, WurstTypeClass c) {
1706
        if (c.isStaticRef()) {
1✔
1707
            stmtDestroy.addError("Cannot destroy class " + c);
×
1708
        }
1709
        calledFunctions.put(stmtDestroy.attrNearestScope(), c.getClassDef().getOnDestroy());
1✔
1710
    }
1✔
1711

1712
    private void visit(ExprVarAccess e) {
1713
        checkVarRef(e, e.attrIsDynamicContext());
1✔
1714
    }
1✔
1715

1716
    private void visit(WImport wImport) {
1717
        if (wImport.attrImportedPackage() == null) {
1✔
1718
            if (!wImport.getPackagename().equals("NoWurst")) {
1✔
1719
                wImport.addError("Could not find imported package " + wImport.getPackagename());
×
1720
            }
1721
            return;
1✔
1722
        }
1723
        if (!wImport.attrImportedPackage().getName().equals("Wurst")
1✔
1724
                && wImport.attrImportedPackage().getName().equals(wImport.attrNearestNamedScope().getName())) {
1✔
1725
            wImport.addError("Packages cannot import themselves");
×
1726
        }
1727
    }
1✔
1728

1729
    /**
1730
     * check if the nameRef e is accessed correctly i.e. not using a dynamic
1731
     * variable from a static context
1732
     *
1733
     * @param e
1734
     * @param dynamicContext
1735
     */
1736
    private void checkVarRef(NameRef e, boolean dynamicContext) {
1737
        NameLink link = e.attrNameLink();
1✔
1738
        if (link == null) {
1✔
1739
            return;
1✔
1740
        }
1741
        NameDef def = link.getDef();
1✔
1742
        if (def instanceof GlobalVarDef) {
1✔
1743
            GlobalVarDef g = (GlobalVarDef) def;
1✔
1744
            if (g.attrIsDynamicClassMember() && !dynamicContext) {
1✔
1745
                e.addError("Cannot reference dynamic variable " + e.getVarName() + " from static context.");
×
1746
            }
1747
        }
1748
        checkNameRefDeprecated(e, def);
1✔
1749
        if (e.attrTyp() instanceof WurstTypeNamedScope) {
1✔
1750
            WurstTypeNamedScope wtns = (WurstTypeNamedScope) e.attrTyp();
1✔
1751
            if (wtns.isStaticRef()) {
1✔
1752
                if (!isUsedAsReceiverInExprMember(e)) {
1✔
1753
                    e.addError("Reference to " + e.getVarName() + " cannot be used as an expression.");
×
1754
                } else if (e.getParent() instanceof ExprMemberMethodDotDot) {
1✔
1755
                    e.addError("Reference to " + e.getVarName()
×
1756
                            + " cannot be used with the cascade operator. Only dynamic objects are allowed.");
1757
                } else if (e.getParent() instanceof ExprMemberMethod) {
1✔
1758
                    ExprMemberMethod em = (ExprMemberMethod) e.getParent();
1✔
1759
                    if (em.attrFuncDef() instanceof ExtensionFuncDef) {
1✔
1760
                        e.addError("Reference to " + e.getVarName()
1✔
1761
                                + " can only be used for calling static methods, but not for calling extension method method '" + em.getFuncName() + "'.");
1✔
1762
                    }
1763

1764

1765
                }
1766
            }
1767
        }
1768

1769
    }
1✔
1770

1771
    private boolean isUsedAsReceiverInExprMember(Expr e) {
1772
        if (e.getParent() instanceof ExprMember) {
1✔
1773
            ExprMember em = (ExprMember) e.getParent();
1✔
1774
            return em.getLeft() == e;
1✔
1775
        } else if (e.getParent() instanceof StmtForIn) {
1✔
1776
            // if we write for x in E, then it actually calls E.iterator(), so it is used in an ExprMember
1777
            StmtForIn parent = (StmtForIn) e.getParent();
1✔
1778
            return parent.getIn() == e;
1✔
1779
        } else if (e.getParent() instanceof StmtForFrom) {
×
1780
            StmtForFrom parent = (StmtForFrom) e.getParent();
×
1781
            return parent.getIn() == e;
×
1782
        }
1783
        return false;
×
1784
    }
1785

1786
    private void checkTypeBinding(HasTypeArgs e) {
1787
        VariableBinding mapping = e.match(new HasTypeArgs.Matcher<VariableBinding>() {
1✔
1788

1789
            @Override
1790
            public VariableBinding case_ExprNewObject(ExprNewObject e) {
1791
                return e.attrTyp().getTypeArgBinding();
1✔
1792
            }
1793

1794
            @Override
1795
            public VariableBinding case_ModuleUse(ModuleUse moduleUse) {
1796
                return null;
1✔
1797
            }
1798

1799
            @Override
1800
            public VariableBinding case_TypeExprSimple(TypeExprSimple e) {
1801
                return e.attrTyp().getTypeArgBinding();
1✔
1802
            }
1803

1804
            @Override
1805
            public VariableBinding case_ExprFunctionCall(ExprFunctionCall e) {
1806
                return e.attrTyp().getTypeArgBinding();
1✔
1807
            }
1808

1809
            @Override
1810
            public VariableBinding case_ExprMemberMethodDot(ExprMemberMethodDot e) {
1811
                return e.attrTyp().getTypeArgBinding();
1✔
1812
            }
1813

1814
            @Override
1815
            public VariableBinding case_ExprMemberMethodDotDot(ExprMemberMethodDotDot e) {
1816
                return e.attrTyp().getTypeArgBinding();
1✔
1817
            }
1818
        });
1819
        if (mapping == null) {
1✔
1820
            return;
1✔
1821
        }
1822

1823
        for (Tuple2<TypeParamDef, WurstTypeBoundTypeParam> t : mapping) {
1✔
1824
            WurstTypeBoundTypeParam boundTyp = t._2();
1✔
1825
            WurstType typ = boundTyp.getBaseType();
1✔
1826

1827
            TypeParamDef tp = t._1();
1✔
1828
            if (tp.getTypeParamConstraints() instanceof TypeExprList) {
1✔
1829
                // new style generics
1830
            } else { // old style generics
1831

1832
                if (!typ.isTranslatedToInt() && !(e instanceof ModuleUse)) {
1✔
1833
                    String toIndexFuncName = ImplicitFuncs.toIndexFuncName(typ);
1✔
1834
                    String fromIndexFuncName = ImplicitFuncs.fromIndexFuncName(typ);
1✔
1835
                    Collection<FuncLink> toIndexFuncs = ImplicitFuncs.findToIndexFuncs(typ, e);
1✔
1836
                    Collection<FuncLink> fromIndexFuncs = ImplicitFuncs.findFromIndexFuncs(typ, e);
1✔
1837
                    if (toIndexFuncs.isEmpty()) {
1✔
1838
                        e.addError("Type parameters can only be bound to ints and class types, but " + "not to " + typ
×
1839
                                + ".\n" + "You can provide functions " + toIndexFuncName + " and " + fromIndexFuncName
1840
                                + " to use this type " + "with generics.");
1841
                    } else if (fromIndexFuncs.isEmpty()) {
1✔
1842
                        e.addError("Could not find function " + fromIndexFuncName + " which is required to use " + typ
×
1843
                                + " with generics.");
1844
                    } else {
1845
                        if (toIndexFuncs.size() > 1) {
1✔
1846
                            e.addError("There is more than one function named " + toIndexFuncName);
×
1847
                        }
1848
                        if (fromIndexFuncs.size() > 1) {
1✔
1849
                            e.addError("There is more than one function named " + fromIndexFuncName);
×
1850
                        }
1851
                        NameDef toIndex = Utils.getFirst(toIndexFuncs).getDef();
1✔
1852
                        if (toIndex instanceof FuncDef) {
1✔
1853
                            FuncDef toIndexF = (FuncDef) toIndex;
1✔
1854

1855
                            if (toIndexF.getParameters().size() != 1) {
1✔
1856
                                toIndexF.addError("Must have exactly one parameter");
×
1857

1858
                            } else if (!toIndexF.getParameters().get(0).attrTyp().equalsType(typ, e)) {
1✔
1859
                                toIndexF.addError("Parameter must be of type " + typ);
×
1860
                            }
1861

1862
                            WurstType returnType = toIndexF.attrReturnTyp();
1✔
1863
                            if (!returnType.equalsType(WurstTypeInt.instance(), e)) {
1✔
1864
                                toIndexF.addError("Return type must be of type int " + " but was " + returnType);
×
1865
                            }
1866
                        } else {
1✔
1867
                            toIndex.addError("This should be a function.");
×
1868
                        }
1869

1870
                        NameDef fromIndex = Utils.getFirst(fromIndexFuncs).getDef();
1✔
1871
                        if (fromIndex instanceof FuncDef) {
1✔
1872
                            FuncDef fromIndexF = (FuncDef) fromIndex;
1✔
1873

1874
                            if (fromIndexF.getParameters().size() != 1) {
1✔
1875
                                fromIndexF.addError("Must have exactly one parameter");
×
1876

1877
                            } else if (!fromIndexF.getParameters().get(0).attrTyp()
1✔
1878
                                    .equalsType(WurstTypeInt.instance(), e)) {
1✔
1879
                                fromIndexF.addError("Parameter must be of type int");
×
1880
                            }
1881

1882
                            WurstType returnType = fromIndexF.attrReturnTyp();
1✔
1883
                            if (!returnType.equalsType(typ, e)) {
1✔
1884
                                fromIndexF.addError("Return type must be of type " + typ + " but was " + returnType);
×
1885
                            }
1886

1887
                        } else {
1✔
1888
                            fromIndex.addError("This should be a function.");
×
1889
                        }
1890
                    }
1891
                }
1892
            }
1893
        }
1✔
1894
    }
1✔
1895

1896
    private void checkFuncRef(FuncRef ref) {
1897
        if (ref.getFuncName().isEmpty()) {
1✔
1898
            ref.addError("Missing function name.");
×
1899
        }
1900
        checkFuncDefDeprecated(ref);
1✔
1901
        FuncLink called = ref.attrFuncLink();
1✔
1902
        if (called == null) {
1✔
1903
            return;
1✔
1904
        }
1905
        WScope scope = ref.attrNearestFuncDef();
1✔
1906
        if (scope == null) {
1✔
1907
            scope = ref.attrNearestScope();
1✔
1908
        }
1909
        if (!(ref instanceof ExprFuncRef)) { // ExprFuncRef is not a direct call
1✔
1910
            calledFunctions.put(scope, called.getDef());
1✔
1911
        }
1912
    }
1✔
1913

1914
    private void checkNameRefDeprecated(Element trace, NameLink link) {
1915
        if (link != null) {
1✔
1916
            checkNameRefDeprecated(trace, link.getDef());
1✔
1917
        }
1918

1919
    }
1✔
1920

1921
    private void checkNameRefDeprecated(Element trace, NameDef def) {
1922
        if (def != null && def.hasAnnotation("@deprecated")) {
1✔
1923
            Annotation annotation = def.getAnnotation("@deprecated");
1✔
1924
            String msg = annotation.getAnnotationMessage();
1✔
1925
            msg = (msg == null || msg.isEmpty()) ? "It shouldn't be used and will be removed in the future." : msg;
1✔
1926
            trace.addWarning("<" + def.getName() + "> is deprecated. " + msg);
1✔
1927
        }
1928
    }
1✔
1929

1930
    private void checkFuncDefDeprecated(FuncRef ref) {
1931
        checkNameRefDeprecated(ref, ref.attrFuncLink());
1✔
1932
    }
1✔
1933

1934
    private void checkFuncRef(ExprFuncRef ref) {
1935
        FuncLink called = ref.attrFuncLink();
1✔
1936
        if (called == null) {
1✔
1937
            return;
×
1938
        }
1939
        if (ref.attrTyp() instanceof WurstTypeCode) {
1✔
1940
            if (called.getDef().attrParameterTypesIncludingReceiver().size() > 0) {
1✔
1941
                String msg = "Can only use functions without parameters in 'code' function references.";
1✔
1942
                if (called.getDef().attrIsDynamicClassMember()) {
1✔
1943
                    msg += "\nNote that " + called.getName()
1✔
1944
                            + " is a dynamic function and thus has an implicit parameter 'this'.";
1945
                }
1946
                ref.addError(msg);
×
1947
            }
1948
        }
1949
    }
1✔
1950

1951
    private void checkModifiers(final HasModifier e) {
1952
        final boolean inParams = e.getParent() instanceof WParameters;
1✔
1953

1954
        for (final Modifier m : e.getModifiers()) {
1✔
1955
            if (m instanceof WurstDoc) continue;
1✔
1956
            if (m instanceof ModVararg && inParams) continue;
1✔
1957

1958
            final boolean isJurst = m.attrSource().getFile().endsWith(".jurst");
1✔
1959

1960
            final StringBuilder[] error = {null}; // lazily allocate only if needed
1✔
1961

1962
            e.match(new HasModifier.MatcherVoid() {
1✔
1963

1964
                @SafeVarargs
1965
                private final void check(Class<? extends Modifier>... allowed) {
1966
                    if (allowed.length == 0) {
1✔
1967
                        if (error[0] == null) error[0] = new StringBuilder(96);
×
1968
                        error[0].setLength(0);
×
1969
                        error[0].append("Type Parameters must not have modifiers");
×
1970
                        return;
×
1971
                    }
1972

1973
                    boolean isAllowed = false;
1✔
1974
                    for (Class<? extends Modifier> a : allowed) {
1✔
1975
                        String modName = m.getClass().getName();
1✔
1976
                        String allowedName = a.getName();
1✔
1977
                        if (modName.startsWith(allowedName)) {
1✔
1978
                            isAllowed = true;
1✔
1979
                            break;
1✔
1980
                        }
1981
                    }
1982
                    if (isAllowed) return;
1✔
1983

1984
                    if (error[0] == null) {
×
1985
                        error[0] = new StringBuilder(160);
×
1986
                        error[0].append("Modifier ")
×
1987
                            .append(printMod(m))
×
1988
                            .append(" not allowed for ")
×
1989
                            .append(Utils.printElement(e))
×
1990
                            .append(". Allowed: ");
×
1991
                    } else {
1992
                        error[0].append(", ");
×
1993
                    }
1994

1995
                    boolean first = true;
×
1996
                    for (Class<? extends Modifier> c : allowed) {
×
1997
                        if (!first) error[0].append(", ");
×
1998
                        error[0].append(printMod(c));
×
1999
                        first = false;
×
2000
                    }
2001
                }
×
2002

2003
                @Override
2004
                public void case_WParameter(WParameter wParameter) {
2005
                    check(ModConstant.class);
1✔
2006
                }
1✔
2007

2008
                @Override
2009
                public void case_WShortParameter(WShortParameter wShortParameter) {
2010
                    check(ModConstant.class);
1✔
2011
                }
1✔
2012

2013
                @Override
2014
                public void case_TypeParamDef(TypeParamDef typeParamDef) {
2015
                    check();
×
2016
                }
×
2017

2018
                @Override
2019
                public void case_NativeType(NativeType nativeType) {
2020
                    check(VisibilityPublic.class);
×
2021
                }
×
2022

2023
                @Override
2024
                public void case_NativeFunc(NativeFunc nativeFunc) {
2025
                    check(VisibilityPublic.class, Annotation.class);
1✔
2026
                }
1✔
2027

2028
                @Override
2029
                public void case_ModuleInstanciation(ModuleInstanciation moduleInstanciation) {
2030
                    check(VisibilityPrivate.class, VisibilityProtected.class);
×
2031
                }
×
2032

2033
                @Override
2034
                public void case_ModuleDef(ModuleDef moduleDef) {
2035
                    check(VisibilityPublic.class);
1✔
2036
                }
1✔
2037

2038
                @Override
2039
                public void case_LocalVarDef(LocalVarDef localVarDef) {
2040
                    check(ModConstant.class);
1✔
2041
                    if (localVarDef.hasAnnotation("@compiletime")) {
1✔
2042
                        localVarDef.getAnnotation("@compiletime")
×
2043
                            .addWarning("The annotation '@compiletime' has no effect on variables.");
×
2044
                    }
2045
                }
1✔
2046

2047
                @Override
2048
                public void case_GlobalVarDef(GlobalVarDef g) {
2049
                    if (g.attrNearestClassOrModule() != null) {
1✔
2050
                        check(VisibilityPrivate.class, VisibilityProtected.class,
1✔
2051
                            ModStatic.class, ModConstant.class, Annotation.class);
2052
                    } else {
2053
                        check(VisibilityPublic.class, ModConstant.class, Annotation.class);
1✔
2054
                    }
2055
                    if (g.hasAnnotation("@compiletime")) {
1✔
2056
                        g.getAnnotation("@compiletime")
×
2057
                            .addWarning("The annotation '@compiletime' has no effect on variables.");
×
2058
                    }
2059
                }
1✔
2060

2061
                @Override
2062
                public void case_FuncDef(FuncDef f) {
2063
                    if (f.attrNearestStructureDef() != null) {
1✔
2064
                        if (f.attrNearestStructureDef() instanceof InterfaceDef) {
1✔
2065
                            check(VisibilityPrivate.class, VisibilityProtected.class,
1✔
2066
                                ModAbstract.class, ModOverride.class, Annotation.class);
2067
                        } else {
2068
                            check(VisibilityPrivate.class, VisibilityProtected.class,
1✔
2069
                                ModAbstract.class, ModOverride.class, ModStatic.class, Annotation.class);
2070
                            if (f.attrNearestStructureDef() instanceof ClassDef) {
1✔
2071
                                if (f.attrIsStatic() && f.attrIsAbstract()) {
1✔
2072
                                    f.addError("Static functions cannot be abstract.");
×
2073
                                }
2074
                            }
2075
                        }
2076
                    } else {
2077
                        check(VisibilityPublic.class, Annotation.class);
1✔
2078
                    }
2079
                    if (f.attrIsCompiletime()) {
1✔
2080
                        if (f.getParameters().size() > 0) {
1✔
2081
                            f.addError("Functions annotated '@compiletime' may not take parameters.\n"
×
2082
                                + "Note: The annotation marks functions to be executed by wurst at compiletime.");
2083
                        } else if (f.attrIsDynamicClassMember()) {
1✔
2084
                            f.addError("Functions annotated '@compiletime' must be static.\n"
×
2085
                                + "Note: The annotation marks functions to be executed by wurst at compiletime.");
2086
                        }
2087
                    }
2088
                }
1✔
2089

2090
                @Override
2091
                public void case_ExtensionFuncDef(ExtensionFuncDef extensionFuncDef) {
2092
                    check(VisibilityPublic.class, Annotation.class);
1✔
2093
                }
1✔
2094

2095
                @Override
2096
                public void case_ConstructorDef(ConstructorDef constructorDef) {
2097
                    check(VisibilityPrivate.class);
1✔
2098
                }
1✔
2099

2100
                @Override
2101
                public void case_ClassDef(ClassDef classDef) {
2102
                    check(VisibilityPublic.class, ModAbstract.class, ModStatic.class);
1✔
2103
                    if (!classDef.isInnerClass() && classDef.attrIsStatic()) {
1✔
2104
                        classDef.addError("Top-level class " + classDef.getName()
×
2105
                            + " cannot be static. Only inner classes can be declared static.");
2106
                    }
2107
                }
1✔
2108

2109
                @Override
2110
                public void case_InterfaceDef(InterfaceDef interfaceDef) {
2111
                    check(VisibilityPublic.class);
1✔
2112
                }
1✔
2113

2114
                @Override
2115
                public void case_TupleDef(TupleDef tupleDef) {
2116
                    check(VisibilityPublic.class);
1✔
2117
                }
1✔
2118

2119
                @Override
2120
                public void case_WPackage(WPackage wPackage) {
2121
                    check();
×
2122
                }
×
2123

2124
                @Override
2125
                public void case_EnumDef(EnumDef enumDef) {
2126
                    check(VisibilityPublic.class);
1✔
2127
                }
1✔
2128

2129
                @Override
2130
                public void case_EnumMember(EnumMember enumMember) {
2131
                    check();
×
2132
                }
×
2133
            });
2134

2135
            if (error[0] != null && error[0].length() > 0) {
1✔
2136
                if (isJurst) {
×
2137
                    m.addWarning(error[0].toString());
×
2138
                } else {
2139
                    m.addError(error[0].toString());
×
2140
                }
2141
            }
2142
        }
1✔
2143
    }
1✔
2144

2145

2146

2147
    private static String printMod(Class<? extends Modifier> c) {
2148
        String name = c.getName().toLowerCase();
×
2149
        name = name.replaceFirst("^.*\\.", "");
×
2150
        name = name.replaceAll("^(mod|visibility)", "");
×
2151
        name = name.replaceAll("impl$", "");
×
2152
        return name;
×
2153
    }
2154

2155
    private static String printMod(Modifier m) {
2156
        if (m instanceof Annotation) {
×
2157
            return ((Annotation) m).getAnnotationType();
×
2158
        }
2159
        return printMod(m.getClass());
×
2160
    }
2161

2162
    private void checkConstructor(ConstructorDef d) {
2163
        if (d.attrNearestClassOrModule() instanceof ModuleDef) {
1✔
2164
            if (d.getParameters().size() > 0) {
1✔
2165
                d.getParameters().addError("Module constructors must not have parameters.");
×
2166
            }
2167
        }
2168
        StructureDef s = d.attrNearestStructureDef();
1✔
2169
        if (s instanceof ClassDef) {
1✔
2170
            ClassDef c = (ClassDef) s;
1✔
2171
            WurstTypeClass ct = c.attrTypC();
1✔
2172
            WurstTypeClass extendedClass = ct.extendedClass();
1✔
2173
            if (extendedClass != null) {
1✔
2174
                // check if super constructor is called correctly...
2175
                // TODO check constr: get it from ct so that it has the correct type binding
2176
                ConstructorDef sc = d.attrSuperConstructor();
1✔
2177
                if (sc == null) {
1✔
2178
                    d.addError("No super constructor found.");
×
2179
                } else {
2180
                    List<WurstType> paramTypes = Lists.newArrayList();
1✔
2181
                    for (WParameter p : sc.getParameters()) {
1✔
2182
                        paramTypes.add(p.attrTyp());
1✔
2183
                    }
1✔
2184
                    if (d.getSuperConstructorCall() instanceof NoSuperConstructorCall
1✔
2185
                            && paramTypes.size() > 0) {
1✔
2186
                        c.addError("The extended class <" + extendedClass.getName() + "> does not expose a no-arg constructor. " +
×
2187
                                "You must define a constructor that calls super(..) appropriately, in this class.");
2188
                    } else {
2189
                        checkParams(d, "Incorrect call to super constructor: ", superArgs(d), paramTypes);
1✔
2190
                    }
2191
                }
2192
            }
2193
        } else {
1✔
2194
            if (d.getSuperConstructorCall() instanceof SomeSuperConstructorCall) {
1✔
2195
                d.addError("Module constructors cannot have super calls.");
×
2196
            }
2197
        }
2198
    }
1✔
2199

2200

2201
    private void checkArrayAccess(ExprVarArrayAccess ea) {
2202
        checkNameRefDeprecated(ea, ea.tryGetNameDef());
1✔
2203
        for (Expr index : ea.getIndexes()) {
1✔
2204
            if (!(index.attrTyp().isSubtypeOf(WurstTypeInt.instance(), ea))) {
1✔
2205
                index.addError("Arrayindices have to be of type int");
×
2206
            }
2207
        }
1✔
2208
    }
1✔
2209

2210
    private void checkInterfaceDef(InterfaceDef i) {
2211
        checkTypeName(i, i.getName());
1✔
2212
        // TODO check if functions are refinements
2213
    }
1✔
2214

2215
    private void checkNewObj(ExprNewObject e) {
2216
        ConstructorDef constr = e.attrConstructorDef();
1✔
2217
        if (constr != null) {
1✔
2218
            calledFunctions.put(e.attrNearestScope(), constr);
1✔
2219
            if (constr.attrNearestClassDef().attrIsAbstract()) {
1✔
2220
                e.addError("Cannot create an instance of the abstract class " + constr.attrNearestClassDef().getName());
×
2221
                return;
×
2222
            }
2223
            checkParams(e, "Wrong object creation: ", e.getArgs(), e.attrFunctionSignature());
1✔
2224
        }
2225

2226
    }
1✔
2227

2228
    private void nameDefsMustNotBeNamedAfterJassNativeTypes(NameDef n) {
2229
        PackageOrGlobal p = n.attrNearestPackage();
1✔
2230
        if (p == null) {
1✔
2231
            n.addError("Not in package or global: " + n.getName());
×
2232
        }
2233
        // checkIfTypeDefExists(n, p);
2234
        // if (p instanceof WPackage) {
2235
        // // check global scope
2236
        // p = p.getParent().attrNearestPackage();
2237
        // checkIfTypeDefExists(n, p);
2238
        // }
2239
    }
1✔
2240

2241
    private void checkMemberVar(ExprMemberVar e) {
2242
        if (e.getVarName().length() == 0) {
1✔
2243
            e.addError("Incomplete member access.");
×
2244
        }
2245
        if (e.getParent() instanceof WStatements) {
1✔
2246
            e.addError("Incomplete statement.");
×
2247
        }
2248
    }
1✔
2249

2250
    private void checkPackageName(CompilationUnit cu) {
2251
        // Fast exits
2252
        List<WPackage> pkgs = cu.getPackages();
1✔
2253
        if (pkgs.size() != 1) return;
1✔
2254

2255
        String filePath = cu.getCuInfo().getFile();     // assume non-null
1✔
2256
        // Ultra-cheap extension check
2257
        boolean wurst = filePath.endsWith(".wurst");
1✔
2258
        boolean jurst = !wurst && filePath.endsWith(".jurst");
1✔
2259
        if (!wurst && !jurst) return;
1✔
2260

2261
        // Get bare file name without touching java.nio
2262
        int slash = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'));
1✔
2263
        String fileName = (slash >= 0) ? filePath.substring(slash + 1) : filePath;
1✔
2264

2265
        // Strip extension once
2266
        int dot = fileName.lastIndexOf('.');
1✔
2267
        if (dot <= 0) return; // no basename
1✔
2268
        String base = fileName.substring(0, dot);
1✔
2269

2270
        String pkgName = pkgs.get(0).getName();
1✔
2271
        if (!base.equals(pkgName)) {
1✔
2272
            pkgs.get(0).addError("The file must have the same name as the package " + pkgName);
×
2273
        }
2274
    }
1✔
2275

2276
    private void checkForDuplicatePackages(WurstModel model) {
2277
        model.attrPackages();
×
2278
    }
×
2279

2280
    private void checkBannedFunctions(ExprFunctionCall e) {
2281
        if (e.getFuncName().equals("TriggerRegisterVariableEvent")) {
1✔
2282
            if (e.getArgs().size() > 1) {
1✔
2283
                if (e.getArgs().get(1) instanceof ExprStringVal) {
1✔
2284
                    ExprStringVal varName = (ExprStringVal) e.getArgs().get(1);
1✔
2285
                    TRVEHelper.protectedVariables.add(varName.getValS());
1✔
2286
                    WLogger.info("keep: " + varName.getValS());
1✔
2287
                    return;
1✔
2288
                } else if (e.getArgs().get(1) instanceof ExprVarAccess) {
1✔
2289
                    // Check if this is a two line hook... thanks Bribe
2290
                    ExprVarAccess varAccess = (ExprVarAccess) e.getArgs().get(1);
1✔
2291
                    @Nullable FunctionImplementation nearestFunc = e.attrNearestFuncDef();
1✔
2292
                    WStatements fbody = nearestFunc.getBody();
1✔
2293
                    if (e.getParent() instanceof StmtReturn && fbody.size() <= 4 && fbody.get(fbody.size() - 2).structuralEquals(e.getParent())) {
1✔
2294
                        WParameters params = nearestFunc.getParameters();
1✔
2295
                        if (params.size() == 4 && ((TypeExprSimple) params.get(0).getTyp()).getTypeName().equals("trigger")
1✔
2296
                            && ((TypeExprSimple) params.get(1).getTyp()).getTypeName().equals("string")
1✔
2297
                            && ((TypeExprSimple) params.get(2).getTyp()).getTypeName().equals("limitop")
1✔
2298
                            && ((TypeExprSimple) params.get(3).getTyp()).getTypeName().equals("real")) {
1✔
2299
                            trveWrapperFuncs.add(nearestFunc.getName());
1✔
2300
                            WLogger.info("found wrapper: " + nearestFunc.getName());
1✔
2301
                            return;
1✔
2302
                        }
2303
                    }
2304
                }
×
2305
            } else {
2306

2307
                e.addError("Map contains TriggerRegisterVariableEvent with non-constant arguments. Can't be optimized.");
×
2308
            }
2309
        }
2310

2311
        if (e.getFuncName().equals("ExecuteFunc")) {
1✔
2312
            // executeFunc can only use constant string arguments
2313
            if (e.getArgs().size() != 1) {
1✔
2314
                e.addError("Wrong number of args");
×
2315
                return;
×
2316
            }
2317
            if (e.getArgs().get(0) instanceof ExprStringVal) {
1✔
2318
                ExprStringVal s = (ExprStringVal) e.getArgs().get(0);
1✔
2319
                String exFunc = s.getValS();
1✔
2320
                Collection<FuncLink> funcs = e.lookupFuncs(exFunc);
1✔
2321
                if (funcs.isEmpty()) {
1✔
2322
                    e.addError("Could not find function " + exFunc + ".");
×
2323
                    return;
×
2324
                }
2325
                if (funcs.size() > 1) {
1✔
2326
                    StringBuilder alternatives = new StringBuilder();
×
2327
                    for (NameLink nameLink : funcs) {
×
2328
                        alternatives.append("\n - ").append(Utils.printElementWithSource(Optional.of(nameLink.getDef())));
×
2329
                    }
×
2330
                    e.addError("Ambiguous function name: " + exFunc + ". Alternatives are: " + alternatives);
×
2331
                    return;
×
2332
                }
2333
                FuncLink func = Utils.getFirst(funcs);
1✔
2334
                if (func.getParameterTypes().size() != 0) {
1✔
2335
                    e.addError("Function " + exFunc + " must not have any parameters.");
×
2336
                }
2337
            } else {
1✔
2338
                e.addError("Wurst does only support ExecuteFunc with a single string as argument.");
×
2339
            }
2340
        }
2341
    }
1✔
2342

2343
    private boolean isViableSwitchtype(Expr expr) {
2344
        WurstType typ = expr.attrTyp();
1✔
2345
        if (typ.equalsType(WurstTypeInt.instance(), null) || typ.equalsType(WurstTypeString.instance(), null)) {
1✔
2346
            return true;
1✔
2347
        } else if (typ instanceof WurstTypeEnum) {
1✔
2348
            WurstTypeEnum wte = (WurstTypeEnum) typ;
1✔
2349
            return !wte.isStaticRef();
1✔
2350
        } else {
2351
            return false;
×
2352
        }
2353
    }
2354

2355
    private void checkSwitch(SwitchStmt s) {
2356
        if (!isViableSwitchtype(s.getExpr())) {
1✔
2357
            s.addError("The type " + s.getExpr().attrTyp()
×
2358
                    + " is not viable as switchtype.\nViable switchtypes: int, string, enum");
2359
        } else {
2360
            List<Expr> switchExprs = s.getCases().stream()
1✔
2361
                    .flatMap(e -> e.getExpressions().stream())
1✔
2362
                    .collect(Collectors.toList());
1✔
2363
            for (Expr cExpr : switchExprs) {
1✔
2364
                if (!cExpr.attrTyp().isSubtypeOf(s.getExpr().attrTyp(), cExpr)) {
1✔
2365
                    cExpr.addError("The type " + cExpr.attrTyp() + " does not match the switchtype "
1✔
2366
                            + s.getExpr().attrTyp() + ".");
1✔
2367
                }
2368
            }
1✔
2369
            for (int i = 0; i < switchExprs.size(); i++) {
1✔
2370
                Expr ei = switchExprs.get(i);
1✔
2371
                for (int j = 0; j < i; j++) {
1✔
2372
                    Expr ej = switchExprs.get(j);
1✔
2373
                    if (ei.structuralEquals(ej)) {
1✔
2374
                        ei.addError("The case " + Utils.prettyPrint(ei) + " is already handled in line " + ej.attrSource().getLine());
×
2375
                        return;
×
2376
                    }
2377
                }
2378
            }
2379
        }
2380
        for (String unhandledCase : s.calculateUnhandledCases()) {
1✔
2381
            s.addError(unhandledCase + " not covered in switchstatement and no default found.");
×
2382
        }
×
2383
        if (s.getCases().isEmpty()) {
1✔
2384
            s.addError("Switch statement without any cases.");
×
2385
        }
2386
    }
1✔
2387

2388
    public static void computeFlowAttributes(Element node) {
2389
        if (node instanceof WStatement) {
1✔
2390
            WStatement s = (WStatement) node;
1✔
2391
            s.attrNextStatements();
1✔
2392
        }
2393

2394
        // traverse childs
2395
        for (int i = 0; i < node.size(); i++) {
1✔
2396
            computeFlowAttributes(node.get(i));
1✔
2397
        }
2398
    }
1✔
2399

2400
    private void checkCodeArrays(TypeExprArray e) {
2401
        if (e.getBase() instanceof TypeExprSimple) {
1✔
2402
            TypeExprSimple base = (TypeExprSimple) e.getBase();
1✔
2403
            if (base.getTypeName().equals("code")) {
1✔
2404
                e.addError("Code arrays are not supported. Try using an array of triggers or conditionfuncs.");
×
2405
            }
2406

2407
        }
2408
    }
1✔
2409

2410

2411
    /**
2412
     * checks if func1 can override func2
2413
     */
2414
    public static boolean canOverride(FuncLink func1, FuncLink func2, boolean allowStaticOverride) {
2415
        return checkOverride(func1, func2, allowStaticOverride) == null;
1✔
2416
    }
2417

2418
    /**
2419
     * checks if func1 can override func2
2420
     * <p>
2421
     * Returns null if yes and an error message if not.
2422
     */
2423
    public static String checkOverride(FuncLink func1, FuncLink func2, boolean allowStaticOverride) {
2424
        if (!allowStaticOverride) {
1✔
2425
            if (func1.isStatic()) {
1✔
2426
                return "Static method " + func1.getName() + " cannot override other methods.";
1✔
2427
            }
2428
            if (func2.isStatic()) {
1✔
2429
                return "Static " + Utils.printElementWithSource(Optional.of(func2.getDef())) + " cannot be overridden.";
1✔
2430
            }
2431
        } else {
2432
            if (func1.isStatic() && !func2.isStatic()) {
1✔
2433
                return "Static method "
×
2434
                    + func1.getName()
×
2435
                    + " cannot override dynamic "
2436
                    + Utils.printElementWithSource(Optional.of(func2.getDef()))
×
2437
                    + ".";
2438
            } else if (!func1.isStatic() && func2.isStatic()) {
1✔
2439
                return "Method "
1✔
2440
                    + func1.getName()
1✔
2441
                    + " cannot override static "
2442
                    + Utils.printElementWithSource(Optional.of(func2.getDef()))
1✔
2443
                    + ".";
2444
            }
2445
        }
2446
        if (func1.isVarargMethod() && !func2.isVarargMethod()) {
1✔
2447
            return "Vararg method "
×
2448
                + func1.getName()
×
2449
                + " cannot override non-vararg method "
2450
                + Utils.printElementWithSource(Optional.of(func2.getDef()))
×
2451
                + ".";
2452
        }
2453
        if (!func1.isVarargMethod() && func2.isVarargMethod()) {
1✔
2454
            return "Non-vararg method "
×
2455
                + func1.getName()
×
2456
                + " cannot override vararg method "
2457
                + Utils.printElementWithSource(Optional.of(func2.getDef()))
×
2458
                + ".";
2459
        }
2460
        int paramCount2 = func2.getParameterTypes().size();
1✔
2461
        int paramCount1 = func1.getParameterTypes().size();
1✔
2462
        if (paramCount1 != paramCount2) {
1✔
2463
            return Utils.printElement(func2.getDef()) + " takes " + paramCount2
1✔
2464
                    + " parameters, but there are only " + paramCount1 + " parameters here.";
2465
        }
2466

2467
        // contravariant parametertypes
2468
        for (int i = 0; i < paramCount1; i++) {
1✔
2469
            WurstType type1 = func1.getParameterType(i);
1✔
2470
            WurstType type2 = func2.getParameterType(i);
1✔
2471
            if (!type1.isSupertypeOf(type2, func1.getDef())) {
1✔
2472
                return "Parameter " + type1 + " " + func1.getParameterName(i) + " should have type " + type2
1✔
2473
                        + " to override " + Utils.printElementWithSource(Optional.of(func2.getDef())) + ".";
1✔
2474
            }
2475
        }
2476
        // covariant return types
2477
        if (!func1.getReturnType().isSubtypeOf(func2.getReturnType(), func1.getDef())) {
1✔
2478
            return "Return type should be "
×
2479
                + func2.getReturnType()
×
2480
                + " to override "
2481
                + Utils.printElementWithSource(Optional.of(func2.getDef()))
×
2482
                + ".";
2483
        }
2484
        // no error
2485
        return null;
1✔
2486
    }
2487

2488
    private void checkForDuplicateNames(WScope scope) {
2489
        ImmutableMultimap<String, DefLink> links = scope.attrNameLinks();
1✔
2490
        for (String name : links.keySet()) {
1✔
2491
            ImmutableCollection<DefLink> nameLinks = links.get(name);
1✔
2492
            if (nameLinks.size() <= 1) {
1✔
2493
                continue;
1✔
2494
            }
2495
            @Nullable List<FuncLink> funcs = null;
1✔
2496
            @Nullable List<NameLink> other = null;
1✔
2497
            for (NameLink nl : nameLinks) {
1✔
2498
                if (nl.getDefinedIn() == scope) {
1✔
2499
                    if (nl instanceof FuncLink) {
1✔
2500
                        if (funcs == null) {
1✔
2501
                            funcs = Lists.newArrayList();
1✔
2502
                        }
2503
                        FuncLink funcLink = (FuncLink) nl;
1✔
2504
                        for (FuncLink link : funcs) {
1✔
2505
                            if (!distinctFunctions(funcLink, link)) {
1✔
2506
                                funcLink.getDef().addError(
1✔
2507
                                    "Function already defined : " + Utils.printElementWithSource(Optional.of(link.getDef())));
1✔
2508
                                link.getDef().addError(
×
2509
                                    "Function already defined : " + Utils.printElementWithSource(Optional.of(funcLink.getDef())));
×
2510
                            }
2511
                        }
1✔
2512

2513
                        funcs.add(funcLink);
1✔
2514
                    } else {
1✔
2515
                        if (other == null) {
1✔
2516
                            other = Lists.newArrayList();
1✔
2517
                        }
2518
                        other.add(nl);
1✔
2519
                    }
2520
                }
2521
            }
1✔
2522
            if (other != null && other.size() > 1) {
1✔
2523
                other.sort(Comparator.comparingInt(o -> o.getDef().attrSource().getLeftPos()));
1✔
2524
                NameLink l1 = other.get(0);
1✔
2525
                for (int j = 1; j < other.size(); j++) {
1✔
2526
                    other.get(j).getDef().addError("An element with name " + name + " already exists: "
1✔
2527
                        + Utils.printElementWithSource(Optional.of(l1.getDef())));
1✔
2528
                }
2529
            }
2530
        }
1✔
2531
    }
1✔
2532

2533
    private boolean distinctFunctions(FuncLink nl1, FuncLink nl2) {
2534
        if (receiverTypesDifferent(nl1, nl2)) {
1✔
2535
            return true;
1✔
2536
        }
2537
        FunctionDefinition f1 = nl1.getDef();
1✔
2538
        FunctionDefinition f2 = nl2.getDef();
1✔
2539
        WParameters ps1 = f1.getParameters();
1✔
2540
        WParameters ps2 = f2.getParameters();
1✔
2541
        if (ps1.size() != ps2.size()) {
1✔
2542
            return true;
1✔
2543
        }
2544
        return parametersTypeDisjunct(ps1, ps2);
1✔
2545
    }
2546

2547
    private boolean receiverTypesDifferent(FuncLink nl1, FuncLink nl2) {
2548
        if (nl1.getReceiverType() == null) {
1✔
2549
            return nl2.getReceiverType() != null;
1✔
2550
        } else {
2551
            return nl2.getReceiverType() == null || !nl1.getReceiverType().equalsType(nl2.getReceiverType(), nl1.getDef());
1✔
2552
        }
2553
    }
2554

2555
    private void checkForDuplicateImports(WPackage p) {
2556
        Set<String> imports = Sets.newLinkedHashSet();
1✔
2557
        for (WImport imp : p.getImports()) {
1✔
2558
            if (!imports.add(imp.getPackagename())) {
1✔
2559
                imp.addError("The package " + imp.getPackagename() + " is already imported.");
×
2560
            }
2561
        }
1✔
2562
    }
1✔
2563

2564
    private void checkVarDef(VarDef v) {
2565
        WurstType vtype = v.attrTyp();
1✔
2566

2567
        if (vtype instanceof WurstTypeCode && v.attrIsDynamicClassMember()) {
1✔
2568
            v.addError("Code members not allowed as dynamic class members (variable " + v.getName() + ")\n"
×
2569
                    + "Try using a trigger or conditionfunc instead.");
2570
        }
2571

2572
        if (v instanceof GlobalOrLocalVarDef) {
1✔
2573
            GlobalOrLocalVarDef g = (GlobalOrLocalVarDef) v;
1✔
2574
            if (g.attrIsConstant() && g.getInitialExpr() instanceof NoExpr && !g.attrIsDynamicClassMember()) {
1✔
2575
                g.addError("Constant variable " + g.getName() + " needs an initial value.");
×
2576
            }
2577
        }
2578

2579
        if (vtype instanceof WurstTypeArray) {
1✔
2580
            WurstTypeArray wta = (WurstTypeArray) vtype;
1✔
2581
            switch (wta.getDimensions()) {
1✔
2582
                case 0:
2583
                    v.addError("0-dimensional arrays are not allowed");
×
2584
                    break;
×
2585
                case 1:
2586
                    if (v.attrIsDynamicClassMember() && wta.getSize(0) <= 0) {
1✔
2587
                        v.addError("Array members require a fixed size greater 0.");
×
2588
                    }
2589
                    break;
2590
                default:
2591
                    v.addError("Multidimensional Arrays are not yet supported.");
×
2592
                    break;
2593
            }
2594
        }
2595

2596
        if (vtype instanceof WurstTypeNull) {
1✔
2597
            v.addError("Initial value of variable " + v.getName() + " is 'null'. Specify a concrete type.");
×
2598
        }
2599

2600
    }
1✔
2601

2602
    private void checkLocalShadowing(LocalVarDef v) {
2603
        NameLink shadowed = v.getParent().getParent().lookupVar(v.getName(), false);
1✔
2604
        if (shadowed != null) {
1✔
2605
            if (shadowed.getDef() instanceof LocalVarDef) {
1✔
2606
                v.addError("Variable " + v.getName() + " hides an other local variable with the same name.");
×
2607
            } else if (shadowed.getDef() instanceof WParameter) {
1✔
2608
                v.addError("Variable " + v.getName() + " hides a parameter with the same name.");
×
2609
            }
2610
        }
2611
    }
1✔
2612

2613
    private void checkConstructorSuperCall(ConstructorDef c) {
2614
        if (c.getSuperConstructorCall() instanceof SomeSuperConstructorCall) {
1✔
2615
            if (c.attrNearestClassDef() != null) {
1✔
2616
                ClassDef classDef = c.attrNearestClassDef();
1✔
2617
                if (classDef.getExtendedClass() instanceof NoTypeExpr) {
1✔
2618
                    c.addError("Super call in a class which extends nothing.");
×
2619
                }
2620
            }
2621
        }
2622
    }
1✔
2623

2624
    private void checkParameter(WParameter param) {
2625
        if (param.attrTyp() instanceof WurstTypeArray) {
1✔
2626
            param.addError("Cannot use arrays as parameters.");
×
2627
        }
2628
    }
1✔
2629
}
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