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

wurstscript / WurstScript / 271

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

Pull #1096

circleci

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

18202 of 28155 relevant lines covered (64.65%)

0.65 hits per line

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

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

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

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

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

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

45

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

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

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

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

68
            lightValidation(toCheck);
1✔
69

70
            heavyValidation();
1✔
71

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

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

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

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

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

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

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

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

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

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

144

145

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

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

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

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

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

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

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

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

218
                }
1✔
219
            }
220
        }
1✔
221
    }
1✔
222

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

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

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

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

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

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

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

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

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

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

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

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

351

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

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

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

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

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

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

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

555
    }
1✔
556

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

730
        return true;
1✔
731
    }
732

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

736
    }
1✔
737

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

741
    }
1✔
742

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

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

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

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

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

774
    }
1✔
775

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

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

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

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

826

827

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

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

839

840

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

843

844

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

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

860

861

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

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

868

869

870

871

872

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

878
    }
1✔
879

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

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

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

899
    }
1✔
900

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

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

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

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

934
    }
1✔
935

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

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

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

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

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

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

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

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

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

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

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

994
    }
1✔
995

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

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

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

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

1032
            }
1✔
1033

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

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

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

1059
            }
1✔
1060

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

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

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

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

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

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

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

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

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

1186
    }
1✔
1187

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

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

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

1222
    }
1✔
1223

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1414

1415

1416

1417

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

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

1434

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

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

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

1451
        boolean res = actual.isSubtypeOf(expected, site);
1✔
1452

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

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

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

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

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

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

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

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

1508

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

1514
        checkFuncDefDeprecated(stmtCall);
1✔
1515

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

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

1543
    }
1✔
1544

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1763

1764
                }
1765
            }
1766
        }
1767

1768
    }
1✔
1769

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1918
    }
1✔
1919

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2144

2145

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

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

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

2199

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

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

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

2225
    }
1✔
2226

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2406
        }
2407
    }
1✔
2408

2409

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

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

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

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

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

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

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

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

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

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

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

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

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

2599
    }
1✔
2600

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

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

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