Skip to content

Commit 30c146e

Browse files
antonsyndclaude
andcommitted
feat(codegen): emit correct C# patterns for Optional/Result match (#482)
Generate positional deconstruction patterns for Optional<T> and Result<T, E> match arms using their Deconstruct methods: Some(v) -> case (true, var v): None() -> case (false, _): Ok(v) -> case (true, var v, _): Err(e) -> case (false, _, var e): Also adds a default throw for exhaustive Optional/Result matches to satisfy the C# compiler's definite return analysis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a0db4b5 commit 30c146e

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

src/Sharpy.Compiler/CodeGen/RoslynEmitter.Patterns.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,21 @@ private PatternSyntax GenerateMatchPattern(
233233

234234
case LiteralPattern literal:
235235
{
236+
// Handle None()/None patterns on Optional scrutinees
237+
var litUnionCase = _context.SemanticInfo?.GetPatternUnionCase(literal);
238+
if (litUnionCase?.Name == "None" && scrutineeType is OptionalType)
239+
{
240+
// Optional<T>.Deconstruct(out bool hasValue, out T value)
241+
// None → (false, _)
242+
return RecursivePattern()
243+
.WithPositionalPatternClause(
244+
PositionalPatternClause(SeparatedList(new[]
245+
{
246+
Subpattern(ConstantPattern(LiteralExpression(SyntaxKind.FalseLiteralExpression))),
247+
Subpattern(VarPattern(DiscardDesignation()))
248+
})));
249+
}
250+
236251
var literalExpr = GenerateExpression(literal.Literal);
237252
return ConstantPattern(literalExpr);
238253
}
@@ -402,6 +417,12 @@ a is MemberAccessPattern ma
402417
var unionCase = _context.SemanticInfo?.GetPatternUnionCase(positionalPattern);
403418
if (unionCase != null)
404419
{
420+
// Handle Optional/Result synthetic union cases via Deconstruct
421+
var optResultPattern = TryGenerateOptionalResultPattern(
422+
positionalPattern, unionCase, scrutineeType, memberGuards, ref matchVarCounter);
423+
if (optResultPattern != null)
424+
return optResultPattern;
425+
405426
return GenerateUnionCasePositionalPattern(
406427
positionalPattern, unionCase, scrutineeType, memberGuards, ref matchVarCounter);
407428
}
@@ -475,6 +496,93 @@ a is MemberAccessPattern ma
475496
}
476497
}
477498

499+
/// <summary>
500+
/// Generates a C# positional deconstruction pattern for Optional/Result synthetic union cases.
501+
/// Returns null if the union case is not from a synthetic Optional/Result union.
502+
///
503+
/// Optional[T].Deconstruct(out bool hasValue, out T value):
504+
/// Some(v) → (true, var v)
505+
/// None() → (false, _)
506+
///
507+
/// Result[T, E].Deconstruct(out bool isOk, out T value, out E error):
508+
/// Ok(v) → (true, var v, _)
509+
/// Err(e) → (false, _, var e)
510+
/// </summary>
511+
private PatternSyntax? TryGenerateOptionalResultPattern(
512+
PositionalPattern positionalPattern,
513+
TypeSymbol unionCaseSymbol,
514+
SemanticType? scrutineeType,
515+
List<ExpressionSyntax> memberGuards,
516+
ref int matchVarCounter)
517+
{
518+
if (scrutineeType is OptionalType && unionCaseSymbol.Name == "Some")
519+
{
520+
// Some(v) → (true, var v)
521+
var subPatterns = new List<SubpatternSyntax>
522+
{
523+
Subpattern(ConstantPattern(LiteralExpression(SyntaxKind.TrueLiteralExpression)))
524+
};
525+
if (positionalPattern.Elements.Length == 1)
526+
{
527+
subPatterns.Add(Subpattern(GenerateMatchPattern(
528+
positionalPattern.Elements[0], memberGuards, ref matchVarCounter)));
529+
}
530+
else
531+
{
532+
subPatterns.Add(Subpattern(VarPattern(DiscardDesignation())));
533+
}
534+
return RecursivePattern()
535+
.WithPositionalPatternClause(
536+
PositionalPatternClause(SeparatedList(subPatterns)));
537+
}
538+
539+
if (scrutineeType is ResultType && unionCaseSymbol.Name == "Ok")
540+
{
541+
// Ok(v) → (true, var v, _)
542+
var subPatterns = new List<SubpatternSyntax>
543+
{
544+
Subpattern(ConstantPattern(LiteralExpression(SyntaxKind.TrueLiteralExpression)))
545+
};
546+
if (positionalPattern.Elements.Length == 1)
547+
{
548+
subPatterns.Add(Subpattern(GenerateMatchPattern(
549+
positionalPattern.Elements[0], memberGuards, ref matchVarCounter)));
550+
}
551+
else
552+
{
553+
subPatterns.Add(Subpattern(VarPattern(DiscardDesignation())));
554+
}
555+
subPatterns.Add(Subpattern(VarPattern(DiscardDesignation())));
556+
return RecursivePattern()
557+
.WithPositionalPatternClause(
558+
PositionalPatternClause(SeparatedList(subPatterns)));
559+
}
560+
561+
if (scrutineeType is ResultType && unionCaseSymbol.Name == "Err")
562+
{
563+
// Err(e) → (false, _, var e)
564+
var subPatterns = new List<SubpatternSyntax>
565+
{
566+
Subpattern(ConstantPattern(LiteralExpression(SyntaxKind.FalseLiteralExpression))),
567+
Subpattern(VarPattern(DiscardDesignation()))
568+
};
569+
if (positionalPattern.Elements.Length == 1)
570+
{
571+
subPatterns.Add(Subpattern(GenerateMatchPattern(
572+
positionalPattern.Elements[0], memberGuards, ref matchVarCounter)));
573+
}
574+
else
575+
{
576+
subPatterns.Add(Subpattern(VarPattern(DiscardDesignation())));
577+
}
578+
return RecursivePattern()
579+
.WithPositionalPatternClause(
580+
PositionalPatternClause(SeparatedList(subPatterns)));
581+
}
582+
583+
return null;
584+
}
585+
478586
/// <summary>
479587
/// Generates a C# positional pattern for a union case with fields.
480588
/// Emits: UnionName{TypeArgs}.CaseName(var field1, var field2)

0 commit comments

Comments
 (0)