Skip to content

Commit

Permalink
Provide repro for missing backing field
Browse files Browse the repository at this point in the history
  • Loading branch information
kzu committed Aug 1, 2024
1 parent 68c9301 commit 150f010
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Analyzer/Analyzer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>Latest</LangVersion>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions Analyzer/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.CodeAnalysis;

internal static class Extensions
{
public static IEnumerable<INamedTypeSymbol> GetAllTypes(this Compilation compilation, bool includeReferenced = true)
=> compilation.Assembly
.GetAllTypes()
.OfType<INamedTypeSymbol>()
.Concat(compilation.GetUsedAssemblyReferences()
.SelectMany(r =>
{
if (compilation.GetAssemblyOrModuleSymbol(r) is IAssemblySymbol asm)
return asm.GetAllTypes().OfType<INamedTypeSymbol>();

return Array.Empty<INamedTypeSymbol>();
}));

public static IEnumerable<INamedTypeSymbol> GetAllTypes(this IAssemblySymbol assembly)
=> GetAllTypes(assembly.GlobalNamespace);

static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol)
{
foreach (var typeSymbol in namespaceSymbol.GetTypeMembers())
yield return typeSymbol;

foreach (var childNamespace in namespaceSymbol.GetNamespaceMembers())
{
foreach (var typeSymbol in GetAllTypes(childNamespace))
{
yield return typeSymbol;
}
}
}
}
52 changes: 52 additions & 0 deletions Analyzer/Generator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System.Text;
using Microsoft.CodeAnalysis;

[Generator]
public class Generator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterSourceOutput(context.CompilationProvider, (ctx, compilation) =>
{
var builder = new StringBuilder();
var local = compilation.GetAllTypes().Single(x => x.Name == "Local");
var localMembers = local.GetMembers().Select(x => x.Name).OrderBy(x => x).ToList();

var referenced = compilation.GetUsedAssemblyReferences()
.Select(x => compilation.GetAssemblyOrModuleSymbol(x))
.Where(x => x is IAssemblySymbol)
.Select(x => (IAssemblySymbol)x)
.SelectMany(x => x.GetAllTypes())
.Single(x => x.Name == "External");

var referencedMembers = referenced.GetMembers().Select(x => x.Name).OrderBy(x => x).ToList();
var left = 0;
var right = 0;

builder.Append("// ").Append(local.Name).Append(new string(' ', 40 - local.Name.Length)).AppendLine(referenced.Name);
builder.Append("// ").AppendLine(new string('-', 80));

while (left < localMembers.Count)
{
var x = localMembers[left];
var y = referencedMembers[right];
// print both members if they match
// print only the local member if it's not in the referenced type
// increment variables accordingly
if (x == y)
{
builder.Append("// ").Append(x).Append(new string(' ', 40 - x.Length)).AppendLine(y);
left++;
right++;
}
else
{
builder.Append("// ").Append(x).Append(new string(' ', 40 - x.Length)).AppendLine();
left++;
}
}

ctx.AddSource("RecordMembers.cs", builder.ToString());
});
}
}
8 changes: 8 additions & 0 deletions Analyzer/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"profiles": {
"Roslyn": {
"commandName": "DebugRoslynComponent",
"targetProject": "..\\Library\\Library.csproj"
}
}
}
15 changes: 15 additions & 0 deletions Library/Library.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Analyzer\Analyzer.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\ReferencedLibrary\ReferencedLibrary.csproj" />
</ItemGroup>

</Project>
12 changes: 12 additions & 0 deletions Library/Record.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// #error version

public record Local(int Amount);

public static class Program
{
public static void Main()
{
// Fake consuming the referenced type, to bring it into the GetUsedAssemblyReferences
Console.WriteLine(new External(42).Amount);
}
}
37 changes: 37 additions & 0 deletions RecordBackingField.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35005.142
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReferencedLibrary", "ReferencedLibrary\ReferencedLibrary.csproj", "{233DC959-D567-4DE8-8FA3-224B92CDCA31}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Library", "Library\Library.csproj", "{F75D1708-8126-4104-8A14-8C5322719217}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Analyzer", "Analyzer\Analyzer.csproj", "{6D44E0BF-AE44-43B9-84A6-D60EFF3D9C1A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{233DC959-D567-4DE8-8FA3-224B92CDCA31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{233DC959-D567-4DE8-8FA3-224B92CDCA31}.Debug|Any CPU.Build.0 = Debug|Any CPU
{233DC959-D567-4DE8-8FA3-224B92CDCA31}.Release|Any CPU.ActiveCfg = Release|Any CPU
{233DC959-D567-4DE8-8FA3-224B92CDCA31}.Release|Any CPU.Build.0 = Release|Any CPU
{F75D1708-8126-4104-8A14-8C5322719217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F75D1708-8126-4104-8A14-8C5322719217}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F75D1708-8126-4104-8A14-8C5322719217}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F75D1708-8126-4104-8A14-8C5322719217}.Release|Any CPU.Build.0 = Release|Any CPU
{6D44E0BF-AE44-43B9-84A6-D60EFF3D9C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6D44E0BF-AE44-43B9-84A6-D60EFF3D9C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D44E0BF-AE44-43B9-84A6-D60EFF3D9C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D44E0BF-AE44-43B9-84A6-D60EFF3D9C1A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CF912B90-ABB5-4AC7-AB17-6DF2F8262EDB}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions ReferencedLibrary/Record.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public record External(int Amount);
9 changes: 9 additions & 0 deletions ReferencedLibrary/ReferencedLibrary.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
42 changes: 42 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Repro for missing backing field in referenced project record with primary constructor

See [reported issue](https://github.com/dotnet/roslyn/issues/74634).

## Steps to reproduce

Run the project setting a breakpoint in the generator.
Alternatively, just compile the solution and inspect the generated
code in `\Library\obj\Debug\net8.0\generated\Analyzer\Generator\RecordMembers.cs`
which looks like the following and showcases the missing field symbol:

```
// Local External
// --------------------------------------------------------------------------------
// .ctor .ctor
// .ctor .ctor
// <Amount>k__BackingField
// <Clone>$ <Clone>$
// Amount Amount
// Deconstruct Deconstruct
// EqualityContract EqualityContract
// Equals Equals
// Equals Equals
// get_Amount get_Amount
// get_EqualityContract get_EqualityContract
// GetHashCode GetHashCode
// op_Equality op_Equality
// op_Inequality op_Inequality
// PrintMembers PrintMembers
// set_Amount set_Amount
// ToString ToString
```

Both record definitions are the same except for the name:

```csharp
// Library
public record Local(int Amount);

// External
public record External(int Amount);
```

0 comments on commit 150f010

Please sign in to comment.