Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
Copy link
Copy Markdown
Contributor

@cston cston Jul 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

Consider also testing these same files, but where source1 is in a separate compilation: testing as a compilation reference and testing as a metadata reference.


[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));
}
}