diff --git a/src/Draco.Compiler/Internal/FlowAnalysis/DecisionTree.cs b/src/Draco.Compiler/Internal/FlowAnalysis/DecisionTree.cs
new file mode 100644
index 000000000..0a8c5ece5
--- /dev/null
+++ b/src/Draco.Compiler/Internal/FlowAnalysis/DecisionTree.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Draco.Compiler.Internal.BoundTree;
+
+namespace Draco.Compiler.Internal.FlowAnalysis;
+
+///
+/// Represents a decision tree computed for a match expression.
+/// It helps guiding the rewriter to generate code as well as reporting things like redundant branches or
+/// missing cases.
+///
+/// The action type being performed, when a branch is matched.
+internal sealed class DecisionTree
+{
+ ///
+ /// A single arm in the match construct.
+ ///
+ /// The matched pattern.
+ /// The taken action if matches.
+ public readonly record struct Arm(BoundPattern Pattern, TAction Action);
+
+ ///
+ /// Represents a redundant arm.
+ ///
+ /// The arm that covers the one already.
+ /// The arm that is redundant, because already
+ /// matches.
+ public readonly record struct Redundance(Arm CoveredBy, Arm Redundant);
+
+ ///
+ /// A single node in the decision tree.
+ ///
+ public abstract class Node
+ {
+ ///
+ /// The parent node of this one.
+ ///
+ public virtual Node? Parent => null;
+
+ ///
+ /// The index of the row this node originated from in the parent.
+ ///
+ public virtual int? ParentRowIndex => null;
+
+ ///
+ /// True, if this is an action node, meaning that there is a match.
+ ///
+ public virtual bool IsAction => false;
+
+ ///
+ /// The action that's associated with the node.
+ ///
+ [MemberNotNullWhen(true, nameof(IsAction))]
+ public virtual TAction? Action => default;
+
+ ///
+ /// True, if this is a failure node.
+ ///
+ public virtual bool IsFail => false;
+
+ ///
+ /// The counterexample of this node, if it's a failure node and a counterexample could be produced.
+ /// The counterexample in this case means a pattern that was not covered.
+ ///
+ public virtual BoundPattern? Counterexample => null;
+
+ ///
+ /// The expression being matched on, if any.
+ ///
+ public virtual BoundExpression? MatchedOn => null;
+
+ ///
+ /// The child nodes of this one, associated to the pattern to match to take the route to the child.
+ ///
+ public virtual ImmutableArray> Children =>
+ ImmutableArray>.Empty;
+ }
+
+ ///
+ /// Builds a decision tree from the given data.
+ ///
+ /// The matched value.
+ /// The arms of the match.
+ /// The constructed decision tree.
+ public static DecisionTree Build(BoundExpression matchedValue, ImmutableArray arms) =>
+ throw new NotImplementedException();
+
+ ///
+ /// Stringifies the given to a user-readable format.
+ ///
+ /// The pattern to stringify.
+ /// The user-readable form of .
+ public static string ToDisplayString(BoundPattern pattern) => pattern switch
+ {
+ _ => throw new ArgumentOutOfRangeException(nameof(pattern)),
+ };
+
+ ///
+ /// The matched value.
+ ///
+ public BoundExpression MatchedValue { get; }
+
+ ///
+ /// The arms of the root construct.
+ ///
+ public ImmutableArray Arms { get; }
+
+ ///
+ /// The root node of this tree.
+ ///
+ public Node Root { get; }
+
+ ///
+ /// All redundancies in the tree.
+ ///
+ public ImmutableArray Redundancies { get; }
+
+ ///
+ /// True, if this tree is exhaustive.
+ ///
+ public bool IsExhaustive { get; }
+
+ ///
+ /// An example of an uncovered pattern, if any.
+ ///
+ public BoundPattern? UncoveredExample { get; }
+
+ private DecisionTree()
+ {
+ // TODO
+ }
+}
diff --git a/src/Draco.Compiler/Internal/FlowAnalysis/Domain/ValueDomain.cs b/src/Draco.Compiler/Internal/FlowAnalysis/Domain/ValueDomain.cs
index fd02dc797..32fcb909e 100644
--- a/src/Draco.Compiler/Internal/FlowAnalysis/Domain/ValueDomain.cs
+++ b/src/Draco.Compiler/Internal/FlowAnalysis/Domain/ValueDomain.cs
@@ -26,16 +26,6 @@ public static ValueDomain CreateDomain(IntrinsicSymbols intrinsics, TypeSymbol t
throw new ArgumentOutOfRangeException(nameof(type));
}
- ///
- /// Stringifies the given to a user-readable format.
- ///
- /// The pattern to stringify.
- /// The user-readable form of .
- public static string ToDisplayString(BoundPattern pattern) => pattern switch
- {
- _ => throw new ArgumentOutOfRangeException(nameof(pattern)),
- };
-
///
/// True, if this domain has been emptied.
///