diff --git a/.github/instructions/Compiler.instructions.md b/.github/instructions/Compiler.instructions.md index e9e40e2b3dcd3..770a07247dfb5 100644 --- a/.github/instructions/Compiler.instructions.md +++ b/.github/instructions/Compiler.instructions.md @@ -60,6 +60,7 @@ dotnet run --file eng/generate-compiler-code.cs - **Unit tests**: Test individual compiler phases (lexing, parsing) - **Compilation tests**: Create `Compilation` objects and verify symbols/diagnostics - **Cross-language patterns**: Many test patterns work for both C# and VB with minor syntax changes +- **Keep tests focused**: Avoid unnecessary assertions. Tests should do the minimal work necessary to get to the core assertions that validate the issue being addressed. For example, use `Single()` instead of checking counts and then accessing the first element. ## Debugger Integration diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs index 7a04fd7df3dd7..f6b4b5fa76627 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs @@ -2576,5 +2576,54 @@ public partial void M() { } Assert.False(partialImpl.IsPartialDefinition); Assert.False(partialImplConstructed.IsPartialDefinition); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/22598")] + public void PartialMethodsLocationsAndSyntaxReferences() + { + var source1 = """ + namespace N1 + { + partial class C1 + { + partial void PartialM(); + } + } + """; + + var source2 = """ + namespace N1 + { + partial class C1 + { + partial void PartialM() { } + } + } + """; + + var comp = CreateCompilation([(source1, "source1"), (source2, "source2")]); + comp.VerifyDiagnostics(); + + var method = (IMethodSymbol)comp.GetSymbolsWithName("PartialM").Single(); + + // For partial methods, Locations and DeclaringSyntaxReferences contain only one location + Assert.Equal(1, method.Locations.Length); + Assert.Equal(1, method.DeclaringSyntaxReferences.Length); + + // The single location is the definition part + Assert.True(method.IsPartialDefinition); + Assert.Null(method.PartialDefinitionPart); + Assert.NotNull(method.PartialImplementationPart); + + // To get all locations, you need to use PartialImplementationPart + var implementationPart = method.PartialImplementationPart; + Assert.Equal(1, implementationPart.Locations.Length); + Assert.Equal(1, implementationPart.DeclaringSyntaxReferences.Length); + + // Verify the locations are different + Assert.NotEqual(method.Locations[0], implementationPart.Locations[0]); + + Assert.Equal("source1", method.Locations[0].SourceTree.FilePath); + Assert.Equal("source2", implementationPart.Locations[0].SourceTree.FilePath); + } } } diff --git a/src/Compilers/Core/Portable/Symbols/ISymbol.cs b/src/Compilers/Core/Portable/Symbols/ISymbol.cs index 1c461b0825233..b7f735d0d0b7f 100644 --- a/src/Compilers/Core/Portable/Symbols/ISymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/ISymbol.cs @@ -165,18 +165,28 @@ public interface ISymbol : IEquatable /// /// Gets the locations where the symbol was originally defined, either in source or - /// metadata. Some symbols (for example, partial classes) may be defined in more than one - /// location. + /// metadata. Some symbols (for example, partial types such as classes, structs, and interfaces) may be defined in more than one + /// location. Note that for partial members (such as methods, properties, and events), this property returns + /// only one location. To get all locations for a partial member, use the PartialDefinitionPart and + /// PartialImplementationPart properties on , , or + /// . /// ImmutableArray Locations { get; } /// /// Get the syntax node(s) where this symbol was declared in source. Some symbols (for example, - /// partial classes) may be defined in more than one location. This property should return + /// partial types such as classes, structs, and interfaces) may be defined in more than one location. This property should return /// one or more syntax nodes only if the symbol was declared in source code and also was /// not implicitly declared (see the IsImplicitlyDeclared property). /// /// + /// Note that for partial members (methods, properties, events), this property returns only one + /// syntax node. To get all syntax nodes for a partial member, use the PartialDefinitionPart and + /// PartialImplementationPart properties on , , or + /// . + /// + /// + /// /// Note that for namespace symbol, the declaring syntax might be declaring a nested namespace. /// For example, the declaring syntax node for N1 in "namespace N1.N2 {...}" is the entire /// NamespaceDeclarationSyntax for N1.N2. For the global namespace, the declaring syntax will