Skip to content

Commit

Permalink
Reporting tree analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
LPeter1997 committed Oct 5, 2023
1 parent c24965e commit 5b65465
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 3 deletions.
20 changes: 17 additions & 3 deletions src/Draco.Compiler/Internal/FlowAnalysis/DecisionTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ public static DecisionTree<TAction> Build(IntrinsicSymbols intrinsicSymbols, Bou
var tree = new DecisionTree<TAction>(intrinsicSymbols, root);
// Build it
tree.Build(root);
// Cleanup
tree.CleanUpRedundancies();
// Done
return tree;
}
Expand Down Expand Up @@ -292,6 +294,7 @@ private SpecializationComparer()
public BoundPattern? UnhandledExample => throw new NotImplementedException();

private readonly IntrinsicSymbols intrinsicSymbols;
private readonly HashSet<TAction> usedActions = new();

private DecisionTree(IntrinsicSymbols intrinsicSymbols, Node root)
{
Expand All @@ -313,11 +316,14 @@ private void Build(Node node)
if (node.PatternMatrix[0].All(MatchesEverything))
{
// This is a succeeding node, set the performed action
node.ActionArm = node.ActionArms[0];
// The remaining ones are redundant
var takenAction = node.ActionArms[0];
node.ActionArm = takenAction;
this.usedActions.Add(takenAction.Action);
// The remaining ones are registered as redundant
for (var i = 1; i < node.PatternMatrix.Count; ++i)
{
this.redundancies.Add(new(node.ActionArms[0].Action, node.ActionArms[i].Action));
// Note, that this is not true redundancy, later cases can still use these
this.redundancies.Add(new(takenAction.Action, node.ActionArms[i].Action));
}
return;
}
Expand Down Expand Up @@ -361,6 +367,14 @@ private void Build(Node node)
foreach (var (_, child) in node.Children) this.Build((Node)child);
}

/// <summary>
/// Cleans up the redundancy info, removing false positives.
/// </summary>
private void CleanUpRedundancies()
{
this.redundancies.RemoveWhere(r => this.usedActions.Contains(r.Uselesss));
}

/// <summary>
/// Specializes the given node.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions src/Draco.Compiler/Internal/FlowAnalysis/FlowAnalysisErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,22 @@ internal static class FlowAnalysisErrors
severity: DiagnosticSeverity.Error,
format: "the immutable variable {0} can not be assigned to, it is read only",
code: Code(5));

/// <summary>
/// A match expression is non-exhaustive.
/// </summary>
public static readonly DiagnosticTemplate NonExhaustiveMatchExpression = DiagnosticTemplate.Create(
title: "match expression is not exhaustive",
severity: DiagnosticSeverity.Warning,
format: "the match expression does not cover all possible values",
code: Code(6));

/// <summary>
/// A match expression arm is already covered by another.
/// </summary>
public static readonly DiagnosticTemplate MatchPatternAlreadyHandled = DiagnosticTemplate.Create(
title: "pattern already handled",
severity: DiagnosticSeverity.Warning,
format: "the pattern is already handled by a previous case",
code: Code(7));
}
67 changes: 67 additions & 0 deletions src/Draco.Compiler/Internal/FlowAnalysis/MatchExhaustiveness.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Collections.Immutable;
using System.Linq;
using Draco.Compiler.Api.Diagnostics;
using Draco.Compiler.Internal.BoundTree;
using Draco.Compiler.Internal.Diagnostics;
using Draco.Compiler.Internal.Symbols;
using Draco.Compiler.Internal.Symbols.Source;
using Draco.Compiler.Internal.Symbols.Synthetized;

namespace Draco.Compiler.Internal.FlowAnalysis;

/// <summary>
/// Checks, if match expressions are exhaustive.
/// </summary>
internal sealed class MatchExhaustiveness : BoundTreeVisitor
{
public static void Analyze(SourceGlobalSymbol global, IntrinsicSymbols intrinsicSymbols, DiagnosticBag diagnostics)
{
var pass = new MatchExhaustiveness(intrinsicSymbols, diagnostics);
global.Value?.Accept(pass);
}

public static void Analyze(SourceFunctionSymbol function, IntrinsicSymbols intrinsicSymbols, DiagnosticBag diagnostics)
{
var pass = new MatchExhaustiveness(intrinsicSymbols, diagnostics);
function.Body.Accept(pass);
}

private readonly IntrinsicSymbols intrinsicSymbols;
private readonly DiagnosticBag diagnostics;

public MatchExhaustiveness(IntrinsicSymbols intrinsicSymbols, DiagnosticBag diagnostics)
{
this.intrinsicSymbols = intrinsicSymbols;
this.diagnostics = diagnostics;
}

public override void VisitMatchExpression(BoundMatchExpression node)
{
base.VisitMatchExpression(node);

// We build up the relevant arms
var arms = node.MatchArms
.Select(a => DecisionTree.Arm(a.Pattern, a.Guard, a))
.ToImmutableArray();
// From that we build the decision tree
var decisionTree = DecisionTree.Build(this.intrinsicSymbols, node.MatchedValue, arms);

if (!decisionTree.IsExhaustive)
{
// TODO: Add example
// Report
this.diagnostics.Add(Diagnostic.Create(
template: FlowAnalysisErrors.NonExhaustiveMatchExpression,
location: node.Syntax?.Location));
}

foreach (var (covers, redundant) in decisionTree.Redundancies)
{
// TODO: Add prev case
// Report
this.diagnostics.Add(Diagnostic.Create(
template: FlowAnalysisErrors.MatchPatternAlreadyHandled,
location: redundant.Syntax?.Location));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public void Bind(IBinderProvider binderProvider)
ReturnsOnAllPaths.Analyze(this, binderProvider.DiagnosticBag);
DefiniteAssignment.Analyze(body, binderProvider.DiagnosticBag);
ValAssignment.Analyze(this, binderProvider.DiagnosticBag);
MatchExhaustiveness.Analyze(this, this.DeclaringCompilation!.IntrinsicSymbols, binderProvider.DiagnosticBag);
}

private void CheckForSameParameterOverloads(IBinderProvider binderProvider)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public void Bind(IBinderProvider binderProvider)
// Flow analysis
if (value is not null) DefiniteAssignment.Analyze(value, binderProvider.DiagnosticBag);
ValAssignment.Analyze(this, binderProvider.DiagnosticBag);
MatchExhaustiveness.Analyze(this, this.DeclaringCompilation!.IntrinsicSymbols, binderProvider.DiagnosticBag);
}

private (TypeSymbol Type, BoundExpression? Value) BindTypeAndValueIfNeeded(IBinderProvider binderProvider)
Expand Down

0 comments on commit 5b65465

Please sign in to comment.