Skip to content

Commit

Permalink
Allow File-local type to shadow a namespace declaration in a differen…
Browse files Browse the repository at this point in the history
…t file (#69254)
  • Loading branch information
RikkiGibson authored Jul 28, 2023
1 parent 4d5d8f5 commit f5b6c71
Show file tree
Hide file tree
Showing 2 changed files with 122 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,8 @@ private static void CheckMembers(NamespaceSymbol @namespace, Dictionary<ReadOnly

switch (nts, other)
{
case ({ } left, SourceMemberContainerTypeSymbol right) when isFileLocalTypeInSeparateFileFrom(left, right) || isFileLocalTypeInSeparateFileFrom(right, left):
case ({ } left, SourceMemberContainerTypeSymbol right) when isFileLocalTypeInSeparateFileFrom(right, left):
case ({ } left1, NamespaceOrTypeSymbol right1) when isFileLocalTypeInSeparateFileFrom(left1, right1):
// no error
break;
case ({ IsFileLocal: true }, _) or (_, SourceMemberContainerTypeSymbol { IsFileLocal: true }):
Expand Down Expand Up @@ -373,20 +374,24 @@ private static void CheckMembers(NamespaceSymbol @namespace, Dictionary<ReadOnly
}
}

static bool isFileLocalTypeInSeparateFileFrom(SourceMemberContainerTypeSymbol possibleFileLocalType, SourceMemberContainerTypeSymbol otherSymbol)
static bool isFileLocalTypeInSeparateFileFrom(SourceMemberContainerTypeSymbol possibleFileLocalType, NamespaceOrTypeSymbol otherSymbol)
{
if (!possibleFileLocalType.IsFileLocal)
{
return false;
}

var leftTree = possibleFileLocalType.MergedDeclaration.Declarations[0].Location.SourceTree;
if (otherSymbol.MergedDeclaration.NameLocations.Any((loc, leftTree) => (object)loc.SourceTree == leftTree, leftTree))
if (otherSymbol is SourceNamedTypeSymbol { MergedDeclaration.NameLocations: var typeNameLocations })
{
return false;
return !typeNameLocations.Any(static (loc, leftTree) => (object)loc.SourceTree == leftTree, leftTree);
}
else if (otherSymbol is SourceNamespaceSymbol { MergedDeclaration.NameLocations: var namespaceNameLocations })
{
return !namespaceNameLocations.Any(static (loc, leftTree) => (object)loc.SourceTree == leftTree, leftTree);
}

return true;
throw ExceptionUtilities.UnexpectedValue(otherSymbol);
}
}

Expand Down
112 changes: 112 additions & 0 deletions src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5004,4 +5004,116 @@ partial class C : I<FI>
var verifier = CompileAndVerify(new[] { (source0, "F0.cs"), (source1, "F1.cs"), (source2, "F2.cs") }, expectedOutput: "12");
verifier.VerifyDiagnostics();
}

[Fact]
public void ShadowNamespace_01()
{
var source1 = """
namespace App.Widget
{
class Inner { }
}
""";

var source2 = """
namespace App
{
file class Widget { }
}
""";

var comp = CreateCompilation(new[] { (source1, "File1.cs"), (source2, "File2.cs") });
comp.VerifyDiagnostics();

comp = CreateCompilation(source1 + source2);
comp.VerifyDiagnostics(
// (7,16): error CS9071: The namespace 'App' already contains a definition for 'Widget' in this file.
// file class Widget { }
Diagnostic(ErrorCode.ERR_FileLocalDuplicateNameInNS, "Widget").WithArguments("Widget", "App").WithLocation(7, 16));

comp = CreateCompilation(source2 + source1);
comp.VerifyDiagnostics(
// (3,16): error CS9071: The namespace 'App' already contains a definition for 'Widget' in this file.
// file class Widget { }
Diagnostic(ErrorCode.ERR_FileLocalDuplicateNameInNS, "Widget").WithArguments("Widget", "App").WithLocation(3, 16));
}

[Theory, CombinatorialData]
public void ShadowNamespace_02(bool useMetadataReference)
{
var source1 = """
namespace App.Widget
{
public class Inner { }
}
""";

var source2 = """
namespace App
{
file class Widget { }
}
""";

var comp1 = CreateCompilation(new[] { (source1, "File1.cs") });
comp1.VerifyEmitDiagnostics();

var comp2 = CreateCompilation(new[] { (source2, "File2.cs") }, references: new[] { useMetadataReference ? comp1.ToMetadataReference() : comp1.EmitToImageReference() });
comp2.VerifyEmitDiagnostics();

comp2 = CreateCompilation(new[] { (source2, "File2.cs") });
comp2.VerifyEmitDiagnostics();

comp1 = CreateCompilation(new[] { (source1, "File1.cs") }, references: new[] { useMetadataReference ? comp2.ToMetadataReference() : comp2.EmitToImageReference() });
comp1.VerifyEmitDiagnostics();
}

[Fact]
public void ShadowNamespace_03()
{
var source1 = """
namespace App.Widget
{
class Inner { }
}
class C1
{
static void M1()
{
new App.Widget(); // 1
new App.Widget.Inner();
}
}
""";

var source2 = """
namespace App
{
file class Widget { }
}
class C2
{
static void M2()
{
new App.Widget();
new App.Widget.Inner(); // 2
}
}
""";

var comp = CreateCompilation(new[] { (source1, "File1.cs"), (source2, "File2.cs") });
comp.VerifyDiagnostics(
// File1.cs(10,13): error CS0118: 'App.Widget' is a namespace but is used like a type
// new App.Widget(); // 1
Diagnostic(ErrorCode.ERR_BadSKknown, "App.Widget").WithArguments("App.Widget", "namespace", "type").WithLocation(10, 13),
// File2.cs(11,24): error CS0426: The type name 'Inner' does not exist in the type 'Widget'
// new App.Widget.Inner(); // 2
Diagnostic(ErrorCode.ERR_DottedTypeNameNotFoundInAgg, "Inner").WithArguments("Inner", "App.Widget").WithLocation(11, 24));
}
}

0 comments on commit f5b6c71

Please sign in to comment.