Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,23 @@ dotnet test -c Debug -f net9.0 src/DelegateDecompiler.EntityFrameworkCore9.Tests
Use `-p:DisableGitVersionTask=true` flag to avoid build issues if GitVersion is not set up.

**Development Guidelines:**
- Must use test-first approach when adding new features or fixing bugs
- Ensure core unit tests pass: `src/DelegateDecompiler.Tests` and `src/DelegateDecompiler.Tests.VB` should have no failures
- Run unit tests frequently during development to catch regressions early

**Test-First Development (MANDATORY):**
- **ALWAYS START FROM TESTS** - Write test cases that define expected behavior BEFORE implementing
- **TESTS ARE THE SOURCE OF TRUTH** - Never change tests to match broken code
- If tests fail: fix implementation, not tests
- Tests define the API contract and expected behavior

**Required Workflow:**
1. Write/examine tests to understand what needs to be built
2. Run tests to see current failures
3. Implement code to make tests pass
4. NEVER modify tests to match implementation bugs

**Core Requirements:**
- All tests in `src/DelegateDecompiler.Tests` and `src/DelegateDecompiler.Tests.VB` must pass
- Run tests frequently during development
- Use `-p:DisableGitVersionTask=true` if GitVersion issues occur

## Project Structure

Expand Down
154 changes: 154 additions & 0 deletions src/DelegateDecompiler.Tests/TryCatchTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System;
using System.Linq.Expressions;
using NUnit.Framework;

namespace DelegateDecompiler.Tests
{
[TestFixture, Ignore("Not supported yet")]
public class TryCatchTests : DecompilerTestsBase
{
[Test]
public void TryCatchSimple()
{
Func<int, int> compiled = x =>
{
try
{
return x * 2;
}
catch (ArgumentException)
{
return -1;
}
};

var x = Expression.Parameter(typeof(int), "x");
var expected = Expression.Lambda<Func<int, int>>(
Expression.TryCatch(
Expression.Multiply(x, Expression.Constant(2)),
Expression.Catch(typeof(ArgumentException), Expression.Constant(-1))
), x);

Test(compiled, expected);
}

[Test]
public void TryCatchWithSpecificException()
{
Func<int, int> compiled = x =>
{
try
{
return x * 2;
}
catch (FormatException)
{
return 0;
}
};

var x = Expression.Parameter(typeof(int), "x");
var expected = Expression.Lambda<Func<int, int>>(
Expression.TryCatch(
Expression.Multiply(x, Expression.Constant(2)),
Expression.Catch(typeof(FormatException), Expression.Constant(0))
), x);

Test(compiled, expected);
}

[Test]
public void TryCatchFinally()
{
Func<int, int> compiled = x =>
{
try
{
return x * 2;
}
catch (ArgumentException)
{
return -1;
}
finally
{
Console.WriteLine("Processing");
}
};

var param = Expression.Parameter(typeof(int), "x");
var expected = Expression.Lambda<Func<int, int>>(
Expression.TryCatchFinally(
Expression.Multiply(param, Expression.Constant(2)),
Expression.Block(
Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) })!,
Expression.Constant("Processing"))
),
Expression.Catch(typeof(ArgumentException), Expression.Constant(-1))
),
param);

Test(compiled, expected);
}

[Test]
public void TryFinallyOnly()
{
Func<int, int> compiled = x =>
{
try
{
return x * 2;
}
finally
{
Console.WriteLine($"Finally executed for {x}");
}
};

var param = Expression.Parameter(typeof(int), "x");
var expected = Expression.Lambda<Func<int, int>>(
Expression.TryFinally(
Expression.Multiply(param, Expression.Constant(2)),
Expression.Block(
Expression.Call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }),
Expression.Call(typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object) }),
Expression.Constant("Finally executed for {0}"),
Expression.Convert(param, typeof(object))))
)
),
param);

Test(compiled, expected);
}

[Test]
public void TryCatchWithExceptionVariable()
{
Func<string, string> compiled = s =>
{
try
{
return s.ToUpper();
}
catch (ArgumentNullException ex)
{
return ex.Message;
}
};

var s = Expression.Parameter(typeof(string), "s");
var ex = Expression.Parameter(typeof(ArgumentNullException), "ex");
var expected = Expression.Lambda<Func<string, string>>(
Expression.TryCatch(
Expression.Call(s, typeof(string).GetMethod("ToUpper", Type.EmptyTypes)!),
Expression.Catch(
ex,
Expression.Property(ex, nameof(Exception.Message))
)
), s);

Test(compiled, expected);
}
}
}
5 changes: 3 additions & 2 deletions src/DelegateDecompiler/Processor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ void ProcessBlock(ProcessorState state, Block block, Block endBlock = null)

void ProcessNextBlock(ProcessorState state, Block block, Block endBlock)
{
switch (block.Successors.Count)
var successors = block.Successors.Where(b => !b.IsException).ToList();
switch (successors.Count)
{
case 0:
break;
case 1:
ProcessBlock(state, block.Successors[0].To, endBlock);
ProcessBlock(state, successors[0].To, endBlock);
break;
case 2:
ProcessConditionalBranch(block, state, endBlock);
Expand Down