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 src/Orleans.CodeGenerator/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
Rule ID | Category | Severity | Notes
--------|----------|----------|--------------------
ORLEANS0109 | Usage | Error | Method has multiple CancellationToken parameters
ORLEANS0110 | Usage | Error | ReferenceAssemblyWithGenerateSerializerDiagnostic
25 changes: 25 additions & 0 deletions src/Orleans.CodeGenerator/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public CompilationUnitSyntax GenerateCode(CancellationToken cancellationToken)
{
foreach (var symbol in asm.GetDeclaredTypes())
{
var containingAssemblyAttributes = symbol.ContainingAssembly.GetAttributes();

if (GetWellKnownTypeId(symbol) is uint wellKnownTypeId)
{
MetadataModel.WellKnownTypeIds.Add((symbol.ToOpenTypeSyntax(), wellKnownTypeId));
Expand Down Expand Up @@ -126,6 +128,29 @@ public CompilationUnitSyntax GenerateCode(CancellationToken cancellationToken)
}
else if (ShouldGenerateSerializer(symbol))
{
// https://learn.microsoft.com/dotnet/api/system.runtime.compilerservices.referenceassemblyattribute
if (containingAssemblyAttributes.Any(attributeData => attributeData.AttributeClass is
{
Name: "ReferenceAssemblyAttribute",
ContainingNamespace:
{
Name: "CompilerServices",
ContainingNamespace:
{
Name: "Runtime",
ContainingNamespace:
{
Name: "System",
ContainingNamespace.IsGlobalNamespace: true
}
}
}
}))
{
// not ALWAYS will be properly processed, therefore emit a warning
throw new OrleansGeneratorDiagnosticAnalysisException(ReferenceAssemblyWithGenerateSerializerDiagnostic.CreateDiagnostic(symbol));
}

if (!Compilation.IsSymbolAccessibleWithin(symbol, Compilation.Assembly))
{
throw new OrleansGeneratorDiagnosticAnalysisException(InaccessibleSerializableTypeDiagnostic.CreateDiagnostic(symbol));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class CanNotGenerateImplicitFieldIdsDiagnostic
{
public const string DiagnosticId = "ORLEANS0106";
public const string DiagnosticId = DiagnosticRuleId.CanNotGenerateImplicitFieldIds;
public const string Title = "Implicit field identifiers could not be generated";
public const string MessageFormat = "Could not generate implicit field identifiers for the type {0}: {reason}";
public const string Category = "Usage";
Expand Down
16 changes: 16 additions & 0 deletions src/Orleans.CodeGenerator/Diagnostics/DiagnosticRuleId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Centralized diagnostic rule IDs for Orleans.CodeGenerator
namespace Orleans.CodeGenerator.Diagnostics;

internal static class DiagnosticRuleId
{
public const string InaccessibleSetter = "ORLEANS0101";
public const string InvalidRpcMethodReturnType = "ORLEANS0102";
public const string UnhandledCodeGenerationException = "ORLEANS0103";
public const string IncorrectProxyBaseClassSpecification = "ORLEANS0104";
public const string RpcInterfaceProperty = "ORLEANS0105";
public const string CanNotGenerateImplicitFieldIds = "ORLEANS0106";
public const string InaccessibleSerializableType = "ORLEANS0107";
public const string GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly = "ORLEANS0108";
public const string MultipleCancellationTokenParameters = "ORLEANS0109";
public const string ReferenceAssemblyWithGenerateSerializer = "ORLEANS0110";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly_Diagnostic
{
public const string DiagnosticId = "ORLEANS0108";
public const string DiagnosticId = DiagnosticRuleId.GenerateCodeForDeclaringAssemblyAttribute_NoDeclaringAssembly;
public const string Title = "Types passed to GenerateCodeForDeclaringAssemblyAttribute must have a declaring assembly";
public const string MessageFormat = "The type {0} provided as an argument to {1} does not have a declaring assembly";
public const string Category = "Usage";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class InaccessibleSerializableTypeDiagnostic
{
public const string RuleId = "ORLEANS0107";
public const string RuleId = DiagnosticRuleId.InaccessibleSerializableType;
public const string Title = "Serializable type must be accessible from generated code";
public const string MessageFormat = "The type {0} is marked as being serializable but it is inaccessible from generated code";
public const string Descsription = "Source generation requires that all types marked as serializable are accessible from generated code. Either make the type public or make it internal and ensure that internals are visible to the generated code.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class InaccessibleSetterDiagnostic
{
public const string RuleId = "ORLEANS0101";
public const string RuleId = DiagnosticRuleId.InaccessibleSetter;
private const string Category = "Usage";
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.InaccessibleSetterTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.InaccessibleSetterMessageFormat), Resources.ResourceManager, typeof(Resources));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class IncorrectProxyBaseClassSpecificationDiagnostic
{
public const string RuleId = "ORLEANS0104";
public const string RuleId = DiagnosticRuleId.IncorrectProxyBaseClassSpecification;
private const string Category = "Usage";
private static readonly LocalizableString Title = "The proxy base class specified is not a valid proxy base class";
private static readonly LocalizableString MessageFormat = "Proxy base class {0} does not conform to requirements: {1}";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class InvalidRpcMethodReturnTypeDiagnostic
{
public const string RuleId = "ORLEANS0102";
public const string RuleId = DiagnosticRuleId.InvalidRpcMethodReturnType;
private const string Category = "Usage";
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.InvalidRpcMethodReturnTypeTitle), Resources.ResourceManager, typeof(Resources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.InvalidRpcMethodReturnTypeMessageFormat), Resources.ResourceManager, typeof(Resources));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class MultipleCancellationTokenParametersDiagnostic
{
public const string DiagnosticId = "ORLEANS0109";
public const string DiagnosticId = DiagnosticRuleId.MultipleCancellationTokenParameters;
public const string Title = "Grain method has multiple parameters of type CancellationToken";
public const string MessageFormat = "The type {0} contains method {1} which has multiple CancellationToken parameters. Only a single CancellationToken parameter is supported.";
public const string Category = "Usage";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Linq;
using Microsoft.CodeAnalysis;

namespace Orleans.CodeGenerator.Diagnostics;

public static class ReferenceAssemblyWithGenerateSerializerDiagnostic
{
public const string DiagnosticId = DiagnosticRuleId.ReferenceAssemblyWithGenerateSerializer;
public const string Title = "[GenerateSerializer] used in a reference assembly";
public const string MessageFormat = "The type {0} is marked with [GenerateSerializer] in a reference assembly";
public const string Description = "The type {0} is marked with [GenerateSerializer] in a reference assembly. Serialization is likely to fail. Options: (1) Enable code generation on the target project directly; (2) Disable reference assemblies using <ProduceReferenceAssembly>false</ProduceReferenceAssembly> in the codegen project; (3) Use a different serializer or create surrogates. See https://aka.ms/orleans-serialization for details.";
public const string Category = "Usage";

private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);

internal static Diagnostic CreateDiagnostic(ISymbol symbol) => Diagnostic.Create(Rule, symbol.Locations.First(), symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class RpcInterfacePropertyDiagnostic
{
public const string DiagnosticId = "ORLEANS0105";
public const string DiagnosticId = DiagnosticRuleId.RpcInterfaceProperty;
public const string Title = "RPC interfaces must not contain properties";
public const string MessageFormat = "The interface {0} contains a property {1}. RPC interfaces must not contain properties.";
public const string Category = "Usage";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Orleans.CodeGenerator.Diagnostics;

public static class UnhandledCodeGenerationExceptionDiagnostic
{
public const string RuleId = "ORLEANS0103";
public const string RuleId = DiagnosticRuleId.UnhandledCodeGenerationException;
private const string Category = "Usage";
private static readonly LocalizableString Title = "An unhandled source generation exception occurred";
private static readonly LocalizableString MessageFormat = "An unhandled exception occurred while generating source for your project: {0} {1}";
Expand Down
5 changes: 4 additions & 1 deletion src/Orleans.CodeGenerator/Orleans.CodeGenerator.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Microsoft.Orleans.CodeGenerator</PackageId>
Expand Down Expand Up @@ -65,4 +65,7 @@
<AssemblyAttribute Remove="Orleans.Metadata.FrameworkPartAttribute"/>
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="Orleans.CodeGenerator.Tests"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<RootNamespace>UnitTests.SerializerExternalModels</RootNamespace>
<AssemblyName>SerializerExternalModels</AssemblyName>
<TargetFrameworks>$(TestTargetFrameworks);netcoreapp3.1</TargetFrameworks>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<SuppressTfmSupportBuildWarnings Condition="'$(TargetFramework)' == 'netcoreapp3.1'">true</SuppressTfmSupportBuildWarnings>
</PropertyGroup>
<ItemGroup>
Expand Down
45 changes: 44 additions & 1 deletion test/Orleans.CodeGenerator.Tests/OrleansSourceGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.Extensions.DependencyInjection;
using Orleans.CodeGenerator.Diagnostics;
using Orleans.Serialization;

namespace Orleans.CodeGenerator.Tests;
Expand All @@ -21,7 +23,7 @@ public class DemoData
public string Value { get; set; } = string.Empty;
}");

[Fact]
[Fact]
public Task TestBasicClassWithFields() => AssertSuccessfulSourceGeneration(
@"using Orleans;

Expand Down Expand Up @@ -503,6 +505,47 @@ public Task<ComplexData> ProcessData(int inputInt, string inputString, ComplexDa
}
}");

[Fact]
public async Task EmitsWarningForGenerateSerializerInReferenceAssembly()
{
var code = """
using Orleans;

namespace TestProject;

[GenerateSerializer]
public class RefAsmType
{
[Id(0)]
public string Value { get; set; } = string.Empty;
}
""";

// The ReferenceAssemblyAttribute marks the assembly as a reference assembly.
// This triggers the Orleans code generator's logic to emit a diagnostic if [GenerateSerializer] is used in such an assembly.
var compilation = await CreateCompilation(code, "TestProject");
var referenceAssemblyAttribute = SyntaxFactory.Attribute(SyntaxFactory.ParseName("System.Runtime.CompilerServices.ReferenceAssemblyAttribute"));
var attrList = SyntaxFactory.AttributeList(SyntaxFactory.SingletonSeparatedList(referenceAssemblyAttribute));
var assemblyAttr = SyntaxFactory.AttributeList(
SyntaxFactory.SingletonSeparatedList(referenceAssemblyAttribute))
.WithTarget(SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword)));
var root = (CSharpSyntaxNode)compilation.SyntaxTrees[0].GetRoot();
var newRoot = ((CompilationUnitSyntax)root).AddAttributeLists(assemblyAttr);
var newTree = compilation.SyntaxTrees[0].WithRootAndOptions(newRoot, compilation.SyntaxTrees[0].Options);

// leave only syntaxTree with the ReferenceAssemblyAttribute
compilation = compilation.RemoveSyntaxTrees(compilation.SyntaxTrees[0]).AddSyntaxTrees(newTree);

var generator = new OrleansSerializationSourceGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(
generators: [generator],
driverOptions: new GeneratorDriverOptions(default));
driver = driver.RunGenerators(compilation);

var result = driver.GetRunResult().Results.Single();
Assert.Contains(result.Diagnostics, d => d.Id == DiagnosticRuleId.ReferenceAssemblyWithGenerateSerializer);
}

private static async Task AssertSuccessfulSourceGeneration(string code)
{
var projectName = "TestProject";
Expand Down