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
1 change: 1 addition & 0 deletions .github/instructions/Compiler.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
49 changes: 49 additions & 0 deletions src/Compilers/CSharp/Test/Symbol/Symbols/Source/MethodTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2576,5 +2576,54 @@ public partial void M<T>() { }
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]);
Copy link
Member

@jcouv jcouv Oct 15, 2025

Choose a reason for hiding this comment

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

By naming the sources when creating the compilation (CreateCompilation([(source, "source"), (source2, "source2")])) it should be easy to observe each location #Closed


Assert.Equal("source1", method.Locations[0].SourceTree.FilePath);
Assert.Equal("source2", implementationPart.Locations[0].SourceTree.FilePath);
}
}
}
16 changes: 13 additions & 3 deletions src/Compilers/Core/Portable/Symbols/ISymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,18 +165,28 @@ public interface ISymbol : IEquatable<ISymbol?>

/// <summary>
/// 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 <c>PartialDefinitionPart</c> and
/// <c>PartialImplementationPart</c> properties on <see cref="IMethodSymbol"/>, <see cref="IPropertySymbol"/>, or
/// <see cref="IEventSymbol"/>.
/// </summary>
ImmutableArray<Location> Locations { get; }

/// <summary>
/// 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).
///
/// <para>
/// 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 <c>PartialDefinitionPart</c> and
/// <c>PartialImplementationPart</c> properties on <see cref="IMethodSymbol"/>, <see cref="IPropertySymbol"/>, or
/// <see cref="IEventSymbol"/>.
/// </para>
///
/// <para>
/// 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
Expand Down