Skip to content

Commit b4cffe6

Browse files
author
Julien Couvreur
authored
Extensions: Do not consider extension block methods as entry points, consider their implementation methods instead (#78667)
1 parent 824116a commit b4cffe6

File tree

3 files changed

+147
-12
lines changed

3 files changed

+147
-12
lines changed

src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1940,7 +1940,14 @@ internal EntryPoint GetEntryPointAndDiagnostics(CancellationToken cancellationTo
19401940
return scriptClass.GetScriptEntryPoint();
19411941
}
19421942

1943-
var mainTypeOrNamespace = globalNamespace.GetNamespaceOrTypeByQualifiedName(mainTypeName.Split('.')).OfMinimalArity();
1943+
var nameParts = mainTypeName.Split('.');
1944+
if (nameParts.Any(n => string.IsNullOrWhiteSpace(n)))
1945+
{
1946+
diagnostics.Add(ErrorCode.ERR_BadCompilationOptionValue, NoLocation.Singleton, nameof(CSharpCompilationOptions.MainTypeName), mainTypeName);
1947+
return null;
1948+
}
1949+
1950+
var mainTypeOrNamespace = globalNamespace.GetNamespaceOrTypeByQualifiedName(nameParts).OfMinimalArity();
19441951
if (mainTypeOrNamespace is null)
19451952
{
19461953
diagnostics.Add(ErrorCode.ERR_MainClassNotFound, NoLocation.Singleton, mainTypeName);
@@ -2157,6 +2164,23 @@ private static void AddEntryPointCandidates(
21572164
ArrayBuilder<MethodSymbol> entryPointCandidates, IEnumerable<Symbol> members)
21582165
{
21592166
foreach (var member in members)
2167+
{
2168+
if (member.GetIsNewExtensionMember())
2169+
{
2170+
// When candidates are collected by GetSymbolsWithName, skeleton members are found but not implementation methods.
2171+
// We want to include the implementation for skeleton methods.
2172+
if (member is MethodSymbol method && method.TryGetCorrespondingExtensionImplementationMethod() is { } implementationMethod)
2173+
{
2174+
addIfCandidate(entryPointCandidates, implementationMethod);
2175+
}
2176+
}
2177+
else
2178+
{
2179+
addIfCandidate(entryPointCandidates, member);
2180+
}
2181+
}
2182+
2183+
static void addIfCandidate(ArrayBuilder<MethodSymbol> entryPointCandidates, Symbol member)
21602184
{
21612185
if (member is MethodSymbol method &&
21622186
method.IsEntryPointCandidate)

src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ internal bool IsEntryPointCandidate
710710
{
711711
get
712712
{
713+
Debug.Assert(!this.GetIsNewExtensionMember());
714+
713715
if (this.IsPartialDefinition() &&
714716
this.PartialImplementationPart is null)
715717
{

src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs

Lines changed: 120 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35607,16 +35607,36 @@ static class E
3560735607
}
3560835608
""";
3560935609
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
35610-
comp.VerifyEmitDiagnostics();
35611-
// Tracked by https://github.com/dotnet/roslyn/issues/76130
35612-
// FindEntryPoint/NameSymbolSearcher skip "E" since it has not declaration named "Main"
35613-
// CompileAndVerify(comp, expectedOutput: "ran");
35610+
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
3561435611

3561535612
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E"));
35616-
comp.VerifyEmitDiagnostics();
3561735613
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
3561835614
}
3561935615

35616+
[Fact]
35617+
public void Validation_EntryPoint_02()
35618+
{
35619+
string source = """
35620+
static class E
35621+
{
35622+
extension(int)
35623+
{
35624+
public static System.Action Main => throw null;
35625+
}
35626+
}
35627+
""";
35628+
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
35629+
comp.VerifyEmitDiagnostics(
35630+
// error CS5001: Program does not contain a static 'Main' method suitable for an entry point
35631+
Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1));
35632+
35633+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E"));
35634+
comp.VerifyDiagnostics(
35635+
// (1,14): error CS1558: 'E' does not have a suitable static 'Main' method
35636+
// static class E
35637+
Diagnostic(ErrorCode.ERR_NoMainInClass, "E").WithArguments("E").WithLocation(1, 14));
35638+
}
35639+
3562035640
[Fact]
3562135641
public void Validation_EntryPoint_03()
3562235642
{
@@ -35625,14 +35645,19 @@ static class E
3562535645
{
3562635646
extension(int i)
3562735647
{
35628-
public static void Main() { System.Console.Write("ran"); }
35648+
public static void Main() => throw null;
3562935649
}
3563035650
}
3563135651
""";
3563235652
var comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E.<>E__0"));
3563335653
comp.VerifyEmitDiagnostics(
3563435654
// error CS1555: Could not find 'E.<>E__0' specified for Main method
3563535655
Diagnostic(ErrorCode.ERR_MainClassNotFound).WithArguments("E.<>E__0").WithLocation(1, 1));
35656+
35657+
Assert.Equal("<>E__0", comp.GetTypeByMetadataName("E").GetTypeMembers().Single().ExtensionName);
35658+
35659+
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : we should find the unspeakable nested type
35660+
Assert.Null(comp.GetTypeByMetadataName("E+<>E__0"));
3563635661
}
3563735662

3563835663
[Fact]
@@ -35647,13 +35672,97 @@ public static void Main() { }
3564735672
}
3564835673
}
3564935674
""";
35650-
// Tracked by https://github.com/dotnet/roslyn/issues/76130
35651-
// FindEntryPoint should not attempt to find types with an empty name
3565235675
var comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E."));
3565335676
comp.VerifyEmitDiagnostics(
35654-
// (3,5): error CS1556: 'E.extension(int)' specified for Main method must be a non-generic class, record, struct, or interface
35655-
// extension(int i)
35656-
Diagnostic(ErrorCode.ERR_MainClassNotClass, "extension").WithArguments("E.extension(int)").WithLocation(3, 5));
35677+
// error CS7088: Invalid 'MainTypeName' value: 'E.'.
35678+
Diagnostic(ErrorCode.ERR_BadCompilationOptionValue).WithArguments("MainTypeName", "E.").WithLocation(1, 1));
35679+
35680+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E. "));
35681+
comp.VerifyEmitDiagnostics(
35682+
// error CS7088: Invalid 'MainTypeName' value: 'E. '.
35683+
Diagnostic(ErrorCode.ERR_BadCompilationOptionValue).WithArguments("MainTypeName", "E. ").WithLocation(1, 1));
35684+
35685+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName(""));
35686+
comp.VerifyEmitDiagnostics(
35687+
Diagnostic(ErrorCode.ERR_BadCompilationOptionValue).WithArguments("MainTypeName", "").WithLocation(1, 1));
35688+
35689+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName(" "));
35690+
comp.VerifyEmitDiagnostics(
35691+
// error CS7088: Invalid 'MainTypeName' value: ' '.
35692+
Diagnostic(ErrorCode.ERR_BadCompilationOptionValue).WithArguments("MainTypeName", " ").WithLocation(1, 1));
35693+
35694+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName(".A"));
35695+
comp.VerifyEmitDiagnostics(
35696+
// error CS7088: Invalid 'MainTypeName' value: '.A'.
35697+
Diagnostic(ErrorCode.ERR_BadCompilationOptionValue).WithArguments("MainTypeName", ".A").WithLocation(1, 1));
35698+
}
35699+
35700+
[Fact]
35701+
public void Validation_EntryPoint_05()
35702+
{
35703+
string source = """
35704+
static class E
35705+
{
35706+
extension(int i)
35707+
{
35708+
public static async System.Threading.Tasks.Task<int> Main() { System.Console.Write("ran"); await System.Threading.Tasks.Task.Yield(); return 0; }
35709+
}
35710+
}
35711+
""";
35712+
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
35713+
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
35714+
35715+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E"));
35716+
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
35717+
}
35718+
35719+
[Fact]
35720+
public void Validation_EntryPoint_06()
35721+
{
35722+
string source = """
35723+
static class E
35724+
{
35725+
extension(string[] args)
35726+
{
35727+
public void Main() { System.Console.Write("ran"); }
35728+
}
35729+
}
35730+
""";
35731+
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
35732+
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
35733+
35734+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E"));
35735+
CompileAndVerify(comp, expectedOutput: "ran").VerifyDiagnostics();
35736+
}
35737+
35738+
[Fact]
35739+
public void Validation_EntryPoint_07()
35740+
{
35741+
string source = """
35742+
static class E
35743+
{
35744+
extension(int i)
35745+
{
35746+
public void Main() => throw null;
35747+
}
35748+
}
35749+
""";
35750+
var comp = CreateCompilation(source, options: TestOptions.DebugExe);
35751+
comp.VerifyEmitDiagnostics(
35752+
// error CS5001: Program does not contain a static 'Main' method suitable for an entry point
35753+
Diagnostic(ErrorCode.ERR_NoEntryPoint).WithLocation(1, 1),
35754+
// (5,21): warning CS0028: 'E.Main(int)' has the wrong signature to be an entry point
35755+
// public void Main() => throw null;
35756+
Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("E.Main(int)").WithLocation(5, 21));
35757+
35758+
comp = CreateCompilation(source, options: TestOptions.ReleaseExe.WithMainTypeName("E"));
35759+
comp.VerifyEmitDiagnostics(
35760+
// (1,14): error CS1558: 'E' does not have a suitable static 'Main' method
35761+
// static class E
35762+
Diagnostic(ErrorCode.ERR_NoMainInClass, "E").WithArguments("E").WithLocation(1, 14),
35763+
// (5,21): warning CS0028: 'E.Main(int)' has the wrong signature to be an entry point
35764+
// public void Main() => throw null;
35765+
Diagnostic(ErrorCode.WRN_InvalidMainSig, "Main").WithArguments("E.Main(int)").WithLocation(5, 21));
3565735766
}
3565835767

3565935768
[Fact]

0 commit comments

Comments
 (0)