Skip to content

Supersede: Support the tractable C# patterns when converting to VB - #1290

Merged
GrahamTheCoder merged 10 commits into
icsharpcode:masterfrom
GrahamTheCoder:claude/bold-brown-yro47p
Oct 19, 2026
Merged

GrahamTheCoder merged 10 commits into
icsharpcode:masterfrom
GrahamTheCoder:claude/bold-brown-yro47p

Conversation

@GrahamTheCoder

Copy link
Copy Markdown
Member

No description provided.

gherards99 and others added 10 commits September 4, 2026 15:01
VisitIsPatternExpression handled only declaration and constant patterns and
threw for everything else, so most pattern matching failed to convert. It is
now split into a recursive ConvertPattern that also covers:

* type patterns, as TypeOf x Is T
* negated patterns, flipped in place to TypeOf x IsNot T or TryCast(x, T) Is
  Nothing rather than wrapped in Not
* relational patterns, as plain comparisons

A constant pattern whose expression binds to a type is a type test, not a
comparison, and was being emitted as Is, which compares references in VB. It
now emits TypeOf x Is T. A constant compared by value went the same way and did
not compile at all; it now uses = for a value type and Equals for anything
else, which is what the pattern means when the operand is an Object.

"x is not T v" declares v just as "x is T v" does, so the hoisting in
CommonConversions follows the negation to find it.

Also here, both smaller and separable:

* ImplicitObjectCreationExpression had no visitor, so target-typed new threw.
  The type the compiler settled on is written out instead.
* GetSymbolInfo(node.Left).Symbol was dereferenced without a null check while
  converting an event subscription, so an unresolved symbol crashed the whole
  file rather than degrading.
* A failed expression conversion comes back as an empty statement carrying its
  report as trivia. A return statement dropped both, losing the value and the
  diagnostic together; the report now travels with the statement.

Property patterns and ??= are deliberately left out: both need the tested
expression evaluated once, which needs a temporary rather than repeating it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"n is 5" on an int? became "n = 5", which lifts to Boolean? and throws
when narrowed to Boolean while n is Nothing. Equals boxes the value and
answers False, matching C#.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
"o is > 0" on an Object also tests that o is an int; "o > 0" late-binds
instead, answering True for "5" and throwing for "abc". On an int? the
comparison lifts to Boolean? and throws when narrowed from Nothing.
Report these as not converted rather than silently changing behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
"x is not null" became "Not x Is Nothing"; emit "x IsNot Nothing" as the
negated declaration pattern already does for its own Nothing test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
Covers the return trivia change: the error comment survives, Return stays
on its own line, and the original comments appear once each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
On Windows the VB error comment ended in an empty "''' " line, and the
line break ReturnStatement added after it was dropped by LineTriviaMapper,
which only kept annotated leading trivia. The Return was commented out.

Build the comment by splitting on either line ending so every line is
prefixed on every platform (on Linux the stack trace lines were left
uncommented) with no trailing empty line, and keep the line break that
follows a leading error comment when mapping trivia.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
In C# "is" binds tighter than ==, but VB's comparison operators, Is
included, share one precedence level and group left to right, so
"flag == x is 5" became "flag = x = 5", read as "(flag = x) = 5".
Simplification still removes the parentheses where they are redundant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
Equals compares boxed types, so "l is 5" on a long? became
"Equals(l, 5)", which is False even when l is 5. "=" instead lifts to
Boolean? and throws when narrowed from Nothing. Neither matches C#, so
report the pattern rather than emit silently wrong code. Null tests on
a nullable are unaffected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
"d is double.NaN" matches NaN, but became "d = Double.NaN", which is
always False. Emit Double.IsNaN(d) / Single.IsNaN(f) instead. On Object
the existing Equals path already answers True for a boxed NaN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
Property patterns, which apply a nested pattern to a member, are not
supported; the type travels along to pick the comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxCQrzahEZZWcfrGNSR4Sc
@GrahamTheCoder
GrahamTheCoder merged commit 7d9ab4b into icsharpcode:master Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants