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

wurstscript / WurstScript / 265

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

Pull #1096

circleci

Frotty
restore determinism, fix tests
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.ForwardExecution;
18
import de.peeeq.wurstscript.validation.controlflow.ReturnsAnalysis;
19
import io.vavr.Tuple2;
20
import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
21
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
22
import org.eclipse.jdt.annotation.Nullable;
23

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

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

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

47

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

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

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

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

70
            lightValidation(toCheck);
1✔
71

72
            heavyValidation();
1✔
73

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

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

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

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

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

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

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

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

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

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

146

147

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

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

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

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

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

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

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

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

220
                }
1✔
221
            }
222
        }
1✔
223
    }
1✔
224

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

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

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

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

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

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

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

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

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

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

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

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

353

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

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

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

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

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

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

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

557
    }
1✔
558

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

732
        return true;
1✔
733
    }
734

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

738
    }
1✔
739

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

743
    }
1✔
744

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

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

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

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

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

776
    }
1✔
777

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

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

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

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

828

829

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

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

841

842

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

845

846

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

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

862

863

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

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

870

871

872

873

874

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

880
    }
1✔
881

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

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

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

901
    }
1✔
902

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

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

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

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

936
    }
1✔
937

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

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

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

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

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

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

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

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

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

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

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

996
    }
1✔
997

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

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

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

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

1034
            }
1✔
1035

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

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

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

1061
            }
1✔
1062

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

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

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

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

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

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

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

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

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

1188
    }
1✔
1189

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

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

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

1224
    }
1✔
1225

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1416

1417

1418

1419

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

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

1436

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

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

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

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

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

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

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

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

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

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

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

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

1510

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

1516
        checkFuncDefDeprecated(stmtCall);
1✔
1517

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

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

1545
    }
1✔
1546

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1765

1766
                }
1767
            }
1768
        }
1769

1770
    }
1✔
1771

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1920
    }
1✔
1921

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2146

2147

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

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

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

2201

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

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

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

2227
    }
1✔
2228

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2408
        }
2409
    }
1✔
2410

2411

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

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

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

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

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

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

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

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

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

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

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

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

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

2601
    }
1✔
2602

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

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

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