C#: Add precedence-aware parenthesization utility#7068
Merged
knutwannheden merged 12 commits intomainfrom Mar 20, 2026
Merged
Conversation
Precedence-aware parenthesization/unwrapping utility for the C# SDK, modeled after Java's ParenthesizeVisitor and UnwrapParentheses.
- Add IsPattern, RangeExpression, SwitchExpression, WithExpression, Lambda to GetPrecedence dispatch and visitor methods - Handle pattern combinator (and/or/not) precedence as separate sub-domain - Add right-associativity handling for ?? in NeedsParentheses - Expand IsUnwrappable structural parent list (ForLoop, FixedStatement, etc.) - Include C#-specific types in MaybeParenthesize fast-exit check - Document IsAssociative return values - Fix Parenthesize helper to use expr.WithPrefix() not J.SetPrefix()
Five tasks: CSharpPrecedences, CSharpParenthesizeVisitor, CSharpUnwrapParentheses, template integration, final verification.
The C# parser models ?? as Ternary with a NullCoalescing marker, not as CsBinary.NullCoalescing. Update GetPrecedence, IsAssociative, IsRightAssociative, and tests to handle both representations.
…Apply Auto-parenthesizes expression substitutions after template application, matching Java's JavaTemplate integration pattern.
- Unary inside different unary no longer gets unnecessary parens: all prefix unary operators are at the same precedence level (13), so -(~x), ~(!x), etc. are always unambiguous - Ternary inside ternary now gets parens: C# parses a ? b : c ? d : e right-to-left, so the left ternary as a condition needs wrapping
- TypeCast no longer over-parenthesized: uses precedence-based logic instead of unconditional wrapping (prec 13 binds tighter than binary) - Add VisitLambda override for recursive visitor (prec -2 like assignment) - Add Lambda to IsPattern parent check in NeedsParenthesesInContext - IsRightAssociative check now verifies both child and parent - MaybeWrapAssignment now checks IsPattern parent - NeedsParentheses parent parameter typed as Expression (was object)
Demonstrates that CSharpTemplate.Apply auto-parenthesizes when a substitution produces a lower-precedence expression in a higher-precedence context: replacing + with - inside a * context preserves the parens.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CSharpPrecedences(internal): C# operator precedence table derived from the language spec, withGetPrecedence,NeedsParentheses,Parenthesize, andIsAssociativemethodsCSharpParenthesizeVisitor: visitor that adds parentheses where needed based on operator precedence and context, with staticMaybeParenthesizeentry point for template integrationCSharpUnwrapParentheses: visitor that safely removes parentheses from a specific node when semantics are preservedMaybeParenthesizeintoCSharpTemplate.Applyso template substitutions auto-parenthesizeThis eliminates the need for recipe authors to manually decide when parentheses are needed. Currently 8+ recipes in recipes-csharp re-implement their own
NeedsParentheseschecks with inconsistent logic.Test plan
CSharpPrecedencesTests)CSharpParenthesizeVisitorTests)CSharpUnwrapParenthesesTests):rewrite-csharp:csharpBuildsucceedsInvertIf,SimplifyBooleanComparison) to use the new utility