Fix #3954: name the entry point of a top-level program 'Main' - #3955
Merged
Conversation
Roslyn compiles top-level statements into a synthesized Program class whose entry point is called '<Main>$', a name that cannot be declared in C#. Decompiled output kept it verbatim (escaped to _003CMain_003E_0024 when exporting a project), and since C# accepts only a method called 'Main' as an entry point, the exported executable did not compile (CS5001). Give that method the name 'Main'. Per the decision recorded in #829 we do not reconstruct top-level statements, so this is the level of support the output needs to compile. An async top-level program needs the name in a different place: it compiles to '<Main>$' holding the statements plus a '<Main>' entry point that only awaits it. That wrapper carries the .entrypoint marker but is hidden from the output, so the name goes to the method it awaits instead - unless AsyncAwait is off, when the wrapper is emitted and keeps the name itself. Assisted-by: Claude:claude-fable-5:Claude Code
siegfriedpammer
force-pushed
the
fix-3954-entrypoint-name
branch
from
August 5, 2026 10:12
03fd48e to
a18654a
Compare
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 #3954.
Roslyn compiles top-level statements into a synthesized
Programclass whose entry point is called<Main>$— a name that cannot be declared in C#. The decompiled output kept it verbatim (escaped to_003CMain_003E_0024when exporting a project), and since C# accepts only a method calledMainas an entry point, the exported executable did not compile:Change:
CSharpDecompiler.DoDecompile(IMethod)gives that method the nameMain, next to the existing explicit-interface-implementation rename. The newIsEntryPointhelper resolves the entry point fromCorHeader.EntryPointTokenOrRelativeVirtualAddressviaMetadataTokenHelpers.EntityHandleOrNiland requires aMethodDefinitionhandle — per ECMA-335 II.25.3.3 that field is a MethodDef or a File token, the latter when the entry point lives in another module of a multi-module assembly. Per the decision recorded in #829 top-level statements are not reconstructed — this is just the level of support the output needs in order to compile.Async top-level programs need the name in a different place. They compile to
<Main>$holding the statements plus a<Main>wrapper that only awaits it. The wrapper carries the.entrypointmarker, but it is hidden from the output (MemberIsHiddenviaAsyncAwaitDecompiler.IsCompilerGeneratedMainMethod), so renaming strictly the method marked.entrypointwould leave everyawait-using program emitting<Main>$and still not compiling.IsEntryPointtherefore also maps the hidden wrapper to the method it awaits, guarded bysettings.AsyncAwait— the same condition under which the wrapper is hidden — so the two can never both end up namedMain. A classicstatic async Task Mainis unaffected: its wrapper is hidden and its own name is already speakable.Not a regression: the same
<Main>$output comes out of release/10.1, v9.1, and the NuGetilspycmd8.2.0 (2023) and 7.2.1 (2022) — the case has simply never been handled since top-level statements arrived in C# 9.Tests:
Ugly/TopLevelProgramandUgly/TopLevelProgramAsync— the input is valid C#, and the.Expected.csrecords the output we intentionally do not reproduce faithfully, which is what the Ugly suite is for. They useRunrather thanRunForLibrary, because top-level statements require an executable and the entry-point token has to be set. All 8 configurations are red before the change (emitting_003CMain_003E_0024) and green after; full decompiler suite 3340 tests, 0 failures.Beyond the fixtures,
dotnet new consoleand anawait-using variant were decompiled with the rebuiltilspycmd: both exported projects now compile and run, where the first previously failed with CS5001.🤖 Generated with Claude Code