Repository navigation
Recognise MyBase.New/Me.New/MyClass.New constructor initializers with any casing and without parentheses - #11
Closed
GrahamTheCoder wants to merge 11 commits into
Closed
GrahamTheCoder wants to merge 11 commits into
GrahamTheCoder wants to merge 11 commits into
Conversation
… and without parentheses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
…or calls The three conversion tests only differed by the blank line the harness leaves where usings would go (as in the other tests in this file). Adds coverage for Me.New/MyClass.New with odd casing (this(...)), MyBase.New without parentheses followed by further statements, a first statement that is not a constructor call but mentions New, and a self-verifying test that runs each form and checks which constructors ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
…yBase.New Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
…ment Pure refactor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
"Call MyBase.New(x)" (and Me.New/MyClass.New) as a constructor's first statement is a CallStatementSyntax rather than an ExpressionStatementSyntax, so it was converted to "base.ctor(x);" in the body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Owner
Author
|
Will merge as icsharpcode#1313 once edge cases around |
Pure refactor. The helper now only returns a result when the first statement really calls another constructor, and arguments are only converted in that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
"Me!New()" means Me("New")(), invoking the delegate returned by the default
property. It was converted to ": this()", a self-call (CS0516), or for
"Call Me!New()" a silent behaviour change. Exact casing without Call was
already affected on master; the case-insensitive and Call support widened it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg
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.
Fixes icsharpcode#1301
Summary
Root cause:
DeclarationNodeVisitor.VisitConstructorBlocktreats a constructor's first statement as a call to another constructor when the member name's text isNew. InMyBase.New,Newis lexed as an identifier, so this text check is what does the work. It missed three forms, and each was left in the body asbase.ctor(...);(CS0117/CS7036):HasMatchingTextcompared case-sensitively, soMyBase.new(value)andMYBASE.NEW(value)weren't recognised.MyBase.Newhas noInvocationExpressionSyntax, so it was never looked at.Callkeyword:Call MyBase.New(value)is aCallStatementSyntax, not anExpressionStatementSyntax, so it was never looked at either.Fix:
HasMatchingText(CodeConverter/Util/SyntaxTokenExtensions.cs) now compares withOrdinalIgnoreCase:SyntaxKinds.GetConstructorInitializerCalldecides whether the first statement is a constructor call. It was extracted in pure-refactor commits. It accepts:Callstatement,.) whose name isNewand whose target isMyBase(→: base(...)) orMe/MyClass(→: this(...)).Me!New()(meaningMe("New")()) is no longer treated as a constructor call:Me!New()already became: this(), a self-call (CS0516).Callsupport would otherwise have widened that, andCall Me!New()would have compiled with a silent behaviour change.Reviewer attention
GetSymbolInforesolving toMethodKind.Constructor):X.Newas a statement is only valid whenXisMyBase,MeorMyClass, so the text check is exact.MyBase.[New](...)is not a constructor call: VB reports BC30456. The check ignores it correctly because the token text includes the brackets.Pre-existing, not fixed
: base(ref ph<guid>)(CS0103), for any casing.Sub New(ByRef r As Integer), derivedMyBase.New(value + 1)orMyBase.New(SharedProp).base.ctor(...)in that case, so it was broken either way.Me!New()converts tothis["New"];(CS0201), which drops the invocation. That's why its test only asserts that no constructor initializer is produced.Review
The blind review converged after 3 rounds:
Callform and some missing tests.Me!Newdictionary-access bug and two quality points.Declined:
:statement separators and line continuations. The output was identical in every case, so the label stands.Call: kept, because it states whyCallStatementSyntaxis handled.Tests
Tests/CSharp/MemberTests/ConstructorTests.cshas 10 new conversion tests. The first 9 compare against output that compiles (the harness reports any compile errors).MyBaseNewWithNonCanonicalCasingAsync: the exact repro from the issue.MyBaseNewWithUpperCasingAsyncMyBaseNewWithoutParenthesesAsync:MyBase.nEwfollowed by another statement.MyBaseNewWithoutParenthesesKeepsCommentAsync: the trailing comment is kept.MeAndMyClassNewWithNonCanonicalCasingAsync:Me.new(...)andmyclass.NEW(...)become: this(...).CallStatementConstructorInitializersWithNonCanonicalCasingAsync:Call MyBase.new(...),call Me.NEW(...)andCALL myclass.New(...).MyBaseNewWithNonCanonicalCasingNamedAndOmittedArgumentsAsync: named arguments, and an omitted optional argument.FirstStatementNotConstructorCallAsync:Me.Newish(New Base(value))stays in the body.CallStatementNonConstructorWithoutParenthesesAsync:Call Me.Foostays in the body.DictionaryAccessNamedNewIsNotConstructorCallAsync:Me!new()andCall Me!New()produce no constructor initializer. Because of the pre-existing body bug above, its output doesn't compile, so the test asserts on parts of the output rather than the whole text.Tests/TestData/SelfVerifyingTests/VBToCS/InheritanceTests.vbadds the self-verifying testConstructorInitializersWithNonCanonicalCasing:Me/MyClasschain andCallforms.Each behaviour test was seen failing before its fix:
base.ctor(...)/CS0117, or: this()forMe!New.Full suite vs master baseline
FullyQualifiedName~CSharprun on the shared machine hung inMultiFileSolutionAndProjectTests.ConvertVbLibraryOnlyAsync. That test passes on its own on both this branch and master. The hang came from contention on the shared machine, not from this change.Coverage of changed lines
All changed lines are covered, except the fallback for
<other>.Newwhere the target is notMyBase/Me/MyClass, which is invalid in VB. That fallback was the uncovered finalelsebefore the refactor.🤖 Generated with Claude Code
https://claude.ai/code/session_019FEr8FVAmHV9bscZ82CChg