3030import java .util .List ;
3131import java .util .Map ;
3232import java .util .Set ;
33+ import java .util .function .Supplier ;
3334import javax .annotation .Nullable ;
3435import vadl .error .Diagnostic ;
3536import vadl .types .BitsType ;
5859public 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 ();
0 commit comments