Skip to content

Commit b70c453

Browse files
authored
Merge pull request #78 from OpenVADL/feature/typechecker-improved-branching
typechecker: Implement new semantics for constant branches
2 parents 2fa5aaa + 401961a commit b70c453

File tree

6 files changed

+212
-43
lines changed

6 files changed

+212
-43
lines changed

vadl-cli/main/vadl/cli/BaseCommand.java

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import vadl.pass.PassManager;
5050
import vadl.pass.PassOrder;
5151
import vadl.pass.exception.DuplicatedPassKeyException;
52+
import vadl.utils.EditorUtils;
5253
import vadl.utils.SourceLocation;
5354
import vadl.viam.Specification;
5455

@@ -211,6 +212,37 @@ private Specification parseToVIAM() {
211212
return spec;
212213
}
213214

215+
protected void printDumps(String message) {
216+
if (ArtifactTracker.getDumpPaths().isEmpty()) {
217+
return;
218+
}
219+
220+
System.out.println(message);
221+
for (var path : ArtifactTracker.getDumpPaths()) {
222+
if (EditorUtils.isIntelliJIDE()) {
223+
var uri = path.toAbsolutePath().toUri();
224+
System.out.printf("\t- %s\n", uri);
225+
} else {
226+
System.out.printf("\t- %s\n", path);
227+
}
228+
}
229+
}
230+
231+
protected void printArtifacts(String message) {
232+
if (ArtifactTracker.getArtifactPathsPaths().isEmpty()) {
233+
return;
234+
}
235+
System.out.println(message);
236+
for (var path : ArtifactTracker.getArtifactPathsPaths()) {
237+
if (EditorUtils.isIntelliJIDE()) {
238+
var uri = path.toAbsolutePath().toUri();
239+
System.out.printf("\t- %s\n", uri);
240+
} else {
241+
System.out.printf("\t- %s\n", path);
242+
}
243+
}
244+
}
245+
214246
// lazy evaluated config, do NOT use this directly.
215247
// use getConfig() instead.
216248
@Nullable
@@ -263,17 +295,10 @@ public Integer call() {
263295
🔥 The vadl compiler crashed 🔥 \s
264296
265297
This shouldn't have happened, please open an issue with the stacktrace below at:
266-
https://ea.complang.tuwien.ac.at/vadl/open-vadl/issues/new
298+
https://github.com/OpenVADL/open-vadl/issues/new
267299
""");
268300

269-
if (!ArtifactTracker.getDumpPaths().isEmpty()) {
270-
var dumpMessage = "\nBefore the crash, the following dumps were generated:";
271-
System.out.println(dumpMessage);
272-
for (var path : ArtifactTracker.getDumpPaths()) {
273-
System.out.printf("\t- %s\n", path);
274-
}
275-
System.out.println("");
276-
}
301+
printDumps("\nBefore the crash, the following dumps were generated:");
277302

278303
// Dirty hack to avoid stdout and stderr getting mixed in IntelliJ (flushing wasn't enough).
279304
try {
@@ -296,25 +321,13 @@ public Integer call() {
296321
}
297322
}
298323

299-
if (!ArtifactTracker.getArtifactPathsPaths().isEmpty()) {
300-
var artifactMessage = returnVal == 0
301-
? "\nThe following artifacts were generated:"
302-
: "\nEven though some errors occurred, the following artifacts were generated:";
303-
System.out.println(artifactMessage);
304-
for (var path : ArtifactTracker.getArtifactPathsPaths()) {
305-
System.out.printf("\t- %s\n", path);
306-
}
307-
}
324+
printArtifacts(returnVal == 0
325+
? "\nThe following artifacts were generated:"
326+
: "\nEven though some errors occurred, the following artifacts were generated:");
308327

309-
if (!ArtifactTracker.getDumpPaths().isEmpty()) {
310-
var dumpMessage = returnVal == 0
311-
? "\nThe following dumps were generated:"
312-
: "\nEven though some errors occurred, the following dumps were generated:";
313-
System.out.println(dumpMessage);
314-
for (var path : ArtifactTracker.getDumpPaths()) {
315-
System.out.printf("\t- %s\n", path);
316-
}
317-
}
328+
printDumps(returnVal == 0
329+
? "\nThe following dumps were generated:"
330+
: "\nEven though some errors occurred, the following dumps were generated:");
318331

319332
return returnVal;
320333
}

vadl/main/vadl/ast/ConstantType.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ public ConstantType(BigInteger value) {
3535
this.value = value;
3636
}
3737

38+
public ConstantType(long value) {
39+
this.value = BigInteger.valueOf(value);
40+
}
41+
3842
BigInteger getValue() {
3943
return value;
4044
}

vadl/main/vadl/ast/TypeChecker.java

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Set;
33+
import java.util.function.Supplier;
3334
import javax.annotation.Nullable;
3435
import vadl.error.Diagnostic;
3536
import vadl.types.BitsType;
@@ -58,9 +59,24 @@
5859
public class TypeChecker
5960
implements DefinitionVisitor<Void>, StatementVisitor<Void>, ExprVisitor<Void> {
6061

62+
private BranchStrategy branchStrategy = BranchStrategy.ALL;
63+
64+
/**
65+
* Describes whether all branches are checked and the result of all branches must be equal, or
66+
* if we must evaluate the condition and propergate the type from the chosen branch.
67+
*/
68+
enum BranchStrategy {
69+
ALL,
70+
ONE,
71+
}
72+
6173
//private final List<Diagnostic> errors = new ArrayList<>();
6274
private final ConstantEvaluator constantEvaluator;
6375

76+
/**
77+
* There is no point in checking a statement or definition twice, so these sets record which
78+
* nodes we already visited. For expressions, we can simply check if the type is set.
79+
*/
6480
private final Set<Statement> checkedStatements =
6581
Collections.newSetFromMap(new IdentityHashMap<>());
6682
private final Set<Definition> checkedDefinitions =
@@ -149,6 +165,23 @@ private void throwInvalidAsmCast(AsmType from, AsmType to, WithSourceLocation lo
149165
.build();
150166
}
151167

168+
/**
169+
* Execute the operation with the specified strategy set, and reset it afterwards again.
170+
*
171+
* @param strategy to be active during the execution of the operation
172+
* @param operation to be executed
173+
* @return the result of the operation
174+
*/
175+
private <T> T withBranchingStrategy(BranchStrategy strategy, Supplier<T> operation) {
176+
var oldStrategy = branchStrategy;
177+
try {
178+
branchStrategy = strategy;
179+
return operation.get();
180+
} finally {
181+
branchStrategy = oldStrategy;
182+
}
183+
}
184+
152185
/**
153186
* Tests whether a type can implicitly be cast to another.
154187
*
@@ -252,7 +285,7 @@ private static Expr wrapImplicitCast(Expr inner, Type to) {
252285
}
253286

254287
if (!canImplicitCast(innerType, to)) {
255-
if (!(innerType instanceof ConstantType innerConstTyp)) {
288+
if (!(innerType instanceof ConstantType innerConstTyp) || to instanceof ConstantType) {
256289
return inner;
257290
}
258291

@@ -311,7 +344,7 @@ private static BuiltInTable.BuiltIn getBuiltIn(String name, List<Type> argTypes)
311344

312345
@Override
313346
public Void visit(ConstantDefinition definition) {
314-
Type valType = check(definition.value);
347+
Type valType = withBranchingStrategy(BranchStrategy.ONE, () -> check(definition.value));
315348

316349
if (definition.typeLiteral == null) {
317350
// Do nothing on purpose
@@ -644,10 +677,10 @@ public Void visit(EnumerationDefinition definition) {
644677
.build();
645678
}
646679

647-
648680
int nextVal = 0;
649681
for (var entry : definition.entries) {
650682
if (entry.value != null) {
683+
check(entry.value);
651684
nextVal = constantEvaluator.eval(entry.value).value().intValueExact() + 1;
652685
continue;
653686
}
@@ -2526,6 +2559,25 @@ public Void visit(IfExpr expr) {
25262559
var thenType = check(expr.thenExpr);
25272560
var elseType = check(expr.elseExpr);
25282561

2562+
// If only one branch should be checked, directly propergate the type, and don't check whether
2563+
// the branches are compatible.
2564+
// This is intended: https://github.com/OpenVADL/open-vadl/issues/47#issuecomment-2725475475
2565+
if (branchStrategy.equals(BranchStrategy.ONE)) {
2566+
var condVal = constantEvaluator.eval(expr.condition).value().intValueExact();
2567+
expr.type = condVal == 1 ? thenType : elseType;
2568+
return null;
2569+
}
2570+
2571+
// FIXME: Fix this with bidirectional checking in the future
2572+
if (thenType instanceof ConstantType && elseType instanceof ConstantType
2573+
&& !thenType.equals(elseType)) {
2574+
throw Diagnostic.error("Type Mismatch", expr)
2575+
.description("Both branches return different constant types.")
2576+
.help("Add an explicit cast on one of the branches.")
2577+
.note("In the future this will work without a cast.")
2578+
.build();
2579+
}
2580+
25292581
// Apply general implicit casting rules after specialised once.
25302582
expr.thenExpr = wrapImplicitCast(expr.thenExpr, elseType);
25312583
thenType = expr.thenExpr.type();
@@ -2579,8 +2631,9 @@ public Void visit(MacroMatchExpr expr) {
25792631

25802632
@Override
25812633
public Void visit(MatchExpr expr) {
2634+
2635+
// Check all entities
25822636
var candidateType = check(expr.candidate);
2583-
var firstResultType = check(expr.cases.get(0).result);
25842637
for (var kase : expr.cases) {
25852638
kase.patterns.forEach(this::check);
25862639
kase.patterns.replaceAll(p -> wrapImplicitCast(p, candidateType));
@@ -2593,28 +2646,49 @@ public Void visit(MatchExpr expr) {
25932646
.note("The type of the candidate and the pattern must be the same.")
25942647
.build();
25952648
}
2649+
check(kase.result);
25962650
}
2651+
}
2652+
check(expr.defaultResult);
25972653

2598-
// Check that all branches have the same type
2599-
check(kase.result);
2654+
// If only one branch should be checked, directly propergate the type, and don't check whether
2655+
// the branches are compatible.
2656+
// This is intended: https://github.com/OpenVADL/open-vadl/issues/47#issuecomment-2725475475
2657+
if (branchStrategy.equals(BranchStrategy.ONE)) {
2658+
var candidateConstant = constantEvaluator.eval(expr.candidate);
2659+
for (var kase : expr.cases) {
2660+
if (kase.patterns.stream()
2661+
.map(constantEvaluator::eval)
2662+
.allMatch(candidateConstant::equals)) {
2663+
expr.type = kase.result.type();
2664+
return null;
2665+
}
2666+
}
2667+
2668+
expr.type = expr.defaultResult.type();
2669+
return null;
2670+
}
2671+
2672+
// Check that all branches have the same type
2673+
var firstResultType = check(expr.cases.get(0).result);
2674+
for (var kase : expr.cases) {
26002675
kase.result = wrapImplicitCast(kase.result, firstResultType);
2601-
var resultType = check(kase.result);
2676+
var resultType = kase.result.type();
26022677
if (!resultType.equals(firstResultType)) {
26032678
throw Diagnostic.error("Type Mismatch", kase.result)
2604-
.locationNote(kase.result, "All results before were of type `%s`, but this is `%s`",
2679+
.locationNote(kase.result, "All previous branches were of type `%s`, but this is `%s`",
26052680
firstResultType, resultType)
26062681
.description("All branches of a match must have the same type")
26072682
.build();
26082683
}
26092684
}
26102685

2611-
check(expr.defaultResult);
26122686
expr.defaultResult = wrapImplicitCast(expr.defaultResult, firstResultType);
26132687
var defaultResultType = expr.defaultResult.type();
26142688
if (!defaultResultType.equals(firstResultType)) {
26152689
throw Diagnostic.error("Type Mismatch", expr.defaultResult)
26162690
.locationNote(expr.defaultResult,
2617-
"All results before were of type `%s`, but this is `%s`",
2691+
"All previous branches were of type `%s`, but this is `%s`",
26182692
firstResultType, defaultResultType)
26192693
.description("All branches of a match must have the same type")
26202694
.build();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// SPDX-FileCopyrightText : © 2025 TU Wien <vadl@tuwien.ac.at>
2+
// SPDX-License-Identifier: GPL-3.0-or-later
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package vadl.utils;
18+
19+
import java.util.Objects;
20+
import javax.annotation.Nullable;
21+
22+
/**
23+
* A file to work around the "quirks" of some editors and IDE's.
24+
*/
25+
public class EditorUtils {
26+
27+
@Nullable
28+
private static Boolean isIntelliJ = null;
29+
30+
/**
31+
* Detect whether the program is currently executing inside IntelliJ.
32+
*/
33+
public static boolean isIntelliJIDE() {
34+
if (isIntelliJ == null) {
35+
isIntelliJ = Objects.requireNonNullElse(System.getenv("TERMINAL_EMULATOR"), "")
36+
.equals("JetBrains-JediTerm")
37+
|| Objects.requireNonNullElse(System.getenv("XPC_SERVICE_NAME"), "")
38+
.startsWith("application.com.jetbrains")
39+
;
40+
}
41+
42+
return isIntelliJ;
43+
}
44+
}

vadl/main/vadl/utils/SourceLocation.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package vadl.utils;
1818

19+
import static vadl.utils.EditorUtils.isIntelliJIDE;
20+
1921
import java.io.IOException;
2022
import java.net.URI;
2123
import java.nio.file.Files;
@@ -173,8 +175,7 @@ public String toIDEString() {
173175

174176
String printablePath;
175177

176-
if (Objects.requireNonNullElse(System.getenv("TERMINAL_EMULATOR"), "")
177-
.equals("JetBrains-JediTerm") || this.uri.getScheme().equals("memory")) {
178+
if (isIntelliJIDE() || this.uri.getScheme().equals("memory")) {
178179
// IntelliJ integrated terminal needs special treatment
179180
printablePath = this.uri.toString();
180181
} else {

0 commit comments

Comments
 (0)